Kubernetes カスタムコントローラー楽々メンテナンス
はじめに
Kubebuilder を利用すると、Kubernetes のカスタムコントローラーを開発するためののひな形を生成することができます。
Kubebuilder のひな形には、カスタムコントローラーのコンテナイメージビルドや、デプロイするためのマニフェストを生成するための仕組みが用意されています。
しかし、下記のような日々のメンテナンスやリリース作業のための仕組みは含まれていません。
- 利用しているツールやライブラリの更新
- Kubernetes のバージョンアップ対応
- リリースノートの作成
- コンテナイメージのビルド、コンテナレジストリへの登録
- Helm Chart の作成とリリース
そこで本記事では、以下のツールを利用した自動化をおこない、日々のメンテナンスやリリース作業を楽にする方法について紹介したいと思います。
- Renovate による依存関係の更新
- GoReleaser によるコンテナイメージのリリース
- GitHub によるリリースノートの自動生成
- Helmify と Chart Releaser による Helm Chart のリリース
リリース&メンテナンス設定例
Renovate による依存関係の更新
Renovate は自動的に依存関係の更新をおこなってくれる非常に便利なツールです。
Renovate を導入するだけで、go.mod のパッケージバージョンやマニフェストに含まれるコンテナイメージのバージョンなどをチェックし、バージョンアップのための PR を自動作成してくれます。
さらに、オートマージやパッケージごとの更新ルールなどを細かく設定することも可能です。
ただし、Kubebuilder で生成したプロジェクトでは Makefile の中にツールのバージョンが記述されているため、Renovate のデフォルト設定ではツールの更新をおこなうことができません。
そこで、各種ツールのバージョン管理には aqua の利用がおすすめです。
aqua を利用すると Renovate による自動更新が可能になります。詳しくは以下の記事を参考にしてください。
Kubernetes 更新のための Renovate 設定
Kubernetes は、4ヶ月に1回マイナーバージョンが更新され、直近3つのマイナーバージョンがサポートされています[1]。
そのためカスタムコントローラーにおいても、直近3バージョンの Kubernetes をサポートするためにマトリックステストを実施します。
以下は GitHub Actions でのマトリックステストの設定例です。
name: CI
on:
pull_request:
jobs:
e2e:
name: End-to-end Test
runs-on: ubuntu-20.04
strategy:
matrix:
k8s-version:
- v1.23.12 # renovate: kindest/node
- v1.24.6 # renovate: kindest/node
- v1.25.3 # renovate: kindest/node
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version-file: go.mod
- run: make test KUBERNETES_VERSION=${{ matrix.k8s-version }}
このとき Renovate で Kubernetes のマイナーバージョンが更新されてしまっては困ります。
そこで以下のようなルールを設定することで、Kubernetes のパッチバージョンの更新のみを Renovate でおこなうことが可能になります。
{
"packageRules": [
{
// マイナーバージョンとパッチバージョンの更新をまとめないようにする
"description": "Separate minor and patch update for Kubernetes",
"matchPackageNames": [
"kindest/node"
],
"separateMinorPatch": true
},
{
// メジャーバージョンとマイナーバージョンの更新はおこなわない
"description": "Disable major and minor update for Kubernetes",
"enabled": false,
"matchPackageNames": [
"kindest/node"
],
"matchUpdateTypes": [
"major",
"minor"
]
}
],
"regexManagers": [
{
// 指定した正規表現にマッチする文字列を kindest/node というコンテナイメージのバージョンで更新する
"datasourceTemplate": "docker",
"depNameTemplate": "kindest/node",
"fileMatch": [
"^\\.github\\/workflows\\/.+\\.ya?ml$"
],
"matchStrings": [
"- (?<currentValue>.+?) # renovate: kindest\\/node"
]
}
]
}
続いて、Kubernetes のカスタムコントローラー実装に利用する k8s.io/client-go
や k8s.io/api
などのパッケージの更新について考えます。
これらのパッケージは Kubernetes v1.x.y
に対応して v0.x.y
というバージョンがつけられています。
ところが k8s.io/client-go
は、過去にメジャーバージョン1以上のものがリリースされていました。
そのため、Renovate は大きなメジャーバージョンのものを最新版だと勘違いして更新をおこなおうとします。
これを防ぐために、以下のような設定を追加しておくとよいでしょう。
{
{
// client-go の v12.0 などを取ってこないように、メジャーバージョンアップを無効化する
"description": "Disable major updates for k8s.io/client-go",
"enabled": false,
"matchDatasources": [
"go"
],
"matchPackageNames": [
"k8s.io/client-go"
],
"matchUpdateTypes": [
"major"
]
}
]
}
GoReleaser によるコンテナイメージのリリース
カスタムコントローラーをリリースする際には、コンテナイメージのビルドとコンテナレジストリへの登録が必要となります。
最近はマルチアーキテクチャに対応したコンテナイメージが要求されることも多いでしょう[2]。
GoReleaser を利用すると、Go言語で書かれたプログラムに対して、複数のアーキテクチャに対応したバイナリやコンテナイメージを作成しリリースすることが可能になります。
ここでは詳細は解説しませんが、以下のような設定ファイルを用意することで、マルチアーキテクチャに対応したコンテナイメージがビルドされます。
project_name: website-operator
dist: bin/
release:
# リリースページにカスタムコントローラーのバイナリをアップロードする必要はないのでスキップする
skip_upload: true
builds:
- id: website-operator
env:
- CGO_ENABLED=0
main: ./cmd/website-operator
binary: website-operator
goos:
- linux
goarch:
- amd64
- arm64
ldflags:
# バージョン番号を埋め込んでビルドする
- -X github.com/zoetrope/website-operator.Version={{.Version}}
dockers:
- image_templates:
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-amd64"
use: buildx
dockerfile: ./Dockerfile
ids:
- website-operator
extra_files:
- LICENSE
build_flag_templates:
- "--platform=linux/amd64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- image_templates:
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-arm64"
use: buildx
dockerfile: ./Dockerfile
ids:
- website-operator
extra_files:
- LICENSE
build_flag_templates:
- "--platform=linux/arm64"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
docker_manifests:
- name_template: "ghcr.io/zoetrope/website-operator:{{ .Version }}"
image_templates:
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-amd64"
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-arm64"
- name_template: "ghcr.io/zoetrope/website-operator:{{ .Major }}.{{ .Minor }}"
image_templates:
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-amd64"
- "ghcr.io/zoetrope/website-operator:{{ .Version }}-arm64"
そして GitHub Actions で以下のようなワークフローを用意すると、タグを打つだけでマルチアーキテクチャ対応のコンテナイメージがレジストリに登録され、GitHub 上でリリースがおこなわれます。
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- name: GHCR Login
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-go@v3
with:
go-version-file: go.mod
- name: Set previous release tag for GoReleaser
run: |
# Helm Chart の最新版ではなく、本体の最新版のタグを取得する
export TAG=$(curl -s "https://api.github.com/repos/zoetrope/website-operator/releases/latest" | jq -r .tag_name)
echo "GORELEASER_PREVIOUS_TAG=${TAG}" >> $GITHUB_ENV
- name: GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GitHub によるリリースノートの自動生成
リリースの際にはリリースノートを用意する必要がありますが、手書きするのは非常に面倒です。
GitHub では、前回のリリースと今回のリリースの差分から自動的にリリースノートを生成することができます[3]。
この機能を利用するために、.goreleaser.yml
に以下の設定を追加します。
changelog:
# GitHub のリリースノート生成機能を利用する
use: github-native
また、以下のファイルを用意することで、付与されたラベルに応じて PR をカテゴリー分けしたり、リリースノートに含めないようすることができます。
changelog:
exclude:
labels:
- ignore-for-release
- ci
- documentation
- refactoring
- test
categories:
- title: Features
labels:
- enhancement
- title: Bug Fixes
labels:
- bug
- title: Deprecated
labels:
- deprecate
- title: Removed
labels:
- remove
- title: Security
labels:
- security
- title: Dependencies
labels:
- dependencies
- title: Others
labels:
- "*"
なお、Renovate に以下の設定を追加しておけば、Renovate が作成した PR にラベルをつけることができます。
{
"labels": [
"dependencies"
]
}
上記の設定により、次のようなリリースノートが作成されます。
Helmify と Chart Releaser による Helm Chart のリリース
Kubebuilder 生成したプロジェクトには、Kustomize を利用してカスタムコントローラーのマニフェストを生成する仕組みが用意されています。
Kustomize は便利なツールではありますが、ユーザーがカスタムコントローラーをデプロイする際にこの仕組みを利用するのはやや不便ではあります。
そこで、Helm Chart を用意して、helm install
コマンドで簡単にデプロイできるような仕組みを用意しましょう。
Helmify
Helmify を利用すると、既存のマニフェストから Helm Chart を生成してくれます。
Kubebuilder のプロジェクトにも対応しており、以下のコマンドで Helm Chart の生成がおこなえます。
$ kustomize build config/default | helmify -crd-dir charts/website-operator
$ tree ./charts/
./charts/
└── website-operator
├── Chart.yaml
├── crds
│ └── website-crd.yaml
├── templates
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── leader-election-rbac.yaml
│ ├── manager-config.yaml
│ ├── manager-rbac.yaml
│ ├── ui-rbac.yaml
│ └── ui.yaml
└── values.yaml
生成された Helm Chart は必要に応じて書き換えるとよいでしょう。
Chart Releaser
Helm Chart をリリースするには、GitHub のリリースページにチャートファイルをアップロードし、gh-pages
でインデックスファイルを公開する必要があります。
Chart Releaser を利用すると、簡単に Helm Chart のリリースをおこなうことができます。
さて、先ほど GoReleaser を使ってカスタムコントローラーをリリースする仕組みを用意しました。
このカスタムコントローラーのリリースにあわせて、自動的に Helm Chart もリリースすると便利ではないでしょうか。
そこで、前段のリリースジョブが成功したら、自動的に Helm Chart をリリースするジョブを用意します。
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
# 省略
chart-release:
runs-on: ubuntu-20.04
needs: release
# 前段のreleaseジョブが成功した場合にのみ実行する
if: contains(needs.release.result, 'success')
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set chart version
run: |
helm repo add website-operator https://zoetrope.github.io/website-operator
helm repo update
# gitのタグからバージョン番号を抜き出す
tag_version=${GITHUB_REF##*/v}
# 現在リリースされているHelm Chartのバージョンを取得する
chart_version=$(helm search repo website-operator -o json | jq -r 'sort_by(.version) | .[-1].version')
chart_patch_version=${chart_version##*.}
# 今回リリースするHelm Chartのバージョンを計算する。
# 通常はパッチバージョンに+1する
# ただしメジャーやマイナーバージョンが変化している場合は、パッチバージョンは0にする。
new_patch_version=$(($chart_patch_version+1))
local_version=$(cat charts/website-operator/Chart.yaml | yq .version | sed "s/0-chart-patch-version-placeholder/$chart_patch_version/g")
[ "$local_version" != "$chart_version" ] && new_patch_version=0
# プレースホルダーのバージョンを今回のリリースバージョンに置き換える
sed --in-place "s/app-version-placeholder/$tag_version/g" charts/website-operator/Chart.yaml
sed --in-place "s/0-chart-patch-version-placeholder/$new_patch_version/g" charts/website-operator/Chart.yaml
sed --in-place "s/app-version-placeholder/$tag_version/g" charts/website-operator/values.yaml
- name: Create release notes
run: |
# Helm Chart のリリースノートには、本体のリリースへのリンクを追加する
tag_version=${GITHUB_REF##*/}
cat <<EOF > ./charts/website-operator/RELEASE.md
Helm chart for Website Operator [$tag_version](https://github.com/zoetrope/website-operator/releases/tag/$tag_version)
EOF
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@v3
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.5.0
with:
config: cr.yaml
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
なお、バージョン番号を自動的に書き換えられるように、Chart.yaml
の version
と appVersion
をプレースホルダーにしておきます。
apiVersion: v2
name: website-operator
description: Helm chart for Website Operator
home: https://github.com/zoetrope/website-operator
type: application
version: 0.2.0-chart-patch-version-placeholder
appVersion: app-version-placeholder
同様に values.yaml
の tag
もプレースホルダーにしておきます。
controller:
image:
repository: ghcr.io/zoetrope/website-operator
tag: app-version-placeholder
replicas: 1
ui:
image:
repository: ghcr.io/zoetrope/website-operator-ui
tag: app-version-placeholder
replicas: 1
Chart Releaser 用の設定ファイルは以下のようになります。
owner: zoetrope
git-repo: website-operator
release-name-template: "{{ .Name }}-chart-{{ .Version }}"
# Helm Chartのリリースがlatestにならないようにしておく
make-release-latest: false
# リリースノートのファイルはGitHub Actionsで生成する
release-notes-file: RELEASE.md
以上で、カスタムコントローラー本体のリリース後に自動的に Helm Chart がリリースされ、helm install
コマンドでカスタムコントローラーをデプロイできるようになりました。
まとめ
今回紹介した仕組みを導入することにより、依存関係は Renovate で自動更新できるようになり、Git のタグを打つだけでコンテナイメージと Helm Chart をリリースすることが可能となりました。
これらの仕組みは下記のリポジトリで利用しているので、興味のある方はぜひ参考にしてみてください。
-
Kubebuilder v3.7.0 で Multiple architecture support が追加されました。 ↩︎
-
GoReleaser にもリリースノート生成機能はありますが、GitHub のほうが使いやすくておすすめです。 ↩︎
Discussion