🔖

【CI/CD】GitHub ActionsからECSにアプリケーションをデプロイする

2021/08/22に公開

はじめに

以前、以下の記事で、CloudFormationを使って自作のアプリケーションをECS上で動かすハンズオンを実施しました。

https://zenn.dev/soshimiyamoto/articles/d9d425ded03ac5

ここができたら、今度はデプロイまで自動化しちゃおうということで、GitHubにpushしたらECSにアプリケーションを自動でデプロイするパイプラインを作成することとしました。

構成

AWSの環境に関しては前回で構築が完了しているので、GitHub周りの設定をしていきます。

GitHub Acitonsとは

GitHubが提供するタスク自動化サービスとなります。GitHubのイベントをトリガーにしてワークフローを起動することができます。例えばmasterブランチにpushされた、Pull Requestが作成されたなどのイベントを検知して、deployを実行するなどのことができるようになります。

https://docs.github.com/ja/actions/learn-github-actions/introduction-to-github-actions

ハンズオン

GitHub Actionsの設定

  1. まずはGitHubにログインし、リポジトリに移動し、そこのActionsタブをクリックします。

  2. テンプレートが複数表示されます。

  3. 今回はDjangoのサンプルアプリケーションのDockerイメージを使うので、Djangoを選択します。set up this workflowをクリックします。

  4. デフォルトでファイルが出来上がるので任意のファイル名(ここではdjango.yml) を入力して、start commitを押下します。

これでworkflowが完成!簡単!

ワークフローファイルの変更(django.yml)

ローカルにPullしてdjango.ymlを変更していきます。

ファイルを以下のようにします。

django.yml
name: Django CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8, 3.9]

    steps:
    - uses: actions/checkout@v2

    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run Tests
      run: |
        python3 manage.py test

    - 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: ap-northeast-1

    - 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: ${{ secrets.AWS_ECR_REPO_NAME }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        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: ./aws/task-definition.json 
        container-name: django-app
        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: django-service
        cluster: django-cluster
        wait-for-service-stability: true

これをmasterブランチにpushすると、ローカルで編集したdjangoアプリケーションがそのままECRリポジトリにpushされて、さらに稼働中のアプリケーションに反映されます!

workflowファイルの詳細

上記で作成したファイルの詳細を見ていきましょう

以下はテンプレートですでに出来上がっている個所です。
nameはworkflowの名前なので任意で大丈夫です。onはworkflowの起動トリガーを表していて、今回の場合は、masterブランチへのpushと、masterブランチへのpull requestをトリガーにしてworkflowを起動することになります。

name: Django CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

以下に関してもテンプレートですでに出来上がっている個所です。
jobsが起動するジョブを記述するセクションです。
buildはjobsのIDを表していて実はbuildでなくても良かったりします。
strategyで、それぞれのジョブを実行する様々なバリエーションを定義できます。max-parallelでジョブの並列最大実行数を定義できますし、matrixでランタイムの選択をする子ができます。

runs-on: ubuntu-latestは、GitHub上でホストされているubuntuで処理が実行されることを意味しています。
stepsはジョブ内で実際に処理する内容をグルーピングします。
基本的にnameでワークフロー名を記述し、そこに処理したい内容をrunに記載していきます。useは、コミュニティで定義されたアクションを利用する際に記述します。uses: actions/checkout@v2actions/checkout@v2という名前のコミュニティアクションのv2 を取得するようにジョブに指示するという意味になります。
withはアクションによって定義される入力パラメータのmapです。キーと値を持ちます。

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.8, 3.9]

    steps:
    - uses: actions/checkout@v2

    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v2
      with:
        python-version: ${{ matrix.python-version }}

    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run Tests
      run: |
        python3 manage.py test

ここからは自分でアクションを記述していきます。
まずは、AWSのクレデンシャル情報を登録します。すでにコミュニティで準備している、aws-actions/configure-aws-credentials@v1を利用します。入力として、クレデンシャル情報を渡してあげる必要があります。
secretsで定義されている値AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYを渡しています。secretsは、GitHubのレポジトリの画面で、[Setting] > [Secrets]から登録することができます。ちなみに、一度登録すると参照ができなくなるので値を忘れてしまった場合はUpdateする必要があります。

 - 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: ap-northeast-1

次に、ECRへのログイン処理と、ECRレポジトリへのコンテナイメージのPushを行います。
ECRへのログイン処理には、コミュニティアクションaws-actions/amazon-ecr-login@v1を利用します。また、ログイン処理の実行結果を利用するために、このアクションにはidでIDlogin-ecrを付与します。
そして、次のアクションで実際にコンテナイメージのbuildとpushを行います。ここでは、envを使って環境変数の設定を行います。
その中で、login-ecrの処理の結果取得されたRegistory情報を環境変数にセットしています。
IMAGE_TAGには、${{ github.sha }}をセットしていますが、これは実行時にそのワークフローの実行のトリガとなったコミットのハッシュ文字列に置き換えられます。

- 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: ${{ secrets.AWS_ECR_REPO_NAME }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

ここまででECRにコンテナイメージを行っているので、ここからは実際にECSタスクの更新処理を記述します。
まずはタスク定義の更新処理です。それにはコミュニティアクションのaws-actions/amazon-ecs-render-task-definition@v1を利用します。入力には、task-definition, container-image, imageを指定する必要があります。imageは上記でpushしたコンテナイメージを利用するため、build-imageの処理結果を取得して値をセットします。
task-definitionは実際にGitHubレポジトリにタスク定義ファイルを配置します。タスク定義ファイルはAWSのECSの画面から取得して加工したものを利用します。

最後に更新されたタスク定義をもとに、ECSにデプロイ処理を実行します。
こちらもコミュニティアクションaws-actions/amazon-ecs-deploy-task-definition@v1を利用します。入力には、task-definition, service, cluster, wait-for-service-stabilityを指定します。task-definitionにはtask-defで更新されたタスク定義を利用します。service, clusterには該当のECSサービス、ECSクラスター名を指定します。
wait-for-service-stabilitytrueにすることで、ECSサービスの安定稼働まで実行完了を待つことができるようになります。

    - 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: ./aws/task-definition.json 
        container-name: django-app
        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: django-service
        cluster: django-cluster
        wait-for-service-stability: true

まとめ

今回はGitHub Actionsを利用して、GithubへファイルをPushすることで、コンテナイメージの作成からECSのデプロイまでのCICDパイプラインを作成しました。ほとんどがコミュニティアクションで構成することができるので簡単に構築することができました。

Discussion