|
| 1 | +# UMS Kubernetes Deployment Plan |
| 2 | + |
| 3 | +This document captures the full deployment plan for the UMS monorepo to a local Docker Desktop Kubernetes cluster, using PostgreSQL, Redis, and an observability stack (Prometheus, Grafana, Loki, Jaeger). |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +# Deploy UMS to Local Docker Desktop Kubernetes (full stack – PostgreSQL) |
| 8 | + |
| 9 | +## Goal |
| 10 | +Deploy the entire UMS monorepo (backend API, frontend web app, and supporting infrastructure) to the local Kubernetes cluster provided by Docker Desktop, ensuring: |
| 11 | +- Super‑Admin user is seeded with password `root` |
| 12 | +- Observability stack (metrics, logs, tracing) is available |
| 13 | +- Caching layer (Redis) is provisioned |
| 14 | +- **PostgreSQL** is used as the relational database |
| 15 | + |
| 16 | +## User Review Required |
| 17 | +- **Docker image tags** you would like to use (e.g., `latest` or a specific git‑sha). |
| 18 | +- **Kubernetes namespace** (recommended: `ums-local`). |
| 19 | +- Whether the admin password should be stored in a **Kubernetes Secret** or remain hard‑coded for dev. |
| 20 | +- **Ingress host** for the UI (e.g., `ums.local`). |
| 21 | +- **TLS** for local dev (self‑signed is fine). |
| 22 | +- **Observability components** you want (Prometheus + Grafana, Loki, Jaeger). |
| 23 | +- **Redis cache** – single‑node is enough for dev. |
| 24 | + |
| 25 | +## Open Questions |
| 26 | +> [!IMPORTANT] |
| 27 | +> **Namespace** – target namespace? |
| 28 | +> |
| 29 | +> > (Recommended) `ums-local` |
| 30 | +> |
| 31 | +> **Ingress host** – hostname to reach the UI? |
| 32 | +> |
| 33 | +> > (Recommended) `ums.local` |
| 34 | +> |
| 35 | +> **TLS** – enable self‑signed TLS? |
| 36 | +> |
| 37 | +> > (Recommended) `yes` |
| 38 | +> |
| 39 | +> **Admin password secret** – store in a Secret? |
| 40 | +> |
| 41 | +> > (Recommended) `ums-admin-secret` with key `ADMIN_PASSWORD` |
| 42 | +> |
| 43 | +> **Observability stack** – include which tools? |
| 44 | +> |
| 45 | +> > (Recommended) Prometheus, Grafana, Loki, Jaeger |
| 46 | +> |
| 47 | +> **Redis cache** – single instance? |
| 48 | +> |
| 49 | +> > (Recommended) `true` |
| 50 | +
|
| 51 | +## Proposed Changes |
| 52 | +--- |
| 53 | +### Core Services |
| 54 | +#### Backend (API) |
| 55 | +- Add a **Dockerfile** (multi‑stage) that builds the .NET 10 API and runs migrations on startup. |
| 56 | +- Expose **Prometheus metrics** endpoint (`/metrics`) via `UsePrometheus()` (already in code). |
| 57 | +- Add **OpenTelemetry** instrumentation for tracing (Jaeger exporter). |
| 58 | +- Environment variables for observability: |
| 59 | + - `OTEL_EXPORTER_JAEGER_ENDPOINT=http://ums-jaeger:14268/api/traces` |
| 60 | + - `OTEL_METRICS_EXPORTER=prometheus` |
| 61 | +- Helm values (`values-backend.yaml`): |
| 62 | + ```yaml |
| 63 | + image: |
| 64 | + repository: ums/backend |
| 65 | + tag: <tag> |
| 66 | + env: |
| 67 | + - name: ASPNETCORE_ENVIRONMENT |
| 68 | + value: Production |
| 69 | + - name: ConnectionStrings__Default |
| 70 | + valueFrom: |
| 71 | + secretKeyRef: |
| 72 | + name: ums-db-secret |
| 73 | + key: CONNECTION_STRING |
| 74 | + - name: ADMIN_PASSWORD |
| 75 | + valueFrom: |
| 76 | + secretKeyRef: |
| 77 | + name: ums-admin-secret |
| 78 | + key: ADMIN_PASSWORD |
| 79 | + - name: REDIS_CONNECTION |
| 80 | + value: redis://ums-redis:6379 |
| 81 | + - name: OTEL_EXPORTER_JAEGER_ENDPOINT |
| 82 | + value: http://ums-jaeger:14268/api/traces |
| 83 | + - name: OTEL_METRICS_EXPORTER |
| 84 | + value: prometheus |
| 85 | + service: |
| 86 | + type: ClusterIP |
| 87 | + replicaCount: 1 |
| 88 | + resources: |
| 89 | + limits: |
| 90 | + cpu: "500m" |
| 91 | + memory: "512Mi" |
| 92 | + requests: |
| 93 | + cpu: "250m" |
| 94 | + memory: "256Mi" |
| 95 | + livenessProbe: |
| 96 | + httpGet: |
| 97 | + path: /healthz |
| 98 | + port: 80 |
| 99 | + initialDelaySeconds: 10 |
| 100 | + periodSeconds: 10 |
| 101 | + readinessProbe: |
| 102 | + httpGet: |
| 103 | + path: /ready |
| 104 | + port: 80 |
| 105 | + initialDelaySeconds: 5 |
| 106 | + periodSeconds: 5 |
| 107 | + ``` |
| 108 | +
|
| 109 | +#### Frontend (Web App) |
| 110 | +- Ensure production build (`npm run build`). |
| 111 | +- Dockerfile using Nginx to serve static files. |
| 112 | +- Helm `values-frontend.yaml`: |
| 113 | + ```yaml |
| 114 | + image: |
| 115 | + repository: ums/frontend |
| 116 | + tag: <tag> |
| 117 | + service: |
| 118 | + type: ClusterIP |
| 119 | + ingress: |
| 120 | + enabled: true |
| 121 | + hosts: |
| 122 | + - host: ums.local |
| 123 | + paths: |
| 124 | + - path: / |
| 125 | + pathType: Prefix |
| 126 | + tls: |
| 127 | + - hosts: |
| 128 | + - ums.local |
| 129 | + secretName: ums-tls |
| 130 | + resources: |
| 131 | + limits: |
| 132 | + cpu: "200m" |
| 133 | + memory: "256Mi" |
| 134 | + requests: |
| 135 | + cpu: "100m" |
| 136 | + memory: "128Mi" |
| 137 | + ``` |
| 138 | + |
| 139 | +### Supporting Infrastructure |
| 140 | +#### PostgreSQL |
| 141 | +- Deploy the official `postgres:15-alpine` image. |
| 142 | +- Helm values (`values-db.yaml`): |
| 143 | + ```yaml |
| 144 | + image: |
| 145 | + repository: postgres |
| 146 | + tag: 15-alpine |
| 147 | + env: |
| 148 | + - name: POSTGRES_USER |
| 149 | + value: postgres |
| 150 | + - name: POSTGRES_PASSWORD |
| 151 | + valueFrom: |
| 152 | + secretKeyRef: |
| 153 | + name: ums-db-secret |
| 154 | + key: POSTGRES_PASSWORD |
| 155 | + - name: POSTGRES_DB |
| 156 | + value: ums |
| 157 | + persistence: |
| 158 | + enabled: true |
| 159 | + size: 5Gi |
| 160 | + ``` |
| 161 | +- Create a **Secret** `ums-db-secret` with keys: |
| 162 | + - `POSTGRES_PASSWORD` – your dev password (e.g., `postgres`). |
| 163 | + - `CONNECTION_STRING` – `Host=ums-postgres;Database=ums;Username=postgres;Password=$(POSTGRES_PASSWORD);` |
| 164 | + |
| 165 | +#### Redis Cache |
| 166 | +- Deploy the official `redis:7-alpine` chart (stand‑alone). |
| 167 | +- Helm values (`values-redis.yaml`): |
| 168 | + ```yaml |
| 169 | + architecture: standalone |
| 170 | + replicaCount: 1 |
| 171 | + auth: |
| 172 | + enabled: false |
| 173 | + ``` |
| 174 | + |
| 175 | +#### Observability Stack |
| 176 | +- **Prometheus + Grafana**: `kube-prometheus-stack` chart. |
| 177 | +- **Loki**: `grafana/loki-stack` chart for log aggregation. |
| 178 | +- **Jaeger**: `jaegertracing/jaeger` chart for tracing. |
| 179 | +- Provide a `values-observability.yaml` that enables ServiceMonitors for the API and Nginx, and sets scrape intervals. |
| 180 | + |
| 181 | +### Helm Chart Structure |
| 182 | +``` |
| 183 | +ums-helm/ |
| 184 | +├─ Chart.yaml |
| 185 | +├─ templates/ |
| 186 | +│ ├─ backend-deployment.yaml |
| 187 | +│ ├─ backend-service.yaml |
| 188 | +│ ├─ frontend-deployment.yaml |
| 189 | +│ ├─ frontend-service.yaml |
| 190 | +│ ├─ ingress.yaml |
| 191 | +│ ├─ postgres-deployment.yaml |
| 192 | +│ ├─ redis-deployment.yaml |
| 193 | +│ └─ observability/ (sub‑charts imports) |
| 194 | +└─ values/ |
| 195 | + ├─ backend.yaml |
| 196 | + ├─ frontend.yaml |
| 197 | + ├─ postgres.yaml |
| 198 | + ├─ redis.yaml |
| 199 | + └─ observability.yaml |
| 200 | +``` |
| 201 | +- Each sub‑chart pulls in the appropriate values files. |
| 202 | +- Include a **Job** (`ums-seeder`) that runs the seeder commands (`dotnet run --project Ums.Api.csproj` with a `--seed` flag) if you need to reseed data. |
| 203 | +
|
| 204 | +## Verification Plan |
| 205 | +### Automated Tests |
| 206 | +1. **Build images** locally: |
| 207 | + ```bash |
| 208 | + docker build -t ums/backend:latest ./src/apps/ums.api |
| 209 | + docker build -t ums/frontend:latest ./src/apps/ums.web-app |
| 210 | + ``` |
| 211 | +2. **Create secrets**: |
| 212 | + ```bash |
| 213 | + kubectl create secret generic ums-db-secret \ |
| 214 | + --from-literal=POSTGRES_PASSWORD=postgres \ |
| 215 | + --from-literal=CONNECTION_STRING="Host=ums-postgres;Database=ums;Username=postgres;Password=postgres;" |
| 216 | + kubectl create secret generic ums-admin-secret --from-literal=ADMIN_PASSWORD=root |
| 217 | + ``` |
| 218 | +3. **Render manifests** and validate: |
| 219 | + ```bash |
| 220 | + helm template ./ums-helm \ |
| 221 | + -f values/backend.yaml \ |
| 222 | + -f values/frontend.yaml \ |
| 223 | + -f values/postgres.yaml \ |
| 224 | + -f values/redis.yaml \ |
| 225 | + -f values/observability.yaml | kubeval |
| 226 | + ``` |
| 227 | +4. **Install to local cluster**: |
| 228 | + ```bash |
| 229 | + helm upgrade --install ums ./ums-helm \ |
| 230 | + -n ums-local --create-namespace \ |
| 231 | + -f values/backend.yaml \ |
| 232 | + -f values/frontend.yaml \ |
| 233 | + -f values/postgres.yaml \ |
| 234 | + -f values/redis.yaml \ |
| 235 | + -f values/observability.yaml |
| 236 | + ``` |
| 237 | +5. **Port‑forward the UI** and test: |
| 238 | + ```bash |
| 239 | + kubectl port-forward svc/ums-frontend 8080:80 -n ums-local |
| 240 | + ``` |
| 241 | + Open `http://ums.local` (or `http://localhost:8080`). |
| 242 | +6. **Login** with `admin@ums.local` / `root` and verify the full admin suite appears. |
| 243 | +7. **Check observability**: |
| 244 | + - Grafana (`http://ums-grafana:3000`) shows dashboards for API metrics and Nginx logs. |
| 245 | + - Jaeger UI (`http://ums-jaeger:16686`) displays traces for the login flow. |
| 246 | + - Prometheus scrapes `/metrics` from the API. |
| 247 | + - Loki receives Nginx access/error logs. |
| 248 | +8. **Validate cache**: |
| 249 | + ```bash |
| 250 | + kubectl exec -it $(kubectl get pod -l app=ums-redis -n ums-local -o jsonpath='{.items[0].metadata.name}') -- redis-cli ping |
| 251 | + ``` |
| 252 | + Should return `PONG`. |
| 253 | + |
| 254 | +### Manual Checks |
| 255 | +- Force‑password‑reset endpoint works and the flow forces a password change on first login. |
| 256 | +- Verify that the PostgreSQL schema was created correctly (tables, constraints, migrations applied). |
| 257 | +- Ensure that the seeder job (`ums-seeder`) can be re‑run without errors. |
| 258 | + |
| 259 | +--- |
| 260 | +**Next steps:** |
| 261 | +1. Confirm the open questions above (tags, namespace, secrets, observability choices). |
| 262 | +2. Once approved, we will run the Docker builds, create the Kubernetes secrets, render the Helm chart, and apply it to your local Docker Desktop Kubernetes cluster. |
| 263 | + |
| 264 | +*When you’re ready, just provide the values you want to use or any adjustments you need.* |
0 commit comments