Amazon Web Services ブログ

AWS Fargate for Amazon ECS のアップデート

先日、AWS Fargate for Amazon ECS 経由でデプロイされたタスクの設定とメトリクスの収集体験を向上させる機能を発表しました。お客様からのフィードバックに基づき、以下の機能を追加しました。

  • 環境ファイルのサポート
  • シークレットバージョンと JSON キーを使用した、AWS Secrets Manager とのより深い統合
  • より詳細なネットワークメトリクスと、タスクメタデータエンドポイントを介して利用可能な追加データ

この記事を通して、これらのアップデートについて深く掘り下げ、Amazon ECS for AWS Fargate にコンテナをデプロイすると、どこに価値をもたらすことができるかを説明します。まず、簡単なデモアプリケーションのデプロイから始めて、これらの各機能を説明します。

何をデプロイしますか?

私たちは 2 つのルートをたどって、様々な機能を表示する Web アプリケーションを構築していきます。追加された機能の背景がどのように動作しているかを理解するために、それぞれのルートの Web アプリケーションをたどってみましょう。Web アプリケーションは、Python のフレームワークである Flask を使用して構築されています。以下のアプリケーションコードは、それぞれのルートが作り出すと期待しているものの概要を示すコードです。

#!/usr/bin/env python3

from flask import Flask, render_template
from flask_nav import Nav
from flask_nav.elements import Navbar, View, Subgroup, Text
from flask_bootstrap import Bootstrap
from os import getenv, environ
from requests import get
import json

app = Flask(__name__)
Bootstrap(app)
nav = Nav(app)

nav.register_element('frontend', Navbar(
    'ECS Demo',
    View('Home', '.index'),
    View('Environment Variables', '.env_vars'),
    View('Secrets', '.secrets'),
    View('Metadata', '.metadata'),
    Text(getenv('DEPLOY_ENVIRONMENT', 'DEPLOY_ENV UNAVAILABLE')),
))

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/env-vars')
def env_vars():
    environment_variables = {x: y for x,y in environ.items() if "DEPLOY_ENVIRONMENT" in x}
    return render_template('env-vars.html', ev_dict=environment_variables)

@app.route('/secrets')
def secrets():
    secret_kvs = {x: y for x,y in environ.items() if "SECRETS_MANAGER" in x}
    return render_template('secrets.html', secret_dict=secret_kvs)
  
@app.route('/metadata')
def metadata():
    metadata_endpoint = getenv('ECS_CONTAINER_METADATA_URI_V4')
    json_response = json.loads(get("{}/task".format(metadata_endpoint)).text)
    launch_type, container_arn, log_driver = json_response['LaunchType'], json_response['Containers'][0]['ContainerARN'], json_response['Containers'][0]['LogDriver']
    metrics = json.loads(get("{}/stats".format(metadata_endpoint)).text)
  
    return render_template(
        'metadata.html', launch_type=launch_type, container_arn=container_arn, 
        log_driver=log_driver, rx=metrics['network_rate_stats']['rx_bytes_per_sec'], 
        tx=metrics['network_rate_stats']['tx_bytes_per_sec']
    )

if __name__ == '__main__':
    app().run(host='0.0.0.0')

そして、使用する Dockerfile は簡単でシンプルなものです。

FROM python:3

EXPOSE 5000

RUN pip install requests flask flask_bootstrap flask_nav

COPY ./ /flask_app

WORKDIR /flask_app

CMD ["flask", "run", "--host", "0.0.0.0"]

これから、AWS Cloud Development Kit(CDK)を使用してデモ環境を構築し、Docker イメージをデプロイしていきます。以下のコードは、2つの ECS サービスと、VPC ネットワーク、IAM ロール、ECS クラスター、アプリケーションロードバランサーなどの依存するインフラストラクチャとリソースを作成します。CDK で使用するアプリケーションファイルの名前は cdk_app.py とします。

#!/usr/bin/env python3

from aws_cdk import core, aws_ecs, aws_ecs_patterns, aws_ecr_assets, aws_s3, aws_secretsmanager


