- Run the platform workflow →
up, with your current public IP (curl -s ifconfig.me) followed by/32. Or run it locally:TF_VAR_ui_allowed_cidrs='["<ip>/32"]' make up. - Wait about 10 minutes after the job finishes, then check:
aws eks update-kubeconfig --region eu-north-1 --name egp-demo kubectl get applications -n argocd # all Synced / Healthy curl -s https://api.demo.bulbashenko.com/version - Grafana password:
kubectl -n monitoring get secret grafana-admin -o jsonpath='{.data.password}' | base64 -d. Argo CD password:kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d. - Run each scenario once, then reset (
FAULT_RATE: "0", merged and synced). - Open these tabs: the Argo CD app
api, Rollouts dashboard (namespaceorders), the Grafana "Orders — service overview" dashboard, and the GitHub repo. - Record a backup video of the whole script.
# 1. Connect the laptop to their Wi-Fi, then:
export AWS_PROFILE=egp-admin
aws sso login --profile egp-admin # browser login + MFA
make allow-ip # detects the new public IP, updates the ALB allowlist (~2-3 min)It prints the three UI URLs with their HTTP status: 200/302 means reachable. If their network egresses from several
IPs (large corporate NAT pools), add them explicitly: EXTRA=203.0.113.0/24 make allow-ip. KEEP=1 adds to the
list instead of replacing it. The script refuses to apply if the plan would touch anything besides the allowlist.
Fallback if their Wi-Fi blocks something: phone hotspot, then make allow-ip again.
Open architecture.md (the diagrams render on GitHub) and cover:
- the layers and why they are split (persistent vs ephemeral);
- how Terraform hands over to Argo CD (GitOps Bridge);
- that CI has no cluster credentials: it commits a tag.
Story: "Someone ships a release that fails 30% of requests. Watch the platform catch it."
git switch -c demo/bad-release
sed -i 's/FAULT_RATE: "0"/FAULT_RATE: "0.3"/' gitops/apps/api/values.yaml
git commit -am "api: new release" && git push -u origin demo/bad-release
gh pr create --fill && gh pr merge --squash --adminPoint out as it happens:
- Argo CD syncs the
apiapp, and the Rollout creates a canary ReplicaSet. - Rollouts dashboard: weight 20%. The ALB is sending real traffic from
loadgento the canary. - Grafana "Error ratio by version": the canary hash jumps to ~30%, while stable stays at 0.
- The AnalysisRun fails within ~1 minute and the Rollout goes Degraded / aborted. Weight returns to 0 and users see stable only.
- SLO panel: the error budget took a small, bounded hit.
Reset: revert the PR (FAULT_RATE: "0"), then run kubectl argo rollouts retry rollout api -n orders, or let the revert create a new revision.
Talking points: exact traffic percentages come from ALB weighted target groups; the analysis compares only the canary pods (rollout_hash label); alternatives are Flagger or a mesh (ADR-0008).
kubectl get nodes -L karpenter.sh/capacity-type,node.kubernetes.io/instance-type -w # terminal 1
# terminal 2: ~150 rps of CPU-heavy requests for 3 minutes
docker run --rm -i grafana/k6 run - <<'EOF'
import http from 'k6/http';
export const options = { scenarios: { burn: { executor: 'constant-arrival-rate', rate: 150, timeUnit: '1s', duration: '3m', preAllocatedVUs: 200 } } };
export default function () { http.get('https://api.demo.bulbashenko.com/burn?ms=40'); }
EOFPoint out:
- HPA desired replicas climb (CPU > 60%).
- New pods go Pending: no room on the current nodes.
- Karpenter launches a Graviton Spot node within about a minute ("Nodes by capacity type" panel), and the pods start.
- After the test, consolidation removes the extra node within a few minutes.
Talking points: why controllers run on a fixed node group; how Spot interruptions are handled (interruption queue + PDBs); the NodePool CPU limit as a cost ceiling (ADR-0002).
Two admission layers, shown one at a time:
# Layer 1, Pod Security Admission (built in, "restricted" on orders):
kubectl -n orders run evil --image=nginx:latest --privileged
# → forbidden: violates PodSecurity "restricted:latest": privileged, allowPrivilegeEscalation, ...
# Layer 2, Kyverno. This pod satisfies PSA, but its image is not from our ECR:
kubectl apply -f docs/demo/untrusted-image.yaml
# → denied: Policy restrict-app-images-to-ecr failed: Images in 'orders' must come from this account's ECR
# Cluster-wide rule outside orders:
kubectl -n default run lazy --image=nginx
# → denied: Policy disallow-latest-tag failed: Images must use an explicit, immutable tagTalking points: PSA covers the pod-security baseline for free. Kyverno adds organisation rules (trusted registry, tags, limits) as CEL ValidatingPolicy, synced by Argo CD. The next step is cosign verifyImages (ADR-0010).
- Cost: about $0.30/h while up, under $2/month when down (ADR-0011).
- What changes for production: see the end of architecture.md.
- Then tear it down: platform workflow →
down.
| Question | Short answer |
|---|---|
| Why not ECS? | Ecosystem for GitOps/policy/canaries; ECS or EKS Auto Mode is fine for a small team (ADR-0001) |
| What if Argo CD is down? | Workloads keep running; only reconciliation pauses. Git still records intent |
| How do you roll back? | git revert (desired state), or kubectl argo rollouts undo in an emergency; Argo CD then shows the drift |
| How are secrets handled? | RDS-generated → Secrets Manager → ESO. Apps have no secret-read permission (ADR-0007) |
| Terraform state safety? | S3 + KMS + versioning + native locking; per-layer state; read-only plan role on PRs |
| Multi-account? | One account per env under Organizations; the foundation layer becomes per-account; GitOps Bridge already supports multiple clusters |
| What would you monitor first in prod? | SLO burn-rate alerts, DLQ depth, Karpenter pending pods, node NotReady, RDS storage/connections |