A cloud-native backend service deployed via GitOps (Argo CD) with CI/CD (GitHub Actions → GHCR) and full observability (Prometheus + Grafana).
- Building a small, real backend service (FastAPI) with production-style endpoints
- Containerization with Docker
- Kubernetes deployment on a local cluster (kind)
- Packaging and configuration with Helm
- Image build/push automation with GitHub Actions
- GitOps-based deployments with Argo CD
- Metrics scraping with Prometheus Operator via ServiceMonitor
- Dashboarding with Grafana (traffic + latency panels)
This project is intentionally designed as a local, reproducible platform setup to demonstrate modern cloud-native practices (CI/CD, GitOps, observability) without relying on managed cloud services.
It focuses on correctness, visibility, and automation rather than feature completeness.
- FastAPI service (document validation)
- Endpoints:
POST /validate— validates payload and returnsACCEPTEDorREJECTEDGET /metrics— Prometheus metricsGET /health/live— liveness probeGET /health/ready— readiness probe
Location: helm/document-service/
templates/deployment.yaml— Deployment with probes + resourcestemplates/service.yaml— ClusterIP servicetemplates/servicemonitor.yaml— Prometheus Operator scraping config (ServiceMonitor)values.yaml— image + replicaCount + resources
- Argo CD Application manifest (example):
argocd/document-service-app.yaml
- GitHub Actions workflow:
.github/workflows/ci.yml
- Builds + pushes image to GHCR (tags typically include
latestand a commit SHA)
- GitHub → Actions: latest workflow run should be green
- GitHub → Packages: container image published to GHCR
Image tags typically include:
latest- commit SHA
A request is ACCEPTED only if:
document_idis not emptydocument_typeis one of:invoice,delivery_note,certificatecreated_atmatchesYYYY-MM-DDsource_systemis not empty
Otherwise it is REJECTED with a reason.
Exposed on /metrics using prometheus_client:
document_validation_requests_total(Counter)document_validation_failures_total(Counter)document_validation_request_latency_seconds(Histogram)
- Docker Desktop
- kubectl, kind, helm
- (Optional) argocd CLI
docker build -t document-service:0.1.0 .
docker run --rm -p 8000:8000 document-service:0.1.0curl -s http://localhost:8000/health/live && echo
curl -s http://localhost:8000/health/ready && echocurl -s -X POST http://localhost:8000/validate \
-H 'Content-Type: application/json' \
-d '{"document_id":"INV-001","document_type":"invoice","created_at":"2025-01-10","source_system":"erp"}' && echocurl -s -X POST http://localhost:8000/validate \
-H 'Content-Type: application/json' \
-d '{"document_id":"","document_type":"invoice","created_at":"2025-01-10","source_system":"erp"}' && echocurl -s http://localhost:8000/metrics | head -n 25
curl -s http://localhost:8000/metrics | grep document_validation_requests_totalkind create cluster --name dev-platform
kubectl get nodes
kubectl create namespace platformhelm upgrade --install document-service helm/document-service -n platformVerify Deployment and Service:
kubectl get deploy,svc -n platformVerify Pods selected by Service:
kubectl get pods -n platform -l app=document-serviceIf label selectors return nothing, run:
kubectl get pods -n platform --show-labelsand adjust the selector.
Verify Service endpoints:
kubectl get endpoints -n platform document-servicekubectl get pods -n platform -wkubectl logs -n platform -l app=document-servicekubectl port-forward -n platform svc/document-service 8000:8000kubectl apply -f argocd/document-service-app.yamlkubectl port-forward -n argocd svc/argocd-server 8080:443kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 --decode && echoLogin (user: admin, password: output above):
argocd login localhost:8080 --username admin --password <PASTE_PASSWORD> --insecureargocd app get document-serviceargocd app sync document-service
argocd app wait document-serviceEdit : helm/document-service/values.yaml
Example:
replicaCount: 4Commit + push:
git add helm/document-service/values.yaml
git commit -m "gitops: change replicaCount to 4"
git pushkubectl get pods -n platform -wExpected: pods get created/terminated until the replica count matches Git.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create ns monitoring
helm upgrade --install monitoring prometheus-community/kube-prometheus-stack -n monitoringkubectl get servicemonitor -n platform
kubectl describe servicemonitor -n platform document-servicekubectl get svc -n monitoringkubectl -n monitoring port-forward svc/monitoring-kube-prometheus-prometheus 9090:9090
# or (depending on chart/service)
kubectl -n monitoring port-forward svc/monitoring-kube-prometheus-stack-prometheus 9090:9090Open Prometheus at: http://localhost:9090
Check:
Status → Targets confirm document-service target is UP
for i in {1..30}; do
curl -s -X POST http://localhost:8000/validate \
-H 'Content-Type: application/json' \
-d "{\"document_id\":\"INV-$i\",\"document_type\":\"invoice\",\"created_at\":\"2025-01-10\",\"source_system\":\"erp\"}" \
>/dev/null
doneTotal Requests :
document_validation_requests_total
Requests per second:
rate(document_validation_requests_total[1m])
P95 latency (5m window for more stable results):
histogram_quantile(0.95, rate(document_validation_request_latency_seconds_bucket[5m]))
If p95 shows NaN, there may be too few samples in the time window. Generate more traffic and widen the window.
List services:
kubectl get svc -n monitoringCommon Grafana service name:
kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80Fetch admin password:
kubectl get secret -n monitoring monitoring-grafana \
-o jsonpath='{.data.admin-password}' | base64 --decode && echo
Open Grafana at : http://localhost:3000
Login:
user: admin password: (from command above)
- CI builds and publishes container images automatically
- Git is the single source of truth for runtime configuration
- Kubernetes state converges automatically via Argo CD
- Application behavior is observable via metrics and dashboards
- No ingress or authentication
- No persistent storage
- No managed cloud services