サイボウズの Kubernetes マニフェスト差分管理について

この記事は、CYBOZU SUMMER BLOG FES '24 (クラウド基盤本部 Stage) DAY 15の記事です。

クラウド基盤本部の深谷です。 サイボウズではクラウドサービスである cybozu.com の基盤を VM ベースの現行基盤から、kubernetes を活用したコンテナベースの新基盤 Neco に移行している最中です。 Neco については記事がいろいろありますのでご興味のある方は検索してみてください。

我々は Neco に対してサービスをデプロイするために Argo CD を活用しています。 Argo CD に関する記事もいくつか掲載しているので興味のある方はご参照ください。 現在では Neco の活用が進み様々なチームが Argo CD でサービスをデプロイするようになっています。

サイボウズでは staging 環境用や production 環境用などいくつかの Kubernetes クラスタを持っています。 これらの環境ではサービスに対してリソース量や FQDN など調整すべき点があります。 このような差分はマニフェストの差分として表されています。

この記事ではどのようにそのようなマニフェストの差分を管理しているかをご紹介します。

Argo CD についておさらい

Argo CD はいわゆる GitOps を実現するためのツールで、Git リポジトリに保存されたマニフェストをクラスタにデプロイしてくれるツールになります。 Argo CD でマニフェストをデプロイするためには CLI で参照するリポジトリを設定したり、Application カスタムリソースを使って設定を行う必要があります。

例えば以下のような Application リソースを書くことで、https://git.example.com/sample.git に保存されているマニフェストを service-a-ns という名前空間にデプロイすることができます。

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: service-a
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://git.example.com/sample.git
    path: base
  destination:
    server: https://kubernetes.default.svc
    namespace: service-a-ns
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

上記の例では Git リポジトリを参照していますが、このリポジトリには生のマニフェストのほかに後述するように Kustomize や Jsonnet を利用することもできます。 また Helm リポジトリを参照することもできます。

それではサイボウズではどのようにマニフェストの差分を管理しているか見ていきましょう。

Kustomize を使ったマニフェスト差分管理

Kustomizekubectl にもインテグレーションされているマニフェスト管理ツールです。 ここで気軽にマニフェスト「管理」ツールと述べていますが、公式には「Kustomize is a tool for customizing Kubernetes configurations」と述べられており、マニフェストのカスタマイズを行うのが本来の目的です。

それでは Kustomize を利用する場合に Git と Argo CD をどのように使ってマニフェストの差分管理を行うか見ていきましょう。

Kustomize のイントロダクションの例を示します。

~/someApp
├── base
│   ├── deployment.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── development
    │   ├── cpu_count.yaml
    │   ├── kustomization.yaml
    │   └── replica_count.yaml
    └── production
        ├── cpu_count.yaml
        ├── kustomization.yaml
        └── replica_count.yaml

ここでは base に共通となるマニフェストを配置し、overlays に環境ごとの差分を記述しています。 これを実環境に適用するときは、Argo CD の Application リソースの path フィールドを使って環境ごとのマニフェストを指定することができます。

たとえば development 環境向けの Application マニフェストは以下のようになります(一部省略しています)。

apiVersion: argoproj.io/v1alpha1
kind: Application
...
spec:
  source:
    repoURL: https://git.example.com/sample.git
    path: overlays/development

また Git のリビジョンを使うことで適用するマニフェストのバージョン管理を行うことができます。 例えば、ステージング環境では main ブランチを、プロダクション環境には release ブランチを Application リソースで指定します。 こうすると main に入ったマニフェストをステージング環境で動作確認し、それが終わった後に release ブランチにマージすることで適用のタイミングをコントロールすることができます。

apiVersion: argoproj.io/v1alpha1
kind: Application
...
spec:
  source:
    targetRevision: release

Kustomize を使う場合は、通常の Kubernetes マニフェストがそのまま使えるため学習コストが低いという利点があります。 一方で、差分はマニフェストの diff で表されるので意味を表すのが難しく、差分量が大きい場合は理解が難しくなる場合があります。

