Deploy NetBox (CNPG Postgres, Valkey, Authentik SSO) (#299)

## Why
NetBox becomes the source of truth for host/interface/IPAM data as part of retiring Cobbler. The ENC role already moved to encapi; a kickstart-generation microservice that consumes NetBox comes later and is intentionally **out of scope** here.

## Change
Adds the `netbox` app (namespace `netbox`, platform project) using the netbox-community helm chart via the OCI helm-through-kustomize pattern, plus estate-native data stores:

- **NetBox** — chart `8.3.40` (appVersion `v4.6.5`), `oci://ghcr.io/netbox-community/netbox-chart`. 2 web replicas, 1 worker; bundled Postgres/Valkey subcharts disabled. Media on RWX CephFS so replicas share uploads. Chart `helm.sh/hook: test` Pod dropped via a kustomize delete patch (we deploy through ArgoCD, not `helm test`).
- **CNPG Postgres** — `netbox-postgres`, 2 instances, PG 18, `cephrbd-fast-delete`. Standard per-cluster S3 backup: `barmanObjectStore -> s3://cnpg-netbox`, cephrgw `ObjectStoreUser` + `Bucket`, nightly `ScheduledBackup` at **03:40** (`0 40 3 * * *` — next free slot after grafana's 03:20), 30d retention. A pgbouncer `Pooler` (session mode) fronts it; NetBox connects via `netbox-postgres-pooler-rw`.
- **Valkey** — standalone Deployment (`valkey/valkey:8-alpine`), AOF-persistent PVC on `cephrbd-fast-delete`. One instance: DB 0 = RQ task queue, DB 1 = cache. No auth (in-cluster, namespace-isolated). Chosen over the bundled Bitnami subchart to keep image control in-estate and avoid Bitnami's legacy-image churn; mirrors the litellm standalone-cache pattern.
- **Ingress** — `Gateway` + `HTTPRoute` at `netbox.k8s.syd1.au.unkin.net` (`traefik-internal`, `vault-issuer` cert into `netbox-tls`, external-dns to the internal VIP), HTTP->HTTPS 301.
- **Secrets** — all via VSO `VaultStaticSecret` (`postgres-credentials`, `netbox-secret-key`, `netbox-superuser`, `oauth-credentials`); no plain Secrets committed. The shared `default` k8s-auth role already binds `*` namespaces with a namespace-templated KV policy, so **no terraform-vault change is needed**.
- **Authentik OIDC SSO** — `remoteAuth` wires `REMOTE_AUTH_ENABLED` + the `OpenIdConnectAuth` backend via chart values; `SOCIAL_AUTH_OIDC_*` via `extraConfig` (the chart's config loader globs `/run/config/extra/*/*.yaml`). The client secret is injected as a YAML fragment mounted from the Vault-synced `oauth-credentials` secret. New users auto-provision on first login.
- Registers `netbox` in the platform ApplicationSet and AppProject (destination namespace + chart sourceRepo).

## Image table (source -> artifactapi -> DHI decision)
All images flow through the estate's containerd registry mirrors; the allowlist patterns gate them. DHI (Docker Hardened Images) require authenticated pulls from a Docker Hub `dhi/` org and are **not** reachable through the anonymous mirror, so upstream official is used throughout.

| Image | Upstream | Mirror / allowlist | Allowlisted? | DHI decision |
|---|---|---|---|---|
| NetBox app/worker/housekeeping | `ghcr.io/netbox-community/netbox:v4.6.5` | ghcr remote, `^netbox-community/` | **added in terraform-artifactapi #17** | No DHI published; upstream official |
| CNPG Postgres | `ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie` | ghcr, `^cloudnative-pg/` | already | estate-standard CNPG image |
| Valkey | `docker.io/valkey/valkey:8-alpine` | dockerhub, `^valkey/valkey` | already | DHI not anon-pullable; upstream official |
| NetBox init (perms) | `docker.io/busybox:1.38.0` | dockerhub, `^library/busybox` | already | upstream official |
| Worker wait-for-backend | `docker.io/rancher/kubectl:v1.36.2` | dockerhub, `^rancher/` | already | upstream official |

## Cross-repo PRs (merge order)
1. **terraform-artifactapi #17** (`^netbox-community/` ghcr allowlist) — **merge before** this PR so the NetBox image pulls on first sync.
2. **terraform-authentik #11** (OIDC provider/application) — independent; SSO works once applied + the secret is seeded.
3. This PR.

> Note: the CNPG S3-backup stanza pattern here is identical to PR #298 (already in main); it merges cleanly regardless of ordering.

## One-time Vault seeds (before/at first sync)
```
# App DB user (CNPG bootstrap + NetBox both consume this)
vault kv put kv/kubernetes/namespace/netbox/default/postgres-credentials \
  username=netbox password="$(openssl rand -base64 30)"

# Django SECRET_KEY
vault kv put kv/kubernetes/namespace/netbox/default/netbox-secret-key \
  secret_key="$(python3 -c 'import secrets;print(secrets.token_urlsafe(60))')"

# Bootstrap superuser
vault kv put kv/kubernetes/namespace/netbox/default/netbox-superuser \
  username=admin email=admin@unkin.net \
  password="$(openssl rand -base64 24)" api_token="$(openssl rand -hex 20)"

# OIDC client secret — ONE value stored two ways (raw for Authentik, YAML for NetBox)
CS="$(openssl rand -base64 30)"
vault kv put kv/kubernetes/namespace/netbox/default/oauth-credentials \
  client_secret="$CS" oidc.yaml="SOCIAL_AUTH_OIDC_SECRET: \"$CS\""
```
Seed `oauth-credentials` **before** applying terraform-authentik #11 (that apply reads `client_secret`).

## Validation
- `kustomize build --enable-helm apps/overlays/au-syd1/netbox` — clean.
- `kubeconform` (CI args, k8s 1.33.7): **27/27 valid, 0 invalid**.
- `pre-commit` (yamllint + no-plain-secrets) on all changed files — pass.

## Out of scope
The PXE/kickstart microservice that will consume NetBox — not scaffolded here.

https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
Reviewed-on: #299
Co-authored-by: Ben Vincent <ben@unkin.net>
Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #299.
This commit is contained in:
2026-07-28 18:12:25 +10:00
committed by BenVincent
parent 3c2bdf307a
commit 2ee4c9518c
16 changed files with 642 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
---
# Ceph RGW (S3) backup target for the netbox CNPG cluster, provisioned by the
# in-estate cephrgw-operator. One dedicated bucket + owner user per cluster:
# cephrgw CRs are namespace-scoped and CNPG reads its S3 credential Secret from
# its own namespace, so backups are per-database rather than one shared bucket.
apiVersion: ceph.unkin.net/v1alpha1
kind: ObjectStoreUser
metadata:
name: cnpg-netbox-backup
namespace: netbox
spec:
displayName: "CNPG backup owner (netbox)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-netbox-backup
maxBuckets: 5
# Operator writes AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (+ RGW_UID,
# S3_ENDPOINT) into this Secret; the Cluster's barmanObjectStore consumes it.
secretName: cnpg-netbox-backup-s3
# Keep the RGW user (and thus the keys) if this CR is ever deleted.
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-netbox
namespace: netbox
spec:
bucketName: cnpg-netbox
ownerRef: cnpg-netbox-backup
versioning: false
tags:
app: netbox
purpose: cnpg-backup
retainOnDelete: true
---
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-netbox-nightly
namespace: netbox
spec:
# 6-field CNPG cron (seconds first). 03:40 — next free slot after grafana
# (03:20), keeping the estate's 20-minute stagger.
schedule: "0 40 3 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: netbox-postgres
+87
View File
@@ -0,0 +1,87 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: netbox-postgres
namespace: netbox
spec:
affinity:
podAntiAffinityType: preferred
backup:
# 30-day retention (DEFAULT — adjust per cluster if needed). Enforced by CNPG
# against the object store on each successful base backup.
retentionPolicy: 30d
barmanObjectStore:
# Dedicated per-cluster Ceph RGW bucket (cephrgw-operator provisions it).
destinationPath: s3://cnpg-netbox
endpointURL: https://s3.ceph.unkin.net
# radosgw serves a Vault-PKI cert; trust the internal CA (reflected into
# every namespace as the vault-ca-cert Secret).
endpointCA:
name: vault-ca-cert
key: ca.crt
# Keys minted by the ObjectStoreUser in cnpg_backup.yaml; never hardcoded.
s3Credentials:
accessKeyId:
name: cnpg-netbox-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-netbox-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: netbox
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: netbox
encoding: UTF8
localeCType: C
localeCollate: C
owner: netbox
secret:
name: postgres-credentials
enablePDB: true
enableSuperuserAccess: false
failoverDelay: 0
imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie
instances: 2
logLevel: info
monitoring:
customQueriesConfigMap:
- key: queries
name: cnpg-default-monitoring
disableDefaultQueries: false
enablePodMonitor: false
postgresql:
parameters:
max_connections: "200"
shared_buffers: 128MB
primaryUpdateMethod: restart
primaryUpdateStrategy: unsupervised
replicationSlots:
highAvailability:
enabled: true
slotPrefix: _cnpg_
synchronizeReplicas:
enabled: true
updateInterval: 30
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: 100m
memory: 512Mi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
storage:
resizeInUseVolumes: true
size: 10Gi
storageClass: cephrbd-fast-delete
switchoverDelay: 3600
+34
View File
@@ -0,0 +1,34 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: netbox-postgres-pooler-rw
namespace: netbox
spec:
cluster:
name: netbox-postgres
instances: 2
pgbouncer:
parameters:
default_pool_size: "50"
max_client_conn: "200"
paused: false
# Session mode: NetBox/Django server-side cursors work unchanged.
poolMode: session
template:
metadata:
labels:
app: pooler-rw
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- pooler-rw
topologyKey: kubernetes.io/hostname
containers: []
type: rw
+39
View File
@@ -0,0 +1,39 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: netbox
namespace: netbox
labels:
app.kubernetes.io/name: netbox
app.kubernetes.io/instance: netbox
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: netbox.k8s.syd1.au.unkin.net
cert-manager.io/private-key-size: "4096"
external-dns.alpha.kubernetes.io/hostname: netbox.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
spec:
gatewayClassName: traefik-internal
listeners:
- name: http
port: 80
protocol: HTTP
hostname: netbox.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTPS
hostname: netbox.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: netbox-tls
+55
View File
@@ -0,0 +1,55 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: netbox-http-redirect
namespace: netbox
labels:
app.kubernetes.io/name: netbox
app.kubernetes.io/instance: netbox
spec:
hostnames:
- netbox.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: netbox
sectionName: http
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: netbox
namespace: netbox
labels:
app.kubernetes.io/name: netbox
app.kubernetes.io/instance: netbox
spec:
hostnames:
- netbox.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: netbox
sectionName: https
rules:
- backendRefs:
- group: ""
kind: Service
name: netbox
port: 80
weight: 1
matches:
- path:
type: PathPrefix
value: /
+16
View File
@@ -0,0 +1,16 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- cnpg_cluster.yaml
- cnpg_backup.yaml
- cnpg_pooler.yaml
- valkey-deployment.yaml
- valkey-pvc.yaml
- valkey-service.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- gateway.yaml
- httproute.yaml
+7
View File
@@ -0,0 +1,7 @@
---
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/name: netbox
name: netbox
+89
View File
@@ -0,0 +1,89 @@
---
# Standalone Valkey (Redis-compatible) for NetBox. The netbox-community chart
# bundles a Bitnami Valkey subchart, but we run our own here: it keeps image
# control in-estate (valkey/valkey, already allowlisted through the artifactapi
# dockerhub mirror), avoids the Bitnami "legacy image" churn, and matches the
# standalone-cache pattern used by litellm. One instance serves both NetBox
# databases: DB 0 = RQ task queue, DB 1 = cache. AOF persistence is enabled so
# queued background jobs survive a restart.
apiVersion: apps/v1
kind: Deployment
metadata:
name: netbox-valkey
namespace: netbox
labels:
app.kubernetes.io/name: netbox
app.kubernetes.io/component: valkey
spec:
replicas: 1
selector:
matchLabels:
app: netbox-valkey
strategy:
type: Recreate
template:
metadata:
labels:
app: netbox-valkey
app.kubernetes.io/name: netbox
app.kubernetes.io/component: valkey
spec:
securityContext:
fsGroup: 999
containers:
- name: valkey
image: valkey/valkey:8-alpine
imagePullPolicy: IfNotPresent
command:
- valkey-server
- --appendonly
- "yes"
- --save
- "60"
- "1"
ports:
- containerPort: 6379
name: valkey
protocol: TCP
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 999
capabilities:
drop:
- ALL
livenessProbe:
exec:
command:
- valkey-cli
- ping
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
exec:
command:
- valkey-cli
- ping
failureThreshold: 3
initialDelaySeconds: 5
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 128Mi
volumeMounts:
- mountPath: /data
name: data
restartPolicy: Always
volumes:
- name: data
persistentVolumeClaim:
claimName: netbox-valkey-data
+14
View File
@@ -0,0 +1,14 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: netbox-valkey-data
namespace: netbox
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: cephrbd-fast-delete
volumeMode: Filesystem
+20
View File
@@ -0,0 +1,20 @@
---
apiVersion: v1
kind: Service
metadata:
name: netbox-valkey
namespace: netbox
labels:
app.kubernetes.io/name: netbox
app.kubernetes.io/component: valkey
spec:
internalTrafficPolicy: Cluster
ports:
- name: valkey
port: 6379
protocol: TCP
targetPort: valkey
selector:
app: netbox-valkey
sessionAffinity: None
type: ClusterIP
+18
View File
@@ -0,0 +1,18 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: netbox
spec:
allowedNamespaces:
- netbox
kubernetes:
audiences:
- vault
role: default
serviceAccount: default
tokenExpirationSeconds: 600
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
+81
View File
@@ -0,0 +1,81 @@
---
# CNPG app-user credentials (keys: username, password). Consumed by the Cluster
# bootstrap (initdb.secret) AND by NetBox (externalDatabase.existingSecretName).
# One-time Vault seed — see the PR description.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: postgres-credentials
namespace: netbox
spec:
destination:
create: true
name: postgres-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/netbox/default/postgres-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Django SECRET_KEY (key: secret_key). One-time Vault seed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: netbox-secret-key
namespace: netbox
spec:
destination:
create: true
name: netbox-secret-key
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/netbox/default/netbox-secret-key
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Bootstrap superuser (keys: username, password, email, api_token). Applied by
# NetBox on first boot. One-time Vault seed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: netbox-superuser
namespace: netbox
spec:
destination:
create: true
name: netbox-superuser
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/netbox/default/netbox-superuser
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Authentik OIDC client secret. Two keys at the same Vault path:
# client_secret : raw secret, read by the terraform-authentik provider runner
# (policy already grants kv/.../namespace/+/default/oauth-credentials).
# oidc.yaml : a YAML fragment 'SOCIAL_AUTH_OIDC_SECRET: "<secret>"', mounted
# into NetBox via values.yaml extraConfig (the chart's config
# loader globs /run/config/extra/*/*.yaml).
# One-time Vault seed — see the PR description.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: oauth-credentials
namespace: netbox
spec:
destination:
create: true
name: oauth-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/netbox/default/oauth-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
@@ -0,0 +1,28 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base/netbox
helmCharts:
- name: netbox
repo: oci://ghcr.io/netbox-community/netbox-chart
version: "8.3.40"
releaseName: netbox
namespace: netbox
valuesFile: values.yaml
# The chart always renders a `helm.sh/hook: test` connection Pod. We deploy via
# kustomize+ArgoCD (not `helm test`), so drop it rather than leave an orphan Pod.
patches:
- target:
version: v1
kind: Pod
name: netbox-test-connection
patch: |-
$patch: delete
apiVersion: v1
kind: Pod
metadata:
name: netbox-test-connection
+101
View File
@@ -0,0 +1,101 @@
---
# NetBox (netbox-community chart 8.3.40, appVersion v4.6.5).
# Postgres = external CNPG cluster, Valkey = standalone Deployment, both defined
# in apps/base/netbox. Secrets come from Vault via VSO (see base/vaultstaticsecret.yaml).
image:
# ghcr.io is mirrored through artifactapi at the containerd level; the pull is
# gated by the ^netbox-community/ allowlist added in terraform-artifactapi.
# tag defaults to the chart appVersion (v4.6.5).
registry: ghcr.io
repository: netbox-community/netbox
# Two web replicas for zero-downtime rollouts (media is RWX, see below).
replicaCount: 2
# Django SECRET_KEY — existingSecret must contain key: secret_key.
existingSecret: netbox-secret-key
# Bootstrap superuser — existingSecret keys: username, password, email, api_token.
superuser:
existingSecret: netbox-superuser
# Disable the bundled Bitnami subcharts; we bring our own Postgres and Valkey.
postgresql:
enabled: false
valkey:
enabled: false
# External Postgres = the CNPG cluster's pgbouncer pooler (session mode).
externalDatabase:
host: netbox-postgres-pooler-rw
port: 5432
database: netbox
username: netbox
existingSecretName: postgres-credentials
existingSecretKey: password
disableServerSideCursors: false
# External Valkey — one instance, DB 0 = RQ task queue, DB 1 = cache. No auth
# (in-cluster, namespace-isolated).
tasksDatabase:
host: netbox-valkey
port: 6379
database: 0
cachingDatabase:
host: netbox-valkey
port: 6379
database: 1
# Uploaded media/attachments, shared across web replicas (RWX CephFS).
persistence:
enabled: true
storageClass: cephfs-raid6-delete
accessMode: ReadWriteMany
size: 5Gi
allowedHosts:
- netbox.k8s.syd1.au.unkin.net
serviceAccount:
create: true
automountServiceAccountToken: false
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: "1"
memory: 2Gi
worker:
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
# --- Authentik OIDC SSO via python-social-auth ---
# remoteAuth wires REMOTE_AUTH_ENABLED + REMOTE_AUTH_BACKEND; the SOCIAL_AUTH_*
# settings are supplied via extraConfig (YAML fragments the chart loads into the
# NetBox config). New OIDC users are auto-provisioned on first login.
remoteAuth:
enabled: true
backends:
- social_core.backends.open_id_connect.OpenIdConnectAuth
autoCreateUser: true
extraConfig:
# index 0 -> /run/config/extra/0/extra-0.yaml (non-secret OIDC config)
- values:
SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: https://identity.k8s.syd1.au.unkin.net/application/o/netbox/
SOCIAL_AUTH_OIDC_KEY: netbox
# index 1 -> /run/config/extra/1/oidc.yaml (client secret, from Vault via VSO)
- secret:
secretName: oauth-credentials
items:
- key: oidc.yaml
path: oidc.yaml
+1
View File
@@ -28,6 +28,7 @@ spec:
- path: apps/overlays/*/inteldeviceplugins-system
- path: apps/overlays/*/jfrog
- path: apps/overlays/*/kanidm
- path: apps/overlays/*/netbox
- path: apps/overlays/*/node-feature-discovery
- path: apps/overlays/*/pdbmux
- path: apps/overlays/*/priority-classes
+3
View File
@@ -12,6 +12,7 @@ spec:
- https://purelb.github.io/purelb/charts
- oci://gcr.io/k8s-staging-nfd/charts
- oci://ghcr.io/woodpecker-ci/helm/woodpecker
- oci://ghcr.io/netbox-community/netbox-chart/netbox
destinations:
- namespace: '*-system'
server: https://kubernetes.default.svc
@@ -37,6 +38,8 @@ spec:
server: https://kubernetes.default.svc
- namespace: 'kanidm'
server: https://kubernetes.default.svc
- namespace: 'netbox'
server: https://kubernetes.default.svc
- namespace: 'node-feature-discovery'
server: https://kubernetes.default.svc
- namespace: 'pdbmux'