Dockerにまつわる誤解とベストプラクティスについて

Dockerはシステム界隈に大きな衝撃を与えました。それはシステム管理にとってはまさに大躍進だったのですが、Dockerには、少々、致命的な誤解があるのです。

非常に限定されたアドバイス

ここで取り上げるDocker議論は、ほぼミッションクリティカルなシステムにおけるマルチホストのセットアップに限定されたものです(Webサービスが主)。それを念頭においてください。でないと、私からのアドバイスは、他のケースには、おそらく意味をなさないでしょう。

Dockerの背景

この記事では、Dockerとは何か、Dockerの一般的な動作については、すでに基本的な知識がある前提で話を進めていきます。

Dockerについて、すべてを網羅するのは、この記事の目的の範疇を越えてしまうので、Dockerについて、自分は初心者だという方は、まずは以下のサイトに目を通してください。

Dockerとは何か?
Dockerの基礎

誤解の数々

Dockerは多くの場面にとって、すばらしいツールです。しかし、Dockerの利用については、いくつか誤った認識が見受けられます。

誤解その1:Dockerを習得したら、他のシステムツールを学ぶ必要はない

もしかしたら、いつか、そんな日が来るかもしれません。しかし現状はそうではないのが事実です。Dockerをすばらしいツールと見なすのはかまいません。もちろん、そのとおりDockerはとても便利で役立つツールです。しかし同時にシステムを複雑化させるため、本番環境の中で安全にDockerを使うポイントを理解している熟練のシステム管理者が、ミッションクリティカルなシステム上で使うべきツールなのです。

Dockerの利用者は、システムにより精通している必要があります。知識が少なくて済むということはありません。しかし多くのDockerに関する記事は、極端にシンプルなユースケースを取り扱い、マルチホストの本番環境でDockerを使う複雑さに言及していません。これがDockerを本番環境で実際に使った場合における誤った印象を植え付けてしまっているのです。

一般的な本番環境で、安全かつ確実な方法でDockerを動作させるには、多くの変数を慎重に管理する必要があります。

・セキュリティで保護された、プライベートイメージリポジトリ (インデックス)
・組織的にコンテナのデプロイをゼロダウンタイムで実行する
・組織的にコンテナのデプロイをロールバックする
・マルチホスト上のコンテナ同士をネットワーク化すること
・コンテナのログを管理
・コンテナのデータを管理(データベースなど)
・適切に初期化やログなどを正しく行うイメージを作成する
・さらにさらに・・・

これは決して不可能なことではなく、すべて実行できることです。現に、大企業の中には本番環境でDockerを使うっているところもあります。しかし簡単なことではありません。(Flynn、Dockerコンテナホスティングなどを通じて) Dockerを取り巻くエコシステムが成熟してくれば、現状の問題点も改善されてくるでしょう。しかし現時点では、Dockerを本番環境に本格的に導入するのであれば、それに見合ったシステム管理と統合の高度なスキルが要求されます。

私が意図したことをさらに理解してもらうために、今までに私が見つけた以下の記事を読んでみてください。本番環境に近いものを取り扱っています(しかしまだDockerの理解に必要な要素が抜けているものもあります)。

Dockerを用いたWebアプリケーションソフトでRedisのデプロイを簡単に

Ruby on Railsアプリケーションにおける継続的デプロイのためのJenkinsとDockerのインテグレーション

リピータブルデプロイとしてGithub、JenkinsとDockerを使用する

Rackspaceのクラウド上でDockerにUbuntuイメージを固定する

サーバ管理についての学習をスキップしたい場合は、Herokuのようなサービスとしてのプラットホーム(PaaS)を使用してください。Dockerはソリューションになりません。

誤解その2:Dockerコンテナごとに1つのプロセスだけを持たせる

Dockerを、デプロイ可能な単一目的のプロセスではなく、ロールベースの仮想マシンであるとみなした場合、Dockerの管理はとても単純になります。この点を理解することが重要です。例えば、初期化、cron、sshなどのプロセスごとに”app” 仮想マシンを作成し、それと極めてよく似た”app”コンテナをビルドしたいとします。この時、すべてのプロセスをssh用、cron用、app用、webサーバ用などの個別のコンテナに分けることはしないでください。

1つのコンテナにつき1つのプロセスという方法については、大論争が巻き起こっていますが、実際に個別化されたものを管理するのはちょっとした悪夢といえます。極端に大規模な状況で使用するのなら、まだ意味がありますが、ほとんどの場合、ロールベースのコンテナ(アプリケーション、データベース、redisなど)で運用したいと思うでしょう。

上記の点について、まだ納得がいかない場合は、似たような管理上の問題を指摘している Microservices – ノーフリーランチ!、この投稿記事を読んでみてください。

