0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actions でのECSへのデプロイ コンパクトなサンプル

Posted at

GitHub Actions によるデプロイの学習目的で、
ソースコードからECSへデプロイする、最小限の構成のサンプルを作ってみました。

元になる環境は、拙著 ( Terraform × AWS 入門から実践へ ) の付録のTerraformのサンプルコードを使って、
nginxのイメージでサンプルページを表示させるECSベースのシステムを作ったところを起点とします。

ディレクトリ構成

リポジトリに格納するファイル、ディレクトリの構成図です。

ルートディレクトリ(リポジトリ名)
├── .github/
│   └── workflows/
│        └── deploy.yml
├── docker/
│   ├── Dockerfile
│   └── nginx.conf
├── ecs/
│   └── task-definition.json
├── public/
│   └── index.html
├── scripts/
├── .gitignore
└── README.md

各ファイル、設定

ファイルの内容や設定を紹介します。

deploy.yml

GitHub Actions の ワークフロー の定義ファイルです。
ワークフローを実行するタイミングは、次の設定にすることで、pushをトリガーとして実行することも可能です。

on:
  push:
    branches: [ "develop" ]

ここでは、GitHubのコンソールから手動でワークフローを実行できるようにしました。
この場合、ブランチはデフォルトブランチのみ実行できる仕様です。
つまり、実行前にデフォルトブランチにプッシュもしくはマージする必要があります。
今回は "develop" ブランチをデフォルトブランチにします。

deploy.yml
name: Build and Deploy to ECS (OIDC)

on:
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: ${{ secrets.AWS_REGION }}
  ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
  ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
  ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
  TASK_DEFINITION: ${{ secrets.TASK_DEFINITION }}

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

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

      - name: Build, tag, and push image to ECR
        env:
          REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG -f docker/Dockerfile .
          docker push $REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "IMAGE=$REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_ENV

      - name: Render new task definition
        id: render-task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: ${{ env.TASK_DEFINITION }}
          container-name: samp01-ecs-service
          image: ${{ env.IMAGE }}

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

Dockerfile

デプロイする イメージ に対する実行内容の定義ファイルです。
今回、nginx でシンプルにWebページを公開するだけなのでこのような記載ですが、
実際のプロダクトでは、別の言語、ミドルウェアなどより複雑な内容の動作環境で動かすことが多いので、要件に合った内容に差し替えます。

FROM nginx:alpine

# 既存の default.conf を削除
RUN rm /etc/nginx/conf.d/default.conf

# nginx 設定を配置
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf

# Webコンテンツを配置
COPY public/ /usr/share/nginx/html/

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

nginx.conf

nginxの設定ファイルです。
内容に深い意味はありません。ChatGPTに作ってもらったものをそのまま使っています。
実環境では、使う言語やミドルウェア等の仕様に合ったファイル名、内容に差し替えます。

nginx.conf
server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

task-definition.json

ECSの対象サービスのタスク定義です。
AWSマネコンから現状のタスク定義の内容を確認してコピペし、いくつか情報を示す要素やリビジョン固有の要素だけ削除しています。

  • 削除した要素
    • registeredAt
    • registeredBy
    • revision
    • status
    • taskDefinitionArn
task-definition.json
{
    "compatibilities": [
        "EC2",
        "MANAGED_INSTANCES",
        "FARGATE"
    ],
    "containerDefinitions": [
        {
            "cpu": 0,
            "environment": [],
            "essential": true,
            "image": "nginx:latest",
            "mountPoints": [],
            "name": "samp01-ecs-service",
            "portMappings": [
                {
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp"
                }
            ],
            "systemControls": [],
            "volumesFrom": []
        }
    ],
    "cpu": "256",
    "executionRoleArn": "<ECSのタスク実行ロールのARN>",
    "family": "<ECSサービス名>",
    "memory": "512",
    "networkMode": "awsvpc",
    "placementConstraints": [],
    "requiresAttributes": [
        {
            "name": "com.amazonaws.ecs.capability.task-iam-role"
        },
        {
            "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
        },
        {
            "name": "ecs.capability.task-eni"
        }
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "taskRoleArn": "<ECSタスクロールのARN>",
    "volumes": [],
    "tags": [
        {
            "key": "Project",
            "value": "samp-pj"
        },
        {
            "key": "Environment",
            "value": "ouyo"
        }
    ]
}

ワークフローが実行されると、リポジトリのファイルを元にDockerイメージとしてビルドされ、ECRに格納されます。
そして、その格納されたイメージを元にタスクが起動されるように、
やはりワークフローの中でこのファイルの定義内容が更新され、
ECSのタスク定義として登録されます。

注意点として、このデプロイ方法を使い始めて以降は、ECSサービスのタスク定義は、Terraformではなくこちらで定義、管理していきます。
(Terraformのコードにタスク定義の変更を無視する設定を追加します)

index.html

nginxでWebページとして公開するhtmlファイルです。
内容に深い意味はありません。ChatGPTに作ってもらったものをそのまま使っています。
実環境では、使う言語やミドルウェア等の仕様に合ったファイル名、内容に差し替えます。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Hello nginx</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      background: #0f172a;
      color: #e5e7eb;
      display: flex;
      height: 100vh;
      align-items: center;
      justify-content: center;
    }
    .card {
      background: #020617;
      padding: 2.5rem 3rem;
      border-radius: 12px;
      box-shadow: 0 10px 25px rgba(0,0,0,.4);
      text-align: center;
    }
    h1 {
      margin: 0 0 0.5rem;
      font-size: 2rem;
    }
    p {
      margin: 0.5rem 0 0;
      color: #94a3b8;
    }
  </style>
