☎️

AI対話システムのE2E testをCI/CDに組み込んだ話

2024/11/05に公開

はじめに

株式会社IVRyのMoriyaです。
IVRyではAI対話システムのサービス提供を行っており、このサービスの安定性を担保するためのE2E testを用意しています。
しかしながら、これまでの開発環境ではE2E testを気軽に実行できず、本番環境にリリースする直前で、E2E testを実行していました。そのため、E2E testでエラーが検知できても、複数のPRが混ざっており、どのPR起因のエラーなのか分からないという課題がありました。
そこで、この問題を解決するために、CI/CD pipelineを整備しました。PRがmain branchにmergeされたタイミングで、変更内容がECSにdeployされて、E2E testが実行されます。実装において、いくつか難しい箇所があったので、備忘録も兼ねて簡単にblogにまとめました。

やったこと

以下のように動作するGitHub Actionsを用意しました。

1. PRがmergeされると、GitHub Actionsが実行される

PRがmain branchにmergeされるたびに実行されます。

on:
  workflow_dispatch:
  push:
    branches:
      - main

2. CI/CD用のECSクラスタにmain branchがdeployされる

ECSクラスタにdeployするGitHub Actionsがすでに同一repository内にあったので、そのGitHub Actionsを参照して、CI/CD環境向けにdeployされます。

jobs:
  deploy:
    uses: ./.github/workflows/deploy-to-env.yml
    with:
      env_name: "cicd"

3. Deploy完了まで待機する

Deployが完了してからE2Eを実行したいので、ECS deployの完了を待機します。deployment-idは、step 2のdeployが完了したら、取得できるようにしています。

  wait-deploy:
    name: wait deployment
    steps:
    - name: wait deployment
      run: aws deploy wait deployment-successful --deployment-id ${{ needs.deploy.outputs.deployment-id }}

4. E2E testを実行する

E2E testを動かすGitHub Actionsを実行します。

  run-e2e:
    needs: wait-deploy
    uses: ivry/e2e-repo/.github/workflows/run_e2e.yml@main
    secrets: inherit

5. 結果を通知する

E2E testの実行結果が成功したのか、失敗したのかをslackに通知します。

  notify:
    name: notify
    runs-on: ubuntu-24.04
    needs: run-e2e
    if: always()
    steps:
      - name: notify-success
        if: ${{ needs.run-e2e.outputs.result == 'success' }}
        uses: rtCamp/action-slack-notify@v2

      - name: notify-failure
        if: ${{ needs.run-e2e.outputs.result == 'failure' }}
        uses: rtCamp/action-slack-notify@v2

難しかったところ

AI対話システムのrepository(AI repo)と、E2E testがあるrepository(e2e repo)が分かれていました。これらを1つのrepositoryにまとめることが出来ない事情があり、AI repoのGitHub Actionsから、e2e repoのGitHub Actions、ソースコードを参照する必要がありました。その権限設定関連などで、難しかったところを記載しました。

GitHub Actionsから外部GitHub Actionsを参照する権限設定が必要

AI repo、e2e repoが同じorganizationに存在していても、e2e repoの設定を変更する必要がありました。
e2e repoのSettings → Actions → General → Accessから、Accessible from repositories in the XXX organizationを選ぶ必要があります。GitHub Actions 公式documentに、詳しい設定がまとまっています。

GitHub Actionsから外部repositoryをfetchするために指定が必要

以下のように、AI repoからe2e repoのGitHub Actionsを呼べるようにしたのに、実際に実行してみると、エラーが出ました。外部のGitHub Actionsを呼んでも、checkout時にrepositoryを指定しない場合は、呼び出し元のrepositoryが参照されるようでした。

jobs:
  run:
    runs-on: ubuntu-24.04
    outputs:
      result: ${{ steps.set_output.outputs.result }}
    steps:
      - uses: actions/checkout@v3

GitHub Actions 公式documentに、以下の記載がありました。

