Backend dịch vụ EmoStagram (FastAPI + SQLAlchemy), có healthcheck, metrics, migrations Alembic, CI/CD GitHub Actions và triển khai mẫu trên GKE.
- FastAPI, Uvicorn
- SQLAlchemy 2.x, Alembic
- PostgreSQL, Redis
- Pytest (unit/integration), Coverage
- Prometheus metrics (prometheus-fastapi-instrumentator)
- GitHub Actions (CI, CD → GKE Autopilot qua OIDC/WIF)
eq_test_backend/
app/ # mã nguồn FastAPI
k8s/ # manifest K8s (Kustomize)
.github/workflows/ # CI/CD pipelines
Dockerfile
requirements.txt # phục vụ dev/CI dạng pip
pyproject.toml # poetry (Dockerfile dùng poetry để build image)
- REST API v1 (xem
app/api/v1/), tài liệu OpenAPI tại/docs. - Health check:
/health(đính kèm metadata lần triển khai gần nhất) - Prometheus metrics:
/metrics - CORS cấu hình qua
CORS_ORIGINS(JSON list) - Job migrate trước khi rollout (K8s hook dạng Job)
- Python 3.12
- PostgreSQL cục bộ hoặc Docker (gợi ý dùng compose kèm repo)
# chạy Postgres + Prometheus + Grafana (tùy chọn)
docker compose -f docker-compose.yml up -d postgres
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
Tạo file .env (không commit) đặt tại eq_test_backend/:
DATABASE_URL=postgresql://eq_user:eq_pass@localhost:5432/eq_test
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=change-me
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
GOOGLE_CLIENT_ID=xxx
GOOGLE_CLIENT_SECRET=xxx
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1
Lưu ý: CORS cấu hình trong k8s/configmap.yaml khi chạy K8s; với local dev có thể để mặc định.
alembic upgrade head
python -c "from app.seed_data import seed; seed()"
uvicorn app.main:app --host 0.0.0.0 --port 5001 --reload
Mở:
- OpenAPI: http://localhost:5001/docs
- Health: http://localhost:5001/health
- Metrics: http://localhost:5001/metrics
pytest -q
CI sẽ chạy đầy đủ test và báo coverage (ngưỡng 40% trong pipeline mẫu).
Workflow CI Pipeline:
- Chạy unit/integration tests, security checks
- Build/push image đa kiến trúc (amd64, arm64) lên Docker Hub
- Tag:
latest(nhánh main) vàsha-<commit>(mọi commit) – CD dùng tagsha-<commit>để rollout chính xác
Workflow CD - Deploy to GKE:
- Trigger bởi
workflow_runcủa CI (khi CI xanh) hoặc chạy tay (workflow_dispatch) - OIDC + Workload Identity Federation (không cần lưu kubeconfig/JSON key)
- Các bước chính:
- Apply
k8s/configmap.yaml - Ghi metadata triển khai (DEPLOYED_AT/SHA/IMAGE/BY) vào ConfigMap
- Tạo/Update
Secret eq-backend-secretstừ GitHub Secrets - Apply
k8s/(Kustomize) → chạy Job migrate (Alembic) → rollout Deployment - Gửi Discord webhook trạng thái (thành công/thất bại, kèm EXTERNAL-IP)
- Apply
Yêu cầu Secrets (GitHub → Settings → Secrets and variables → Actions):
DOCKERHUB_USERNAME,DOCKERHUB_TOKENK8S_NAMESPACE(vd:eq-backend)DATABASE_URL,REDIS_URL,SECRET_KEY,ALGORITHM,ACCESS_TOKEN_EXPIRE_MINUTES,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,OPENAI_API_KEY,CORS_ORIGINSGCP_PROJECT,GKE_CLUSTER,GKE_LOCATIONWORKLOAD_IDENTITY_PROVIDER(dạngprojects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/github-pool/providers/github-provider)GCP_SA_EMAIL(vd:eq-backend-deployer@<PROJECT_ID>.iam.gserviceaccount.com)- (Tùy chọn)
DISCORD_WEBHOOK
Chi tiết cơ chế OIDC/WIF và cách tạo provider/SA: xem mục CD trong deployment.md.
Thư mục k8s/ đã bao gồm:
configmap.yaml: ENV non-secret (nhưENVIRONMENT,LOG_LEVEL,OPENAI_BASE_URL,CORS_ORIGINS)job-migrate.yaml: Job chạyalembic upgrade headtrước khi rolloutdeploy.yaml: Deployment + Service (Service =LoadBalancercho GKE)kustomization.yaml: gom resource + set image tag
Triển khai thủ công (khi đã có secret eq-backend-secrets):
kubectl create ns eq-backend 2>/dev/null || true
kubectl -n eq-backend apply -f k8s/configmap.yaml
kubectl -n eq-backend create secret generic eq-backend-secrets \
--from-env-file=.env.secrets \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k k8s/
kubectl -n eq-backend wait --for=condition=complete job/eq-backend-migrate --timeout=300s
kubectl -n eq-backend rollout status deploy/eq-backend
kubectl -n eq-backend get svc eq-backend -w
Gợi ý tiết kiệm chi phí khi dùng GKE Autopilot: xóa Service
LoadBalancervà scalereplicas=0khi không dùng. Bật lại chỉ cầnkubectl apply -k k8s/.
Biến bắt buộc (qua .env khi dev hoặc Secret/ConfigMap khi K8s):
DATABASE_URL: chuỗi kết nối PostgreSQL (managed DB khuyến nghị, thêm?sslmode=requirenếu cần)REDIS_URL: chuỗi kết nối Redis CloudSECRET_KEY,ALGORITHM,ACCESS_TOKEN_EXPIRE_MINUTESGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETOPENAI_API_KEY,OPENAI_BASE_URLCORS_ORIGINS(JSON list, ví dụ[]hoặc["https://your-frontend"]) – không dùng"*"khi cóallow_credentials=True
Metadata triển khai (được CD ghi vào ConfigMap để hiển thị ở /health):
DEPLOYED_AT,DEPLOY_SHA,DEPLOY_IMAGE,DEPLOY_BY
- Pod kẹt
ImagePullBackOff: kiểm tra image tagsha-<commit>đã được CI push - Job migrate timeout: kiểm tra
DATABASE_URL/firewall/SSL;CORS_ORIGINSphải là JSON list - CD OIDC lỗi
unauthorized_client: kiểm traattributeConditioncủa WIF provider (attribute.repository=='<OWNER>/<REPO>') và binding SA
MIT