サイボウズでは一部のチームが Kustomize を利用して上記で紹介したような overlays を使って環境を、 リビジョンを使ってリリースタイミングのコントロールを行うという方法を採用しています。

Helm を使ったマニフェスト差分管理

Helm は Kubernetes マニフェストのパッケージマネージャです。 そのためマニフェストをパッケージングしリポジトリを通じて配布するという仕組みがビルトインで用意されています。 Helm のパッケージは Chart と呼ばれ、Kubernetes へのデプロイに対応した多くの OSS が Chart を提供しています。 そのため Kubernetes を利用する多くのユーザーが使うデファクトのパケージマネージャになっています。

Helm Chart はマニフェストをテキストとして扱って Go テンプレートで外部からパラメータを埋め込むことができます。 Kustomize では overlays で各環境に対して差分をパッチしていましたが、Helm ではこの機能を使って環境ごとの差分を扱うことができます。

Argo CD は Helm をネイティブに扱うことができます。 例えばサイボウズで開発している MySQL オペレーターの Moco は GitHub Pages で Chart を配布しているので以下のような Application リソースを使ってデプロイすることができます。 helm.values フィールドを使うことで Chart のパラメータを指定することができます。

apiVersion: argoproj.io/v1alpha1
kind: Application
...
spec:
  project: default
  source:
    repoURL: https://cybozu-go.github.io/moco
    chart: moco
    targetRevision: 0.13.1
    helm:
      version: v3
      values: |
        extraArgs:
          - --zap-time-encoding=iso8601
          - --pprof-addr=:9090
          - --apiserver-qps-throttle=200

Helm は Kustomize と違ってパラメータ名で意味を表すことができます。 また単なるテンプレートなので学習コストが低いことも利点です。 しかしテンプレートなのでカスタマイズ項目はあらかじめ挿入しておく必要があります。 またテキストとしてマニフェストを扱うため無効なマニフェストを生成してしまう可能性もあります。

サイボウズでは OSS として公開している第三者にも利用しやすい汎用的なオペレータについて Helm Chart を用意することが多いです。

Kustomize と Helm の組み合わせ

Helm は先に述べたようにあらかじめ埋め込まれたカスタマイズ箇所以外を書き換えることはできません。 しかし、Chart が提供しているカスタマイズ項目以外を変更したい場合があります。 この時に便利な仕組みとして Kustomize の Helm インテグレーションがあります。

Kustomize が利用する kustomization.yaml には helmCharts フィールドを使って Helm Chart を指定することができます。 また同時に resourcepatches やフィールドを使って、独自のリソースを追加したり、Helm Chart から生成されたマニフェストにパッチを当てることができます。

この機能を使うには Kustomize コマンドに --enable-helm オプションを指定する必要があります。 また、Argo CD では argocd-cm ConfigMap の kustomize.buildOptions--enable-helm をつける必要があります。

サイボウズではこの方法を使うことは多くはないですが、upstream が提供している Chart を加工する場合や独自リソースを追加する場合に使用しています。

Jsonnet を使ったマニフェスト差分管理

Jsonnet は Configuration Language と言われる一種のプログラミング言語です。 Kustomize は Kubernetes のマニフェストをカスタマイズすることに特化したツールですが、Jsonnet は JSON を生成することに特化しているもののチューリング完全なプログラミング言語です。

Jsonnet はプログラミング言語なので柔軟にマニフェストを記述することができます。 例えば大量の MySQL クラスターを作成するマニフェストを効率よく記述できます

一方決まった書き方がないため、抽象化の仕方はチームによって異なっています。

Argo CD は Jsonnet もネイティブに扱うことができます。 以下のような Application リソースを書くことで path フィールドで指定されたディレクトリで *.jsonnet が見つかればそのファイルを Jsonnet として扱い、生成されたマニフェストをデプロイします。

ここでは spec.source.directory.jsonnet.tlas フィールドを使って Jsonnet の TLA を使いパラメータを外部から導入しています。

