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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user