NRIネットコム Blog

NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

GitHub Actionsの知識0からとりあえずECSデプロイをやってみる

こんにちは。梅原です。年の瀬ですね、今年1年あっという間でした。

アプリケーションのソースコードをGitHubで管理している方は多いのではないでしょうか。GitHubにはGitHub ActionsというCI/CDを実施できるサービスがあります。
今回は知識0の状態でGitHub ActionsからAmazon ECSへデプロイを試してみます。
(ところどころ命名やコミットメッセージが適当なのは目を瞑ってください)

GitHub Actionsとは

GitHub Actionsとは、GitHub上のイベントをトリガーにアプリケーションのテストやデプロイといったCI/CDパイプラインの実行が可能なサービスです。
GitHub Actionsが実行されるコンピュートリソースはGitHubが用意してくれるので、OSSのようにサーバーを用意する必要がありません。 よく使われるパッケージマネージャーやDockerなどのツール、AWS CLIなど、多くのツールがすでに含まれており簡単に始めることができます。

GitHubのサービスのため、pushやプルリクエストだけでなく、Issueの作成といった多くイベントをトリガーに設定できます。
GitHub Actionsを使うためには、.github/workflows配下に処理を定義したyamlファイルを格納しておくだけで、設定したトリガーがあった際にGitHub Actionsが実行されます。

GitHub Actionsの構成と書き方

GitHub Actionsの構成

GitHub Actionsの構成として、以下4つがあります。

  • イベント
    実行のトリガーとなるGitHubのアクティビティです。特定ブランチへのpushやプルリクエストはもちろんIssueの作成などGitHub上の多くのアクティビティを設定することが可能です。
  • ステップ
    実行したい処理の最小単位です。アプリケーションのテストやデプロイといった実際の処理を定義します。
  • ジョブ
    1つ以上のステップをグルーピングしたものです。
  • ワークフロー
    一連のジョブの集まりをグルーピングしたものです。
    先述のイベントがトリガーとなり、ワークフローが実行されます。

これだけではわからないと思うので、実際にyamlファイルの例を挙げたいと思います。

GitHub Actionsの定義ファイル書き方の例

ワークフローのyamlはこのような形です。

name: sample workflow

on:
  push:
    branches:
      - main

jobs:
  helloworld:
    name: hello world                  
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello World"

  goodmorning:  
    name: greeting
    runs-on: ubuntu-latest
    steps:
      - run: echo "good morning"
      - name: Checkout
        uses: actions/checkout@v4.1.1
      - name: check dirctory
        run: |
          pwd
          ls -al

  goodnight:  
    name: goodnight
    runs-on: ubuntu-latest
    needs: ["goodmorning", "helloworld"]
    steps:
      - run: echo "good night $name!"
        env:
          name: k-umehara

.github/workflows/sample.yamlに格納しておきます。ファイル名は任意の名前で問題ありません。 mainブランチへのpushをトリガーにsample workflowという名前のワークフローが実行されます。 ワークフローには、helloworldgoodmorninggoodnightという3つのジョブが含まれています。

順番に見ていきます。

name: sample workflow

on:
  push:
    branches:
      - main

ここではワークフローの名前や、ワークフローのトリガーとなるイベントの定義をします。 特定パスの変更に限定して、ワークフローを実行するといったこともできます。

jobs:
  helloworld:
    name: hello world                  
    runs-on: ubuntu-latest
    steps:
      - run: echo "Hello World"

1つ目のジョブhelloworldでは単一のステップを実行しています。
nameでジョブに名前を付けています。
runs-onでは、ジョブの実行環境であるランナーを指定します。現在ではLinux、MacOS、Windowsに対応しており、LinuxはUbuntu 22.04が対応しています。
stepsには具体的な処理内容を記述します。今回はただechoしているだけですが、本来はアプリケーションのテストやdockerイメージのビルドといった処理内容を定義します。

  goodmorning:  
    name: greeting
    runs-on: ubuntu-latest
    steps:
      - run: echo "good morning" 
      - name: Checkout
        uses: actions/checkout@v4.1.1
      - name: check dirctory
        run: |
          pwd
          ls -al

2つ目のジョブgoodmorningでは3つのステップを実行しています。
1つ目と2つ目のステップを見たらわかるように、ステップの定義方法は2つあります。

  • run
    cliでコマンドを直接指定。処理したい内容のコマンドを記述します。
  • uses
    事前定義されたアクションの実行。よく利用される一連の処理内容を簡単に呼び出すことができます。 GitHubのマーケットプレイス上で公開されているものを使うか、自分でもアクションを作成することができます。

2つ目のステップで使用しているアクションでは、ソースコードをcloneするときに使うactions/checkoutを使用しています。 自前でコマンドを記述する必要がないです。

3つ目のステップでは、2つ目のアクションの確認として、lsコマンドを実行しています。複数行での実行も可能です。

  goodnight:  
    name: goodnight
    runs-on: ubuntu-latest
    needs: ["helloworld", "goodmorning"]
    steps:
      - run: echo "good night $name!"
        env:
          name: k-umehara

