Kustomizeを利用してk8sの構成管理をシンプルにやってみる

目次

はじめに

キャディでバックエンドエンジニアとCI/CDやIaC、自動テストなどDevOps的な仕事を兼務している山下です。

k8sを実際にサービスの運用に使おうとすると確実にぶつかる壁があります。それは構成管理です。
具体的にいうと、基本は設定を共通化しつつ、環境に応じて一部だけを差し替えて管理しようとするとk8sだけでは運用が難しくなってきます。

そうした課題を解決するのに、最も使われているツールはHelmです。
(Helmは構成管理だけでなく、パッケージマネージャーとしての機能もあり、より広範な存在ですが)

しかし、キャディでは、Kustomizeというツールを使用し、構成管理を行なっています。
その理由やメリット、使い方を簡単に紹介出来ればと思います。

Kustomizeを選定した理由

HelmではなくKustomizeを選定した理由は大きく4つ存在します。
それは、学習コスト、移行コスト、導入コストの三つが低いことと、GitOpsとの親和性が高いことです。

学習コストが低い

HelmはGoのテンプレートを元にした、独自記法が多く存在します。
勿論、その分多くのことを実現でき、高いレベルの共通化を行うことが出来ます。

こうした機能はミドルウェアなどのように、構築がある程度複雑でかつ、幅広いサービスで組み込んで使ってもらうためには非常に有用なツールだと思っています。

弊社でも、RabbitMQやElasticSearchなどのミドルウェアを使っていますが、それらを展開するシーンではHelmを使用しています。
(特にHA構成になるように自力で設定しようとすると大変なコストが掛かりますが、Helmなら一発で展開できます。)

ただ、今回は、ミドルウェアのように幅広い人にツール的に使ってもらうものではなく、自社サービスであり、
サービスのことを理解した同じ会社のメンバーだけに限定して運用することから、シンプルで理解が容易なものを探していました。

その点、Kustomizeは独自の記法がほぼなく、基本的には、k8s本来の記法を使用して構成管理を行うことが出来ます。

移行コストが低い

上記と関連するのですが、k8sは非常に普及してきているとはいえ、まだまだ進化が非常に早いツールです。
そのため、その周辺に存在するツール群は更に変遷が早いと考えらます。折角、様々な記法に慣れても、あっという間にデファクトが変わることはあり得ます。
(例えば、構成管理繋がりですと、ChefやPuppetは一時期盛り上がりましたが、Ansibleが出てから下火になっていき、Dockerが来てから話題にならなくなっています)

その点、Kustomizeはk8sのmanifestと同じ記法を使用するため、Kustomizeが廃れたとしても、過去の資産が無駄にならず、移行が容易に出来ると考えています。
これにより、例え会社やサービスが大きくなったタイミングでも、柔軟にベストプラクティスを追求していけると考えています。

導入コストが低い

既に述べましたが、Kustomizeは独自記法がないため、覚えることが少なく、多くの人が簡単に利用できるため、組織的な導入が早いです。
これは、現状、インフラ専任者をおかずに、バックエンドエンジニアがインフラも一緒に見て、開発からリリースまでの責任を持つようにしている
キャディにとっては非常に重要な用件でした。

実際、既に私以外にも4人のエンジニアがk8sとKustomizeを利用して、インフラコードを変更してリリース作業を行なっていますが、
それも追加の学習コストが低いことが良い影響を及ぼしています。

次に、ローカルで試す際やパイプラインに組み込む時も、kubectlコマンド自体にkustomizeコマンドが同梱されているため、余計なインストールが不要です。

同梱とはどういうことかというと、kubctlをインストールしたタイミングで、既にKustomizeが使えるようになっているということで、
具体的には、 kubectl kustomizekubectl apply -kといったコマンドを利用することで、Kustomizeを利用したmanifest群をデプロイすることが出来ます。
(ただし、ここにハマりどころがあり、あとで解説します)

GitOpsとの親和性が高い

こちらの内容は、GitOps自体の説明だけで一記事分を軽く超え、関連する領域も広いため、
別記事で改めて紹介させてください

Kustomizeの使い方

Kustomizeはどうやって環境毎の差分を吸収しているのか

Kustomizeの環境差分を適用する方法は非常にシンプルで、
変更元のファイルと変更先のファイルの用意し、それらの差分がある部分だけを上書きを行うというものです。

具体的には、下記のファイルのAPI_DOMAINの部分を変更したいとします。

apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-app-config
data:
  API_DOMAIN: localhost
  API_PROTO: http
  API_PORT: "4000"

その際は、下記のように、どのファイルかが分かるようにapiVersion,kind,metadataの部分を同じ値を入れた上で、変更部分を書いていきます。
(ただし、マップであればいいのですが、配列の値を変えたい場合だけは少し特殊であとで説明いたします)

apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-app-config
data:
  API_DOMAIN: sample-domain

そうすると、下記のように、API_DOMAINだけが書き換わったmanifestに書き換わります。

apiVersion: v1
kind: ConfigMap
metadata:
  name: sample-app-config
