こんにちは、コドモンのプロダクト開発部でSREをしている渡辺です。
先日私たちのチームでは、EKSを活用したテスト環境を自動で構築する仕組みをCIに導入しました!
本記事では、自動化の仕組みや工夫した点について紹介していきます。
テスト環境構築を自動構築する以前の課題
これまで、コドモンのテスト基盤はGitHub Actions(以下 GHA)のself-hosted runnnerとして1台のEC2を用意し、docker-composeで必要なコンテナを起動させテストを実行していました。
ローカル環境と同様の環境構築方法のため、ローカル環境のテストをそのまま実行できるメリットがある一方で、CIの順番待ちが発生するという大きな課題がありました。これは、1台のself-hosted runnnerでテストを実行するため、PR単位などCIが並列に実行された場合、後から実行されたCIで順番待ちが発生します。この結果、テストによるフィードバックが遅れていました。
また以下のような問題もあり、テスト実行が失敗したときに、これらの原因調査・対応に時間が取られる状況でした。
- 1台のrunnerに複数コンテナを立てるため、コンテナが増えるたびにメモリ不足解消のためrunnerのスペックをあげる対応
- runnerのストレージが逼迫したときに不要なものを削除する対応
- テストデータを削除しなくてはいけないなど、冪等性を担保するための対応
テスト環境構築自動化の仕組み
上記の課題を解決するため、EKSを活用したテスト環境自動構築の仕組みを導入しました。テスト終了後に環境を削除することで、必要な時に必要な分だけインフラを構築しています。
テスト単位で環境を構築することで、CIの順番待ちを防ぐことができました。テストデータに関しても、テスト単位でDBが立ち上がるため、他のテストによるデータ依存が解消され、冪等性を担保することもできました。
以下の流れでテストが実行されます。
2. テストに必要なKubernetesリソースをApplyする
3. テスト実行Pod(github-actions namespaceのPod)からヘルスチェックを行う
4. テストを実行する
5. テスト終了後、KubernetesリソースをDeleteする
各テスト環境ごとKubernetesリソースを管理する仕組み
今回、テスト環境用のKubernetesリソースはtest-env
というnamespaceで管理しています。テスト環境は並列で複数起動することが想定されるため、各環境ごとにnameが重複しないよう工夫する必要がありました。
そこで、Kubernetesマニフェストでnameのsuffixとして<COMMIT_HASH>
という文字列で設定し、GHAで実際のコミットハッシュに置換してApplyすることで各テスト環境ごとKubernetesリソースを作成しました。
Kubernetesリソース
apiVersion: v1 kind: Service metadata: name: hoge-<COMMIT_HASH> labels: app: hoge-<COMMIT_HASH> spec: selector: app: hoge-<COMMIT_HASH> ports: - protocol: TCP port: 80 targetPort: 80
GHAのJob
- name: Apply K8s Resource working-directory: <working-directory> run: | for file in *.yml; do sed -i"" -e "s|<COMMIT_HASH>|$COMMIT_HASH|g" $file done kubectl apply -k . env: COMMIT_HASH: ${{ github.sha }}
テスト環境が外部からのトラフィックを受け取る仕組み
ALBに*.example.com
のようなワイルドカードでドメインを設定しており、hoge.example.com
やfuga.example.com
のようなドメインでのリクエストはALBに送られます。
ALBのターゲットグループにingress-nginx-controllerを紐づけており、KubernetesのIngressリソースで設定したhostによって各テスト環境のPodへリクエストを転送する仕組みにしています。
ターゲットグループとingress-nginx-controller
を紐づけにはTargetGroupBindingリソースを利用しています。
apiVersion: elbv2.k8s.aws/v1beta1 kind: TargetGroupBinding metadata: name: ingress-nginx-tgb spec: serviceRef: name: ingress-nginx-controller port: 80 targetGroupARN: arn:aws:elasticloadbalancing:ap-northeast-1:<AWS ACCOUNT>:targetgroup/<Target Group Name>/xxx targetType: ip networking: ingress: - from: - securityGroup: groupID: sg-xxx # ALB security groups ports: - protocol: TCP
以下のKubernetesリソースをApplyすることで、hoge-<COMMIT_HASH>.example.com
で各Podにリクエストが届くようになります。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: hoge-<COMMIT_HASH> annotations: kubernetes.io/ingress.class: nginx spec: rules: - host: hoge-<COMMIT_HASH>.example.com http: paths: - path: / pathType: Prefix backend: service: name: hoge-<COMMIT_HASH> port: number: 80 --- apiVersion: v1 kind: Service metadata: name: hoge-<COMMIT_HASH> labels: app: hoge-<COMMIT_HASH> spec: selector: app: hoge-<COMMIT_HASH> ports: - protocol: TCP port: 80 targetPort: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: hoge-<COMMIT_HASH> labels: app: hoge-<COMMIT_HASH> spec: replicas: 1 selector: matchLabels: app: hoge-<COMMIT_HASH> template: metadata: labels: app: hoge-<COMMIT_HASH> spec: containers: - name: hoge image: <AWS ACCOUNT>.dkr.ecr.ap-northeast-1.amazonaws.com/test-env/hoge:<COMMIT_HASH> ports: - containerPort: 80 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1000m memory: 1024Mi
テスト環境構築からテスト環境削除までの流れ
今回、GHA self-hosted runnerをEKSクラスター上にPodとして起動させ、そこでテスト環境のApplyとテストを実行しています。
KubernetesリソースをApplyしてからPodがリクエストに応答できるまでタイムラグが生じるため、テストを実行するPodから疎通確認が完了するまで待機させます。
なお、Private SubnetでPodが起動するため、NAT Gatewayを経由してALBにリクエストを送ります。ALBにアタッチしたSecurity GroupにNAT GatewayのIPのみ許可することで、自分たちで管理しているVPC以外からのリクエストをブロックするようにしています。
疎通確認が取れたらテストを実行し、最後にテストが成功しようが失敗しようがテスト環境のKubernetesリソースを削除します。
######################################## # 必要なKubernetesリソースを作成しテストを実行 ######################################## run_test: needs: - <テストで利用するコンテナのBuild & PushするJob> runs-on: test-runner # Github Actions Runner on Kubernetes steps: - uses: actions/checkout@v4 - name: Apply K8s Resource working-directory: <Kubernetesリソースを管理するディレクトリ> run: | for file in *.yml; do sed -i"" -e "s|<COMMIT_HASH>|$COMMIT_HASH|g" $file done kubectl apply -k . env: COMMIT_HASH: ${{ github.sha }} - name: Wait for 200 OK response for K8s Resources run: | # テスト対象のPodから応答が来るまでヘルスチェックを行う処理 - name: Run Test run: | # テストを実行する ######################################## # Kubernetesリソースを削除 ######################################## cleanup_resources: needs: - run_test if: always() runs-on: test-runner # Github Actions Runner on Kubernetes steps: - uses: actions/checkout@v4 - name: Delete K8s Resource working-directory: <Kubernetesリソースを管理するディレクトリ> run: | for file in *.yml; do sed -i"" -e "s|<COMMIT_HASH>|$COMMIT_HASH|g" $file done kubectl delete -k . env: COMMIT_HASH: ${{ github.sha }}
まとめ
今回、EKSを活用してテスト環境構築自動化の仕組みを導入し、一部のプロダクトでCIの課題であったテスト環境の順番待ちを改善できました。
SREチームと開発チームが一緒に試行錯誤しながら取り組んだことで、設計から運用まで素早く導入できました。まさにチームコドモンで成し遂げた成果と言えますね!今後は、この仕組みを横展開で拡大していきたいと思います。