この記事は KMC アドベントカレンダー 14 日目の記事です。
前回の記事は id:crashrt さんでした。自分も百舌谷さん好きです!
背景
現在、Kubernetes クラスタに入れる Manifest を Flux (Fluxcd) を使って管理している。
Flux には Helm を管理できる機能があって、HelmRelease という Flux の CRD で Chart のバージョンや values を指定したらインストールしてくれる。
クラスタを運用するにあたっては積極的に Chart のバージョンを上げていきたいものの、いちいち Upstream repository を確認しにいくのはめんどくさい。
調べてみると依存パッケージを自動更新してくれることで有名な Renovate が Helm の更新に対応しているということで、導入してみた*1。
Renovate と Flux
Renovate は Flux にしっかり対応していて、HelmRelease ファイルがあれば自動的にバージョン更新の面倒を見てくれる。
ただ、更新を確認するためには HelmRelease とそのオブジェクトが参照している HelmRepository (Helm chart に対応するレポジトリの情報) の紐付けを正しく行う必要があるそうで、
- HelmRelease / HelmRepository ともに namespace が明示的に設定されている必要がある
- HelmRelease / HelmRepository ともに名前が一意じゃないと多分動かない
という制約がある。
ここで、現在クラスタでは Manifest の構成に Kustomize をいろいろ活用しており、
- namepsace は上位の Kustomization で上書きするようにしている
- HelmReelase / HelmRepository の共通パラメータは Component にまとめており、各アプリの Kustomzation でそれを上書きして
namePrefix
でリソース名を変更している
ため、 各 Kustomization 内の HelmRelease / HelmRepository には namespace の記述はなく、また(`namePrefix` が適用される前なので)リソース名も全て同じになっている。
そのため、 Renovate の Flux 機能をそのまま使うことはできなかった。
自前でパースする
Renovate は "Managers" でパースを行って、 パッケージ情報を "Datasource" として抽出し、その Datasource に対して更新のチェックを行う仕組みになっている。
これまでに書いていた話は Flux Manager がパースを行って Helm Datasource を HelmRelease から抽出するものだった。もし Flux Manager がうまく使えなかったとしても、他の Manager を使って Helm Datasource が抽出できれば Renovate はそれに対して更新チェックを行ってくれる。
そこで Regex Manager を使って正規表現で自前でパースするようにした。
具体的には、以下のような HelmRelease / HelmRepository が書かれたファイルに対して
--- apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: HelmRepository metadata: name: repo spec: url: https://helm.cilium.io/ --- apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata: name: release spec: chart: spec: chart: cilium version: 1.12.3 values: ipam: mode: kubernetes
以下のような正規表現を設定して Helm Datasource を抽出する*2。
{ ... "regexManagers": [ { "fileMatch": ["^k8s/apps/.*/helm-overlay.yaml$"], "matchStringsStrategy": "combination", "matchStrings": [ " url: \"?(?<registryUrl>\\S+)\"?\\s*", " chart: \"?(?<depName>[a-z0-9-]+)\"?\\s*", " version: \"?(?<currentValue>\\S+)\"?\\s*" ], "datasourceTemplate": "helm" } ], ... }
設定して数分待つと、ちゃんと Renovate が Helm を認識してくれてアップデートの PR を作ってくれた。
その他
これで Chart の更新が楽になったが、他にも Renovate を運用する上で細々としたポイントがあった。
StabilityDays
新しいバージョンが出てから一週間くらいは様子見したいので、StabilityDays を設定した。
確かに最新のバージョンが Pending になって Push されないようになった一方、1週間経過した(がすでに新しいバージョンが Publish されている)古いバージョンが Stable なものとして Push されるようになった。自分はリリースされてから1週間たっても新しいバージョンがない場合に Stable とみなしたいのであり、古いバージョンが Push されるのは意図した挙動ではない*3。
軽減策として "internalChecksFilter": "none"
を設定して StabilityDays に関わらず常に新しいバージョンを Push させることで古いバージョンが Suggest されることはなくなった。
StabilityDays を有効にすると GitHub PR に StabilityCheck という Status Check が追加されるようになる。最新バージョンが StabilityDays 分経過するまでこの Status が通らないので、 (Automerge を特に設定しない場合は)定期的に PR を見てステータスが緑色であれば確認してマージ、というフローを行うようになった。
StabilityDays が経過する前に新しいバージョンが出た場合は、その新しいバージョンが Push されるのでまたしばらく StabilityCheck が通らないことになる。Grafana のような高頻度で新バージョンがリリースされる Chart の場合、一生マージできなくなるという課題もある*4。
Automerge
Bitnami の Chart などはアプリケーションのバージョンとは関係なく Base image が更新されるだけで新しい Chart がリリースされることがある。そのような些細な変更をいちいち確認するのはめんどくさいので、patch バージョンの変更であれば自動的にマージ(Automerge)するように設定した。
{ ... "packageRules": [ { "matchUpdateTypes": ["patch"], "matchDatasources": ["helm"], "matchCurrentVersion": "!/^0/", "automerge": true } ], ... }
今のところ Renovate のマージを使っているので時間がかかるが、基本的にはうまくマージされている。 遅い問題で困ったら GitHub の Automerge を検討してもいいかもしれない。
うるさい
無限にアップデートがきてダルいという Renovate あるあるな問題もある。 Schedule を設定したら静かになると思いきや、既存の PR には Schedule に関わらず更新を送り続けるので Push 通知がうるさかった。
これに関しては、 "updateNotScheduled": false
を設定することで既存ブランチの更新も Schedule 内におさめるようにすることができた。
ただ、そうすると
月曜に 1.2.1 が出る → 土曜日に更新される → (月曜に 1.2.2 が出るがブランチは更新されない) → 火曜に 8日間たって stabilityDays を通過する → 意図せず 1.2.1 がマージされる
といったロジックで古いバージョンがマージされてしまう可能性がある。その対策として、automergeSchedule
を schedule
の直後に設定するようにして Automerge 発動を PR の更新直後に限定することにした。
Recap
最終的な Config は↓のようになった。将来的には minor も自動マージの対象とするかも。
{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", ":disableDependencyDashboard" ], "timezone": "Asia/Tokyo", // バージョンが出てから8日間は Unstable とみなして Status check を pending にさせる "stabilityDays": 8, // Fluxcd は色々 flag をつけているので手動でアップデートしたい "flux": { "enabled": false }, "regexManagers": [ { "fileMatch": ["^k8s/apps/.*/helm-overlay.yaml$"], "matchStringsStrategy": "combination", "matchStrings": [ " url: \"?(?<registryUrl>\\S+)\"?\\s*", " chart: \"?(?<depName>[a-z0-9-]+)\"?\\s*", " version: \"?(?<currentValue>\\S+)\"?\\s*" ], "datasourceTemplate": "helm" } ], "packageRules": [ { "matchUpdateTypes": ["patch"], "matchDatasources": ["helm"], "matchCurrentVersion": "!/^0/", "automerge": true } ], // StabilityDays に関わらず、最新の package 情報を PR にする。 Automerge で古いバージョンが StabilityDays を満たしてマージされるのを防ぐため "internalChecksFilter": "none", "schedule": ["before 8am on saturday"], "automergeSchedule": ["after 7am and before 11am on saturday"], // Rebase やバージョン更新も Schedule 内に行わせる "updateNotScheduled": false, "prHourlyLimit": 0, }
Renovate と Helm の組み合わせ、なかなか便利なので使ってみてください!
*1:Dependabot は非対応だった https://github.com/dependabot/dependabot-core/issues/1744
*2:これができたのは HelmRepository と HelmRelease を同じファイルに記述するようにしていたため。別のファイルに書いて場合はどうすればいいのか知らない
*3:これはドキュメントに記載された挙動なので仕方ない
*4:実際になっている