PR が出るたびに Kubernetes 上で dev 環境を立ち上げるための Kubernetes Operator

2021/12/22に公開

この記事は、Kubernetes Advent Calendar 2021 21 日目の記事です。

reviewapp-operator という、アプリケーション開発リポジトリへ PR が出るたびに Kubernetes 上で dev 環境を立ち上げるための Kubernetes Operator を自作した話になります。

この記事は、以前 CloudNative Days Tokyo 2021 プレイベント にて CloudNative Days を支える CI/CD ワークフローの変遷 というタイトルで話したものをより一般的な内容にしました。
上記発表は「CloudNative Days というイベントの裏方として CI/CD ワークフローを整備した」ことに焦点を当てた内容となっているため、もしよければ 発表資料 も合わせてご覧ください。

背景

GitHub 等のリモートリポジトリ上の Kubernetes マニフェストファイルを参照し Argo CD を用いてアプリケーションの Continuous Delivery する場合、以下のいずれかの方法で「リモートリポジトリ上の Kubernetes マニフェストファイルの継続的な更新」を行う必要があります。

  • アプリケーションを開発しているリポジトリ (以降 アプリケーションリポジトリ) で動作する CI にて、マニフェストを配置しているリポジトリ (以降 マニフェストリポジトリ) 上にある該当マニフェストのバージョン情報を書き換える
  • Argo CD Image Updater を用いて、コンテナレジストリの更新契機でマニフェストリポジトリ上にある該当マニフェストのバージョン情報を書き換える

この方法により「あるブランチの更新契機で、そのブランチに紐づく環境への CD」を実現できます。

しかしながら、上記から一歩進んで「アプリケーションリポジトリに PR が出るたびに新規 Namespace を作成しそこに新規環境を立ち上げる」というように監視対象ブランチが動的に変わる場合、 Argo CD Image Updater では実現できないです。

上記のように動的に新規環境を立ち上げる仕組みは、特に複数人で同じアプリケーションを開発する際において、共用利用のステージング環境へマージする前に自分の実装の動作を試す事ができるためとても便利です。 例えば有名な PaaS である Heroku では Review Apps という機能でこれを実現しています。

そのため Kubernetes を利用している場合においても同じことを実現したいというのがモチベーションとしてありました。

reviewapp-operator

reviewapp-operator は「アプリケーションリポジトリに PR が出るたびに dev 用の新規環境 (以下、Review Apps 環境) を立ち上げる」ことを実現するために、Argo CD と協調して動作します。
reviewapp-operator の責務は主に「アプリケーションリポジトリの PR の更新契機でマニフェストリポジトリのマニフェストを作成・削除する」ことであり、実際にマニフェストリポジトリからマニフェストを Kubernetes に適用するのは Argo CD の責務となります。

reviewapp-operator の workflow
reviewapp-operator の workflow

なお、以降この記事は reviewapp-operator v0.0.5 を前提に話します。

使い方

reviewapp-operator の使い方について説明します。

本当はドキュメントのリンクを貼り付けるだけで済めば良かったのですが、 reviewapp-operator のドキュメントは現状皆無なので、ここで各リソースの役割の説明や使い方について説明します。(後日、公式ドキュメントを作成予定です🙇)

reviewapp-operator のインストール

Controller をデプロイするための Namespace の作成と、CRD や Custom Controller の動作する Deployment 等が書かれたマニフェストの適用だけです。 (いつもの)

kubectl create namespace reviewapp-operator-system
kubectl apply -n reviewapp-operator-system -f https://github.com/cloudnativedaysjp/reviewapp-operator/releases/download/v0.0.5/install.yaml

reviewapp-operator の提供する Custom Resources

reviewapp-operator は以下の 4 つの Custom Resource を提供します。