class FargateDemo(core.Stack):

    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        s3_bucket = aws_s3.Bucket.from_bucket_name(self, "EnvConfigBucket", bucket_name="ecs-demo-env-files")
        sm_secret = aws_secretsmanager.Secret.from_secret_name_v2(self, "SecretJson", secret_name="ecs-demo")
        
        container_image = aws_ecr_assets.DockerImageAsset(self, "Image", directory=".", exclude=["cdk.out"])
        
        task_def = aws_ecs_patterns.ApplicationLoadBalancedTaskImageOptions(
            image=aws_ecs.ContainerImage.from_docker_image_asset(asset=container_image),
            container_port=5000,
            environment={
                "TASK_DEF_SHARED_DEPLOY_ENVIRONMENT": "SAME_VALUE_SHARED_BETWEEN_ENVS",
            },
            secrets={
                "SECRETS_MANAGER_SECRET_ALL_PREVIOUS_VERSION": aws_ecs.Secret.from_secrets_manager(secret=sm_secret),
                "SECRETS_MANAGER_SECRET_ALL_CURRENT_VERSION": aws_ecs.Secret.from_secrets_manager(secret=sm_secret),
                "SECRETS_MANAGER_SECRET_JSON_KEY": aws_ecs.Secret.from_secrets_manager(secret=sm_secret, field="SECRET_KEY_2"),
            },
        )
        
        for deploy_env in ["test", "production"]:

            fargate_ecs_service = aws_ecs_patterns.ApplicationLoadBalancedFargateService(
                self, "FargateService{}".format(deploy_env),
                cpu=256,
                memory_limit_mib=512,
                platform_version=aws_ecs.FargatePlatformVersion.VERSION1_4,
                task_image_options=task_def
            )
            
            fargate_ecs_service.target_group.set_attribute(
                key='deregistration_delay.timeout_seconds',
                value='5'
            )
            
            cfn_task_definition = fargate_ecs_service.task_definition.node.default_child
            cfn_task_definition.add_override(
                "Properties.ContainerDefinitions.0.EnvironmentFiles", 
                [
                    {
                        "Type": "s3",
                        "Value": "{}/{}.env".format(s3_bucket.bucket_arn, deploy_env)
                    }
                ]
            )
            
            cfn_task_definition.add_override(
                "Properties.ContainerDefinitions.0.Secrets.0.ValueFrom",
                "{}::AWSPREVIOUS:".format(sm_secret.secret_arn)
            )

            
            _lb_url = "http://{}".format(fargate_ecs_service.load_balancer.load_balancer_dns_name)
            core.CfnOutput(
                self, "Output{}".format(deploy_env), 
                value=_lb_url,
                export_name="EnvAppUrl{}".format(deploy_env)
            )
            
            s3_bucket.grant_read(fargate_ecs_service.task_definition.execution_role)


app = core.App()
FargateDemo(app, "fargate-features-demo")
app.synth()

CDK を使って環境を構築する前に、S3 バケットを作成します。また AWS Secrets Manager にシークレットを作成します。これらをどう使用するかについては、この後、追加された機能について深く掘り下げるときに説明します。

# Create S3 Bucket
aws s3 mb s3://ecs-demo-env-files

# Create two environment files: production.env & test.env
echo -e "DEPLOY_ENVIRONMENT=TEST\nDEPLOY_ENVIRONMENT_FILE_NAME=test.env" > test.env
echo -e "DEPLOY_ENVIRONMENT=PRODUCTION\nDEPLOY_ENVIRONMENT_FILE_NAME=production.env" > production.env

# Copy files to Amazon S3 Bucket
aws s3 cp --recursive --exclude "*" --include "*.env" ./ s3://ecs-demo-env-files/

# Create secret with JSON object
aws secretsmanager create-secret \
  --name ecs-demo \
  --secret-string '{ "SECRET_KEY_1": "SECRET_VALUE_1", "SECRET_KEY_2": "SECRET_VALUE_2" }'

# Update the secret by adding a new SECRET_KEY_3 key to the JSON
aws secretsmanager update-secret \
  --secret-id ecs-demo \
  --secret-string '{ "SECRET_KEY_1": "SECRET_VALUE_1", "SECRET_KEY_2": "SECRET_VALUE_2", "SECRET_KEY_3": "SECRET_VALUE_3" }'

