BETA

The Twelve-Factors App

投稿日:2019-11-19
最終更新:2019-11-19

The Twelve-Factors Appって?

Herokuのエンジニアが提唱した、ソフトウェアサービスの方法論だそうです。
お仕事でAWSを使う関係で、コンテナに関してあれこれ調べたら出てきました。
Dockerが使いにくいと感じたらこれに従ってない場合がほとんどらしいので、とりあえずかみ砕いてみましょう。
ちなみに日本語版の原文らしきものはこちら

コードベース

コードベースとアプリケーションの間には、常に1対1の関係がある。
コードベースはGitやSVNにおけるリポジトリのこと、アプリケーションはサービス(「〇〇サービス」と名付けられる)のことを指しているかと思います。
例えば、GitHubでコードを管理している「HOGEサービス」がある場合、コード管理の中枢たるリモートリポジトリ(コードベース)とその管理対象たる「HOGEサービス」(アプリケーション)は1対1となります。
なお、複数のバージョンが存在した場合でも、同一のサービスなら1つのアプリケーションとみなします。

ここで実際に動作するサービスは、アプリケーションではなく「デプロイ」と呼ばれます。通常、「ローカル環境」「検証環境」「本番環境」の3環境以上でサービスが動作するかと思いますが、これらはすべて「デプロイ」となります。

依存関係

Twelve-Factor Appは、システム全体にインストールされるパッケージが暗黙的に存在することに決して依存しない。
これは、アプリケーションが動作するために必要な外部パッケージおよびライブラリは、すべて何らかの方法で明文化されていることを示します。明文化の方法としては、RubyならGemfile、Gradle(Java)ならbuild.gradleが挙げられます。
これはつまり、誰が見ても必要な外部パッケージおよびライブラリが明確であることを要求しています。
このように明文化されていることで、新規にチームに加わったメンバーでもセットアップが容易になります。

設定

Twelve-Factorは、設定をコードから厳密に分離することを要求する。
ここでいう設定とはアプリケーション外部に関する設定であり、アプリケーション内部で完結する設定は含めません。
アプリケーションのコードに対し、外部環境に関する設定値は変動しやすいものです。本番環境と検証環境でことなる設定値を使いたい、ということはよくあるかと思います。
このとき、設定値をコードベースに含めてしまうと、以下の問題が起こるかもしれません。

  • デプロイ(環境)の増加により、設定値の管理が複雑になる
  • 設定値に認証情報等の機密情報が含まれている場合、流出する危険性が高まる

この対策として、「設定値は環境変数に格納する」という方法が提案されています。
設定値を環境変数に格納する理由としては以下の通りです。

  • 誤って流出する危険性がほとんどない
  • 環境変数はOSに依存しない標準であるため、デプロイによっては存在しないということがない
  • 設定値を名前付きのグループで管理しようとしても上手くいかない(グループが山ほど増える)
    • 一方、環境変数はデプロイごとに独立して管理されるため、デプロイ増加につれて無理なく増えていく

昨今ではDockerfileに環境変数を定義して管理することも多いかと思います。
その場合、基本的な環境変数のみ記述しておいて、デプロイごとに変わる値については起動時の-eオプションで注入するのが適切かもしれません。

バックエンドサービス

Twelve-Factor Appのコードは、ローカルサービスとサードパーティサービスを区別しない。
ローカルサービスとは、自社で管理されるサービスのことを指します。またバックエンドサービスとは、アプリケーションに含まれず、ただしアプリケーションが利用するサービスのことです。
ローカルサービスとサードパーティサービスを区別しないとは、どちらのサービスでも問題なくアプリケーションが利用できるということ。すなわち、外部サービスに対しアプリケーションが疎結合であることです。
例えばローカルでアプリケーションを実行するときはMySQL、本番環境ではAmazon RDSを利用する、といったことが可能であるべきです。
デプロイごとに利用するサービスを切り替えたい場合は、先の項目通りに環境変数を利用しましょう。

ビルド、リリース、実行

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

  • ビルドステージは、コードを実行可能な塊へと変換する
    • 例えば、GitHubからコードをローカルにクローンし、Gradleやnpm(Yarn)等によるビルドを行うこと
  • リリースステージは、上記の塊をデプロイの設定と結合し、いつでも実行できる状態にする
    • 例えば、上記ビルドの成果物をAWSのコンテナに配置すること
  • 実行ステージは、アプリケーションを実行する

デプロイツールは通常、リリース管理ツールを提供し、以前のリリースにロールバックする機能を持つそうです。
また、すべてのリリースは一意のIDを持つべきだそうです。これは様々な理由があるかと思いますが、その一つはデプロイやロールバック等が容易かつ確実に、一切の誤解なく進められるようにするためでしょう。

ビルドはアプリケーションの開発者によって開始されます。そのため、ビルド途中で停止することがあっても開発者が対応できるため、複雑な工程であっても構いません。
一方、実行は自動で開始されます。よって、できるだけ可変部分を持たず、単純であるべきです。万一、真夜中に障害が発生した場合であっても自動復旧してくれなければ困りますから。

プロセス

Twelve-Factorのプロセスはステートレスかつシェアードナッシングである。
ステートレスはともかく、シェアードナッシングはあまり聞き覚えがありませんね。調べてみたところ、簡単に言うと各アプリケーションがネットワーク通信のみでつながっており、リソース的には独立していることを指すようです。ウィキペディア調べ。解釈を間違えているかもしれません。
ともあれ、アプリケーションは極力ステートレスであるべきということです。
例えばアプリケーションを保持するコンテナがなんらかの障害で止まってしまったとき、再起動しても作業中データがすべて吹き飛んでいたらと悲しいですね。
そうならないように、アプリケーションはステートレスに、一時データはアプリケーションには保存しないように設計する必要があります。

