Preskoči na sadržaj

Deployment Model

Source of truth

hns-platform/playbooks/*.yml and roles/hns-service/tasks/{main,deploy-one,deploy-job}.yml. Update this page when those change.

A deploy is an Ansible run on app-01. There is no Helm and no per-service role — one generic role, hns-service, reads each service's .platform.yml and renders Kubernetes manifests from Jinja2 templates.

How a deploy is triggered

hns-platform itself is deployed via a Nebion manual recipe: Nebion clones the repo onto app-01 and runs Ansible directly on the box (no container) with ansible_connection: local. The entry point is playbooks/services.yml. Per-service redeploys and maintenance tasks are exposed as Nebion actions (clickable from the Drupal UI).

The pipeline

playbooks/services.yml runs two safety guards, creates the namespace, loops the service inventory through the hns-service role, then applies the shared TLS cert and prunes unused images.

playbooks/services.yml
  ├─ guard: K8S_NAMESPACE must be set            (refuse otherwise)
  ├─ guard: preview namespace ⇒ non-baseline DB_NAME
  ├─ ensure Namespace {{ K8S_NAMESPACE }}
  ├─ for each service in components.yml → role hns-service:
  │     1. git clone/pull repo → srv/<name> @ version
  │     2. image_tag = <git-sha7>-<namespace>
  │     3. load that repo's .platform.yml
  │     4. for each deployment → deploy-one.yml
  │     5. for each job        → deploy-job.yml
  ├─ role portal-tls          (one shared multi-SAN cert)
  └─ prune unused images      (k3s crictl rmi --prune)

Per-deployment steps (deploy-one.yml)

  1. List registry tags — query the internal registry for the target image.
  2. Build (if needed) + push — build the image when a build: section is declared and (the git ref changed or the tag is absent from the registry). See build-if-missing below. Tag = registry/<deployment>:<sha7>-<namespace>.
  3. Apply manifests — ConfigMap → PVC → Deployment → Service → (headless Service if discovery) → (basic-auth Secret + Middleware if ingress.basic_auth) → Ingress → (internal-deny Middleware + Ingress if ingress.internal_paths) → HPA (if scaling).
  4. Wait for rolloutkubectl rollout status with a 300s timeout. On failure: auto-rollback (kubectl rollout undo) and fail the play.
  5. Post-deploy — run each post_deploy command via kubectl exec deploy/<name> (see below).

Jobs (deploy-job.yml)

One-shot jobs[] are idempotent: a Job that already reports succeeded is skipped. Otherwise the existing Job is deleted (its spec.template is immutable, so re-apply needs a delete first) and re-created.

Two reliability behaviours worth knowing

Build-if-missing

The build step fires when the git ref changed or the image tag is absent from the registry — not on git changed alone. This self-heals the dangling-tag class of failure (a prior run advanced git but died before pushing, leaving a Deployment referencing a tag the registry doesn't have → ImagePullBackOff). It also surfaces a genuinely broken build at the build step with the real error, instead of as an opaque rollout timeout 300s later.

Post-deploy runs in a live pod

Post-deploy commands (migrations, cache clear) run via kubectl exec deploy/<name> -- sh -c '<cmd> 2>&1'. kubectl picks an active (preferentially Ready) pod of the deployment, so there is no manual pod-name lookup that could grab an old, still-terminating pod. The 2>&1 + explicit exit-code check surface the real stdout/stderr instead of masking a non-zero remote exit.

Image tags carry the namespace

The image tag is <git-sha7>-<namespace> (e.g. a1b2c3d-hns-ver2), not a bare SHA. The namespace suffix isolates images per composition: a service that bakes composition-specific values at build time (e.g. the mobile-web SPA baking VITE_* from {{ domain }}) would otherwise collide on a SHA-only tag across namespaces on the single node. Code-only images still share all layers (the registry dedupes by digest), so the suffix is near-free. See Compositions & environments.

Playbooks

Playbook Purpose
site.yml Full provision: infra + database + services
infra.yml k3s + cert-manager + ClusterIssuers
database.yml Initial PostgreSQL setup on db-01
services.yml Main deploy entry point — all services into a namespace
deploy-one.yml Deploy a single service (per-service Nebion action)
init-composition-db.yml Provision a preview composition's database on db-01
teardown-composition-db.yml Drop a composition's database (guards the baseline names)
seed-composition.yml Converge a namespace to seed users + reference content
teardown.yml Delete a namespace and its workloads
agents.yml Join a new k3s agent node
registry-gc.yml Garbage-collect unused images from the internal registry

Safety guards

Both enforced in services.yml (and mirrored in .nebion.yml and the Makefile):

  • K8S_NAMESPACE is mandatory. The play refuses to run if it is unset — silently defaulting to hns would overwrite the baseline.
  • Preview compositions must use a non-baseline DB_NAME. If the namespace is not hns and DB_NAME is empty or hns_ticketing, the play refuses — this prevents a preview stack from connecting to and mixing baseline data.

See Compositions & environments for the full workflow.


Last Updated: June 2026