誤解その3:Dockerを使えば、構成管理(CM)ツールを使う必要はない

これは部分的には事実です。Dockerを使用している場合、サーバに対する構成管理の必要性はそれほど高くはありませんが、サーバに対してDockerを準備やデプロイ、管理するためのオーケストレーションツールは絶対に必要です。

Ansibleのようなツールはこういうところで使ってこそ真価を発揮します。Ansibleは基本的にはオーケストレーションツールですが、思いがけず構成管理にも使うことができます。これはつまり、ホストサーバの準備、Dockerコンテナのデプロイ、管理、そしてネットワーキングの管理すべての段階をAnsibleで網羅できるということです。

Dockerを本番環境で使いたいのであれば、Ansibleのようなツールを学ぶことは必須条件です。オーケストレーションツールにはさまざまなものがあり(Docker向けに特化したものもいくつかあります)が、シンプルさ、理解しやすさ、そして使い勝手においてAnsibleにかなうものはありません。必要な機能を兼ね備えていないツールよりも(結局、他のツールを学ぶはめになります)、すぐれたオーケストレーションツールを1つだけ学んでみるのがよいでしょう。

誤解その4:今すぐDockerを使うべき

多くの人々が、準備不足のままDockerを使おうとするのを見てきました。Dockerを本番環境で使用することを検討する以前に、その時点でその本番環境が正常に動いている必要があります。

現行のシステムが以下の要件を満たしていなければなりません。

  • セキュリティで保護された、最小限の権限によるユーザアクセス(パスワードによるログイン、ファイアウォール、fail2banなど)
  • 復元可能かつセキュリティで保護された、オフサイトのデータベース・バックアップ
  • システム管理の自動化(Ansible、Puppetなど)
  • デプロイの自動化
  • プロビジョニングの自動化
  • 重要なサービスをすべて監視できていること
  • その他(ドキュメンテーションなど)

もしインフラに重大な欠陥があるなら、Dockerの導入を検討している場合ではありません。それは、今にも崩れそうな崖っぷちにフェラーリを駐車するようなものです。

Dockerは優秀なシステム最適化ツールですが、利用するには安全で確かな基盤が必要です。

誤解その5:速度と一貫性を実現するためにはDockerを使うべき

Dockerを使わなくても同程度のパフォーマンスと一貫性を実現できる最適化の方法を以下に挙げます。これらの方法のうち少なくともいくつかは、大規模なシステムを構築している企業の多くが実際に使っているものです。

構成管理ツール(Ansible、Puppetなど)

構成管理ツールで記述されているシステムであれば、簡単に作成と管理を行うことができます。特にクラウド上では、サーバインスタンスの追加と削除を低コストで簡単に実現できます。

クラウドイメージ

クラウドサーバのプロバイダの多くは、サーバ構成をイメージとして保存できる機能を提供しています。イメージから新たなサーバインスタンスを作成する方が、構成管理ツールを用いて一からサーバインスタンスを作るよりもはるかに速くて簡単です。

1つのやり方を示しましょう。まず、構成管理ツールを用いて、サーバのロール(アプリケーション、データベース、キャッシュなど)ごとにベースイメージを作成します。次に、それらのイメージから新たなサーバを起動し、それらを構成管理ツールでチェックし管理します。

サーバにマイナーな変更がある場合は、構成管理ツールを用いて変更管理を行うだけでいいのです。しかし時間が経つと、現在のサーバ構成とイメージの間にずれが生じてきます。ですから、定期的に新たなサーバイメージを作成し、常に最新の状態を保つよう心がけてください。

この方法は、ゴールデンイメージを作成する手法の応用版です。サーバイメージを作成しておくことで作業時間を短縮できるだけでなく、マイナーな変更のたびにわざわざイメージを作り直す必要もなくなります。

バージョンの指定

環境が変わった時に生じる不具合の多くは、ソフトウェアのバージョンの違いが主な原因です。ですから、Dockerを使う利点である、ほぼ完璧な一貫性を得るには、主要なソフトウェアのバージョンをすべて明確に記述してください。例えば、構成管理ツールでただ“nginxをインストール”と記述するのではなく、“nginx version 1.4.6-1 ubuntu3をインストール”と正確に宣言するようにしてください。

Ansibleを使っている場合であれば、本番環境と開発環境で同じスクリプトを用いるので、Vagrantでの環境構築が劇的に簡単になります。すべての環境において同じOSバージョン(例えば、Ubuntu 12.04 x64など)を用いるようにすれば、一貫性の高いシステムが構築でき、異なる環境間で不具合が生じることも少なくなります。

デプロイのバージョン管理