# Deploy our demo environment and applications
cdk deploy --app "python3 cdk_app.py"

# Store the application URLs in as environment variables for reference throughout the demo
prod_url=$(aws cloudformation describe-stacks --stack-name fargate-features-demo --query 'Stacks[].Outputs[?ExportName == `EnvAppUrlproduction`].OutputValue' --output text)
test_url=$(aws cloudformation describe-stacks --stack-name fargate-features-demo --query 'Stacks[].Outputs[?ExportName == `EnvAppUrltest`].OutputValue' --output text)

環境の構築には数分かかります。構築が完了したら、追加された機能について見ていきましょう。

環境ファイル

https://github.com/aws/containers-roadmap/issues/371

コンテナを使う際の一般的な方法は、環境変数の値を動的にアプリケーションに公開することです。これらの値は環境やデプロイメントの更新に応じて変更される可能性があります。これはコンテナ実行時に環境変数として公開することによって行われます。Amazon ECS では、これは次の 2 つの方法で実現できます。

  • 環境変数のキーと値のペアを タスク定義 に追加する
  • タスク定義に環境変数を格納する環境ファイルへのパスを追加する

最初のアプローチでは、環境変数はタスク定義に格納されます。これらの値を変更したい場合は、更新したデータを使ってタスク定義の新しいバージョンを作成し、サービスのデプロイまたは新しいタスクの起動が必要です。このアプローチでは、タスクの設定とデプロイメントが密接に結合されます。環境変数を定義するタスク定義の例は次のようになります。

{
    "family": "",
    "containerDefinitions": [
        {
            "name": "",
            "image": "",
            ...
            "environment": [
                {
                    "name": "variable",
                    "value": "value"
                }
            ],
            ...
        }
    ],
    ...
}

環境変数が増えてくると、タスク定義での管理が煩雑になる可能性があります。また、コンテナに環境変数を公開したいが、タスク定義を直接更新する方法を持っていないチームもあります。この場合には 2 番目のアプローチが有効です。

環境ファイルを使用すると、環境変数がタスク定義から切り離されます。これによってテスト環境と本番環境それぞれの環境変数を管理したり、環境ファイルとタスク定義が持つ責務の分離が可能です。環境ファイル内のデータに変更が加えられた場合、タスク定義が指している環境ファイルの名前が同じである限り、タスク定義に直接変更を加える必要はありません。つまり、次回タスクを起動したときに、最新の環境ファイルに変更が反映されます。これにより、チームはサービス構成とは別に環境変数の値を管理できるようになります。

  {
    "family": "",
    "containerDefinitions": [
        {
            "name": "",
            "image": "",
            ...
            "environmentFiles": [
                {
                    "value": "arn:aws:s3:::s3_bucket_name/envfile_object_name.env",
                    "type": "s3"
                }
            ],
            ...
        }
    ],
    ...
}

チュートリアル

デモでは 2 つの環境ファイルを作成し、同じ S3 バケットに保存しました。本番環境とテスト環境のサービスを 2 つの環境にデプロイしました。各サービスのタスク定義は、そのデプロイメント環境 ( production.env と test.env ) の環境ファイルを指しています。サービスは 2 つの環境の間で共通の環境変数を共有し、タスク定義から直接参照されます。本番環境とテスト環境の環境変数は、環境ファイルによって区別されています。

次に、環境ファイルを参照するタスク定義のスニペットと、テスト環境の環境変数を示します。

# environment file referenced in the task definition
"environmentFiles": [
  {
    "value": "arn:aws:s3:::ecs-demo-env-files/test.env",
    "type": "s3"
  }
]

# environment variable "hardcoded" into the task definition
"environment": [
  {
    "name": "TASK_DEF_SHARED_DEPLOY_ENVIRONMENT",
    "value": "SAME_VALUE_SHARED_BETWEEN_ENVS"
  }
],

また、参照される環境ファイルは以下になります。

# test.env file contents
DEPLOY_ENVIRONMENT=TEST
DEPLOY_ENVIRONMENT_FILE_NAME=test.env

# production.env file contents
DEPLOY_ENVIRONMENT=PRODUCTION
DEPLOY_ENVIRONMENT_FILE_NAME=production.env