リソース名 説明
ReviewAppManager ユーザが宣言するリソース①。監視対象のアプリケーションリポジトリ・マニフェストリポジトリと、Review Apps 環境を構築するための各種マニフェストのテンプレート (ApplicationTemplate, ManifestsTemplate) を指定する。
ReviewApp ReviewAppManager オブジェクトを親リソースとして、アプリケーションリポジトリの PR ごとに ReviewAppManager コントローラより作成される。reviewapp-operator のメインロジック (マニフェストのテンプレーティング、マニフェストリポジトリへマニフェストファイルの push、etc...) はすべて ReviewApp コントローラより実行される。
ApplicationTemplate ユーザが宣言するリソース②。Review Apps 環境を構築するための Argo CD Application のテンプレートを記述するためのリソース。
ManifestsTemplate ユーザが宣言するリソース③。Review Apps 環境を構築するための各種マニフェストファイルのテンプレートを記述するためのリソース。

reviewapp-operator の提供する Custom Resources
reviewapp-operator の提供する Custom Resources

また、現状 ReviewAppManager, ApplicationTemplate, ManifestsTemplate の値を記述する際に利用できるテンプレート変数には以下があります。
テンプレート変数がどのように利用されるかついては 実際の設定例 をご覧ください。

テンプレート変数名 説明
.Apprepo.Organization ReviewApp が監視するアプリケーションリポジトリの組織名
.Apprepo.Repository ReviewApp が監視するアプリケーションリポジトリのリポジトリ名
.Apprepo.Branch ReviewApp が監視するアプリケーションリポジトリのブランチ名
.Apprepo.PrNumber ReviewApp が監視するアプリケーションリポジトリの PR 番号
.Apprepo.LatestCommitSha ReviewApp が監視するアプリケーションリポジトリの PR における最新 commit の hash
.InfraRepo.Organization ReviewApp が管理するマニフェストリポジトリの組織名
.InfraRepo.Repository ReviewApp が管理するマニフェストリポジトリのリポジトリ名
.Variables.<任意の名前> ReviewAppManager.spec.variables 以下に定義された KeyValue の組。これにより ReviewAppManager リソースから任意の値を設定可能。

実際の設定例

以下は cloudnativedaysjp/dreamkast の Review Apps 環境を管理するための実際のマニフェストです。

上記ファイルには ReviewAppManager, ApplicationTemplate, ManifestsTemplate リソースの宣言がそれぞれ記述されています。以降この記述について説明していきます。

  • 8~14行目: ReviewAppManager.spec.appRepoTarget
    • 監視対象のアプリケーションリポジトリへの接続情報を記述するフィールド
    • この情報をもとに、PR が 1 つ増えるたびに ReviewAppManager オブジェクトから ReviewApp オブジェクトが 1 つ生成される
クリックで展開
  appRepoTarget:
    username: showks-containerdaysjp
    organization: cloudnativedaysjp
    repository: dreamkast
    gitSecretRef:
      name: git-creds
      key: token
  • 18~22行目: ReviewAppManager.spec.appRepoConfig
クリックで展開
  appRepoConfig:
    message: |
      Review app
      https://dreamkast-{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}.dev.cloudnativedays.jp
  • 23~30行目: ReviewAppManager.spec.infraRepoTarget
    • 管理対象のマニフェストリポジトリへの接続情報を記述するフィールド
クリックで展開
  infraRepoTarget:
    username: showks-containerdaysjp
    organization: cloudnativedaysjp
    repository: dreamkast-infra
    branch: main
    gitSecretRef:
      name: git-creds
      key: token
  • 31~41行目: ReviewAppManager.spec.infraRepoConfig
    • マニフェストリポジトリのどのパスにマニフェストを配置するかを記述するフィールド
    • テンプレート変数を記述可能
    • ReviewAppManager.spec.infraRepoConfig.manifests には複数個の ManifestsTemplate の指定が、ReviewAppManager.spec.infraRepoConfig.argocdApp には 1 つの ApplicationTemplate の指定がそれぞれ可能
    • ReviewAppManager.spec.infraRepoConfig.argocdApp.filepath の dirname を監視する Argo CD Application を事前に定義しておくことで以下の図のように Argo CD Application を多段で管理でき Review Apps 環境の構築を実現可能
