RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

GitHub ActionsとArgo CDでプレビュー環境を構築した話

目次

1. はじめに

こんにちは!SRE課のモリモトです。

今回は、プレビュー環境基盤をKubernetes上に構築した話をご紹介します。

解決したかった課題

弊社のあるサービスでは、フロントエンド開発において以下のような課題がありました。

  • 現状では、ある程度修正がまとまったタイミングでないと検証環境に上げることが難しいため、ローカル環境以外で気軽に動作確認できる環境がない
  • デザイナーや企画/要件を詰めるチームに画面仕様を確認してもらうのに検証環境にデプロイする必要があるが、上記の理由からリードタイムが発生している
  • 検証環境にデプロイせずに確認してもらうためには画面のスクショを送ったりする必要があるが、それだと正確な仕様を把握してもらいにくい
  • PRレビュー時に、レビュアーがローカルでアプリケーションを起動して動作確認する必要があり、無駄な時間が発生している

そこで、PRにラベルを付けるだけで自動的にプレビュー環境がデプロイされる仕組みを構築することにしました。

具体的には以下のような体験を実現しています。

  1. 開発者がPRを作成し、PRに対してpreviewラベルを付与
  2. 数分後、PRにプレビューURLがコメントされる
  3. そのURLにアクセスすると、PRの内容が反映されたUIを確認できる
  4. PRをクローズすると、環境が自動削除される

2. アーキテクチャ

アーキテクチャ図

本構成では、PRを起点にGitHub ActionsとArgo CDがそれぞれ独立して動作します。 GitHub Actionsはイメージの生成と通知を、Argo CDはKubernetesリソースの生成と削除を担います。 両者を疎結合にすることで、責務を明確に分離しています。

ポイントは、Argo CD ApplicationSetの Pull Request Generator を活用している点です。 ApplicationSet は、同一構成の Argo CD Application を動的に複数生成するための仕組みです。 Pull Request Generator を使うことで、GitHub の PR 情報を元に Application を自動生成できます。

これにより、previewラベルが付いたPRを自動検知し、対応するKubernetesリソースを動的に生成・削除できます。

3. プレビュー環境の作成・更新・削除

すべての操作がPRに連動しており、開発者が意識的に環境管理を行う必要がないように設計しました。

作成・更新フロー

作成・更新のシーケンス図

作成・更新の流れは以下のとおりです。

  1. 開発者がPRを作成し、previewラベルを付与
  2. GitHub Actionsがワークフローを起動
    • まず、すでに作成済みのプレビュー環境数(previewラベルが付いたPR数)を確認します
    • 上限(1リポジトリにつき最大10環境)に達している場合は、その旨をPRにコメントし、previewラベルを自動で外します
  3. 並行して2つの処理が実行
    • GitHub Actions側: コンテナイメージをビルド・プッシュし、プレビューURLをPRにコメント
    • Argo CD側: previewラベルの付いたPRを検知(3分おきにポーリング)し、プレビュー環境のNamespaceとリソースを自動作成

同一PRに対して再コミットが行われた場合は、GitHub Actions側で再度イメージがビルド・プッシュされ、Argo CD側でデプロイされるイメージタグが自動更新されるため、追加の操作なしでプレビュー環境が最新化されます。

削除フロー

削除は2つのパターンがあります。

パターンA: PRクローズ or ラベル削除

Argo CDがPRのクローズまたはラベル削除を検知し、Namespaceごと環境を自動削除します。

パターンB: TTLによる定期クリーンアップ

プレビュー環境が溜まり続けることを防ぐために、TTLを設けて更新から10日間経過した環境を削除するようにしています。

削除のシーケンス図

GitHub Actionsのスケジュール実行(毎日0:00)で以下を実行します。

  1. previewラベルの付いたPR一覧を取得
  2. 最終更新日から10日以上経過したPRからpreviewラベルを削除
  3. 不要になった古いコンテナイメージも合わせて削除
  4. Argo CDがラベル削除を検知し、Namespaceごと環境を自動削除

プレビュー環境へのアクセス

プレビュー環境のURLは以下の形式で動的に発行され、PRにコメントされます。

https://pr<PR番号>-<サービス名>.preview-example.com

例): PR #123 の場合 → https://pr123-serviceA.preview-example.com

なおプレビューURLは社内ネットワークの範囲でのみアクセス可能とし、外部公開はしない前提で運用しています。

PRコメント例

PRコメント例

4. 実装のポイント

Pull Request Generator の実装

Argo CD ApplicationSetでPull Request Generatorを使用することで、preview ラベルが付いたPRを自動検知し、動的に環境を作成できます。

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: serviceA-preview-applications-applicationset
  namespace: preview-applications
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - pullRequest:
        github:
          owner: "my-org"
          repo: "serviceA-frontend-repo"
          appSecretName: app-secret  # GitHub Apps認証用のSecret名
          labels:
            - preview # previewラベルがついたPRのみを対象にフィルタリング
        requeueAfterSeconds: 180 # イメージビルドに3~4分かかることを考慮して3分に設定
  template:
    metadata:
      name: 'pr{{ .number }}-serviceA-preview-applications'
    spec:
      project: preview-project
      source:
        repoURL: https://github.com/my-org/manifest-repo.git
        path: preview-frontend
        targetRevision: develop
        helm:
          releaseName: 'preview-frontend'
          valueFiles:
            - "values/develop.yaml"
          valuesObject:
            namespace: "pr{{ .number }}-serviceA"
            image:
              repository: 'ghcr.io/my-org/serviceA-frontend'
              tag: 'preview-pr{{ .number }}-{{ .head_sha }}'
            ingress:
              app:
                hosts:
                  - host: 'pr{{ .number }}-serviceA.preview-example.com'
                    paths:
                      - path: /
                        pathType: Prefix
      destination:
        name: "dest-cluster-name"
        namespace: "pr{{ .number }}-serviceA"
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

