DockerはLinuxコンテナの内部でアプリケーションを動かすためのオープンソースツールで、軽量な仮想マシンの一種です。アプリケーションを実行するだけでなく、コンテナ化されたアプリケーションをDocker indexという独自のDockerレジストリを通じて配布するツールも提供します。複雑なアプリケーション配置作業を単純化できるのです。
この記事では、複雑なシステムの配置作業で企業が直面している課題を説明し、そして、Dockerがこの課題を解決するための価値あるツールになり得ることを示します。また、ほかの使い方についても説明します。
配置の問題
サーバアプリケーションの配置はますます複雑になっています。いくつかのPerlスクリプトをコピーするだけでインストールが完了する時代は終わりました。今日、ソフトウエアは多くの種類の要求を抱えています。
- インストールするソフトウエアやライブラリの依存物("Python >= 2.6.3とDjango 1.2に依存する")
- 実行するサービスへの依存("MySQL 5.5とRabbitMQのキュー"が必要)
- 特定のOSに対する依存("64-bit Ubuntu Linux 12.04でビルドとテストをした")
- リソースの要件:
- 利用可能なメモリの最少量("1GBのメモリが必要")
- 特定のポートへのバインド("80と443を使う")
例えば、比較的シンプルなアプリケーションの配置を考えてみましょう。Wordpressです。典型的なWordpressのインストールでは、
- Apache 2
- PHP 5
- MySQL
- Wordpressのソースコード
- WordpressのMySQLデータベース。Wordpress向けの構成が必要。
- Apache。次の構成が必要。
- PHPモジュールのロード
- URLリライトの有効化と.htaccessファイル
- DocumentRootをWordpressのソースに向ける
配置してシステムを動かすまでに、次のような問題や困難にぶつかるはずです。
- 分離: このサーバにすでに違うサイトをホストしていて、そのサイトがnginxで動いている場合、WordpressはApacheで動くので、困ったことになります。両方とも80ポートをリッスンしようとします。両方を同じサーバで動かすことは不可能ではありませんが、構成をひねらなければなりません(リッスンするポートを変更する)。ライブラリでも同じような衝突が発生するでしょう。また、既に動作しているアプリケーションがPHP4に依存していたら、厄介です。WordpressはPHP4をサポートしていないのです。そして、PHP4とPHP5を同時に動かすのはとても難しいです。同じサーバで動いているアプリケーションは互いに分離(この場合はファイルシステムやネットワークのレベルで)されていませんので、衝突が起きてしまうのです。
- セキュリティ: 私たちがインストールしているのはWordpressです。最強のセキュリティレベルを誇るソフトウエアではありません。Wordpressをサンドボックス化して、ハッキングされてもほかのアプリケーションが影響を受けないようにできたら素晴らしいです。
- アップグレード、ダウングレード: アプリケーションのアップグレードは既存ファイルの上書きを伴います。アップグレードの最中に何が起きるでしょうか。対象のシステムは停止するのでしょうか。アップグレードが失敗したらどうなるでしょう。失敗していたことがわかったらどうでしょうか。以前のバージョンにロールバックできるのでしょうか。
- スナップショット、バックアップ: 一度すべてをセットアップしたら、システムの"スナップショット"が取れると便利です。スナップショットはバックアップできますし、ほかのサーバに移して、実行できます。可用性を確保するため複製して複数のサーバに配置するのもいいでしょう。
- 再現性: 配置を自動化して運用環境で動作させる前にテスト用のインフラで新しいバージョンのシステムをテストするのは素晴らしいやり方です。このようなやり方は通常Chef、Puppetというようなツールを使い、パッケージを自動的にサーバにインストールして、問題なく動作すれば、運用環境で全く同じ配置スクリプトを実行することで実現します。このやり方は99%うまくいきます。しかし、残りの1%の場合、すなわち、テスト環境への配置と運用環境への配置の間にリポジトリのパッケージが更新されると互換性のパッケージになってしまう可能性があります。その結果、運用環境のセットアップはテスト環境と異なってしまい、問題が発生する可能性が生まれます。配置のほんの小さな側面もコントロールしなければ(例えば、独自のAPTやYUMリポジトリをホストしなければ)、同じシステムを複数の環境に再現するのは難しいです。
- Constrain resources: WordpressのCPU使用がおかしくなって、CPUサイクルをすべて使い尽くしてしまい、ほかのアプリケーションの実行をブロックしてしまったらどうなるでしょう。メモリを使い尽くしてしまったらどうでしょうか。ログの生成が暴走してデスクを使い尽くしてしまったらどうでしょう。こう考えるとアプリケーションが利用できるリソースに制限をかけられると便利そうです。
- 簡単なインストール: there may be DebianやCentOSのパッケージ、あるいはChefのレシピでWordpressの複雑なインストールステップを自動で実行してくれるかもしれません。しかし、これらの仕組みは安定的に動かすには複雑すぎます。というのは、これらの仕組みは対象となるシステムのあり得る構成を考慮しなければならないからです。そして、多くの場合、このような仕組みはクリーンな環境でのみ正常に動作します。
- 簡単なアンインストール: ソフトウエアは簡単にきれいに除去できる必要があります。しかし、アプリケーションの配置は通常、既存の構成ファイルに変更を加え、状態(MySQLのデータやログ)を左右します。したがって、アプリケーションを完全に除去するのは難しいです。
ではこのような問題をどのように解決すればいいでしょうか。
仮想マシン!
個別のアプリケーションを分離された仮想マシンで動作させるとき、例えばAmazonのEC2を使うとき、ほとんどの問題は解決します。
- 分離: ひとつの仮想マシンにひとつのアプリケーションをインストールすることで完全な分離を実現できます。
- 再現性: システムを好きなように準備して、AMIを作成すれば、このAMIを使って好きなだけインスタンスを作成できます。完全に再現性があります。
- セキュリティ 完全に分離されているので、Wordpressのサーバがハッキングされても、ほかのインフラは影響を受けません。SSHのキーを散らかしたり、パスワードを再利用していれば別ですが。でもそんなことしていないですよね。
- リソースの制限: 仮想マシンは一定のCPUサイクル、メモリ、デスクスペースを確保します。この確保したリソース量を超えることはありません。
- 簡単なインストール: アプリケーションの増加はEC2アプライアンスとして利用でき、AWSマーケットプレイスでワンクリックでインスタンス化できます。数分の再起動を必要としますが、それだけです。
- 簡単なアンインストール: アプリケーションが必要なくなったら、仮想マシンを捨てればいいだけです。
- アップグレード、ダウングレード: Netflixが行っていることは新しい仮想マシンを配置するだけです。そして、ロードバランサの向き先を古い仮想マシンから新しい仮想マシンへ向けます。ただし、維持しなければならない状態をローカルに保存している場合はこの方法はうまくいきません。
- スナップショット、バックアップ: EBSのディスクはボタンのクリックでスナップショットが取れます。スナップショットはS3へバックアップされます。
完璧です。
しかし、新しい問題があります。これは高価なやり方です。
- お金: 必要なアプリケーション分だけEC2のインスタンスを立ち上げるほどの金銭的な余裕はありますか。必要なインスタンスサイズを予測できますか。あとで追加のリソースが必要になったら、アップグレードするために仮想マシンを止めなければなりません。また、必要以上にリソースを確保してしまったら、お金を払いすぎてしまうことになります(SolarisのZonesはJoyentと同様動的にリソースをリサイズします)。
- 時間: 仮想マシンに関連するオペレーションは遅いです。起動には数分かかりますし、スナップショットを取るのにも時間がかかります。イメージの作成にも時間がかかります。世界は動いています。このような無駄な時間はありません。
もっと良い方法はないでしょうか。
Dockerを使ってみましょう。
DockerはプラットフォームプロバイダのdotCloudの人々が始めたオープンソースプロジェクトで今年のはじめに始まりました。技術的観点ではDockerは(Goで書かれている)ふたつの既存技術を使いやすくします。
- LXC: Linux Containers。各プロセスを通常のUnixプロセスよりも高いレベルで分離して実行します。この仕組みにはコンテナ化という言葉が使われます。プロセスはコンテナの内部で実行される、と言います。コンテナは次の水準での分離をサポートします。
- AUFS。コピーオンライトなファイルシステムを実現する。
DockerはAUFSをサポートしているカーネルが3.8以上のLinuxにインストールできます。しかし、概念的にはDockerはこれらの技術には依存していません。将来的には類似の技術で動作するようになるかもしれません。例えば、SolarisのZonesやBSD jails、ZFSといった技術です。現時点では前述の条件のLinuxにのみインストールできます。
では、なぜDockerは興味深い技術なのでしょうか。
- まず、Dockerはとても軽量です。仮想マシンを立ち上げるにはリソースがたくさん必要です。しかし、Dockerコンテナを起動するのに必要なCPUとメモリのオーバヘッドは小さく、とても素早く起動できます。通常のプロセスの起動と比べられるくらいです。コンテナの実行も高速で、イメージの作成やファイルシステムのスナップショット取得も同様に高速です。
- また、Dockerはすでに仮想化された環境でも動作します。つまり、EC2インスタンス、Rackspace VM、 VirtualBoxの中で動かすこともできるのです。実際、MacやWindowsでDockerを使うにはVagrantを使うのが有効です。
- そして、DockerコンテナはポータブルでDockerが動くOSになら、どこにでも移動できます。UbuntuであれCentOSであれ、Dockerが動作するなら、コンテナを走らせることができます。
では、前述した配置と運用に関わる問題のリストを振り返って、Dockerがどの程度役に立つかを見てみましょう。
- 分離: Dockerはアプリケーションをファイルシステムやネットワークのレベルで分離します。まるで"本当"の仮想マシンで動作させているようです。
- 再現性: 好きなようにシステムを準備して(ログインしてapt-getですべてのソフトウエアをそろえる、またはDockerfileを使う)、イメージの変更をコミットします。そうすれば好きなだけ、イメージをインスタンス化し、ほかのマシンで全く同じ環境をセットアップできます。
- セキュリティ: Dockerのコンテナは通常のプロセス分離よりも安全です。Dockerチームはいくつかのセキュリティ問題を発見して対処しています。
- リソースの制限: DockerはCPUの利用を一定のCPUサイクルで制限できます。メモリも同様です。ディスクスペースの制限はまだ直接的にはサポートされていません。
- 簡単なインストール DockerにはDocker Indexがあります。これは単一のコマンドでインスタンスかできるDockerイメージのリポジトリです。例えば、Clojure REPLのイメージを使うにはdocker run -t -i zefhemel/clojure-replというコマンドを実行すれば、後は自動的にリポジトリからイメージを取り出して、インスタンス化してくれます。
- 簡単なアンインストール: アプリケーションが必要なくなったらコンテナを破棄すればいいだけです。
- アップグレード、ダウングレード: EC2の仮想マシンと同じです。新しいバージョンのアプリが乗っているインスタンスを起動し、ロードバランサを古い仮想マシンから新しい仮想マシンに向ければいいのです。
- スナップショット、バックアップ: Dockerはイメージのコミットとタグ付けをサポートします。EC2のスナップショットとは違い、一瞬で終わります。
どのように使うか
Dockerをインストールしたとしましょう。Ubuntuコンテナ内でbashを実行するには次のようにします。
docker run -t -i ubuntu /bin/bash
"ubuntu"イメージが既にダウンロードされているか否かによって、DockerはUbuntuのイメージをダウンロードするか、既にローカルにあるイメージをコピーするかします。それから、Ubuntuコンテナ内で/bin/bashを実行します。コンテナ内では、普通のUbuntuのように作業をすることができます。例えば、新しいパッケージをインストールすることもできます。
"hello"をインストールしてみましょう。
$ docker run -t -i ubuntu /bin/bash root@78b96377e546:/# apt-get install hello Reading package lists... Done Building dependency tree... Done The following NEW packages will be installed: hello 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 26.1 kB of archives. After this operation, 102 kB of additional disk space will be used. Get:1 http://archive.ubuntu.com/ubuntu/ precise/main hello amd64 2.7-2 [26.1 kB] Fetched 26.1 kB in 0s (390 kB/s) debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package hello. (Reading database ... 7545 files and directories currently installed.) Unpacking hello (from .../archives/hello_2.7-2_amd64.deb) ... Setting up hello (2.7-2) ... root@78b96377e546:/# hello Hello, world!
いったん、終了してからもう一度Dockerコマンドを実行してみましょう。
root@78b96377e546:/# exit exit $ docker run -t -i ubuntu /bin/bash root@e5e9cde16021:/# hello bash: hello: command not found
何が起きたのでしょうか。helloコマンドはどこへいってしまったのでしょうか。上記のコマンドでは単純に真新しいUbuntuのイメージから新しいコンテナを起動しただけなのです。以前のコンテナで引き続き作業を続けるには、そのコンテナをリポジトリにコミットしなければなりません。では、新しく立ち上げたコンテナを終了し、立ち上げたコンテナのidを見てみましょう。
$ docker ps -a ID IMAGE COMMAND CREATED STATUS PORTS e5e9cde16021 ubuntu:12.04 /bin/bash About a minute ago Exit 127 78b96377e546 ubuntu:12.04 /bin/bash 2 minutes ago Exit 0
docker psコマンドを実行すると、現在実行中のコンテナの一覧が見れます。docker ps -aはすでに終了しているコンテナの一覧が見れます。コンテナはそれぞれユニークなIDを持ちます。一覧にはコンテナが使ったイメージも表示されます。いつ作成されたか、現在の状態、公開しているポートとホストとのポートのマッピングをも表示します。
最初に表示されたのが、"hello"がなかった、後に立ち上げたコンテナで、次が再利用したいと思っているコンテナです。このコンテナをコミットして、このコンテナから新しいコンテナを作成しましょう。
$ docker commit 78b96377e546 zefhemel/ubuntu
356e4d516681
$ docker run -t -i zefhemel/ubuntu /bin/bash
root@0d7898bbf8cd:/# hello
Hello, world!
ここではコンテナをリポジトリにコミットして(IDを使って)います。リポジトリはgitのリポジトリに似ています。タグの名前を付けなかったら(私のように)、"latest"という名前がつきます。ローカルにインストールしたイメージを見るにはdocker imagesを実行します。
Dockerには2、3のイメージ(例えば、ubuntuやcentos)しかありません。自分自身でイメージを作成することができます。ユーザのリポジトリはGithubライクな命名モデルに従います。Dockerのユーザ名はスラッシュとリポジトリ名で構成されます。
さて、上述したのはDockerイメージを作成するひとつの方法です。もっと明確な方法もあります。Dockerfileを使う方法です。
Dockerfileでイメージを作る
Dockerfileはベースのイメージからどのようにイメージを作成するかを記した単純なテキストファイルです。Githubにいくつかあげてあります。次に示す例では、イメージを実行し、SSHサーバをインストールしています。
FROM ubuntu RUN apt-get update RUN apt-get install -y openssh-server RUN mkdir /var/run/sshd RUN echo "root:root" | chpasswd EXPOSE 22
見ての通り、本当に明白なやり方です。FROMコマンドはベースとなるイメージを指定します。上記の例では公式のイメージを指定していますが、先ほど作成したzefhemel/ubuntuを指定することもできます。RUNコマンドはイメージを構成するためのコマンドです。上記の例では、APTパッケージリポジトリをアップデートし、OpenSSHのサーバをインストールして、ディレクトリを作成し、rootアカウントにパスワードを設定しています。EXPOSEコマンドは22ポート(SSHのポート)を外部に公開します。では、このDockerfileをどのようにビルドしてインスタンス化するのか、見てみましょう。
まず、イメージをビルドします。Dockerfileのあるディレクトリで次を実行します。
$ docker build -t zefhemel/ssh .
これによって新しいSSHのイメージとともにzefhemel/sshリポジトリが作成されます。ビルドが成功すれば、次のコマンドでイメージをインスタンス化できます。
$ docker run -d zefhemel/ssh /usr/sbin/sshd -D
これは前のコマンドとは違います。-dによってコンテナがバックグラウンドで動作します。また、bashを動かす代わりに、sshdデーモンを動かしています(フォアグラウンドで動かすには-D)。
実行中のコンテナを確認してみましょう。
$ docker ps ID IMAGE COMMAND CREATED STATUS PORTS 23ee5acf5c91 zefhemel/ssh:latest /usr/sbin/sshd -D 3 seconds ago Up 2 seconds 49154->22
コンテナが起動していることがわかります。PORTSに着目してください。ポート22を公開したのですが、ホストシステムのポート(49154)にマップされています。動作を確認してみましょう。
$ ssh root@localhost -p 49154
The authenticity of host '[localhost]:49154 ([127.0.0.1]:49154)' can't be established.
ECDSA key fingerprint is f3:cc:c1:0b:e9:e4:49:f2:98:9a:af:3b:30:59:77:35.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:49154' (ECDSA) to the list of known hosts.
root@localhost's password: Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.8.0-27-generic x86_64) * Documentation: https://help.ubuntu.com/ The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. root@23ee5acf5c91:~#
成功しました。SSHサーバが実行されており、ログインできることがわかりました。SSHから抜けて、コンテナを停止してみましょう。
$ docker kill 23ee5acf5c91
上述の通り、ポート22はホストの49154にマップされました。しかし、このマップはランダムです。特定のポートにマップするには、次のように-pをつけて実行します。
docker run -p 2222:22 -d zefhemel/ssh /usr/sbin/sshd -D
こうすることで2222にマップされます。また、イメージをユーザフレンドリにするにはDockerfileの最終業に次の1行を加えるといいでしょう。
CMD /usr/sbin/sshd -D
CMDはコマンドをビルドしたときでなく、インスタンス化したときに実行するようにします。引数が追加されなければ、/usr/sbin/sshd -Dを実行します。つまり、次を実行すればいいだけになります。
docker run -p 2222:22 -d zefhemel/ssh
これは前述のコマンドと一緒です。このイメージを公開するにはdocker pushを実行します。
docker push zefhemel/ssh
ログインした後は、前述したdocker runコマンドを使えば誰でも利用できます。
Wordpressの例に戻りましょう。Dockerを使ってどのようにWordpressをコンテナ内で動かせばいいでしょうか。WordpressのイメージをビルドするにはDockerfileを作成する必要があります。
- Apache、PHP5、MySQLをインストールする
- Wordpressをダウンロードし、ファイルシステム上のどこかに展開する
- MySQLデータベースを作成する
- Wordpressの構成ファイルを更新してMySQLデータベースを使うようにする
- WordpressをApacheのDocumentRootにする
- MySQLとApacheを起動する (例えば、supervisordを使う)
幸運なことに多くの人がすでに上記を実施しています。例えば、John Fink氏のgithubのリポジトリにはWordpressのイメージをビルドするのに必要な情報がすべて含まれています。
Dockerの使い道
複雑なアプリケーションを簡単にかつ信頼でき、再現性があるかたちで配置する以外に、Dockerにはほかの使い方もあります。例えば、
- 継続的統合と配置: Dockerコンテナの内部でソフトウエアをビルドすることで、ビルドを明確に分離できます。ビルドされたソフトウエアのイメージは自動的にリポジトリに登録され、テスト環境や運用環境に配置されます。
- Dokku: シンプルなPlatform-as-a-Serviceでたった100行未満のBashで書かれている。
- FlynnとDeisはオープンソースのPlatform-as-a-ServiceプロジェクトでDockerを使っている。
- ディスクトップ環境をDockerで実行する。
- 論理的帰結としてDockerを採用したプロジェクトがCoreOSです。CoreOSは軽量なLinuxディストリビューションで、Dockerを使ってすべてのアプリケーションをインストールし実行します。systemdで管理されます。
Dockerはなんではないか
Dockerは信頼性のある配置作業を実現しますが、必要な機能をすべて備えた配置システムではありません。Dockerもコンテナ内のアプリケーションが動作するのとレベルで動作します。どのコンテナをどのサーバにインストールし、どのように実行するのかはDockerのスコープの範囲外です。
同様に、複数のコンテナや物理サーバ、仮想マシンをまたいで動作するアプリケーションを制御するのもスコープ外です。コンテナに通信させるにはほかのアプリケーションがどのポートやIPで動作しているのかを判別する仕組みが必要です。etcdのようなツールやその他のサービス検出の仕組みが使えるでしょう。
結論
この記事で説明したことは生のままLXC、cgroups、AUFSを使っても実現できることです。しかし簡単でもシンプルでもありません。Dockerは複雑なアプリケーションをコンテナにパッケージングして、簡単にバージョニングして配布できるようにします。その結果、現在広く普及している"本当"の仮想マシンと同等の柔軟さとパワーを軽量なLinuxコンテナで実現できるのです。Macbook Pro上のVagrant VirtualBoxで動作しているDockerで作成したイメージはEC2、Rackspace Cloud 、物理サーバでも動作しますし、その逆も可能です。
Dockerはウェブサイトから無償で入手できます。利用を始めるにはインタラクティブなスタートガイドを使うといいでしょう。プロジェクトのロードマップによれば、運用環境で使える初めてのバージョンは0.8で2013年10月にリリースされる予定。しかし、すでに運用環境でも使われています。
著者について
Zef HemelはLogicBloxのエバンジェリストであり、プロダクトマネジメントチームの一員。LogicBloxは論理プログラミング、特にDatalogをベースにしたアプリケーションサーバやデータベースを開発している。以前は、ブラウザベースのIDEを開発しているCloud9 IDEのVPをつとめていた。90年代からウェブアプリケーションの開発をしており、宣言的プログラミングを強く支持している。