クリックで展開
  infraRepoConfig:
    manifests:
      templates:
        - namespace: reviewapp-operator-system
          name: dreamkast
      dirpath: "manifests/app/dreamkast/overlays/development/{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}"
    argocdApp:
      template:
        namespace: reviewapp-operator-system
        name: dreamkast
      filepath: "manifests/app/argocd-apps/development/dreamkast-{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}.yaml"

Review Apps を実現するディレクトリ構成
Review Apps を実現するディレクトリ構成

  • 42~43行目: ReviewAppManager.spec.variables
    • テンプレート変数として {{.Variables.<key>}} で呼び出し可能な KeyValue を定義
クリックで展開
  variables:
      - AppRepositoryAlias=dk
  • 51~70行目: ApplicationTemplate.spec.stable
    • ReviewApp からマニフェストリポジトリに push する Argo CD Application 用マニフェストのテンプレートを定義するフィールド
    • テンプレート変数を記述可能
クリックで展開
  stable: &application |
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: "dreamkast-development-{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}"
      namespace: argocd
      finalizers:
        - resources-finalizer.argocd.argoproj.io # cascade deletion on this App deletion
    spec:
      destination:
        namespace: "dreamkast-{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}"
        server: https://kubernetes.default.svc
      project: app
      source:
        repoURL: https://github.com/cloudnativedaysjp/dreamkast-infra
        path: "manifests/app/dreamkast/overlays/development/{{.Variables.AppRepositoryAlias}}-{{.AppRepo.PrNumber}}"
        targetRevision: main
      syncPolicy:
        automated:
          prune: true

ApplicationTemplate が配置される箇所
ApplicationTemplate が配置される箇所

  • 71行目: ApplicationTemplate.spec.candidate
    • アプリケーションリポジトリの PR に candidate-template というラベルが付いている場合、 ApplicationTemplate.spec.stable の代わりにこちらのテンプレートが利用される
      • ApplicationTemplate 自体をバージョンアップしたい際の検証用として利用を想定
    • dreamkast では yaml の安価を利用して stable と同値としている
クリックで展開
  candidate: *application
  • 78~612行目: ManifestsTemplate.spec.stable
    • ReviewApp からマニフェストリポジトリに push する Review Apps 環境構築用マニフェストのテンプレートを定義するフィールド
    • テンプレート変数を記述可能
    • dreamkast の場合、kustomization.yaml も ManifestsTemplate に記述している
      • kustomization.yaml の images.newTag に {{.AppRepo.LatestCommitSha}} を記述することで、アプリケーションリポジトリ PR の HEAD のコンテナイメージを常に利用するようになっている
      • この ManifestsTemplate より作成されたディレクトリ を見ると、 kustomize build . で解決可能なディレクトリ構成になっていることが確認できる
クリックで展開

ManifestsTemplate が配置される箇所
ManifestsTemplate が配置される箇所

  • 613行目: ManifestsTemplate.spec.candidate
    • ApplicationTemplate.spec.candidate と同様の利用方法
クリックで展開
    candidate: *manifests

reviewapp-operator を3ヶ月ほど運用してみての感想など

上記で紹介した reviewapp-operator は 2021/10 あたりから Dreamkast というオンラインカンファレンスプラットフォームの開発環境を管理するのに利用されています。
ここからは、reviewapp-operator を実際に運用してみて分かったことやその感想などを書いていきます。

ManifestsTemplate リソースのマニフェストが非常に読みにくい

実際の設定例 にて実際の ManifestsTemplate の書きっぷりをお見せしましたが、string なフィールドにマニフェストを列挙する形になっており非常に読みにくいです。
この問題を解決するために、以下の方針を考えています。

  • 複数の yaml 形式のマニフェストファイルを引数に与えることで ManifestsTemplate リソースを出力する CLI ツールを実装
  • Argo CD の Config Management Plugins を利用して、Argo CD による apply の前に上記 CLI ツールで ManifestsTemplate リソースの記述されたマニフェストを生成