data:
  API_DOMAIN: sample-domain
  API_PROTO: http
  API_PORT: "4000"

ただ、勿論、そのファイルを置いているだけで書き換わるということはありません。
その時に登場するのが、Kustomizeが唯一持つ独自記法であるkustomization.yamlという設定ファイルです。

Kustomization.yamlの記法

Sampleと概要

まずはサンプルはこちらになります。

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base

patchesStrategicMerge:
- ./services/sample_micro_service_A/be/config.yaml
- ./services/sample_micro_service_B/be/config.yaml

patchesJson6902:
- path: ./ingress/ingress_patch.yaml
  target:
    group: extensions
    kind: Ingress
    name: sample_ingress
    version: v1beta1

images:
- name: sample-app
  newName: gcr.io/bucket-name/image-name
  newTag: 91c8cee4b05a0ab1642d63198969fac9df5f62ae

resources

これは、どのファイルをデプロイや更新の対象にするかを決めるための設定です。

ファイルを指定するとそのファイルを管理対象とし、
ディレクトリーを指定すると、その配下のkustomization.yamlのresourcesを読みます。

patchesStrategicMerge

この項目が、最初のサンプルで説明した、Diffをとって更新をかけるために必要な設定項目です。

設定の仕方としてはシンプルで、変更先のファイルをresourcesに設定し、更新したい値が入ったファイルを
このpatchesStrategicMergeに設定します。

この時注意が必要なのは、この項目ではディレクトリーを指定出来ず、ファイルのみが設定可能なことです。

patchesJson6902

少し上記で書きましたが、書き換えたい値が配列の場合に利用するのが、この設定です。

ファイルではなく、JsonPathを使用して一部の値だけをピンポイントで変換します。

ちなみに、変わった名前だな、と思われると思いますが、
これはRPFの6902の仕様を元にした機能なためです。

サンプルとしては、まずの元のファイルが下記だとすると、

spec:
  rules:
    - host: HOST_NAME
      http:
        paths: ...

patchファイルは下記の様になります

- op: replace
  path: /spec/rules/0/host
  value: dev.caddi.com
- op: replace
  path: /spec/tls/0/hosts/0
  value: dev.caddi.com

pathで対象のプロパティまでのパスを指定し、valueに変更したい値をセットします。
patchJson6902の詳しい使い方

どのようなディレクトリー構成にすればいいのか?

kutomizeはtemplate-free way to customize application configurationと謳っているだけあり、
何かこうしなければいけないという構成がある訳ではありません。

ただ、大まかにbaseoverlaysというディレクトリーに分け、
共通項目はbaseに、上書きを行いたいものはoverlaysにいれ、overlays以下に環境毎のディレクトリーと
kustomization.yamlが存在しているというのが、一般的な構成のようです。

とはいっても、私自身、そこから先を決めるのが困るんだよな、と思って結構頭を悩ましたので、
改善中の状態ではありますが、現状のキャディのインフラの構成を公開致します。参考にして頂ければ幸いです。

.
├── README.md
├── base
│   ├── ingress
│   │   ├── configmap.yaml
│   │   ├── ingress.yaml
│   │   └── kustomization.yaml
│   ├── kustomization.yaml
│   ├── middleware
│   │   ├── README.md
│   │   └── charts_config.json
│   ├── secrets
│   │   ├── kustomization.yaml
│   │   └── tls-secret.yaml
│   └── services
│       ├── kustomization.yaml
│       ├── sample_micro_service_A
│       │   ├── be
│       │   │   ├── config.yaml
│       │   │   ├── deployment.yaml
│       │   │   ├── service.yaml
│       │   │   └── subscriber-config.yaml
│       │   ├── bff
│       │   │   ├── config.yaml
│       │   │   ├── deployment.yaml
│       │   │   └── service.yaml
│       │   ├── kustomization.yaml
│       │   └── ui
│       │       ├── config.yaml
│       │       ├── deployment.yaml
│       │       └── service.yaml
│       └── service_micro_service_B
│           ├── be
│           │   ├── deployment.yaml
│           │   └── service.yaml
│           ├── bff
│           │   ├── config.yaml
│           │   ├── deployment.yaml
│           │   └── service.yaml
│           ├── kustomization.yaml
│           └── ui
│               ├── config.yaml
│               ├── deployment.yaml
│               └── service.yaml
└── overlays
    ├── dev
    │   ├── ingress
    │   │   └── ingress_patch.yaml
    │   ├── kustomization.yaml
    │   ├── middleware
    │   │   ├── elasticsearch
    │   │   │   └── values.yaml
    │   │   └── rabbitmq-ha
    │   │       └── values.yaml
    │   ├── secrets
    │   │   ├── kustomization.yaml
    │   │   ├── sample_micro_service_A
    │   │   │   ├── be
    │   │   │   │   ├── cloudsql-secret.yaml
    │   │   │   │   ├── publisher-secret.yaml
    │   │   │   │   └── subscriber-secret.yaml
    │   │   │   └── kustomization.yaml
    │   │   └── sample_micro_service_B
    │   │       ├── be
    │   │       │   ├── be-secret.yaml
    │   │       │   └── cloudsql-secret.yaml
    │   │       └── kustomization.yaml
    │   └── services
    │       ├── sample_micro_service_A
    │       │   ├── be
    │       │   │   └── deployment_patch.yaml
    │       │   ├── bff
    │       │   │   └── config.yaml
    │       │   └── ui
    │       │       └── config.yaml
    │       └── sample_micro_service_B
    │           ├── be
    │           │   └── deployment_patch.yaml
    │           ├── bff
    │           │   └── config.yaml
    │           └── ui
    │               └── config.yaml
    ├── local_sample
    ├── prod
    ├── stg
    └── test