3つ目のジョブgoodnightです。
通常、ジョブは並列で実行されますが、needsを使用して依存関係を持たせる事ができます。 今回の例だと、1つ目のhelloworldと2つ目のgoodmorningジョブが完了後に、3つ目のgoodnightを実行させています。
ステップに環境変数をもたせることもできます。

ワークフロー実行確認

実際にmainブランチにpushしてみるとGitHubリポジトリのActionsタブは以下のようになります。

定義したsample workflowという名のワークフローがcommitメッセージとともに存在しています。

ワークフローの詳細ページでは、3つのジョブが存在し、1つ目と2つ目のジョブは並列実行ですが、3つ目のジョブは直列実行されていることが可視化されています。

詳細なジョブ画面に移るとログを見ることができます。usesで指定したアクションが実行され、lsコマンドも正常に出力されていることがわかります。

これまででワークフローの大まかな構成や流れは掴めたのではないでしょうか。

ここからはECSへのデプロイをやってみます。

AWSとの接続

AWS上のリソースを触るときには、AWSの認証情報がもちろん必要です。 クレデンシャルを発行してアクセスキーを使用することもできますが、アクセスキーが流出するおそれがあるので推奨ではありません。
GitHub Actionsでは、OpenID Connectがサポートされているので、IAM IDプロバイダーを使用した認証での接続が推奨です。 IAM IDプロバイダーを使うことで、GitHub Actionsから特定のロールを使用することができます。 認証処理に関しても、先述のアクションを使うことができます。

uses: aws-actions/configure-aws-credentials@v4

設定方法はドキュメントを参照ください。

docs.github.com

環境変数と秘匿情報

ワークフロー内で使用したい変数は環境変数を使うことができます。 またジョブ毎にランナーが、ステップ毎にシェルが与えられるので、環境変数のスコープは意識しておく必要があります。 定義ファイルでは以下のような式で参照できます。

aws-region: ${{ env.AWS_REGION }}

秘匿情報はGitHub Actionsの定義ファイルにハードコード、つまりリポジトリに含めてはいけません。 OIDC認証に使用するIAMロール名やDBの接続情報といった秘匿情報を参照する場合は、リポジトリのsettingsから秘匿情報を設定し、そこから読むようにしましょう。 定義ファイルでは以下で参照できます。ステップのログにおいても*でマスクされるので安全です。

role-to-assume: "arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}"

ECSへデプロイしてみる

具体的なアプリケーションの内容やECSへのデプロイは本題ではないので、ここでは省略します。 ECSのデプロイの流れに関してはこちら参照ください。

tech.nri-net.com

.github/workflows/ecs.yamlに以下内容を記載してmainブランチにpushします。

name: ECS deploy

on:
  push:
    branches: "main"

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: <ECRリポジトリ名>
  ECS_SERVICE: <ECSサービス名>
  ECS_CLUSTER: <ECSクラスター名>
  ECS_TASK_DEFINITION: taskdef.json  # ソースコードにtaskdef.jsonを含めておく
  CONTAINER_NAME: <コンテナ名>
  
permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v4
      
    - name: aws setup credentials
      uses: aws-actions/configure-aws-credentials@v4   # AWSのcredentials設定アクション
      with:
        role-to-assume: "arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}"
        aws-region: ${{ env.AWS_REGION }}


    - name: Login to Amazon ECR  #ECRへログイン
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2

    - name: Build and push docker image to ECR  # dockerイメージのbuildとpush
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      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: Render Task Definition  # タスク定義のイメージ部分のレンダリング
      id: render-taskdefinition
      uses: aws-actions/amazon-ecs-render-task-definition@v1.2.0
      with:
        task-definition: ${{ env.ECS_TASK_DEFINITION }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

    - name: Deploy Amazon ECS task definition  # タスク定義を元にECSをデプロイ
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.render-taskdefinition.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

ECSへのデプロイの流れとして、ECRにログイン、ECRにdockerイメージをpush、イメージURIを書き換えたタスク定義を元にデプロイといった流れが必要です。
これらの一連の処理も、アクションとしてAWSが用意してくれており、入力パラメータを渡してあげるだけで実装できます。 デプロイアクションはECS標準のローリングアップデートはもちろんB/Gデプロイも使用することができます。 github.com

このワークフロー定義ファイルをmainブランチにpushすると、デプロイが開始されます。
ECSサービスの画面にもプロビジョニングステータスのタスクがあります。

デプロイが完了して新しいタスクの画面を見ると、 新しいリビジョンのタスク定義でデプロイされていることがわかます。

また旧タスクが終了しています。

GitHub上でも無事完了していることがわかります。

最後に

用意されているactionをうまく活用することで非常に簡単に実装できました。 今回は実施しませんでしたが、条件分岐やエラーハンドリング、アーティファクトの保存、承認フローといった処理もできます。
また料金に関して、パブリックに公開しているリポジトリは無料です。プライベートに閉じているリポジトリは、無料枠を超えると従量課金となっています。 少し高めな印象を受けたので料金には注意する必要があります。

執筆者: 梅原 航 インフラエンジニア。 主にAWSを使ってます。