Ansible role: The Button

Deploy The Button to Kubernetes with persistent game data and a TLS-enabled Ingress. The Ingress uses cert-manager's ClusterIssuer ca-issuer by default.

Resources

  • Namespace (optional creation).
  • PersistentVolumeClaim for /data/data.json and /data/mailing_list.csv, or an existing claim in the application namespace.
  • One-replica Deployment, with Recreate updates to avoid concurrent file writers.
  • ClusterIP Service on port 80, forwarding to the application on port 8000.
  • networking.k8s.io/v1 Ingress routing / to the Service with TLS termination. cert-manager's ingress-shim creates and renews the certificate in the TLS Secret.

Requirements

  • Ansible Core 2.16–2.19 on a Linux/macOS control host, or WSL on Windows. Tasks run on the play's execution host; use localhost with connection: local.
  • The kubernetes.core collection and its Python dependencies; see requirements.yml and requirements.txt.
  • A reachable Kubernetes API and kubeconfig credentials authorized to manage namespaces (unless namespace creation is disabled), PVCs, Deployments, Services, and Ingresses. TLS certificate validation for the API stays enabled.
  • A working Ingress controller, cert-manager with ingress-shim enabled, and an existing, Ready ClusterIssuer named ca-issuer (or the issuer selected in your annotations). The role does not install these components or create the CA.
  • DNS for the chosen hostname pointing at the Ingress controller, with port 443 reachable. Select an IngressClass explicitly if there is no cluster default.
  • A default StorageClass, a named StorageClass, or a pre-provisioned PVC. The storage driver must support writable volumes for UID/GID 10001 using fsGroup.
  • A published container image built from The Button application's Dockerfile. The role deploys an image; it does not build or push one. A plain Python base image does not contain the application. Prefer an immutable tag or digest.

Install dependencies on the execution host:

python -m pip install -r requirements.txt
ansible-galaxy collection install -r requirements.yml

Example playbook

Place this checkout in your Ansible roles search path as ansible-role-the-button:

---
- name: Deploy The Button
  hosts: localhost
  connection: local
  gather_facts: false
  roles:
    - role: ansible-role-the-button
      vars:
        the_button_image: registry.example.com/the-button:1.0.0
        the_button_ingress_host: button.example.com
        # Omit this when a default IngressClass is configured in the cluster.
        the_button_ingress_class_name: nginx
        # Controller-specific: use the equivalent configuration for your controller.
        the_button_ingress_annotations:
          cert-manager.io/cluster-issuer: ca-issuer
          nginx.ingress.kubernetes.io/ssl-redirect: "true"

Replace the example registry and hostname with real values. With dependencies installed, deploy directly from this checkout using the test playbook:

ansible-playbook -i tests/inventory tests/test.yml \
  -e the_button_image=registry.example.com/the-button:1.0.0 \
  -e the_button_ingress_host=button.example.com

Variables

All defaults are in defaults/main.yml.

Variable Default Purpose
the_button_image "" (required) Complete published application image reference.
the_button_ingress_host "" (required) DNS hostname, without scheme, port, path, or wildcard.
the_button_name the-button Resource name and instance label; at most 50 characters.
the_button_namespace the-button Namespace for all application resources.
the_button_create_namespace true Create the namespace; disable for an existing namespace.
the_button_kubeconfig null Optional kubeconfig path; otherwise use normal discovery, including KUBECONFIG.
the_button_kube_context null Optional kubeconfig context.
the_button_wait true Wait for the Deployment to become ready.
the_button_wait_timeout 180 Readiness timeout in seconds.
the_button_image_pull_policy IfNotPresent Container image pull policy.
the_button_image_pull_secrets [] Names of existing image-pull Secrets in the namespace.
the_button_duration_seconds 86400 Countdown duration; must be a positive integer.
the_button_ingress_class_name "" IngressClass; empty omits the field and uses the cluster default.
the_button_ingress_annotations {"cert-manager.io/cluster-issuer": "ca-issuer"} Complete Ingress annotations map, including issuer and controller-specific settings.
the_button_tls_secret_name <name>-tls Secret managed by cert-manager in the application namespace.
the_button_existing_claim "" Existing PVC to mount; empty creates <name>-data.
the_button_storage_size 1Gi Size of the role-managed PVC.
the_button_storage_class null Omit for cluster default; "" disables dynamic provisioning; a string selects a class.
the_button_storage_access_modes [ReadWriteOnce] Access modes for the role-managed PVC.
the_button_resources See defaults Requests: 100m CPU / 64Mi RAM; limits: 1 CPU / 256Mi RAM. Tune for actual load.
the_button_node_selector {} Pod node selector.
the_button_tolerations [] Pod tolerations.
the_button_affinity {} Pod affinity configuration.

