APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

GitHub ActionsでAWS ECSを使ったCI/CD環境を構築してみる

こんにちは、IaC技術推進部の松尾です。

GitHub ActionsでCI/CD環境を構築してみたので記事を書いてみます。躓いたポイントなど参考になれば。

  

構築の目的

CI/CDを理解する為というのが一番の目的です。覚えるには実際に手を動かしてみるのが一番かなと。

  

構築の手段

構築手順は、弊社が提供予定の「CI/CD体験パッケージ」の手順を利用していきます。 「CI/CD体験パッケージ」は、簡易的にCI/CD体験が出来る一連の手順を纏めたものになります。(来年公開予定) 「CI/CD体験パッケージ」の内容としては、バージョン管理にGitHub、CI/CD機能にGitHubActions、環境にAWS、を利用するものになります。

  

CI/CD体験パッケージのファイル構成

CI/CD体験パッケージのファイル、フォルダ構成はこのようになっています。 大まかに、AWSへリソースを構築する為のCloudFormation用ファイル、GitHub Action用のワークフローファイル、サンプルWebアプリのファイル、から構成されています。

このファイル一式をGitHubリポジトリへコミットした状態から構築開始していきます。

C:.
│  .gitignore
│  app.py
│  Dockerfile
│  README.md
│  requirements.txt
│  task-definition.json ---------------------------ワークフロー「aws-ecs-deploy.yml」でECSをデプロイする際に参照されるタスク定義を記載したファイル。
│
├─.github
│  └─workflows---------------------------------.github/workflws/*へ配置されたファイルがGitHubワークフローファイルとして読み込まれる
│          aws-cloudformation-deploy.yml-------ステップ1でAWSリソースを作成する為のyamlファイル。
│          aws-ecs-deploy.yml--------------------ステップ2とステップ3でECSをデプロイする為のyamlファイル。
│
├─app------------------------------------------サンプルWebアプリ用フォルダ
│  │  __init__.py
│  │
│  ├─models
│  │      model.py
│  │
│  ├─static
│  │  ├─css
│  │  │      style.css
│  │  │
│  │  └─images
│  │          books.jpg
│  │          favicon.ico
│  │
│  ├─templates
│  │      index.html---------------------------ステップ3でVersion2のWebアプリに更新する際に修正するファイル。
│  │      layout.html
│  │      resigter_articles.html
│  │      resigter_tags.html
│  │      update_articles.html
│  │
│  └─views
│          articles.py
│          tags.py
│
├─cloudformation-------------------------CloudFormation用フォルダ。
│  │  alb.yml--------------------------------aws-cloudformation-deploy.ymlが参照するファイル。
│  │  ec2.yml-------------------------------同上
│  │  ecr.yml-------------------------------同上
│  │  ecs.yml-------------------------------同上
│  │  rds.yml-------------------------------同上
│  │  securitygroup.yml-------------------------------同上
│  │  vpc.yml-------------------------------同上
│  │  vpcendpoints.yml-------------------------------同上
│  │
│  └─manual----------------------------手動実行用のCloudFormation用フォルダ。
│          iam.yml--------------------------GitHub ActionがAWSへ接続する為の認証情報を作成するファイル。
│          secretsmanager.yml-------------サンプルWebアプリがDBへ接続する為の認証情報を作成するファイル。
│
├─docs
│  │  ApplicationDesign.md
│  │  CICDFlowDesign.md
│  │  StartupGuide.md------------------構築手順ガイド。
│  │
│  └─images
│          architecture.svg
│          cicd_architecture.svg
│

  

概要図

まずはゴールを考えてみます。完成イメージはこんな感じでしょうか。実際はECSをデプロイする為にAWSへVPCやサブネット、SGなども作りますが図には記載していません。 「Git Users」のcommitをトリガーとして、PushされたImageを元に、ECSへ自動でデプロイされる仕組みを構築していきます。

構築準備

バージョン管理ツールとCI/CDツールがあれば構築できます。今回はGitHubとGitHubActionsになります。 環境はAWSを使っていきます。

GitHubアカウントの用意

今回、バージョン管理はGitHubを利用します。フリープランで問題無いので用意します。CI/CDツールとしてGitHub Actionsも使います。

AWSアカウントの用意

CI/CD環境をAWS上に構築していきます。AWSアカウントとIAMユーザを作成出来る権限が必要です。(今回はアクセスキーとシークレットキーを利用)

AWS CloudFormationの知識(Better)

CI/CD環境のベースとなるAWS上のリソースをClodFormationで構築していきます。AWSリソースの作成そのものは、CI/CDの本筋ではないので極力自動化して作っていきます。

  

構築手順概要

ステップ1~3として大きく3段階で進めていきます。

ステップ1はAWS環境の構築です。CI/CDの本筋ではないのでCloudFormationでさくっと作ってしまいます。

ステップ2では、初回の構築を想定したWebアプリをECSへデプロイしていきます。

ステップ3では、更新を加えたWebアプリをバージョン2としてcommitし、ECSへのデプロイまで自動で展開される過程を見ていきます。

ステップ1. AWS環境構築

AWSへGitHubActions用のCredentialを作成
AWSへGitHubActions用の踏み台サーバとキーペアを作成
GitHubActionsワークフローファイルを編集しAWSリソースを作成
Webアプリ用のデータベースを作成

  

ステップ2. Webアプリを構築

GitHubActions用のタスク定義ファイルを作成
Webアプリをデプロイ

  

ステップ3. Webアプリを更新

更新したWebアプリをコミットすると自動でデプロイされることを確認

  

構築

では進めていきます。

ステップ1. AWS環境構築

AWSへGitHubActions用のCredentialを作成

CI/CDの準備部分でAWS側のみの作業となるため、構築手順は割愛します。

  

AWSへGitHubActions用の踏み台サーバとキーペアを作成

CI/CDの準備部分でAWS側のみの作業となるため、構築手順は割愛します。

  

GitHubActionsワークフローファイルを編集しAWSリソースを作成

2つのワークフローファイルを編集していきます。

.github/workflow/aws-cloudformation-deploy.yml ・・・ステップ1でAWSリソースを作成する為のyamlファイル。AWSへClodFormationで複数リソースを作成することが目的のため説明は割愛。

.github/workflow/aws-ecs-deploy.yml ・・・ステップ2とステップ3でECSをデプロイする為のyamlファイル。初回のECSデプロイ、2回目以降のECSデプロイでも使用されるyaml。 元となるファイルは一般公開されているもの「

Amazon Elastic Container Serviceへのデプロイ - GitHub Docs

」 です。

元となるファイルからの変更箇所(★)と、設定の一部について説明をしていきます。

.github/workflow/aws-ecs-deploy.yml

name: Deploy to Amazon ECS

on:-------------------------------ワークフローファイル実行のトリガー設定
  push:---------------------------トリガー設定を「push」とするとGithubにpushされることがトリガーとなる。
    branches: [ "main" ]----------「branches」を「main」とすると、ブランチがmainの条件にマッチした場合がトリガー条件となる。
    paths-ignore:-----------------★ただし、「paths-ignore」で定義したパスにマッチするpushの場合は無視する。
      - '.github/**'
      - 'README.md'
      - 'cloudformation/**'
      - 'docs/**'
      - 'helloworld/**'

env:-----------------------------★ECSのbuild、deployに必要な変数を定義
  AWS_REGION: ap-northeast-1                   # set this to your preferred AWS region, e.g. us-west-1
  ECR_REPOSITORY: m0j1-knowledge-flask              # set this to your Amazon ECR repository name
  PJ_NAME: m0j1-knowledge-flask
  ECS_TASK_DEFINITION: task-definition.json # set this to the path to your Amazon ECS task definition
                                               # file, e.g. .aws/task-definition.json
                                               # containerDefinitions section of your task definition
--------------------------------以下設定はデフォルト値。BuildしたイメージをECRへプッシュ、トリガーにマッチした場合にECSへイメージをDeployする動作を行う。詳細は元ファイルのリンク参照。
permissions:
  contents: read

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

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

    - 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: ${{ env.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 }}
        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: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.ECR_REPOSITORY }}
        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: ${{ env.PJ_NAME }}-ecs-service
        cluster: ${{ env.PJ_NAME }}-ecs-cluster
        wait-for-service-stability: true

  

Webアプリ用のデータベースを作成

CI/CDの準備部分でAWS側のみの作業となるため、構築手順は割愛します。

ステップ1の完了後はAWS上にECSが展開された状態になっています。具体的にはVPC、サブネット、セキュリティグループ、VPCエンドポイント、RDS、ALB、ECR、ECSが構築された状態です。(赤枠内が構築された)

  

ステップ1の構築完了確認として、ALBのDNS名へアクセスしてみます。

  

無事HelloWorldが表示されました。これでクライアントからECS上のアプリまで疎通出来ることが確認出来たことになります。

  

構成図でいうとこの部分の疎通確認ですね。

  

ステップ2. AWS ECS上にWebアプリを構築

ステップ2の完了後イメージは、ECSへWebアプリ(バージョン1)がデプロイされた状態です。

GitHubActions用のタスク定義ファイルを作成

GitHubActionsでECSをデプロイするためのタスク定義ファイル(task-definition.json)を編集していきます。

familyブロック

familyのパラメータを先ほど構築したECSのタスク名に修正します。

項目
family デプロイするECSのタスク名

Before

{
    "family": "knowledge-flask-ecs-service-task",
    "networkMode": "awsvpc",
    "containerDefinitions": [
      {

After

{
    "family": "m0j1-knowledge-flask-ecs-service-task",
    "networkMode": "awsvpc",
    "containerDefinitions": [
      {

  

containerDefinitionsブロック

コンテナ定義のパラメータを各リソース名に修正します。

項目
containerDefinitions[0].name ECSのデプロイ元となるイメージを配置するECR名
containerDefinitions[0].image ECSのデプロイ元となるイメージ*1
containerDefinitions[0].environment[0].value RDSに作成したDBのエンドポイント名
containerDefinitions[0].secrets[0].valueFrom SecretManagerに登録したusernameのARN
containerDefinitions[0].secrets[1].valueFrom SecretManagerに登録したpasswordのARN
containerDefinitions[0].logConfiguration.options.awslogs-group ECSログの配置パス
containerDefinitions[0].logConfiguration.options.awslogs-region ECSログの保存リージョン

*1・・・このimage名はpushした際のECRのimage名に置き換わるのでダミーのパラメータになります。  imageURIを置き換える処理はAWSが提供する「AWS for GitHub Actions」の「amazon-ecs-render-task-definition」というActionが行います。

Before

    "containerDefinitions": [
      {
        "name": "knowledge-flask",
        "image": "knowledge-flask",
        "cpu": 256,
        "memory": 512,
        "portMappings": [
          {
            "containerPort": 80,
            "hostPort": 80,
            "protocol": "tcp"
          }
        ],
        "environment": [
          {
            "name": "DATABASE_ENDPOINT",
            "value": "knowledge-flask-postgres.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
          },
          {
            "name": "DATABASE_NAME",
            "value": "knowledge"
          }
          
        ],
        "secrets": [
          {
            "name": "DATABASE_USERNAME",
            "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:postgresql-credential-XXXXXX:username::"
          },
          {
            "name": "DATABASE_PASSWORD",
            "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:000000000000:secret:postgresql-credential-XXXXXX:password::"
          }
        ],
        "essential": true,
        "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "/ecs/knowledge-flask-ecs-service",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "ecs",
            "awslogs-create-group": "true"
          }
        }
      }
    ],
    "runtimePlatform": {
      "cpuArchitecture": "X86_64",
      "operatingSystemFamily": "LINUX"
    },
    "requiresCompatibilities": [
      "FARGATE"
    ],

After

AWSアカウントIDなど一部マスク(******)していますが実際に構築した値を入れていきます。

    "containerDefinitions": [
      {
        "name": "m0j1-knowledge-flask",
        "image": "hoge",
        "cpu": 256,
        "memory": 512,
        "portMappings": [
          {
            "containerPort": 80,
            "hostPort": 80,
            "protocol": "tcp"
          }
        ],
        "environment": [
          {
            "name": "DATABASE_ENDPOINT",
            "value": "m0j1-knowledge-flask-postgres.************.ap-northeast-1.rds.amazonaws.com"
          },
          {
            "name": "DATABASE_NAME",
            "value": "knowledge"
          }
          
        ],
        "secrets": [
          {
            "name": "DATABASE_USERNAME",
            "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:************:secret:postgresql-credential-******:username::"
          },
          {
            "name": "DATABASE_PASSWORD",
            "valueFrom": "arn:aws:secretsmanager:ap-northeast-1:************:secret:postgresql-credential-******:password::"
          }
        ],
        "essential": true,
        "logConfiguration": {
          "logDriver": "awslogs",
          "options": {
            "awslogs-group": "/ecs/m0j1-knowledge-flask-ecs-service",
            "awslogs-region": "ap-northeast-1",
            "awslogs-stream-prefix": "ecs",
            "awslogs-create-group": "true"
          }
        }
      }
    ],
    "runtimePlatform": {
      "cpuArchitecture": "X86_64",
      "operatingSystemFamily": "LINUX"
    },
    "requiresCompatibilities": [
      "FARGATE"
    ],

  

executionRoleArnブロック

実行ロールのARNを記載していきます。

項目
executionRoleArn ECS実行用に作成したRole

Before

    "executionRoleArn": "arn:aws:iam::000000000000:role/knowledge-flask-ecs-cluster-task-execution-role",
    "cpu": "256",
    "memory": "512"
}

After

    "executionRoleArn": "arn:aws:iam::************:role/m0j1-knowledge-flask-ecs-cluster-task-execution-role",
    "cpu": "256",
    "memory": "512"
}

    

これでタスク定義ファイルの準備が整いました。commitしてアプリがデプロイされることを見ていきます。

  

Webアプリをデプロイ

git push 後にGitHubActionsを見てみると、push をトリガーとしてWorkflowが動いていることが確認出来ました。完了まで暫く待ってみます。

成功しました!

  

ブラウザから先ほどと同じURLへアクセスしてみると

想定通りのWebアプリが表示されました。これは一回目のBuildとDeployがされた状態(Version1のWebアプリデプロイを疑似した状態)です。

  

ステップ3. Webアプリを更新

ステップ3の完了後イメージは、ECSへWebアプリ(Version2)がデプロイされた状態です。

更新したWebアプリをコミットすると自動でデプロイされることを確認

CI/CDの流れを確認する為、Webアプリを更新してcommitしてみます。 これにより、Version2のWebアプリデプロイを疑似しています。

更新箇所は、トップページとなる .app/templates/index.html へVerison2! を追加しました。

再度commitすると、自動的にワークフローが開始され、

  

「App Version 2 commit 」が完了します。

  

ブラウザを見てみます。

想定通り、修正したWebアプリ(Version2!の追記)がデプロイされています。

今回のCI/CD構築はここまで。

 

まとめ

構築前はCI/CDの具体的なイメージがし辛かったのですが、実際に構築してみると一連の流れが分かりやすく、理解が進んだなという印象です。 この構築は通して2回行ったのですが、2回目の理解度がだいぶ違いました。

また、今回はCI/CDの「Code」⇒「Build」⇒「Test」⇒「Deploy」のうち、「Test」部分が無い構成でした。 実案件では「Test」部分も重要な要素かと思うので確認しておく必要があるなと感じました。

「Deploy」方法についても、今回は一度に切り替えてますが、実際には切り替え方法も 環境に合ったベストな形を選択していくことが必要だと思いました。 具体的には、段階的な切り替え、全体が切り替わる時間、切り戻しのリスク/しやすさなどでしょうか。

CI/CDツールも、オンプレとクラウドに多数の製品があるので、メリデメを踏まえた提案をしていきたいなと思います。

  

弊社ではCI/CDの導入支援サービスも行っています。 是非ご検討下さい。

www.ap-com.co.jp

(おまけ)デプロイの失敗

ステップ2. Webアプリを構築

Webアプリをデプロイ

を実行した際、Failする場合がありました。原因はパラメータの記述に良くない点があったことで、対策としては以下のように修正することでOKでした。

  

Webアプリをデプロイ時、Workflowは完了しましたがFail..

  

ログを追っていくとwarningメッセージが出ていました。

The set-output command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

リンク先を見ると「set-output」が廃止されるようです。

リンク先の説明を参考に、今回のパラメータでは、.github/workflow/aws-ecs-deploy.ymlのこの部分を

echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

このように修正すればOK。

echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

commitして成功でした。