Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea30ba876c | ||
|
|
e8d26669a4 | ||
|
|
048254bfaf | ||
|
|
dd94101647 | ||
|
|
60e3a24652 | ||
|
|
2d32b9bd7d | ||
|
|
9f82b2accf | ||
|
|
1b2ec82a9d | ||
|
|
3724d3068b | ||
|
|
0605ecf593 |
No files matched your search
@@ -80,7 +80,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # full history so ci/touched-requirements.sh can diff vs main
|
fetch-depth: 0 # full history so ci/touched-requirements.sh can diff vs main
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
timeout-minutes: 75
|
timeout-minutes: 75
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # the per-image change detection diffs against the base commit
|
fetch-depth: 0 # the per-image change detection diffs against the base commit
|
||||||
|
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ jobs:
|
|||||||
spec-immutability:
|
spec-immutability:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 # the implementation repo
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 # the implementation repo
|
||||||
- name: Check out the read-only spec (sibling path)
|
- name: Check out the read-only spec (sibling path)
|
||||||
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
repository: ${{ vars.SPEC_REPO_SLUG }} # e.g. Home-Network/home-network-k3s (set in Gitea)
|
repository: ${{ vars.SPEC_REPO_SLUG }} # e.g. Home-Network/home-network-k3s (set in Gitea)
|
||||||
path: ../home-network-k3s
|
path: ../home-network-k3s
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [spec-immutability]
|
needs: [spec-immutability]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 } # full history so touched-requirement diff works
|
with: { fetch-depth: 0 } # full history so touched-requirement diff works
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh lint
|
run: ./.gitea/workflows/scripts/install-tools.sh lint
|
||||||
@@ -85,7 +85,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint]
|
needs: [lint]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l0
|
run: ./.gitea/workflows/scripts/install-tools.sh l0
|
||||||
@@ -96,7 +96,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [l0]
|
needs: [l0]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
||||||
@@ -107,7 +107,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [render-golden]
|
needs: [render-golden]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
run: ./.gitea/workflows/scripts/install-tools.sh l1
|
||||||
@@ -115,7 +115,7 @@ jobs:
|
|||||||
run: make -C ci l1
|
run: make -C ci l1
|
||||||
- name: Upload L1 artifacts
|
- name: Upload L1 artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
with:
|
with:
|
||||||
name: l1-junit-coverage
|
name: l1-junit-coverage
|
||||||
path: |
|
path: |
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
needs: [l1]
|
needs: [l1]
|
||||||
if: ${{ vars.CLUSTER_AVAILABLE == 'true' }}
|
if: ${{ vars.CLUSTER_AVAILABLE == 'true' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
- name: Install pinned tools
|
- name: Install pinned tools
|
||||||
run: ./.gitea/workflows/scripts/install-tools.sh conformance
|
run: ./.gitea/workflows/scripts/install-tools.sh conformance
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
run: make -C ci conformance
|
run: make -C ci conformance
|
||||||
- name: Upload L2/L3 artifacts
|
- name: Upload L2/L3 artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
|
||||||
with:
|
with:
|
||||||
name: conformance-junit-coverage
|
name: conformance-junit-coverage
|
||||||
path: |
|
path: |
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
needs: [lint, l0, render-golden, l1, conformance]
|
needs: [lint, l0, render-golden, l1, conformance]
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- name: Decide
|
- name: Decide
|
||||||
env:
|
env:
|
||||||
R_LINT: ${{ needs.lint.result }}
|
R_LINT: ${{ needs.lint.result }}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-07
|
||||||
|
branch: feat/devcontainer-velero-cli
|
||||||
|
title: Bake the velero CLI into the dev toolbox, pinned to the prod server version
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
- `infra/devcontainer/versions.env`: new `VELERO_VERSION=1.18.2`, with a comment explaining that
|
||||||
|
the CLI and server are version-coupled, so this pin follows the server and is not bumped on
|
||||||
|
its own. It has **no** `# renovate:` marker on purpose. When a `velero` chart bump in
|
||||||
|
`platform/velero/helmrelease.yaml` changes the appVersion, bump this pin in the same PR.
|
||||||
|
- `infra/devcontainer/Containerfile`: `ARG VELERO_VERSION`; the binary comes from the
|
||||||
|
vmware-tanzu GitHub release tarball and is installed in the same RUN as flux/helm. There is
|
||||||
|
no checksum check, which matches the other tools in that file (none of them verify one).
|
||||||
|
`velero=` is recorded in `/etc/dev-toolchain-versions`. The verify stage now checks that
|
||||||
|
`velero` is on PATH, and `check velero v${VELERO_VERSION}` asserts the output of
|
||||||
|
`velero version --client-only`.
|
||||||
|
- `infra/devcontainer/build-image.sh`: `VELERO_VERSION` added to `BUILD_ARGS`. CI reads that
|
||||||
|
list generically through `--print-build-args` (`.gitea/workflows/scripts/images.sh`), so
|
||||||
|
**no sensitive path (`.gitea/**`, `ci/**`) was touched**.
|
||||||
|
- `infra/devcontainer/README.md`: added a Backups row to the tool table.
|
||||||
|
- `runbooks/backup-failure.md`: under "First commands", a read-only
|
||||||
|
`velero backup describe --details` / `backup logs` example run through dev.sh.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Live server version, read-only: `kubectl --context k3s-prod -n velero get deploy velero`
|
||||||
|
shows image `docker.io/velero/velero:v1.18.2`, and HelmRelease `velero` has
|
||||||
|
lastAttemptedRevision `12.2.3`.
|
||||||
|
- Local build: `IMAGE=localhost/home-k3s-dev:velero-wt ./build-image.sh` returned rc 0. The
|
||||||
|
verify stage printed `ok velero` (on PATH), `ok velero v1.18.2` (pin check) and
|
||||||
|
`TOOLBOX VERIFIED`. The image was built under a separate local tag and **not pushed**.
|
||||||
|
- Smoke test with that image against prod (read-only): `velero version` reports Client v1.18.2
|
||||||
|
and Server v1.18.2. `velero backup get` lists the daily backups, all `Completed` with 0 errors.
|
||||||
|
- Recurring warning (reported only, not fixed): every daily backup since
|
||||||
|
`velero-daily-20261031030050` has 1 warning. Backups up to 20261030 have 0. The warning text is:
|
||||||
|
`gitea-actions: resource: /pods name: /act-runner-5944dfdfb8-69bvq message: /Skip pod volume
|
||||||
|
data error: /pod is not in the expected status, ... phase=Failed: pod is not running`.
|
||||||
|
It is the same pod in 20261031, 20261104 and 20261108. `gitea-actions` holds 11 `Failed`
|
||||||
|
pods with reason `Evicted` from ReplicaSet `act-runner-5944dfdfb8`, ten of them started
|
||||||
|
2026-10-30T08:26Z. The running runner is `act-runner-fc597f6b5-vsvkl`. So the warning comes from a
|
||||||
|
leftover evicted (`Failed`) pod that Velero still tries to back up volumes for. Cleaning up the evicted pods is an
|
||||||
|
operator action.
|
||||||
|
- Gate: see the PR description for the toolbox `ci/gate.sh` rc and the skipped stages.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- The CI image build (`.gitea/workflows/images.yaml`) on this branch. It runs on push/PR and I
|
||||||
|
have not seen its result while writing this.
|
||||||
|
- An arm64 build. Only amd64 was built (`TARGETARCH` defaults to amd64).
|
||||||
|
- Any mutating velero command (`backup create`, `restore`, `delete`). None were run.
|
||||||
|
- Why only `-69bvq` of the 11 evicted act-runner pods shows up in the warning. A likely guess is
|
||||||
|
that only that pod's volumes match the backup's pod-volume selection, but I did not check.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
date: 2026-11-08
|
||||||
|
branch: fix/keycloak-operator-crds
|
||||||
|
title: "Keycloak 26.8.2 operator CrashLoop: install its two new CRDs, raise its memory"
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
**Incident.** PR #121 (Renovate) bumped `platform/keycloak/kustomization.yaml`'s three
|
||||||
|
`keycloak-k8s-resources` URLs 26.0.6 -> 26.8.2. Upstream `kubernetes/` at 26.8.2 holds **four**
|
||||||
|
CRDs, not two (GitHub contents API, `?ref=26.8.2`):
|
||||||
|
|
||||||
|
| file | at 26.0.6 | at 26.8.2 |
|
||||||
|
|---|---|---|
|
||||||
|
| `keycloaks.k8s.keycloak.org-v1.yml` | yes | yes |
|
||||||
|
| `keycloakrealmimports.k8s.keycloak.org-v1.yml` | yes | yes |
|
||||||
|
| `keycloakoidcclients.k8s.keycloak.org-v1.yml` | — | **new** (v2alpha1) |
|
||||||
|
| `keycloaksamlclients.k8s.keycloak.org-v1.yml` | — | **new** (v2alpha1) |
|
||||||
|
| `kubernetes.yml` | yes | yes (now carries RBAC for both new kinds) |
|
||||||
|
|
||||||
|
(`cluster-wide/` is a separate variant; upstream's own `kubernetes/kustomization.yml` lists exactly
|
||||||
|
the five files above.) The 26.8.2 operator starts an informer for every controller it has and dies
|
||||||
|
on the first missing kind: `Couldn't start informer for keycloaksamlclients.k8s.keycloak.org/v2alpha1
|
||||||
|
... GET .../namespaces/keycloak/keycloaksamlclients ... Not Found`, exit 1, CrashLoopBackOff. Flux
|
||||||
|
`platform` (`wait: true`, 5m) sat in health checks and every dependent Kustomization reported
|
||||||
|
`dependency ... is not ready`. The old 26.0.6 ReplicaSet kept serving (RollingUpdate 25%/25%), so SSO
|
||||||
|
stayed up.
|
||||||
|
|
||||||
|
**Fix.**
|
||||||
|
- `platform/keycloak/kustomization.yaml`: added the two CRD URLs, each with the same
|
||||||
|
`# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources` marker (the repo's
|
||||||
|
custom.regex manager matches all five — checked by running its regex over the file). Header
|
||||||
|
comment updated to v26.8.2, plus a warning that Renovate cannot add a CRD a release introduces.
|
||||||
|
- Same file: a `patches:` JSON6902 patch on Deployment `keycloak-operator`: memory limit
|
||||||
|
450Mi -> **768Mi**, request 450Mi -> **512Mi**. 450Mi is upstream's default (unchanged at 26.8.2,
|
||||||
|
so not something the bump changed). Evidence it is too small here: the 26.0.6 pod had **154
|
||||||
|
restarts**, `lastState.terminated.reason: OOMKilled` (exit 137, 2026-11-07T23:09:54Z), and
|
||||||
|
`kubectl top` showed **421Mi / 450Mi**. 26.8.2 runs four controllers instead of two on Quarkus
|
||||||
|
3.40.1.1 (was 3.15.1). No variable in the operator to disable watching unused kinds was used —
|
||||||
|
installing the CRDs is upstream's supported shape.
|
||||||
|
- `platform/keycloak/README.md` "Upgrading Keycloak": the diff-the-CRD-list step; "three URLs" ->
|
||||||
|
"the release URLs". `renovate.json5`: same wording fix in a comment only.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- Upstream file lists at 26.0.6 and 26.8.2 (GitHub API) and `diff` of `kubernetes.yml` between the
|
||||||
|
two: the new kinds' ClusterRoles + RoleBindings ship in it, and the Deployment's resources are
|
||||||
|
still 450Mi/450Mi.
|
||||||
|
- Live, read-only (`kubectl --context k3s-prod`, 2026-11-08 ~05:57Z): only `keycloaks` and
|
||||||
|
`keycloakrealmimports` CRDs installed; Deployment strategy RollingUpdate maxSurge/maxUnavailable
|
||||||
|
25%; RS `keycloak-operator-58dff6c4` (26.8.2) 0/1 CrashLoopBackOff, RS `78dbb75f64` (26.0.6) 1/1;
|
||||||
|
operator log is the informer error above; Flux `platform` `Healthy=Unknown` "Running health
|
||||||
|
checks for revision main@sha1:e8d2666"; no NetworkPolicy and no CiliumNetworkPolicy in ns
|
||||||
|
`keycloak`.
|
||||||
|
- No sensitive path touched (`infra/gitea/approval-policy.json`), so this should merge on green.
|
||||||
|
- Gate: see the PR body for the exact rc and stages of the run against the final commit.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **That the 26.8.2 operator becomes Ready after this merges.** Only the two missing kinds were
|
||||||
|
observed failing; another start-up failure behind them is possible.
|
||||||
|
- **That 768Mi is enough** — no 26.8.2 memory measurement exists yet; re-check `kubectl top` after
|
||||||
|
a week.
|
||||||
|
- **The Keycloak server upgrade 26.0.6 -> 26.8.2 itself.** Once the operator runs it will roll
|
||||||
|
keycloak-0/1 and Keycloak migrates the DB schema. The CR's `spec.update.strategy` defaults to
|
||||||
|
`RecreateOnImageChange` — expect a **full SSO outage** while both pods recreate, not a rolling
|
||||||
|
one. Back up / confirm the latest CNPG backup first if that matters.
|
||||||
|
- **The 26.8.2 Keycloak CRD's `spec.networkPolicy.enabled` defaults to `true`.** The operator will
|
||||||
|
create a NetworkPolicy selecting the Keycloak pods (first policy in that namespace). With no
|
||||||
|
`from` lists set it allows all sources on the HTTP/management ports, so Gateway, ServiceMonitor
|
||||||
|
and the reconciler Jobs should still reach it — but this was read from the CRD text, not observed.
|
||||||
|
- **The passkey reconciler's 26.0.6 Admin API assumptions against 26.8.2** (README "Upgrading
|
||||||
|
Keycloak" list) — not re-checked here; PR #121 merged without it.
|
||||||
|
- `catalog/keycloak.yaml`'s documentation-only image tag was not updated.
|
||||||
@@ -30,6 +30,9 @@ ARG CHECK_JSONSCHEMA_VERSION
|
|||||||
# Pinned to the Prometheus version prod actually runs — see versions.env for why that lockstep is
|
# Pinned to the Prometheus version prod actually runs — see versions.env for why that lockstep is
|
||||||
# the point, and for the live-cluster command that establishes it.
|
# the point, and for the live-cluster command that establishes it.
|
||||||
ARG PROMTOOL_VERSION
|
ARG PROMTOOL_VERSION
|
||||||
|
# velero: operator CLI for the backup server. Pinned to the server version prod runs — the CLI
|
||||||
|
# and server are version-coupled; see versions.env.
|
||||||
|
ARG VELERO_VERSION
|
||||||
ARG TARGETARCH=amd64
|
ARG TARGETARCH=amd64
|
||||||
|
|
||||||
# Distro packages: libvirt client libs (harmless disk-image utilities), ansible, ssh,
|
# Distro packages: libvirt client libs (harmless disk-image utilities), ansible, ssh,
|
||||||
@@ -66,6 +69,10 @@ RUN set -eux; \
|
|||||||
curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl; chmod +x /usr/local/bin/kubectl; \
|
curl -fsSL "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" -o /usr/local/bin/kubectl; chmod +x /usr/local/bin/kubectl; \
|
||||||
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" | tar -xz -C /tmp; \
|
||||||
install -m0755 "/tmp/linux-${TARGETARCH}/helm" /usr/local/bin/helm; \
|
install -m0755 "/tmp/linux-${TARGETARCH}/helm" /usr/local/bin/helm; \
|
||||||
|
VELERO_TARBALL="velero-v${VELERO_VERSION}-linux-${TARGETARCH}"; \
|
||||||
|
curl -fsSL "https://github.com/vmware-tanzu/velero/releases/download/v${VELERO_VERSION}/${VELERO_TARBALL}.tar.gz" \
|
||||||
|
| tar -xz -C /usr/local/bin --strip-components=1 "${VELERO_TARBALL}/velero"; \
|
||||||
|
chmod +x /usr/local/bin/velero; \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
# Pinned conformance test toolchain (L0/L1 static gate + the L2/L3 Chainsaw runner).
|
# Pinned conformance test toolchain (L0/L1 static gate + the L2/L3 Chainsaw runner).
|
||||||
@@ -93,7 +100,7 @@ RUN { echo "opentofu=${OPENTOFU_VERSION}"; echo "age=${AGE_VERSION}"; echo "sops
|
|||||||
echo "kyverno=${KYVERNO_VERSION}"; echo "kyverno-json=${KYVERNO_JSON_VERSION}"; \
|
echo "kyverno=${KYVERNO_VERSION}"; echo "kyverno-json=${KYVERNO_JSON_VERSION}"; \
|
||||||
echo "chainsaw=${CHAINSAW_VERSION}"; echo "yamllint=${YAMLLINT_VERSION}"; \
|
echo "chainsaw=${CHAINSAW_VERSION}"; echo "yamllint=${YAMLLINT_VERSION}"; \
|
||||||
echo "yq=${YQ_VERSION}"; echo "check-jsonschema=${CHECK_JSONSCHEMA_VERSION}"; \
|
echo "yq=${YQ_VERSION}"; echo "check-jsonschema=${CHECK_JSONSCHEMA_VERSION}"; \
|
||||||
echo "promtool=${PROMTOOL_VERSION}"; \
|
echo "promtool=${PROMTOOL_VERSION}"; echo "velero=${VELERO_VERSION}"; \
|
||||||
} > /etc/dev-toolchain-versions
|
} > /etc/dev-toolchain-versions
|
||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
@@ -112,6 +119,7 @@ ARG KUBECTL_VERSION
|
|||||||
ARG CHAINSAW_VERSION
|
ARG CHAINSAW_VERSION
|
||||||
ARG KYVERNO_VERSION
|
ARG KYVERNO_VERSION
|
||||||
ARG SOPS_VERSION
|
ARG SOPS_VERSION
|
||||||
|
ARG VELERO_VERSION
|
||||||
RUN --network=none /bin/bash -euo pipefail <<'VERIFY'
|
RUN --network=none /bin/bash -euo pipefail <<'VERIFY'
|
||||||
echo "--- no network (the build-time stand-in for run --network none) ---"
|
echo "--- no network (the build-time stand-in for run --network none) ---"
|
||||||
if getent hosts registry.fedoraproject.org >/dev/null 2>&1 || curl -sS --max-time 3 -o /dev/null https://1.1.1.1 2>/dev/null; then
|
if getent hosts registry.fedoraproject.org >/dev/null 2>&1 || curl -sS --max-time 3 -o /dev/null https://1.1.1.1 2>/dev/null; then
|
||||||
@@ -122,13 +130,13 @@ echo "--- baked versions ---"; cat /etc/dev-toolchain-versions
|
|||||||
echo "--- every tool is on PATH ---"
|
echo "--- every tool is on PATH ---"
|
||||||
for t in tofu age age-keygen sops flux kubectl helm kyverno kyverno-json chainsaw yq \
|
for t in tofu age age-keygen sops flux kubectl helm kyverno kyverno-json chainsaw yq \
|
||||||
yamllint check-jsonschema promtool ansible-playbook git make jq diff python3 \
|
yamllint check-jsonschema promtool ansible-playbook git make jq diff python3 \
|
||||||
wg qrencode; do
|
wg qrencode velero; do
|
||||||
if command -v "$t" >/dev/null 2>&1; then printf " ok %s\n" "$t";
|
if command -v "$t" >/dev/null 2>&1; then printf " ok %s\n" "$t";
|
||||||
else printf " MISSING %s\n" "$t"; missing=1; fi
|
else printf " MISSING %s\n" "$t"; missing=1; fi
|
||||||
done
|
done
|
||||||
[ -z "${missing:-}" ] || { echo "toolbox is missing tools"; exit 1; }
|
[ -z "${missing:-}" ] || { echo "toolbox is missing tools"; exit 1; }
|
||||||
echo "--- version-sensitive tools report their pins ---"
|
echo "--- version-sensitive tools report their pins ---"
|
||||||
: "${PROMTOOL_VERSION:?}" "${KUBECTL_VERSION:?}" "${CHAINSAW_VERSION:?}" "${KYVERNO_VERSION:?}" "${SOPS_VERSION:?}"
|
: "${PROMTOOL_VERSION:?}" "${KUBECTL_VERSION:?}" "${CHAINSAW_VERSION:?}" "${KYVERNO_VERSION:?}" "${SOPS_VERSION:?}" "${VELERO_VERSION:?}"
|
||||||
check() { # name pin actual-output
|
check() { # name pin actual-output
|
||||||
if printf "%s" "$3" | grep -qF "$2"; then printf " ok %-10s %s\n" "$1" "$2";
|
if printf "%s" "$3" | grep -qF "$2"; then printf " ok %-10s %s\n" "$1" "$2";
|
||||||
else printf " DRIFT %-10s want %s, got: %s\n" "$1" "$2" "$(printf "%s" "$3" | head -1)"; drift=1; fi
|
else printf " DRIFT %-10s want %s, got: %s\n" "$1" "$2" "$(printf "%s" "$3" | head -1)"; drift=1; fi
|
||||||
@@ -138,6 +146,7 @@ check kubectl "${KUBECTL_VERSION}" "$(kubectl version --client 2>&1)"
|
|||||||
check chainsaw "${CHAINSAW_VERSION}" "$(chainsaw version 2>&1)"
|
check chainsaw "${CHAINSAW_VERSION}" "$(chainsaw version 2>&1)"
|
||||||
check kyverno "${KYVERNO_VERSION}" "$(kyverno version 2>&1)"
|
check kyverno "${KYVERNO_VERSION}" "$(kyverno version 2>&1)"
|
||||||
check sops "${SOPS_VERSION}" "$(sops --version --disable-version-check 2>&1 || sops --version 2>&1)"
|
check sops "${SOPS_VERSION}" "$(sops --version --disable-version-check 2>&1 || sops --version 2>&1)"
|
||||||
|
check velero "v${VELERO_VERSION}" "$(velero version --client-only 2>&1)"
|
||||||
[ -z "${drift:-}" ] || { echo "a tool does not report its pinned version"; exit 1; }
|
[ -z "${drift:-}" ] || { echo "a tool does not report its pinned version"; exit 1; }
|
||||||
echo "--- python can import what the renderer and the gate need ---"
|
echo "--- python can import what the renderer and the gate need ---"
|
||||||
python3 -c "import yaml; print(' PyYAML', yaml.__version__)"
|
python3 -c "import yaml; print(' PyYAML', yaml.__version__)"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ baseline, and the *same* binaries CI uses.
|
|||||||
| Provisioning | OpenTofu (`bpg/proxmox` provider), `ansible-core`, `openssh-clients` |
|
| Provisioning | OpenTofu (`bpg/proxmox` provider), `ansible-core`, `openssh-clients` |
|
||||||
| GitOps / cluster | `flux`, `kubectl`, `helm` |
|
| GitOps / cluster | `flux`, `kubectl`, `helm` |
|
||||||
| Secrets | `sops`, `age` + `age-keygen` |
|
| Secrets | `sops`, `age` + `age-keygen` |
|
||||||
|
| Backups (`platform/velero`) | `velero` CLI — pinned to the **server** version prod runs (version-coupled; see `versions.env`) |
|
||||||
| VPN (`platform/vpn-ingress`) | `wg` (wireguard-tools) — mint the server key, derive client public keys; `qrencode` — render a client profile as a phone-scannable QR |
|
| VPN (`platform/vpn-ingress`) | `wg` (wireguard-tools) — mint the server key, derive client public keys; `qrencode` — render a client profile as a phone-scannable QR |
|
||||||
| **CI gate** | `yamllint`, `check-jsonschema`, `kyverno` CLI, `kyverno-json`, `chainsaw`, `yq`, `diffutils`, `make` |
|
| **CI gate** | `yamllint`, `check-jsonschema`, `kyverno` CLI, `kyverno-json`, `chainsaw`, `yq`, `diffutils`, `make` |
|
||||||
| Misc | `qemu-img`, `cloud-utils`/`genisoimage`, `libvirt-client` (disk-image utilities only — leftovers from the libvirt era, harmless) |
|
| Misc | `qemu-img`, `cloud-utils`/`genisoimage`, `libvirt-client` (disk-image utilities only — leftovers from the libvirt era, harmless) |
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ BUILD_ARGS=(
|
|||||||
"KYVERNO_JSON_VERSION=$KYVERNO_JSON_VERSION" "CHAINSAW_VERSION=$CHAINSAW_VERSION"
|
"KYVERNO_JSON_VERSION=$KYVERNO_JSON_VERSION" "CHAINSAW_VERSION=$CHAINSAW_VERSION"
|
||||||
"YAMLLINT_VERSION=$YAMLLINT_VERSION" "YQ_VERSION=$YQ_VERSION"
|
"YAMLLINT_VERSION=$YAMLLINT_VERSION" "YQ_VERSION=$YQ_VERSION"
|
||||||
"CHECK_JSONSCHEMA_VERSION=$CHECK_JSONSCHEMA_VERSION" "PROMTOOL_VERSION=$PROMTOOL_VERSION"
|
"CHECK_JSONSCHEMA_VERSION=$CHECK_JSONSCHEMA_VERSION" "PROMTOOL_VERSION=$PROMTOOL_VERSION"
|
||||||
|
"VELERO_VERSION=$VELERO_VERSION"
|
||||||
)
|
)
|
||||||
if [ "${PRINT_ARGS}" -eq 1 ]; then printf '%s\n' "${BUILD_ARGS[@]}"; exit 0; fi
|
if [ "${PRINT_ARGS}" -eq 1 ]; then printf '%s\n' "${BUILD_ARGS[@]}"; exit 0; fi
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,19 @@ CHECK_JSONSCHEMA_VERSION=0.29.4
|
|||||||
# promtool ships INSIDE the prometheus release tarball (prometheus-<v>.linux-<arch>.tar.gz), so
|
# promtool ships INSIDE the prometheus release tarball (prometheus-<v>.linux-<arch>.tar.gz), so
|
||||||
# the version string here is a Prometheus release, not a separate promtool release.
|
# the version string here is a Prometheus release, not a separate promtool release.
|
||||||
PROMTOOL_VERSION=3.14.0
|
PROMTOOL_VERSION=3.14.0
|
||||||
|
# velero — the operator CLI for the Velero backup server (platform/velero): `velero backup
|
||||||
|
# describe <name> --details`, `velero backup logs`, `velero backup create --from-schedule`.
|
||||||
|
#
|
||||||
|
# THIS PIN IS NOT FREE-FLOATING: the CLI and server are VERSION-COUPLED (the CLI talks to the
|
||||||
|
# server's CRDs and its download-request protocol; upstream supports only a matching minor), so
|
||||||
|
# it MUST equal the Velero server version actually running on prod.
|
||||||
|
# Verified against the live cluster on 2026-11-07:
|
||||||
|
# kubectl --context k3s-prod -n velero get deploy velero \
|
||||||
|
# -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||||
|
# -> docker.io/velero/velero:v1.18.2 (HelmRelease velero, chart velero-12.2.3)
|
||||||
|
# Deliberately carries NO `# renovate:` marker: it must move WITH the server, i.e. when a
|
||||||
|
# vmware-tanzu `velero` chart bump (platform/velero/helmrelease.yaml, range >=12 <13) changes the
|
||||||
|
# chart's appVersion, bump this in the same PR. Never bump it on its own. The verify stage
|
||||||
|
# asserts `velero version --client-only` reports exactly this version.
|
||||||
|
VELERO_VERSION=1.18.2
|
||||||
# Proxmox provider (bpg/proxmox) is pinned in the tofu module (infra/tofu/versions.tf), not here.
|
# Proxmox provider (bpg/proxmox) is pinned in the tofu module (infra/tofu/versions.tf), not here.
|
||||||
@@ -367,9 +367,17 @@ is involved — so step 2 failing with no log entry is the expected signature of
|
|||||||
The `Keycloak` CR has no `spec.image`, so the server image is whatever the operator's
|
The `Keycloak` CR has no `spec.image`, so the server image is whatever the operator's
|
||||||
`RELATED_IMAGE_KEYCLOAK` names — the same tag as the operator (prod, 2026-10-30: operator
|
`RELATED_IMAGE_KEYCLOAK` names — the same tag as the operator (prod, 2026-10-30: operator
|
||||||
`26.0.6`, `keycloak-0/1` on `quay.io/keycloak/keycloak:26.0.6`). Operator, CRDs and server are
|
`26.0.6`, `keycloak-0/1` on `quay.io/keycloak/keycloak:26.0.6`). Operator, CRDs and server are
|
||||||
released in lockstep from `keycloak/keycloak-k8s-resources`, and the three URLs in
|
released in lockstep from `keycloak/keycloak-k8s-resources`, and the release URLs in
|
||||||
`kustomization.yaml` are the one knob. Renovate raises them as ONE PR (`keycloak-operator` group,
|
`kustomization.yaml` are the one knob. Renovate raises them as ONE PR (`keycloak-operator` group,
|
||||||
labels `needs-review` + `blast-radius`), **never auto-merged**; majors wait for a dashboard tick.
|
labels `needs-review` + `blast-radius`), **never auto-merged**; majors wait for a dashboard tick.
|
||||||
|
|
||||||
|
**Renovate bumps the URLs that exist; it cannot add a CRD a release introduced.** Before merging a
|
||||||
|
bump, diff `kubernetes/` (and its `kustomization.yml`) at the new tag against the URL list. 26.0.6
|
||||||
|
shipped 2 CRDs, 26.8.2 ships 4 (`keycloakoidcclients`, `keycloaksamlclients` added); PR #121 bumped
|
||||||
|
only the existing URLs and the 26.8.2 operator CrashLooped on the missing `keycloaksamlclients`
|
||||||
|
informer, stalling Flux `platform` and every tier below it (2026-11-08,
|
||||||
|
`decisions/2026-11-08-keycloak-operator-crds.md`). The operator's memory is also patched in
|
||||||
|
`kustomization.yaml` (upstream's 450Mi OOMKilled 26.0.6 here); keep the patch across bumps.
|
||||||
`catalog/keycloak.yaml`'s image tag is documentation only (its render deploys nothing) and
|
`catalog/keycloak.yaml`'s image tag is documentation only (its render deploys nothing) and
|
||||||
Renovate is disabled for it — update it by hand to match after an operator bump merges.
|
Renovate is disabled for it — update it by hand to match after an operator bump merges.
|
||||||
|
|
||||||
|
|||||||
@@ -20,19 +20,33 @@ kind: Kustomization
|
|||||||
namespace: keycloak
|
namespace: keycloak
|
||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
# --- Keycloak Operator v26.0.6 (pinned upstream release manifests) ---
|
# --- Keycloak Operator v26.8.2 (pinned upstream release manifests) ---
|
||||||
# The operator, its CRDs and the SERVER IMAGE move in lockstep: the Keycloak CR has no
|
# The operator, its CRDs and the SERVER IMAGE move in lockstep: the Keycloak CR has no
|
||||||
# spec.image, so the operator's RELATED_IMAGE_KEYCLOAK (quay.io/keycloak/keycloak:<same tag>)
|
# spec.image, so the operator's RELATED_IMAGE_KEYCLOAK (quay.io/keycloak/keycloak:<same tag>)
|
||||||
# is what runs. These three URLs are therefore THE Keycloak version. Each carries a Renovate
|
# is what runs. These URLs are therefore THE Keycloak version. Each carries a Renovate
|
||||||
# marker (custom.regex manager, renovate.json5) under ONE depName, so all three bump in ONE
|
# marker (custom.regex manager, renovate.json5) under ONE depName, so all of them bump in ONE
|
||||||
# branch, never auto-merged (packageRule "keycloak-operator"). Before merging a bump, re-check
|
# branch, never auto-merged (packageRule "keycloak-operator"). Before merging a bump, re-check
|
||||||
# passkey-reconciler.yaml's 26.0.6 Admin API assumptions — see README.md "Upgrading Keycloak".
|
# passkey-reconciler.yaml's 26.0.6 Admin API assumptions — see README.md "Upgrading Keycloak".
|
||||||
|
#
|
||||||
|
# THE URL LIST MUST MATCH UPSTREAM'S kubernetes/kustomization.yml AT THE NEW TAG — Renovate only
|
||||||
|
# rewrites the version in the URLs that are here; it cannot add a CRD the new release introduced.
|
||||||
|
# 26.0.6 shipped 2 CRDs; 26.8.2 ships 4 (+ keycloakoidcclients, keycloaksamlclients, both
|
||||||
|
# v2alpha1). PR #121 bumped only the existing three URLs, and the 26.8.2 operator — which starts an
|
||||||
|
# informer for EVERY kind it controls, used or not — CrashLooped on
|
||||||
|
# `Couldn't start informer for keycloaksamlclients.k8s.keycloak.org/v2alpha1 ... Not Found`,
|
||||||
|
# stalling Flux `platform` and everything downstream (2026-11-08). On every bump, diff
|
||||||
|
# https://github.com/keycloak/keycloak-k8s-resources/tree/<tag>/kubernetes
|
||||||
|
# against this list before merging.
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloaks.k8s.keycloak.org-v1.yml
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml
|
||||||
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.0.6/kubernetes/kubernetes.yml
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloakoidcclients.k8s.keycloak.org-v1.yml
|
||||||
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/keycloaksamlclients.k8s.keycloak.org-v1.yml
|
||||||
|
# renovate: datasource=github-tags depName=keycloak/keycloak-k8s-resources
|
||||||
|
- https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/26.8.2/kubernetes/kubernetes.yml
|
||||||
# --- Admin (break-glass) credential — SOPS-encrypted, referenced by name (ADR-0007, R-IDP-04) ---
|
# --- Admin (break-glass) credential — SOPS-encrypted, referenced by name (ADR-0007, R-IDP-04) ---
|
||||||
- keycloak-admin-credentials.sops.yaml
|
- keycloak-admin-credentials.sops.yaml
|
||||||
# --- OIDC client secrets (grafana + oauth2-proxy) the realm import injects via placeholders ---
|
# --- OIDC client secrets (grafana + oauth2-proxy) the realm import injects via placeholders ---
|
||||||
@@ -51,3 +65,23 @@ resources:
|
|||||||
- keycloak-db-backup-credentials-prod.sops.yaml
|
- keycloak-db-backup-credentials-prod.sops.yaml
|
||||||
# --- Grafana dashboard for Keycloak health (plain ConfigMap; R-OBS-03) ---
|
# --- Grafana dashboard for Keycloak health (plain ConfigMap; R-OBS-03) ---
|
||||||
- grafana-dashboard.yaml
|
- grafana-dashboard.yaml
|
||||||
|
# --- Operator memory: upstream ships requests=limits=450Mi, which this cluster outgrew ON 26.0.6 ---
|
||||||
|
# Measured on prod 2026-11-08: the 26.0.6 operator pod had 154 restarts in 64d, lastState
|
||||||
|
# `OOMKilled` (exit 137, 2026-11-07T23:09:54Z), and sat at 421Mi of its 450Mi (94%) in `kubectl top`.
|
||||||
|
# 26.8.2 keeps the same 450Mi default but runs FOUR controllers/informers instead of two (adds
|
||||||
|
# keycloakoidcclient + keycloaksamlclient) on a newer Quarkus (3.15.1 -> 3.40.1.1), so it would
|
||||||
|
# plausibly start above a ceiling 26.0.6 already crossed. Limit 768Mi = ~1.8x the observed 421Mi
|
||||||
|
# working set; request 512Mi = the observed working set plus headroom, so the scheduler reserves what
|
||||||
|
# it actually uses. Revisit with `kubectl top` once 26.8.2 has run for a week. Patched here (not in
|
||||||
|
# the remote kubernetes.yml) so a Renovate bump keeps it.
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
kind: Deployment
|
||||||
|
name: keycloak-operator
|
||||||
|
patch: |-
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/resources/limits/memory
|
||||||
|
value: 768Mi
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/containers/0/resources/requests/memory
|
||||||
|
value: 512Mi
|
||||||
+1
-1
@@ -264,7 +264,7 @@
|
|||||||
// and the server image are released in lockstep from keycloak/keycloak-k8s-resources, and the
|
// and the server image are released in lockstep from keycloak/keycloak-k8s-resources, and the
|
||||||
// Keycloak CR carries no spec.image, so the operator's RELATED_IMAGE_KEYCLOAK picks the server
|
// Keycloak CR carries no spec.image, so the operator's RELATED_IMAGE_KEYCLOAK picks the server
|
||||||
// (measured on prod 2026-10-30: operator 26.0.6 -> keycloak-0/1 run quay.io/keycloak/keycloak:
|
// (measured on prod 2026-10-30: operator 26.0.6 -> keycloak-0/1 run quay.io/keycloak/keycloak:
|
||||||
// 26.0.6). The three release URLs in platform/keycloak/kustomization.yaml carry `# renovate:`
|
// 26.0.6). The release URLs in platform/keycloak/kustomization.yaml carry `# renovate:`
|
||||||
// markers under this ONE depName, so they move together in one branch (groupName makes that
|
// markers under this ONE depName, so they move together in one branch (groupName makes that
|
||||||
// explicit). NEVER AUTO-MERGED, not even a patch: Keycloak is the IdP that gates SSO for every
|
// explicit). NEVER AUTO-MERGED, not even a patch: Keycloak is the IdP that gates SSO for every
|
||||||
// app AND kubectl OIDC, and platform/keycloak/passkey-reconciler.yaml encodes Keycloak 26.0.6
|
// app AND kubectl OIDC, and platform/keycloak/passkey-reconciler.yaml encodes Keycloak 26.0.6
|
||||||
|
|||||||
@@ -99,6 +99,16 @@ infra/devcontainer/dev.sh run bash -c \
|
|||||||
-o custom-columns=NAME:.metadata.name,BACKUP:.metadata.labels.velero\\.io/backup-name,PHASE:.status.phase,BYTES:.status.progress.bytesDone'
|
-o custom-columns=NAME:.metadata.name,BACKUP:.metadata.labels.velero\\.io/backup-name,PHASE:.status.phase,BYTES:.status.progress.bytesDone'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `velero` CLI is in the toolbox (pinned to the server version) and gives the per-item errors and
|
||||||
|
warnings the CR only counts. Read-only; `velero backup create/delete` and `velero restore` are
|
||||||
|
operator actions:
|
||||||
|
|
||||||
|
```
|
||||||
|
infra/devcontainer/dev.sh run bash -c \
|
||||||
|
'velero --kubecontext k3s-prod -n velero backup describe <name> --details; \
|
||||||
|
velero --kubecontext k3s-prod -n velero backup logs <name> | grep -iE "level=(error|warn)" | tail -25'
|
||||||
|
```
|
||||||
|
|
||||||
The plugin pin (cause 1 depends entirely on this):
|
The plugin pin (cause 1 depends entirely on this):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in new issue
Block a user