
目次
1. はじめに
こんにちは!SRE課のモリモトです。
今回は、プレビュー環境基盤をKubernetes上に構築した話をご紹介します。
解決したかった課題
弊社のあるサービスでは、フロントエンド開発において以下のような課題がありました。
- 現状では、ある程度修正がまとまったタイミングでないと検証環境に上げることが難しいため、ローカル環境以外で気軽に動作確認できる環境がない
- デザイナーや企画/要件を詰めるチームに画面仕様を確認してもらうのに検証環境にデプロイする必要があるが、上記の理由からリードタイムが発生している
- 検証環境にデプロイせずに確認してもらうためには画面のスクショを送ったりする必要があるが、それだと正確な仕様を把握してもらいにくい
- PRレビュー時に、レビュアーがローカルでアプリケーションを起動して動作確認する必要があり、無駄な時間が発生している
そこで、PRにラベルを付けるだけで自動的にプレビュー環境がデプロイされる仕組みを構築することにしました。
具体的には以下のような体験を実現しています。
- 開発者がPRを作成し、PRに対して
previewラベルを付与 - 数分後、PRにプレビューURLがコメントされる
- そのURLにアクセスすると、PRの内容が反映されたUIを確認できる
- 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に連動しており、開発者が意識的に環境管理を行う必要がないように設計しました。
作成・更新フロー

作成・更新の流れは以下のとおりです。
- 開発者がPRを作成し、
previewラベルを付与 - GitHub Actionsがワークフローを起動
- まず、すでに作成済みのプレビュー環境数(
previewラベルが付いたPR数)を確認します - 上限(1リポジトリにつき最大10環境)に達している場合は、その旨をPRにコメントし、
previewラベルを自動で外します
- まず、すでに作成済みのプレビュー環境数(
- 並行して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)で以下を実行します。
previewラベルの付いたPR一覧を取得- 最終更新日から10日以上経過したPRから
previewラベルを削除 - 不要になった古いコンテナイメージも合わせて削除
- Argo CDがラベル削除を検知し、Namespaceごと環境を自動削除
プレビュー環境へのアクセス
プレビュー環境のURLは以下の形式で動的に発行され、PRにコメントされます。
https://pr<PR番号>-<サービス名>.preview-example.com
例): PR #123 の場合 → https://pr123-serviceA.preview-example.com
なおプレビューURLは社内ネットワークの範囲でのみアクセス可能とし、外部公開はしない前提で運用しています。
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のままで自動でプレビュー環境が更新される仕組みになっています。
仕組み
- 再コミットすると、GitHub Actionsが新しいイメージをBuild&Push(タグ:
preview-pr<PR番号>-<新コミットSHA>) - 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を前提にプレビュー環境の仕組みが構築されていますが、今後の展望としては他のミドルウェアにも対応を広げていきたいと考えています。
プレビュー環境の構築を検討されている方の参考になれば幸いです!
最後まで読んでいただき、ありがとうございました。