175 lines
9.6 KiB
Markdown
175 lines
9.6 KiB
Markdown
# 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](requirements.yml) and [requirements.txt](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:
|
||
|
||
```sh
|
||
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`:
|
||
|
||
```yaml
|
||
---
|
||
- 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:
|
||
|
||
```sh
|
||
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](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:
|
||
|
||
```sh
|
||
ansible-playbook -i tests/inventory tests/render.yml
|
||
ansible-playbook -i tests/inventory tests/test.yml --syntax-check
|
||
```
|
||
|
||
[tests/test.yml](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).
|