</head>

<body>
  <div class="card">
    <h1> Hello nginx!</h1>
    <p id="message">This site is served by nginx.</p>
    <p id="time"></p>
  </div>

  <script>
    const now = new Date();
    document.getElementById("time").textContent =
      "Loaded at: " + now.toLocaleString();
  </script>
</body>
</html>

IAM IDプロバイダの設定

GitHub Actions から AWSアカウントへの認証は OpenID Connect(OIDC) を使うので、IDプロバイダを設定します。

CloudShellから、次のコマンドを実行しました。

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

thumbprint の値は、次の記事から情報を得ました。(今後変更される可能性があります)
https://github.blog/changelog/2023-06-27-github-actions-update-on-oidc-integration-with-aws/?utm_source=chatgpt.com

s03.png

IAMロールの設定

AssumeRole用のIAMロールを作成します。

  • ロール名は、ここでは "GitHubActionsRole" とします(ロール名は任意です)
  • リポジトリ名は「<アカウント名>/<リポジトリ名>」の形式です

信頼ポリシー

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<アカウントID>:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:<リポジトリ名>:ref:refs/heads/<ブランチ名(サンプルでは"develop")>"
                }
            }
        }
    ]
}

インラインポリシー(ポリシー名は任意)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload",
                "ecr:PutImage",
                "ecr:DescribeRepositories"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ecs:RegisterTaskDefinition",
                "ecs:UpdateService",
                "ecs:DescribeServices",
                "ecs:DescribeTaskDefinition",
                "ecs:TagResource"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": [
                "<ECSのタスクロールARN>",
                "<ECSのタスク実行ロールARN>"
            ]
        }
    ]
}

Repository secrets

GitHub側に持たせる情報です。Secretとして設定します。
ここではRepository secretsに設定しますので、
Environment secrets と混同しないように注意してください。
(プロジェクトの方針によっては、Environment secrets に持たせることもできそうです)

Name Value(Secret)
AWS_REGION ap-northeast-1
AWS_ROLE_ARN <IAMロール "GitHubActionsRole" のARN>
ECR_REPOSITORY <ECRのリポジトリ名>
ECS_CLUSTER <ECSのクラスター名>
ECS_SERVICE <ECSのサービス名>
TASK_DEFINITION ecs/task-definition.json

s01.png
s02.png

デプロイの動き

実行前に、サイトの表示内容、ECSタスクの起動状況、ECSタスク定義のリビジョン番号を確認します。
ugoki001.png
ugoki002.png

GitHub Actions のメニューから、ワークフローを実行します。
ugoki003.png

図の箇所をクリックすることで、実行の経過や実行ログを見られます。
ugoki004.png

エラーが発生すると、エラーメッセージが表示されます。
ugoki005.png
ugoki006.png
ugoki007.png

エラー内容を元に設定を修正後、リランできます。
ugoki008.png
ugoki009.png

リポジトリ内のファイルを修正した場合は、再度デフォルトブランチへマージして、新たに実行します。

成功したときの画面表示です。
ugoki010.png
ugoki011.png

ECSのタスクが新たに直近の時間で起動されて差し替えられており、
ECSタスク定義のリビジョン番号がインクリメントされています。
(一度やり直したので図では2インクリメントされています)
ugoki012.png
ugoki013.png

Webページの表示内容が更新されています。
ugoki014.png

おわりに

このコンパクトなサンプルを元に、発展させて使っていければと思います。
CodeDeploy と組み合わせるようにするとか。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?