この章の前半だけ読んだところで「セッションは……JWT? ホントに?」と早とちりしてしまいましたが、ベストプラクティスとして有効期限を持つデータストアを利用するべきと述べられていますね。具体的な対象としてはRedis、AWSではElastiCacheが該当するかと思います。

ポートバインディング

Twelve-Factor Appは完全に自己完結し、Webに公開されるサービスを作成するために、コンテナが実行環境にWebサーバーランタイムを注入することを頼りにしない。WebアプリケーションはポートにバインドすることでHTTPをサービスとして公開し、そのポートにリクエストが来るのを待つ。
ポートバインドはあまり聞きなれない単語ですね。私だけかもしれませんが。
言い換えると、アプリケーション自身が特定のポートを占有し、そのポートに送信されるHTTPリクエストを処理する能力を持つべきであるということでしょうか。

いわゆるレガシーなJavaアプリケーションでは、オンプレミスのサーバーにApacheTomcatを持ち、warファイルを配置して起動することでアプリケーションを実行していました。そのため、アプリケーション(warファイル)単体では動作しません。
一方、本項目を満たすアプリケーションは単体で動作し、HTTPリクエストを受け取ることができます。
これがどのように作用するかというと、特定のWebサーバーランタイムが存在しない環境でもHTTPリクエストを処理することができるようになります。すなわち、他のアプリケーションが存在する環境に配置することで、バックエンドサービスとして運用することが可能になるのです。

並行性

Twelve-Factor Appではプロセスは第一級市民である。
これだけでは何を求められているのかよくわかりませんね。全体を読んでも求められていることがわからなかったので、カンニングしました

アプリケーションがステートレス・シェアードナッシングに構築できていれば、そのアプリケーションは容易にスケールアウトすることができます。すなわち、唐突に大量のアクセスが来た場合でも対応することができるのです。
もちろん、スケールインについても同様です。

ここで述べられている「デーモン化するべきではない」について、原文のリンクに理由が述べられていました。
簡単にまとめると、「デーモン化したプログラムなんて安全に再起動できるわけないし、プロセスリストとPIDを頼りに綱渡りすることになるからお先真っ暗だよ?」ってところでしょうか。

廃棄容易性

Twelve-Factor Appのプロセスは廃棄容易である、すなわち即座に起動・終了することができる。
先の並行性と同じような内容ですね。
逆に言えば、アプリケーションを即座に起動・終了することが可能ならば、アプリケーションのスケールアウト・スケールインが容易に行えるはずです。
ただし、リクエスト処理の途中で終了してしまえば、ユーザーにとって「サーバーが異常終了した」ように見えてしまいます。
そのため、処理中のリクエストをすべて終えてからアプリケーションを終了させる必要があります。この処理をGraceful Shutdownと呼びます。

開発/本番一致

Twelve-Factor Appでは、継続的デプロイしやすいよう開発環境と本番環境のギャップを小さく保つ。
開発環境と本番環境には以下の3領域でギャップが発生すると述べられています。

  • 時間のギャップ
    • 開発者がコードを編集してから、本番環境に反映されるまでの時間的ギャップ
  • 人材のギャップ
    • コードを編集する開発者と、本番環境に配置する人材の知識的ギャップ
  • ツールのギャップ
    • 開発環境で用いられているツールと、本番環境で用いられているツールのギャップ(Webサーバー、DB、OS等)

当然、上記ギャップが大きいほどアプリケーションが正常に動作しない可能性が高まります。開発はWindows、本番環境はLinuxで上手く動かないなんてことはよくあったかと思います。
よって、開発者は上記ギャップを最小限にとどめ、極力本番環境と近しい環境で開発を行うべきと述べられています。最近ではDockerやVagrant等の仮想環境システムが気軽に利用できるようになったため、本項目の要求を満たすのは比較的容易になったのかなと思います。

ログ

Twelve-Factor Appはアプリケーションの出力ストリームの送り先やストレージについて一切関知しない。
いいから全部標準出力に垂れ流せ、ということですね。
ローカル環境における開発の場合、標準出力へのログは開発者が目視で確認でき、開発の一助となります。
一方で本番環境等の場合、出力されたログは専門のログ管理システムによって管理されます。これにより、出力されたログに対し柔軟に対処することが可能になります。原文では以下の3項目が例示されています。

  • 過去の特定のイベントを見つける
  • 大きなスケールの傾向をグラフ化する
    • 単位時間当たりのリクエスト数等の傾向を可視化する
  • ユーザーによって定義されたヒューリスティクスに基づき、素早くアラートを出す
    • 経験則で得られた条件に基づいたルールを設定することで、アプリケーションの異常に気付きやすくなる

アプリケーション自身がログ管理機能を持ち、思い思いのところにログが分散するより、一括して管理した方が大きなメリットがあるということですね。

管理プロセス

1回限りの管理プロセスは、アプリケーションの通常の長時間実行されるプロセスと全く同じ環境で実行されるべきである。
DBマイグレーションなどの単発実行されるプロセスについても、アプリケーションが配置される本番環境と同様の環境で行うべきということですね。
おそらく環境の違いに起因する不具合を回避することが目的だと思いますが、明確な表記はありませんでした。明文化されていないということは、そこまで重大な理由では無いかと思いますが、気になりますね。

まとめ

本12項を満たすアプリケーションの場合、モダンなクラウドプラットフォームへのデプロイが容易になるようです。
モダンなアプリケーション開発ではクラウドを利用するかと思いますので、しっかり頭に入れておきたいところですね。

参考

Developers.IO
The Twelve-Factor App(日本語訳)
nazolabo

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

だらだら更新していきたい技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!