Amazon Web Services ブログ

GitHub Actions と AWS CodeBuild テストを使用して Amazon ECS の CI/CD パイプラインを作成する



Amazon Elastic Container Service (Amazon ECS) は、フルマネージド型のコンテナオーケストレーションサービスであり、コンテナ化されたワークロードを大規模かつ簡単に運用できます。  また、Amazon Route 53、AWS Identity and Access Management (IAM)、Amazon CloudWatch などの他の主要な AWS のサービスと統合します。  コンテナの管理に使用しているプラットフォームに関係なく、コンテナ化されたアプリケーションにとって効果的かつ効率的な CI/CD パイプラインを確立することは重要です。

この投稿では、ソースコードリポジトリとして GitHub を使用している組織が、GitHub アクションを使用して Amazon ECS にデプロイされたアプリケーション用の完全な CI/CD パイプラインを構築する方法を探り、そのデモを行います。  また、この投稿では、完全な CI/CD パイプラインの一部としてアプリケーションテストを実行するための、GitHub Actions を用いた AWS CodeBulid の使用方法を説明します。

Amazon ECS または AWS CodeBuild を初めて使用する場合は、「Amazon ECS の使用開始」および「AWS の開発者用ツール」をご覧ください。

GitHub Actions

GitHub をソースコードリポジトリとして使用している組織の場合、GitHub Actions は、GitHub イベントでワークフローを開始することにより、GitHub に複雑な CI/CD 機能を直接実装することを可能にします。

GitHub Action は、他の GitHub Actions と組み合わせて、特定の GitHub イベント (プル、プッシュ、コミットなど) に応答してトリガーされるワークフローを作成できる個々の機能ユニットです。  ワークフローは、GitHub がホストするサーバーの管理環境内で実行されます。

Amazon ECS で実行されているアプリケーションの CI/CD 操作をサポートするために、AWS は github.com/aws-actions で次の JavaScript ベースの GitHub Actions をオープンソース化しました。

AWS および GitHub によって提供されるものに加えて、カスタム GitHub Actions を使用してワークフローを構築することにより、GitHub から直接、Amazon ECS アプリケーションの CI/CD 機能を簡単に提供できます。

AWS CodeBulid を使用したテスト

GitHub がソースコードリポジトリとして使用される場合、AWS CodeBulid は webhook をサポートします。  これにより、CodeBulid は、GitHub に保存されたソースコードを操作し、コード変更が GitHub リポジトリにプッシュされるたびに webhook を使用してソースコードのビルドをトリガーできます。

CodeBuild は、アプリケーションコードのコンパイルまたはアプリケーションコンテナイメージの構築以外にも使用できます。 CodeBuild を使用することで、ユニットテスト、静的コード解析、統合テストなど、コードに対してさまざまなテストを簡単に実行できます。 CI/CD パイプラインを構築する場合、「テスト」ステージを含めることは、アプリケーションの品質基準を維持するために重要です。

この投稿では、CI/CD パイプラインの「ビルド」アクションとして CodeBuild を使用しません。 代わりに、テストを実行する環境として CodeBuild を使用します。CodeBuild をテストプラットフォームとして使用すると、AWS にデプロイされたアプリケーションのテストを簡単に実行できます。  他の AWS のサービスと簡単に統合できる管理された環境を提供することに加えて、CodeBuild プロジェクトを実行して、AWS の外部に到達できないコンポーネントとの統合テストを実行できます。  また、ユニットテストと統合テストの結果から、CodeBuild を使用してテストレポートを生成することもできます。  この投稿の例では、CodeBuild を使用して単純なユニットテストを実行し、ステータスを GitHub に報告します。

サンプルアーキテクチャ

次の図は、実装する高レベルアーキテクチャを示しています。  このアーキテクチャは、GitHub ワークフローを使用してリポジトリへのコミットごとに ECS へのアプリケーションの構築、テスト、デプロイを自動的に調整する完全な CI/CD パイプラインを表します。  この GitHub ワークフローは、AWS オープンソース GitHub Actions を使用してタスクのビルドとデプロイを調整し、CodeBuild を使用してアプリケーションテストを実行します。  また、このパイプラインにカスタム GitHub Action を導入して、CodeBuild テストのステータスを評価します。

