Amazon Web Services ブログ

Docker Compose と Amazon ECS を利用したソフトウェアデリバリの自動化

この記事は Automated software delivery using Docker Compose and Amazon ECS を翻訳したものです。

2020 年 11 月、Docker Compose for Amazon ECS の一般提供を開始しました。開発者はコンテナ化されたマイクロサービスベースのアプリケーションをワークステーションから取り出し、AWS クラウドに直接デプロイすることがさらに簡単になりました。以前紹介したこのブログにあるように、開発者は docker compose up コマンドを実行して既存の Docker Compose ファイルをそのまま Amazon ECS にデプロイできます。Docker Compose for Amazon ECS を活用することで、開発者はアプリケーションをローカルで開発する場合と Amazon ECS で実行する場合で一貫したフレームワークを持つことができます。

しかし、多くの企業にとって開発者のワークステーションから AWS クラウドへの直接のデプロイは認められておらず、代わりにより自動化された集中型デプロイモデルを採用しています。ここで問題となるのが、開発時にワークステーションで使用している Docker Compose ファイルを本番環境にデプロイするために、より制御された方法を使用できるか?ということです。Docker Compose for Amazon ECS を活用することで、答えは「はい」となると信じています。

組織内の規模とガバナンスに応じて、開発者のワークステーションから本番環境へのアプリケーションのデプロイのステップは大きく異なります。そのため、このブログ記事ではソリューションが満たす必要があるいくつかの要件を想定します。

  • 同じ Docker Compose ファイルが、開発者のワークステーションからテスト / ステージング / 本番環境までの全てで使用される必要があります。
  • Docker Compose で定義されているマイクロサービスベースのアプリケーションは、既存の AWS インフラストラクチャにデプロイできる必要があります。Amazon VPC、ECS クラスター、およびカスタム AMI は多くの場合すでにデプロイ済みであり、複数のアプリケーションチーム間で共有されています。
  • ソリューションは開発者がコードをソースコードリポジトリにチェックインした時点から、既存の AWS インフラストラクチャへの本番デプロイまでを自動化する必要があります。手動によるデプロイは標準化を阻害し、ヒューマンエラーの温床になります。

CI/CD パイプライン

このブログ記事では Docker Compose ファイルを取得し、マイクロサービスベースのアプリケーションをデプロイするためのより一般的なアプローチであるパイプラインを利用した自動デプロイを行う方法について紹介したいと思います。このウォークスルーでは AWS CodePipelineAWS CodeBuild を活用しますが、これらの手順はあなたの環境にある既存の CI/CD パイプラインに合わせて変更することができます。

このサンプルパイプラインに含まれる手順は以下の通りです。

  1. アプリケーションのコードを S3 に保存し、S3 でオブジェクトが変更されるとパイプラインが起動します。AWS CodePipeline は、S3、CodeCommit、GitHub、BitBucket など、パイプラインを実行する様々なソースをサポートしています。S3 ではなく GitHub リポジトリを利用してこのサンプルパイプラインを実行するにはこのドキュメントを参照してください。
  2. S3 オブジェクトが変更されると、AWS CodePipeline は AWS CodeBuild のジョブを開始して新しいコンテナイメージをビルドし、出来上がったコンテナイメージを Amazon Elastic Container Registry (Amazon ECR) にプッシュします。パイプラインのこのステージでは、様々なアプリケーションの単体テストや結合テストを実行するのが一般的ですが、このウォークスルーには含まれていません。
  3. コンテナイメージがビルドされたら Docker Compose ファイルに焦点を当てます。まず、既存の Docker Compose ファイルを Docker Compose for ECS plugin を利用して AWS CloudFormation テンプレートに変換します。これは docker compose convert コマンドを実行することによって AWS CodeBuild のインスタンス内で行われます。このコマンドによって出力されるアーティファクトは、AWS CloudFormation の変更セットを作成するために利用できる CloudFormation のテンプレートです。
  4. AWS CodePipeline が停止し、ユーザが AWS CloudFormation の変更セットを確認するのを待ちます。
  5. 最後に、CloudFormation の変更セットが承認されると AWS CodePipeline は CloudFormation の変更セットを実行し、ECS サービスのローリングアップデートを利用して ECS 環境に新しい変更をデプロイします。

AWS CloudFormation への変換

Docker Compose を本番環境へロールアウト可能な CloudFormation へ変換することは、パイプラインのウォークスルーの中で重要なステップの1つです。これを行うための難しい要件はありません。パイプラインの中の CodeBuild 内で docker compose up コマンドを実行し、Massimo のブログにあるように、最新の Docker Compose ファイルが本番環境にデプロイされます。

