コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

EKSを活用したテスト環境構築自動化の仕組みを導入し開発者体験を向上させた取り組み

こんにちは、コドモンのプロダクト開発部で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が立ち上がるため、他のテストによるデータ依存が解消され、冪等性を担保することもできました。

以下の流れでテストが実行されます。

1. テストに必要なコンテナイメージをBuild & Pushする
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.comfuga.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チームと開発チームが一緒に試行錯誤しながら取り組んだことで、設計から運用まで素早く導入できました。まさにチームコドモンで成し遂げた成果と言えますね!今後は、この仕組みを横展開で拡大していきたいと思います。