GitHub Actions と CodeBuild を使用した ECS アプリケーション用 CI/CD パイプライン

GitHub Actions と CodeBuild を使用した ECS アプリケーション用 CI/CD パイプライン

GitHub リポジトリは、シンプルなウェブアプリケーションとそれに付随するインフラストラクチャファイルで構成されています。  CI/CD パイプラインの目標は、ユニットテストの実行、コンテナイメージの構築、コンテナイメージの ECR へのアップロード、GitHub リポジトリへのコミットごとの ECS タスク定義の更新です。

GitHub リポジトリの作成

サンプルアーキテクチャの構築に必要なコンポーネントの作成を開始するには、GitHub リポジトリが必要です。  このプロセスに慣れていない場合は、「GitHub でリポジトリを作成する」をご覧ください。  この例では、次のファイルを GitHub リポジトリに配置する必要があります。

  • app.py: シンプルな Flask ウェブアプリケーション。  このアプリケーションには、要求された URI を反転して返す 1 つのエンドポイントが含まれます
"""Main application file"""
from flask import Flask
app = Flask(__name__)

@app.route('/<random_string>')
def returnBackwardsString(random_string):
    """Reverse and return the provided URI"""
    return "".join(reversed(random_string))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)
  • app_test.py: 文字列が正しく反転されることを確認するための app.py のユニットテストファイル
"""Unit test file for app.py"""
from app import returnBackwardsString
import unittest

class TestApp(unittest.TestCase):
    """Unit tests defined for app.py"""

    def test_return_backwards_string(self):
        """Test return backwards simple string"""
        random_string = "This is my test string"
        random_string_reversed = "gnirts tset ym si sihT"
        self.assertEqual(random_string_reversed, returnBackwardsString(random_string))

if __name__ == "__main__":
    unittest.main()
  • requirements.txtapp.py の依存関係

flask==1.0.2

  • buildspec.yml: AWS CodeBulid がユニットテストを実行するための手順。  buildspec ファイルで使用可能な機能および構文の詳細については、「ビルド仕様のリファレンス」を参照してください。
version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.8
  pre_build:
    commands:
      - pip install -r requirements.txt
      - python app_test.py
  • Dockerfile: アプリケーションコンテナイメージの構築手順

FROM python:3
# Set application working directory
WORKDIR /usr/src/app
# Install requirements
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Install application
COPY app.py ./
# Run application
CMD python app.py

  • task-definition.json: ECS タスク定義の仕様。 注意: このファイルで、プレースホルダーの値を AWS アカウント ID に置き換えます
{
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "inferenceAccelerators": [],
    "containerDefinitions": [
        {
            "name": "ecs-devops-sandbox",
            "image": "ecs-devops-sandbox-repository:00000",
            "resourceRequirements": null,
            "essential": true,
            "portMappings": [
                {
                    "containerPort": "8080",
                    "protocol": "tcp"
                }
                
            ]
        }
    ],
    "volumes": [],
    "networkMode": "awsvpc",
    "memory": "512",
    "cpu": "256",
    "executionRoleArn": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:role/ecs-devops-sandbox-execution-role",
    "family": "ecs-devops-sandbox-task-definition",
    "taskRoleArn": "",
    "placementConstraints": []
}

このステップの最終結果は、すべてのアプリケーション、テスト、およびデプロイメントファイルを含む GitHub リポジトリになるはずです。

GitHub リポジトリの例

GitHub リポジトリの例

ECS インフラストラクチャの作成

ソリューションの概要で説明されているアーキテクチャを構築するには、次の ECS コンポーネントが必要です。

  • ECR リポジトリー: バージョン管理されたアプリケーションコンテナイメージを保存します
  • ECS クラスター: アプリケーションコンテナインスタンスを実行するための計算能力を提供します
  • ECS タスク定義: アプリケーションコンテナイメージのバージョンと環境の考慮事項を指定します
  • ECS サービス: 基になる計算リソースにタスク定義をデプロイする方法を指定します

このインフラストラクチャを構築するには、AWS Cloud Development Kit (CDK) を使用します。CDK を初めて使用する場合は、AWS CDK の使用開始を参照してください。この投稿では、Python 3.7 で CDK を使用します。

