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.jsonand/data/mailing_list.csv, or an existing claim in the application namespace. - One-replica Deployment, with
Recreateupdates to avoid concurrent file writers. - ClusterIP Service on port 80, forwarding to the application on port 8000.
networking.k8s.io/v1Ingress 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
localhostwithconnection: local. - The
kubernetes.corecollection 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
RuntimeDefaultseccomp, and has a read-only root filesystem. Only/dataand a bounded/tmpvolume 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).