Perl Hackers Hub

第34回 DockerによるPerlのWebアプリケーション開発(2)

この記事を読むのに必要な時間:およそ 4.5 分

PerlのWebアプリケーションをDocker化する方法

メインとなる本節では,PerlのWebアプリケーションをDocker化する方法について紹介します。

PerlがインストールされたDockerイメージの構築

まずは,PerlがインストールされたDockerイメージが必要です。Perl 5.20.2に加え,いずれのプロジェクトでも必要になるcpanminusとCartonをインストールするためのDockerfileの例をリスト2に示します。

リスト2 Perl用のDockerfile

FROM debian:wheezy

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update -yq && \
    apt-get install -yq --no-install-recommends \
    build-essential \
    curl \
    ca-certificates \
    tar \
    bzip2 \
    patch && \
    apt-get clean && \
    rm -rf /var/cache/apt/archives/* && \
    rm -rf /var/lib/apt/lists/*

ENV PERL_VERSION 5.20.2
ENV PATH /opt/perl-$PERL_VERSION/bin:$PATH
ENV PERL_CARTON_PATH /cpan

RUN curl -sL http://git.io/perl-build > \
    /usr/bin/perl-build
RUN chmod +x /usr/bin/perl-build
RUN perl-build $PERL_VERSION /opt/perl-$PERL_VERSION
RUN curl -sL http://cpanmin.us/ | \
    /opt/perl-$PERL_VERSION/bin/perl - \
    --notest App::cpanminus Carton

リスト2では,ベースのイメージとしてdebian:wheezyを選んでいます。Debian GNU/Linuxは初期のイメージサイズが比較的小さいため,特別な理由がなければベースのイメージとして選択することをお勧めします。また,Perlのビルドにはperl-buildを用いています。

Dockerfileと同じディレクトリで次のコマンドを実行すると,Perl入りのDockerイメージを構築できます。

Perlのイメージのビルド

$ docker build -t perl-5.20.2 .

本当にPerlがインストールされているかを確認するために,コンテナを起動してみましょう。次のコマンドにより,先ほどのDockerイメージからコンテナを起動し,bashプロセスを立ち上げ,コンテナの中に入り,perl --versionを実行します。

Perlのイメージ内に入る

$ docker run --rm -i -t perl-5.20.2 /bin/bash
root@0bf912ff086d:/# perl --version

-i-tオプションにより,シェルによるインタラクティブな操作が可能になります。

PerlのWebアプリケーション向けのDockerfileの書き方

続いて,PerlのWebアプリケーション向けのDockerfileの書き方を紹介します。

Dockerfileの書き方は,プロジェクトのソースコードとCPANモジュールをDockerイメージ内に取り込むかどうかにより異なります。まずソースコードとCPANモジュールをイメージ内に取り込む方法を説明し,次にイメージ内に取り込まない方法を紹介します。ここでは,前者をBundled Container方式,後者をRuntime Container方式と呼ぶことにします注2⁠。

注2)
筆者独自の命名です。
Bundled Container方式

Bundled Container方式のDockerfileをリスト3に示します。リスト2のDockerfileでビルドしたPerl入りのDockerイメージをFROMに指定しています。FROMによる継承を使わずに,リスト2に続けて,リスト3の内容を書いたDockerfileを用意してもよいでしょう。

リスト3 Bundled Container方式のDockerfile

FROM perl-5.20.2

RUN apt-get update && \
    apt-get install -yqq --no-install-recommends \
    mysql-client-5.5 \
    libmysqlclient-dev \
    libssl-dev && \
    apt-get clean && \
    rm -rf /var/cache/apt/archives/* && \
    rm -rf /var/lib/apt/lists/*

ENV APPROOT /code
RUN mkdir -p $APPROOT
WORKDIR /code

COPY cpanfile $APPROOT/cpanfile
RUN carton install
COPY ./ $APPROOT

EXPOSE 5000
CMD ["carton","exec","plackup","-a","script/local-server"]

リスト3ではまず,libmysqlclient-devなどのCPANモジュールが依存するパッケージをインストールします。プロジェクト内のcpanfileの内容によって,ここでインストールすべきパッケージは異なります。

次に,Webアプリケーションコードを配置するディレクトリを作成します。/codeディレクトリ以下にWebアプリケーションコードが配置されるようにします。これ以降は/codeディレクトリ以下で作業するため,WORKDIRにより,カレントディレクトリを変更します。

続いて,モジュールをインストールするためにcarton installを実行します。ポイントは,carton installの実行より先に,COPY命令によりcpanfileをDockerイメージ内に取り込んでいるところです。単純に考えれば,次のようにCOPY命令でWebアプリケーションコードをすべて取り込み,carton installを実行すれば済みます。

COPY ./ $APPROOT
RUN carton install

しかし,COPY命令は取り込むディレクトリ以下の内容が変化するとビルドキャッシュが無効になり,COPY命令以下の命令はスキップされません。上記ではWebアプリケーションコードを変更するたびにcarton installが一から実行されるため,開発効率が低下します。そこでリスト3のように先にcpanfileだけ取り込むことにより,cpanfileが変更されたときのみ,carton installを実行するようにします。これはHow to Skip Bundle Install When Deploying a Rails App to Docker if the Gemfile Hasn't Changedで紹介されていたテクニックです。

最後に,CMD命令によりplackupを実行し,Webアプリケーションを起動します。CMD命令の内容は,docker runコマンドの引数により上書きできます。したがって,本番やステージングなどの環境に合わせて,ワーカ数を変化させて起動できます。

次のコマンドにより,リスト3のDockerfileをビルドし,コンテナを起動します。plackupはデフォルトで5000番ポートをListenするので,-p 5000:5000を指定して,コンテナの外から5000番ポートでアクセスできるようにします。

リスト3のDockerfileのビルド

$ docker build -t docker-sample .

Dockerコンテナの起動

$ docker run -d -p 5000:5000 docker-sample
Runtime Container方式

Runtime Container方式では,WebアプリケーションコードとCPANモジュールをDockerイメージ内に持ちません。代わりに,DockerのData Volume機構によりホスト側のファイルシステム上のディレクトリをマウントして,コンテナ内から参照できるようにします。

リスト4にRuntime Container方式のDockerfileを示します。リスト3との違いは,COPY命令によるWebアプリケーションコードの取り込みとcarton installによるモジュールのインストールが省かれている点,そしてVOLUME命令により/code/cpanをData Volumeとして指定している点です。

リスト4 Runtime Container方式のDockerfile

FROM perl-5.20.2

RUN apt-get update && \
    apt-get install -yqq --no-install-recommends \
    mysql-client-5.5 \
    libmysqlclient-dev \
    libssl-dev && \
    apt-get clean && \
    rm -rf /var/cache/apt/archives/* && \
    rm -rf /var/lib/apt/lists/*

RUN mkdir -p /code

EXPOSE 5000
VOLUME ["/code", "/cpan"]
WORKDIR /code

まず,次のコマンドでリスト4のDockerfileをビルドします。

リスト4のDockerfileのビルド

$ docker build -t docker-sample-runtime .

Runtime Container方式はあくまでPerlの実行環境のみを提供しています。リスト4のDockerfileをビルドしただけでは,carton installは実行されません。

したがって,次のコマンドでコンテナを起動してcarton installを実行する必要があります。docker runには-vオプションを付けて,ホスト側のディレクトリをマウントしています。さらに--rmオプションを付けることで,コマンドの実行の終了と同時にコンテナを破棄します。

リスト4のDockerfileのビルド

$ docker run --rm -v ./:/code -v ./local:/cpan docker-samp le-runtime carton install

最後に,docker runによりPlackサーバを起動します。

Dockerコンテナの起動

$ docker run -d -p 5000:5000 -v ./:/code -v ./local:/cpan docker-sample-runtime carton exec plackup -a script/localserver
両方式の比較

Dockerの思想の一つに,ローカル開発環境で動作実績のあるコンテナをそのまま本番環境まで持っていくというものがあります。Bundled Container方式のメリットは,Dockerイメージ内にWebアプリケーションの動作に必要なものがすべて入っていることです。Bundled Container方式であれば,手元でビルドしたイメージをDocker HubやDistributionにpushして,本番環境からpullするというフローを作りやすいと思います。

しかしリスト3のDockerfileは,Webアプリケーションコードを変更するたびにdocker buildを実行しなければなりません。いくらcarton installをスキップできるとはいえ,ローカル開発環境に導入するには少々面倒です。ほかにもGitのブランチごとにWebアプリケーションコードが異なるケースでのDockerイメージの管理などを考えると,どんどん新しい課題が出てきます。もともとやりたかったことはソフトウェアの依存関係の解決ですので,ローカル環境と本番環境で同じランタイム上でWebアプリケーションを実行できれば十分だと思います。

そこで,Runtime Container方式はOSとPerlの実行環境のみを提供することにより,変化の激しいWebアプリケーションコードとDockerイメージのビルドを分離できます。副次的なメリットとして,手元のファイルシステムにWebアプリケーションコードとモジュールがあるため,これまでの開発パラダイムを大きく変えないということがあります。現在のWebアプリケーション開発フローを考えると,Dockerに限らずなんらかの成果物のビルドをJenkinsやTravis CIのようなCIサーバに任せるのが自然です。ローカル開発環境からDocker Hubなどにpushせずに,CI環境でBundled Container方式でビルドしてpushするという方法もあると思います。

筆者は今のところ,特にDockerの導入初期には,パラダイムをあまり変えないRuntime Container方式を採用するのがよいと考えています。以降の説明では,Runtime Container方式の採用を前提とします。

<続きの(3)こちら。>

著者プロフィール

坪内佑樹(つぼうちゆうき)

京都出身京都在住。2013年に株式会社はてなに入社。

WebオペレーションエンジニアとしてMackerel,はてなブログを始めとしたWebサービスのサーバ・ネットワーク構築と運用を担当する。

計算機システムのしくみと性能,サーバオペレーションの自動化について関心がある。

Twitter:@y_uuk1

Blog:http://yuuki.hatenablog.com