アプリケーションインフラストラクチャを作成する方法:

1.以下のテンプレートで説明されているリソース (VPC、ECS、ECR、IAM ロール) を作成する権限を持つ IAM ユーザーで AWS CLI を設定します。カスタムユーザーとポリシーの作成の詳細については、「IAM アクセス許可の管理」を参照してください。

2.次のコマンドを実行して、CDK プロジェクトを初期化します

# Create a project directory
mkdir ecs-devops-sandbox-cdk
# Enter the directory
cd ecs-devops-sandbox-cdk
# Use the CDK CLI to initiate a Python CDK project
cdk init --language python 
# Activate your Python virtual environment
# NOTE: For Windows users, replace with ".env\Scripts\activate.bat"
source .env/bin/activate
# Install CDK Python general dependencies
pip install -r requirements.txt
# Install CDK Python ECS dependencies
pip install aws_cdk.aws_ec2 aws_cdk.aws_ecs aws_cdk.aws_ecr aws_cdk.aws_iam

3.ファイルの内容の ecs_devops_sandbox_cdk/ecs_devops_sandbox_cdk_stack.py (CDK によって自動作成されます) を以下のコードに置き換えます

"""AWS CDK module to create ECS infrastructure"""
from aws_cdk import (core, aws_ecs as ecs, aws_ecr as ecr, aws_ec2 as ec2, aws_iam as iam)

class EcsDevopsSandboxCdkStack(core.Stack):

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

        # Create the ECR Repository
        ecr_repository = ecr.Repository(self,
                                        "ecs-devops-sandbox-repository",
                                        repository_name="ecs-devops-sandbox-repository")

        # Create the ECS Cluster (and VPC)
        vpc = ec2.Vpc(self,
                      "ecs-devops-sandbox-vpc",
                      max_azs=3)
        cluster = ecs.Cluster(self,
                              "ecs-devops-sandbox-cluster",
                              cluster_name="ecs-devops-sandbox-cluster",
                              vpc=vpc)

        # Create the ECS Task Definition with placeholder container (and named Task Execution IAM Role)
        execution_role = iam.Role(self,
                                  "ecs-devops-sandbox-execution-role",
                                  assumed_by=iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
                                  role_name="ecs-devops-sandbox-execution-role")
        execution_role.add_to_policy(iam.PolicyStatement(
            effect=iam.Effect.ALLOW,
            resources=["*"],
            actions=[
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
                ]
        ))
        task_definition = ecs.FargateTaskDefinition(self,
                                                    "ecs-devops-sandbox-task-definition",
                                                    execution_role=execution_role,
                                                    family="ecs-devops-sandbox-task-definition")
        container = task_definition.add_container(
            "ecs-devops-sandbox",
            image=ecs.ContainerImage.from_registry("amazon/amazon-ecs-sample")
        )

        # Create the ECS Service
        service = ecs.FargateService(self,
                                     "ecs-devops-sandbox-service",
                                     cluster=cluster,
                                     task_definition=task_definition,
                                     service_name="ecs-devops-sandbox-service")

4.CDK プロジェクトのルートディレクトリから次のコマンドを実行します

# Create the CloudFormation stack
cdk deploy

このコードは、CDK を使用して、アプリケーションに必要な ECS インフラストラクチャを含む CloudFormation スタックを構築およびデプロイします。  上記のコードでは、パブリック AWS サンプルレジストリのサンプルコンテナで実行するタスク定義を最初に指定しています。  CI/CD パイプラインがタスク定義を更新すると、このサンプルコンテナはアプリケーションコンテナに置き換えられます。  サンプルレジストリのコンテナを使用して、アプリケーションコンテナイメージが ECR リポジトリに追加される前にサービスを安定させます。

このステップの最終結果は、タスク定義 ecs-devops-sandbox-task-definition で構成されるサービス ecs-devops-sandbox-service を実行するクラスター ecs-devops-sandbox-cluster になります。

CodeBuild プロジェクトの作成