各サービスの URL を取得して /env-vars のルートに移動します。このアプリケーションは、キーに DEPLOY_ENVIRONMENT を持つすべての環境変数を出力します。出力された内容には、 TASK_DEF_SHARED_DEPLOY_ENVIRONMENT というキーに対して同じ値が表示されますが、環境ごとに環境変数から取得された DEPLOY_ENVIRONMENT* には異なる値が表示されます。

上記の画像を見ると、Fargate サービスの各タスクが共通の環境変数をタスク定義から取得し、環境ファイルを環境ごとにデプロイすることが明確にわかります。これで環境ファイルのデモは完了です。AWS Secrets Manager との統合に移りましょう。

AWS Secrets Manager との統合

https://github.com/aws/containers-roadmap/issues/636

本番環境で動いているアプリケーションは、別のサービスやデータストアと通信することは避けられません。通信を行うために必要な認証情報は秘密にしておく必要があります。これらのシークレットをアプリケーション内にハードコーディングしたり、タスク定義内の環境変数に平文でハードコーディングしたりするのは避けましょう。AWS Secrets Manager はアプリケーションやサービス、他のリソースへのアクセスに必要なシークレットを保護するのに役立つサービスです。このサービスを使用すると、データベースの認証情報や、API キー、およびその他のシークレットをライフサイクルを通して簡単にローテーション、管理、取得できます。

AWS Secrets Manager との統合は Amazon ECS にすでに存在していますが、お客様からのフィードバックに基づき、2 つのサービス間により深い統合の機会がありました。リクエストされた機能の 1 つは、タスクが AWS Secrets Manager からシークレットのバージョンを参照できるようにすることでした。AWS Secrets Manager を使用する主な利点は、サービスに組み込まれている ローテーション機能 です。Amazon RDS のようなネイティブに統合されているサービスでも、シークレットをローテーションさせるカスタム AWS Lambda 関数でも、シークレットを操作するときに人間が介入する必要がありません。シークレットの最新バージョンを指したいチームもあれば、特定のバージョンを指し、自分のペースで新しいバージョンにアップグレードしたいチームもあります。これは、どのコンピューティングタイプ( EC2 または Fargate )が使用されているかに関わらず、Amazon ECS で簡単に実現できます。この統合を有効にする方法の詳細については、ドキュメント を参照してください。

AWS Secrets Manager にシークレットを保存する場合の一般的な方法は、環境ごとの複数のキー値ペアを含む JSON オブジェクトを格納することです。これにより、AWS Secrets Manager に格納された JSON の特定のキーから環境変数の値を取得するいう、お客様からリクエストがあったもう一つの機能につながります。これにより、JSON を解析するためにカスタムコードを記述する必要がなくなります。

これがアプリケーションでどのように機能するかを見てみましょう。

チュートリアル

以下のデモでは、アプリケーションはタスクに公開されているシークレットを参照します。これは、ECS タスクが Secrets Manager と統合できるさまざまな方法を説明するために意図的に行っています。シークレットは環境変数としてコンテナに公開されますが、タスク定義にシークレットを平文として格納しないようにする必要があります。この統合は、Secrets Manager でシークレットの ARN を参照する secrets パラメータを使用するだけで有効になります。以下は、タスク定義の一部であるコンテナ定義からのスニペットです。設定には、環境変数名として公開されるキーと、実行時に ARN から実際のシークレットに変換される値が表示されます。

"secrets": [
  {
    "valueFrom": "arn:aws:secretsmanager:us-east-2:333258026273:secret:ecs-demo::AWSPREVIOUS:",
    "name": "SECRETS_MANAGER_SECRET_ALL_PREVIOUS_VERSION"
  },
  {
    "valueFrom": "arn:aws:secretsmanager:us-east-2:333258026273:secret:ecs-demo",
    "name": "SECRETS_MANAGER_SECRET_ALL_CURRENT_VERSION"
  },
  {
    "valueFrom": "arn:aws:secretsmanager:us-east-2:333258026273:secret:ecs-demo:SECRET_KEY_2::",
    "name": "SECRETS_MANAGER_SECRET_JSON_KEY"
  }
],

