Herokuの人気機能のひとつに Review Apps があります。プルリクエスト(PR)の作成と同時に使い捨ての環境とアプリのインスタンスを生成し、PRをマージすると一時環境を自動で破棄してくれる仕組みです。
このコンセプトと、先日の DoiT International のお客様との打ち合わせに刺激を受け、Google Cloud Platform(GCP)上でクラウドネイティブなアプローチと定番のオープンソースツールを組み合わせて、同じ挙動を再現してみました。
開発者体験(20分のデモ)
GitOpsの原則に従えば、開発チームはコードを書くことだけに集中できます。PRを作成すると、数秒から数分のうちにレビュー可能な最新コードのバージョンがオンラインで利用できるようになります。
その後のコミットでもレビューアプリは更新され続け、レビュアーやテスターが納得してPRを承認・マージするまで継続します。アプリが上位環境にプッシュされた時点でレビューアプリは破棄され、あとは機能ごとにこの流れを繰り返すだけです。
Heroku風レビューアプリとGitOps CI/CDパイプラインのデモ(20分)
使用するコンポーネント
- Github — ソースコード管理
- Kustomize — Kubernetesネイティブな設定管理
- Argo CD — Kubernetes向けの宣言的GitOps CD
- Kong Ingress Controller — APIゲートウェイ
- Google Kubernetes Engine(GKE)— マネージドKubernetes
- Google Cloud Build — CIサーバー
- Google Container Registry(GCR)— プライベートコンテナイメージレジストリ
- Google Secret Manager — 一元的なシークレット管理
- Google Cloud DNS — API対応のDNSサービス(任意)
アーキテクチャ
レビューアプリの中核となる機能は、開発者がソース管理上でプルリクエスト(PR)を作成すると、デプロイパイプラインがアプリのコピーをビルドし、レビュー用のユニークなURLを持つ独立した環境にホスティングするというものです。

開発者がPRを作成するとレビューアプリが生成される
さらに、PRをマージしたあとにアプリを本番環境へプロモートし、レビューアプリを削除する処理も含まれます。