コマンド群

まず、導入理由で、ツールをインストールがしなくてよく導入コストが低いと書いておきながら、
恐縮なのですが、Kustomizeで実際にインフラコードを書いたり、より踏み込んだ機能を使いたい場合は、
kustomizeコマンドのインストールが必要になってきます。macであればbrewでインストール可能です。

ただ、Kustomizeには様々なコマンドがありますが、よく利用するコマンドは下記の二つです

Build

kutomize buildは、kustomization.yamlのresourcesで指定したファイルをbundleし、
overlaysで設定したファイルで値を更新したものを出力します。

これによって、manifestやkustomization.yamlを書いて、動作確認をする際に、
ちゃんと値が意図通り更新しているかを確かめたりする際に非常に有用ですし、
ファイルが一ファイルにまとまるため、取り回しが用意になります。
(デプロイする時も、buildの結果をファイルとして書き出し、
 それをkubectl apply -f で指定すると、build結果と必ず同じものを参照するようになるので、変なところでハマることが少なくなります。)

実行するときは、kustomization.yamlがあるディレクトリーを指定してください。

Edit

記事の終盤ですが、実は、このEdit機能こそがKustomizeで最も重要な機能だと私は考えています。
そして、これがGitOpsの親和性が高いというメリットに繋がります。

では、このEdit機能、めちゃくちゃ高機能なものかというと実は異なり、
kustomization.yamlのimagesで指定したimageのtagやdigestというhash値を書き換えるというシンプルな機能になります。

具体的な使い方は下記となります。

変更対象

元の情報が下記だとすると(kustomization.yaml)

images:
- name: base-sample-image-name
  newName: new-sample-image-name
  digest: hash value
  newTag: tag name

tagの変更の場合

対象のkustomization.yamlの場所までcdした上で
下記コマンドを実行することでimageのtagを変更出来ます、

kustomize edit set image image_name:tag_name

具体的には、下記の様なコマンドになります。

kustomize edit set image base-sample-image-name:v1.2.3

digestの変更

tagと同様に対象のkustomization.yamlの場所までcdした上で
下記コマンドを実行することでimageのdigestを変更出来ます。

kustomize edit set image base_image_name=new_image_name@digest

具体的には、下記の様なコマンドになります。

kustomize edit set image base-sample-image-name=gcr.io/caddi/new-sample-image-name@sha256:bcfda0cb68ebe4e2a6d8157066623147bbe00aa80b664426443d9c60409551eb

何が嬉しいのか

この機能を利用することで、対象のアプリケーションのbuild後に合わせて
インフラコードの該当のイメージ情報をeditするだけでアプリケーションのデプロイを完了することができ、簡単にGitOpsを実現することができます。
(GitOpsの詳細な内容は別記事でご紹介致します。)

Tips

Helmとの共存

基本的に、ミドルウェアなど、決まった構成のものはHelmを利用するのが適切ですが、
Kustomizeだけで完結させたい場合は、Helmのtemplate機能を使うとKustomizeに寄せることも出来ます。

Helmには作られているChart(Helmの設定ファイル)からk8sがそのまま扱えるファイルであるmanifestを生成するtemplate機能があります。
このtemplate機能を利用して、manifestを生成し、一部必要なものだけをkustomizationで上書きするようにすれば、Helmと併用して使用することが出来ます。

helm template stable/nginx-ingress > nginx-ingress-manifest.yaml

Pluginの作成

Kustomizeの基本であるシンプルさから離れてしまいますが、より高いレベルの共通化や、自動化を行う時にはPluginを利用するのも一つ手です。
Shellもしくは、Golangを用いて、追加の処理を実装することが出来ます。

まとめ

以上、駆け足でしたが、KustomizeとHelmの違いとKustomizeを選んだ理由、簡単な使い方をご紹介させて頂きました。
現在、ツール選定を行なっている方の参考になれば、非常に嬉しい限りです。

参考資料

Hiroaki Yamashita
  • Hiroaki Yamashita
  • 前職で、DevOpsエンジニアとしてCI/CDやIaC、自動テストなどの導入を行った後、自動テストツールの企画・開発・導入などを経験
    現在は、GitOpsに則したCI/CDの構築やk8s,Terraformを使ったIaCの実現などを行ったのち、Rustによるバックエンド開発に従事