apiVersion: argoproj.io/v1alpha1
kind: Application
...
spec:
  project: default
  source:
    repoURL: https://git.example.com/jsonnet.git
    path: jsonnet
    directory:
      jsonnet:
        tlas:
          - name: environment
            value: staging

Jsonnet は今まで紹介した方法の中で最も柔軟で、構造を抽象化して理解しやすくすることもできます。 一方で Jsonnet 自体の学習コストが必要であることや、一見しただけでは構造を理解することが難しい場合があります。

サイボウズではクローズドなコンポーネントを扱うチームが良く利用しています。

DRY と WET

これまで見てきたマニフェスト差分管理方法はすべて DRY(Don't Repeat Yourself)に基づいていて、 最終的なマニフェストではなく成果物を生成するためのアーティファクトをリポジトリに保存していました。

一方で、WET(Write Everything Twice)に基づきクラスタに適用されるマニフェストをリポジトリに保存する方法もあります。

生成物をリポジトリに保存するのは少し違和感があるかもしれませんが、リポジトリ内を見るだけでクラスタに適用されるマニフェストを確認できるので理解がしやすかったりレビューがしやすいという利点があります。

これらの方法は GKE Enterprise hybrid environment (part 2) - implementation details にも記載されているので参考になるかもしれません。

サイボウズでは DRY でマニフェストを差分管理するチームが多いですが、上に述べた利点を踏まえて WET で差分管理しているチームも存在します。 DRY を採用しているチームではレンダリングされたマニフェストの差分を PR のコメントに表示することで、レビューを簡単にする仕組みを導入しているチームもあります。 これを実現するための OSS のツールもあるようです。

リリースフローとリポジトリ構成

これまでは環境差分を主に取り上げてきました。 しかしリリースフローにおいて、新しいマニフェストやコンテイメージバージョンの更新を差分として考えることもできます。

Kustomize の例ではリリースを Git のブランチで扱うことを述べました。 一方でほかの例と同様にパッチやパラメータ、あるいは WET であれば新バージョンを別ディレクトリに保存することもできます。

ブランチで表す場合はマニフェストの変更を通常の Git を使った開発フローを使うことができるという利点があります。 一方で production 環境で不具合を見つけた場合にすぐに修正を適用することが難しいというデメリットがあります。 これは WET リポジトリを使っている場合特定の環境のみへの変更を行うのが容易です。

サイボウズではリリースフローについてはほとんどのチームがブランチを使っているようです。 しかし上記で述べたような問題があるためディレクトリを使う方法を検討しているチームも存在します。

我々はどうしたか

サイボウズでは統一した方法はとらずにチームごとに好きな方法でマニフェストを差分管理しています。 しかし扱うコンポーネントの性質によって差分管理手法に傾向が表れるようです。

例えば Neco チームのような OSS のツールを導入したり、作成するチームでは Kustomize や Helm を利用することが多いようです。 これはオペレータを作成するための Kubebuilder がデフォルトで Kustomize を利用することや、Helm は OSS のパッケージングに使われることが多いためです。

一方でクローズドなコンポーネントを扱うチームでは Jsonnet を使うことが多いようです。 これは設定の抽象化ができることや環境ごとの差異を柔軟に扱えることが理由だと思われます。 またアプリケーションの Neco 移行を推進するチームが Jsonnet を使っていることの影響も大きいかもしれません。

まとめ

この記事ではサイボウズのチームが Argo CD による GitOps でどのようにしてマニフェストを差分管理しているかを紹介しました。

アプリケーションチームは Jsonnet を使うことが多いですが、Jsonnet に慣れていないことや、型がないといった不満を聞くこともあります。 また DRY にすることでレビューにひと手間入れる必要があることや複数環境へのデプロイが難しいといった課題もあります。 ここでは紹介しませんでしたが、kpt を試しているチーム存在します。

今後も様々なツールやプラクティスが生まれていくでしょう。 この記事が少しでも皆様のマニフェスト差分管理の参考になれば幸いです。