|
| 1 | +# DevOps Starter Kit — FastAPI • Docker • CI/CD • Azure • Terraform |
1 | 2 |
|
2 | | -# DevOps Starter Kit — FastAPI • Docker • CI/CD • Azure • Terraform (foundational) |
| 3 | + |
3 | 4 |
|
4 | | -This is a **sandbox** project to help you demonstrate real DevOps skills safely and honestly for interviews. |
| 5 | +A **sandbox** project to demonstrate real DevOps skills safely and honestly for interviews. |
5 | 6 |
|
6 | | -## What you get |
7 | | -- **FastAPI** app with `/health` |
| 7 | +--- |
| 8 | + |
| 9 | +## What’s inside |
| 10 | +- **FastAPI** app with `/health` and a root redirect (`/` → `/health`) |
8 | 11 | - **Dockerfile** and `docker-compose.yml` |
9 | 12 | - **Pytest** unit test |
10 | | -- **GitHub Actions** CI (build + test + lint) — with commented sections for **SonarCloud** and **Azure Web App** deploy |
11 | | -- **Terraform (foundational)** to provision an Azure Resource Group, Linux App Service Plan, and Web App |
12 | | -- **README** instructions + rollback notes |
| 13 | +- **GitHub Actions** CI/CD (lint + test + **deploy to Azure Web App**) |
| 14 | +- **Terraform (foundational)**: Azure Resource Group, Linux App Service Plan, Web App |
| 15 | +- **README** instructions, rollback notes, and **evidence** screenshots |
| 16 | + |
| 17 | +> ⚠️ Never commit secrets. Use GitHub **Secrets**. |
13 | 18 |
|
14 | | -> ⚠️ Auth/secrets are placeholders. Don’t commit real credentials. |
| 19 | +--- |
| 20 | + |
| 21 | +## Prereqs |
| 22 | +- Python **3.11+** |
| 23 | +- Git |
| 24 | +- (Optional) Docker Desktop |
| 25 | +- Azure CLI (`az`) and Terraform (only needed if you run Terraform locally) |
15 | 26 |
|
16 | 27 | --- |
17 | 28 |
|
18 | | -## 1) Run locally (no Docker) |
19 | | -```bash |
20 | | -python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate |
| 29 | +## 1) Run locally |
| 30 | + |
| 31 | +### Windows (PowerShell) |
| 32 | +```powershell |
| 33 | +python -m venv .venv |
| 34 | +.\.venv\Scripts\Activate.ps1 |
21 | 35 | pip install -r requirements.txt |
22 | 36 | uvicorn app.main:app --reload |
23 | | -# Visit http://127.0.0.1:8000/health |
| 37 | +# http://127.0.0.1:8000/health |
| 38 | +
|
| 39 | +``` |
| 40 | + |
| 41 | +### MacOS / Linux |
| 42 | +``` |
| 43 | +python -m venv .venv && source .venv/bin/activate |
| 44 | +pip install -r requirements.txt |
| 45 | +uvicorn app.main:app --reload |
| 46 | +# http://127.0.0.1:8000/health |
| 47 | +
|
24 | 48 | ``` |
25 | 49 |
|
26 | 50 | ## 2) Run with Docker |
27 | | -```bash |
28 | 51 | docker compose up --build |
29 | | -# Visit http://127.0.0.1:8000/health |
30 | 52 | ``` |
| 53 | +http://127.0.0.1:8000/health |
| 54 | +``` |
| 55 | + |
| 56 | + |
31 | 57 |
|
32 | 58 | ## 3) Tests |
33 | | -```bash |
| 59 | +``` |
34 | 60 | pytest -q |
35 | 61 | ``` |
36 | 62 |
|
37 | | ---- |
38 | 63 |
|
39 | | -## 4) GitHub Actions CI |
40 | | -Workflow file: `.github/workflows/ci-cd.yml` |
| 64 | +## 4) CI/CD with GitHub Actions |
41 | 65 |
|
42 | | -- Runs on push/PR: setup Python → install deps → lint (ruff) → tests. |
43 | | -- **Optional SonarCloud** step is commented. To enable: |
44 | | - 1. Create a SonarCloud project. |
45 | | - 2. Add repo **Secrets**: `SONAR_TOKEN`. |
46 | | - 3. Add repo **Variables**: `SONAR_ORG`, `SONAR_PROJECT_KEY`. |
47 | | - 4. Uncomment the Sonar step and `sonar-project.properties` content. |
| 66 | +Workflow: .github/workflows/ci-cd.yml |
48 | 67 |
|
49 | | -- **Optional Deploy to Azure Web App** step is commented. To enable: |
50 | | - 1. Create an Azure Web App (Linux). |
51 | | - 2. In Azure Portal → Web App → **Get publish profile**. |
52 | | - 3. Add repo **Secret**: `AZURE_WEBAPP_PUBLISH_PROFILE` with the XML content. |
53 | | - 4. Set `AZURE_WEBAPP_NAME` in repo **Variables**. |
54 | | - 5. Uncomment the deploy step. |
55 | | - 6. Protect `production` environment in GitHub (requires manual approval). |
| 68 | +Pipeline |
56 | 69 |
|
57 | | ---- |
| 70 | +- On push/PR to main: Setup Python → install deps → ruff → pytest |
58 | 71 |
|
59 | | -## 5) Terraform (foundational) |
60 | | -**Folder:** `terraform/` |
| 72 | +- Then deploys to Azure Web App using a Publish Profile secret |
| 73 | + |
| 74 | +Required repo secret (one-time) |
61 | 75 |
|
62 | | -> You need an Azure subscription. This creates: Resource Group, Linux App Service Plan (B1 by default), and a Web App. |
| 76 | +-AZURE_WEBAPP_PUBLISH_PROFILE → paste the XML from Azure Portal → App Service → Get publish profile |
| 77 | +(or via CLI: az webapp deployment list-publishing-profiles --resource-group <rg> --name <webapp> --xml) |
63 | 78 |
|
64 | | -```bash |
| 79 | + |
| 80 | +App name |
| 81 | + |
| 82 | +-Hard-coded in the workflow deploy job: |
| 83 | +``` |
| 84 | +env: |
| 85 | + AZURE_WEBAPP_NAME: devops-starter-webapp-dev31 |
| 86 | +``` |
| 87 | +Change here if you rename the app. |
| 88 | + |
| 89 | + |
| 90 | +## 5) Terraform (foundational) |
| 91 | +``` |
| 92 | +Folder: terraform/ — Creates Resource Group, Linux App Service Plan, Web App. |
| 93 | +``` |
| 94 | +``` |
65 | 95 | cd terraform |
66 | | -cp terraform.tfvars.example terraform.tfvars # edit values |
| 96 | +# Windows: copy terraform.tfvars.example terraform.tfvars |
| 97 | +# macOS/Linux: |
| 98 | +cp terraform.tfvars.example terraform.tfvars |
| 99 | +# Edit terraform.tfvars: |
| 100 | +# - subscription_id = "<your-sub-id>" |
| 101 | +# - webapp_name = "devops-starter-webapp-<unique>" |
| 102 | +# - location = "Central India" (or nearest) |
| 103 | +# - sku_name = "F1" (Free; if unavailable, use "B1") |
67 | 104 | terraform init |
68 | 105 | terraform plan |
69 | 106 | terraform apply |
70 | 107 | ``` |
71 | 108 |
|
72 | | -**Rollback:** `terraform destroy` — or in emergencies, stop the Web App (Portal → Your Web App → Stop). |
| 109 | +Outputs |
| 110 | +``` |
| 111 | +terraform output webapp_url |
| 112 | +``` |
73 | 113 |
|
74 | | ---- |
| 114 | +Refresh-only apply (nice for screenshots) |
| 115 | +``` |
| 116 | +terraform apply -refresh-only |
| 117 | +``` |
75 | 118 |
|
76 | | -## 6) Rollback strategy (App Service) |
77 | | -- If using **Deploy Slots** (recommended), swap back to last known good slot. |
78 | | -- If using single slot, re-deploy last **green artifact** from Actions → `Download artifact` → redeploy. |
79 | | -- Keep a simple **RUNBOOK.md** with exact commands (add your own screenshots). |
| 119 | +Rollback / clean-up |
| 120 | +terraform destroy |
| 121 | +``` |
| 122 | +# Emergency: stop Web App in Portal |
| 123 | +``` |
80 | 124 |
|
81 | | ---- |
| 125 | +## 6) App Service configuration (how it boots) |
82 | 126 |
|
83 | | -## 7) What to screenshot for your portfolio |
84 | | -- Successful Actions run (build + test). |
85 | | -- SonarCloud Quality Gate (if enabled). |
86 | | -- Docker container running locally + `/health` response. |
87 | | -- Azure Web App → Deployment Center logs (if deployed). |
88 | | -- Terraform `plan`/`apply` output + resources in Portal. |
89 | | -- Azure Monitor alert rule and a fired test alert email. |
| 127 | +- Startup command required for FastAPI on App Service: |
| 128 | +``` |
| 129 | +gunicorn -w 2 -k uvicorn.workers.UvicornWorker app.main:app |
| 130 | +``` |
90 | 131 |
|
91 | | ---- |
| 132 | +(Set via Terraform/CLI.) |
92 | 133 |
|
93 | | -## 8) Next steps / Extensions |
94 | | -- Add a staging slot and enable **slot swap** in deploy step. |
95 | | -- Remote state for Terraform (Azure Storage). |
96 | | -- Add **PostgreSQL** (Azure Flexible Server) or containerized Postgres with secrets. |
97 | | -- Wire **Azure Monitor** alerts for 5xx spikes and CPU > 80%. |
98 | | -- Add basic **cost tags** to resources. |
| 134 | +- HTTPS only: |
| 135 | +``` |
| 136 | +az webapp update -g rg-devops-starter -n devops-starter-webapp-dev31 --set httpsOnly=true |
| 137 | +``` |
| 138 | + |
| 139 | +- Free (F1) plan: always_on = false and cold starts are normal after idle. |
| 140 | + |
| 141 | + |
| 142 | +## 7) Proofs & Screenshots |
| 143 | + |
| 144 | +Place all images in /evidence: |
| 145 | + |
| 146 | +- CI → CD success: |
| 147 | + |
| 148 | +- Deploy logs: |
| 149 | + |
| 150 | +- Terraform apply (refresh-only) & outputs: |
| 151 | + |
| 152 | +- Azure resources: |
| 153 | + |
| 154 | +- Live health: |
| 155 | + |
| 156 | +(Optional extras) |
| 157 | + |
| 158 | +- Secrets present (name only): evidence/06-secrets-present.png |
| 159 | + |
| 160 | +- Startup command set: evidence/07-startup-cmd.png |
| 161 | + |
| 162 | +- App logs (startup): evidence/08-log-startup.png |
| 163 | + |
| 164 | +- Alerts (rule + email): evidence/09-alert-rule.png, evidence/10-alert-email.png |
| 165 | + |
| 166 | + |
| 167 | + |
| 168 | +## 8) Extensions (great interview talking points) |
| 169 | + |
| 170 | +- Deployment Slots + slot swap for zero-downtime rollouts |
| 171 | + |
| 172 | +- Remote state for Terraform (Azure Storage + SAS) |
| 173 | + |
| 174 | +- PostgreSQL (Azure Flexible Server) or containerized DB (secrets in Key Vault) |
| 175 | + |
| 176 | +- Azure Monitor metrics/alerts (CPU, 5xx, latency p95, availability SLO) |
| 177 | + |
| 178 | +- Cost tags on resources (project, owner, env) |
| 179 | + |
| 180 | + |
| 181 | + |
| 182 | +## 9) Troubleshooting quickies |
| 183 | + |
| 184 | +- Deploy step: “Missing AZURE_WEBAPP_PUBLISH_PROFILE” |
| 185 | + Re-create the repo secret and paste the full XML (don’t trim). |
| 186 | + |
| 187 | +- App returns 500 after deploy |
| 188 | + Ensure startup command is set, then restart: |
| 189 | +``` |
| 190 | +az webapp config set -g rg-devops-starter -n devops-starter-webapp-dev31 --startup-file "gunicorn -w 2 -k uvicorn.workers.UvicornWorker app.main:app" |
| 191 | +
|
| 192 | +az webapp restart -g rg-devops-starter -n devops-starter-webapp-dev31 |
| 193 | +``` |
| 194 | + |
| 195 | +- Pytest import error for app |
| 196 | + Ensure app/__init__.py exists and CI sets PYTHONPATH if needed. |
| 197 | + |
| 198 | +- requirements.txt parse error |
| 199 | + Re-save as UTF-8/ASCII (no UTF-16 BOM / null bytes). |
| 200 | + |
| 201 | + |
| 202 | + |
| 203 | + |
| 204 | +## 10) License |
| 205 | + |
| 206 | +MIT (or your choice). |
| 207 | +``` |
| 208 | +::contentReference[oaicite:0]{index=0} |
| 209 | +``` |
0 commit comments