Twelve-Factor Appを読み解いていく

2021/10/11に公開

概要

Twelve-Factor Appを改めて読み解き、理解していこうというのが本記事の目的になります。

なお、具体例(AWSやIaCを踏まえたモダンなWebアプリにおけるTwelve-factor)を踏まえた話は以下の記事により詳細に落とし込んでいるので、こちらを参照してください。

https://developers.kddi.com/blog/2pcE20cmzJwt2wwov1QN5X

Twelve-Factor App

  • Twelve-Factor Appとは、Herokuプラットフォーム上で開発・運用・スケールした何百何千ものアプリケーションから得られた知見をモダンなWebアプリケーションとしてあるべき姿として、12のベストプラクティスにまとめた方法論です。
  • 提唱されたのが2012年と少々古い一面もありますが、現在でも示唆に富む数々のプラクティスが得られます。

1.コードベース

バージョン管理されている1つのコードベースと複数のデプロイ

コードベース(Githubのリポジトリ)とアプリケーションは常に1対1の関係であるようにする。

つまり、アプリケーションごとに1つのコードベースが存在し、アプリケーションのデプロイは複数存在するパターン(例えば、開発環境, 検証環境, 本番環境)は、Twelve-Factorに適合するといえる。
逆に、1つのコードベースから複数のアプリケーションが作られる場合は、Twelve-Factorに違反しているといえます。

2.依存関係

依存関係を明示的に宣言し分離する

システム全体にインストールされるパッケージが暗黙的に存在してはいけない
つまり、Nodeならnpmのようなパッケージ管理システムを利用し、常に使用するライブラリ等は明示的にしてアプリケーションだけで完結するように、すべての依存関係を依存関係宣言マニフェスト(package.jsonなど) で完全かつ厳密に宣言する。

3.設定

設定を環境変数に格納する

Twelve-Factorでは、設定をコードから厳密に分離することを要求するため、環境変数で設定する必要があります。
アプリケーションが設定を定数としてコード内に格納することは、Twelve-Factorに違反しています。
これにより、コードを変更することなくデプロイごとに簡単に設定を変更できるうえ、設定ファイルとは異なり、誤ってリポジトリに混入する可能性もほとんどなくなります。

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

バックエンドサービスをアタッチされたリソースとして扱う

ここでのバックエンドサービスとは、データストアや、メッセージキューイングシステム、SMTPサービスなどが該当します。
これらのバックエンドサービスは、アプリケーションにとっては、どれもアタッチされたリソースであり、設定に格納されたURLやその他のロケーター、認証情報でアクセスできるようにします。

Twelve-Factor Appのデプロイでは、DBや外部サービスはアタッチ/デタッチ可能なリソースとして捉え、アプリケーションのコードを修正しなくても変更できるように環境変数に設定値(URLやユーザ情報等)を定義しましょうとしています。
これは、コード修正が必要になると環境ごとにビルドが必要となってしまうためです。

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

ビルド、リリース、実行の3つのステージを厳密に分離する

Twelve-Factor Appは、ビルド、リリース、実行の3つのステージを厳密に分離します。

  • ビルドステージ : コードリポジトリを実行可能なアーティファクトへ変換します。
  • リリースステージ : ビルドステージで生成されたアーティファクトを受け取り、それをデプロイの現在の設定と結合します。アーティファクトと設定の両方が含まれ、実行環境の中ですぐにでも実行できるよう準備が整います。
  • 実行ステージ(ランタイム) : 選択されたリリース資源に対して、アプリケーションのいくつかのプロセスを起動することで、アプリケーションを実行環境の中で実行します。

6.プロセス

アプリケーションを1つもしくは複数のステートレスなプロセスとして実行する

Twelve-Factor Appのプロセスは、永続化する必要のあるすべてのデータは、ステートフルなバックエンドサービス(データベース)に格納しなければなりません。

なぜなら、プロセス自体が仮想サーバのスケールアウト/スケールインによって増減することで変化し、永続的に存在するものではないためです。

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

ポートバインディングを通してサービスを公開する

Twelve-Factor Appは自己完結し、ポートにバインドすることでHTTPをサービスとして公開し、そのポートにリクエストが来るのを待つようにします。これにより、環境ごとのサーバ設定が不要になります。

8.並行性

プロセスモデルによってスケールアウトする

  • Twelve-Factor Appにおけるプロセスの考え方は、例えば、HTTPリクエストはWebプロセスによって処理し、時間のかかるバックグラウンドタスクはワーカープロセスによって処理する。
    つまりこの場合、バックグラウンドタスクのワーカープロセスをスケールアップするのではなく、スケールアウトすることで性能向上をさせます。

  • Twelve-Factor Appプロセスの性質は、並行性を高める操作が単純かつ確実なものであることを指しています。

9.廃棄容易性

高速な起動とグレースフルシャットダウンで堅牢性を最大化する

Twelve-Factor Appのプロセスは、廃棄容易 である必要があります。(即座に起動・終了することができる。)

この性質により、素早く柔軟なスケールと、コードや設定に対する変更があった場合に、素早いデプロイを容易にし、本番デプロイの堅牢性を高めることにつながります。
そのため、プロセスは起動時間を最小化するよう努力するべきであると書かれています。

理想的 => 1つのプロセスは、起動コマンドが実行されてから数秒間でリクエストやジョブを受け取れるようになるべきである。

10.開発/本番一致

開発、ステージング、本番環境をできるだけ一致させた状態を保つ

Twelve-Factor Appでは、継続的デプロイしやすいよう開発環境と本番環境の差分を小さく保つ必要があります。

よく現れる、開発環境と本番環境の差分(ギャップ)は3つの領域に分けられます。

  • 時間のギャップ: 開発者が編集したコードが本番に反映されるまで数日、数週間、時には数ヶ月かかることがある。
  • 人材のギャップ: 開発者が書いたコードを、インフラエンジニアがデプロイする。
  • ツールのギャップ: 本番デプロイではApache、MySQL、開発者がNginx、SQLiteのように別のものを使う場合がある。

11.ログ

ログをイベントストリームとして扱う

サーバーベースの環境では、ログは一般的にディスク上のログファイルに書き込まれる。
Twelve-Factor Appは、アプリケーションの出力ストリームの送り先やストレージについて一切関知しない。
アプリケーションは、ログファイルに書き込んだり管理せずに、それぞれの実行中のプロセスはイベントストリーム(標準出力)にバッファリングせずに書きだし、ツールで1箇所に集約する。

12.管理プロセス

管理タスクを1回限りのプロセスとして実行する

1回限りの管理プロセス(例: Djangoにおける manage.py migrateやRailsにおけるrake db:migrateによる、データベースのマイグレーションを実行)は、アプリケーションの通常の長時間実行されるプロセスと全く同じ環境で実行されるべきです。

管理用のコードは、同期の問題を避けるためにアプリケーションコードと一緒にデプロイされるべきです。

引用/参考文献

https://12factor.net/ja/

Discussion