Skip to content

Commit 5182aa5

Browse files
committed
Chart and scaling updates, scaling docs commit
1 parent 1de7943 commit 5182aa5

35 files changed

Lines changed: 3264 additions & 42 deletions

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,7 @@ env/
3434

3535
# Engine legacy generated outputs
3636
engine/legacy/engine/autoaudit_reports.json
37+
38+
# Per-developer docker compose overlay (auto-loaded by compose; not for sharing)
39+
docker-compose.override.yml
40+
docker-compose.override.yaml

backend-api/app/api/health.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ async def _check_redis(timeout: float = 2.0) -> tuple[Literal["ok", "error"], st
5757
return "ok", None
5858
except Exception as exc:
5959
logger.warning("healthz: Redis check failed: %s", exc)
60-
return "error", str(exc)
60+
# str(exc) is empty for some redis.asyncio connection failures; fall
61+
# back to the exception class so /healthz output is always debuggable.
62+
msg = str(exc) or f"{type(exc).__name__}"
63+
return "error", msg
6164

6265

6366
async def _check_opa(timeout: float = 2.0) -> tuple[Literal["ok", "error"], str | None]:

backend-api/app/api/v1/test.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ class SyntheticScanRequest(BaseModel):
3838
"powershell-service to drive its HPA in lockstep."
3939
),
4040
)
41+
call_opa: bool = Field(
42+
True,
43+
description=(
44+
"If true, each synthetic_evaluate task issues a POST to OPA's "
45+
"/v1/data/<package>/result so OPA decision metrics populate. "
46+
"Mirrors what a real evaluate_control task does."
47+
),
48+
)
4149

4250

