Cluster Topology¶
Source of truth
hns-platform/inventory/hosts.yml, inventory/host_vars/*.yml, inventory/group_vars/all/main.yml, cloud-init.yml, cloud-init-db.yml, and roles/registry/, roles/k3s-server/. Update this page when those change.
The HNS Ticketing cluster runs on two Hetzner Cloud servers joined by a private network (10.0.0.0/24). Application workloads run in a single-node k3s cluster; PostgreSQL runs on bare metal outside Kubernetes.
Public Internet
│ :80 / :443
▼
┌─────────────────────────────────────┐ ┌──────────────────────────┐
│ app-01 private 10.0.0.2 │ │ db-01 private 10.0.0.3 │
│ public 178.105.212.28 │ 10.0.0.x│ │
│ │────────▶│ PostgreSQL 16 │
│ k3s (server mode) v1.35.5+k3s1 │ :5432 │ (bare metal) │
│ ├─ Traefik (built-in ingress) │ │ ├─ hns_ticketing │
│ ├─ HNS service pods (see inventory)│ │ └─ keycloak │
│ └─ hns-registry 10.0.0.2:5000 │ │ │
└─────────────────────────────────────┘ └──────────────────────────┘
NOT in k8s — managed by Ansible/cloud-init ▲
Hosts¶
| Host | Private IP | Public IP | Role |
|---|---|---|---|
app-01 |
10.0.0.2 |
178.105.212.28 |
k3s server (control plane + workloads), internal registry |
db-01 |
10.0.0.3 |
— | PostgreSQL 16, bare metal |
k3s_agents is defined but empty — the inventory is ready to scale out to worker nodes (playbooks/agents.yml), but the cluster currently runs everything on app-01.
k3s¶
- Distribution: k3s (single-binary certified Kubernetes), server mode on
app-01. - Version: pinned cluster-wide as
k3s_version: v1.35.5+k3s1inmain.yml. Bump it deliberately, then run the upgrade playbook node-by-node (cordon/drain/reinstall) to avoid kubelet ↔ kube-apiserver skew. - Ingress: Traefik, bundled with k3s. See TLS & ingress.
- Service discovery: in-cluster traffic uses Kubernetes DNS — services reach each other by name over ClusterIP (e.g.
http://hns-ticketing-backend:80,nats://nats:4222). See Service inventory. This traffic never traverses Traefik, so it is unaffected by ingress auth or TLS.
Internal Docker registry¶
All HNS service images are built on app-01 and pushed to a private registry; every k3s node pulls from it.
- Container:
hns-registry(registry:3), bound to10.0.0.2:5000(theapp-01private IP only — never exposed publicly). - Storage:
/srv/registryonapp-01; deletes enabled (REGISTRY_STORAGE_DELETE_ENABLED=true) so the registry-gc maintenance action can reclaim space. - Plain HTTP, trusted explicitly: the Docker daemon lists it under
insecure-registries, and k3s containerd mirrors it via/etc/rancher/k3s/registries.yamlwithinsecure_skip_verify. This is safe because the endpoint is private-network-only.
Database server (db-01)¶
PostgreSQL 16 runs on bare metal, provisioned by cloud-init-db.yml:
- Listens on the private IP
10.0.0.3:5432;pg_hba.confallows the10.0.0.0/24network withmd5auth. - Two baseline databases (
inventory/host_vars/db-01.yml):hns_ticketing(ownervault_db_user) andkeycloak(ownervault_kc_db_user). - Preview compositions get their own backend database on this same server — see Compositions & environments.
Keycloak's database is split between two places
The realm/identity database keycloak lives on db-01 (above). But each composition also runs its own in-cluster Postgres for Keycloak (hns-idp-db) where applicable — KC data is isolated per namespace automatically, so it needs no per-composition db-01 knob. The bare-metal keycloak DB is the baseline instance.
Bootstrap¶
Servers are provisioned from cloud-init, then converged by Ansible (playbooks/infra.yml / site.yml):
cloud-init.yml(app-01): installsdocker.io,git, Ansible + thekubernetes.core/community.dockercollections, thenk3s ... serverand wireskubectl+ kubeconfig.cloud-init-db.yml(db-01): adds the PGDG apt repo, installspostgresql-16, switcheslisten_addressesto the private IP, and openspg_hbato the private network.
Last Updated: June 2026