アジャイル環境で開発している場合や、変更管理プロセスが小さい場合はこれで問題はありません。しかし多くの場合、特に大企業では、パイプラインでの手動承認プロセスは変更が本番に反映される前にチームの他のメンバーがレビューする時間を提供するという点で良いことです。この手動承認が発生した場合、環境に適用されようとしている変更を表示するには CloudFormation の変更セットが適切であると考えています。コードの変更が GitHub のプルリクエストでレビューされたとしても、インフラ上の根本的な変更は見つけにくいかもしれません。Docker Compose for ECS plugin は、マイクロサービスの新しいバージョンを反映するだけでなく多くのことをコントロールします。Docker Compose が提供する素晴らしい抽象化は、より多くの AWS インフラストラクチャが Docker Compose フレームワークで定義されることを意味します。セキュリティグループ、ロードバランサー、IAM ロールとポリシーはすべて、Docker Compose の変更の結果として更新することができます。これらの項目は本番環境おいて重要な項目であることが多く、手動のゲートを設けて更新を確認することを正当化します。

docker compose convert コマンドを使って Docker Compose ファイルを CloudFormation に変換し CloudFormation の変更セットを作成することで、AWS クラウド内のどのリソースが作成・更新されるかを深く調べることができます。これは AWS CloudFormation のベストプラクティスとされています。なお、AWS Cloud Development Kit (CDK)AWS Serverless Application Framework (SAM) などの AWS 開発者向けツールでも、ワークロードをデプロイする前に CloudFormation の変更セットへ手動での承認を求めます。

サンプルパイプライン

このウォークスルーでは 2 層構造のサンプルアプリケーションを利用します。1 層目は Python の Flask アプリケーションで、2 層目は Redis を実行するステートフルな層です。ユーザーが Flask アプリケーションでボタンをクリックするたびに、タイムスタンプのエントリが Redis のキーバリューストアに保存されます。このサンプルアプリケーションは、Docker Compose を使ってローカルにデプロイすることができます。Docker Desktop を実行しているワークステーション、または Docker EngineCompose CLI がインストールされている Linux ホストの両方でデプロイすることができます。

$ git clone https://github.com/aws-containers/demo-app-for-docker-compose.git
$ cd demo-app-for-docker-compose/application
$ docker compose up

すべてが正しく動作していれば、Web ブラウザで http://localhost へアクセスするとサンプルアプリケーションが表示されるはずです。2 つの層がローカルの Docker ネットワークブリッジ上で正しく通信しているかどうか、ボタンをクリックして自由にテストしてください。

Docker Compose ファイルの確認

Docker Compose ファイルのサンプルアプリケーションは非常に単純です。この Dcoker Compose ファイルをローカルにデプロイした場合、フロントエンドサービス、バックエンドサービスの 2 つのコンテナを接続する Docker ネットワークと、最終的に Redis に状態を格納するための Docker ボリューム が含まれます。

x-aws-vpc: ${AWS_VPC}
x-aws-cluster: ${AWS_ECS_CLUSTER}
x-aws-loadbalancer: ${AWS_ELB}

services:
  frontend:
    image: ${IMAGE_URI:-frontend}:${IMAGE_TAG:-latest}
    build: ./frontend
    environment:
      REDIS_URL: "backend"
    networks:
      - demoapp
    ports:
      - 80:80

  backend:
    image: public.ecr.aws/bitnami/redis:6.2
    environment:
      ALLOW_EMPTY_PASSWORD: "yes"
    volumes:
      - redisdata:/data
    networks:
      - demoapp

volumes:
  redisdata:

networks:
  demoapp:

Docker Compose to ECS plugin を使ってデプロイすると、興味深いことが起こります。

  • ワークロードでオーケストレーションされたデプロイメントを提供する 2 つの Amazon ECS サービス が作成されます。実行中のコンテナは AWS Fargate にデプロイされます。
  • Amazon EC2 セキュリティグループが作成され、“フロントエンド” と “バックエンド” サービスへのインバウンドルールが定義されます。
  • AWS Cloud Map のネームスペースが透過的に作成され、環境内でのサービスディスカバリーを提供し、“フロントエンド” サービスが “バックエンド” サービスを発見できるようになります。
  • Application Load Balancer が作成され、80 番ポートのリスナーと、“フロントエンド” サービスに接続されたターゲットグループが設定されます。
  • Amazon Elastic File Share (EFS) が作成され、Redis データベースにステートを提供する “バックエンド” サービスにマウントされます。
  • x-aws-* の項目はアプリケーションと、それをサポートする AWS インフラストラクチャがどこにデプロイされているかを定義するために使用されます。サポートされる x-aws の項目については、Docker Documentation を参照してください。