上記のように、各項目の参照先は同じシークレットを指していますが、異なる方法で値を公開します。これを掘り下げてみましょう。

  1. SECRETS_MANAGER_SECRET_ALL_CURRENT_VERSION: ValueFrom はシークレットの ARN を直接指しているので、これは常に最新のシークレットをプルダウンします。
  2. SECRETS_MANAGER_SECRET_ALL_PREVIOUS_VERSION: ARN の識別子に AWSSPREVIUS が含まれています。これは、 ECS に公開された最新のシークレットの以前のバージョンを指し示しています。
  3. SECRETS_MANAGER_SECRET_JSON_KEY: 簡単に言うと、Secrets Manager に保存されている JSON から特定のキーを参照しています。

シークレットを参照する方法の詳細については、ドキュメント を参照してください。

シークレットの取得方法を説明したのでアプリケーションに戻り、次はそのアプリケーション内でシークレットがどのように公開されているかを見てみましょう。

完了すると、テストアプリケーションの URL を生成します。デモでは、アプリケーション内でシークレットを公開しない場合でも、 ECS で Secrets Manager からシークレットを参照できるさまざまな方法を視覚化するための効果的な方法を提供します。

エンドポイントを開き、結果を見てみましょう。

Fargate タスクは私たちが要求した通りにシークレットを公開していることがわかります。これでシークレットのセクションをは終わりです。タスクのメタデータの更新に移りましょう。

タスクメタデータの更新

Amazon ECS でコンテナを実行する場合、アプリケーションまたはモニタリングサービスがタスクに関するデータを収集したり、実行中にコンテナに関する統計情報を収集する必要がある場合があります。タスクメタデータエンドポイントから使用できるものの詳細については、ドキュメント を参照してください。メタデータサービスの最新のアップデートにより、AWS Fargate for Amazon ECS で実行されているタスクは、コンテナからより詳細なネットワークメトリクスにアクセスできるようになりました。具体的には 1 秒あたりの送信バイト数( Tx および Rx )です。さらに、タスクメタデータから 3 つのフィールド (起動タイプ、コンテナの ARN、使用されているログドライバの詳細など) が使用できるようになりました。

メタデータエンドポイントに送信および受信ネットワークメトリクスを追加することによって、Fargate タスクのネットワークパフォーマンスの可視性が向上します。この最新のアップデートでは、メタデータエンドポイントを介したメトリクスが利用できることになったことに加えて、Fargate タスクレベルのネットワークメトリクスを CloudWatch Container Insights で表示できます。つまり、お客様は Fargate で実行されているタスクから意味のあるネットワークメトリクスを導き出し、必要に応じて適切なアクションを実行できます。ネットワークトラフィックパターンで自動スケーリングを有効にする場合でも、アプリケーションのトラフィックパターンをよりよく理解するためであっても、AWS Fargate で実行されているタスクのネットワークパフォーマンスをより詳細に把握できます。

デモアプリケーションでは、コンテナで使用できるメタデータエンドポイントを環境変数として呼び出し、それを解析して新しいフィールドを視覚化します。このデモアプリケーションは本番ワークロードを表すものではありませんが、メタデータエンドポイント経由でデータにアクセスする方法の例を示しています。例えば、ネットワークメトリクスは、サイドカーでコンテナを監視したり、コンテナのヘルスチェックの結果を決定するための計算を実行したりするのに最適です。

最後に、CloudWatch Container Insights に追加されたこれらのネットワークメトリクスは、Fargate 起動タイプまたは EC2 起動タイプの ECS タスクで実行されているコンテナの状態をより詳細に把握できます。下の画像では、ECS クラスターで実行されている Fargate サービスを可視化できるようになり、ネットワークメトリクスが追加されています。

まとめ

今回のブログ記事では、AWS Fargate on Amazon ECS に追加された最新機能をデモで紹介しました。Fargate の プラットフォームバージョン1.4 を使用しているお客様がこの機能を開始できます。EC2 起動タイプのタスクの場合は ドキュメント に従ってECS エージェントをバージョン 1.43.0 に更新してください。

翻訳はソリューションアーキテクト加治が担当しました。原文はこちらです。