Unyablog.

のにれんのブログ

Renovate で Flux 配下の Helm Chart をアップデートする

この記事は KMC アドベントカレンダー 14 日目の記事です。

adventar.org

前回の記事は id:crashrt さんでした。自分も百舌谷さん好きです!

crashrt.hatenablog.com

背景

現在、Kubernetes クラスタに入れる Manifest を Flux (Fluxcd) を使って管理している。

blog.kmc.gr.jp

Flux には Helm を管理できる機能があって、HelmRelease という Flux の CRD で Chart のバージョンや values を指定したらインストールしてくれる。

fluxcd.io

クラスタを運用するにあたっては積極的に Chart のバージョンを上げていきたいものの、いちいち Upstream repository を確認しにいくのはめんどくさい。

調べてみると依存パッケージを自動更新してくれることで有名な Renovate が Helm の更新に対応しているということで、導入してみた*1

Renovate と Flux

Renovate は Flux にしっかり対応していて、HelmRelease ファイルがあれば自動的にバージョン更新の面倒を見てくれる。

docs.renovatebot.com

ただ、更新を確認するためには 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 がマージされる

といったロジックで古いバージョンがマージされてしまう可能性がある。その対策として、automergeScheduleschedule の直後に設定するようにして 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:実際になっている