Amazon Web Services ブログ
Amazon ECS と AWS Fargate を利用した Twelve-Factor Apps の開発
この記事は、Developing Twelve-Factor Apps using Amazon ECS and AWS Fargate を翻訳したものです。
本投稿は、Solutions Architect の Sushanth Mangalore と Chance Lee により寄稿されました。
はじめに
The Twelve-Aactor App と呼ばれる方法論は、モダンでスケーラブル、かつメンテナンス性に優れた Software-as-a-Service アプリケーションの構築に役立ちます。この方法論はテクノロジーにとらわれず、クラウドネイティブアプリケーションを開発するためのアプローチとして広く採用されています。
AWS で Twelve-Factor アプリケーションを開発するには、いくつかの方法があります。コンテナ技術をベースにしたソリューションは、Twelve-Factor アプリケーションに自然に適合します。この投稿では、Amazon Elastic Container Service (Amazon ECS) の Fargate 起動タイプを使用して開発されたサンプルソリューションを通じて、この方法論の要点を説明します。もし AWS 上でコンテナソリューションをすでに構築し始めている場合、この投稿で提供される情報は Twelve-Factor のコンテナアプリケーションアーキテクチャを計画するためのガイダンスとして使用できます。ここで説明したコンセプトは、EC2 起動タイプの ECS ベースのアプリケーションや、Amazon Elastic Kubernetes Service (Amazon EKS) ベースのアプリケーションにも同様に適用できます。
ソリューションの概要
サンプルのソリューションでは、コンテナベースの Python アプリケーションを使用し、データストアとして DynamoDB テーブルを使用しています。このソリューションでは Amazon ECS とともに、継続的インテグレーションと継続的デリバリ (CI/CD) のための AWS サービス、コンテナレジストリのための Amazon Elastic Container Registry (Amazon ECR)、アプリケーション構成をホストするための AWS AppConfig、ログを管理するための Amazon CloudWatch Logs を、それぞれ組み合わせて使用しています。AWS Virtual Private Cloud (VPC) がネットワークの設定を管理し、Amazon Application Load Balancer (ALB) が外部からのトラフィックを Amazon ECS サービスにルーティングします。次のセクションでは、サンプルソリューションのさまざまな部分が、方法論で推奨されているベストプラクティスをどのように実現しているかを詳細に説明します。このサンプルソリューションのアーキテクチャ概要を以下に示します。
サンプルソリューションの Twelve-Factor
コードベース (Codebase) – すべてのコードをソースコントロールシステムで管理し、アプリケーションごとに 1 つのコードベースを使用する必要があります。つまりソリューションに 2 つのコンテナアプリケーションがある場合は、Git のようなソースコントロールシステムにおいて 2 つのコードを別々のコードリポジトリに格納する必要があります。このソリューションでは、メインのコンテナアプリケーションのソースコードリポジトリとして AWS CodeCommit を使用します。
コンテナアプリケーションには、コンテナイメージと呼ばれるバイナリが存在します。各コンテナアプリケーションは、コンテナイメージレジストリ内の独自のリポジトリに格納されるコンテナイメージを持つ必要があります。サンプルソリューションでは、プライベートな Amazon ECR リポジトリをコンテナイメージレジストリとして使用しています。
依存関係 (Dependencies) – アプリケーションの依存関係は明示的に宣言されるべきであり、実行環境に由来する依存関係を定義することはできません。コンテナベースのアプリケーションでは、この制限を Dockerfile で解決することができます。Dockerfile は、コンテナイメージを作成するための指示をテキストベースでまとめたものです。アプリケーションの依存関係をプログラミング言語のパッケージシステムのマニフェストファイルで宣言し、Dockerfile の指示を使ってインストールすることができます。ここでは Python のパッケージインストーラーである Pip を、2 つの重要な依存関係である boto3 と Flask をインストールするためのソリューションの一部として使用しています。
設定 (Config) – Twelve-Factor の方法論では、構成をコードから厳密に分離することを規定しています。デプロイ環境によって異なる設定を、アプリケーションコードと一緒に保存してはいけません。アプリケーションのコードは、どのデプロイ環境でも同じである必要があります。デプロイのための特定の設定は、環境変数を使用してデプロイ環境によって提供されます。デプロイパイプラインの異なるステージでは、特定の環境設定を提供することができます。ECS アプリケーションのコンテナ定義で環境変数を指定できます。
バックエンドサービス (Backing services) – アプリケーションではそのビジネス機能を実行するために使用するサービスを、アタッチされたリソースとして扱わなければなりません。これは RDS データベース、DynamoDB テーブル、S3 バケット、あるいは AWS 以外のサービスの場合もあります。アタッチされたリソースとは、これらのリソースが構成としてアプリケーションに提供されることを意味しています。アプリケーションはアタッチされたリソースを認識しますが、それに緊密に結合されているわけではありません。DynamoDB のテーブルの場合は、リソースの識別子であるテーブル名を認識できれば十分です。サンプルソリューションでは、DynamoDB テーブルの名前は AppConfig から取得しているので、アプリケーションのダウンタイムなしにテーブルを別のものに切り替えることができます。これは、A/B テストを実現するために異なるデータセットを使って実験するようなシナリオで有用です。
ビルド、リリース、実行 (Build, release and run) – AWS CodePipeline は、Twelve-Factor アプリケーションで推奨されているように、デプロイのためのビルド、リリース、および実行ステージを分離するメカニズムを提供します。ターゲットとなる実行環境に新しいコードをリリースする際には、まずアプリケーションコードをデプロイするアーティファクトとしてパッケージ化します。コンテナベースのアプリケーションの場合は Docker イメージがそれに相当します。この役目はビルドステージの一部であり、このソリューションにおいてはビルドステージに AWS CodeBuild を使用してコンテナイメージを Amazon ECR にプッシュします。次にパイプラインは、デプロイするアーティファクトを環境固有の構成と組み合わせて、リリースを形成します。サンプルソリューションでは、環境固有の変数はコンテナ定義で設定されています。ランタイム中に変更される可能性がある動的な構成は、AppConfig から供給されます。最後のステップでは、パイプラインは ECS と Fargate を使用してアプリケーションをデプロイします。
プロセス (Processes) – Twelve-Factor アプリケーションは、1 つまたは複数のステートレスなプロセスとして構築する必要があります。コンテナは、ステートレスで不変であるように設計されています。ほとんどのユースケースでは、コンテナごとに 1 つのアプリケーションプロセスを実行することが推奨されます。どのような状態も、データベースのようなバッキングストアに持続させる必要があります。実行ランタイムのメモリやローカルディスクは、リクエスト間で永続的な状態を維持するべきではありません。サンプルソリューションでは、1 つのステートレスな Python アプリケーションプロセスがコンテナ内で実行され、API エンドポイントを公開します。
ポートバインディング (Port binding) – Twelve-Factor アプリケーションは、実行環境のポートにバインドすることで、他のアプリケーションから利用できるようにする必要があります。コンテナベースのアプリケーションは、Dockerfile の EXPOSE 命令によってポートを公開します。ECS タスク定義のコンテナの定義では、ホストポートとコンテナポートのマッピングを指定することができます。Fargate 上で動作するアプリケーションは、各タスクが独自のネットワークインターフェースを取得する awsvpc ネットワークモードを使用します。つまり、コンテナポートを指定するだけでよく、ホストポートのバインディングは必要ありません。このサンプルソリューションでは、メインの Python アプリケーションプロセスに組み込まれた Flask サーバを使用して、コンテナ実行環境のポート 80 にバインドします。
並行性 (Concurrency) – コンテナベースのアプリケーションは、コンテナプロセスのインスタンスを増やしていくことでスケールアウトすることができます。これは、Twelve-Factor の方法論で規定されているスケーリングのモデルでもあります。ECS のスケーリングの単位はタスクです。ECS のサービスでは、アプリケーションが受ける負荷を確実に処理するために、実行すべきタスクに応じた数を定義します。またフォールトトレランスを実現するためには、サービスには複数のタスク数を指定して、ECS において失敗したタスクが自動的に再起動されるようにします。サービス定義内のタスク数は、アプリケーションのスケーリングニーズに応じて増減できます。このソリューションでは、ECS サービス内でメインアプリケーションタスクの 2 つのコピーを実行します。
廃棄容易性 (Disposability) – 高速な起動とグレースフルなシャットダウンは、コンテナベースのアプリケーションの利点です。Dockerfile を使用してコンテナイメージを構築するベストプラクティスに従うことで、起動時間を短くすることができます。またコンテナアプリケーションにおけるシャットダウンでは、緩やかな終了と突然の停止の両方を処理することが期待されます。ECS タスクが停止すると、SIGTERM とデフォルト 30 秒のタイムアウトが発生し、その後 SIGKILL が送信され、コンテナが強制的に停止されます。アプリケーションの要件に基づいて、stopTimeout パラメータを使用して SIGKILL シグナルが送信されるまでの待ち時間を設定することも可能で、Fargate では最大 120 秒まで設定できます。シャットダウンの一環として、アプリケーションはすべての未処理のリクエストの処理を終了し、適切な SIGTERM ハンドラで新しいリクエストの受け入れを停止する必要があります。こちらのブログ記事では、ECS によるグレースフルシャットダウンについて詳しく説明しています。
開発/本番一致 (Dev/prod parity) – Twelve-Factor App の本番環境と非本番環境は、可能な限り類似している必要があります。これにより、アプリケーションのコードが現実的な条件でテストされていることを保証します。これは、アプリケーションとバックグランドサービスの統合が異なる環境間で均一にテストされるように、バックグランドサービスの使用にも当てはまります。Docker は、一度ビルドしたら何度でもデプロイできるというコンセプトでこの考えを推進しています。したがって同じバージョンの Docker イメージを、非本番環境と本番環境の両方で使用できます。
サンプルソリューションの CloudFormation スタックには、カスタマイズ可能な環境タイプを表すパラメーターが含まれています。このパラメーターの値が prod の場合、デプロイパイプラインには追加のステージが作成され、リリースがステージングクラスタにデプロイされます。これによって本番環境にリリースする前に、ステージングクラスタでテストを実行できます。ステージングのデプロイの後には、手動の承認ステップがあります。ステージングリリースが承認されると、パイプラインは本番クラスタにデプロイされます。
ログ (Logs) – ログを静的なファイルではなく、イベントの連続的な流れとして扱うことで、ログ生成の連続的な性質に対応することができます。リアルタイムのログデータを取得、保存、分析することで、アプリケーションのパフォーマンス、ネットワーク、その他の特性について意味のある洞察を得ることができます。すなわち、アプリケーションが自らのログファイルを管理する必要があってはならないということです。タスク定義の logConfiguration パラメータにコンテナ用の awslogs ログドライバを指定することで、stdout と stderr の I/O ストリームを Amazon CloudWatch Logs の指定されたロググループに出力し、閲覧やアーカイブを行うことができます。さらに Amazon ECS 対応 FireLens では、タスク定義パラメータを awsfirelens ログドライバーで使用し、ログを他の AWS サービスやサードパーティのログアグリゲーションツールにルーティングして、ログの保存や分析を行うことが可能です。AWS では AWS for Fluent Bit の Docker イメージを提供していますが、ご自身の Fluentd や Fluent Bit のイメージを使用することもできます。
管理プロセス (Admin processes) – アプリケーションの管理やメンテナンス作業は、アプリケーションと同一の環境で行う必要があります。これらのメンテナンス作業は一般的に短時間で行われ、主なアプリケーションの通常のビジネス機能をサポートすることを目的としています。サイドカーコンテナは、メインのアプリケーションと並行して動作させることで管理機能を実現する一つの方法ですが、これらは長時間動作するアプリケーションにもなる可能性があります。それを避けるため、クラスタ内で設定された cron のようなスケジュールや間隔によってタスクを実行できる Amazon ECS タスクのスケジューリングは、管理機能に好適です。サンプルソリューションでは、毎日 1 回 DynamoDB テーブルのバックアップを作成するスケジュールされた ECS 管理タスクをイメージしています。この ECS 管理タスクは、その機能を実行してすぐに終了する 1 つのプロセスを実行する 1 つのコンテナを持っています。
サンプルソリューションの起動
サンプルソリューションは、こちらの GitHub リポジトリにある CloudFormation テンプレートを使って、ご利用の AWS アカウントで起動することができます。このテンプレートは、サンプルソリューションに必要なすべてのリソースを作成します。GitHub リポジトリのソースは、ご自身のアカウントの CodeCommit リポジトリを初期化し、デプロイパイプラインの起点となります。
スタックが正常に起動した後、AWS CodePipeline でスタックが作成したデプロイパイプラインが正常に実行されたことを確認する必要があります。EnvironmentName パラメータのデフォルト値である prod を使用した場合、パイプラインの実行は Deployment_Approval ステージで停止します。このステージで承認を行うことで、本番環境として機能する ECS クラスタへのデプロイが完了します。
パイプラインは、スタックが作成した CodeCommit リポジトリから最新のコードを取り出し、CodeBuild ステージで Docker イメージをビルドし、ECR リポジトリにプッシュします。ECS クラスタにデプロイされたアプリケーションは、インターネットに接続された Application Load Balancer を介して ECS サービスにトラフィックを流します。
出力セクションには、アプリケーションの URL が /hello エンドポイントとともに表示されます。
この URL にブラウザでアクセスすると、DynamoDB テーブルの情報を使って出力された Hello メッセージを確認できます。
その他の利用可能なアプリケーションエンドポイントは、
- / – ロードバランサーが使用するヘルスチェックのエンドポイントです。
- /refresh-config – AWS AppConfig から送られてくるアプリケーションの構成をリフレッシュするために使用します。
- /table-name – アプリケーションが使用している DynamoDB テーブルの名前を表示するためのものです。スケジュールされた管理タスクは、AWS AppConfig で設定されたこの値を使用して、DynamoDB テーブルの夜間バックアップを取ることができます。
以下の手順で、いくつかの要素を確認することができます。
- DynamoDB テーブルのバックアップを作成し、そのバックアップを新しい DynamoDB テーブルにリストアします。
- AWS マネジメントコンソールを使用して、DynamoDB アイテムのアプリケーションの任意の表示属性を変更します。例えば、アプリケーションの背景色を変更することができます。
- 次に、ホストされている AppConfig 構成を更新して、復元された DynamoDB テーブルの名前を使用するようにし、新しいバージョンの構成を展開します。
- アプリケーションの /refresh-config エンドポイントにアクセスすることで、構成変更を追跡することができます。ECS サービスの後ろにある各 ECS タスクコピーの設定が確実に更新されるように、この URL に複数回アクセスする必要があるかもしれません。(訳注: この操作は、ECS サービスのすべてのタスクの /refresh-config エンドポイントにアクセスするためです。) 操作後、設定がリフレッシュされたことを示すメッセージが表示されます。
- 最後に、アプリケーションの /hello エンドポイントにアクセスすると、出力に表示されるアプリケーションの変更を確認することができます。下の画面は、アプリケーションを停止することなく実行時にアプリケーションの依存関係を入れ替えた例です。
CodeCommit リポジトリに新しいコミットをプッシュすると、デプロイパイプラインのステージが再び動作するのを確認できます。パイプラインは、更新されたコンテナイメージを使用して、ECS タスク定義の新しいバージョンをデプロイします。パイプラインは、ECS サービスの背後にある ECS タスクをローリング方式で更新し、同じ /hello アプリケーションのエンドポイントからアクセスできるようになります。
まとめ
この記事では、Twelve-Factor App を実装するために使用できる AWS 上のサービスの可能な組み合わせの 1 つを紹介しました。Amazon ECS のような AWS コンテナサービスは、セキュアでスケーラブル、かつ運用効率の高いソリューションを構築するための、より大きな AWS エコシステムとの強力なネイティブ統合を備えています。AWS は、Twelve-Factor App のような業界が推奨する方法論の採用を容易にすることを常に考えており、AWS のドキュメント、ブログ、ホワイトペーパーを使用して、AWS コンテナサービスについて学び続けることをお勧め致します。
参考文献
- Container Migration Methodology (コンテナ移行の方法論)
- Running Containerized Microservices on AWS (AWS 上でのコンテナ化されたマイクロサービスの実行)
- Linear and Canary deployments for Amazon ECS (Amazon ECS の Linear および Canary デプロイ)
- Cost Optimization Checklist for Amazon ECS and AWS Fargate (Amazon ECS と AWS Fargate のコスト最適化チェックリスト)
翻訳はソリューションアーキテクト 杉本 晋吾 が担当しました。原文はこちらです。