ここでの主な意図の 1 つは、Docker Compose ファイルを汎用的に保ち、変数をハードコーディングしないことで環境(例: 開発環境 / 検証環境 / 本番環境)に依存しないようにすることです。Amazon VPC や ECS クラスター、およびロードバランサーの値は、コンテナイメージの URI やタグと共に環境変数に設定されます。これらの環境変数は、CI/CD パイプラインの様々な CodeBuild のステージで設定されます。開発者がローカルでアプリケーションを開発していた場合、Compose ファイル内の x-aws-* の項目は無視されるため、ワークステーションで環境変数を設定する必要はありません。

インフラストラクチャの要件

このウォークスルーでは、既存の AWS インフラストラクチャに Docker Compose ファイルをデプロイします。Docker Compose for ECS plugin は前提となるリソースを全て作成できますが、本番同様の環境をシミュレートするために、個別の環境にデプロイします。既存の VPC や ECS クラスターが存在しない場合は、サンプルアプリケーションの git リポジトリにある CloudFormation テンプレートを使用して開始できます。

このサンプル CloudFormation テンプレートはAWS のベストプラクティスに沿って、VPC、パブリックサブネット、プライベートサブネット、および Amazon ECS クラスターを作成します。サンプルテンプレートはサンプルアプリケーションの前段となる Application Load Balancer も作成します。この段階では awscli がワークステーションにインストールされており、AWS アカウントにネットワークインフラを作成するために必要な IAM 認証情報があることが前提となっています。

# Navigate to the Infrastructure Directory
$ cd ../infrastructure

# Deploy the CloudFormation Template
$ aws cloudformation create-stack \
    --stack-name compose-infrastructure \
    --template-body file://cloudformation.yaml \
    --capabilities CAPABILITY_IAM

VPC ID、ECS クラスター、および Application Load Balancer の ARN は後のパイプラインで必要になります。サンプルの CloudFormation テンプレートを利用する場合、CloudFormation スタックが正常にデプロイされたときに以下のコマンドを実行することでこれらをローカル変数として設定できます。

# Set the VPC Id
$ VPC_ID=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='VpcId'].OutputValue" --output text)

# Set the ECS Cluster Name
$ ECS_CLUSTER=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='ClusterName'].OutputValue" --output text)

# The Loadbalancer Arn
$ LOADBALANCER_ARN=$(aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='LoadbalancerId'].OutputValue" --output text)

パイプラインインフラストラクチャ

このウォークスルーでは、AWS CodePipeline、AWS CodeBuild、および AWS CloudFormation を利用して、サンプルアプリケーションを Amazon ECS へデプロイします。パイプラインのコンポーネントをデプロイするために、CloudFormation テンプレートがサンプルアプリケーションのリポジトリ内に提供されています。この CloudFormation テンプレートは次のリソースをデプロイします。

  • アプリケーションのソースコードを保存する S3 バケット
  • サンプルアプリケーションのコンテナイメージを保存する Amazon ECR repository
  • パイプラインをオーケストレーションする AWS CodePipeline
  • コンテナイメージをビルドする AWS CodeBuild のビルドジョブ
  • Docker Compose ファイルを CloudFormation テンプレートに変換する AWS CodeBuild のビルドジョブ
# Navigate to the directory that stores the Pipeline Template
$ cd ../pipeline/

# Deploy the AWS CloudFormation Template, passing in the existing AWS Resource Paramaters
$ aws cloudformation create-stack \
     --stack-name compose-pipeline \
     --template-body file://cloudformation.yaml \
     --capabilities CAPABILITY_IAM \
     --parameters \
     ParameterKey=ExistingAwsVpc,ParameterValue=$VPC_ID \
     ParameterKey=ExistingEcsCluster,ParameterValue=$ECS_CLUSTER \
     ParameterKey=ExistingLoadbalancer,ParameterValue=$LOADBALANCER_ARN

AWS マネジメントコンソールで AWS CodePipeline のページを表示すると、直近にデプロイされたパイプラインが作られていいるはずです。新しく作成された S3 バケットには現在オブジェクトが格納されていないため、パイプラインの初回実行は失敗します。

サンプルアプリケーションを AWS へデプロイ

git リポジトリでは、アプリケーションコードと一緒にインフラストラクチャをコードとして保存します。サンプルアプリケーションを AWS にデプロイするには、アプリケーションのソースコードと docker-compose.yml のみを Amazon S3 にアップロードする必要があります。これらのアーティファクトはすべて、リポジトリ内の application ディレクトリに含まれます。

