- HCL 58.9%
- Python 27.6%
- HTML 7.8%
- Shell 3.9%
- Dockerfile 1.8%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| ansible | ||
| app | ||
| docker | ||
| frontend | ||
| terraform | ||
| tests | ||
| .gitignore | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| renovate.json | ||
| VERSION | ||
Calculator API - DevOps Project
Prosta REST API kalkulatora zbudowana w FastAPI, wdrożona na AWS ECS Fargate przy użyciu w pełni zautomatyzowanego pipeline CI/CD opartego na Forgejo Actions.
Opis projektu
Aplikacja udostępnia trzy endpointy:
GET /health- status aplikacji (liveness check), zawierahostnamekonteneraGET /version- wersja aktualnie wdrożonego obrazu, też zhostnamePOST /calculate- operacje arytmetyczne (add, subtract, multiply, divide)
Pole hostname w /health//version pozwala zobaczyć High Availability w akcji - prod działa jako 2 zadania ECS za jednym ALB (desired_count = 2), więc kolejne odświeżenia curl prod_url/health powinny pokazywać różne wartości hostname (różne kontenery odpowiadające przez round-robin).
Architektura
flowchart TD
A[Push -> develop] --> B[pytest + Ruff + Bandit]
B --> C[docker build + Trivy CVE/SBOM]
C --> D["ECR push :preprod"]
D --> E[ECS preprod deploy]
E --> F[Playwright e2e]
F --> G["PR develop -> main<br/>code review"]
G --> H[Merge do main]
H --> I[pytest + Ruff + Bandit]
I --> J[docker build + Trivy CVE/SBOM]
J --> K["ECR push :latest"]
K --> L[ECS prod deploy]
L --> M[Playwright smoke]
flowchart LR
ECR[("Amazon ECR<br/>:preprod / :latest / :sha")]
subgraph Cluster["ECS Fargate - jeden klaster"]
Prod["calculator-api-prod<br/>2x task (HA)"]
Preprod["calculator-api-preprod<br/>1x task"]
end
ECR --> Prod
ECR --> Preprod
ALB["Application Load Balancer"]
ALB -->|":80"| Prod
ALB -->|":8080"| Preprod
S3[("S3 - statyczny frontend")] -. fetch API .-> ALB
flowchart TB
subgraph AWS["AWS"]
EC2["EC2 t3.micro (free tier)"]
DinD["docker-in-docker"]
Runner1["Forgejo Runner<br/>label aws:host"]
EC2 --- DinD
EC2 --- Runner1
end
subgraph External["Poza AWS - self-hosted"]
Forgejo["Forgejo<br/>git.jwerwinski.pl"]
Runner2["Forgejo Runner<br/>label aws:host"]
Renovate["Renovate<br/>cron: poniedzialek"]
end
Runner1 -. rejestracja .-> Forgejo
Runner2 -. lokalnie .-> Forgejo
Definicje pipeline'u: .forgejo/workflows/ci.yml (PR → main/develop), deploy-preprod.yml (push → develop), deploy-prod.yml (push → main).
Postawienie projektu od zera
-
Self-hosted Forgejo musi już działać - patrz Forgejo. Wymagane: Actions, Runners, Secrets.
-
Wypchnij kod do Forgejo - w Forgejo:
+→ New Repository (nazwacalculator-api, bez "Initialize Repository"). Lokalnie:git init git add -A && git commit -m "Initial commit" git remote add origin https://<TWOJ_FORGEJO>/<USER>/calculator-api.git git branch -M main git push -u origin main git checkout -b develop && git push -u origin develop -
tofu apply- patrz Infrastruktura (OpenTofu). Stawia VPC, ECR (pusty), ECS+ALB, EC2 dla runnera, bucket S3 (pusty). Zapisztofu output- będzie potrzebny w punktach 5 i 7. -
Zarejestruj runner(y) w Forgejo - Site Administration → Actions → Runners → Create new runner. Zapisz
uuiditoken. -
Skonfiguruj runner(y) Ansiblem - sekcja Rejestracja Forgejo Runnera, podsekcja "Automatyczna konfiguracja (Ansible)". Po tym kroku
Manage runnersw Forgejo powinno pokazywać statusIdle/Online, nieOffline. -
Dodaj sekrety w repo Forgejo - patrz Secrets w Forgejo. Wartości
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYztofu output ci_access_key_id/tofu output -raw ci_secret_access_key,PREPROD_URL/PROD_URL/FRONTEND_BUCKETz odpowiednichtofu output. -
Pierwszy push -
git push origin developodpaladeploy-preprod.yml(build → ECR:preprod→ ECS preprod → e2e). Jeśli przejdzie, PRdevelop → main→ po mergedeploy-prod.yml(→ ECS prod → upload frontendu na S3 → smoke testy). Przed tym pierwszym pushem ECR jest pusty, więc ECS będzie zwracać 503 - to oczekiwane, mija po pierwszym udanym przebiegu. -
Zweryfikuj -
curl <prod_url>/health,/version, otwórz<frontend_url>w przeglądarce.
Wymagania lokalne
- Docker
- Python 3.12+
- OpenTofu >= 1.6
- AWS CLI v2
Uruchomienie lokalne
docker build -t calculator-api .
docker run -p 8000:8000 calculator-api
curl http://localhost:8000/health
curl http://localhost:8000/version
curl -X POST http://localhost:8000/calculate \
-H "Content-Type: application/json" \
-d '{"a": 10, "b": 5, "operation": "add"}'
Testy
pip install -r tests/requirements-test.txt
# Testy jednostkowe
pytest tests/test_main.py -v
# Lint + SAST
ruff check app tests
bandit -r app
# Testy e2e na preprodzie
APP_URL=http://<ALB_DNS>:8080 pytest tests/test_e2e.py -v
# Smoke testy na prodzie
APP_URL=http://<ALB_DNS> pytest tests/test_e2e.py -v -k "health or version"
Demo High Availability
for i in $(seq 1 10); do curl -s http://<ALB_DNS>/health | jq .hostname; done
Przy 2 zadaniach ECS za ALB powinny się tu przewijać dwie różne wartości hostname.
Infrastruktura (OpenTofu)
cd terraform/environments/prod
tofu init
tofu plan
tofu apply
tofu output
Flow pracy z branchami
# Praca na develop
git checkout develop
# ... zmiany ...
git push origin develop
# → pipeline: build → preprod deploy → e2e testy
# Po przejściu testów - PR do main w Forgejo
# → code review → merge
# → pipeline: build → prod deploy → smoke testy
Secrets w Forgejo
Settings → Secrets → Actions:
| Secret | Opis |
|---|---|
AWS_ACCESS_KEY_ID |
Klucz AWS |
AWS_SECRET_ACCESS_KEY |
Secret AWS |
PREPROD_URL |
URL preprod (http://ALB:8080) |
PROD_URL |
URL prod (http://ALB) |
FRONTEND_BUCKET |
Nazwa bucketu S3 z frontendem (tofu output frontend_bucket_name) |
RENOVATE_TOKEN |
Token Forgejo dla Renovate bota |
Rejestracja Forgejo Runnera
Runner działa jako kontener Docker (wg dokumentacji Forgejo): osobny kontener docker:dind jako silnik Docker, runner łączy się z nim po TCP. Obraz runnera (docker/Dockerfile.runner) ma wbudowane docker, aws i trivy, więc joby z labelem aws:host (czyli odpalane bezpośrednio w kontenerze runnera, bez zagnieżdżonego kontenera na job) mają od razu dostęp do tych narzędzi.
Automatyczna konfiguracja (Ansible) - zalecane
Terraform stawia maszynę EC2; cała konfiguracja runnera (build obrazu, wpisanie tokenu rejestracyjnego, start daemona) jest w ansible/playbook.yml - idempotentny, bezpieczny do wielokrotnego odpalenia (np. po tofu destroy + tofu apply).
cd ansible
cp group_vars/all.yml.example group_vars/all.yml
./generate-inventory.sh # czyta IP z `tofu output runner_ips`
ansible-playbook -i inventory.ini playbook.yml
group_vars/all.yml i inventory.ini są w .gitignore (zawierają token i IP) - commitowany jest tylko .example.
Konfiguracja Renovate
Renovate działa razem z Forgejo na własnym serwerze (poza tym Terraformem). Reguły (harmonogram, automerge patchy) są już zdefiniowane w renovate.json w korzeniu repo - Renovate odczytuje je automatycznie przy skanowaniu repozytorium, nie trzeba ich osobno montować do kontenera.
Renovate otwiera PR-y codziennie w poniedzialek. Patch releases - automerge, minor/major - wymagają zatwierdzenia.
Wazne informacje:
Reguła main:
Disable push - nikt nie może pushować bezpośrednio na main Zmiany na main mogą trafić tylko przez Pull Request
Reguła develop:
Enable push - można pushować i tworzyć nowe branche od develop Mergeowanie do develop działa normalnie przez PR
Linki
| Środowisko | URL |
|---|---|
| Prod | tofu output prod_url |
| Preprod | tofu output preprod_url |
| Frontend (kalkulator) | tofu output frontend_url |
| Forgejo | https://git.jwerwinski.pl |
Demo linki
ecr_repo = "445606684565.dkr.ecr.us-west-2.amazonaws.com/calculator-api"
frontend_bucket_name = "calculator-api-frontend-445606684565"
frontend_url = "http://calculator-api-frontend-445606684565.s3-website-us-west-2.amazonaws.com"
preprod_url = "http://calculator-api-alb-3370378.us-west-2.elb.amazonaws.com:8080"
prod_url = "http://calculator-api-alb-3370378.us-west-2.elb.amazonaws.com"
runner_ips = [ "44.248.16.224", ]