レビュアー/管理者によるマージで、プロモートとレビューアプリ削除を同時に実行
TL;DR
図のように動作させるには、次の手順が必要です。
- Githubに App リポジトリと Config リポジトリを別々に作成
- Google APIを有効化し、GKEクラスタとArgo CDをブートストラップ
- Config リポジトリ経由でKong API Gateway(ingressコントローラー)をインストール
- kong-proxy の外部IP宛てにDNS Aレコードを作成
- Cloud BuildをGithubリポジトリに接続
- 秘密鍵を作成しGoogle Secret Managerに保存
- 公開鍵をGithubの Config リポジトリに登録
- Cloud Buildトリガーを3つ作成(push、PR、merge)
- Cloud BuildサービスアカウントにSecretsへのアクセス権を付与
- Cloud Build設定ファイルを App リポジトリに追加
- KubernetesマニフェストとKustomizeオーバーレイを Config リポジトリに追加
Githubソースリポジトリの作成
以下のリポジトリは実際に動作するサンプルです。構築手順は後続のセクションで順番に解説します。
- Demo App — DockerfileとCloud Build CIスクリプト
- Demo App Config — Argo CD用のセットアップスクリプトとKubernetesマニフェスト
APIの有効化、GKEとArgo CDのブートストラップ
次のスクリプトでGoogle CloudのサービスAPIを有効化し、Kubernetesクラスタを立ち上げます。
以前の記事では、IntuitのArgo開発チームでも採用されている app of appsパターン をご紹介しました。本ソリューションを設計するうえで、「Application」を動的に作成・削除できるこの仕組みは、レビューアプリを生成・破棄するというユースケースにぴたりとはまります。
Kong API Gateway(ingressコントローラー)のインストール
Kongのインストールは、Config リポジトリの /apps フォルダにkong.yamlを置くだけで完了します。Argo CDは指定したリポジトリやフォルダを自動で検知して監視します。Kongを選んだ理由は、作成する Ingress ごとにホストを設定できるため、レビューアプリのホストURLを動的に生成する用途に最適だからです。
対象フォルダにKongの設定ファイルを追加すれば、Kubernetesクラスタにインストールされます(他のアプリも同様の手順で追加できます)。
source.path(kong)に注目してください。これはArgo CDがインストール対象とするYAMLマニフェストを格納する、リポジトリ内のフォルダです。Kong公式の単一マニフェストを使い、Config リポジトリの /kong フォルダ内に 01-install.yaml というファイルを作成しました(https://bit.ly/k4k8s が最新のKong設定を指します)。
curl https://raw.githubusercontent.com/Kong/kubernetes-ingress-controller/master/deploy/single/all-in-one-dbless.yaml --output kong/01-install.yaml
Kong Proxy用のDNS Aレコードを作成
Kongをクラスタにインストールすると、外部IPを持つ外部TCPロードバランサー(Service)が作成されます。

Kubernetesクラスタ上のKong Proxyロードバランサーサービス
このIPにドメインを向ける必要があり、利用中のプロバイダーで手動設定できます。最初のデモでは、以下のようにAレコードといくつかのCNAMEエイリアスを設定しただけです。

手動で設定したDNSエントリ
もうひとつのやり方として、Google Cloud DNSでドメインを管理する方法もあります。Googleのネームサーバーに向ける必要はありますが、PR番号に応じてレビューアプリのURLを動的に生成できる(これが理想形です)というメリットがあります。GKEとKongのブートストラップ後、以下のコマンドで設定できます。
Cloud BuildをGithubに接続
普段は他記事への誘導は避けたいところですが、簡潔さを優先します。Cloud BuildのGithub接続とSecret ManagerへのSSHキー追加については、Googleの以下2つの記事に分かりやすくまとまっているので、こちらの手順をご参照ください。
- Cloud BuildをGithubに接続する
- プライベートリポジトリへのアクセス用にキーとシークレットを追加 — App のビルドパイプラインからDockerイメージのタグバージョン更新をコミットできるよう、Config リポジトリでは「commit」チェックボックスを有効にしてください。
3つのCloud Buildトリガーを作成
目的の挙動を再現するだけなら、技術的にはPRトリガーとマージトリガー、それぞれの設定ファイルさえあれば事足ります。とはいえ、通常は開発者向けのアプリビルドとデプロイも必要なので、developブランチへのpushトリガーを3つ目として加えました。

Cloud Buildトリガー
下記の設定では、ユーザー定義の環境変数(各ページ下部)を追加しています。これにより設定ファイル側からアプリ名を参照したり、ステージに応じた条件分岐を任意で組み込んだりできます。
分かりやすさを優先して3つのトリガーと3つの設定ファイル(次のセクション参照)に分けましたが、これらの機能を使えば1つにまとめることもできます。

developブランチへのpushごとにDockerイメージをビルド/デプロイ

他ブランチからmaster(prod)へのプルリクエスト

master(prod)ブランチへのPRマージ
Cloud Build設定ファイルをAppリポジトリに追加
ビルド設定ファイルの前半はごく基本的で、Dockerイメージをビルドし、Google Container Registry(GCR)にプッシュするだけです。後半のステップでは、Secret Managerのシークレットを使って設定リポジトリをクローンし、ファイルを書き換えてコミットをプッシュします。これによってArgo CDが反応し、最新の変更を同期してGKEクラスタ上のアプリを更新します。
このファイル内の sed -i ... の箇所が、GCRにプッシュされた最新ビルドのイメージタグへ書き換える処理です。
PRステージでは v$_PR_NUMBER をイメージタグに用いることで、PRレビュー中の以降のコミットでも同じタグを更新できるようにしています(将来的に変更する可能性はあります)。もうひとつの特徴は、/templates/demo-app-review.yaml を /apps フォルダへコピーすることで、Argo CDに対象パスの監視を開始させている点です。
ここで特に独特なのが「Removing review app…」の処理で、rm apps/demo-app-review.yaml によってファイルを削除しています。Application 設定でautoSyncとpruneを有効にしているため、ArgoCDがこの削除を検知してGKEクラスタからレビューアプリを取り除きます。
KubernetesマニフェストとKustomizeオーバーレイの追加
Kustomizeそのものや、それがKubernetesネイティブな設定管理をどう実現するかについては、別途ブログ記事を1本書けるほど(書くべき)テーマです。従来はHelmチャートやコードベースの「sonnet」系ツールでアプリをパッケージ化するのが定番ですが、私はシンプルで拡張性の高い Kustomize を好んで使っています。
Config リポジトリの構成は、各アプリ(本例では /demo-app/base)の配下にマニフェストと kustomization.yaml を置く形になっています。
- namespace.yaml — カスタムnamespace
- ingress.yaml — Kongが検知してルートを追加するIngress設定
- service.yaml — バックエンドPod用のネットワーク「ラッパー」
- deployment.yaml — 実際のアプリ(イメージを指定)
- kustomization.yaml — Kustomize設定
ここに各環境を表すオーバーレイを重ね、アプリ名のサフィックス、namespace、Ingressホスト名(動的なレビューアプリURLを実現するため)など、環境固有の値でYAMLファイルを置換またはパッチします。
/overlays/develop— develop環境設定/overlays/review— レビューアプリ環境設定/overlays/production— production環境設定
サンプルのGithub設定リポジトリで実物を確認できますが、以下はIngressに環境固有のURLを設定するためのオーバーレイ設定とパッチファイルの例です。
patchesStrategicMerge プロパティがファイルを参照している点に注目してください。Kustomizeはそのファイル(複数可)の内容をマージし、下記のIngressホスト名のようなカスタム値を上書きすることで、環境ごとに異なるURLを実現します。
PRごとに動的URLを生成(オプション)
最初のデモでは同じURLを使い回しましたが、Cloud DNSを利用する場合は、cloudbuild-pr.yamlに以下のようなステップを追加することでURLを動的にできます。執筆時点では未検証ですが、ネームサーバーを更新したうえで検証する予定です。
あわせて、設定スクリプト内にもう1つ sed -i ... コマンドを追加し、PRデプロイのたびに ingress-host.yaml パッチファイルを動的に置き換える必要があります。
私たちのチームで一緒に働きませんか
DoiT International に参加し、こうしたエキサイティングな発見をご自身の手で生み出してみませんか。詳しくは採用情報ページをご覧ください。
- https://careers.doit-intl.com(タイムゾーンが近いリモートポジション)
本記事には多くの内容を盛り込みましたが、全体の流れはそれほど難しくありません。ご紹介したサンプルリポジトリを活用すれば、エンジニアがインフラに気を取られることなく、本来の得意分野に集中できる環境を整えられるはずです。

GitOpsとHeroku風レビューアプリを活用したPR時のCI/CDビルドパイプラインの成功例