なおこの問題は #50 ManifestsTemplate, ApplicationTemplate が見にくいのをどうにかする という Issue で管理しています。

新機能の実装ネタが思ったよりもある

実は reviewapp-operator を実装するよりも前にも GitHub Actions で reviewapp-operator 相当のものを実現していたのですが (発表資料を参照)、「Review Apps 環境を構築・クリーンアップする他に様々な追加機能が欲しくなった際、その開発をちゃんとテスタブルな言語で行いたい」というのが reviewapp-operator を実装するモチベーションの 1 つとしてありました。

個人的にこの理由は「言うても機能追加はそうそうないだろう」と高をくくっていたのですが、いざ運用してみると様々な追加機能が欲しくなりました。

機能の案は GitHub Issue で管理しているため、自分の手が空いたときにやりたいもの順で実装していこうと思っています。

統合テストに時間がかかる

これは reviewapp-operator の話というよりは、Kubernetes Operator の「Custom Resource オブジェクトの状態を監視し、更新などを契機に該当 Custom Resource オブジェクトや外部リソース (例. GitHub PullRequest) を CRUD する」という、宣言的 API を実現するための実装の難しさの話です。

宣言的でない API (例. 単純な CRUD が可能な Web API サーバ) に対する統合テストの場合は外部リソースの収束を待たずともハンドラがエラー落ちしたらテスト失敗とみなせる場合が多いですが、Kubernetes Operator の場合は Reconciler メソッドが複数回呼び出されることで宣言された状態に収束するような実装をするため、ただ単純に Reconciler がエラー落ちするかどうかではなく、数秒以内に外部リソースが期待する状態に収束しているかというテストの書き方をする必要があります。

このようなテストにおいて Operator が期待通り動作すれば問題ないのですが、実装が間違っている場合はいくら待っても外部リソースが期待する状態に収束しないため、タイムアウトするまで待つことになり、個人の感想としてテストの実行時間が長いと感じることが良くあります。

このあたりについて、今の所「短すぎず長すぎないタイムアウトの時間を見つける」以上の案がないので、もし何か良い手があればコメント等で教えていただきたいです。

「reviewapp-operator 何もわからん」

使い方の項で述べたとおり現状 reviewapp-operator のドキュメントが皆無であるため、チームメンバーであっても reviewapp-operator 周りについて触れないという現状になってしまっていると感じています。

幸い reviewapp-operator で立ち上げる Review Apps 環境のマニフェスト自体を更新したい機会が現状そこまで多くないためその機会が訪れるたびに自分が作業者になれば良いとして回っていますが、この作業が属人化してしまってる現状はあまり健全でないので是正していきたいです。

reviewapp-operator は OSS として公開されているため、他の方にも利用してもらえるよう、ドキュメントや CRD のフィールドに対する description の拡充はできるだけ優先度を高くしてやっていこうと思っています。
// 本当はこの記事の公開までにドキュメントを整備したかった...orz

最後に

「アプリケーションリポジトリに PR が出るたびに新規 Namespace を作成しそこに新規環境を立ち上げる」ことを実現する Kubernetes Operator である reviewapp-operator について、開発したモチベーションやその使い方を紹介しました。
また、reviewapp-operator を複数人で開発されるアプリケーションの開発環境構築へ実際に導入してみて、分かったことや感想を書きました。

CloudNative Days の特定のユースケースというよりはより汎用的に利用できるものとして実装したつもりなので、この記事を読んでくれた方が実際にそう感じてもらえたらならば幸いです。

reviewapp-operator は現状 Dreamkast でのみ利用されていますが、上述したとおり動的に dev 環境を立ち上げるという用途でより汎用的に利用可能なので、引き続き機能開発を勧めつつどなたにでも reviewapp-operator を使ってもらえるようドキュメントを充実させていこうと思っています。

GitHubで編集を提案

Discussion