本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは、普段ははてなでPerlで書かれたWebサービスを運用している@y_uuk1こと坪内佑樹さんで、テーマは「Docker によるPerlのWebアプリケーション開発」です。Dockerを用いてPerlのWebアプリケーション開発環境を構築するメリットやノウハウを紹介していきます。
本稿のサンプルコードは、WEB+DB PRESS Vol.88のサポートサイト から入手できます。
Dockerとは何か
本題であるPerlのWebアプリケーションとDockerの話に入る前に、Dockerを初めて触る人のために、Dockerそのものについて簡単に紹介します[1] 。
最近はDockerの話を頻繁に見聞きする一方、「 結局、何を目的として、どのような方法で、何をするものなのかがよくわからない」という話も耳にします。「 Dockerはコンテナ仮想化技術の一つである」という説明を聞いたことがあると思いますが、これは、「 何を目的として」「 どのような方法で」「 何をするものなのか」という3つの疑問のうち、「 どのような方法で」の一部を答えたものになります。3つの疑問すべてへの答えは、「 Dockerは、あらゆる環境でアプリケーションを実行することを目的として、コンテナ仮想化技術を用いてアプリケーションの実行環境を構築し、配布するためのプラットフォームである」というのが筆者の理解です。
あらゆる環境でアプリケーションを実行する難しさ
あらゆる環境でアプリケーションを実行するのは、とても難しいことです。アプリケーションは、たいていさまざまなソフトウェアに依存します。動作させたいアプリケーションが依存しているソフトウェアが、さらに別のソフトウェアに依存していることもあります。これは「依存地獄」とも呼ばれます。
Web開発の現場でよく起こる具体的なケースを考えてみましょう。MySQLを扱うためのCPANモジュールであるDBD::mysqlを、Carton でインストールすることを考えます。Cartonは宮川達彦さん 作のCPANモジュールの依存関係管理ツールです。
DBD::mysql自体のバージョンはCartonが管理します。しかし、実際にはDBD::mysqlはMySQLのクライアントライブラリであるlibmysqlclientに依存します。libmysqlclientはCPANモジュールではないため、Cartonではインストールできません。libmysqlclientをインストールするためには、yumやaptなどのパッケージマネージャを利用するか、ソースコードからビルドすることになるでしょう。パッケージマネージャを利用する場合も、使用したいバージョンまたはディストリビューションのパッケージが配布されていないことがあり、自前でパッケージをビルドすることもあります。ソースコードからビルドするにせよ、パッケージを自前でビルドするにせよ、libmysqlclientが依存しているソフトウェアをすべてそろえた環境でビルドしなければなりません。
このように、言語側で専用のモジュール管理のしくみを持っていたとしても、実際にはC言語で書かれたライブラリに依存することはよくあります。依存するソフトウェアが多く依存地獄に陥ると、目的の環境でアプリケーションを実行することが難しくなります。
コンテナ仮想化技術とは何か
Dockerは、コンテナ仮想化技術を利用して、同じOS上に複数のアプリケーションの実行環境を構築することを容易にしています。Docker自体がコンテナ仮想化技術であるというよりは、Dockerは手段としてコンテナ仮想化技術を用いているというのがより正確な表現でしょう。
Dockerはコンテナ仮想化技術のうち、Linuxコンテナを用いています。Linuxコンテナは、Linuxカーネルの名前空間の機能により、プロセスをグループ化して、コンテナという隔離された空間を作成します。名前空間とは、ホスト名、プロセスID、ユーザID、ネットワークインタフェース、ルーティングテーブルなどのカーネルが持つリソースを隔離するためのLinuxカーネルの機能です。つまりコンテナの実体はプロセスグループであり、ハードウェアをエミュレートするVM(Virtual Machine 、仮想マシン)型の仮想化技術とはしくみが大きく異なります。
コンテナを用いることにより、アプリケーション単位で隔離された環境を作成できます。したがって、複数のアプリケーションが同じソフトウェアに依存することを避けやすくなり、依存地獄を解決できます。
コンテナ仮想化技術について詳しくは、gihyo.jpの連載「LXCで学ぶコンテナ入門 」を参照してください。
アプリケーションの実行環境を構築し、配布する
Dockerはアプリケーションの実行環境を丸ごとイメージとして構築し、構築したイメージを配布できます。構築したイメージは、Dockerの動作している環境であればどこでも実行できます。
ポイントは、実行環境を丸ごとイメージとして構築しているところです。Perlの例を再び用いると、Cartonが管理するCPANモジュール以外に、CPANモジュールが依存しているC言語ライブラリなどの環境もまとめて1つのイメージに閉じ込めるという発想です。Dockerは、実行環境を丸ごとイメージとして構築するということと、先述したイメージをコンテナとして実行するという2点の工夫により、環境に依存せずにアプリケーションを実行できるようにしています。
さらに、構築したイメージを配布するためのプラットフォームとして、Docker Hub があります。Docker HubにDockerイメージを登録しておくと、誰もがそのDockerイメージを再利用できます。Docker HubはWeb上に公開されたSaaS(Software as a Service )型のシステムです。プライベートな環境でDockerイメージを配布するためにはDistribution を用います。
Dockerの基本的な使い方
本節では、Dockerの具体的な使い方を紹介します。Dockerにはさまざまな機能がありますが、常用する機能はさほど多くありません。ここでは、筆者がよく使用する機能を重点的に解説します。
Dockerの使い方の流れ
Dockerの使い方の流れの基本は以下になります。
① Dockerイメージの取得または構築
② Dockerコンテナの起動
③ Dockerコンテナの停止・削除
順に見ていきます。
Dockerイメージの取得
まず、Dockerイメージを取得する方法について説明します。
Docker Hubから構築済みのイメージを取得するには、docker pull
コマンドを使います。たとえば次のコマンドにより、MySQLのイメージを取得します。
mysqlイメージをダウンロード
Dockerイメージの構築
次に、Dockerイメージを構築する方法について説明します。
dockerイメージを自前で構築するためには、Dockerfileを利用するのが簡単です。例として、nginxのDockerイメージを構築するためのDockerfileを示します(リスト1 ) 。
リスト1 nginx用のDockerfile
FROM debian
RUN apt-get update -yq && \
apt-get install -yq --force-yes nginx
RUN echo "\ndaemon off;" >> \
/etc/nginx/nginx.conf
CMD ["nginx"]
EXPOSE 80
Dockerfileには、基本的にシェルコマンドを1行ずつ書くだけです。
まず、FROM
命令にベースとなるイメージを指定します。リスト1では、Docker公式のDebian GNU/Linuxのイメージを指定しています。
次に、RUN
命令にシェルコマンドを指定します。リスト1では、aptリポジトリの更新とnginxパッケージのインストールを行い、起動時にデーモン化しないようにするオプションをnginx.conf
に指定しています。Dockerを使用する場合はこのように、nginxのようなデーモンとして動作させるタイプのアプリケーションでもアプリケーション自体はデーモン化せずに、docker run
のオプション引数として-d
を指定することにより、Dockerコンテナをデーモン化します。
基本的にFROM
命令とRUN
命令の2つだけで、たいていのことはできます。
ほかにも、CMD
命令には、docker run
の引数のコマンドを省略したときのデフォルトのコマンドを指定しておけます。EXPOSE
命令には、docker run
のオプション引数-p
を省略したときのデフォルトの公開ポート番号を指定できます。
なお、Docker公式ドキュメントの「Best practices for writing Dockerfiles 」にDockerfileの書き方のベストプラクティスが紹介されています。より詳細なDockerfileの書き方についてはこちらを参照してください。
Dockerイメージのビルド
DockerfileからDockerイメージをビルドするには、docker build
コマンドを用います。Dockerfileのパスと同一ディレクトリにて、次のコマンドを実行します。
nginxイメージのビルド
$ docker build -t mynginx .
-t
オプションによりDockerイメージにタグ名を付けておくと、あとあと参照するために便利です。
Dockerfileのビルドキャッシュ
docker build
を実行するときに、Dockerfileの各命令を毎回最初から実行するのは時間がかかります。そこで、DockerfileによるDockerイメージのビルドには、デフォルトでキャッシュ機構が提供されています。
Dockerfileによるイメージのビルドでは、Dockerfileの各命令ごとに中間イメージが作成されます。そしてDockerイメージのビルドを高速化するために、中間イメージをキャッシュとして利用します。具体的には、前回のdocker build
の実行からDockerfileの内容が変化した部分を検出し、検出した命令の直前の命令までは、中間イメージを再利用してビルドをスキップします。
Dockerコンテナの起動
Dockerイメージの取得または構築ができたら、docker run
コマンドにより、DockerイメージからDockerコンテナを起動します。先ほど作成したmynginx
タグの付いたDockerイメージから、コンテナを起動してみましょう。
nginxコンテナの起動
$ docker run -d -p 8000:80 -t mynginx
先述したように、nginxのようなデーモンとして動作させるタイプのアプリケーションには-d
オプションを付けます。また-p
オプションにより、ホスト側とコンテナ側のポートのマッピングを指定します。-p8000:80
の場合は、コンテナ側の80番ポートに対して、ホスト側の8000番ポートからアクセスできます。
curlコマンドやブラウザからlocalhostの8000番にアクセスして、ページが表示されれば成功です。
コンテナ内のnginxの起動確認
$ curl http://localhost:8000
Dockerコンテナの停止・削除
起動したDockerコンテナを停止するときは、docker stop
コマンドまたはdocker kill
コマンドを使います。stopとkillでは、停止のためのシグナルが異なります。stopの場合は、まずSIGTERMシグナルを送ります。SIGTERMシグナルで停止しなければ、一定時間経過後にSIGKILLシグナルを送ります。killの場合は、最初からSIGKILLを送ります。安全にコンテナを停止させたければstop、すばやくコンテナを停止させたい場合はkillを使うとよいでしょう。
停止させる場合は、停止させるコンテナのIDを指定する必要があります。docker ps
コマンドにより、コンテナIDを含んだ起動中のコンテナの情報を一覧できます。
起動中のコンテナの一覧表示
コンテナの停止
コンテナの強制停止
Dockerコンテナ内部にログインする方法
Dockerを用いて開発していると、Dockerコンテナ内部にログインしたいことがたびたびあります。docker exec
コマンドを使うと、起動中のコンテナの内部に入れます。この方法により、たとえばsshdなどの余分なサービスを起動せずに、手軽にコンテナにログインできます。
起動中のコンテナの内部に入る
$ docker exec -i -t <コンテナID> /bin/bash
<続きの(2)はこちら >