📄

Github Actionsを用いてReactアプリのCI/CD環境を構築

2023/01/07に公開

はじめに

CI/CDとは、アプリケーション開発に自動化を取り入れて、アプリを提供する頻度を高める手法です。

CIは継続的インテグレーション(Continuous Integration)を指し、自動化プロセスを意味します。CIが正常に機能すると、アプリへの新しいコード変更が定期的にビルド、テストされ、迅速かつ容易に共通リポジトリに統合されます。

CDは継続的デリバリー(Continuous Delivery)、または継続的デプロイメント(Continuous Deployment)を指します。開発者による変更をリポジトリから本番環境に自動的にリリースし、顧客が使用できるようにするというものです。運用チームが担当する手動プロセスが多く、アプリケーション提供が遅れるという問題に対処します。

GitHub ActionsはCI/CDツールの一つで、GitHub上で動作するサーバレス実行環境です。複数のActionを組み合わせ、実行する順番を定義することで独自のワークフローを構築できます。他に有名なツールとしてCircleCI、Jenkinsなどがあります。

今回はECS-FargateにデプロイされたReactアプリケーションを用いて、Github Actionsを用いてビルド・デプロイの自動化を行います。

業務にて行なった内容のメモとして執筆します。AWSを使うようになって日も浅いので、誤りや指摘、よりよいやり方などありましたらコメントにて教えていただけると嬉しいです。

前提

・AWS CLIがインストールされていること

手順

ECSを用いたデプロイ環境の準備

ECS-Fargateでデプロイされたリソースを使用します。
こちらの記事(Reactアプリをdocker composeとECS(Fargate)でデプロイする)で手順を公開しているので必要であれば参考にしてください。

Githubリポジトリ用環境変数の設定

CI/CD環境を構築するにあたってAWSのアカウントIDや鍵情報が必要となりますが、それらの情報をそのままリポジトリ上のファイル記載するのは好ましくありません。
今回はGithubのRepository secretsにそれらの情報を登録し、Github Actions用のファイルから参照できるようにします。

作成するのは下記6点です。

  • AWS_ACCESS_KEY_ID: アクセスキーID
  • AWS_SECRET_ACCESS_KEY: 秘密アクセスキー
  • CONTAINER_NAME: コンテナ名
  • ECR_REPOSITORY: ECRリポジトリ名
  • ECS_CLUSTER: ECSクラスター名
  • ECS_SERVICE: ECSサービス名

アクセスキーID、秘密アクセスキーは~/.aws/credentialsにて確認可能です。

CI/CD環境構築用ファイルの作成

↑のGithub ActionsのECSテンプレートのyamlファイルをもとに少し手を加えます。
下記内容にてdeploy.yaml, task-definition.jsonを作成します。

.github/workflows/deploy.yaml
name: Deploy to Amazon ECS

on:
  push:
    branches: ["main"]

env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_REGION: ap-northeast-1
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  CONTAINER_NAME: ${{ secrets.CONTAINER_NAME }}
  ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY }}
  ECS_CLUSTER: ${{ secrets.ECS_CLUSTER }}
  ECS_SERVICE: ${{ secrets.ECS_SERVICE }}
  IMAGE_TAG: ${{ github.sha }}

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: ${{ env.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ env.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 }}
          ECR_REPOSITORY: ${{ env.ECR_REPOSITORY }}
          IMAGE_TAG: ${{ env.IMAGE_TAG }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
      - 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: ${{ env.CONTAINER_NAME }}
          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.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true
task-definition.json
{
  "family": "react-ecs-sample",
  "requiresCompatibilities": ["FARGATE"],
  "executionRoleArn": "ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "react-ecs-sample",
      "portMappings": [
        {
          "containerPort": 80,
          "hostPort": 80,
          "protocol": "tcp"
        }
      ],
      "essential": true
    }
  ]
}

タスクの細部についてはjsonファイルに追記することで設定が可能です。
(参考:https://docs.aws.amazon.com/cli/latest/reference/ecs/register-task-definition.html

本稿ではcreate-react-appで作成したプロジェクトがあらかじめデプロイされており、最終的なディレクトリ構成は次のようになります。

.
├── .github
├── .gitignore
├── Dockerfile
├── README.md
├── docker-compose.yaml
├── node_modules
├── package-lock.json
├── package.json
├── public
├── src
├── task-definition.json
└── tsconfig.json

作成した2ファイルをmainブランチにプッシュします。

ECSタスク実行用ロールの作成

プッシュしたことによりGithub Actionsが起動しますが、下記のように失敗しました。

ECS > Clusters > Service > Events
service XXXXXXXXXXXX failed to launch a task with (error ECS was unable to assume the role 'arn:aws:iam::YYYYYYYYYYYY:role/ecsTaskExecutionRole' that was provided for this task. Please verify that the role being passed has the proper trust relationship and permissions and that your IAM user has permissions to pass this role.).

理由はtask-definition.jsonの4行目のecsTaskExecutionRoleという名前のリソース名がないからでした。
この値がAmazonECSTaskExecutionRolePolicyが付与されたロールであれば問題なくECSへのデプロイができますが、Github上にプッシュされているtask-definition.jsonにIDなどが含まれたリソース名を記述したくなかったので、
ecsTaskExecutionRoleという名前のロールリソースを新たに作成しました。

構成した環境の確認

create-react-appで作成したApp.tsx<a>React ECS Sample</a>を追記してプッシュし、再度Github Actionsを起動させてみます。

リソースにアクセスすると追記した内容が反映されていることを確認できました!

参考

使用したコード
https://github.com/maximum-maximum/react-ecs-sample
https://zenn.dev/maximum_maximum/articles/31c09e1b0f9491
https://docs.aws.amazon.com/cli/latest/reference/ecs/register-task-definition.html
https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-unable-to-assume-role
https://docs.github.com/en/actions/deployment/deploying-to-your-cloud-provider/deploying-to-amazon-elastic-container-service
https://www.redhat.com/ja/topics/devops/what-is-ci-cd

Discussion