home-assistant: write to in-cluster InfluxDB

Add a Job that creates an HA-write-scoped auth on the in-cluster
InfluxDB on every fresh deploy and stashes the token in a Secret in
the home-assistant namespace; HA's config-reconciler reads it and
patches configuration.yaml's `influxdb:` block to point at
`influxdb-influxdb2.influxdb.svc.cluster.local:8086`, bucket
`suedstr28`, org `renner`, with the new token.

Two pieces:

1. kubernetes/seed/influxdb/ha-token-job.yaml — Job in influxdb ns
   that, after `wait-for-influxd`:
   - resolves suedstr28's bucket id,
   - reuses an existing matching auth (description "home-assistant
     write") if one's there, else creates a fresh write-bucket auth,
   - kubectl-applies a generic Secret named `ha-influxdb-token` into
     the home-assistant namespace with the resulting token.
   ServiceAccount lives in influxdb; Role+RoleBinding in
   home-assistant grant just get/create/update/patch on the single
   named Secret. Tokens don't survive a fresh InfluxDB bolt store, so
   a static SOPS Secret won't do — must be re-created per deploy.

2. kubernetes/apps/home-assistant/deployment.yaml — config-reconciler
   sidecar gets `INFLUX_TOKEN` env from `ha-influxdb-token` (with
   `optional: true` so HA boots even before the Job finishes), plus a
   new step in the reconcile loop that drift-detects and patches the
   `influxdb:` block. Only connection+auth keys are touched; HA-
   managed include/exclude/tags filters stay intact.

Lives in seed/ for now (matches CNPG and the chart's own location);
moves to apps/influxdb/ at cutover when the seed phase is removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 18:08:59 +02:00
co-authored by Claude Opus 4.7
parent 2d22a52526
commit 1daaa6f59f
3 changed files with 221 additions and 0 deletions
@@ -234,6 +234,24 @@ spec:
name: mosquitto-credentials
key: homeassistant-password
optional: true
# Write-only token for the in-cluster InfluxDB suedstr28
# bucket. Created by the influxdb-ha-token Job in the
# influxdb namespace (kubernetes/seed/influxdb/
# ha-token-job.yaml) on each fresh deploy and dropped into
# this Secret cross-namespace via RBAC.
#
# optional: HA must come up even if the Job hasn't
# populated the Secret yet (cluster InfluxDB might still
# be reseeding). The InfluxDB reconcile step below skips
# itself when the env is empty; once the Secret appears,
# the next loop iteration patches configuration.yaml and
# self-deletes the pod for a clean rollover.
- name: INFLUX_TOKEN
valueFrom:
secretKeyRef:
name: ha-influxdb-token
key: INFLUX_TOKEN
optional: true
# Single switch covering every integration that has to stay
# off until cutover (legacy HA still running on VM 103 + IoT
# plumbing / USB devices not yet wired). When true, the
@@ -412,6 +430,46 @@ spec:
fi
fi
# InfluxDB recorder reconcile. Top-level `influxdb:`
# block in configuration.yaml gets repointed at the
# in-cluster InfluxDB. Skipped when INFLUX_TOKEN is
# empty (Job in influxdb namespace hasn't populated
# the cross-namespace Secret yet). Once the token
# appears, the next loop iteration patches and
# self-deletes the pod for a clean rollover. Only the
# connection+auth keys are touched; HA-managed
# include/exclude/tags filters are left intact.
if [ -n "${INFLUX_TOKEN:-}" ]; then
desired_influx=$(jq -ncS \
--arg token "$INFLUX_TOKEN" \
'{api_version: 2,
host: "influxdb-influxdb2.influxdb.svc.cluster.local",
port: 8086,
ssl: false,
verify_ssl: false,
organization: "renner",
bucket: "suedstr28",
token: $token}')
current_influx=$(yq -o=json '.influxdb // {}' "$CONFIG" 2>/dev/null \
| jq -cS --argjson d "$desired_influx" \
'with_entries(select(.key as $k | $d | has($k)))' \
|| echo '{}')
if [ "$desired_influx" != "$current_influx" ]; then
echo "influxdb config drift; patching"
yq -i '
.influxdb.api_version = 2 |
.influxdb.host = "influxdb-influxdb2.influxdb.svc.cluster.local" |
.influxdb.port = 8086 |
.influxdb.ssl = false |
.influxdb.verify_ssl = false |
.influxdb.organization = "renner" |
.influxdb.bucket = "suedstr28" |
.influxdb.token = strenv(INFLUX_TOKEN)
' "$CONFIG"
drift=1
fi
fi
# Sibling-app integrations: HAOS supervisor addon names
# (core-whisper / core-matter-server / …) don't resolve in
# the cluster. Repoint each existing entry at the matching
+162
View File
@@ -0,0 +1,162 @@
---
# Create a write-only InfluxDB auth scoped to the suedstr28 bucket and
# stash the token as a Secret in the home-assistant namespace, so HA's
# `influxdb:` integration can write to the in-cluster InfluxDB.
#
# Why a Job (not a static SOPS Secret): tokens are persisted in the
# server's bolt store, which is fresh on every rebuild. A SOPS-stored
# token wouldn't actually exist as an auth on the new server; it would
# just be a string. Each rebuild needs to create the auth fresh and
# capture the resulting token.
#
# Why in seed/influxdb/: the seed Flux KS already runs in the right
# phase (after CNPG/Ceph/cnpg-barman, before per-app KSes) and at
# cutover the whole directory moves to apps/influxdb/ along with the
# chart. The Job is part of InfluxDB's initial-state setup.
#
# Cross-namespace Secret write: ServiceAccount lives in influxdb;
# Role+RoleBinding in home-assistant grant just `get/create/update/
# patch` on the single named Secret.
apiVersion: v1
kind: ServiceAccount
metadata:
name: influxdb-ha-token
namespace: influxdb
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ha-influxdb-token-writer
namespace: home-assistant
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["ha-influxdb-token"]
verbs: ["get", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ha-influxdb-token-writer
namespace: home-assistant
subjects:
- kind: ServiceAccount
name: influxdb-ha-token
namespace: influxdb
roleRef:
kind: Role
name: ha-influxdb-token-writer
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: Job
metadata:
name: influxdb-ha-token
namespace: influxdb
annotations:
# Job specs are immutable; force-recreate so spec changes actually
# apply on Flux re-reconcile.
kustomize.toolkit.fluxcd.io/force: enabled
spec:
backoffLimit: 6
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
serviceAccountName: influxdb-ha-token
securityContext:
runAsUser: 0
runAsNonRoot: false
initContainers:
# Same wait pattern as the seed Job's wait-for-influxd: ensure
# the API is reachable AND the bootstrap admin token is the
# live token (not still falling back to a stale onboarding
# state). Two-stage so we don't try to create an auth before
# the chart's `influx setup` has finished.
- name: wait-for-influxd
image: influxdb:2.7.4
env:
- { name: INFLUX_HOST, value: http://influxdb-influxdb2.influxdb.svc.cluster.local:8086 }
- { name: INFLUX_ORG, value: renner }
- name: INFLUX_TOKEN
valueFrom:
secretKeyRef: { name: influxdb-bootstrap, key: admin-token }
command:
- sh
- -c
- |
set -eu
until influx ping >/dev/null 2>&1; do
echo "[$(date -u +%H:%M:%S)] waiting for $INFLUX_HOST..."
sleep 5
done
until influx bucket list >/dev/null 2>&1; do
echo "[$(date -u +%H:%M:%S)] API up; waiting for bootstrap admin token..."
sleep 5
done
echo "[$(date -u +%H:%M:%S)] $INFLUX_HOST ready and authenticated"
containers:
- name: create
image: influxdb:2.7.4
env:
- { name: INFLUX_HOST, value: http://influxdb-influxdb2.influxdb.svc.cluster.local:8086 }
- { name: INFLUX_ORG, value: renner }
- { name: INFLUX_BUCKET, value: suedstr28 }
- { name: TOKEN_DESC, value: home-assistant write }
- { name: TARGET_NS, value: home-assistant }
- { name: TARGET_SECRET, value: ha-influxdb-token }
- name: INFLUX_TOKEN
valueFrom:
secretKeyRef: { name: influxdb-bootstrap, key: admin-token }
command:
- bash
- -c
- |
set -euo pipefail
log() { printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*"; }
# Need jq + kubectl. influxdb:2.7.4 has neither baked in.
apt-get update -qq
apt-get install -y --no-install-recommends curl jq ca-certificates >/dev/null
KV=$(curl -fsSL https://dl.k8s.io/release/stable.txt)
curl -fsSL "https://dl.k8s.io/release/$KV/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl
chmod +x /usr/local/bin/kubectl
# Resolve bucket id (write-bucket auth needs the id, not
# the name).
BUCKET_ID=$(influx bucket list --name "$INFLUX_BUCKET" --json | jq -r '.[0].id')
[ -n "$BUCKET_ID" ] && [ "$BUCKET_ID" != "null" ] || {
log "ERROR: bucket $INFLUX_BUCKET not found"
exit 1
}
log "bucket $INFLUX_BUCKET id=$BUCKET_ID"
# Idempotency: reuse an existing matching auth if one is
# already on the server (e.g. left over from a previous
# Job run within the same cluster lifetime).
EXISTING=$(influx auth list --json \
| jq -r --arg d "$TOKEN_DESC" '.[] | select(.description == $d) | .token' \
| head -1)
if [ -n "$EXISTING" ]; then
TOKEN="$EXISTING"
log "reusing existing auth ($TOKEN_DESC)"
else
TOKEN=$(influx auth create \
--org "$INFLUX_ORG" \
--description "$TOKEN_DESC" \
--write-bucket "$BUCKET_ID" \
--json | jq -r '.token')
[ -n "$TOKEN" ] && [ "$TOKEN" != "null" ] || {
log "ERROR: influx auth create produced empty token"
exit 1
}
log "created new auth ($TOKEN_DESC)"
fi
log "writing $TARGET_NS/$TARGET_SECRET via apply"
kubectl -n "$TARGET_NS" create secret generic "$TARGET_SECRET" \
--from-literal=INFLUX_TOKEN="$TOKEN" \
--dry-run=client -o yaml \
| kubectl apply -f -
log "Done."
@@ -6,3 +6,4 @@ resources:
- staging-pvc.yaml
- legacy-token.sops.yaml
- seed-job.yaml
- ha-token-job.yaml