コネヒト開発者ブログ

コネヒト開発者ブログ

GitHub Actions & ecspressoによるデプロイフロー構築

こんにちは、リードエンジニアの @dachi_023 です。今回はGitHub Actionsとecspressoでデプロイフローの構築をしたのでそれについて書いていきます。先に言っておくと簡単にセットアップできるし設定もシンプルなのでかなりおすすめです。

これまでのデプロイ

コネヒトではECS環境へのデプロイに silinternational/ecs-deploy を採用しています。CodeBuildもしくはTravis CI上からecs-deployを利用してECS環境にアプリケーションをデプロイする構成です。

CI/CDツールの乗り換え検討

これまでずっとTravis CIを利用してきました。しかし 料金体系の変更 があったり、CodeBuildでデプロイの改善 をしたり、アプリ開発ではBitriseを利用 し始めたりするなどTravis CIを選択する理由が減ってきました。それと、デプロイ起点が複数あることで各CI/CDサービスの挙動理解や、メンテ時の面倒さを生み出してしまっているかもなと今この記事を書きながら思いました。

そこで今回はGitHub Actionsが良いのでは、という判断をしました。ecspressoを導入したリポジトリでは乗り換え先の検証としてGitHub Actionsを使ってCIを実行しており、どうせやるなら1つのCI/CDサービスでやろうと思ったからです。これまでTravis CIで実行してきた処理はすべて移行可能ですし、CodeBuildで行っている各家庭の回線に依存しない手動デプロイも引き続き行うことができます。

あとはGitHubの各種イベントに連動させたり、HTTPリクエストで起動したりするのが簡単なのも良いです。機能面以外だと請求がGitHubにまとめられるというのも良いのではないでしょうか。(コード管理をGitHubでしている前提)

ECS設定をコード化したい

アプリケーションをECS環境にデプロイするという用途だけならecs-deployで十分です。しかし、サービス定義やタスク定義を管理する仕組みがないためアプリケーション側の都合でタスク定義を更新したい場合などにECSコンソールから更新をかけるかインフラチームへ依頼するという手順が必要でした。コード管理していないためPull Requestを使ったレビューを行うことができないという欠点もあります。

これらの課題を解決するため、ecs-deployをやめてecspressoを使ってみることにしました。

ecspresso

ecspressoはECSのサービス定義・タスク定義も更新可能で、その設定内容はJSONで管理できます。なのでリポジトリ内で管理して変更する際にはPull Requestを通してレビューすることも可能です。ecspressoがやってくれることや、その思想についての解説は以下の記事が非常に分かりやすかったです。

ecspresso advent calendar 2020 day 21 - やること、やらないこと

デプロイまでの手順

ここから先は既存のECS環境に対してecspressoを利用し設定ファイルの作成、デプロイフローの構築をするための手順になります。

ecspressoのインストール

Homebrew経由でインストール可能です。

$ brew install kayac/tap/ecspresso

定義ファイルを作成

以下コマンドを実行して設定ファイルを作成します。

$ ecspresso init \
  --region my-ecs-region \
  --cluster my-ecs-cluster \
  --service my-ecs-service \
  --config config.yaml

実行すると3つファイルが作成されるのでそれらをコミットしておきます。複数環境へのデプロイが想定される場合は ecspresso/{env}/ のような形でディレクトリを作成して格納しておくと管理しやすいです。

  • config.yaml : ecspressoの設定ファイル
  • ecs-service-def.json : サービス定義
  • ecs-task-def.json : タスク定義

Workflowの作成

デプロイフローなどは各開発チームによって手順が異なるのであくまで参考程度にですが、私のいる開発チームでは以下のような設定になっています。

# .github/workflows/push-main.yml
# main ブランチにマージされたら development 環境へデプロイする
name: Push main branch

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      IMAGE_TAG: latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - 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: my-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
        env:
          DOCKER_BUILDKIT: 1
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: my-repository
        run: |
          docker build . -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
      - uses: kayac/ecspresso@v0
        with:
          version: v1.1.3
      - name: Deploy to Amazon ECS
        run: |
          ecspresso deploy --config .ecspresso/development/config.yaml
  1. 最新コードのチェックアウト
  2. ECRへのログイン
  3. イメージのビルド・プッシュ
  4. ecspressoを利用してECSタスク定義の更新・デプロイ

という順で実行されていきます。実行される処理のほとんどは既存のGitHub Actionを利用・組み合わせただけなので非常に簡単ですし、シンプルなのでメンテも楽になります。

より便利にするために

ここまででデプロイに必要な最低限を設定してきました。ここからは必要ならやってみてね、というものになります。

Slackへのデプロイ結果の通知

Slack通知用のGitHub Actionを入れます。ざっと調べただけでも3〜4個あるのですが今回は 8398a7/action-slack を使うことにしました。以下の設定をジョブの一番最後に入れます。

# .github/workflows/push-main.yml
- name: Notify slack of deployment result
  uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    # このへんはドキュメントを読みながらお好みで設定してください
    fields: repo,commit,ref,workflow,message,took
    # 誰がデプロイ作業をしているのかが分かるように GitHub ユーザー名を設定しています
    author_name: ${{ github.actor }}
    text: |
      Deployment has ${{
        (job.status == 'success' && 'succeeded') ||
        (job.status == 'failure' && 'failed') ||
        'cancelled'
      }} to my-app.
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  if: always()

タスク定義の変更がなければ処理をスキップする

タスク定義にdiffが出てなかったらタスク定義の更新処理をスキップしたくなりました。ecspressoのドキュメントを読んでいたら --skip-task-definition --force-new-deployment という2つのオプションを見つけたのでこれをdiffがなければ付けるようにします*1。差分チェックは dorny/paths-filter というGitHub Actionを使っています。

# .github/workflows/push-main.yml
- name: Check if file has changed
  uses: dorny/paths-filter@v2
  id: changes
  with:
    filters: .github/file-filters.yml
# 〜 中略 〜
- name: Deploy to Amazon ECS
  run: |
    ecspresso deploy --config .ecspresso/development/config.yaml ${{
      (steps.changes.outputs.task-def == 'false' && '--skip-task-definition --force-new-deployment') || ''
    }}
# .github/file-filters.yml
task-def:
  - .ecspresso/development/ecs-task-def.json

さいごに

ECS Scheduled Taskの管理をecscheduleでGitOps化しました などを推進してくれている @laugh_k に協力してもらいながら無事デプロイできるようになりました。また、ECSのようなインフラエンジニア以外も触れる機会の多い部分のコード化は非常に重要だなということを学びました。

参考リンク

PR

エンジニア募集しています!

hrmos.co

この記事はShodo (https://shodo.ink) で執筆されました。

*1:ecspresso advent calendar 2020 day 4 - deploy を読むとデプロイ時の挙動に詳しくなれます。