現代のコンテナベースアプリケーションについて学ぶ(第二回): Dockerを境界として導入する意味
こんにちは。SREの@okazu-dmです。
この記事は3回の連載の中の2回目となります。
過去の記事は以下の通りです。
前回は、極力Dockerについて意識しなくても使えるPaaSを前提とした話をしましたが、前回の補足として今回はPaaSの経験を基により一般的なプラクティスについて解説します。
この回でわかること
- Dockerが「何を解決し、何を解決しないか」
- アプリとインフラの責務は、現状どのように分離されているか
- アプリ側が従うべき制約
前回の振り返り
前回の話では、PaaSであるHerokuを通してコンテナの内側(アプリケーション側)から外側(プラットフォーム側)との境界の存在を意識する、という内容でした。
Herokuでは、以下のような点はプラットフォーム側の責務とされており、開発者側はアプリケーションをデプロイするだけで最低限動かすことができるような仕組みになっています。
一方で、以下のような制約がプラットフォーム側から課されており、運用コストの低さと自由度がある種のトレードオフとなっていることを解説しました。
- ローカルに書いたファイルの変更は永続化されない
- ログは標準出力/標準エラー出力に吐く必要がある
- プロセスがいつ死ぬか(再起動のタイミング)をコントロールできない
境界としてのDocker
前回の記事の後半でDockerを「責任境界を固定するための道具」と書きましたが、今回はこの点について深堀りします。
PaaSを触っていると、アプリケーションをデプロイするだけで動き、落ちても勝手に復旧し、勝手にスケールし、ログも自動で収集してWebUIで見れるようにしてくれます。
では、なぜPaaSはあれほど楽だったのでしょうか。
結論から言えば、PaaSが楽だったのは「PaaSが高機能だったから」だけではなく、「ユーザの責任範囲が限定化/明確化されていたから」です。
前回の記事でHerokuがやってくれていたのは「運用自動化」ですが、その自動化が成立するためには、アプリ側が一定の条件を満たしている必要があります。
PaaSが「アプリをデプロイすれば動く」ように見えるのは、裏側でプラットフォームが以下を肩代わりしているからでした。
- プロセス監視(落ちたら再起動)
- ログの集約と保管
- 設定値の注入
- デプロイとロールバック
そして、これらがうまく動作するにはアプリケーション側がプラットフォームの要求する仕様を満たしていることが前提になっています。
逆に言えば、Dockerでコンテナ化しただけでは、Heroku的な楽さは手に入りません。
Dockerは責任境界を作るだけであって、境界の外側(プラットフォーム)を良い感じにはしてくれるわけではないからです。
今回の記事では、プラットフォーム側についてはほとんど触れずに、あくまでコンテナベースのアプリケーションをどう設計すべきか、という点について考えます。
このあと紹介する12-Factor Appは、PaaSが要求していた「アプリが守るべき仕様」を言語化したものであり、Docker時代においても「コンテナの内側をどう設計すべきか」の一例になります。
Twelve-Factor App
以上のように、Dockerを境界として考えたとき、アプリケーション側はどのように設計すべきなのでしょうか。
この点については、Herokuの共同創設者であるAdam Wiggins氏が提唱したTwelve-Factor Appが参考になるため紹介します。
これは、2011年に考えられたHerokuのようなPaaSで動かすアプリケーションを設計する際のベストプラクティス集です。
たとえばHerokuでは以下が当たり前でした。
- ログは標準出力に出す => プラットフォームが標準出力の内容を収集してくれる
- 設定値は環境変数で読み込む => プラットフォーム側が環境変数で設定値を渡してくる
- 落ちてもOKなように作る => プロセスはいつ死ぬかわからない
これらはすべて、Twelve-Factor Appの項目に対応しています。
以下に、Herokuの体験とTwelve-Factor Appの対応関係の一例をまとめます。
| Herokuでの体験 | 何が起きていたか | 対応するTwelve-Factor |
|---|---|---|
heroku config:set で設定を差し替えられる |
設定値をデプロイ物に含めず、環境変数で渡す | III. Config(設定を環境変数に格納) |
| Dynoが勝手に再起動する | プロセスがいつでも死ぬ前提の設計 | IX. Disposability(即時起動とGraceful Shutdown) |
ログは heroku logs で見られる |
アプリはstdout/stderrに吐いているだけ | XI. Logs(ログをイベントストリームとして扱う) |
このように、Heroku上での適切な設計がベストプラクティスとして現れており、ほとんどの要素は現在の他のPaaSでも通用する内容でもあります。
また、これらは同時にほとんどそのまま「コンテナベースのアプリケーションのベストプラクティス」としても使えます。
Dockerを「境界」として見た時、境界の外側(プラットフォーム側)から見た前提条件がTwelve-Factor Appにあらわれていると言えます。
そしてプラットフォームは、その境界の前提に沿ってコンテナを扱います。
そのため、アプリ側は以下を仕様として受け入れる必要があります。
- コンテナは死ぬ(死ぬものとして作る)
- ログはファイルに書かない(ストリームとして吐く)
- 設定は環境ごとに差し替える(成果物に埋め込まない)
注意点
注意点として、Twelve-Factor Appの内容は最初に書かれてから時間が経過したこともあり、組織や利用しているプラットフォームによってマッチしない部分や読み替えが必要な点があるため、Herokuで動かすときのベストプラクティスと考えると良いでしょう。
コンテナの外の世界
前述したように、コンテナベースのアプリが備えるべき性質としてはいくつかありますが、このようなアプリがあったときに、コンテナの外側がどのようにコンテナを扱うべきか、という点については一例として以下のような観点があります。
- ログをどのサービスで見て、どこに永続化するのか
- シークレットを含む環境変数をどこで安全に扱うか
- コンテナのヘルスチェックが通らない場合の処理
これらについては、次回でAWS上の具体的なアーキテクチャをベースに説明します。
まとめ
今回の記事では、前回の補足としてPaaS(Heroku)の体験を一般化し、Dockerが境界として果たす役割を内側(アプリケーション側)と外側(プラットフォーム側)から、それぞれの立場で簡単に紹介しました。
- PaaSが楽だったのは、運用が自動化されていたから
- その自動化は「アプリが一定の仕様を満たす」ことで成立していた
- その仕様を言語化したものがTwelve-Factor App
- Dockerは境界を作るが、外側(運用)は勝手に整えてくれない
Herokuの運用が楽だったのは、Heroku(プラットフォーム)自体の機能が優れていたからだけでなく、プラットフォームの運用に適した形でアプリが作られていたからです。
そして「プラットフォームが要求する設計」が存在する点は、コンテナ実行基盤に移っても変わりません。
次回はいよいよ、Dockerで切った境界の外側――つまり「コンテナをどう運用するか」の話に入ります。
ECS(場合によってはFargate)などを例に、ログ・設定・ヘルスチェック・再起動・スケールといった運用の現実を、AWSの具体例として組み立てていきます。
Discussion