4351
def _require_synthetic_enabled() -> None:
@@ -58,7 +66,11 @@ async def synthetic_scan(
5866
# module (which would pull collector/policy code into the API image).
5967
client = Celery("autoaudit_synthetic", broker=settings.REDIS_URL)
6068

61-
common = {"sleep_ms": request.sleep_ms, "cpu_burn_ms": request.cpu_burn_ms}
69+
common = {
70+
"sleep_ms": request.sleep_ms,
71+
"cpu_burn_ms": request.cpu_burn_ms,
72+
"call_opa": request.call_opa,
73+
}
6274

6375
for _i in range(request.n_graph):
6476
client.send_task(

docker-compose.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ services:
112112

113113
# Optional: Path to policies directory (default: /app/policies)
114114
- POLICIES_DIR=/app/policies
115+
116+
# Opt-in: enables the synthetic load endpoints for scaling tests.
117+
# Off by default — set `SYNTHETIC_ENABLED=true` in your shell (or .env)
118+
# before `docker compose up`. App-side is also gated by `APP_ENV != prod`.
119+
- SYNTHETIC_ENABLED=${SYNTHETIC_ENABLED:-}
115120
volumes:
116121
# Mount policies directory for benchmark/control discovery API
117122
- ./engine/policies:/app/policies:ro
@@ -132,6 +137,20 @@ services:
132137
context: ./engine
133138
dockerfile: Dockerfile
134139
container_name: autoaudit-worker
140+
# Local single-worker overrides the image's default `--queues=default` so
141+
# it drains every queue the chart now splits into separate Deployments
142+
# (`default`, `controls.graph`, `controls.powershell`). In K8s the chart
143+
# renders one Deployment per queue with `--queues=<one>`, so this only
144+
# affects local docker compose.
145+
command:
146+
- celery
147+
- -A
148+
- worker.celery_app
149+
- worker
150+
- --pool=prefork
151+
- --concurrency=4
152+
- --loglevel=info
153+
- --queues=default,controls.graph,controls.powershell
135154
environment:
136155
# Required: Database connection (PostgreSQL - worker uses sync driver)
137156
- DATABASE_URL=postgresql+asyncpg://autoaudit:autoaudit_dev_password@db:5432/autoaudit
@@ -148,6 +167,11 @@ services:
148167
# Optional: PowerShell service URL for Exchange/Teams cmdlets
149168
# When set, worker uses HTTP service instead of spawning Docker containers
150169
- POWERSHELL_SERVICE_URL=http://powershell-service:8001
170+
171+
# Opt-in: enables the synthetic load endpoints / tasks for scaling tests.
172+
# Off by default — set `SYNTHETIC_ENABLED=true` in your shell (or .env)
173+
# before `docker compose up`. App-side is also gated by `APP_ENV != prod`.
174+
- SYNTHETIC_ENABLED=${SYNTHETIC_ENABLED:-}
151175
volumes:
152176
- ./engine:/app/engine:ro
153177
- ./engine/policies:/app/policies:ro
@@ -179,6 +203,11 @@ services:
179203
context: ./engine/powershell
180204
dockerfile: Dockerfile
181205
container_name: autoaudit-powershell-service
206+
environment:
207+
# Opt-in: enables the /execute/synthetic endpoint for scaling tests.
208+
# Off by default — set `SYNTHETIC_ENABLED=true` in your shell (or .env)
209+
# before `docker compose up`. App-side is also gated by `APP_ENV != prod`.
210+
- SYNTHETIC_ENABLED=${SYNTHETIC_ENABLED:-}
182211
ports:
183212
- "8001:8001"
184213
healthcheck:

docs/scaling/saas-scaling-aks-platform.md

Lines changed: 543 additions & 0 deletions
Large diffs are not rendered by default.

docs/scaling/saas-scaling-architecture.md

Lines changed: 572 additions & 0 deletions
Large diffs are not rendered by default.

docs/scaling/scaling-after.md

Lines changed: 322 additions & 0 deletions
Large diffs are not rendered by default.

docs/scaling/scaling-baseline.md

Lines changed: 329 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# AutoAudit scaling test runbook (AKS)
2+
3+
End-to-end "deploy this on a fresh AKS cluster" sequence used for the
4+
scaling demo. Companion to [`scaling-after.md`](./scaling-after.md) (the
5+
"what changed and why" doc) and the two component READMEs:
6+
7+
- [`infrastructure/monitoring/in-cluster/README.md`](../../infrastructure/monitoring/in-cluster/README.md) — Prometheus / Grafana / KEDA / celery-exporter
8+
- [`tests/load/in-cluster/README.md`](../../tests/load/in-cluster/README.md) — in-cluster k6 load runner
9+
10+
If you're re-running this after a teardown, skip whatever you've already
11+
done — every step is idempotent.
12+
13+
## Prerequisites
14+
15+
| What | Why |
16+
|---|---|
17+
| `kubectl` configured for the target AKS cluster | Everything that follows |
18+
| `helm` v3+ | KEDA, Prometheus, Grafana, AutoAudit |
19+
| `az` logged in to the subscription holding the ACR | Image push (or `az acr login -n autoaudit`) |
20+
| `docker` | Local builds of the 5 service images |
21+
| Git Bash (Windows) or any POSIX shell | The launcher scripts in `tests/load/in-cluster/` are bash |
22+
| AKS metrics-server present | HPA needs it. Default on AKS, no action. |
23+
| AKS↔ACR pull permission | Either ACR attached to AKS via `az aks update --attach-acr`, or imagePullSecret. |
24+
25+
## 1. Namespace + Secret
26+
27+
```bash
28+
kubectl create namespace autoaudit
29+
30+
# Generate and create the autoaudit-secrets Secret (4 keys the chart requires).
31+
# Keep /tmp/autoaudit-creds.env somewhere safe — Secrets cannot be retrieved
32+
# in cleartext after creation.
33+
python -c "
34+
import secrets
35+
from cryptography.fernet import Fernet
36+
print(f'PG_PASS={secrets.token_urlsafe(24)}')
37+
print(f'JWT_SECRET={secrets.token_urlsafe(48)}')
38+
print(f'FERNET={Fernet.generate_key().decode()}')
39+
print(f'REDIS_PASS={secrets.token_urlsafe(24)}')
40+
" > /tmp/autoaudit-creds.env
41+
source /tmp/autoaudit-creds.env
42+
43+
kubectl create secret generic autoaudit-secrets -n autoaudit \
44+
--from-literal=postgresql-password="$PG_PASS" \
45+
--from-literal=jwt-secret-key="$JWT_SECRET" \
46+
--from-literal=encryption-key="$FERNET" \
47+
--from-literal=redis-password="$REDIS_PASS"
48+
```
49+
50+
## 2. KEDA + monitoring stack
51+
52+
Follow the install block in
53+
[`infrastructure/monitoring/in-cluster/README.md`](../../infrastructure/monitoring/in-cluster/README.md#install-order-matters).
54+
It installs (in order):
55+
56+
1. KEDA (namespace-scoped to `autoaudit`, with `prometheus.operator.enabled=true` so the queue-depth metric is exposed)
57+
2. Prometheus + kube-state-metrics
58+
3. celery-exporter
59+
4. Dashboard ConfigMap
60+
5. Grafana
61+
62+
## 3. Images in ACR
63+
64+
> **Heads-up for future students / maintainers.** `autoaudit.azurecr.io`
65+
> was set up as a **temporary registry for testing purposes only**. It
66+
> does **not** live in a long-lived or university-owned Azure
67+
> subscription and will be torn down once the scaling proof-of-concept
68+
> is complete. Do **not** assume it will still resolve, and do **not**
69+
> depend on the tags that are pushed there. The "move CI/CD to a
70+
> project-owned ACR" story is captured in
71+
> [saas-scaling-aks-platform.md §5](./saas-scaling-aks-platform.md#5-image--build-supply-chain);
72+
> when you re-run this runbook, substitute your own registry (any OCI
73+
> registry works — ACR, GHCR, Docker Hub) and update
74+
> `helm/autoaudit/values-scaling.yaml` accordingly.
75+
76+
The five custom images live in `autoaudit.azurecr.io/autoaudit/{backend-api,
77+
worker, frontend, opa, powershell-service}`. Existing tags (e.g.
78+
`20260523-1de7943`) were pushed during the original test and are still
79+
valid *while the registry exists*; only rebuild when chart-affecting code changes.
80+
81+
Rebuild + push (run from repo root):
82+
83+
```bash
84+
az acr login -n autoaudit
85+
86+
# Compose-built images. backend-api / worker / powershell-service / frontend
87+
docker compose --profile all build
88+
TAG="$(date +%Y%m%d)-$(git rev-parse --short=7 HEAD)"
89+
for svc in backend-api worker frontend powershell-service; do
90+
docker tag "autoaudit-${svc}:latest" "autoaudit.azurecr.io/autoaudit/${svc}:latest"
91+
docker tag "autoaudit-${svc}:latest" "autoaudit.azurecr.io/autoaudit/${svc}:${TAG}"
92+
docker push "autoaudit.azurecr.io/autoaudit/${svc}:latest"
93+
docker push "autoaudit.azurecr.io/autoaudit/${svc}:${TAG}"
94+
done
95+
96+
# Custom OPA image (policies baked in) — build context MUST be repo root.
97+
docker build -f engine/opa/Dockerfile \
98+
-t autoaudit.azurecr.io/autoaudit/opa:latest \
99+
-t autoaudit.azurecr.io/autoaudit/opa:${TAG} .
100+
docker push autoaudit.azurecr.io/autoaudit/opa:latest
101+
docker push autoaudit.azurecr.io/autoaudit/opa:${TAG}
102+
```
103+
104+
Important: the frontend MUST be built from `frontend/Dockerfile.prod` (nginx
105+
serving the built bundle). Compose uses `frontend/Dockerfile` (vite dev
106+
server on :3000) which mismatches the chart's Service port 80 — pods crash-loop.
107+
The compose `--profile all build` uses the dev Dockerfile, so for the chart
108+
you need:
109+
110+
```bash
111+
docker build -f frontend/Dockerfile.prod \
112+
-t autoaudit.azurecr.io/autoaudit/frontend:latest \
113+
-t autoaudit.azurecr.io/autoaudit/frontend:${TAG} ./frontend
114+
docker push autoaudit.azurecr.io/autoaudit/frontend:latest
115+
docker push autoaudit.azurecr.io/autoaudit/frontend:${TAG}
116+
```
117+
118+
## 4. Install the chart
119+
120+
```bash
121+
helm upgrade --install autoaudit ./helm/autoaudit \
122+
-n autoaudit \
123+
-f ./helm/autoaudit/values-scaling.yaml \
124+
--wait --timeout 10m
125+
```
126+
127+
`values-scaling.yaml` is the demo overlay — HPA + KEDA + PDBs + synthetic
128+
load levers all enabled, with maxReplicas tuned for a 2–3 node AKS cluster.
129+
Don't use `values-poc.yaml` for the scaling demo (it disables autoscaling).
130+
131+
## 5. Smoke check
132+
133+
```bash
134+
kubectl get deploy,hpa,pdb,scaledobject -n autoaudit
135+
```
136+
137+
Expected: **9 Deployments** (5 services + 3 worker queues + redis), **1 StatefulSet** (postgresql),
138+
**4 HPAs** + **3 KEDA-managed HPAs** = 7 HPA objects total, **7 PDBs**, **3 ScaledObjects**
139+
all `READY=True`, **1 TriggerAuthentication**.
140+
141+
## 6. Fire load tests
142+
143+
Per [`tests/load/in-cluster/README.md`](../../tests/load/in-cluster/README.md#fire-it):
144+
145+
```bash
146+
# Full demo (api-baseline → 60s cooldown → scan-fanout)
147+
bash tests/load/in-cluster/demo.sh
148+
149+
# Or one phase at a time
150+
bash tests/load/in-cluster/run.sh api-baseline.js
151+
bash tests/load/in-cluster/run.sh scan-fanout.js -e N_GRAPH=150 -e N_POWERSHELL=50
152+
```
153+
154+
## Teardown
155+
156+
```bash
157+
# 1. The chart (delete PVC so a fresh install gets a fresh database).
158+
helm -n autoaudit uninstall autoaudit
159+
kubectl delete pvc -n autoaudit -l app.kubernetes.io/instance=autoaudit
160+
161+
# 2. Monitoring stack (per the in-cluster monitoring README teardown).
162+
helm -n autoaudit uninstall grafana prometheus
163+
kubectl -n autoaudit delete -f infrastructure/monitoring/in-cluster/celery-exporter.yaml
164+
kubectl -n autoaudit delete configmap autoaudit-scaling-dashboard
165+
166+
# 3. KEDA (cluster-wide CRDs).
167+
helm -n keda uninstall keda
168+
kubectl delete namespace keda
169+
170+
# 4. autoaudit namespace (drops the Secrets too — re-create per step 1 next time).
171+
kubectl delete namespace autoaudit
172+
```
173+
174+
Then scale the AKS node pool back down via the Azure portal / CLI to stop
175+
the bill.
176+
177+
## Gotchas hit during the first run
178+
179+
The `Chart bugs found and fixed during AKS testing` section of
180+
[`scaling-after.md`](./scaling-after.md#chart-bugs-found-and-fixed-during-aks-testing)
181+
lists each one with the file/line. Skim it before re-running on a new
182+
cluster — most of the fixes are already merged into the chart, but the
183+
list explains the why.

engine/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ description = "AutoAudit Celery worker for compliance scanning"
55
requires-python = ">=3.10"
66
dependencies = [
77
"celery[redis]>=5.6.3",
8+
"gevent>=24.2.1",
89
"httpx>=0.26.0",
910
"msal>=1.36.0",
1011
"sqlalchemy>=2.0.0",

0 commit comments

Comments
 (0)