こんにちは、hachi8833です。今回は弊社システム管理者のyamasitaさん監修のもとで、Matt Jaynes氏のDocker Misconceptionsを翻訳いたしました。それなりに文言を最適化してあり、原文と一対一対応しているとは限りませんのでご了承ください。エラーがありましたらお知らせいただけると助かります。
Dockerについてよくある勘違い
Matt Jaynes
元記事: Docker Misconceptions
Dockerは最近のシステム管理業界で大変な脚光を浴びてます。これによるシステム管理の進歩ははかりしれないものがありますが、いくつか重要な点で勘違いしている人を見かけます。
分野を限定して語っているのでよろしく
この記事で説明する内容は、主にWebサービスにおけるミッションクリティカルなシステムのマルチホストセットアップに限定しています。Dockerをそれ以外のところで使う場合、この記事の内容が必ずしも該当するとは限りませんので、その点くれぐれもご注意ください。
Dockerについての基礎知識
この記事では、読者がDockerとその一般的な動作について基本的なことを理解していることを前提にしています。
Dockerそのものについてすべてを説明するのはこの記事の範疇を超えるので、Dockerが何なのかまったくわからない方は以下の記事を読み通してからにしてください。
Dockerとは何か (英語)
Dockerの基礎 (英語)
誤解されがちなこと
Dockerは多くの場面で使用できる素晴らしいツールです。しかしながら、Dockerを恒常的に使ううちに、さまざまな勘違いに遭遇しました。
勘違い: Dockerさえ学べば他のシステム管理ツールを学ぶ必要なし
いつの日かそうなるとよいですね。でも残念ながら、当分そうはいきません。Dockerは高度なシステム管理にこそ使うものであり、何もかもをカバーするものではありません。もちろんDockerは最高にクールでパワフルですが、その分システムがかなり複雑になるのも確かです。Dockerをミッションクリティカルなシステムに適用するのであれば、あなた自身が経験豊富なシステム管理のエキスパートであり、かつ本番環境で安全に運用するために必要なポイントを完璧に把握している必要があります。
現時点では、Dockerを使いこなすには相当なシステム管理の知識が必要です。知識が少なくて済むということはありません。皆様が目にするDockerの紹介記事のほとんどは、事例があまりに単純化されていて、Dockerを本番マルチホスト環境で使用する場合の複雑さから目を背けています。このため、Dockerを本番環境で実際に使用する場合に必要なことがらについて誤った印象を与えています。
Dockerを安全かつ頑健な方法で典型的なマルチホスト本番環境で実行するには、以下のような多くの要素を極めて注意深く管理することが不可欠です。
- セキュリティで保護された、非公開のイメージリポジトリ (index)
- 組織的にコンテナのデプロイをダウンタイムゼロで実行
- 組織的にコンテナのデプロイをロールバックする
- 複数ホスト間のコンテナのネットワーキング
- コンテナログの管理
- コンテナデータの管理(dbなど)
- initやログを正しく扱えるイメージの作成
- 他にもまだまだある
これらをすべて完璧に実行することは不可能ではありません。実際、いくつもの大企業がDockerを本番環境で利用しています。ただしそれは片手間などではなく、全面的に取り組んだからこそできたことです。将来、(Flynn、Dockerコンテナホスティングなどを通じて) Dockerを取り囲むエコシステムが成熟してくればこうした点は改善されるでしょう。しかし現時点では、Dockerを本番環境に真剣に導入するのであれば、それ相応のシステム管理と統合のスキルが要求されます。
この点の理解を助けるために、以下の記事を参照してください。ここでは、これまで筆者が見かけた中で本番環境に近いものをリストアップしています (ただし重要な要素がまだまだ不足していたりします)。
- Easily Deploy Redis Backed Web Apps With Docker
- Integrating Docker with Jenkins for Continuous Deployment of a Ruby on Rails App
- Using Docker with Github and Jenkins for Repeatable Deployments
- Fixing the Docker Ubuntu image on Rackspace Cloud
サーバー管理を今更学びたくないという方は、HerokuのようなPlatform-as-a-Service (PaaS)をお使いください。Dockerはそういう問題を解決するためのものではありません。
勘違い: Dockerコンテナ1つごとに1プロセスだけ持たせればよい
Dockerを、デプロイ可能な単一目的のプロセスではなく、ロールベースの仮想マシンであるとした方が、Dockerの管理は遥かに単純になります。この点を理解することが重要です。たとえば、あなたがinit、cron、sshなどのプロセスごとに’app’ VMを作成し、それと極めてよく似た’app’コンテナをビルドしたいとしましょう。このとき、すべてのプロセスをssh用、cron用、app用、webサーバー用などの個別のコンテナに分担するようなことはしないでください。
「1コンテナ1プロセス」というやり方については、理論上の大論争が繰り広げられていますが、実際にこんな細切れになったものを管理するのはちょっとした悪夢と言えます。「1コンテナ1プロセス」は、極端に大規模な状況で使用するのならまだわかりますが、普通はapp、db、redisなどのロールベースのコンテナで運用したいと思うものです。
この点が納得できない方は、同種の管理上の問題について言及しているMicroservices – Not A Free Lunch!の投稿をお読みください。
勘違い: Dockerを使っていれば設定管理 (CM) ツールは不要
これは部分的には真実です。Dockerを使用している場合、サーバーに対する設定管理の必要性は低くなりますが、サーバーに対してDockerを準備・デプロイ・管理するためのオーケストレーションツールは絶対に必要です。
Ansibleのようなツールはこういうところで使ってこそ輝きを放ちます。Ansibleは基本的にはオーケストレーションツールですが、(たまたま) 設定管理に使うこともできます。これはつまり、ホストサーバーの準備、Dockerコンテナのデプロイ・管理、そしてネットワーキングの管理のすべてのステップをAnsibleでまかなえるということです。
Dockerを本番環境で使おうとするのであれば、Ansibleのようなツールを学ぶのはもはや必須といえます。その種のオーケストレーションツールにはさまざまなものがあり、Docker向けに特化したものもいくつかありますが、シンプルさ、学習の容易さ、そしてパワーにおいてAnsibleに優るものはありません。要件を満たしきれない力不足のツールを選んでしまい、最後のところで他のツールを学ぶ羽目になるよりも、優れたオーケストレーションツールを1つ究めることをぜひお勧めします。
勘違い: 今すぐDockerを導入しなきゃ
筆者は、実に多くの人々が、力不足のままDockerを導入しようとしているところを目撃してきました。Dockerを本番環境で使用することを検討するだけの段階であっても、その時点でその本番環境が正常に動いていなければお話になりません。
システムは次の要件を満たす必要があります。
- セキュリティで保護された、最小限の権限でのアクセス (キーベースのログイン、ファイアウォール、fail2banなど)
- 復旧可能で、セキュリティで保護され、物理的に離れた場所に保存されたデータベースバックアップ
- システム設定が自動化されていること (AnsibleやPuppetなどを使用して)
- デプロイが自動化されていること
- プロビジョニングが自動化されていること
- 重要なサービスがもれなく監視されていること
- その他諸々 (ドキュメントなど)
インフラに重大な穴が開いているのであれば、そもそもDockerを検討できるような状態ではありません。崩れかかった崖っぷちにフェラーリを駐車しているようなものです。
Dockerは優れた最適化ではありますが、安全確実な基礎の上に築かれる必要があります。
勘違い: Dockerを導入するのは速度と一貫性のため
Dockerを導入しなくても同程度の性能と一貫性を実現できる最適化の手法を以下にリストアップしました。実際、スケーラビリティの高い企業は、多少の差はあれ以下のような最適化を実施しているものです。
設定管理ツール (Ansible、Puppetなど)
システムが設定管理ツールで記述されていれば、作成や管理が簡単に行えます。特にクラウドにおいては、サーバーインスタンスの作成や削除を簡単かつ低価格で行なうことができます。
クラウドイメージ
多くのクラウドサーバーは、サーバー設定をイメージとして扱う機能をある程度持ちあわせています。1つのイメージから新しいサーバーインスタンスを1つ作成することは、設定管理ツールを使用してスクラッチから作成するよりも遥かに迅速に行うことができます。
次のような手法が考えられます: 最初に設定管理ツールを使用して、サーバーのロール (app、db、キャッシュなど) に合わせたベースイメージをいくつか作成します。次にそれらのイメージから新しいサーバーを立ち上げたら、設定管理ツールを使用してそれらを検証・管理します。
サーバーで若干の変更が生じた場合は、設定管理ツールでそれらの変更を管理すれば済みます。時間とともに変更が積み重なって、現在のサーバー設定とイメージの差が大きくなってきたら、定期的にイメージを新しく作成して差を埋めます。
この手法は「黄金のイメージ」パターンの変種です。イメージを使用することによって作業を迅速にしながら、わずかな変更のたびにイメージを再作成せずに済みます。
バージョンピニング (Version Pinning)
ある環境から別の環境に移動したときに生じる破損は、ほとんどがソフトウェアのバージョンの違いが原因です。従って、Dockerの利点である一貫した環境を得るために、重要なソフトウェアのバージョンをすべて明示的に定義 (ピニング (pinning): ピンで止めること) するようにしてください。たとえば、設定管理ツールでは単に’nginx’をインストールするのではなく、’nginx version 1.4.6-1ubuntu3′のように明確にバージョンを指定します。
もしAnsibleを使用しているのであれば、Vagrantにインストールされている開発環境を、同じスクリプトを使用して本番環境にインストールすることがかなり簡単に行えます。すべての環境でOSのバージョンが同一になっていれば、(Ubuntu 12.04 x64などのように) システムの一貫性が非常に高まり、環境間の移動によって破損することはほぼなくなるでしょう。
デプロイをバージョン管理する
Gitまたは同等のバージョン管理システムを使用していれば、使用するアプリケーション・ソフトウェアをサーバーにキャッシュしておき、ダウンロードを最小限に済ますことができます。これは、Dockerのイメージレイヤでのキャッシュと似ています。たとえば、コードベースのサイズが50 MBあり、数ファイルで生じたほんのわずかな更新をコードにデプロイしたいとします。Gitなどを使用してサーバー上のコードを更新すれば、コードベースを更新する際に発生するダウンロードもほんのわずかで済みます。これにより、デプロイを極めて迅速に行えます。
メモ: 高速化のために必ずしもバージョン管理システムを導入しなければならないというわけではありません。rsync
のようなツールを使用して、コードをサーバーにキャッシュしてコードの変更を差分アップデートでデプロイしても同様に高速化できます。
(アプリケーションコードを優先して)デプロイをパッケージ化する
CSSやJavaScriptアセットのコンパイルや圧縮によって、ソフトウェアのデプロイに時間がかかるのであれば、デプロイ用にコードのプリコンパイルとパッケージ化を検討してみましょう。プリコンパイルとパッケージ化を行なうことにより、コードをzip圧縮してデプロイするのと同程度に手順をシンプルにできます。dpkg
やrpm
などの実際のパッケージマネージャを使用してデプロイを管理するという方法も考えられます。
Gitなどのバージョン管理システムを使用しているのであれば、コンパイル済みアセット専用のリポジトリ (コードと同一であってもコードと切り離されていてもよい) を用意してそれを使用するという手もあります。
さらに高速化するのであれば、パッケージ (種類は問いません) をサーバーと同じローカルネットワーク内に配置するのがよいでしょう。同じネットワークに配置しても思ったほど高速にならないこともありますので、この方法はサーバー・ネットワークの外からのダウンロードがボトルネックになる場合にのみ検討すればよいでしょう。
どんなときにDockerを使用するのがよいか
さて、開発環境でVagrantを使用しているのであれば、すぐにでもDockerを使用できます。Vagrant 1.6以降は、VirtualBoxやVMWareと同様DockerもVMプロバイダとして扱えるようになりました。これにより、Dockerの面倒な部分の大半が抽象化され、追加学習なしでDockerを高速かつ低リソース消費で使用できます。詳細についてはFeature Preview: Docker-Based Development Environmentsを参照してください。
マルチホストの本番環境の場合は、先ほど説明した (Dockerを使用しないがなくてもできる) 最適化の手法をなるべく使用することをお勧めします。Docker自身が提供する高度な最適化の導入は、サーバーのスケールが拡大して従来の手法では間に合わなくなってからにしましょう。現状では、相当大規模な環境でなければ、DockerによるメリットよりもDockerによってシステムが複雑になるデメリットの方が大きくなってしまいます。Dockerが進歩してツールやパターンが成熟してくればこうした状況は今後変わるかもしれません。
あ、もちろん上でお勧めしているのは、あなたのシステムが頑丈に構成されていて、スクリプト化、自動化、セキュリティ対策、バックアップ、監視がちゃんと行われているということが前提になっているのをお忘れなく。
結論
Dockerは本当に素晴らしいプロジェクトであり、高度なシステム管理を大きく前進させました。その能力は非常に高く、ここではとても説明しきれないぐらい多くのユースケースをカバーしています。筆者はDockerを評価するにあたり、Webアプリケーションをデプロイするためのサーバー設定に着目しましたが、筆者のアドバイスが適用できないような設定や状況はいくらでもあります。
ここで説明した新しい一連の最適化には大きな期待が持てます。ただし、Dockerといえども運用管理していればたちまち複雑怪奇な状態になってしまうということはどうかお忘れなく。Dockerの宣伝記事で使用されているような小規模な事例などあっという間に遥か彼方に置いていかれるでしょう。
Dockerは今も急速な進歩を続けていますので、筆者のアドバイスもそのうち時代遅れになるかもしれません。ぜひそうなって欲しいものです。Dockerの複雑さが軽減され、マルチホストの本番環境での使い勝手が向上することを切に望みます。その日が来るまでは、現在わかっているDockerのメリットと、Dockerを使用することによるコストを冷静に比較してください。
追記
本番環境でのDockerをシンプルにするためのいくつかの手法
あなたがシステム管理の達人であり、現在のシステムのスケールがDockerのコストとメリットがちょうどバランスするぐらいの規模であれば、開始をシンプルにするためにこれらの手法を検討してみましょう。
すべてをDockerにする必要はない
Dockerを使用するのは、サーバーのロールという観点からメリットがある場合だけにしましょう。たとえば、現在数千のアプリケーション・サーバーを運用しており、アプリケーションのデプロイを最適化するためにDockerを導入したいとします。この場合、Docker化するのはアプリケーション・サーバーだけにしておき、それ以外のサーバーは現状のままにします。
Dockerイメージはロールベースで使用する
既に述べましたがもう一度繰り返します。Dockerは (app、db、キャッシュなどの) ロールベースで使用する方が、個別のプロセス単位で使用するよりも遥かに管理が楽です。
ベテランシステム管理者であれば、既にサーバーをロールベースでスクリプト化しているはずなので、Docker導入はずっとシンプルになります。
ベテランともなれば、ある程度以上のスケールのシステムでは1つのサーバーにほぼ間違いなく1つのロールだけを割り当てているはずです。アプリケーションサーバーにはアプリケーションサービスだけを行わせ、データベースサーバーを兼用したりしないはずです。このとき、1つのサーバーには1つのDockerコンテナだけが割り当てられることになります。1サーバー1コンテナとすることにより、ポートの衝突などを気にする必要がなくなり、ネットワーキングが非常にシンプルになります。
設定はできるだけ明示的に、自動設定に頼らないこと
Dockerは、明示的に指定しない限りサービスへのアクセスのポートをランダムに割り当てます。これはこれで、同一ホスト上で複数コンテナのポートの衝突が避けられるといったメリットがあるのですが、1ホストサーバー1ロールコンテナの原則を固く守ることでずっとシンプルになり、管理も容易になります。この原則に従うのなら、ポート番号を明示的に指定しましょう。それにより、ランダムに割り当てられたポートに他のサーバーからアクセスするような面倒を避ける事ができます。
システムのサービスを検出するための便利なツールとして、etcd、zookeeper、serfなどがあります。サーバーの場所をハードコードしてしまう (データベースをdatabase.example.orgに置くなど) よりも、アプリケーションからこれらのサービス検出ツールを使用してサーバーの場所を検出することができます。システムが大規模になり、自動スケーリングを行なうのであれば、こうしたサービス検出は実に便利です。ハードコードされたサービスの場所をいちいち管理していてはコストもかかるうえに問題の種になりがちです。しかしながら、サービス検出はそれはそれでシステムが複雑になり、自動設定のマジックに頼ることになり、その箇所でかえって問題が生じてしまう可能性もあります。従って、サービス検出はどうしても必要になるまでは導入しないでください。その代わり、それまではサーバーをできるだけ明示的に定義するようにします。こういう作業は、Ansibleテンプレートのインベントリ変数のようなものを使用すれば楽に行えます。
コンテナにはデータを保存しないこと
やむを得ない理由がない限り、Dockerのコンテナにはデータを保存しないでください。動いているDockerコンテナをうかつに止めてしまうと、データが一瞬で永遠に失われることになりかねません。データは、共有ディレクトリのあるホストに直接保存する方が安全かつ楽に管理できます。
ログファイルはホストの共有ディレクトリに置くか、logstashやpapertrailなどのリモートログ収集サービスを使用しましょう。
ユーザーがアップロードしたファイルは、専用のストレージサーバーに置くか、Amazon S3やGoogle Cloud Storageのようなファイルストレージサービスに置きましょう。
もちろん、動作していないデータ専用コンテナにあえてデータを保存しようと思えばできないことはありませんが、よほど自信があるのでなければ、共有ディレクトリのあるホストサーバーかどこか外部のサーバーに保存するのが賢明です。
プライベートインデックスプロバイダを使用する
セキュリティで保護されたプライベートなDockerインデックスを自前で正しく設定するのはなかなか面倒です。ホスティングされたプライベートDockerインデックスプロバイダを代りに使用すればずっと早く設定できます。
Dockerはあなたが所有するリポジトリをホスティングするイメージを提供しています。その代わり、それも管理の対象に加えなければならず、設定時には多くの設定を決定しなければなりませんが。それでも、ホスティングされたリポジトリインデックスで開始する方がおそらく便利でしょう。イメージにデータベースのパスワードのような重大なデータが含まれていれば別ですが。もちろん、Dockerイメージに最初からそのようなクリティカルなデータをそもそも含めるべきではありません。もっと常識的な方法を使用しましょう。たとえば、Ansibleを使用すれば、Dockerコンテナの実行時にこうした重要な詳細を環境変数として設定することができます。
他人の専門知識の上に構築しよう
Phusion社はPassengerという優秀なWebサーバーで知られていますが、実は高度なDockerイメージも構築していて、自分のサービスを構築するときの基礎として使用できます。同社は、ロールベースのDockerイメージを作成するときにありがちな問題を解決するために膨大な時間を費やしています。同社のドキュメントは非常によくできていて、自分のイメージを作成するにはうってつけのスタートポイントです。
https://phusion.github.io/baseimage-docker/
https://github.com/phusion/baseimage-docker
https://github.com/phusion/passenger-docker
https://github.com/phusion/open-vagrant-boxes
注: この投稿は筆者の著書『Taste Test: Puppet, Chef, Salt, Ansible』からの抜粋です。
2014/06/09公開
宣伝:『Taste Test: Puppet, Chef, Salt, Ansible』の新エディションをリリースしました!