The Twelve-Factor App (日本語訳)を読んだので、そのメモとか補足とかを書き残しておきます。 ほとんど引用なので、人様に見せられるようなものではないのは承知しているつもりです。
Twelve-Factor Appは、次のようなSoftware as a Serviceを作り上げるための方法論
- セットアップ自動化のために 宣言的な フォーマットを使い、プロジェクトに新しく加わった開発者が要する時間とコストを最小化する。
- 下層のOSへの 依存関係を明確化 し、実行環境間での 移植性を最大化 する。
- モダンな クラウドプラットフォーム 上への デプロイ に適しており、サーバー管理やシステム管理を不要なものにする。
- 開発環境と本番環境の 差異を最小限 にし、アジリティを最大化する 継続的デプロイ を可能にする。
- ツール、アーキテクチャ、開発プラクティスを大幅に変更することなく スケールアップ できる。
I. コードベース
バージョン管理されている1つのコードベースと複数のデプロイ
Twelve-Factor AppはGitやMercurial、Subversionなどのバージョン管理システムで常に変更を追跡している。
コードベースとは、Gitの場合にはルートコミットを共有する複数のリポジトリ(同じリモートリポジトリを見ているリポジトリ)を指す。
コードベースとアプリケーションは常に1対1の関係がある。
もし複数のコードベースがある場合、それはアプリケーションではない – それは分散システムである。分散システムのそれぞれのコンポーネントがアプリケーションであり、個別にTwelve-Factorに適合することができる。 同じコードを共有する複数のアプリケーションは、Twelve-Factorに違反している。その場合の解決策は、共通のコードをライブラリに分解し、そのライブラリを依存関係管理ツールで組み込むようにすることである。
アプリケーションのデプロイは複数存在する。
デプロイはアプリケーションの実行中のインスタンスのことを指す。つまり、本番サイト、ステージングサイト、開発者ごとのローカル開発環境をそれぞれデプロイとみなすことができる。
II. 依存関係
依存関係を明示的に宣言し分離する
Twelve-Factor Appは、システム全体にインストールされるパッケージが暗黙的に存在することに決して依存しない。 すべての依存関係を 依存関係宣言 マニフェストで完全かつ厳密に宣言する。さらに、実行時には 依存関係分離 ツールを使って、取り囲んでいるシステムから暗黙の依存関係が“漏れ出ない”ことを保証する。完全かつ明示的な依存関係の指定は、本番環境と開発環境の両方に対して同様に適用される。
Node.jsの場合、「依存関係宣言マニフェスト」はpackage.jsonであり、「依存関係分離ツール」はnvm等だろうか。
Twelve-Factor Appは、いかなるシステムツールの暗黙的な存在にも依存しない。
例えば、アプリケーションcurlを利用する場合、他のOSや将来のOSにもcurlが存在し続けている保証はない。そのため、例でのcurlのような外部のツールを必要とする・依存する場合にはそのツールをアプリケーションに組み込むべきである。
III. 設定
設定を環境変数に格納する
アプリケーションの 設定 は、デプロイ(ステージング、本番、開発環境など)の間で異なり得る唯一のものである。
Twelve-Factorは 設定をコードから厳密に分離すること を要求する。
ただし、アプリケーション内部の設定、つまりデプロイ間で変わらないものはコードの内部で定義してよい。
設定には、バージョン管理システムにチェックインされない設定ファイルを使う。
Twelve-Factor Appは設定を 環境変数 に格納する。 環境変数は、コードを変更することなくデプロイごとに簡単に変更できる。設定ファイルとは異なり、誤ってリポジトリにチェックインされる可能性はほとんどない。また、独自形式の設定ファイルやJava System Propertiesなど他の設定の仕組みとは異なり、環境変数は言語やOSに依存しない標準である。
アプリケーションは設定を名前付きのグループ(しばしば“環境”と呼ばれる)にまとめることがある。
これは良くない傾向である、なぜなら
アプリケーションのデプロイが増えるにつれて、新しい環境名(stagingやqa)が必要になる。さらにプロジェクトが拡大すると、開発者はjoes-stagingのような自分用の環境を追加する。結果として設定が組み合わせ的に爆発し、アプリケーションのデプロイの管理が非常に不安定になる。
IV. バックエンドサービス
バックエンドサービスをアタッチされたリソースとして扱う
バックエンドサービス はアプリケーションが通常の動作の中でネットワーク越しに利用するすべてのサービスを言う。
リソースは自由にデプロイにアタッチしたり、デプロイからデタッチしたりできる。例えば、ハードウェアの問題によってアプリケーションのデータベースの動作がおかしい場合、アプリケーションの管理者は最新のバックアップから新しいデータベースサーバーを立ち上げる。そして現在の本番データベースをデタッチし、新しいデータベースをアタッチする – コードを一切変更せずに。
V. ビルド、リリース、実行
ビルド、リリース、実行の3つのステージを厳密に分離する
コードベースは3つのステージを経て、(開発環境ではない)デプロイへと変換される。
- ビルドステージ は、コードリポジトリを ビルド と呼ばれる実行可能な塊へと変える変換である。デプロイプロセスで指定したコミットのコードで指定されたバージョンを使って、ビルドステージは依存関係を取得してローカル環境に配置し、バイナリやアセットファイルをコンパイルする。
- リリースステージ は、ビルドステージで生成されたビルドを受け取り、それをデプロイの現在の設定と結合する。出来上がる リリース にはビルドと設定の両方が含まれ、実行環境の中ですぐにでも実行できるよう準備が整う。
- 実行ステージ (ランタイムとも呼ばれる)は、選択されたリリースに対して、アプリケーションのいくつかのプロセスを起動することで、アプリケーションを実行環境の中で実行する。
すべてのリリースは常に一意のリリースIDを持つべきである。
ビルドステージは、新しいコードがデプロイされるときに必ずアプリケーションの開発者によって開始される。一方実行ステージは、サーバーの再起動時や、クラッシュしたプロセスがプロセスマネージャーによって再起動された時に自動で開始される。このため、実行ステージはできるだけ可変部分を持たないようにするべきである。なぜなら、アプリケーションの実行を妨げるような問題が起きると、開発者が待機していない真夜中にアプリケーションが壊れる結果になるためである。ビルドステージはもっと複雑でも構わない。なぜなら、ビルドステージのエラーは常にデプロイを実行している開発者の目の前で発生するためである。
VI. プロセス
アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する
Twelve-Factorのプロセスはステートレスかつシェアードナッシング である。永続化する必要のあるすべてのデータは、ステートフルなバックエンドサービス(典型的にはデータベース)に格納しなければならない。
プロセスのメモリ空間やファイルシステムは、短い単一のトランザクション内でのキャッシュとして利用してもよい。
Twelve-Factor Appは、メモリやディスクにキャッシュされたものが将来のリクエストやジョブにおいて利用できることを決して仮定しない – それぞれのプロセスタイプのプロセスが多く実行されている場合、将来のリクエストやジョブが別のプロセスで処理される可能性が高い。
スティッキーセッションはTwelve-Factorに違反しており、決して使ったり頼ったりしてはならない。セッション状態のデータは、有効期限を持つデータストア(例:Memcached や Redis)に格納すべきである。
VII. ポートバインディング
ポートバインディングを通してサービスを公開する
Twelve-Factor Appは完全に自己完結 し、Webに公開されるサービスを作成するために、コンテナが実行環境にWebサーバーランタイムを注入することを頼りにしない。Webアプリケーションは ポートにバインドすることでHTTPをサービスとして公開し、 そのポートにリクエストが来るのを待つ。
依存関係宣言を使ってWebサーバーライブラリをアプリケーションに追加することで実装される。
WebアプリケーションではApacheやTomcatのようなWebサーバーを使うことがあるが、アプリケーション自体にWebサーバーライブラリ(Jetty)を含めておく(依存関係に含めておく)。
VIII. 並行性
プロセスモデルによってスケールアウトする
Twelve-Factor Appではプロセスは第一級市民である。 Twelve-Factor Appにおけるプロセスの考え方は、サービスのデーモンを実行するためのUnixプロセスモデルから大きなヒントを得ている。
Applying the Unix Process Model to Web Apps
JVMやNode.jsのように大きな親プロセス内部でスレッドを使って内部的に並行性を管理するよりも、プロセスの管理をOSのプロセスマネージャー(systemd等)やクラウドプラットフォームの分散プロセスマネージャーに任せるべきである。
個々のVMは垂直にスケールすることが難しい。 アプリケーションは複数の物理マシンで動作する複数のプロセスへと拡大できる(Lambda関数のように複数起動して処理をおこなえる)ようにすべき。
IX. 廃棄容易性
高速な起動とグレースフルシャットダウンで堅牢性を最大化する
Twelve-Factor Appの プロセス は 廃棄容易 である、すなわち即座に起動・終了することができる。この性質が、素早く柔軟なスケールと、コード や 設定 に対する変更の素早いデプロイを容易にし、本番デプロイの堅牢性を高める。
プロセスは、 起動時間を最小化する よう努力するべきである。理想的には、1つのプロセスは、起動コマンドが実行されてから数秒間でリクエストやジョブを受け取れるようになるべきである。
プロセスは、プロセスマネージャーから SIGTERMシグナルを受け取ったときに、グレースフルにシャットダウンする。
Webプロセスの場合、グレースフルシャットダウンは、サービスポートのリッスンを中止し(従って新しいリクエストを拒み)、処理中のリクエストが終了するまで待ち、シャットダウンすることで実現される。このモデルでは暗黙的にHTTPリクエストが短い(せいぜい数秒)ことを仮定している。
ワーカープロセスの場合、グレースフルシャットダウンは、処理中のジョブをワーカーキューに戻すことで実現される。
このモデルでは、暗黙的にすべてのジョブが再入可能であることを仮定している。再入可能性は一般的に、結果をトランザクションで包んだり、処理をべき等にすることで実現される。
「クラッシュオンリー」設計はこのコンセプトをその論理的帰結に導く。
Crash-only software: More than meets the eye [LWN.net]
X. 開発/本番一致
開発、ステージング、本番環境をできるだけ一致させた状態を保つ
Twelve-Factor Appでは、継続的デプロイしやすいよう開発環境と本番環境のギャップを小さく保つ。
- 時間のギャップ: 開発者が編集したコードが本番に反映されるまで数日、数週間、時には数ヶ月かかることがある。
<ー ギャップを小さくするために、開発者が書いたコードは数時間後、さらには数分後にはデプロイされる。
- 人材のギャップ: 開発者が書いたコードを、インフラエンジニアがデプロイする。
<ー ギャップを小さくするために、コードを書いた開発者はそのコードのデプロイに深く関わり、そのコードの本番環境での挙動をモニタリングする。
- ツールのギャップ: 本番デプロイではApache、MySQL、Linuxを使うのに、開発者がNginx、SQLite、OS Xのようなスタックを使うことがある。
<ー ギャップを小さくするために、開発環境と本番環境をできるだけ一致させた状態を保つ。
たとえ理論的にはアダプターがバックエンドサービスの違いをすべて抽象化してくれるとしても、 Twelve-Factorの開発者は、開発と本番の間で異なるバックエンドサービスを使いたくなる衝動に抵抗する。 バックエンドサービスの違いは、わずかな非互換性が顕在化し、開発環境やステージング環境では正常に動作してテストも通過するコードが本番環境でエラーを起こす事態を招くことを意味する。
XI. ログ
ログをイベントストリームとして扱う
Twelve-Factor Appはアプリケーションの出力ストリームの送り先やストレージについて一切関知しない。 アプリケーションはログファイルに書き込んだり管理しようとするべきではない。代わりに、それぞれの実行中のプロセスはイベントストリームをstdout(標準出力)にバッファリングせずに書きだす。
ローカルでの開発中、開発者はこのストリームをターミナルのフォアグラウンドで見ることで、アプリケーションの挙動を観察する。
ステージングや本番のデプロイでは、それぞれのプロセスのストリームは実行環境に捕らえられ、アプリケーションからの他のすべてのストリームと一緒に並べられ、表示や長期アーカイブのために1つもしくは複数の最終目的地に送られる。これらの保存のための目的地は、アプリケーションからは見ることも設定することもできず、代わりに実行環境によって完全に管理される。
XII. 管理プロセス
管理タスクを1回限りのプロセスとして実行する
プロセスフォーメーションは、アプリケーションが実行されたときにアプリケーションの通常の役割(Webリクエストの処理など)に使われるプロセス群である。それとは別に、開発者はしばしばアプリケーションのために1回限りの管理・メンテナンス用のタスクを実行したくなる。
1回限りのプロセスとしては、データベース移行、アプリケーションのバージョンアップに伴うデータベース上のデータ修正など。
1回限りの管理プロセスは、アプリケーションの通常の長時間実行されるプロセスと全く同じ環境で実行されるべきである。これらのプロセスは、あるリリースに対して実行され、そのリリースに対して実行されるすべてのプロセスと同じコードベースと設定を使う。管理用のコードは、同期の問題を避けるためにアプリケーションコードと一緒にデプロイされるべきである。
同じ依存関係分離技術をすべてのプロセスタイプに利用するべきである。
Twelve-Factorは細かい設定なしですぐに使えるREPLシェルを提供する言語を強く好む。1回限りのスクリプトを実行するのが簡単になるからである。