Status: Live at https://flaskapp.prsmalley.dev/health — flaskapp is served via a Cloudflare Tunnel running on the EC2 (provisioned by terraform-flaskapp-infra). A Grafana dashboard of app metrics is live at https://grafana.prsmalley.dev. The cluster itself stays SG-restricted to the operator IP.
This repo owns cluster bootstrap and per-release deploys: it installs k3s on a fresh EC2 host and ships Kubernetes manifests to the cluster via an ephemeral self-hosted GitHub Actions runner (ARC). It's one of three repos that together build, provision, and deploy a Flask app to a k3s cluster on AWS EC2:
- flaskapp-docker-practice — builds and publishes the container image to GHCR.
- terraform-flaskapp-infra — provisions the EC2 host.
- ansible-playground — bootstraps k3s and deploys the app via ephemeral self-hosted runners (ARC) running inside the cluster.
See ARCHITECTURE.md for the full design.
flowchart LR
A[flaskapp-docker-practice] -->|CI + release| GHCR[(GHCR)]
B[terraform-flaskapp-infra] -.provisions.-> EC2
C[ansible-playground] -.bootstraps.-> k3s
C --> Runner
subgraph EC2[AWS EC2]
subgraph k3s[k3s cluster]
Runner[ARC runner pod] -->|kubectl apply| APP[flaskapp pods]
end
end
GHCR -.image pull.-> APP
.
├── ansible.cfg # Ansible defaults
├── inventory.ini.example # Copy to inventory.ini for bootstrap
├── requirements.yml # Ansible collections (community.docker — used by legacy/ only)
├── bootstrap-k3s.yml # k3s install playbook (one-time per cluster)
├── manifests/ # K8s manifests applied per deploy
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
├── setup/ # One-time cluster bootstrap (not redeployed)
│ └── rbac.yaml # ServiceAccount + Role + RoleBinding for deploy-runner
├── monitoring/ # Prometheus + Grafana (applied once, not per deploy)
│ ├── prometheus.yaml
│ └── grafana.yaml
├── legacy/ # Archived from earlier architecture iterations
├── .github/workflows/
│ ├── lint.yml # ansible-lint on every PR
│ └── deploy.yml # ARC ephemeral runner runs kubectl apply
├── BOOTSTRAP.md # One-time cluster setup runbook
└── ARCHITECTURE.md # End-to-end design across all three repos
After the one-time cluster setup, deploys are triggered from the CLI or the GitHub Actions UI:
gh workflow run deploy.yml \
-f image_tag=sha-abc1234 \
-f dry_run=falseSet dry_run=true to preview what would be applied without making changes — useful for verifying a new image tag before a real deploy.
What happens when you trigger it:
release.ymlin flaskapp-docker-practice has already published a multi-arch OCI image to GHCR.- Operator triggers
deploy.ymlviaworkflow_dispatch(the command above). - ARC's listener detects the queued job and spawns an ephemeral self-hosted runner pod inside the k3s cluster.
- The runner pod authenticates to the K8s API via its mounted ServiceAccount token,
sed-substitutes the image tag intomanifests/deployment.yaml, and runskubectl apply -f manifests/. - k3s reconciles to the declared state; flaskapp pods come up and reach Ready when their readiness probes pass.
kubectl rollout statusblocks until the new pods are Ready; the runner pod terminates.
One-time procedure for spinning up a new cluster from scratch. See BOOTSTRAP.md for the full operator runbook (~30 minutes end to end, assuming AWS / GitHub / SSH credentials are already in place). After bootstrap, deploys are workflow-triggered as above.
The cluster runs Prometheus (collects metrics) and Grafana (charts them), both in the monitoring namespace. The app exposes a /metrics endpoint, Prometheus scrapes it every 30 seconds, and Grafana reads from Prometheus and serves a dashboard at https://grafana.prsmalley.dev.
These live in monitoring/ rather than manifests/ on purpose: they're set up once when the cluster is built, not re-applied on every app deploy. Install steps are in BOOTSTRAP.md.
ansible-lint runs on every PR via lint.yml at the production profile. To run locally:
pip install ansible-lint
ansible-galaxy collection install -r requirements.yml
ansible-lintArchived files from earlier architecture iterations. Kept for portfolio depth and as Ansible examples; not part of the active deploy.
site.yml— early Ansible learning playbook. Multipass VM config (packages, user, templated config, nginx). Demonstrates templates, handlers, multi-module orchestration.deploy-flaskapp.yml— Ansible-based deploy that pulled the image and ran it via Docker on a single host. Replaced by the K8s deploy. Demonstrates thecommunity.dockercollection.inventory-runner.ini— inventory used by the olddeploy.ymlworkflow when the runner was the deploy target (Multipass VM era).
MIT — see LICENSE.