Git(あるいはそれに類似するバージョン管理システム)を使えば、アプリケーション・ソフトウェアをサーバにキャッシュしておき、最小限のダウンロードだけでアップデートできます。これは、Dockerで作成したイメージをサーバにキャッシュするのと似ています。例えば、50 MBのコードベースがあったとして、いくつかのファイルで生じたわずかな変更をコードに反映させたいとしましょう。そんな時は、Git(あるいはそれに類似するもの)を介してサーバ上のコードを修正するだけで、コードベースの更新に必要な変更をダウンロードできます。この方法を用いれば、極めて迅速なデプロイを実現できます。

注記:デプロイの高速化を実現するために、必ずしもバージョン管理システムを使う必要はありません。rsyncなどのツールを用いてコードの大半をサーバにキャッシュし、軽くて速い差分アップデートを介してコードの変更をデプロイすることもできます。

デプロイのパッケージ化(主にアプリケーションコードの場合)

CSSやJavaScriptのコンパイル・圧縮などが原因でソフトウェアのデプロイに時間がかかるのであれば、コードのプリコンパイルとパッケージ化を検討してみてください。この方法は、zipファイルを作成してコードをデプロイするやり方と同じくらい簡単です。あるいは、dpkgやrpmなどのパッケージ管理ツールを使って、デプロイを管理してもいいでしょう。

Git(あるいはそれに類似するバージョン管理システム)を用いている場合であれば、コンパイル済みのアセットだけに特化したリポジトリ(コードと同じリポジトリでも、別のリポジトリでも可)を用意してそれを使うということもできます。

さらに高速化したい場合は、パッケージ化したコードを自分のサーバと同じローカルネットワーク内に配置するようにしてください。しかし同じネットワーク内に配置しても、ごくわずかしか高速化されないことがあります。ですから、この方法は、外部ネットワークからのダウンロードに問題がある場合のみ検討してください。

いつDockerを使うか

開発環境の構築にVagrantを用いている場合は、今すぐにでもDockerを使用できます。Vagrant(version 1.6以降)は、VirtualBoxやVMwareなどの仮想化ソフトウェアに加え、Dockerもサポートするようになりました。これにより、Dockerベースの複雑な作業が大幅に単純化され、初心者でも高速で低コストの仮想化を実現できます。詳細については、アップデート情報:Dockerベースの開発環境をご覧ください。

マルチホストの本番環境では、できる限り、上述したような、Dockerを使わない方法による最適化を行うことをおすすめします。もしサーバの規模が大きくなりすぎて、それらの方法で対応できなくなれば、その時こそDockerを導入し高度な最適化を行いましょう。現在のところ、Dockerを使うことでシステムが複雑になるデメリットを相殺できるだけのメリットを感じられるのは、非常に大規模なシステムを扱っている場合のみです。しかしこうした状況も、Docker自体やDockerに関連するツールや手法が成熟することで、数ヶ月後あるいは数年後には変わっているかもしれません。

もちろん、この記事でアドバイスしたことは、あなたのシステムがすでにロバストで、スクリプト化、自動化、セキュリティ保護、バックアップ、サービス監視、その他様々な要件をクリアしていることを前提にしています。

結論

Dockerは本当に驚くべきプロジェクトで、高度なシステム管理にとって大きな飛躍であると言えるでしょう。非常にパワフルで、ここで話した以外にも様々な使い道があります。今回の評価における私の焦点はWebアプリケーションを配信するサーバのセットアップについてでしたが、もちろんその他のセットアップに関しては、上記の内容がすべて当てはまるわけではありません。

Dockerは、大きな可能性を秘めた最適化の新しいツールです。ただし、使用と管理については、Dockerを推奨する多くの記事の小さな使用事例とは比較にならないほどの複雑さが伴うことは覚えておいてください。

Dockerの進化は日進月歩のため、私のアドバイスも遅かれ早かれ(きっと)無用なものとなるはずです。マルチホストでの運用において、複雑さが軽減しユーザビリティも上がることでしょう。ですので、それまでの間は、得られるメリットとDockerを使用するコストを天秤にかけ、じっくりと考えてから使うことをお勧めします。

追記

Dockerの運用をシンプルにするためのヒント

もし、これをお読みの皆さんが経験豊富なシステム管理者で、担当されているシステムがDockerのメリット対コストに見合う状態にあるなら、Dockerの使用を開始するにあたって、そのプロセスをシンプルにするための以下の提案を検討してみてください。

すべてをDocker化する必要はない

恩恵が見込めるサーバロールに対してのみDockerを使用しましょう。例えば、非常に多くのアプリケーションサーバを運用している中で、Dockerを使ってアプリケーションのデプロイを最適化したいとしますね。その場合は、アプリケーションサーバのみにDockerを使い、その他のサーバはそのまま変更を加えずに現状を維持した方がいいでしょう。

