Compare commits

..
Author SHA1 Message Date
issue-fix-bot 7c00ecf43c fix: [alert] OpenBaoReplicaNotReady (openbao) (#808)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
2026-09-07 07:48:24 +00:00
k.renner c7fd3e208a Merge pull request 'fix(infrastructure): LAPI ServersTransport behind the Traefik CRDs (phase 2/2, remove)' (#809) from lapi-transport-phase-2 into master
ci/woodpecker/cron/alert-monitor Pipeline was successful
ci/woodpecker/cron/issue-fix Pipeline was successful
Reviewed-on: k.renner/homelab#809
2026-09-07 06:09:31 +00:00
claude-botandClaude Fable 5.1 5d52e42887 fix(infrastructure): LAPI ServersTransport behind the Traefik CRDs (phase 2: remove)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Phase 1 (#802) put the ServersTransport under post-crds and
infrastructure-crds has applied it. The copy in crowdsec/lapi-ingress.yaml
goes; the Ingress stays (core kind). With it, the infrastructure apply set
holds no traefik.io consumer any more, which is what a cold rebuild needs.

Prune: infrastructure reconciles first and skips the object if the last
applier label names infrastructure-crds; otherwise it deletes and
infrastructure-crds recreates it seconds later. Only the public LAPI
ingress used by the external bouncers is behind that transport.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 07:48:22 +02:00
6 changed files with 19 additions and 121 deletions
@@ -27,11 +27,25 @@ spec:
# not-Ready. The converse does not hold — a crash or a stuck
# scheduling would also fire this — but both want a human too, so the
# imprecision costs nothing.
#
# WHY THE TWO THRESHOLD ALERTS AGGREGATE AWAY THE SCRAPE TARGET
#
# kube-state-metrics stamps instance/pod/endpoint onto every series it
# exports, so a bare selector yields a *different* series each time the
# kube-state-metrics pod is replaced — and Prometheus tracks the `for:`
# timer per output series, so a replacement resets a pending alert to
# inactive. That is not hypothetical: during the 2026-09-07 quorum
# outage kube-state-metrics itself moved twice (the openbao replicas
# were on the same three nodes that were being rebooted), and the two
# alerts below fired ~14 min and ~6.5 min later than their `for:`
# windows say they should have. Aggregating to the identity that
# actually matters — the StatefulSet — makes the series survive a
# kube-state-metrics restart and the timer run continuously.
- alert: OpenBaoReplicaNotReady
# A replica that is sealed never recovers on its own, so the
# duration is only there to ignore ordinary rescheduling: a healthy
# pod moving between nodes is Ready again in well under a minute.
expr: kube_statefulset_status_replicas_ready{namespace="openbao", statefulset="openbao"} < 3
expr: max by (namespace, statefulset) (kube_statefulset_status_replicas_ready{namespace="openbao", statefulset="openbao"}) < 3
for: 10m
labels:
severity: warning
@@ -56,7 +70,7 @@ spec:
# Shorter window than the warning above because this is an outage,
# not a degradation — but not zero, so a rolling restart that
# briefly dips does not page.
expr: kube_statefulset_status_replicas_ready{namespace="openbao", statefulset="openbao"} < 2
expr: max by (namespace, statefulset) (kube_statefulset_status_replicas_ready{namespace="openbao", statefulset="openbao"}) < 2
for: 5m
labels:
severity: critical
@@ -31,22 +31,12 @@
# serves read-only decision streams to a valid bouncer key and nothing else.
# The admin surface is cscli, in-cluster. If the source address ever becomes
# deterministic, an ipAllowList can be added on top.
---
#
# The LAPI serves HTTPS only once tls.enabled is set — there is no plaintext
# fallback and no second port; 8080 is the TLS port. Its certificate comes
# from the chart's own self-signed CA, so Traefik needs that CA to verify the
# backend. SANs include crowdsec-service.crowdsec.svc.cluster.local, which is
# what the Service resolves to.
apiVersion: traefik.io/v1alpha1
kind: ServersTransport
metadata:
name: crowdsec-lapi
namespace: crowdsec
spec:
serverName: crowdsec-service.crowdsec.svc.cluster.local
rootCAs:
- secret: crowdsec-root-secret
---
# backend; the ServersTransport carrying it is a traefik.io CRD consumer and
# lives in post-crds/crowdsec-lapi-serverstransport.yaml.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
@@ -18,7 +18,6 @@ resources:
- cloudnative-pg/
- mariadb-operator/
- netbird-ingress/
- netbird-proxy-services/
- coredns-config/
- k8s-gateway/
- external-dns-unifi/
@@ -1,99 +0,0 @@
# Reconciles NetBird reverse-proxy services from Ingresses that carry
# dns.renner.uno/public: one TLS-passthrough service per host on :443,
# targeting Traefik's ClusterIP through a subnet resource over the service
# CIDR (ci/images/netbird-proxy-services/controller.py).
#
# openbao namespace, like the rotators: its default-deny already allows egress
# to OpenBao, DNS, the apiserver and `world` (netbird.renner.uno), and the
# NetBird PAT is read from OpenBao on every pass through the
# netbird-proxy-services role — policies/netbird-proxy-services.hcl, applied
# once by hand like the rotator roles. Until that role exists the pod logs an
# auth error each minute and does nothing.
apiVersion: v1
kind: ServiceAccount
metadata:
name: netbird-proxy-services
namespace: openbao
labels:
app.kubernetes.io/name: netbird-proxy-services
---
# Read-only, cluster-wide: public Ingresses live in many namespaces, the
# Traefik Service in ingress/.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: netbird-proxy-services-read
rules:
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["services"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: netbird-proxy-services-read
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: netbird-proxy-services-read
subjects:
- kind: ServiceAccount
name: netbird-proxy-services
namespace: openbao
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: netbird-proxy-services
namespace: openbao
labels:
app.kubernetes.io/name: netbird-proxy-services
spec:
replicas: 1
# One writer against the management API at a time.
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: netbird-proxy-services
template:
metadata:
labels:
app.kubernetes.io/name: netbird-proxy-services
spec:
serviceAccountName: netbird-proxy-services
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: controller
image: gitea.renner.uno/gitea_admin/netbird-proxy-services:2026-09-07
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: NB_MANAGEMENT_URL
value: https://netbird.renner.uno
- name: BAO_ADDR
value: http://openbao.openbao.svc.cluster.local:8200
- name: BAO_ROLE
value: netbird-proxy-services
# Must match cluster.network.serviceSubnets in talos/patches/cluster.yaml.
- name: SERVICE_CIDR
value: "10.96.0.0/12"
- name: TRAEFIK_SERVICE
value: ingress/traefik
- name: INTERVAL_SECONDS
value: "60"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
@@ -1,4 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
@@ -31,7 +31,5 @@ resources:
- traefik-crowdsec-middleware.yaml
- traefik-rate-limit-middleware.yaml
- traefik-security-headers-middleware.yaml
# Phase 1 of a two-Kustomization move: also still in crowdsec/lapi-ingress.yaml
# until this layer has applied it once, so prune never runs ahead of apply.
- crowdsec-lapi-serverstransport.yaml
- ../network-policies/