AI対話システムのE2E testをCI/CDに組み込んだ話
はじめに
株式会社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ではエンジニアを精力的に採用しています。興味がある方は是非よろしくお願いします!
Discussion