アプリケーション作成の原則「The Twelve-Factor App」を読んだ

12factor.net

Dockerに関するウェブ記事の中にたまたま名前が出ており、気になって見てみたら、示唆に富み奥の深い資料だった。

この資料は、HerokuのエンジニアであったAdam Wiggins氏が、大小様々なSaaSアプリケーションのデプロイに接した経験をもとに、言語や技術によらないアプリケーション作成時の12の原則をまとめたものである。抽象的で少し読みにくいところもあるが、自分の経験を振り返っても共感できるところが多かったので、ここにまとめてみる。

I. コードベース

ここでいうコードベースは、リポジトリと考えて差し支えない。コードベースはアプリケーションと1対1であるべき。コードベースが複数あるとデプロイ手順が複雑になり、逆に1つのコードベースから複数アプリケーションをデプロイする場合、片方の保守がもう片方に与える影響を予測しにくい。大規模アプリケーションの場合、コンポーネントごとにリポジトリを分けることも多いだろう。
また、ビルド・リリース・実行の各環境へのデプロイは、同じ内容のコードベースがもとになっているべき。本番だけコードが少し変わっているというようなことはあってはいけない。

II. 依存関係

The Twelve-Factor Appでは、依存関係用のツールを2つに分類している。

  • 依存関係宣言マニフェスト: 依存するパッケージの名前とバージョンを指定してインストールするためのもの。GenfileやPipなど。
  • 依存関係分離ツール: そのプロジェクトの依存パッケージを、グローバル環境や他プロジェクトから独立した環境に置くことで、宣言マニフェストにない暗黙の依存関係の混入を防ぐためのもの。bundle execやvirtualenvなど。

この両方を使うことで、環境を問わず誰でも開発に参加しやすくなる。逆に、グローバル環境にあるシステムツールやコマンドへの依存はなるべく避ける。
個人的には、依存関係分離ツールは環境汚染による異常な挙動を防ぐためかと思っていたが、依存関係を漏らさないという観点はためになった。

III. 設定

DBの認証情報など、アプリケーション外のサービスと接続するための設定は、ベタ書きや設定ファイルではなく、環境変数に入れる。こうすることで、リポジトリにプッシュしてしまう心配もないうえ、各デプロイ環境ごとに値を簡単に切り替えられる。

IV. バックエンドサービス

DBやキューイングサービスなど、アプリケーション外にあるサービスは、ローカルのものでもネットワーク越しのものであっても、環境変数を変えるだけで異なるサービスに接続できるようにするべき。このためだけにコード修正が発生してはいけない。
疎結合の徹底と捉えるとわかりやすい。また、これを徹底すればひとつ前の項目3も自然と守られるだろう。

V. ビルド、リリース、実行

これらの3つの環境を明確に区別する。また、1でもあったように、全て同じコードベースをもとにしていなければならない。
リリースを簡単にロールバックできるツールがあるとなお良い。

VI. プロセス

アプリケーションの中では極力状態を持たない。状態はDBなど外部サービスに保管するよあにし、アプリケーション内部ではステートレスな処理を保つ。
これは身を持ってわかるが、状態があるとテストが大変になるだけでなく、整合性を保ったままリリースすることが難しい。開発者の苦労が倍どころではなくなる。

VII. ポートバインディング

アプリケーションの中にサーバを組み込むことで、環境にサーバを立てることなく、開放した特定のポートへのアクセスを待つだけで良くなる。こうすると、別のアプリケーションにとってのバックエンドサービスにもなれる。
最近は自分でサーバ設定をする機会はどんどん減っているような気がする。

VIII. 並行性

並行処理を行うアプリケーションの場合、状態を共有せず水平に分割されたプロセスが理想である。これなら簡単にプロセスを増やせるうえ、プロセスの管理も容易になる。

IX. 廃棄容易性

短時間で起動し、gracefulにシャットダウンできるようにするべき。ここでのgracefulとは、SIGTERMを受け取ったら、停止する前に各プロセスでのリクエスト受け付けを止めたり、レコードのロックを解放したりする処理である。
また、万が一突然落ちてしまっても、データが失われたり重複したりしない設計を考えなければいけない。

X. 開発/本番一致

開発環境と本番環境の差が小さいほど、デプロイが容易になる。このため、小さな変更を自動で本番環境に反映できる仕組みが望ましい。
また、開発環境と本番環境で異なるバックエンドサービスを使わない。環境の差異は想定外の挙動を生みやすく、原因の特定も難しい。最近ではパッケージ管理ツールやDockerがあるので、本番と同じ環境を開発環境でも十分に作れる。

XI. ログ

ログはファイルではなく標準出力にする。そしてそのログはlogstashなどのログルーターによって集積される。このとき、ログルーターもバックエンドサービスの1つなので、アプリケーションは設定を感知しない。
ログファイルの方が手軽なようにも見えるが、ログルーターを使うとログの監視・集約を効率的に行える。また、標準出力ならどんなOSでもできるので、実行環境を移す際も手間がない。

XII. 管理プロセス

DBのマイグレーションなど、1回限りの管理タスクであっても、アプリケーションと同じコードベース・設定で管理する。コードベースを共通にすることで、オペレーションミスを減らすことができる。
また、REPLを提供している言語であれば、開発時や本番環境の調査時に管理スクリプトを簡単に実行できる。とはいえ、本番環境での通常の管理スクリプトは可能な限り自動化する方が良い。

今までなんとなくこれが良いんだろうなと思っていたことがしっかり言語化されており、とても参考になった。このような指針があれば、自分で作る小さなアプリケーションでも取り入れやすいかもしれない。