Sample Application Configuration¶
The sample application (sample-app/) is the deployment target that MANA protects. It is configured through environment variables read by sample-app/src/config.py. Several of those variables exist specifically so you can break the app on purpose and watch MANA respond. This page explains each setting and shows recipes for triggering each kind of failure.
For the flat variable list see Environment Variables.
How Configuration Is Loaded¶
get_config() builds a Config dataclass from the environment on first use and validates it in __post_init__. A violated rule raises ValueError and the app fails to start, which Kubernetes surfaces as CrashLoopBackOff.
Settings¶
Application¶
| Variable | Default | Validation | Description |
|---|---|---|---|
APP_VERSION |
1.0.0 |
none | Version string reported by /health, /, /api/data, /api/config and the app_info metric |
ENVIRONMENT |
development |
warns if not one of development, staging, production, testing |
Environment name. production switches logging to JSON; development enables debug-level logs |
PORT |
5000 |
1024 to 65535 |
Port used when running python src/app.py. The container's Gunicorn command binds 0.0.0.0:5000 directly |
DEBUG |
false |
none | Enables Flask debug mode (when run directly), debug log level, and extra detail in error responses |
Keep APP_VERSION in step with the image tag
The deploy workflow updates the image with kubectl set image but does not touch environment variables. The manifest pins APP_VERSION=1.0.0, so a running v1a2b3c4-… image keeps reporting 1.0.0. If you want /health to show the real deployed version, set APP_VERSION to the tag (for example with kubectl set env) or inject it from the pod's image label.
Failure simulation¶
| Variable | Default | Validation | Description |
|---|---|---|---|
FAILURE_RATE |
0.0 |
0.0 to 1.0 |
Probability that GET / and GET /api/data return HTTP 500 (SIMULATED_FAILURE) |
STARTUP_TIME |
0 |
>= 0 |
Seconds after process start during which GET /ready returns HTTP 503 NOT_READY |
What these do and do not affect:
| Endpoint | FAILURE_RATE |
STARTUP_TIME |
|---|---|---|
GET /health (liveness, and the orchestrator's health check) |
unaffected | unaffected |
GET /ready (readiness) |
unaffected | 503 until the delay has elapsed |
GET /, GET /api/data |
random 500s | unaffected |
GET /api/stress, GET /api/config, GET /metrics |
unaffected | unaffected |
Performance¶
| Variable | Default | Validation | Description |
|---|---|---|---|
WORKERS |
4 |
>= 1 |
Intended Gunicorn worker count |
TIMEOUT |
30 |
none | Intended request timeout (seconds) |
KEEPALIVE |
5 |
none | Intended keep-alive (seconds) |
Parsed but not consumed
These three values are validated and stored in the config object, but the container starts with a fixed command (gunicorn --bind 0.0.0.0:5000 --workers 2 --timeout 120 src.app:app). To change worker count or timeout, edit the CMD in sample-app/Dockerfile or add --workers $WORKERS style arguments via a shell form.
Monitoring¶
| Variable | Default | Description |
|---|---|---|
ENABLE_METRICS |
true |
Reported in GET /api/config. Metrics are always exposed |
METRICS_PORT |
5000 |
Not used. Metrics are served at /metrics on the application port |
Observability Built In¶
- Metrics:
prometheus-flask-exporterexposes request counts and latency histograms (for exampleflask_http_request_totalandflask_http_request_duration_seconds) plus anapp_info{version=…}gauge atGET /metrics. - Logs: JSON to stdout when
ENVIRONMENT=production, human-readable text otherwise. Each request gets anX-Request-ID(honouring an incoming header) that is returned in the response and included in every log line for that request. - Graceful shutdown: SIGTERM and SIGINT run registered cleanup handlers and exit with status 0. Pair this with
terminationGracePeriodSecondsin your Deployment if cleanup becomes non-trivial.
Kubernetes Settings¶
sample-app/k8s/deployment.yaml sets only three variables (APP_VERSION, ENVIRONMENT, PORT) and configures:
| Setting | Value |
|---|---|
| Replicas | 3 (HPA range 3 to 10, CPU 70%, memory 80%) |
| Rolling update | maxSurge: 1, maxUnavailable: 1 |
| Resources | requests 100m / 128Mi; limits 200m / 256Mi |
| Liveness probe | GET /health, initial delay 30 s, period 10 s, failure threshold 3 |
| Readiness probe | GET /ready, initial delay 10 s, period 5 s, failure threshold 3 |
| Security context | runAsNonRoot, UID 1000, no privilege escalation, all capabilities dropped (readOnlyRootFilesystem: false) |
| Pod Disruption Budget | minAvailable: 2 |
| Prometheus annotations | prometheus.io/scrape: "true", port 5000, path /metrics |
Label and container-name contract with the orchestrator
The orchestrator finds pods with the label selector app=<deployment name> and rolls back by editing the container whose name equals the deployment name. Keep app: sample-app on the pods and name the container sample-app (as the manifests do) or rollbacks and health checks will not find their targets. Pods also need the version label for the ReplicaSet-based previous-version fallback.
With Terraform, failure_rate is a root variable that is forced to 0.0 when environment = "production"; the staging copy of the app uses the value you provide. See the Configuration Reference.
Failure Recipes¶
Use these in non-production environments to exercise MANA. Each one produces a different failure_type from the deploy workflow.
1. Pods never become ready (deployment_failure)¶
Make /ready fail for ten minutes:
kubectl set env deployment/sample-app STARTUP_TIME=600 -n staging
kubectl rollout status deployment/sample-app -n staging --timeout=60s # times out
The new pods are never Ready, so kubectl rollout status fails in the deploy job and the pipeline reports failure_type=deployment_failure.
2. Intermittent application errors (smoke-test failures)¶
kubectl set env deployment/sample-app FAILURE_RATE=1.0 -n staging
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/ # 500
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/health # 200
Pods stay Ready because /health and /ready are unaffected, but the production smoke tests (/, /api/data) and the flask_http_request_total{status="500"} metric will show the failure.
A smoke-test failure reaches the orchestrator as failure_type=smoke_test_failure.
3. Bad image (deployment_failure)¶
Deploy a tag that does not exist or an image that exits immediately:
kubectl set image deployment/sample-app sample-app=ghcr.io/<owner>/mana/sample-app:does-not-exist -n staging
The pods go to ImagePullBackOff, the rollout never completes, and the deploy job fails.
4. Load and resource pressure¶
curl "http://localhost:8080/api/stress?duration=10&compute=true"
Run several in parallel to push CPU toward the HPA target and watch scaling in kubectl get hpa -n production -w.
Running Locally¶
cd sample-app
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
ENVIRONMENT=development FAILURE_RATE=0.2 python -m flask --app src.app run --port 5000
# or
docker compose up sample-app # docker-compose.yml sets FAILURE_RATE=0.1
Run the tests with cd sample-app && pytest tests/ -v --cov=src.