feat(updates): per-service update-policy render contract + Renovate config (R-UPD-01/R-UPD-03)
Map a ServiceDefinition `update` block to ImageRepository/ImagePolicy + the $imagepolicy setter marker: stable-only (semver range + bare-X.Y.Z filterTags, pre-release excluded, R-UPD-01), pin -> fixed image with no marker / suspended policy (R-UPD-03). Worked pihole example under per-service/ (reference only, not deployed; WS-GITOPS emits the equivalent into apps/<svc>/). Add renovate.json for Helm chart / dependency / operator-manifest bumps: auto-merge minor/patch/digest on green CI, major needs review, image managers disabled so Renovate never fights Flux over a tag. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
37f4fa6767
commit
f538f8ff38
3 files changed
+196
No files matched your search
@@ -0,0 +1,48 @@
|
|||||||
|
# Per-service image-automation — the render contract (`update` block → Flux CRs)
|
||||||
|
|
||||||
|
**Owner:** WS-UPDATE (this contract) + WS-GITOPS (emits these next to each app's Deployment/HelmRelease
|
||||||
|
under `apps/`). **Serves:** R-UPD-01 (stable-only), R-UPD-02 (no human/AI authoring), R-UPD-03
|
||||||
|
(pin / auto-rollback). **Source of truth:** [`renderer-contract.md` §`update`](../../../schemas/renderer-contract.md),
|
||||||
|
[ADR-0009](../../../../home-network-k3s/decisions/adr-0009-automated-image-updates.md),
|
||||||
|
[docs/11](../../../../home-network-k3s/docs/11-automated-updates.md).
|
||||||
|
|
||||||
|
> These files are **reference examples**, deliberately **not** wired into any `kustomization.yaml`, so
|
||||||
|
> they never deploy. WS-GITOPS renders the equivalent per real container service into `apps/<svc>/`.
|
||||||
|
> The cluster-wide write-back engine that consumes the setter markers is `../image-update-automation.yaml`.
|
||||||
|
|
||||||
|
## What a container `kind` ServiceDefinition `update` block renders
|
||||||
|
|
||||||
|
For every `kind: container` service whose `update.pin` is **unset**, render THREE things:
|
||||||
|
|
||||||
|
| `update` field | Renders to | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| (implicit — `source.image`) | **`ImageRepository`** | scans the registry repo on `interval`. |
|
||||||
|
| `channel: stable` + `range` | **`ImagePolicy`** `.spec.policy.semver.range` | **stable-only**: the range plus a filter that **excludes pre-release** tags (`-alpha/-rc/…`). |
|
||||||
|
| `cadence` | `ImageRepository.spec.interval` (scan schedule) | the "on a schedule" of R-UPD-02. Cron `cadence` → nearest Flux duration (Flux scans on a duration, not cron). |
|
||||||
|
| (image ref in the Deployment) | a **setter marker** comment on the `image:` field | `# {"$imagepolicy": "<ns>:<name>"}` — where image-automation writes the advanced tag. |
|
||||||
|
| `autoRollbackOnFailedHealth: true` | delivery-side **health gate + rollback** | see below — HelmRelease remediation, or Deployment progressDeadline + revert. |
|
||||||
|
| `pin: <version>` | **freeze** | render the image `image:` pinned to `<version>` with **NO** setter marker, and **suspend** (or omit) the ImagePolicy. Automation cannot advance a pinned service (R-UPD-03). |
|
||||||
|
|
||||||
|
`kind: vm` services do **not** render these — they update in-guest (dnf-automatic/unattended-upgrades on
|
||||||
|
`cadence`, cloud-init), per docs/11 §3. See `catalog/vm-demo.yaml`.
|
||||||
|
|
||||||
|
## Stable-only (R-UPD-01) — the two guardrails
|
||||||
|
1. **semver range** (`.spec.policy.semver.range`) — only versions matching the operator's `range`.
|
||||||
|
2. **pre-release exclusion** — either a `filterTags` pattern that only admits `X.Y.Z` (no `-suffix`),
|
||||||
|
or rely on semver's rule that pre-releases (`1.2.3-rc1`) sort **below** their release and are only
|
||||||
|
selected if the range explicitly asks for them. We do BOTH (belt-and-braces) so an upstream `latest`
|
||||||
|
or `-rc` tag can never be picked.
|
||||||
|
|
||||||
|
## Auto-rollback (R-UPD-03) — where the health gate lives
|
||||||
|
The setter write only changes a tag in git; the **rollback** is enforced at delivery:
|
||||||
|
- **HelmRelease-delivered services (preferred):** Flux `HelmRelease.spec.upgrade.remediation.retries`
|
||||||
|
+ `.spec.rollback` — a failed, health-gated upgrade auto-rolls-back to the last-good Helm revision.
|
||||||
|
The image tag lives in the HelmRelease `values` with the setter marker, so a bad advance is caught
|
||||||
|
and reverted by Flux with no human step. This is the mechanism the conformance test exercises.
|
||||||
|
- **Plain-Deployment services:** the Deployment `progressDeadlineSeconds` + readiness probe make a bad
|
||||||
|
image a stalled (never-Available) rollout; the old ReplicaSet keeps serving, and the
|
||||||
|
`UpdateHealthGateFailed` alert fires (see `platform/monitoring/alerts.yaml`). Recovery is the next
|
||||||
|
good advance or the operator `pin` escape hatch.
|
||||||
|
|
||||||
|
Either way a failed/rolled-back update **alerts by email** (R-UPD-02 + R-OBS) via the
|
||||||
|
`automated-updates` PrometheusRule group.
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
# WORKED EXAMPLE (reference only — NOT wired into a kustomization, never deployed).
|
||||||
|
# Rendered from catalog/pihole.yaml's `update` block by WS-GITOSS into apps/pihole/. Shows the exact
|
||||||
|
# ImageRepository + ImagePolicy + Deployment setter-marker shape for a container ServiceDefinition.
|
||||||
|
#
|
||||||
|
# update:
|
||||||
|
# channel: stable
|
||||||
|
# range: ">=2024.0.0"
|
||||||
|
# cadence: "0 4 * * *"
|
||||||
|
# autoRollbackOnFailedHealth: true
|
||||||
|
#
|
||||||
|
# See ./README.md for the field-by-field contract.
|
||||||
|
# ---------------------------------------------------------------------------------------------------
|
||||||
|
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: ImageRepository
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
image: pihole/pihole # from source.image
|
||||||
|
interval: 6h # scan schedule; nearest Flux duration to cadence "0 4 * * *" (daily-ish)
|
||||||
|
# provider: generic # dockerhub anon; a pull secret goes here if registry rate-limits bite
|
||||||
|
---
|
||||||
|
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: ImagePolicy
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
imageRepositoryRef:
|
||||||
|
name: pihole
|
||||||
|
# STABLE-ONLY (R-UPD-01): (1) semver range from `update.range`; (2) a tag filter that admits only
|
||||||
|
# bare X.Y.Z releases so a pre-release / `latest` / date-nightly tag can never be selected.
|
||||||
|
filterTags:
|
||||||
|
pattern: '^v?(?P<v>[0-9]+\.[0-9]+\.[0-9]+)$'
|
||||||
|
extract: '$v'
|
||||||
|
policy:
|
||||||
|
semver:
|
||||||
|
range: '>=2024.0.0'
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
# progressDeadlineSeconds bounds a bad rollout so the health gate (readiness probe) can catch it and
|
||||||
|
# the old ReplicaSet keeps serving — the plain-Deployment auto-rollback path (R-UPD-03). A
|
||||||
|
# HelmRelease-delivered service would instead use spec.upgrade.remediation + spec.rollback.
|
||||||
|
progressDeadlineSeconds: 120
|
||||||
|
selector:
|
||||||
|
matchLabels: { app: pihole }
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels: { app: pihole }
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: pihole
|
||||||
|
# THE SETTER MARKER: image-automation writes the ImagePolicy-resolved stable tag here.
|
||||||
|
# No human/AI ever edits this tag — the marker binds it to the pihole ImagePolicy above.
|
||||||
|
image: pihole/pihole:2024.7.0 # {"$imagepolicy": "pihole:pihole"}
|
||||||
|
readinessProbe: # the health gate: a bad image never becomes Ready
|
||||||
|
httpGet: { path: /admin/, port: 80 }
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
---
|
||||||
|
# PINNED VARIANT (R-UPD-03 escape hatch) — what the SAME service renders when `update.pin: "2024.7.0"`
|
||||||
|
# is set: the image is fixed, the setter marker is ABSENT, and NO ImagePolicy/ImageRepository is
|
||||||
|
# rendered (or it is suspended). Automation cannot advance it. This is the one sanctioned human touch,
|
||||||
|
# and it STOPS automation rather than performing an update.
|
||||||
|
#
|
||||||
|
# containers:
|
||||||
|
# - name: pihole
|
||||||
|
# image: pihole/pihole:2024.7.0 # pinned — no $imagepolicy marker; automation is frozen
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"_comment": "Renovate config — Phase 4 automated updates (ADR-0009, R-UPD-01/02). Renovate owns HELM CHART + DEPENDENCY bumps (opens PRs; allowed classes auto-merge on green CI). Flux image-automation owns per-service container IMAGE TAGS (the $imagepolicy setters). The two do NOT overlap: this config DISABLES image-tag updates so Renovate never fights Flux over a running version. Self-hosted against the bootstrap Gitea; platform: gitea set at runtime by the operator's renovate runner.",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
":dependencyDashboard",
|
||||||
|
":semanticCommits",
|
||||||
|
"helpers:pinGitHubActionDigests"
|
||||||
|
],
|
||||||
|
"timezone": "America/New_York",
|
||||||
|
"schedule": ["after 3am and before 6am"],
|
||||||
|
"prConcurrentLimit": 5,
|
||||||
|
"prHourlyLimit": 2,
|
||||||
|
"commitMessagePrefix": "chore(deps):",
|
||||||
|
"labels": ["dependencies", "automated-update"],
|
||||||
|
"ignoreUnstable": true,
|
||||||
|
"respectLatest": true,
|
||||||
|
"enabledManagers": ["flux", "kustomize", "helmv3", "regex", "github-actions"],
|
||||||
|
"flux": {
|
||||||
|
"managerFilePatterns": ["/(^|/)platform/.+\\.ya?ml$/", "/(^|/)apps/.+\\.ya?ml$/"]
|
||||||
|
},
|
||||||
|
"kustomize": {
|
||||||
|
"managerFilePatterns": ["/(^|/)kustomization\\.ya?ml$/"]
|
||||||
|
},
|
||||||
|
"customManagers": [
|
||||||
|
{
|
||||||
|
"customType": "regex",
|
||||||
|
"description": "Inline `# renovate: datasource=... depName=...` markers on pinned URLs / version literals (e.g. platform/kubevirt/kustomization.yaml release URLs, ci/versions.env, infra/devcontainer/versions.env).",
|
||||||
|
"managerFilePatterns": [
|
||||||
|
"/(^|/)kustomization\\.ya?ml$/",
|
||||||
|
"/(^|/)versions\\.env$/"
|
||||||
|
],
|
||||||
|
"matchStrings": [
|
||||||
|
"#\\s*renovate:\\s*datasource=(?<datasource>\\S+)\\s+depName=(?<depName>\\S+)(?:\\s+versioning=(?<versioning>\\S+))?\\s*\\n.*?(?<currentValue>v?[0-9]+\\.[0-9]+\\.[0-9]+)"
|
||||||
|
],
|
||||||
|
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"description": "Auto-merge the allowed low-risk classes on green CI — no human authoring (R-UPD-02). The Gitea merge gate (make -C ci gate) is the required check.",
|
||||||
|
"matchUpdateTypes": ["minor", "patch", "digest"],
|
||||||
|
"automerge": true,
|
||||||
|
"automergeType": "pr",
|
||||||
|
"platformAutomerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "MAJOR / potentially-breaking bumps require human review (docs/11 safety rail). Never auto-merged.",
|
||||||
|
"matchUpdateTypes": ["major"],
|
||||||
|
"automerge": false,
|
||||||
|
"addLabels": ["needs-review", "breaking"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Keep the KubeVirt + CDI operator pair on a compatible track — grouped so they bump together (platform/kubevirt/kustomization.yaml).",
|
||||||
|
"matchDepNames": ["kubevirt/kubevirt", "kubevirt/containerized-data-importer"],
|
||||||
|
"groupName": "kubevirt-cdi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Pinned tool versions (ci/versions.env, infra/devcontainer/versions.env) are a deliberate change surface — label but do not auto-merge; a version bump there changes CI reproducibility.",
|
||||||
|
"matchFileNames": ["ci/versions.env", "infra/devcontainer/versions.env"],
|
||||||
|
"automerge": false,
|
||||||
|
"addLabels": ["ci-tooling"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ignorePaths": [
|
||||||
|
"**/*.example",
|
||||||
|
"**/*.sops.yaml",
|
||||||
|
"conformance/**",
|
||||||
|
"platform/image-automation/per-service/**"
|
||||||
|
],
|
||||||
|
"docker": { "enabled": false },
|
||||||
|
"kubernetes": { "enabled": false }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user