Kubernetes
Cluster: quinza
| Property | Value |
|---|---|
| Distribution | Talos Linux v1.13.0 |
| Kubernetes | v1.36.0 |
| Nodes | 3 (1 control plane + 2 workers) |
| Scheduling on CP | Enabled (allowSchedulingOnControlPlanes: true) |
Nodes
| Role | Specs | WireGuard IP |
|---|---|---|
| Control Plane | 2 vCPU, 4 GB RAM | 10.10.1.1 |
| Worker 1 | 4 vCPU, 8 GB RAM | 10.10.1.2 |
| Worker 2 | 4 vCPU, 8 GB RAM | 10.10.1.3 |
Networking
CNI: Flannel with --iface=wg0 — all pod traffic routes through the WireGuard mesh.
Load Balancer: MetalLB in L2 mode, deployed via Helm in the metallb-system namespace. IP pool: 10.10.1.200-10.10.1.210 (WireGuard subnet).
Ingress: Traefik v3.6.13, deployed via Helm chart with LoadBalancer service type. MetalLB assigns VIP 10.10.1.200. Caddy on the bastion points to this single VIP instead of hardcoded node IPs. The variable k8s_traefik_upstream in bastion vars controls the upstream address.
MetalLB and WireGuard
MetalLB L2 mode uses ARP announcements, which do not cross WireGuard tunnels. The VIP must be added to WireGuard AllowedIPs for the node peer so the bastion can route to it. See WireGuard - MetalLB VIP Routing.
DNS Automation: ExternalDNS watches K8s Ingress resources and auto-creates DNS A records in Cloudflare for quinza.dev domains. Deployed via Helm (external-dns/external-dns) in the external-dns namespace with txt-owner-id: quinza-k8s.
| Property | Value |
|---|---|
| Namespace | external-dns |
| Provider | Cloudflare (quinza.dev only) |
| Sync policy | sync with TXT owner ID quinza-k8s |
| GCore (carzying.es) | Pending -- needs API token |
Storage
local-path-provisioner is the default StorageClass. Namespaces using PVCs need the pod-security label:
labels:
pod-security.kubernetes.io/enforce: privilegedPostgreSQL
CloudNativePG v1.29.0 operator manages PostgreSQL instances. StackGres was evaluated but is incompatible with Kubernetes v1.36.
PostgreSQL runs in the apps namespace as a shared instance for all applications.
Backups
A CronJob in talos/manifests/postgresql/backup-cronjob.yaml runs pg_dump daily at 02:00 UTC with 7-day retention, storing dumps on a 5Gi PVC.
| Property | Value |
|---|---|
| Schedule | 0 2 * * * (daily at 02:00 UTC) |
| Retention | 7 days |
| Storage | 5Gi PVC |
Additional scripts:
| Script | Purpose |
|---|---|
scripts/pg-backup-to-bastion.sh | Copy backups offsite to the bastion |
scripts/pg-restore.sh | Restore a backup to the cluster |
Namespace Layout
graph TB
subgraph kube-system
COREDNS[CoreDNS x2]
FLANNEL[Flannel x3]
KUBEPROXY[kube-proxy x3]
TRAEFIK[Traefik v3.6.13]
end
subgraph metallb-system
METALLB[MetalLB L2]
end
subgraph external-dns
EXTDNS[ExternalDNS - Cloudflare]
end
subgraph cnpg-system
CNPG_OP[CloudNativePG v1.29.0]
end
subgraph local-path-storage
LPP[local-path-provisioner]
end
subgraph apps
PG[PostgreSQL 16 - 1 instance, 10Gi]
end
subgraph carzying
FE[Frontend - SolidStart x1]
DIRECTUS[Directus v11]
end
subgraph carzying-preview
PREV[Preview Deployments]
end
subgraph argocd
ARGO_SRV[ArgoCD Server]
ARGO_REPO[Repo Server]
ARGO_REDIS[Redis]
ARGO_CTRL[Application Controller]
end
subgraph gitlab-runner
GLRUNNER[GitLab Runner - Helm]
end
subgraph monitoring
OTEL[OTel Collector DaemonSet x3]
OTEL_CNPG[OTel CNPG Collector]
end
CNPG_OP -->|manages| PG
DIRECTUS -->|uses| PG
TRAEFIK -->|routes to| FE
TRAEFIK -->|routes to| DIRECTUS
TRAEFIK -->|routes to| ARGO_SRV
OTEL -->|OTLP to bastion relay| RELAY[10.10.0.1:4317]Workload Summary
| Namespace | Workloads |
|---|---|
| kube-system | CoreDNS (2), Flannel (3), kube-proxy (3), Traefik v3.6.13 (LoadBalancer), gitlab-ci-deploy SA |
| metallb-system | MetalLB controller + speaker (L2 mode, pool 10.10.1.200-210) |
| external-dns | ExternalDNS (Cloudflare provider, quinza.dev) |
| cnpg-system | CloudNativePG operator v1.29.0 |
| local-path-storage | local-path-provisioner (default StorageClass) |
| apps | PostgreSQL 16 (CNPG, 1 instance, 10Gi), pg_dump CronJob (daily) |
| carzying | Frontend (1 replica), Directus v11 |
| carzying-preview | Ephemeral preview deployments from MRs |
| argocd | Server, repo-server, redis, application-controller |
| gitlab-runner | GitLab Runner (Helm, Kubernetes executor) |
| monitoring | OTel Collector DaemonSet (3 pods), OTel CNPG Collector (Deployment) |
Configuration Management
Talos nodes are configured via talosctl machineconfig patch. Each node has its own patch file applied directly.
talhelper was evaluated but v3 is broken — do not use it.
Secrets
talsecret.yaml contains cluster secrets, encrypted with SOPS + age. The age key is stored outside the repository.
talsecret.yaml → SOPS (age encryption) → git