preview-frontend/に汎用的に利用できるフロントエンドのhelm chartを実装し、それをプレビュー環境としてPRの数だけ複製するようなイメージになります。

その際に、イメージタグやプレビューURLなど、PRごとに異なるValuesを渡したくなりますが、valuesObjectから{{ .number }}{{ .head_sha }}などの値を渡すことでPRごとに別々の値をhelm chartに渡すようにしています。

PRごとに異なるValuesの命名規則

命名規則は以下のようにしました。

  • イメージタグ:preview-pr<PR番号>-<コミットSHA>
  • Namespace名:pr<PR番号>-<サービス名>
  • ドメイン:pr<PR番号>-<サービス名>.preview-example.com

特にイメージタグとドメインについては、GitHub Actions側とArgo CD側で整合性を保つためにルールを決めておく必要があります。 このルールに沿って、GitHub Actions側でのイメージビルドやPRコメント、Argo CD側でのアプリケーションデプロイを実施しています。

GitHub Actions

# イメージタグ生成側の例
- name: Set up parameters
   id: set-params
   run: |
     IMAGE_TAG=preview-pr${{ inputs.pr-number }}-${{ inputs.pull-request-head-sha }}

# PRコメント時のプレビューURL生成の例
- name: Comment preview URL on PR
   env:
     GH_TOKEN: ${{ github.token }}
   run: |
     PREVIEW_URL="https://pr${{ inputs.pr-number }}-serviceA.preview-example.com"

Argo CD

# イメージタグ使用側の例
valuesObject:
  image:
    repository: 'ghcr.io/my-org/serviceA-frontend'
    tag: 'preview-pr{{ .number }}-{{ .head_sha }}'

# プレビューURL指定の例
valuesObject:
  ingress:
    app:
    hosts:
      - host: 'pr{{ .number }}-serviceA.preview-example.com'

再コミット時の自動イメージ更新

同一PRに再コミットした場合、同一URLのままで自動でプレビュー環境が更新される仕組みになっています。

仕組み

  1. 再コミットすると、GitHub Actionsが新しいイメージをBuild&Push(タグ: preview-pr<PR番号>-<新コミットSHA>
  2. Argo CD ApplicationSetが{{ .head_sha }}を参照しているため、新しいコミットSHAでDeploymentのイメージタグが自動更新され、新しいPodがデプロイされる

これにより、開発者は再コミットするだけで最新の変更がプレビュー環境に反映されます。

環境数の上限制御

1リポジトリあたりの環境数を10に制限し、環境が作られすぎないようにしています。

制御はGitHub Actions側で行い、上限に達した場合はPRにコメントで通知し、preview ラベルを自動で外すようにしています。

# GitHub Actions Workflow内での制御イメージ
- name: Check preview environment count limit
  id: check-limit
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    # previewラベルが付いているオープンPRの数を取得
    PREVIEW_PR_COUNT=$(gh pr list \
      --repo ${{ github.repository }} \
      --state open \
      --label preview \
      --json number \
      --jq 'length')

    echo "プレビュー環境数:"
    echo "  - 現在: $PREVIEW_PR_COUNT"
    echo "  - 最大値: ${{ inputs.max-preview-environments }}"

    # 上限チェック
    if [ "$PREVIEW_PR_COUNT" -gt "${{ inputs.max-preview-environments }}" ]; then
      echo "[FAIL] 環境数上限に達しています。"
      echo "should-continue=false" >> $GITHUB_OUTPUT
    else
      echo "[PASS] 環境数は上限内です。"
      echo "should-continue=true" >> $GITHUB_OUTPUT
    fi

- name: Comment on PR if limit reached
  if: steps.check-limit.outputs.should-continue == 'false'
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    gh pr comment ${{ inputs.pr-number }} \
      --repo ${{ github.repository }} \
      --body "## ⚠️ **プレビュー環境数の上限に達しています!**

    現在、プレビュー環境の最大数(${{ inputs.max-preview-environments }}環境)に達しているため、新しいプレビュー環境を作成できません。
    他のPRのプレビュー環境が不要になった場合は、そのPRから \`preview\` ラベルを削除してください。"

- name: Remove preview label if limit reached
  if: steps.check-limit.outputs.should-continue == 'false'
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    gh pr edit ${{ inputs.pr-number }} \
      --repo ${{ github.repository }} \
      --remove-label preview

ResourceQuota によるリソース使用量の制御

プレビュー環境は多数同時に立ち上がる可能性があるため、ResourceQuota でリソース使用量を制限しています。

これにより、1つのプレビュー環境が過剰にリソースを消費することを防いでいます。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: preview-frontend-resource-quota
spec:
  hard:
    requests.cpu: 20m
    requests.memory: 64Mi
    limits.cpu: 100m
    limits.memory: 128Mi
    pods: 2
    # (...省略...)

5. おわりに

GitHub ActionsとArgo CDのApplicationSetを活用することで、ラベル付与だけで自動的にプレビュー環境が立ち上がる仕組みを構築できました。

本記事では詳しく説明できませんでしたが、GitHub ActionsのワークフローをReusable Workflow化するなど横展開が容易な形で設計しているため、社内にどんどん展開して複数チームで共通利用できる基盤となっています。

現在はNginxを前提にプレビュー環境の仕組みが構築されていますが、今後の展望としては他のミドルウェアにも対応を広げていきたいと考えています。

プレビュー環境の構築を検討されている方の参考になれば幸いです!

最後まで読んでいただき、ありがとうございました。

参考

tech-blog.monotaro.com

Copyright © RAKUS Co., Ltd. All rights reserved.