ロールに基づいたDockerイメージを使用する

この投稿の前半に話したことの繰り返しになりますが、Dockerは、個別のプロセス(sshd、nginxなど)に基づいて運用するよりも、ロール(アプリケーション、データベース、キャッシュなど)に基づいて運用した方が、はるかに管理が楽になります。

経験豊富なシステム管理者なら、大抵はロールベースでサーバをスクリプト化されているでしょうから、Dockerの導入はよりシンプルになると思われます。

また、サーバがある程度のスケールの場合、1つのサーバに対して1つのロール(例えば、アプリケーションサーバならアプリケーションサーバとしてのロールのみで、データベースサーバとして併用しない)を割り当てているはずです。つまり、1つのサーバに1つのDockerコンテナが割り当てられた状態ですね。このようにサーバとコンテナの1対1の関係性を築くと、(ポートが競合する恐れがないため)ネットワーキングが大幅にシンプルになります。

できる限り明確に(曖昧さは避ける)

Dockerでは、具体的に指定しない限り、サービスにアクセスするポートはコンテナ内で自動的に振り分けられます。もちろんこの自動振り分けが都合のいい時もあるのですが(同一ホスト内の複数コンテナによるポートの競合を回避できるなど)、それよりもホストサーバとロールコンテナ(アプリケーション、データベース、キャッシュなど)の1対1の関係性を維持した方がシンプルで、管理もずっと容易です。それを実現するには、ポート番号を個別に振り分けましょう。そうすれば、自動で振られたポート番号とサーバを必要に応じて通信させるような複雑なプロセスは回避することができます。

管理中のシステムのサービスを検索する優れたツールとして、etcdzookeeperserfなどがあります。サーバの場所をハードコーディングで(例えばデータベースをdatabase.example.orgに置くなど)指定することもできますが、アプリケーションでこれらのサービス検索ツールを使うと、サーバの場所を特定することが可能です。サービス検索は、サーバの規模が大きくなった時やオートスケール使用時には非常に便利で、ハードコーディングではコストがかかり複雑になるような検索を代わって行ってくれます。ただし、ここで注意しておきたいのは、サービス検索ツールを導入することでシステムが複雑になり、それによって挙動不審な動きが誘発され問題が生じることです。そのため、どうしても必要という時以外は、それらのツールの使用は控えた方がいいでしょう。では、どうすればいいのかというと、構築されたサーバの構成をできる限り具体的に指定すればいいのです。Ansibleのテンプレートにあるインベントリ変数のようなものを使えば、それほど難しいことではありません。

コンテナにデータを保存しない

よほどの自信がある時以外は、Dockerコンテナ内にデータを保存するのはやめましょう。動作中のコンテナを誤って止めてしまうと、そのデータは永遠に失われてしまいます。データを管理するのであれば、共有ディレクトリのあるホストに直接、保存した方が安全ですし簡単です。

ログに関しては、ホストの共有ディレクトリか[logstash](http://logstash.net/)やpapertrailのようなリモートのログ収集サービスを使うといいでしょう。

ユーザーからアップロードされたファイルの管理には、専用の保存サーバかAmazon S3やGoogleのストレージサービスを使いましょう。

動作していないデータ専用コンテナにデータを保存する方法がないわけではありませんが、ある程度の確信がない限りは、ホストサーバの共有ディレクトリか外部サーバに保存することをお勧めします。

プライベートインデックスプロバイダを使用する

セキュリティの確保されたセルフホスティングのプライベートDockerインデックスを正しく設定するのは一苦労です。そういう場合、外部ホスティングのプライベートDockerインデックスプロバイダを使用すれば、設定も早く済みます。

プライベートリポジトリのホスティングサービス

Dockerには、あなたのリポジトリをホスティングするイメージ提供のサービスがあります。ただし、これを利用した場合、管理項目が増えることにもなりますし、設定に関する決断も多く下さなければなりません。それでも、例えばイメージが繊細なデータ(パスワードやデータベースなど)を含むような構成でなければ、まずはこのホスティングのリポジトリインデックスを使った方がスムーズに始めることができると思います。そもそもDockerイメージやアプリケーションに、このような重要なデータを含むこと自体、あまりお勧めできることではありませんよね。通常は、Ansibleを使ってそれらのデータを環境変数として設定するなどした方が、Dockerコンテナを使用する上では、健全と言えるでしょう。

他人の知恵を利用する

Phusion社(Passengerという優れたウェブサーバを構築)は、多くの人がサービスのベースとして使えるような高度な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』からの抜粋を基にしています。