36. The Twelve-Factor App
【所要時間】
4時間42分(2018年8月19,20日)
【概要】
開発を成功させる12要素
【要約・学んだこと】
Introduction
- オートメーションのセットアップに、declarative formatを使う。これはプロジェクトの新メンバーの時間、コストの節約のためだ。
- 基本となるオペレーティングシステムと明確な契約を結び、実行環境間の移植性を最大にする。
- 最新のクラウドプラットフォームでの展開に適しており、サーバーやシステムアドミニストレーションの必要性を排除する。
- 開発と生産の違いを最小限にし、最大の敏捷性のために継続的な環境をつくる。
- ツール、アーキテクチャ、開発の実践に重大な変更なしでスケールアッップができる。
twelve-factorの方法論は、任意のプログラミング言語で書かれた、任意のバッキングサービスのコンビネーションを使うアプリケーションに適用できる。(データベース、キュー、メモリーキャッシュなど)
The Twelve Factors
I. Codebase
コードベースは更新、多くのの管理の追跡をする。
Git, Mercurial, or Subversion などで常にバージョンコントロールをする。これらはレポジトリとして知られ、code repo, repoなどと通称される。
コードベースは単一のrepo(Sbversionのような集中管理されたリビジョン管理システム)か、ルートコミット(Gitのような分散リビジョン管理システム)を共有するrepoのセットだ。
- コードベースが複数あるのはアプリではなく、ディストリビューテッドシステムだ。ディストリビューテッドシステムのそれぞれのコンポーネントがアプリで、それぞれが12の要素に個別に従うことができる。
- 同じコードを複数のアプリでシェアするのは、12要素に反する。解決策はシェアコードをdependency managerを通じてライブラリに組み込むことだ。
アプリごとに1つのコードベースだけがある。しかし、アプリにはたくさんのデプロイが存在する。デプロイはアプリで動作中のインスタンスだである。これは通常本番サイトと1つ以上のステージングサイトだ。さらに、全てのディベロッパーはローカル開発環境に実行アプリのコピーをもち、それらもディプロイトみなす。
コードベースは全てのディプロイを通して同じだが、デプロイごとに異なるバージョンがアクティブになるだろう。例えばディベロッパーがまだステージングにディプロイされていないいくらかのコミットを持つ。ステージングはまだプロダクションにディプロイされていないコミットをいくつか持つ。しかし、全ては同じコードベースで共有される。そのため、同じアプリで異なるディプロイであると認識できる。
II. Dependencies
Explicitly declareとisolate dependencies
ほとんどのプログラミング言語は、サポートライブラリの配布のために、パッケージシステムを提供している。パッケージングシステムを通じてインストールされるライブラリは、システムワイド(site-packageとして知られる)にインストールされるか、ディクショナリのアプリ(vendoring、bundlingとしてしられる)にスコープされる場合がある。
12 factor appはimplicitなシステムワイドパッケージの存在を信頼しない。全てのdependencyをdependency declaration manifestを通じて、完全に、正確に宣言する。さらに、implicit dependencyが周囲の環境から守れないことを確かにする。完全なるexplicit dependencyの仕様は、プロダクションと開発の両方で同様に適用される。
explicit dependency宣言の1つのメリットは、アプリの新しい開発者が簡単にセットアップできることだ。新しい開発者は開発マシンのアプリのコードベースを自分の開発マシンにチェックアウトできる。前提条件は言語ランタイムと、depencency managerがインストールされていることだ。それらはビルドコマンドでアプリのコードを実行するために必要な全てのものをセットアップできる。
12 factorアプリもまた、implicitなシステムツールの存在を頼らない。アプリがシステムツールを必要とするなら、ツールをアプリに組み込むべきだ。
III. Config
環境内にconfigを格納する。
アプリのconfigはdeploy(staging, production, developer environmentsなど)の全てだ。これは下記を含む。
- データベースのリソースハンドル、他のbacking services
- Amazon S3やTwitterなどの外部サービスへの認証情報
- deployのための標準ホスト名などのpre-deploy value
アプリはコードにconstantとしてconfigを蓄えることがある。これは12 factorに違反する。コードからconfigは厳密に分けるべきだ。configはコード毎にことなるが、コードはそうではない。
アプリが全てのconfigを正しくコードの外部に保存しているかどうかは、コードベースが認証情報を漏洩することなく、すぐにでもオープンソースにできるかどうかだ。
configの定義はアプリの内部configを含まないことに気をつける。
他のconfigのアプローチは、バージョンコントロールにチェックされていないconfig fileを使うことだ。
レポジトリに保存されているconstantを使うよりは大きな進歩だが、弱点もまだある。
12 factor app はenvironment variableにconfigを格納する。(よくenv vars or envと呼ばれる) Env varはコードを変えることなく簡単にdeployを変えられる。config fileとはことなり、コードレポに謝ってチェックインすることはほとんどない。また、custom config fileやJAVA System Propertyや他のconfigメカニズムとは異なり、言語やOSに依存しない。
config managementのもう1つの側面はgroupingだ。アプリはconfigを特定のdeployの後に名前付きのグループにまとめる。(environmentと呼ばれる)Railsではdevelopment, test, production 環境などだ。
この方法は綺麗にスケールしない。プロジェクトが拡大すると、アプリのdeploy管理が非常に不安定になる。
12 factorアプリでは、 env varは細かい管理で、env varはお互い直行する。それらは決してenvironmentとして同じグループにならないが、代わりにそれぞれのdeployで独立して管理される。これはアプリが生涯にわたり多くのdeployへと自然拡大するにつれて、スムーズにスケールアップするモデルだ。
In a twelve-factor app, env vars are granular controls, each fully orthogonal to other env vars. They are never grouped together as “environments”, but instead are independently managed for each deploy. This is a model that scales up smoothly as the app naturally expands into more deploys over its lifetime.
IV. Backing services
添付リソースとしてbacking serviceを取り扱う
backing service(バックエンドサービス)とは通常操作の一部としてネットワークを消費するアプリのサービスことだ。データストア(MySQL、CouchDB)や、メッセージ/キューイングシステム(RabbitMQやBeanstalkd)、メールを送信するためのSMTPサービス(Postfix)、キャッシュシステム(Memcached)などがある。
従来データストアなどのbacking serviceはdeployされたアプリと同じシステム管理者によって管理されていた。
12 factor app のコードは、ローカルとサードパーティのサービスを区別しない。アプリにとってはどちらもアタッチされたリソースで、configに格納されたURL, locater/credentialを通じてアクセスする。12 factor アプリのdeployは、アプリのコードのいかなる変更もなしで、サードパーティ(Amazon RDSなど)によって1つに統合されたローカルMySQLなどに切り替えることができるべきだ。
ローカルSMTPサーバーも同様にコードの変更なしでサードパーティのSMTPサービスに切り替えができるべきだ。どちらの場合も、configのリソースハンドルだけが変更を必要とする。
それぞれのbacking serviceはリソースだ。例えばMySQLデータベースはリソースだ。2つのMySQLデータベースは2つの異なるリソースとみなすことができる。12 factor appは、これらのデータベースをアタッチされたリソースとして扱う。これはアタッチされたリソースのdeployが、疎結合であることを示している。
リソースはのちにdeployからアタッチしたり、外したりすることができる。例えば、ハードゥエアに問題があり、アプリのデータベースの動作がうまくいかないとき、管理者は最新のバックアップから新しいデータベースサーバーをつくる。現在のプロダクションデータベースは切り離し、新しいデータベースをつける。これら全てにコードの変更は不要だ。
V. Build, release, run
buildとrun stageを厳密に分ける
codebaseは3つのステージを通じて(non-development)deployに変換される。
- build stageはcode repoをbuildとしてしられる実行可能なバンドルへと変えることである。deployment processで指定されたコミットのコードのバージョンを使い、build stageではベンダー dependencyが取得され、バイナリとアセットがコンパイルされる。
- release stageではbuild stageで作られたビルドを受け取り、deployの現在のconfigに結合する。releaseの結果は、buildとconfigを含み、すぐにexecution environmentで実行される準備ができる。
- run stage(runtimeとしても知られる)では、選択されたリリースに対してアプリのプロセスの一部を起動することで、execution environmentで作られたアプリを実行する。
12 factor appはbuild, releaseとrunstageを厳密に区別する。例えば、runtimeでコードを変えることは不可能だ。なぜならbuild stageにそれらの変更を伝える方法がないからだ。
deployment toolは基本的にrelease management toolを提供する。最も注目することは、以前のreleaseにロールバックする機能だ。
全てのリリースは、タイムスタンプ(2011–04–05–20:32:17)や、incrementing number(v100など)などの独自のリリースIDをいつも持つべきだ。リリースは追加専用元帳であり、リリースは一度作られると変更できない。全ての変更は新しいリリースを作らなければならない。
ビルドはアプリのディベロッパーが新しいコードをdeployするときはいつでも開始される。一方runtimeの実行は、サーバーリブート、クラッシュしたプロセスがプロセスマネージャーによってリスタートした時に自動で起こる。そのため、run stageは可能な限り可変部を少なくするべきである。
なぜならアプリの実行を妨げる問題が起こると、開発者が待機していないときにアプリが壊れる結果となるからだ。ビルドステージはもっと複雑でも問題ない。なぜならビルドステージのエラーは常にdeployを実行しているディベロッパーの前で発生するからだ。
VI. Processes
アプリは1つ以上のステートレスなプロセスとして実行する
アプリは実行環境の中で1つ以上のプロセスとして実行される。
最も単純なケースでは、コードは単体のスクリプトで、実行環境はディベロッパーのラップトップにインストールされた言語 runtimeで、プロセスはコマンドライン経由で実行される。
一方で、洗練されたアプリのプロダクションdeployは、多くのプロセスタイプを使用し、0以上のruntimeプロセスにインスタンス化されることもある。
12 factorプロセスはステートレスでシェアナッシングである。接続する必要のある全てのデータは、ステートフルなバッキングサービス(データベースなど)に格納しなければならない。
メモリスペースやプロセスのファイルシステムは、短い単一のトランザクション内のキャッシュとして利用できる。12factor appは、メモリーやディスクにキャッシュされたものが、リクエストジョブにおいて利用できることを想定していない。それぞれのタイプランニングの多くのプロセスは、将来のリクエストが異なるプロセスによって処理される可能性が高い。
django-assetpackagerなどのアセットパッケージャーは、コンパイルドアッセトのキャッシュとしてファイルシステムを使う。12 factor appはbuild stage中にこのコンパイルをすることを好む。
ウェブシステムには”sticky session”に頼るものがある。アプリのプロセスのメモリーにユーザーセッションデータをキャッシュし、同じ訪問者の将来のリクエストが同じプロセスでルートされることを期待している。
sticky sessionは12factorに違反するので、使うべきではない。セッションステートデータは、Memcached や Redisなどの有効期限を提供するデータストアのいい候補である。
VII. Port binding
ポートバインディング経由でサービスを出力する。
ウェブアプリはウェブサーバーコンテナ内で実行されることもある。例えばPHPアプリはApache HTTPD内部でモジュールとして実行されるかもしれない。また、Java アプリはTomcat内部で実行されるかもしれない。
12 factor appは、完全に自己完結で、ウェブサーバをweb-facingサービスを作るために実行環境に挿入し、実行することに頼らない。ウェブアプリはポートにバインディングすることでサービスとしてHTTPを出力し、そのポートにリクエストが来るのを待つ。
ローカルの開発環境では、ディベロッパーが出力されたサービスにアクセスするためにURLを訪れるが、deploymentでは、ルート層が公共のホストネームからポートバインドのウェブプロセスへのリクエストをルートする。
これはdependency宣言をつかってアプリにウェブサーバーライブラリを加えることによって、典型的に実行される。これはユーザー空間、つまりアプリのコード内で完結する。リクエストを処理するための実行環境と契約は、ポートをバインドすることだ。
HTTPはサービスだけでなくポートバインディングによって出力される。ほぼ全てのサーバーソフトウェアはポートをバインドするプロセスを経由して動作し、リクエストが来るのを待つ。
ポートバインディングアプローチは、ポートバインディングの方法によって、1つのアプリが他のアプリのバッキングサービスになりうるという意味だ。バッックエンドアプリへのURLを提供し、consuming アプリのconfigにリソースハンドルとして格納すれば良い。
VIII. Concurrency
プロセスモデル経由でスケールアウトする。
全てのコンピュータプログラムは、一度実行すると、1つ以上のプロセスで表現される。ウェブアプリはプロセス実行の多様性がある。ランニングプロセスは、アプリの開発者にはほとんど見えない。
12factor appでは、プロセスは一級国民だ。12factor appのプロセスは、サービスデーモンを実行するためのUnixプロセスモデルから強いヒントを得ている。このモデルを使うことで、ディベロッパはそれぞれのworkタイプをprocess タイプへと割り当てることで、多様なワークロードを処理するためにアプリを設計することができる。
これはランタイムVM内のスレッド、またはEventMachine, Twisted, Node.js などのツールで発見されるasync/evented モデルによって、自身の内部多重化を扱うことから個々のプロセスを排除するのではない。
しかし、個々のVMは大きくなる(垂直スケール)のみなので、アプリは複数の物理マシンで複数のプロセスにまたがって実行される必要がある。
プロセスモデルはスケールアウトが必要な時に本当に輝く。12factor appのshare-nothingでプロセスの水平に分割できる性質は、より並行性があることが、単純で信頼できる操作性を加えるという意味だ。プロセスタイプの配列や、それぞれのタイプのプロセス数の配列は、プロセスフォーメーションと言われる。
12factor appのプロセスはデーモン化するべきではなく、またPIDファイルを書き出すべきでもない。代わりに、出力ストリームを管理し、ユーザーが意図するリスタートやシャットダウンを処理するために、OSのプロセスマネージャーを頼る。
IX. Disposability
速いスタートアップと、穏やかなシャットダウンで堅牢性を最大化する。
12factor appのプロセスは使い捨てだ。つまり即座にスタート、ストップすることができる。これにより、迅速なelastic scaling, コードやconfigの変更の迅速なdeployment、プロダクション deployの堅牢性が促進される。
プロセスはスタートアップの時間の最小化に努める。理想的には、プロセスはコマンドが実行されてからプロセスが完了し、リクエストやジョブを受け取る準備ができるまで数秒だ。短いスタートアップ時間はreleaseプロセスやスケーリングアップの敏捷性をさらに高める。そして堅牢性を助ける。なぜならプロセスマネージャーは保証された時に物理マシンに簡単にプロセスを移動できるからだ。
プロセスはSIGTERM signalをプロセスマネージャーから受け取る時、穏やかにシャットダウンする。Webプロセスでは、サービスポートのリッスンすることを中止し(これによって新しい要求を拒否することによって)、穏やかなシャットダウンが達成され、現在の要求が完了してから終了する。このモデルの暗黙的に、HTTPのリクエストが短い(数秒以内)だとしている。そうでなければ、ロングポーリングの場合、クライエントが接続を失った時に、再接続を試みる必要がある。
ワーカープロセスでは、穏やかなシャットダウンが処理中のジョブをワークキューに返すことで達成される。
このモデルの暗黙的な部分は、全てのジョブがreetrantで、典型的にトランザクションの結果にラッピングされることや、操作が偶発的になることで達成される。
下層ハードウェアの障害に関しては、プロセスもまた突然死に対して堅牢であるべきだ。SIGTERMによる穏やかなシャットダウンよりも起こることはかなり少ない一方で、発生する可能性はまだある。推奨されるアプローチは、クライエントが接続解除したり、タイムアウトしたときにキューにジョブを返すようにBeanstalkedなどの堅牢なキューイングバックエンドを使うことだ。
いずれにせよ、12 factor jobは予期せぬ穏やかでない停止を処理できるように設計されている。Crash-only design はこのコンセプトをlogical conclusion に導く。
X. Dev/prod parity
開発、ステージング、プロダクションで可能な限り同じ状態にする。
歴史的に、開発(ディベロッパーがローカルのアプリのdeployで変更する)とプロダクション(エンドユーザーからアクセスされるアプリ実行中のdeploy)では実質的なギャップがある。これらのギャップは3つのエリアで表せる。
- The time gap:
ディベロッパーがコードでワークしたことが、プロダクションに反映されるのに数日、数週間、数ヶ月さえかかることがある。 - The personnel gap:
ディベロッパーがコードを書き、エンジニアがそれをdeployする。 - The tools gap:
ディベロッパーはNginx, SQLite, OSXなどのスタックを使い、プロダクションはApache,MySQL,Linuxなどを使いdeployする。
12 factor appは開発とプロダクションのギャップを少なく保つことで、継続的にdeployしやすいように設計している。
- Make the time gap small:
ディベロッパーはコードを書き、それを数時間、できれば数分でdeployする。 - Make the personnel gap small:
コードを書くディベロッパーはdeployに深く関わり、プロダクションでの動作を見る。 - Make the tools gap small:
開発とプロダクションをできるだけ小さく保つ
アプリのデータベース、キューリングシステム、キャッシュなどの Backing services は、dev/prod が1つのエリアにあることが重要だ。多くの言語は、異なるサービスタイプへのadapterを含むバッキングサービスにアクセスするのを簡単にするライブラリを提供する。
ディベロッパーは時々とても軽いバッキングサービスをローカル環境で使うことに魅力を感じ、一方でプロダクションではより堅牢で重大なバッキングサービスを使う。
12 factor ディベロッパーは開発とプロダクションで異なるサービスを使うことに抵抗する。違うバッキングサービスを使うということは、わずかな非互換性が現れ、開発でのテストは合格し、プロダクションではうまく動かないということを引き起こす。この種のエラーは、断続したdeployを妨げる摩擦をうむ。
軽量のローカルサービスは以前ほど魅力がない。インストールのコストは、dev/prod 一致と継続したdeployの利益を考えると低い。
現代の異なるバッキングサービスのアダプタは使い勝手が良い。なぜならバッキングサービスへのポーティングを和らげてくれるからだ。しかし、アプリのdeploy全て(開発環境、staging, プロダクション)は、同じタイプ、同じバージョンのバッキングサービスを使うべきだ。
XI. Logs
イベントストリームとしてログを扱う。
ログは実行中のアプリを可視化する。サーバーベース環境では、ログは一般的にディスク(“logfile”)上のファイルに書かれる。しかし、これはアウトプットフォーマットに過ぎない。
ログは集約されたストリームで、全ての実行中のプロセスとバッキングサービスのストリームの出力から、時系列順に集められたイベントである。生のフォームのロゴは、基本的に1行に1イベントのテキストフォーマット(例外のバックトレースは複数行になる可能性もある)だ。ログは始まりと終わりを固定せず、アプリが実行しているかぎり流れ続ける。
12 factor appは決してルーティングや、それの出力ストリームのストレージ自体に関与しない。アプリはログに描こうとしたり、ログファイルを管理しようとしない。代わりに、それぞれのランニングプロセスが、そのイベントストリームをstdoutにバッファせずに書く。ローカルでの開発中に、ディベロッパーはアプリの動作を監視するためにストリームのターミナルを前面に表示する。
stagingまたはプロダクションdeployは、それぞれのプロセスのストリームが、実行環境に捕らえられ、アプリの他全てのストリームと一緒に集められる。そして表示やロングタームアーカイブのために1つ以上の最終目的地にルートされる。
これらのアーカイブの目的地は、アプリから見ることも設定することもできない。代わりに、実行環境で全て管理できる。オープンソースルーターズ(LogplexやFluented)はこの目的で使われる。
アプリのイベントストリームはファイルにルートされたり、ターミナルのtailを経由してリアルタイムに見ることができる。最も重要なのは、Splunkのようなストリームはログインデックスや解析システムや、Hadoop/Hiveなどの一般的なデータウェアハウスシステムに送られることができる。
これらのシステムは長期のアプリの動作の挙動を確認するために、強い力や柔軟性を与える。それらは下記を含む。
- 過去の特定のイベントを見つける。
- 大きなスケールのトレンドをグラフ化する(1分毎といった)
- ユーザー定義の経験則によって、アクティブアラートを出す。(1分毎のエラー数がある闘値を超えた時にアラートを出すなど)
XII. Admin processes
管理タスクを1回だけのプロセスとして実行する。
プロセスフォーメーションは、それが実行された時に()アプリの通常の役割を行うために使われる一連のプロセスだ。他に、ディベロッパーは1回だけの管理、メンテナンスタスクをアプリに行うことがしばしばある。
- データベースの移行を実行
- 任意のコードにコンソール(REPL shellとして知られる)を実行したり、生きたデータベースに対してアプリのモデルを調査する。大抵の言語では、argumentなしでinterpreterを実行することでREPLを提供するが、他のコマンドを持つ場合もある。
- アプリのレポジトリに1回限りコミットされたコードを実行する。
1回だけのアドミンプロセスは、アプリのロングランニングプロセスと同じ環境で実行されるべきだ。それらはreleaseに対して実行し、そのreleaseに対して実行するプロセスとして同じコードベースとconfigを使う。アドミンコードは同期問題を避けるために、アプリコードト一緒に送られる。
同じdependency isolationテクニックを全てのプロセスタイプで使うべきだ。
12 factorはREPL shellをそのままの形で提供する言語を強く好む。それは1回限りのスクリプトの実行を簡単にする。ローカルdeployでは、ディベロッパーは1回だけのアドミンプロセスをアプリのチェックアウトディレクトリの内部のシェルコマンドで直接呼び出す。プロダクションdeployでは、ディベロッパーがsshや、deployの実行環境でそのようなプロセスを実行することで、他のリモートコマンド実行メカニズムを使うことができる。
【わからなかったこと】
とくになし
【感想】
コード以外にも気を使う部分がたくさんあるようだ。