ステージングサーバーのボトルネック
数年前、私のチームでは1つのステージングサーバーを共有していました。それはまるで、コンロが1つしかない混み合ったキッチンのようでした。あるエンジニアが新しいログインフローをテストしている一方で、別のエンジニアがチェックアウトのエラーをデバッグしようとする。お互いの作業が干渉し合い、午後4時には「誰がステージングを壊した?」という悲痛なSlackメッセージが飛び交うのが日常茶飯事でした。作業は混乱し、15人のチーム全体の開発速度が低下し、QA(品質保証)はまるで終わりのない「モグラ叩き」のような状態でした。
高スピードで開発を進めるチームには、より良いアプローチが必要です。それがエフェメラル環境(一時的な環境)、通称プレビュー環境です。仕組みはこうです。プルリクエスト(PR)が作成されるたびに、Kubernetes上にそのアプリの分離された短命なバージョンが立ち上がります。PRがマージまたはクローズされると、その環境は自動的に消滅します。レビュアーは、メインブランチにコードが反映される前に、本番に近いURLを使って隔離された環境で変更内容を確認できるようになります。
私たちは昨年、このセットアップを本番に導入し、手動デプロイのオーバーヘッドを週に約12時間削減することに成功しました。Argo CDとそのApplicationSetコントローラーを使用することで、手動管理の悪夢を、完全に自動化された「ただ動く」システムへと置き換えたのです。
エンジン:Argo CD ApplicationSet
ApplicationSetが登場する前は、ブランチごとにArgo CDの「Application」を手動で定義する必要がありました。これではスケールしません。ApplicationSetコントローラーは、テンプレートを使用してGitHubのプルリクエストなどの外部トリガーに基づき、複数のApplicationを自動生成することでこの問題を解決しました。
ここで重要な役割を果たすのがPull Request Generatorです。これはリポジトリのオープンなPRを監視し、PR番号やコミットSHAなどのメタデータを取得して、デプロイテンプレートに流し込みます。これがエフェメラルなワークフローの核心です。
なぜこのスタックなのか?
- 完全な隔離: すべてのPRは専用の名前空間(Namespace)で動作します。
- 自己修復: プレビュー環境でPodがクラッシュしても、Argo CDが自動的に復旧させます。
- 高い視認性: Argo CDのダッシュボードを見れば、どのPRがアクティブな環境を持っているかが一目でわかります。
実践:パイプラインの構築
前提条件はシンプルです。Argo CDがインストールされたKubernetesクラスター、Gitリポジトリ、そしてIngress Controllerを指すワイルドカードDNSレコード(例:*.preview.mycompany.com)が必要です。
ステップ1:GitHub認証
Argo CDにはリポジトリを監視する権限が必要です。repoスコープを持つパーソナルアクセストークン(PAT)を作成し、argocd名前空間にSecretとして保存します。
kubectl create secret generic github-token \
--from-literal=token=ここにあなたのGITHUBトークンを入力 \
-n argocd
ステップ2:ApplicationSetマニフェスト
ApplicationSetリソースは、新しいPRをどのように処理するかをArgo CDに指示します。generators(生成器)とtemplate(テンプレート)セクションがどのように連携するかに注目してください。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-previews
namespace: argocd
spec:
generators:
- pullRequest:
github:
owner: my-organization
repo: my-cool-app
tokenRef:
secretName: github-token
key: token
requeueAfterSeconds: 60
template:
metadata:
name: 'preview-pr-{{number}}'
spec:
project: default
source:
repoURL: 'https://github.com/my-organization/my-cool-app.git'
targetRevision: '{{head_sha}}'
path: charts/my-app
helm:
parameters:
- name: "ingress.host"
value: "pr-{{number}}.preview.mycompany.com"
- name: "image.tag"
value: "{{head_sha}}"
destination:
server: https://kubernetes.default.svc
namespace: 'preview-pr-{{number}}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
仕組みはこうです。Argo CDは pr-123.preview.mycompany.com のような一意のURLを生成します。{{number}} や {{head_sha}} といった変数は自動的に注入されます。新しいコミットをプッシュすると、Argo CDがSHAの変更を検知し、即座に環境を更新します。
ステップ3:DNSパズルを解く
DNSはしばしば導入の障壁になります。すべてのPRに対して手動でレコードを作成することは不可能です。解決策はワイルドカードDNSレコードです。*.preview.mycompany.com をNginxやTraefikといったIngress Controller의 外部IPに向けます。
ApplicationSetが新しいIngressを立ち上げると、コントローラーがホスト名を認識し、トラフィックを新しいPodに自動でルーティングします。手間いらずで信頼性の高い方法です。
リソース管理
リソースは無料ではありません。放置しておくと、すべてのPRに対して環境を実行するのはコストがかさみます。幸い、ApplicationSetのPRジェネレーターは、PRがマージされると自動的にApplicationを削除します。prune: true を有効にしているため、Kubernetesは関連するPodやServiceを即座に削除(クリーンアップ)します。
それでも、制限を設けるべきです。忙しいチームが一度に20個のPRを作成した場合、ノードのメモリ不足が発生する可能性があります。プレビュー環境の名前空間ごとに、RAMを約512Mi、CPUコアを0.5個程度に制限するResourceQuotasの使用をお勧めします。
apiVersion: v1
kind: ResourceQuota
metadata:
name: preview-quota
spec:
hard:
requests.cpu: "500m"
requests.memory: "512Mi"
limits.cpu: "1000m"
limits.memory: "1Gi"
新しいワークフロー
これが稼働すると、一日の流れは劇的に変わります:
- 開発者がブランチをプッシュし、PRを作成する。
- Argo CDが60秒以内にPRを検知する。
- 新しい
preview-pr-X名前空間が作成される。 - Helm経由でアプリがデプロイされる。
- ボットがPRにプレビューリンクをコメントする。
- ステークホルダーが実際の機能を確認し、フィードバックを残す。
- PRがマージされ、Argo CDがリソースを自動でクリーンアップする。
まとめ
エフェメラル環境への移行は、あらゆるDevOpsパイプラインにおいて最大の成果の一つです。ステージングのボトルネックを解消し、本番環境に到達する前にバグをキャッチできます。ワイルドカードDNSやApplicationSetの設定には最初少し手間がかかりますが、それによってチームが得られるスピードと安心感は、その努力に見合う十分な価値があります。ぜひ試してみてください。開発者もきっと感謝するはずです。

