Compare commits

..

2 Commits

Author SHA1 Message Date
Ben Vincent ffcf646d87 Rework jellyfin-ha into a true-HA StatefulSet deployment
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline failed
Turn the single-replica jellyfin-ha app into a proper high-availability
deployment so the fork's Redis-coordinated distributed transcoding and
PostgreSQL main database can actually be exercised.

- Replace the Deployment with a 2-replica StatefulSet for stable pod
  identity; set JELLYFIN_INSTANCE_ID from metadata.name (the fork's Redis
  transcode-lease owner id), add soft podAntiAffinity and a PDB
  minAvailable 1.
- Move the main Jellyfin DB to PostgreSQL via a CloudNativePG trio
  (3-instance Cluster, PgBouncer Pooler, Ceph RGW barman backups) mirroring
  the litellm pattern; an init container writes database.xml selecting the
  fork's Jellyfin-PostgreSQL provider and the DSN is composed from the
  CNPG-generated app secret pointed at the pooler.
- Share /config on an RWX cephfs PVC across replicas; keep /cache per-pod
  via a volumeClaimTemplate.
- Fix the transcode mount to the fork's real path /config/transcodes on the
  RWX PVC (raid5) so a surviving pod can resume the segments of the pod it
  takes over.
- Add Intel iGPU hardware transcoding via the gpu.intel.com/i915 device
  plugin resource plus render/video supplemental groups.
- Switch the Service to sessionAffinity ClientIP to reduce transcode churn.
- Disable UDP auto-discovery. Library scans still run on every replica; a
  single-scanner leader election is a planned follow-up.
2026-08-10 23:27:36 +10:00
Ben Vin a29fa5b8cc Add jellyfin (HA fork) app under a new media project
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/kubeconform Pipeline was successful
Deploys the jellyfin-ha fork (git.unkin.net/unkin/jellyfin-ha) to au-syd1
via ArgoCD, under a dedicated media AppProject/ApplicationSet rather than
extending platform.