別のリポジトリのワークフローを再利用する場合、呼び出し先ワークフロー内のすべてのアクションは、呼び出し元ワークフローの一部であるかのように実行されます。 たとえば、呼び出し先ワークフローで actions/checkout を使用する場合、アクションでは、呼び出し先ワークフローではなく、呼び出し元ワークフローがホストされるリポジトリの内容をチェックアウトします。

そのため、以下のように書く必要がありました。

jobs:
  run:
    runs-on: ubuntu-24.04
    outputs:
      result: ${{ steps.set_output.outputs.result }}
    steps:
      - uses: actions/checkout@v3
        with:
          repository: ivry/e2e-repo
          token: ${{ secrets.AI_PAT || secrets.GITHUB_TOKEN }}          

GitHub Actionsから外部repositoryをfetchするためのTokenも必要

この箇所が一番難しい箇所でした。
外部repositoryをfetchするためには、Tokenを用意する必要がありました。今回はPersonal Access Tokenを利用しました。Zennの記事などには、Contents: Read-only, Metadata: Read-onlyの権限があれば外部のprivate repositoryをfetchできると書いてありましたが、それではfetchできませんでした。
GitHub Actionsの公式ドキュメントを読んでも原因がわからなかったのですが、Issueを見ていたら、解決策が記載されていました。

I made my token with permissions as closely resembling those which are given to the default GITHUB_TOKEN. While this works for now, I would still like to know the minimal permissions necessary to make use of the checkout action.

つまり、fetchするためには、通常のGITHUB_TOKENと同様の権限を付与する必要があるとのことでした。強い権限ではありますが、このissueに書いてある方針に従うことでfetchできるようになりました。

シークレット設定はrepository毎に必要

e2e repoにシークレットキーが設定してあっても、AI repoにシークレットキーを改めて設定する必要がありました。

成功・失敗時の分岐がややこしい

直感的には、以下のように書けば分岐が起きると思っていましたが、想定通りの挙動をしませんでした。failureを通知してくれませんでした。

  notify-success:
    name: notify-success
    runs-on: ubuntu-24.04
    needs: run-e2e
    if: ${{ needs.run-e2e.outputs.result == 'success' }}
    steps:
      - name: notify-success
        uses: rtCamp/action-slack-notify@v2
        
  notify-failure:
    name: notify-failure
    runs-on: ubuntu-24.04
    needs: run-e2e
    if: ${{ needs.run-e2e.outputs.result == 'failure' }}
    steps:
      - name: notify-failure
        uses: rtCamp/action-slack-notify@v2

GitHub Actions 公式documentによると、needsで設定したjobが成功しないと、ifに書いた判定を行ってくれないようでした。そのため、以下のように書き換える必要がありました。

  notify:
    name: notify
    runs-on: ubuntu-24.04
    needs: run-e2e
    if: always()
    steps:
      - name: notify-success
        if: ${{ needs.run-e2e.outputs.result == 'success' }}
        uses: rtCamp/action-slack-notify@v2

      - name: notify-failure
        if: ${{ needs.run-e2e.outputs.result == 'failure' }}
        uses: rtCamp/action-slack-notify@v2

まとめ

今回のCI/CD pipelineを通して、IVRyのAI開発が、より安定的・スピーディに進められるようになりました。今後も、安定的なリリースサイクルを実現できるような改善をしていこうと思います。

今後の抱負

「1つのPRを作るとECS taskが起動して、E2E testを実行して、ECSが停止する」のが理想だと思います。そうすれば、E2E testをpassしない限り、PRをmergeできなくなるので、より確実な開発ができると思います。
上記以外にもToken運用の見直し、通知方法の改善など、改善余地は色々ありそうなので、検討していきたいと思います。

最後に

IVRyではエンジニアを精力的に採用しています。興味がある方は是非よろしくお願いします!
https://ivry-jp.notion.site/IVRy-e1d47e4a79ba4f9d8a891fc938e02271

IVRyテックブログ

Discussion