Configure the issuer directly in the_button_ingress_annotations, alongside any controller-specific annotations. Overriding this map replaces the defaults: include cert-manager.io/cluster-issuer: ca-issuer to retain the default issuer, choose another ClusterIssuer, or use cert-manager.io/issuer for a namespace-scoped Issuer. Do not specify both issuer annotations. An empty map renders no annotations; in that case, arrange certificate provisioning separately for the TLS Secret.

TLS and security

  • TLS terminates at the Ingress; traffic from the controller to the Pod is HTTP. Configure HTTP-to-HTTPS redirects, HSTS, modern TLS policy, rate limiting, and request-size limits on your Ingress controller as appropriate. Redirect annotations differ between controllers and are not imposed by this role.
  • Certificates from a private CA are not automatically trusted by browsers. Distribute the CA trust chain to clients; do not bypass certificate validation. cert-manager owns private-key generation, certificate issuance, and renewal; no private keys or credentials are hardcoded in the role.
  • Readiness waiting covers the Deployment, not certificate issuance or DNS propagation. Confirm the generated Certificate is Ready and the HTTPS endpoint presents a valid certificate for the configured hostname before exposing it.
  • The container runs as non-root UID/GID 10001, drops all capabilities, disallows privilege escalation, uses RuntimeDefault seccomp, and has a read-only root filesystem. Only /data and a bounded /tmp volume are writable. Kubernetes service-account credentials are not mounted. Health probes use /api/state.
  • The Service is internal only. Apply cluster-specific NetworkPolicies to allow your Ingress controller to reach TCP/8000 and restrict other traffic. The role does not assume controller namespace labels or manage cluster network policy.
  • Use an encrypted StorageClass and encrypted, access-controlled backups: the volume contains password hashes, session hashes, and mailing-list personal data. Restrict access to the namespace and TLS/registry Secrets through cluster RBAC.

Persistence and updates

Do not scale this Deployment beyond one replica or attach its PVC to another running application instance. The application caches state in memory and writes local files; a shared filesystem does not make multiple replicas safe.

Re-running the role applies resource changes idempotently. Updates intentionally incur brief downtime because the previous Pod must stop before its replacement starts. There is no autoscaler. Back up /data before upgrades and test restores. Changing the countdown setting does not promise to reset an existing saved game.

PVC creation does not wait for binding before creating the Deployment, allowing StorageClasses with WaitForFirstConsumer. Existing claims are not modified. Do not change immutable PVC fields on an existing claim. Expansion requires a StorageClass that permits it. Changing the resource name or namespace creates new resources; it does not migrate data or clean up the old installation.

The role has no uninstall operation and does not delete claims. Manually deleting the namespace or PVC can destroy game data according to the PV reclaim policy.

Tests

Cluster-free render checks exercise default and custom settings, TLS wiring, labels and ports, storage modes, existing-claim selection, and container hardening:

ansible-playbook -i tests/inventory tests/render.yml
ansible-playbook -i tests/inventory tests/test.yml --syntax-check

tests/test.yml is an integration deployment playbook: supply the required variables and a test-cluster kubeconfig as shown above. Run it twice to check idempotency, then verify HTTPS, certificate readiness, game operations, and data persistence across a Pod replacement. These integration checks require a real cluster and are not performed by the cluster-free tests.

License

MIT-0 (matching the role's SPDX headers).

S
Description
Ansible role to deploy The Button to Kubernetes
Readme
40 KiB
Languages
Jinja 100%