# Ensure you are in the Application directory of the cloned repository
$ cd ../application

# Retrieve the S3 Bucket Name
$ BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name compose-pipeline --query "Stacks[0].Outputs[?OutputKey=='S3BucketName'].OutputValue" --output text)

# Zip up the Code and Upload to S3
$ zip -r compose-bundle.zip .
$ aws s3 cp compose-bundle.zip s3://$BUCKET_NAME/compose-bundle.zip

ZIP ファイルを S3 バケットにコピーすると CodePipeline がトリガーされます。数秒後にパイプラインが最初のステージに移行し、CodeBuild を使用してコンテナイメージをビルドします。Docker Compose ファイルを docker compose up コマンドでローカルで実行すると、Docker Compose はコンテナイメージをビルドしコンテナを起動します。しかし、パイプライン経由でデプロイする場合はビルドを独自ステージに分割します。コンテナイメージがタグ付けされ Amazon ECR にプッシュされる前に、パイプラインのこの段階でユニットテストとコンテナスキャンツールを実行するのが一般的です。

コンテナイメージを構築するコマンドは BuildSpec ファイルで定義されています。次の BuildSpec ファイルは、パイプライン用の CloudFormation テンプレートに埋め込まれています。CodeBuild ジョブは、まず compose-bundle.zip をダウンロード、解凍してからステップを実行します。CodeBuild は BuildSpec ファイルを介して動作します。まず Amazon ECR で認証し、ソースコードディレクトリに移動します。次に docker build を使用してコンテナイメージをビルドし、最後にコンテナイメージを Amazon ECR にプッシュします。

version: 0.2
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - echo Building the Docker image...
      - cd frontend/
      - docker build -t $IMAGE_URI:$IMAGE_TAG .
  post_build:
    commands:
       - echo Pushing the Docker image...
      - docker push $IMAGE_URI:$IMAGE_TAG

コンテナイメージのビルド後、Docker Compose ファイルは CloudFormation に変換されます。以下の BuildSpec ファイルは “Convert2CloudFormation” ステージのパイプライン用 CloudFormation テンプレートにも埋め込まれます。
この BuildSpec ファイルは次のことを行います。

  • Docker Compose CLI のインストール
  • CodeBuild ジョブにアタッチされた IAM ロールを抽出し、Docker Compose Context を作成
  • docker compose convert コマンドを実行し、Docker Compose ファイルを CloudFormation に変換
version: 0.2
phases:
 install:
   commands:
     - mv /usr/local/bin/docker /usr/bin/docker
     - curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh
 pre_build:
   commands:
     - echo Logging in to Amazon ECR...
     - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
     - echo Creating Docker Compose Context
     - curl "http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}" > creds.json
     - export AWS_ACCESS_KEY_ID=$(cat creds.json | jq -r .AccessKeyId)
     - export AWS_SECRET_ACCESS_KEY=$(cat creds.json | jq -r .SecretAccessKey)
     - export AWS_SESSION_TOKEN=$(cat creds.json | jq -r .Token)
     - docker context create ecs demoecs --from-env
     - docker context use demoecs
 build:
   commands:
     - echo Convert Compose File
     - docker --debug compose convert > cloudformation.yml
artifacts:
  files:
    - cloudformation.yml

AWS マネジメントコンソールにて、AWS CodePipeline のデプロイの進捗を追跡することができます。最終的にパイプラインは手動承認の段階で一時停止します。

AWS マネジメントコンソールの AWS CloudFormation ページで、CloudFormation スタックの compose-application をクリックし “変更セット” に移動します。そこで、Docker Compose ファイルから作成された CloudFormation の変更セットを確認できます。この変更セットの数は、Docker Compose の抽象化によって管理されるリソースの数を表しています。

CloudFormation の変更セットを承認するために、AWS CodePipeline のコンソールに戻ります。DeployStage で、“Review >> Approve” をクリックします。これにより CloudFormation の変更セットが実行され、デモアプリケーションが Amazon ECS にデプロイされます。ECS タスクがロールアウトし、ロードバランサーのヘルスチェックをパスするまでに数分掛かります。インフラストラクチャ用の CloudFormation テンプレートを利用していた場合、ロードバランサーのエンドポイントは compose-infrastructure スタックの出力から確認することができます。

$ aws cloudformation describe-stacks --stack-name compose-infrastructure --query "Stacks[0].Outputs[?OutputKey=='LoadbalancerEndpoint'].OutputValue" --output text
http://compose-infrastructure-alb-1377269157.eu-west-1.elb.amazonaws.com