CodeBulid は、アプリケーションテストを実行し、これらのテストのステータスを GitHub に提供するために使用されます。  テストに合格しない場合、CodeBulid はビルドを Failed としてマークし、このステータスは GitHub に報告されます。  このアーキテクチャでは、GitHub リポジトリの master ブランチへのコミットがあるたびに、CodeBulid によって自動的に作成される webhook を使用して、アプリケーションコードのビルドをトリガーします。

GitHub と統合して CodeBulid プロジェクトを作成する方法:

1.     AWS CodeBulid に移動し、[Create build project] を選択します

2.     [Project Configuration] で [Project name] に ecs-devops-sandbox と入力します

CodeBuild プロジェクトの設定の例

CodeBuild プロジェクトの設定の例

3.     [Source] の [Source Provider] で [GitHub] を選択します

4.     [Source] の [Repository] で [Connect using OAuth] を選択し、[Connect to GitHub] を選択します

5.     ポップアップウィンドウで、使用するリポジトリを所有する GitHub アカウントにログインします

6.     [Source] の [Repository] で [Repository in my GitHub account] を選択します

7.     [Source] の [GitHub repository] で前に設定したリポジトリを選択します

8.     [Source – Additional configuration] の [Build Status – optional] でボックスにチェックを入れます

CodeBuild ソースの設定の例

CodeBuild ソースの設定の例

9.     [Primary source webhook events] の [Webhook – optional] でボックスにチェックを入れます

CodeBuild webhook の設定の例

CodeBuild webhook の設定の例

10.[Environment] の [Environment image] で [Managed image] を選択します

11.[Environment] の [Operating system] で [Ubuntu] を選択します

12.[Environment] の [Runtime] で [Standard] を選択します

13.[Environment] の [Image] で [aws/AWS CodeBulid/standard:3.0] を選択します

CodeBuild 環境の設定の例

CodeBuild 環境の設定の例

14.残りのデフォルト値のまま、[Create build project] を選択します

GitHub ワークフローの作成

AWS は、AWS オープンソース GitHub Actions を利用して、リポジトリの master ブランチへのコミットごとに、ECS でコンテナを構築およびデプロイするスターター GitHub ワークフローを提供しています。

スターター GitHub ワークフローを GitHub リポジトリに追加する方法:

1.     [Actions] タブで [New workflow] を選択します

GitHub ワークフローの追加の例

GitHub ワークフローの追加の例

2.     [Deploy to Amazon ECS] を検索して、[Set up this workflow] を選択します

AWS ECS スターターワークフローの追加の例

AWS ECS スターターワークフローの追加の例

3.     このワークフローをアプリケーションで使用するには、スターターワークフローファイルに記載されている指示に従ってください。  すでに ECR リポジトリ、ECS タスク定義、ECS クラスター、ECS サービスを作成していることに留意してください。

ECS のための GitHub ワークフローの例

ECS のための GitHub ワークフローの例

実際には、CodeBulid で実行されたテストが成功した場合にのみ、GitHub ワークフローで新しいコンテナを構築して ECS にデプロイする必要があります。  前に作成した CodeBulid プロジェクトは、コミットごとにビルド実行の成功または失敗を GitHub に報告します。  CodeBulid プロジェクトがコミットに失敗した場合、GitHub はこのコミットに failure のステータスをマークします。  CodeBuild プロジェクトが成功または失敗をまだ報告していない場合、GitHub はこのコミットに pending のステータスをマークします (詳細は、「GitHub のコミットステータス」を参照してください)。

この例では、master ブランチへのコミットごとに、GitHub ワークフローと CodeBulid プロジェクトを並行して実行しています。  ワークフローが必要なテストに合格しないコミットをデプロイしないようにするため、スターター GitHub ワークフローに追加のステップを導入して、コミットのステータスを確認します。

以下のコードは、新しい GitHub Action である「Check commit status」を含む完全な GitHub ワークフロー (AWS が提供するスターターワークフローに基づく) を示しています。  この新しい GitHub Action は、GitHub によってプロビジョニングされた環境と変数を使用して、GitHub API から現在のコミットのステータスをクエリします。  現在のコミットが pending の場合、10 秒後に再度確認します。  コミットステータスが failure の場合、つまり CodeBulid テストに合格しなかった場合、GitHub ワークフローは失敗します。

on:
  push:
    branches:
      - master