- New media AppProject + media-apps ApplicationSet (watches
  apps/overlays/*/jellyfin); registered in the argocd kustomizations
- apps/base/jellyfin: namespace, deployment (single replica to start),
  service, in-namespace Redis (transcode session store), gateway + httproute
  at jellyfin.k8s.syd1.au.unkin.net
- Storage: RWO config (cephrbd), RWX transcode scratch and RWX media
  library (cephfs) per the HA fork's pod-takeover requirement
- au-syd1 overlay
2026-07-05 22:32:24 +10:00
269 changed files with 850 additions and 12225 deletions
-29
View File
@@ -1,29 +0,0 @@
when:
- event: pull_request
steps:
- name: vector-test
image: artifactapi.k8s.syd1.au.unkin.net/dockerhub/timberio/vector:0.57.0-debian
commands:
# Dummy creds + writable dirs so the full topologies build; the unit tests
# only exercise the transforms (sources are not started).
- export CLICKHOUSE_USER=ci CLICKHOUSE_PASSWORD=ci
- export NATS_PRODUCER_PASSWORD=ci NATS_CONSUMER_PASSWORD=ci
- mkdir -p /vector-data-dir /etc/vault-ca
- cp /etc/ssl/certs/ca-certificates.crt /etc/vault-ca/ca.crt
# Transform tier + VM ingest: unit-tested transforms.
- vector test apps/base/logging/vector/aggregator.yaml apps/base/logging/vector/aggregator-tests.yaml
- vector test apps/base/logging/vector/vm-ingest.yaml apps/base/logging/vector/vm-ingest-tests.yaml
# Agent has no transforms to unit-test; validate it builds. (The archiver
# leg is now the logarchiver service, not a Vector pipeline.)
- vector validate --no-environment apps/base/logging/vector/agent.yaml
backend_options:
kubernetes:
serviceAccountName: default
resources:
requests:
memory: 256Mi
cpu: 250m
limits:
memory: 1Gi
cpu: 1
+2 -2
View File
@@ -12,13 +12,13 @@ spec:
template:
metadata:
annotations:
configmap.reloader.stakater.com/auto: "true"
reloader.stakater.com/auto: "true"
labels:
app: age-api
spec:
containers:
- name: age-api
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/age-api:v0.1.0
image: git.unkin.net/unkin/age-api:v0.1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
-1
View File
@@ -8,7 +8,6 @@ resources:
- httproute.yaml
- namespace.yaml
- service.yaml
- vpa.yaml
configMapGenerator:
- name: age-api-config
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: age-api-vpa
namespace: age-api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: age-api
updatePolicy:
updateMode: "Off"
+2 -3
View File
@@ -5,8 +5,7 @@ metadata:
name: api
namespace: artifactapi
annotations:
configmap.reloader.stakater.com/auto: "true"
secret.reloader.stakater.com/reload: "vault-ca-cert"
reloader.stakater.com/auto: "true"
spec:
selector:
matchLabels:
@@ -36,7 +35,7 @@ spec:
mountPath: /combined-certs
containers:
- name: api
image: git.unkin.net/unkin/artifactapi:v3.7.7
image: git.unkin.net/unkin/artifactapi:v3.7.6
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
-56
View File
@@ -1,56 +0,0 @@
---
# Ceph RGW (S3) backup target for the artifactapi 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-artifactapi-backup
namespace: artifactapi
spec:
displayName: "CNPG backup owner (artifactapi)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-artifactapi-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-artifactapi-backup-s3
# Keep the RGW user (and thus the keys) if this CR is ever deleted, so an
# in-flight restore can still reach the archive.
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-artifactapi
namespace: artifactapi
spec:
placementTarget: ec
bucketName: cnpg-artifactapi
# The owner user has full control of its own bucket (read + write), which is
# all the backup/restore identity needs — no extra BucketAccess grant.
ownerRef: cnpg-artifactapi-backup
versioning: false
tags:
app: artifactapi
purpose: cnpg-backup
# Never drop the backups if the CR is removed; retire buckets by hand.
retainOnDelete: true
---
# Nightly base backup. Continuous WAL archiving is always-on via the Cluster's
# spec.backup.barmanObjectStore; this schedules the periodic full backup that
# WAL is layered on top of. Schedules are staggered across clusters so the 8
# base backups do not hit RGW at once (CNPG cron is 6-field, seconds first).
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-artifactapi-nightly
namespace: artifactapi
spec:
schedule: "0 40 1 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: postgres
+1 -30
View File
@@ -7,35 +7,6 @@ metadata:
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-artifactapi
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-artifactapi-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-artifactapi-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: artifactapi
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: artifacts
@@ -108,7 +79,7 @@ spec:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
cpu: 250m
memory: 256Mi
smartShutdownTimeout: 180
startDelay: 3600
+24 -6
View File
@@ -1,6 +1,30 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-redirect
namespace: artifactapi
spec:
hostnames:
- artifactapi.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: artifactapi
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: api-route
namespace: artifactapi
@@ -8,12 +32,6 @@ spec:
hostnames:
- artifactapi.k8s.syd1.au.unkin.net
parentRefs:
# Early-boot clients (anaconda/kickstart, yum in %post, PXE) need plain HTTP
# for the rpm repos; serve the app directly on port 80 instead of redirecting.
- group: gateway.networking.k8s.io
kind: Gateway
name: artifactapi
sectionName: http
- group: gateway.networking.k8s.io
kind: Gateway
name: artifactapi
-2
View File
@@ -7,7 +7,6 @@ resources:
- api-hpa.yaml
- configmap.yaml
- cnpg_cluster.yaml
- cnpg_backup.yaml
- cnpg_pooler.yaml
- gateway.yaml
- httproute.yaml
@@ -18,4 +17,3 @@ resources:
- ui-hpa.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- vpa.yaml
+2 -2
View File
@@ -5,7 +5,7 @@ metadata:
name: ui
namespace: artifactapi
annotations:
configmap.reloader.stakater.com/auto: "true"
reloader.stakater.com/auto: "true"
spec:
selector:
matchLabels:
@@ -22,7 +22,7 @@ spec:
automountServiceAccountToken: true
containers:
- name: ui
image: git.unkin.net/unkin/artifactapi-ui:v3.7.7
image: git.unkin.net/unkin/artifactapi-ui:v3.7.6
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
-45
View File
@@ -1,45 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
namespace: artifactapi
# NOTE: this workload also has an HPA. updateMode Off is recommendation-only
# and does not act, so there is no HPA/VPA conflict today. Do not flip to Auto/
# Initial without first moving the HPA off CPU/memory (VPA owns those under Auto).
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api
updatePolicy:
updateMode: "Off"
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: redis-vpa
namespace: artifactapi
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: redis
updatePolicy:
updateMode: "Off"
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: ui-vpa
namespace: artifactapi
# NOTE: this workload also has an HPA. updateMode Off is recommendation-only
# and does not act, so there is no HPA/VPA conflict today. Do not flip to Auto/
# Initial without first moving the HPA off CPU/memory (VPA owns those under Auto).
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: ui
updatePolicy:
updateMode: "Off"
-56
View File
@@ -1,56 +0,0 @@
---
# Ceph RGW (S3) backup target for the authentik 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-authentik-backup
namespace: authentik
spec:
displayName: "CNPG backup owner (authentik)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-authentik-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-authentik-backup-s3
# Keep the RGW user (and thus the keys) if this CR is ever deleted, so an
# in-flight restore can still reach the archive.
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-authentik
namespace: authentik
spec:
placementTarget: ec
bucketName: cnpg-authentik
# The owner user has full control of its own bucket (read + write), which is
# all the backup/restore identity needs — no extra BucketAccess grant.
ownerRef: cnpg-authentik-backup
versioning: false
tags:
app: authentik
purpose: cnpg-backup
# Never drop the backups if the CR is removed; retire buckets by hand.
retainOnDelete: true
---
# Nightly base backup. Continuous WAL archiving is always-on via the Cluster's
# spec.backup.barmanObjectStore; this schedules the periodic full backup that
# WAL is layered on top of. Schedules are staggered across clusters so the 8
# base backups do not hit RGW at once (CNPG cron is 6-field, seconds first).
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-authentik-nightly
namespace: authentik
spec:
schedule: "0 0 1 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: postgres
+3 -34
View File
@@ -7,35 +7,6 @@ metadata:
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-authentik
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-authentik-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-authentik-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: authentik
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: authentik
@@ -106,12 +77,10 @@ spec:
resources:
limits:
cpu: 500m
# 512Mi OOMKilled replicas under load (shared_buffers 128MB +
# max_connections 200 leave no headroom) — see incident 2026-07-28.
memory: 1Gi
requests:
cpu: 50m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
-2
View File
@@ -4,7 +4,6 @@ kind: Kustomization
resources:
- cnpg_cluster.yaml
- cnpg_backup.yaml
- cnpg_pooler.yaml
- gateway.yaml
- httproute.yaml
@@ -18,4 +17,3 @@ resources:
- redis-service.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- vpa.yaml
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: redis-vpa
namespace: authentik
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: redis
updatePolicy:
updateMode: "Off"
@@ -1,16 +0,0 @@
---
# Confines the agent-dns service account (in bind-system) to the agent-dns
# ClusterRole within this namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-dns
namespace: bind-external
subjects:
- kind: ServiceAccount
name: agent-dns
namespace: bind-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: agent-dns
-52
View File
@@ -1,52 +0,0 @@
---
# Externally-reachable authoritative BIND for zones we delegate to ourselves.
# First tenant: acme.unkin.net, the DNS-01 challenge zone Let's Encrypt validates
# via a one-time _acme-challenge.unkin.net CNAME. Authoritative-only, recursion
# off, no forwarding, no open transfers -- the primaryService is the single
# dmz-pinned LoadBalancer that public NAT targets and that cert-manager writes to.
apiVersion: bind.unkin.net/v1alpha1
kind: BindCluster
metadata:
name: bind-external
namespace: bind-external
spec:
mode: authoritative
recursion: false
replicas: 2
storageClassName: cephrbd-fast-delete
storageSize: 1Gi
# Public server: answer queries from anywhere (Let's Encrypt validates over the
# internet), deny recursion and open zone transfers. localhost + pod net are
# implied by "any" and cover in-pod nsupdate and secondary SOA refresh; per-zone
# allow-transfer (catalog + acme zone) still permits key-authenticated AXFR.
extraOptions:
- "allow-query { any; }"
- "allow-transfer { none; }"
service:
type: ClusterIP
primaryService:
type: LoadBalancer
externalTrafficPolicy: Local
annotations:
purelb.io/service-group: dmz
purelb.io/addresses: 198.18.199.53
external-dns.alpha.kubernetes.io/hostname: bind-external-primary.k8s.syd1.au.unkin.net
resources:
requests:
cpu: 20m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
---
# Catalog zone so the acme zone replicates onto the secondary (AXFR/IXFR keyed
# with the certmanager TSIG key, reused here as the transfer key).
apiVersion: bind.unkin.net/v1alpha1
kind: BindCatalogZone
metadata:
name: bind-external-catalog
namespace: bind-external
spec:
clusterRef: bind-external
zoneName: catalog.external
transferKeyRef: certmanager
@@ -1,10 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- cluster.yaml
- tsigkey.yaml
- zones.yaml
- agent-dns-rolebinding.yaml
-5
View File
@@ -1,5 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: bind-external
-20
View File
@@ -1,20 +0,0 @@
---
# TSIG key cert-manager uses to send RFC2136 dynamic updates (the DNS-01 TXT
# records) to the primary, and that the secondary reuses for AXFR. The operator
# generates the material into Secret certmanager-tsig in this namespace;
# secretTemplate stamps emberstack reflector hints so the Secret is mirrored into
# the cert-manager namespace, where the rfc2136 solver reads its "secret" key.
apiVersion: bind.unkin.net/v1alpha1
kind: BindTSIGKey
metadata:
name: certmanager
namespace: bind-external
spec:
clusterRef: bind-external
algorithm: hmac-sha256
secretTemplate:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "cert-manager"
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "cert-manager"
-19
View File
@@ -1,19 +0,0 @@
---
# Self-delegated ACME challenge zone. Google Cloud DNS holds a one-time
# _acme-challenge.unkin.net CNAME -> _acme-challenge.acme.unkin.net and an
# acme.unkin.net NS delegation pointing here; cert-manager writes the challenge
# TXT records via RFC2136 authenticated with the certmanager key.
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
metadata:
name: acme-unkin-net
namespace: bind-external
spec:
clusterRef: bind-external
zoneName: acme.unkin.net
type: primary
defaultTTL: 60
dynamicUpdate: true
updateKeyRef: certmanager
allowTransfer:
- key certmanager
@@ -1,16 +0,0 @@
---
# Confines the agent-dns service account (in bind-system) to the agent-dns
# ClusterRole within this namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-dns
namespace: bind-internal
subjects:
- kind: ServiceAccount
name: agent-dns
namespace: bind-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: agent-dns
@@ -24,6 +24,3 @@ spec:
- 198.18.27.0/24
- 198.18.28.0/24
- 198.18.29.0/24
# Admin/management access (individual hosts, not whole subnets)
- 10.10.12.200/32 # benvin workstation (wireguard)
- 198.18.21.160/32 # benvin router
@@ -13,14 +13,9 @@ spec:
storageSize: 2Gi
# Restrict queries to internal networks (puppet acl-main.unkin.net).
# 10.42.0.0/16 (pod net) is required so secondaries can SOA-refresh
# from the primary during catalog replication. localhost is required so the
# operator's in-pod `nsupdate` (sent to 127.0.0.1) passes query-authorization;
# without it every dynamic update is "denied due to allow-query".
# from the primary during catalog replication.
extraOptions:
- "allow-query { localhost; auth-acl-main; 10.42.0.0/16; }"
# Enable query logging for the Tier-2 vector bind_query pipeline (see the
# resolvers cluster for the routing rationale).
- "querylog yes"
- "allow-query { auth-acl-main; 10.42.0.0/16; }"
service:
type: LoadBalancer
externalTrafficPolicy: Local
@@ -36,7 +31,7 @@ spec:
external-dns.alpha.kubernetes.io/hostname: bind-authoritative-primary.k8s.syd1.au.unkin.net
resources:
requests:
cpu: 20m
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
@@ -6,5 +6,4 @@ resources:
- cluster.yaml
- tsigkey.yaml
- zones.yaml
- records.yaml
- acls.yaml
@@ -1,90 +0,0 @@
# Individually-managed authoritative records for the unkin.net zone.
# DNSRecords must live in the same namespace as their BindZone (the operator
# resolves zoneRef/clusterRef/updateKeyRef within the record's namespace), so
# these sit alongside the zone in bind-internal, not in the app namespace.
---
apiVersion: bind.unkin.net/v1alpha1
kind: DNSRecord
metadata:
# "internal" in the name distinguishes this from the external DNS that
# Authentik will manage its own records from later.
name: identity-dns-internal
namespace: bind-internal
spec:
zoneRef: unkin-net
name: identity
type: A
ttl: 600
values:
# traefik-internal gateway VIP; the authentik Gateway serves the
# identity.unkin.net hostname there.
- 198.18.200.4
---
# PRODUCTION CUTOVER RECORD — intentionally commented out.
# git.unkin.net currently resolves to the LIVE VM forge (HAProxy VRRP VIP
# 198.18.19.17), which holds every repo the estate depends on. Uncommenting this
# repoints the whole org's git.unkin.net at the new k8s Gitea gateway VIP, so it
# is the FINAL step of the forge migration — gated on the data migration (gitea
# dump/restore + SECRET_KEY copy) in argocd-apps docs/gitea-migration.md.
# NOTE: the live git.unkin.net answer is served by the puppet DNS master today
# (profiles::dns::master, records from PuppetDB); this k8s apex zone holds only
# SOA+NS + a few DNSRecords so far. Confirm the k8s bind cluster is the live
# authority for unkin.net (or update the puppet record instead) before relying
# on this CR at cutover.
# ---
# apiVersion: bind.unkin.net/v1alpha1
# kind: DNSRecord
# metadata:
# name: git-dns-internal
# namespace: bind-internal
# spec:
# zoneRef: unkin-net
# name: git
# type: A
# ttl: 600
# values:
# # traefik-internal gateway VIP; the gitea Gateway serves git.unkin.net there.
# - 198.18.200.4
---
apiVersion: bind.unkin.net/v1alpha1
kind: DNSRecord
metadata:
name: s3-ceph-cname
namespace: bind-internal
spec:
zoneRef: ceph-unkin-net
name: s3
type: CNAME
ttl: 600
values:
# radosgw S3 endpoint. Points at the Consul service for now; the real
# target will be changed later.
- radosgw.service.consul.
---
apiVersion: bind.unkin.net/v1alpha1
kind: DNSRecord
metadata:
name: dashboard-ceph-cname
namespace: bind-internal
spec:
zoneRef: ceph-unkin-net
name: dashboard
type: CNAME
ttl: 600
values:
# Ceph mgr dashboard, reached via lb1. Lets in-cluster clients (the
# cephrgw-operator) resolve dashboard.ceph.unkin.net.
- lb1.unkin.net.
---
apiVersion: bind.unkin.net/v1alpha1
kind: DNSRecord
metadata:
name: lb1-unkin-net
namespace: bind-internal
spec:
zoneRef: unkin-net
name: lb1
type: A
ttl: 600
values:
- 103.216.191.185
@@ -9,16 +9,3 @@ metadata:
spec:
clusterRef: bind-authoritative
algorithm: hmac-sha256
---
# Client-update key: puppet clients (profiles::dns::updater) nsupdate their own
# records to the authoritative zones with this key. Operator generates the
# material into Secret client-update-tsig; the same value must reach puppet
# eyaml (or the planned Vault-sync bridge) for clients to authenticate.
apiVersion: bind.unkin.net/v1alpha1
kind: BindTSIGKey
metadata:
name: client-update
namespace: bind-internal
spec:
clusterRef: bind-authoritative
algorithm: hmac-sha256
@@ -15,8 +15,6 @@ spec:
zoneName: unkin.net
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -28,24 +26,6 @@ spec:
zoneName: main.unkin.net
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
# ceph.unkin.net: the ceph host (ausyd1nxvm2069/halb) publishes
# dashboard.ceph.unkin.net via nsupdate; puppet targets a dedicated
# `zone ceph.unkin.net.`, so it must exist here or the update gets NOTZONE.
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
metadata:
name: ceph-unkin-net
namespace: bind-internal
spec:
clusterRef: bind-authoritative
zoneName: ceph.unkin.net
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -57,8 +37,6 @@ spec:
zoneName: 13.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -70,8 +48,6 @@ spec:
zoneName: 14.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -83,8 +59,6 @@ spec:
zoneName: 15.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -96,8 +70,6 @@ spec:
zoneName: 16.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -109,8 +81,6 @@ spec:
zoneName: 17.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -122,8 +92,6 @@ spec:
zoneName: 19.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -135,8 +103,6 @@ spec:
zoneName: 20.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -148,8 +114,6 @@ spec:
zoneName: 21.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -161,8 +125,6 @@ spec:
zoneName: 22.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -174,8 +136,6 @@ spec:
zoneName: 23.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -187,8 +147,6 @@ spec:
zoneName: 24.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -200,8 +158,6 @@ spec:
zoneName: 25.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -213,8 +169,6 @@ spec:
zoneName: 26.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -226,8 +180,6 @@ spec:
zoneName: 27.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -239,8 +191,6 @@ spec:
zoneName: 28.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
@@ -252,5 +202,3 @@ spec:
zoneName: 29.18.198.in-addr.arpa
type: primary
defaultTTL: 600
dynamicUpdate: true
updateKeyRef: client-update
@@ -23,7 +23,7 @@ spec:
type: ClusterIP
resources:
requests:
cpu: 20m
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
@@ -1,10 +1,6 @@
---
# Key that external-dns (and DNSRecord objects) use to send RFC2136 dynamic
# updates to the primary. The operator generates the material into a Secret
# (externaldns-key-tsig) in this namespace. secretTemplate stamps emberstack
# reflector hints onto that Secret so it is mirrored into the externaldns
# namespace, where the external-dns controller reads it -- guaranteeing
# external-dns presents exactly the key the primary's allow-update accepts.
# updates to the primary. The operator generates the material into a Secret.
apiVersion: bind.unkin.net/v1alpha1
kind: BindTSIGKey
metadata:
@@ -13,9 +9,3 @@ metadata:
spec:
clusterRef: bind-externaldns
algorithm: hmac-sha256
secretTemplate:
annotations:
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
reflector.v1.k8s.emberstack.com/reflection-allowed-namespaces: "externaldns"
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "externaldns"
@@ -7,5 +7,3 @@ resources:
- authoritative
- resolvers
- externaldns
- tsig-api
- agent-dns-rolebinding.yaml
@@ -8,15 +8,11 @@ metadata:
spec:
clusterRef: bind-resolvers
entries:
- 10.42.0.0/16 # k8s pod network (kube-proxy masquerades node-originated LB queries)
- 198.18.1.10/32
- 198.18.2.160/27
- 198.18.21.160/27
- 198.18.2.192/27
- 198.18.21.192/27
# Admin/management access
- 10.10.12.200/32 # benvin workstation (wireguard)
- 198.18.21.160/32 # benvin router (also within 198.18.21.160/27 above)
- 198.18.13.0/24
- 198.18.14.0/24
- 198.18.15.0/24
+1 -15
View File
@@ -21,23 +21,9 @@ spec:
forwarders:
- 8.8.8.8
- 1.1.1.1
# The internal split-horizon zones are served UNSIGNED by the in-cluster
# authoritative, but their public parents publish DS records (e.g. unkin.net
# is DNSSEC-signed on the Internet). With dnssec-validation on, the validator
# sees "parent indicates secure" but gets an insecure answer and returns
# SERVFAIL (broken trust chain). Treat the forwarded internal domains as
# insecure so they are not validated. unkin.net covers all *.unkin.net
# (incl. k8s.syd1.au.unkin.net); 18.198.in-addr.arpa covers every reverse zone.
extraOptions:
- "validate-except { unkin.net; 18.198.in-addr.arpa; consul; }"
# Enable query logging so the Tier-2 vector bind_query pipeline can parse
# client/qname/qtype. Routes to the `queries` category which, with no explicit
# logging{} clause, follows the default category to the named foreground
# stderr channel -> pod stdout -> vector (subject logs.k8s.bind-internal.*).
- "querylog yes"
resources:
requests:
cpu: 20m
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
@@ -1,9 +1,6 @@
# Conditional forward zones, from the puppet openforwarder view.
# Upstreams: unkin authoritative 198.18.200.6, consul 198.18.19.14,
# k8s 198.18.200.8 (in-cluster bind-externaldns VIP).
# k8s -> in-cluster bind-externaldns 198.18.200.8 for both the forward zone
# k8s.syd1.au.unkin.net and the reverse zone 200.18.198.in-addr.arpa, which
# external-dns now publishes to (see the external-dns migration PRs).
# Upstreams: unkin authoritative 198.18.200.6, consul 198.18.19.14, k8s 198.18.200.8.
# k8s -> in-cluster bind-externaldns 198.18.200.8.
# (Zones that forwarded to 10.10.16.x were dropped; consul left as-is.)
---
apiVersion: bind.unkin.net/v1alpha1
@@ -64,22 +61,6 @@ spec:
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
metadata:
name: fwd-200-18-198-in-addr-arpa
namespace: bind-internal
spec:
clusterRef: bind-resolvers
viewRef: openforwarder
zoneName: 200.18.198.in-addr.arpa
type: forward
catalog: false
forwarders:
# Reverse zone for the k8s LB range, published by external-dns to the
# in-cluster bind-externaldns alongside k8s.syd1.au.unkin.net.
- 198.18.200.8
---
apiVersion: bind.unkin.net/v1alpha1
kind: BindZone
metadata:
name: fwd-13-18-198-in-addr-arpa
namespace: bind-internal
@@ -1,27 +0,0 @@
---
# Companion TSIG API. The operator reconciles this into a Deployment, Service,
# ConfigMap, master-token Secret and namespaced RBAC. vault-plugin-secrets-bind-tsig
# calls it to create/rotate/delete TSIG keys, which it does by managing
# BindTSIGKey resources in this namespace (the operator reconciles the material).
#
# The master access token Secret (bind-tsig-api-token) is generated by the
# operator if absent; a VaultStaticSecret may later pre-seed/overwrite it so the
# token is sourced from Vault rather than generated in-cluster.
apiVersion: bind.unkin.net/v1alpha1
kind: BindTSIGAPI
metadata:
name: bind-tsig-api
namespace: bind-internal
spec:
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/bind-tsig-api:v0.2.3
replicas: 1
port: 8443
# targetNamespace defaults to this resource's namespace (bind-internal), where
# the authoritative cluster and its keys live.
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
cpu: 250m
memory: 128Mi
-38
View File
@@ -1,38 +0,0 @@
---
# Static service account that Vault's kubernetes secret engine mints scoped
# tokens for (agent-dns role). RBAC is confined to the bind namespaces via the
# per-namespace RoleBindings below, not a ClusterRoleBinding.
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-dns
namespace: bind-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: agent-dns
rules:
- apiGroups: ["bind.unkin.net"]
resources: ["*"]
verbs: ["*"]
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-dns
namespace: bind-system
subjects:
- kind: ServiceAccount
name: agent-dns
namespace: bind-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: agent-dns
+1 -1
View File
@@ -21,7 +21,7 @@ spec:
runAsNonRoot: true
containers:
- name: operator
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/bind-operator:v0.2.6
image: git.unkin.net/unkin/bind-operator:v0.1.5
args:
- --metrics-bind-address=:8080
- --health-probe-bind-address=:8081
+1 -3
View File
@@ -6,8 +6,6 @@ resources:
- namespace.yaml
# CRDs are pulled from the bind-operator repo at the matching tag rather than
# vendored here, so they never drift from the operator.
- https://git.unkin.net/unkin/bind-operator/raw/tag/v0.2.6/config/crd/install.yaml
- https://git.unkin.net/unkin/bind-operator/raw/tag/v0.1.5/config/crd/install.yaml
- rbac.yaml
- agent-dns-rbac.yaml
- deployment.yaml
- vpa.yaml
+1 -9
View File
@@ -23,15 +23,7 @@ rules:
resources: ["pods/exec"]
verbs: ["create", "get"]
- apiGroups: ["apps"]
resources: ["statefulsets", "deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# The BindTSIGAPI reconciler deploys the companion API: a ServiceAccount plus
# a namespaced Role/RoleBinding granting it access to BindTSIGKey + Secrets.
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings"]
resources: ["statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["events"]
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: bind-operator-vpa
namespace: bind-system
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: bind-operator
updatePolicy:
updateMode: "Off"
-84
View File
@@ -1,84 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: cephrgw-operator
namespace: cephrgw-system
labels:
app.kubernetes.io/name: cephrgw-operator
annotations:
# Restart on internal CA rotation only; cephrgw-credentials is Vault-rotated
# (VSO) and deliberately excluded so routine key rotation causes no restart.
configmap.reloader.stakater.com/auto: "true"
secret.reloader.stakater.com/reload: "vault-ca-cert"
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: cephrgw-operator
template:
metadata:
labels:
app.kubernetes.io/name: cephrgw-operator
spec:
serviceAccountName: cephrgw-operator
securityContext:
runAsNonRoot: true
containers:
- name: operator
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/cephrgw-operator:v0.4.0
args:
- --metrics-bind-address=:8080
- --health-probe-bind-address=:8081
- --leader-elect
envFrom:
# Provides CEPH_RGW_ACCESS_KEY/SECRET_KEY and the endpoints
# (CEPH_RGW_ENDPOINT / CEPH_RGW_ADMIN_ENDPOINT), plus optional
# CEPH_RGW_REGION / CEPH_RGW_CA / CEPH_RGW_INSECURE. Rendered from
# Vault per docs/ceph-setup.md; not managed in GitOps.
- secretRef:
name: cephrgw-credentials
env:
# Trust the internal unkin.net (Vault PKI) CA so the operator can
# verify radosgw's TLS cert. vault-ca-cert is reflected into every
# namespace from the certificates namespace.
- name: CEPH_RGW_CA_FILE
value: /etc/vault-ca/ca.crt
volumeMounts:
- name: vault-ca-cert
mountPath: /etc/vault-ca/ca.crt
subPath: ca.crt
readOnly: true
ports:
- containerPort: 8080
name: metrics
- containerPort: 8081
name: health
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
volumes:
- name: vault-ca-cert
secret:
secretName: vault-ca-cert
@@ -1,14 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
# CRDs are pulled from the cephrgw-operator repo at the matching tag rather
# than vendored here, so they never drift from the operator.
- https://git.unkin.net/unkin/cephrgw-operator/raw/tag/v0.4.0/config/crd/install.yaml
- rbac.yaml
- deployment.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- vpa.yaml
-5
View File
@@ -1,5 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: cephrgw-system
-42
View File
@@ -1,42 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: cephrgw-operator
namespace: cephrgw-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cephrgw-operator
rules:
- apiGroups: ["ceph.unkin.net"]
resources: ["*"]
verbs: ["*"]
# The operator delivers RGW access/secret keys into Secrets.
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# v0.3.1 startup check reads its own CRDs to warn if they are stale/missing.
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: cephrgw-operator
subjects:
- kind: ServiceAccount
name: cephrgw-operator
namespace: cephrgw-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cephrgw-operator
-21
View File
@@ -1,21 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: cephrgw-system
spec:
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
allowedNamespaces:
- cephrgw-system
kubernetes:
# Shared "default" role: binds the namespace's default ServiceAccount and
# grants the templated kv/kubernetes/namespace/<ns>/<sa>/* read policy, so
# no per-app terraform-vault change is needed.
role: default
serviceAccount: default
audiences:
- vault
tokenExpirationSeconds: 600
@@ -1,30 +0,0 @@
---
# Renders the radosgw credentials from Vault into the cephrgw-credentials
# Secret the operator Deployment consumes via envFrom. The KV secret's keys
# (CEPH_RGW_ACCESS_KEY/SECRET_KEY, CEPH_RGW_ENDPOINT, optional
# CEPH_RGW_ADMIN_ENDPOINT/REGION/CA) are copied verbatim, so they land as the
# matching env vars.
#
# The path sits under the templated default policy
# (kv/data/kubernetes/namespace/<ns>/<sa>/*), so it needs no dedicated Vault
# role or policy. Seed the values with:
# vault kv put kv/kubernetes/namespace/cephrgw-system/default/cephrgw-credentials \
# CEPH_RGW_ENDPOINT=https://s3.ceph.unkin.net \
# CEPH_RGW_ADMIN_ENDPOINT=https://radosgw.service.consul:443 \
# CEPH_RGW_ACCESS_KEY=... CEPH_RGW_SECRET_KEY=...
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: cephrgw-credentials
namespace: cephrgw-system
spec:
vaultAuthRef: default
mount: kv
type: kv-v2
path: kubernetes/namespace/cephrgw-system/default/cephrgw-credentials
refreshAfter: 5m
hmacSecretData: true
destination:
name: cephrgw-credentials
create: true
overwrite: true
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: cephrgw-operator-vpa
namespace: cephrgw-system
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: cephrgw-operator
updatePolicy:
updateMode: "Off"
@@ -1,21 +0,0 @@
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@unkin.net
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- dns01:
cnameStrategy: Follow
rfc2136:
nameserver: "198.18.199.53:53"
tsigKeyName: certmanager
tsigAlgorithm: HMACSHA256
tsigSecretSecretRef:
name: certmanager-tsig
key: secret
@@ -1,21 +0,0 @@
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@unkin.net
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- dns01:
cnameStrategy: Follow
rfc2136:
nameserver: "198.18.199.53:53"
tsigKeyName: certmanager
tsigAlgorithm: HMACSHA256
tsigSecretSecretRef:
name: certmanager-tsig
key: secret
@@ -1,27 +0,0 @@
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: vault-issuer
labels:
app.kubernetes.io/instance: cert-manager-config
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: cert-manager-config
annotations:
meta.helm.sh/release-name: cert-manager-clusterissuer
meta.helm.sh/release-namespace: cert-manager
spec:
vault:
server: https://vault.service.consul:8200
path: pki_int/sign/servers_default
caBundleSecretRef:
key: ca.crt
name: vault-ca-cert
auth:
kubernetes:
mountPath: /v1/auth/k8s/au/syd1
role: cert_manager_issuer
serviceAccountRef:
name: cert-manager-vault-issuer
audiences:
- vault
@@ -7,7 +7,3 @@ resources:
- serviceaccount.yaml
- clusterrole.yaml
- clusterrolebinding.yaml
- clusterissuer_vault-issuer.yaml
- vmservicescrape.yaml
- clusterissuer_letsencrypt.yaml
- clusterissuer_letsencrypt-staging.yaml
@@ -1,17 +0,0 @@
---
# Scrape cert-manager webhook + cainjector metrics endpoints.
# Picked up by the observability VMAgent (selectAllByDefault).
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMServiceScrape
metadata:
name: cert-manager
namespace: cert-manager
spec:
selector:
matchLabels:
app.kubernetes.io/instance: cert-manager
endpoints:
- port: metrics
path: /metrics
- port: http-metrics
path: /metrics
+1 -3
View File
@@ -1,6 +1,4 @@
---
# pre-commit: allow-plain-secret -- public CA bundle; this secret bootstraps
# trust in Vault itself and therefore cannot be Vault-sourced.
apiVersion: v1
kind: Secret
metadata:
@@ -30,7 +28,7 @@ stringData:
mitItX+RAgMBAAGjgewwgekwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFEp/+grAdVqRSeb9xJjSeZYNW32MMB8GA1UdIwQYMBaAFBqc
v6Y+hfHt4EjgKa/uoQGEHTknMEcGCCsGAQUFBwEBBDswOTA3BggrBgEFBQcwAoYr
aHR0cHM6Ly92YXVsdC5zZXJ2dWNlLmNvbnN1bC92MS9wa2lfcm9vdC9jYTA9BgNV
aHR0cHM6Ly92YXVsdC5zZXJ2aWNlLmNvbnN1bC92MS9wa2lfcm9vdC9jYTA9BgNV
HR8ENjA0MDKgMKAuhixodHRwczovL3ZhdWx0LnNlcnZpY2UuY29uc3VsL3YxL3Br
aV9yb290L2NybDANBgkqhkiG9w0BAQsFAAOCAQEAM0FS8tscZe7yly/gM7jO6lx5
muMFusifjUIrcQGnZBkoECeuUVPNTs3e/Th+XaxjCnmSpqSNT3z9Irr6Hhxf7n03
@@ -1,7 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/name: clickhouse-system
name: clickhouse-system
-30
View File
@@ -1,30 +0,0 @@
# consul (k8s)
Consul server cluster (DC `au-syd1`), deployed via the HashiCorp helm chart with
ACLs enabled (`default_policy: deny`, parity with the VM cluster).
## API access (ACL auth)
The HTTP API and UI are served on port 8500 behind the gateway at
`https://consul.k8s.syd1.au.unkin.net` (and `https://consul.service.consul`).
With ACLs enabled, requests beyond the anonymous policy require a token:
```bash
# management (bootstrap) token — seeded from Vault, synced by VSO into the
# consul-bootstrap-acl-token secret; same value as the VM cluster's
# initial_management token:
CONSUL_HTTP_TOKEN=$(vault kv get -field=token kv/kubernetes/namespace/consul/default/bootstrap-acl-token)
curl -H "X-Consul-Token: $CONSUL_HTTP_TOKEN" https://consul.k8s.syd1.au.unkin.net/v1/status/leader
# consul CLI:
CONSUL_HTTP_ADDR=https://consul.k8s.syd1.au.unkin.net CONSUL_HTTP_TOKEN=$CONSUL_HTTP_TOKEN consul members
```
The UI at the same hostname exposes an ACL login (top right) — paste a token.
Anonymous requests get the anonymous-token policy only (reads for DNS/service
discovery; no writes, no ACL/token APIs).
Prefer short-lived tokens minted by Vault's consul secrets engine over the
management token for day-to-day use; the terraform-* CI roles already work this
way.
+4 -4
View File
@@ -46,8 +46,8 @@ spec:
- backendRefs:
- group: ""
kind: Service
name: consul-http
port: 8500
name: consul-ui
port: 80
weight: 1
matches:
- path:
@@ -74,8 +74,8 @@ spec:
- backendRefs:
- group: ""
kind: Service
name: consul-http
port: 8500
name: consul-ui
port: 80
weight: 1
matches:
- path:
-3
View File
@@ -6,6 +6,3 @@ resources:
- namespace.yaml
- gateway.yaml
- httproute.yaml
- service.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
-25
View File
@@ -1,25 +0,0 @@
---
# ClusterIP service targeting the consul server pods' HTTP API (8500).
# The HashiCorp chart only ships consul-ui (also 8500 via the server pods)
# and the headless consul-server; this named service gives the Gateway a
# stable API backend. Consul serves both the HTTP API and the UI (at /ui/)
# on this same port, so routing the API hostname here preserves the UI too.
apiVersion: v1
kind: Service
metadata:
name: consul-http
namespace: consul
labels:
app.kubernetes.io/name: consul
app.kubernetes.io/instance: consul
spec:
type: ClusterIP
selector:
app: consul
component: server
release: consul
ports:
- name: http
port: 8500
protocol: TCP
targetPort: 8500
-18
View File
@@ -1,18 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: consul
spec:
allowedNamespaces:
- consul
kubernetes:
audiences:
- vault
role: default
serviceAccount: default
tokenExpirationSeconds: 600
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
-17
View File
@@ -1,17 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: bootstrap-acl-token
namespace: consul
spec:
destination:
create: true
name: consul-bootstrap-acl-token
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/consul/default/bootstrap-acl-token
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
-1
View File
@@ -7,4 +7,3 @@ resources:
- vaultauth.yaml
- vaultstaticsecret.yaml
- storageclass.yaml
- vmservicescrape.yaml
-15
View File
@@ -1,15 +0,0 @@
---
# Scrape the ceph-csi-cephfs nodeplugin + provisioner http-metrics endpoints.
# Picked up by the observability VMAgent (selectAllByDefault).
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMServiceScrape
metadata:
name: ceph-csi-cephfs
namespace: csi-cephfs
spec:
selector:
matchLabels:
app: ceph-csi-cephfs
endpoints:
- port: http-metrics
path: /metrics
-1
View File
@@ -7,4 +7,3 @@ resources:
- vaultauth.yaml
- vaultstaticsecret.yaml
- storageclass.yaml
- vmservicescrape.yaml
@@ -1,15 +0,0 @@
---
# Scrape the ceph-csi-rbd nodeplugin + provisioner http-metrics endpoints.
# Picked up by the observability VMAgent (selectAllByDefault).
apiVersion: operator.victoriametrics.com/v1beta1
kind: VMServiceScrape
metadata:
name: ceph-csi-rbd
namespace: csi-cephrbd
spec:
selector:
matchLabels:
app: ceph-csi-rbd
endpoints:
- port: http-metrics
path: /metrics
-25
View File
@@ -1,25 +0,0 @@
---
# Terraform-friendly REST API for KeaSubnet/KeaClientClass CRUD. The bearer
# token Secret is generated by the operator when absent (no plain Secret is
# committed here); it can later be pre-seeded from Vault under the same name.
apiVersion: kea.unkin.net/v1alpha1
kind: KeaAPI
metadata:
name: kea-api
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 1
image: git.unkin.net/unkin/kea-api:v0.1.2
tokenSecretName: kea-api-token
service:
type: ClusterIP
port: 8080
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: "1"
memory: 256Mi
@@ -1,27 +0,0 @@
# PXE boot classes matching client architecture (option 93), replacing the
# legacy dhcpd "Legacy" and "UEFI-64" classes. Object names are lowercased to
# satisfy RFC1123 (the operator renders the kea class name from metadata.name).
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaClientClass
metadata:
name: legacy
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
archHex: ["0x0000"]
bootFileName: /undionly.kpxe
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaClientClass
metadata:
name: uefi-64
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
archHex: ["0x0007", "0x0009"]
bootFileName: /ipxe.efi
-35
View File
@@ -1,35 +0,0 @@
---
# HA pair fronted by a PureLB anycast Service on a NEW, unused common-pool IP
# (198.18.200.10). This is intentionally NOT the current isc-dhcpd anycast
# address (198.18.19.18) -- the production cutover is a separate later task.
apiVersion: kea.unkin.net/v1alpha1
kind: KeaCluster
metadata:
name: kea
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
replicas: 2
image: git.unkin.net/unkin/kea:v0.1.2
domainName: main.unkin.net
defaultLeaseTime: 1200
maxLeaseTime: 86400
# No ntpServers: DHCP option 42 (ntp-servers) carries IPv4 addresses only, so
# the rotating AU pool.ntp.org hostnames cannot be delivered this way (kea
# rejects them at config load). Add concrete NTP server IPs here if needed.
ha:
mode: hot-standby
service:
type: LoadBalancer
ipAddressPool: common
loadBalancerIP: 198.18.200.10
annotations:
purelb.io/addresses: 198.18.200.10
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
-90
View File
@@ -1,90 +0,0 @@
# Translation of the legacy ISC dhcpd pools (puppet
# roles/infra/dhcp/server.yaml): 198.18.13-17.0/24, each a .200-.220 pool,
# next-server 198.18.19.19. Gateways per the original config:
# .13/.14/.15/.16 -> .254, .17 -> .1. DNS points at the in-cluster
# bind-resolvers PureLB IP (198.18.200.7), not the legacy 198.18.19.15.
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaSubnet
metadata:
name: net-198-18-13
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
subnet: 198.18.13.0/24
pools:
- 198.18.13.200 - 198.18.13.220
routers: [198.18.13.254]
dnsServers: [198.18.200.7]
domainName: main.unkin.net
nextServer: 198.18.19.19
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaSubnet
metadata:
name: net-198-18-14
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
subnet: 198.18.14.0/24
pools:
- 198.18.14.200 - 198.18.14.220
routers: [198.18.14.254]
dnsServers: [198.18.200.7]
domainName: main.unkin.net
nextServer: 198.18.19.19
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaSubnet
metadata:
name: net-198-18-15
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
subnet: 198.18.15.0/24
pools:
- 198.18.15.200 - 198.18.15.220
routers: [198.18.15.254]
dnsServers: [198.18.200.7]
domainName: main.unkin.net
nextServer: 198.18.19.19
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaSubnet
metadata:
name: net-198-18-16
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
subnet: 198.18.16.0/24
pools:
- 198.18.16.200 - 198.18.16.220
routers: [198.18.16.254]
dnsServers: [198.18.200.7]
domainName: main.unkin.net
nextServer: 198.18.19.19
---
apiVersion: kea.unkin.net/v1alpha1
kind: KeaSubnet
metadata:
name: net-198-18-17
namespace: dhcp-system
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
clusterRef: kea
subnet: 198.18.17.0/24
pools:
- 198.18.17.200 - 198.18.17.220
routers: [198.18.17.1]
dnsServers: [198.18.200.7]
domainName: main.unkin.net
nextServer: 198.18.19.19
-56
View File
@@ -1,56 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kea-operator
namespace: dhcp-system
labels:
app.kubernetes.io/name: kea-operator
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: kea-operator
template:
metadata:
labels:
app.kubernetes.io/name: kea-operator
spec:
serviceAccountName: kea-operator
securityContext:
runAsNonRoot: true
containers:
- name: operator
image: git.unkin.net/unkin/kea-operator:v0.1.2
args:
- --metrics-bind-address=:8080
- --health-probe-bind-address=:8081
ports:
- containerPort: 8080
name: metrics
- containerPort: 8081
name: health
readinessProbe:
httpGet:
path: /readyz
port: 8081
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8081
initialDelaySeconds: 15
periodSeconds: 20
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
-17
View File
@@ -1,17 +0,0 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
# CRDs are pulled from the kea-operator repo at the matching tag rather than
# vendored here, so they never drift from the operator.
- https://git.unkin.net/unkin/kea-operator/raw/tag/v0.1.0/config/crd/install.yaml
- rbac.yaml
- deployment.yaml
- vpa.yaml
# CRs (sync-wave 1) reconcile after the operator + CRDs are established.
- cr/keacluster.yaml
- cr/keasubnets.yaml
- cr/keaclientclasses.yaml
- cr/keaapi.yaml
-5
View File
@@ -1,5 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: dhcp-system
-46
View File
@@ -1,46 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kea-operator
namespace: dhcp-system
---
# Sourced from the kea-operator repo config/rbac/role.yaml (v0.1.0). Leader
# election is disabled so no coordination.k8s.io/leases grant is needed.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kea-operator
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets", "serviceaccounts", "services"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: ["kea.unkin.net"]
resources: ["keaapis", "keaclientclasses", "keaclusters", "keasubnets"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
- apiGroups: ["kea.unkin.net"]
resources:
["keaapis/status", "keaclientclasses/status", "keaclusters/status", "keasubnets/status"]
verbs: ["get", "patch", "update"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "roles"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kea-operator
subjects:
- kind: ServiceAccount
name: kea-operator
namespace: dhcp-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kea-operator
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: kea-operator-vpa
namespace: dhcp-system
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: kea-operator
updatePolicy:
updateMode: "Off"
-56
View File
@@ -1,56 +0,0 @@
---
# Ceph RGW (S3) backup target for the encapi 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-encapi-backup
namespace: encapi
spec:
displayName: "CNPG backup owner (encapi)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-encapi-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-encapi-backup-s3
# Keep the RGW user (and thus the keys) if this CR is ever deleted, so an
# in-flight restore can still reach the archive.
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-encapi
namespace: encapi
spec:
placementTarget: ec
bucketName: cnpg-encapi
# The owner user has full control of its own bucket (read + write), which is
# all the backup/restore identity needs — no extra BucketAccess grant.
ownerRef: cnpg-encapi-backup
versioning: false
tags:
app: encapi
purpose: cnpg-backup
# Never drop the backups if the CR is removed; retire buckets by hand.
retainOnDelete: true
---
# Nightly base backup. Continuous WAL archiving is always-on via the Cluster's
# spec.backup.barmanObjectStore; this schedules the periodic full backup that
# WAL is layered on top of. Schedules are staggered across clusters so the 8
# base backups do not hit RGW at once (CNPG cron is 6-field, seconds first).
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-encapi-nightly
namespace: encapi
spec:
schedule: "0 40 2 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: postgres
+1 -30
View File
@@ -7,35 +7,6 @@ metadata:
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-encapi
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-encapi-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-encapi-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: encapi
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: encapi
@@ -108,7 +79,7 @@ spec:
cpu: 500m
memory: 512Mi
requests:
cpu: 50m
cpu: 250m
memory: 256Mi
smartShutdownTimeout: 180
startDelay: 3600
+2 -2
View File
@@ -5,7 +5,7 @@ metadata:
name: encapi
namespace: encapi
annotations:
configmap.reloader.stakater.com/auto: "true"
reloader.stakater.com/auto: "true"
spec:
replicas: 2
selector:
@@ -23,7 +23,7 @@ spec:
automountServiceAccountToken: true
containers:
- name: encapi
image: artifactapi.k8s.syd1.au.unkin.net/docker-internal/encapi:v0.1.1
image: git.unkin.net/unkin/encapi:v0.1.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
-2
View File
@@ -10,8 +10,6 @@ resources:
- gateway.yaml
- httproute.yaml
- cnpg_cluster.yaml
- cnpg_backup.yaml
- cnpg_pooler.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- vpa.yaml
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: encapi-vpa
namespace: encapi
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: encapi
updatePolicy:
updateMode: "Off"
@@ -1,16 +0,0 @@
---
# Confines the agent-dns service account (in bind-system) to the agent-dns
# ClusterRole within this namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: agent-dns
namespace: externaldns
subjects:
- kind: ServiceAccount
name: agent-dns
namespace: bind-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: agent-dns
-1
View File
@@ -6,4 +6,3 @@ resources:
- namespace.yaml
- vaultauth.yaml
- vaultstaticsecret.yaml
- agent-dns-rolebinding.yaml
-46
View File
@@ -1,46 +0,0 @@
---
# Ceph RGW (S3) backup target for the gitea CNPG cluster, provisioned by the
# in-estate cephrgw-operator. One dedicated bucket + owner user per cluster.
apiVersion: ceph.unkin.net/v1alpha1
kind: ObjectStoreUser
metadata:
name: cnpg-gitea-backup
namespace: gitea
spec:
displayName: "CNPG backup owner (gitea)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-gitea-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-gitea-backup-s3
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-gitea
namespace: gitea
spec:
bucketName: cnpg-gitea
ownerRef: cnpg-gitea-backup
versioning: false
tags:
app: gitea
purpose: cnpg-backup
retainOnDelete: true
---
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-gitea-nightly
namespace: gitea
spec:
# 6-field CNPG cron (seconds first). 04:00 — next free slot after netbox
# (03:40), keeping the estate's 20-minute stagger.
schedule: "0 0 4 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: gitea-postgres
-90
View File
@@ -1,90 +0,0 @@
---
# Postgres for the k8s Gitea (replaces the Patroni-shared DB the VM uses). Gitea
# already runs on Postgres, so cutover is a plain pg dump/restore (no engine
# conversion). App-user creds come from the postgres-credentials Vault secret.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: gitea-postgres
namespace: gitea
spec:
affinity:
podAntiAffinityType: preferred
backup:
# 30-day retention. 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-gitea
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-gitea-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-gitea-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: gitea
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: gitea
encoding: UTF8
localeCType: C
localeCollate: C
owner: gitea
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: 256MB
primaryUpdateMethod: restart
primaryUpdateStrategy: unsupervised
replicationSlots:
highAvailability:
enabled: true
slotPrefix: _cnpg_
synchronizeReplicas:
enabled: true
updateInterval: 30
resources:
limits:
cpu: "2"
memory: 2Gi
requests:
cpu: 250m
memory: 512Mi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
storage:
resizeInUseVolumes: true
size: 20Gi
storageClass: cephrbd-fast-delete
switchoverDelay: 3600
-36
View File
@@ -1,36 +0,0 @@
---
# pgbouncer in front of the primary. Gitea opens a connection per request and
# benefits from pooling under multiple app replicas. Session mode keeps Gitea's
# occasional session-scoped state (advisory locks, LISTEN/NOTIFY) working.
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: gitea-postgres-pooler-rw
namespace: gitea
spec:
cluster:
name: gitea-postgres
instances: 2
pgbouncer:
parameters:
default_pool_size: "50"
max_client_conn: "200"
paused: false
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
-69
View File
@@ -1,69 +0,0 @@
---
# HTTPS front for the k8s Gitea, served on two names:
# git.unkin.net — canonical/production (apex, bind-operator zone;
# DNS flip is the gated cutover step, see the doc)
# git.k8s.syd1.au.unkin.net — admin/backup route (external-dns k8s.syd1 zone),
# same dual-name pattern as identity.unkin.net.
# The cert-manager Certificate (vault-issuer) takes CN git.unkin.net and gets a
# DNS SAN for each TLS listener hostname automatically.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gitea
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/instance: gitea
traefik.io/instance: internal
annotations:
cert-manager.io/cluster-issuer: vault-issuer
cert-manager.io/common-name: git.unkin.net
cert-manager.io/private-key-size: "4096"
# Only the k8s admin route is published by external-dns (it owns just the
# k8s.syd1.au.unkin.net zone). git.unkin.net lives in the apex zone and is
# flipped at cutover — NOT managed here.
external-dns.alpha.kubernetes.io/hostname: git.k8s.syd1.au.unkin.net
external-dns.alpha.kubernetes.io/target: 198.18.200.4
spec:
gatewayClassName: traefik-internal
listeners:
- name: http-primary
port: 80
protocol: HTTP
hostname: git.unkin.net
allowedRoutes:
namespaces:
from: Same
- name: https-primary
port: 443
protocol: HTTPS
hostname: git.unkin.net
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: gitea-tls
- name: http-admin
port: 80
protocol: HTTP
hostname: git.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
- name: https-admin
port: 443
protocol: HTTPS
hostname: git.k8s.syd1.au.unkin.net
allowedRoutes:
namespaces:
from: Same
tls:
mode: Terminate
certificateRefs:
- group: ""
kind: Secret
name: gitea-tls
-65
View File
@@ -1,65 +0,0 @@
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitea-http-redirect
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/instance: gitea
spec:
hostnames:
- git.unkin.net
- git.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: gitea
sectionName: http-primary
- group: gateway.networking.k8s.io
kind: Gateway
name: gitea
sectionName: http-admin
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301
matches:
- path:
type: PathPrefix
value: /
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: gitea
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/instance: gitea
spec:
hostnames:
- git.unkin.net
- git.k8s.syd1.au.unkin.net
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: gitea
sectionName: https-primary
- group: gateway.networking.k8s.io
kind: Gateway
name: gitea
sectionName: https-admin
rules:
- backendRefs:
- group: ""
kind: Service
name: gitea-http
port: 3000
weight: 1
matches:
- path:
type: PathPrefix
value: /
-7
View File
@@ -1,7 +0,0 @@
---
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/name: gitea
name: gitea
-89
View File
@@ -1,89 +0,0 @@
---
# Standalone Valkey (Redis-compatible) for Gitea's session store, cache and
# queue. The Gitea chart bundles a redis-cluster subchart, but we run our own
# standalone Valkey here: it keeps image control in-estate (valkey/valkey,
# already allowlisted through the artifactapi dockerhub mirror) and matches the
# standalone-cache pattern used by litellm/netbox. One instance serves three
# logical DBs: DB 0 = session, DB 1 = cache, DB 2 = queue. AOF persistence is
# enabled so queued actions/webhook deliveries survive a restart.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitea-valkey
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/component: valkey
spec:
replicas: 1
selector:
matchLabels:
app: gitea-valkey
strategy:
type: Recreate
template:
metadata:
labels:
app: gitea-valkey
app.kubernetes.io/name: gitea
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: gitea-valkey-data
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: gitea-valkey
namespace: gitea
labels:
app.kubernetes.io/name: gitea
app.kubernetes.io/component: valkey
spec:
internalTrafficPolicy: Cluster
ports:
- name: valkey
port: 6379
protocol: TCP
targetPort: valkey
selector:
app: gitea-valkey
sessionAffinity: None
type: ClusterIP
-18
View File
@@ -1,18 +0,0 @@
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: default
namespace: gitea
spec:
allowedNamespaces:
- gitea
kubernetes:
audiences:
- vault
role: default
serviceAccount: default
tokenExpirationSeconds: 600
method: kubernetes
mount: k8s/au/syd1
vaultConnectionRef: vso-system/default
-83
View File
@@ -1,83 +0,0 @@
---
# CNPG app-user credentials (keys: username, password). Consumed by the Cluster
# bootstrap (initdb.secret) AND by Gitea (gitea.config.database.PASSWD via the
# chart's existingSecret wiring). One-time Vault seed — see the PR description.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: postgres-credentials
namespace: gitea
spec:
destination:
create: true
name: postgres-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/gitea/default/postgres-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Initial Gitea admin (keys: username, password, email). Applied by the chart's
# init job on first boot (gitea.admin.existingSecret). Local fallback account
# that survives the Authentik OIDC cutover. One-time Vault seed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-admin
namespace: gitea
spec:
destination:
create: true
name: gitea-admin
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/gitea/default/gitea-admin
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Gitea internal secrets (keys: SECRET_KEY, INTERNAL_TOKEN). Pinned here rather
# than chart-generated so all replicas share identical values AND so the data
# cutover can replace them with the VM's app.ini values (SECRET_KEY encrypts
# 2FA/mirror/oauth secrets in the DB — it MUST match the restored database).
# One-time Vault seed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: gitea-inner
namespace: gitea
spec:
destination:
create: true
name: gitea-inner
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/gitea/default/gitea-inner
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
---
# Authentik OIDC client secret (key: client_secret). Read by the
# terraform-authentik provider runner (policy already grants
# kv/.../namespace/+/default/oauth-credentials) AND mounted into Gitea to
# register the OIDC login source. One-time Vault seed.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: oauth-credentials
namespace: gitea
spec:
destination:
create: true
name: oauth-credentials
overwrite: true
hmacSecretData: true
mount: kv
path: kubernetes/namespace/gitea/default/oauth-credentials
refreshAfter: 5m
type: kv-v2
vaultAuthRef: default
-56
View File
@@ -1,56 +0,0 @@
---
# Ceph RGW (S3) backup target for the grafana 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-grafana-backup
namespace: grafana
spec:
displayName: "CNPG backup owner (grafana)"
# RGW users are global; keep the uid namespace-qualified so it never collides.
uid: cnpg-grafana-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-grafana-backup-s3
# Keep the RGW user (and thus the keys) if this CR is ever deleted, so an
# in-flight restore can still reach the archive.
retainOnDelete: true
---
apiVersion: ceph.unkin.net/v1alpha1
kind: Bucket
metadata:
name: cnpg-grafana
namespace: grafana
spec:
placementTarget: ec
bucketName: cnpg-grafana
# The owner user has full control of its own bucket (read + write), which is
# all the backup/restore identity needs — no extra BucketAccess grant.
ownerRef: cnpg-grafana-backup
versioning: false
tags:
app: grafana
purpose: cnpg-backup
# Never drop the backups if the CR is removed; retire buckets by hand.
retainOnDelete: true
---
# Nightly base backup. Continuous WAL archiving is always-on via the Cluster's
# spec.backup.barmanObjectStore; this schedules the periodic full backup that
# WAL is layered on top of. Schedules are staggered across clusters so the 8
# base backups do not hit RGW at once (CNPG cron is 6-field, seconds first).
apiVersion: postgresql.cnpg.io/v1
kind: ScheduledBackup
metadata:
name: cnpg-grafana-nightly
namespace: grafana
spec:
schedule: "0 20 3 * * *"
immediate: false
backupOwnerReference: self
method: barmanObjectStore
cluster:
name: postgres
-87
View File
@@ -1,87 +0,0 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: postgres
namespace: grafana
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-grafana
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-grafana-backup-s3
key: AWS_ACCESS_KEY_ID
secretAccessKey:
name: cnpg-grafana-backup-s3
key: AWS_SECRET_ACCESS_KEY
# Path prefix within the bucket; keep stable across restores (see docs).
serverName: grafana
data:
compression: bzip2
jobs: 2
wal:
compression: zstd
maxParallel: 2
bootstrap:
initdb:
database: grafana
encoding: UTF8
localeCType: C
localeCollate: C
owner: grafana
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: 500m
memory: 512Mi
requests:
cpu: 50m
memory: 256Mi
smartShutdownTimeout: 180
startDelay: 3600
stopDelay: 1800
storage:
resizeInUseVolumes: true
size: 10Gi
storageClass: cephrbd-fast-delete
switchoverDelay: 3600
-33
View File
@@ -1,33 +0,0 @@
---
apiVersion: postgresql.cnpg.io/v1
kind: Pooler
metadata:
name: postgres-pooler-rw
namespace: grafana
spec:
cluster:
name: postgres
instances: 2
pgbouncer:
parameters:
default_pool_size: "50"
max_client_conn: "200"
paused: false
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
@@ -1,13 +0,0 @@
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: bind9-exporter-dns
namespace: grafana
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
resyncPeriod: 5m
allowCrossNamespaceImport: false
gzipJson: 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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,13 +0,0 @@
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: frr-ospf-route-metrics
namespace: grafana
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
resyncPeriod: 5m
allowCrossNamespaceImport: false
gzipJson: H4sIAAycS2oC/+1ZbW/bNhD+KwJRFC0gF7brZkGAfXDcZgiQpkFcbNiawqCkk82FJjWScuIK/u+7o14sN0lXB0nRJfliS5TIe+655046sWBcKe24E1pZtlcwKaxje58KFuVCukPF9nohS7jjVucmBrrFLTP8Z1PDU644C1kuEjzvdILfyqGg02GrkIHikcQ7nckhZDORNMci1mqkpTY4zUwj/qIbBv1eD3/evAmD3ktcU/E5GRmu0QXPg6EE4yxerSAk3M4izU3CVp/JYCJc22QqbMzln8DN2HHj3mvlZmyvGxL0bPZRa+lE5gekUOfo/qfPIcu4Amk9BakAmYy0SsWU/E4g5bl0nqY5zzKhptUcNzNgZ1om5TVNnjIeWS1zBwjXOsjKJePK66kBUHhlwWWON3dX4fqagWR9ZbeLrqFvegHGIIXe4opcEMmJ9vbQp9chu2B7g5BdeneWfkVGYcHo6ayJrjfxvsRXGqjIqMaUVjT0d26dSJfVYAzKgcFhbQQe+miQf7nTOJiBoRtGM66mMGqtbx1XCcUmJI/yGD60cHAZkydMcpTb57CkGkdY7Tkep1xaQDeQ2YuTtpXqEvIOl66yVqHZPhLEdpv9ryLT88q6QOaP+FLnrtQWDmUynwr1Oxhb0tHrv+q96hIEbqbgSiPtxGHPWmco1suMzKXGTNC0hNhpM8mzojn59Yxpm6VnLBSKuIwBR57Vx2ds5YlNDyn3hpgAaFk4Ej/7MD45CEb1OgGK3+WtrMEFnHf5/6nvwYa+d7bSNzfAr+i7ks49qftYu+NcyrsU+f2q0eC6MHFYduUkFVHBU4HCE9ligFJcmJRUiH+30OXhyWIQnNLyNhAqODjcfzCi3N0Q5S9PovxRoty5A1HuPFBRlpXydSXKwZMo70+U91MlH1h5rJX45kmJP0KJd1kat1NipZpGdhmX4Bx0YiTXipiawxjDqed0D78Udl+bBMwY+WwopOGR7zwg+QuM3rzQiipxzcrhIx6BLGNFpyeSxzDHNdZhiLgZSjFV5WjXD/whEjc74PSujEOv8I0yMfxi7JaeBGwNiexUSPkh47FwKORe2UMmJL7Nzom63ANTOiZhCipZa6LuN6vzhfiylhKGA1tbEp1tbiDLh0RApmUtcRrj1IvRgQfO9vooJS2UG4sv4JOLul54i428EVFeTmy69mqBWr00j9StYOFbPIvt7yYKjHd8jmWF1piizjIvk7AOrfd71S43FXFN8HWaMqoWP1l52vXlqd9t9cw7VX3qb9SnOozrGoHwE2Ezyetq5D+aUEKv9RZpR/ouaT6qlqgSv1FC4fVC6l7zXVFhkSrpidDGNTzfbdWg9nKiQEzxI4qxxXfWipKPA23mnIAVhUjR7dV/tKPHtZkgWgZe1TRrXVacmAOmgMBoPaLikpX591V5+amqS+8OqkvFz6MuLr1BVV1eP6bqQi9vE2n5ROXzCEzx3WXlI73LBEfjYVAUtMjX9SW8JQxwF9qcbw3nuJx3E6D9WwPiNjJboxmO909vgjK6PTf2FnE6HiOMG6C83RYKocCCC0Zhmx/rnMrT90N5V80kOC0U764+jAjviJa3Tw+gpwfQQ3wA7Vx9APX71QNo95E8gGyMXxFgkuTGy2tiAbcdE1ts8WrbbMZ84/X24PS0vdnijQZvK6PX1xfaZjQgNV+nD65NkiGPu5SPNp7BnDc8DHrEgJcXGcQbqJDhH7XnpF8CBPOMMqlUc7ORG+fG+KAVPhFjmScwxI8bteVqp3WDxX9yMEsqD5jr4Gbgt48aiL3WBmwzyQu2MVXWLYwzEC9BTfNaxozS6htxvBloa60apqTKOCk/z7y42tmEjf2X17tRrnO9B1XlbyGvR27rADZY22DH278Nm7Kf1JWJ+BxMGWc6/0I5halq9IX1zX1bsf45+Lz8uhO8B6ylsW228hFGh2B0TP3xZ1ELsb/6F7MoRVUzIAAA
-13
View File
@@ -1,13 +0,0 @@
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: gitea
namespace: grafana
spec:
instanceSelector:
matchLabels:
dashboards: "grafana"
resyncPeriod: 5m
allowCrossNamespaceImport: false
gzipJson: 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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More