サンプルアプリケーションの更新

作成したスタックのライフサイクルをシミュレートするために、サンプルアプリケーションに小さな変更を加えます。フロントエンドの “Click Me” ボタンの色を変更することで、アプリケーションから視覚的な変更を確認できます。ECS のローリングアップデートによって新しいバージョンのアプリケーションがデプロイされると、変更されたことが確認できます。

# Ensure you are in the application directory
$ cd ../application/

# Replace the word "Blue" with "Green" in the frontend application.# For Linux Users:
$ sed -i 's/blue/green/g' frontend/myweb/app.py

# For Mac OS users:
$ sed -i "" 's/blue/green/g' frontend/myweb/app.py

ワークステーションのマシン上で実行されるローカルの Docker Engine を使用してこの変更が必要な効果をもたらしたことを確認するために、Docker Compose を通してサンプルアプリケーションを再度デプロイします。

# Ensure the previous Docker Compose stack is not running
$ docker compose down

# Remove the previous container image and start the Docker Compose Stack 
$ docker rmi frontend:latest
$ docker compose up --build

成功しました!これでサンプルアプリケーションを再度圧縮して S3 バケットにアップロードし、AWS クラウドにアプリケーションをデプロイできます。

$ zip -r compose-bundle.zip .
$ aws s3 cp compose-bundle.zip s3://$BUCKET_NAME/compose-bundle.zip

再び、AWS マネジメントコンソールにて AWS CodePipeline のパイプラインを追いかけることができるようになりました。コンテナがビルドされ、CloudFormation の変更セットが作成されると手動での承認のゲートで再度停止します。CloudFormation の変更点を確認すると、今回はインフラストラクチャへの変更が少ないことがわかります。

DevOps / Operations チームが変更セット内のリソースの変更が期待通りであることを確認し、CodePipeline コンソールで承認すると、ECS はフロントエンドサービスのローリングアップデートを実行します。新しいバージョンのアプリケーションがデプロイされるのには数分かかります。ロードバランサーは古い ECS タスクを破棄する前に安全にドレインします。CloudFormation のロールアウトが完了すると、同じロードバランサーのエンドポイントで新しいバージョンのアプリケーションが使用できるようになります。

素晴らしいですね! アプリケーションに変更を加えて index.html を変更したり、パイプラインを利用して新しいバージョンのアプリケーションをデプロイしてみたりしてください。

後片付け

# Delete the Sample Application deployed via the Pipeline
$ aws cloudformation delete-stack --stack-name compose-application

# Delete the S3 Objects
$ BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name compose-pipeline --query "Stacks[0].Outputs[?OutputKey=='S3BucketName'].OutputValue" --output text)
$ aws s3api delete-objects \
  --bucket $BUCKET_NAME --delete \
  "$(aws s3api list-object-versions \
    --bucket "${BUCKET_NAME}" \
    --output=json \
    --query='{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"

# Delete the S3 Bucket    
$ aws s3 rb s3://$BUCKET_NAME

# Delete the ECR Repository
$ ECR_REPO=$(aws cloudformation describe-stacks --stack-name compose-pipeline --query "Stacks[0].Outputs[?OutputKey=='DemoAppEcrName'].OutputValue" --output text)
$ aws ecr delete-repository --repository-name $ECR_REPO --force

# Delete the Sample Pipeline
$ aws cloudformation delete-stack --stack-name compose-pipeline

# Delete the Networking and ECS Infrastructure
$ aws cloudformation delete-stack --stack-name compose-infrastructure

まとめ

このブログでは開発チームによって定義および所有される Docker Compose ファイルをアプリケーションコードと共に配置し、一元管理されたデプロイパイプラインを通して Amazon ECS にデプロイする方法を説明しました。

  • サンプルアプリケーションをローカルに構築し Docker Compose を使用してその機能をデモンストレーション
  • AWS CloudFormation を使用した AWS CodePipeline の構築
  • Docker Compose を利用しサンプルアプリケーションを CI/CD パイプラインを通して Amazon ECS へデプロイ
  • アプリケーションに変更を加え Docker Compose を利用して CI/CD パイプラインで新バージョンのデプロイ

Docker Compose for Amazon ECS plugin の詳細については Docker ドキュメントおよび、Compose CLI の GitHub リポジトリを参照してください。フィードバックの提供、機能リクエストの追加、Docker Compose for Amazon ECS Plugin の進捗状況を確認するには GitHub にある Docker ロードマップを参照してください。

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