name: Deploy to Amazon ECS

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
    - name: Check commit status
      id: commit-status
      run: |
        # Check the status of the Git commit
        CURRENT_STATUS=$(curl --url https://api.github.com/repos/${{ github.repository }}/commits/${{ github.sha }}/status --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | jq -r '.state');
        echo "Current status is: $CURRENT_STATUS"
        while [ "${CURRENT_STATUS^^}" = "PENDING" ]; 
          do sleep 10; 
          CURRENT_STATUS=$(curl --url https://api.github.com/repos/${{ github.repository }}/commits/${{ github.sha }}/status --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | jq -r '.state');
        done;
        echo "Current status is: $CURRENT_STATUS"
        if [ "${CURRENT_STATUS^^}" = "FAILURE" ]; 
          then echo "Commit status failed.Canceling execution"; 
          exit 1; 
        fi
        
    - name: Checkout
      uses: actions/checkout@v1

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: <YOUR_AWS_REGION>

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: ecs-devops-sandbox-repository
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
        
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: task-definition.json
        container-name: ecs-devops-sandbox
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ecs-devops-sandbox-service
        cluster: ecs-devops-sandbox-cluster
        wait-for-service-stability: true

結果

AWS CodeBulid プロジェクトと GitHub ワークフローを配置すると、すべてのテストに合格した場合に Amazon ECS に自動的にデプロイする新しい機能をアプリケーションに追加できます。

この例は、ユニットテストに合格しないアプリケーションコード (app.py) の変更を示しています。

"""
Main application file
"""
from flask import Flask
app = Flask(__name__)

@app.route('/<random_string>')
def returnBackwardsString(random_string):
    """Reverse and return the provided URI"""
    return "Breaking the unit test"

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

このコードがリポジトリにチェックインされると、CodeBuild が webhook を介して自動的に呼び出され、アプリケーションテストが実行されることがわかります。

失敗した CodeBuild テストの実行の例

失敗した CodeBuild テストの実行の例

CodeBuild は、失敗したテストの実行を GitHub に報告します。

失敗した GitHub コミットステータスの例

失敗した GitHub コミットステータスの例

GitHub ワークフローがコミットのステータスを failure と検出したため、ワークフローは「Check commit status」のステップを通過しませんでした。

失敗した GitHub ワークフローの例

失敗した GitHub ワークフローの例

次の例は、アプリケーションコード (app.py) の変更を示しています。このコードは、追加のロギングステートメントを導入しますが、ユニットテストに合格します。

"""
Main application file
"""
from flask import Flask
import logging
app = Flask(__name__)

# Initialize Logger
LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)

@app.route('/<random_string>')
def returnBackwardsString(random_string):
    """Reverse and return the provided URI"""
    LOGGER.info('Received a message: %s', random_string)
    return "".join(reversed(random_string))

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

このコードがリポジトリにチェックインされると、CodeBuild が webhook を介して再び呼び出され、アプリケーションテストが正常に実行されることがわかります。

CodeBuild テストの正常な実行の例

CodeBuild テストの正常な実行の例

CodeBuild は、テストの正常な実行を GitHub に報告します。

正常な GitHub コミットステータスの例

正常な GitHub コミットステータスの例

GitHub ワークフローは、コミットのステータスが success になることを検出し、ワークフロー内の残りのステップが ECS へのアプリケーションのデプロイを正常に実行します。

正常な GitHub ワークフローの例

正常な GitHub ワークフローの例

最後に、ECS タスク定義が更新され、新しいアプリケーションコンテナイメージが反映されていることがわかります。  Git コミットハッシュがコンテナイメージタグとして使用されることに注意してください。

Git コミットハッシュを用いた ECS タスク定義の例

Git コミットハッシュを用いた ECS タスク定義の例

まとめ

この投稿では、GitHub Actions を使用して Amazon ECS で実行されているアプリケーションをサポートする方法を説明しました。  DevOps インフラストラクチャの一部として GitHub Actions を使用したい組織にとって、GitHub Actions を他の CI/CD サービス (AWS CodeBuild など) と組み合わせることは、機能豊富な CI/CD パイプラインを提供するためのシンプルで強力な方法となり得ます。  GitHub Actions の詳細については、「GitHub Actions の機能」を参照してください。