Aller au contenu

End-to-End CI/CD with ArgoCD, GitHub Actions, and FastAPI on Kubernetes

Sitemap

In this tutorial, you’ll create a full CI/CD pipeline using GitHub Actions and ArgoCD to automatically build and deploy a FastAPI app into a local Kubernetes cluster. You’ll also integrate Prometheus and Grafana for monitoring, all running locally with kind.

This tutorial will guide you through:

  • Setting up a local Kubernetes cluster with kind
  • Building a FastAPI app with Prometheus metrics
  • Creating a CI/CD pipeline using GitHub Actions
  • Deploying and syncing changes with ArgoCD
  • Visualizing metrics in Grafana

This assumes you already have [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation), [helm](https://helm.sh/docs/intro/install/), and [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) installed.

Project Structure:

CICDpipeline/
├── .github/
   └── workflows/
       └── deploy.yml
├── todo-api/
   ├── main.py
   ├── Dockerfile
   └── requirements.txt
├── k8s-manifest/
   ├── deployment.yaml
   ├── service.yaml
   ├── servicemonitor.yaml
   └── argocd-app.yaml
1. Create a local kubernetes cluster
kind create cluster --name todo-dev

2. Build a FastAPI app with Prometheus metrics

todo-api/main.py

from fastapi import FastAPI
from prometheus_client import Counter, generate_latest
from starlette.responses import Response

app = FastAPI()
hits = Counter("hits", "Number of hits to the root")

@app.get("/")
def read_root():
    hits.inc()
    return {"message": "Hello World"}

@app.get("/metrics")
def metrics():
    return Response(generate_latest(), media_type="text/plain")

todo-api/test_main.py

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_read_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

todo-api/requirements.txt

fastapi
uvicorn
prometheus-client
pytest
httpx

todo-api/Dockerfile

FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

3. Setup Github actions

This workflow:

  • Runs tests
  • Builds and pushes a Docker image
  • Updates the Kubernetes manifest with the new tag
    .github/workflows/deploy.yml
    
    name: Build and Deploy
    
    on:
      push:
        branches:
          - main
    
    jobs:
      build-and-deploy:
        runs-on: ubuntu-latest
    
        env:
          TAG: ${{ github.sha }}
    
        steps:
        - name: Checkout source code
          uses: actions/checkout@v3
    
        - name: Stop infinite loop if commit is from GitHub Actions
          if: github.actor == 'github-actions'
          run: |
            echo "Triggered by GitHub Actions bot — skipping workflow."
            exit 0
    
        - name: Set up Python
          uses: actions/setup-python@v4
          with:
            python-version: '3.11'
    
        - name: Install dependencies and run tests
          run: |
            pip install -r todo-api/requirements.txt
            pytest todo-api
    
        - name: Set lowercase owner
          run: |
            echo "owner_lc=${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_ENV
            echo "image_name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/todo-api" >> $GITHUB_ENV
    
        - name: Log in to GHCR
          run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ env.owner_lc }} --password-stdin
    
        - name: Build Docker image
          run: docker build -t ${{ env.image_name }}:${{ env.TAG }} -f todo-api/Dockerfile todo-api
    
        - name: Push Docker image
          run: docker push ${{ env.image_name }}:${{ env.TAG }}
    
        - name: Clone manifests repo (same repo in this case)
          run: |
            git config --global user.name "GitHub Actions"
            git config --global user.email "actions@github.com"
    
            git clone https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/CSpiegelhalter/CICDpipeline.git
            cd CICDpipeline/k8s-manifest
    
            sed -i "s|image: .*|image: ${{ env.image_name }}:${{ env.TAG }}|" deployment.yaml
    
            git add deployment.yaml
            if git diff --cached --quiet; then
              echo "No changes to commit"
            else
              git commit -m "Update image to ${{ env.TAG }} [skip ci]"
              git push
            fi
    

4. Install ArgoCD into the cluster (from ArgoCD)

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

Access the UI

kubectl port-forward svc/argocd-server -n argocd 8080:443

Go to: https://localhost:8080/

Login with:

  • Username: admin
  • Password (run this command and copy the output):
    kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath="{.data.password}" | base64 -d
    

(You can port-forward and log into the ArgoCD UI right away, even before you deploy any apps.)

5. Add Prometheus + Grafana

kubectl create namespace monitoring

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --set prometheus.prometheusSpec.maximumStartupDurationSeconds=300

Port-forward Grafana

kubectl port-forward svc/monitoring-grafana 3000:3000

Go to [http://localhost:3000](http://localhost:3000/)

Login with:

  • Username: admin
  • Password: prom-operator

(Grafana will launch and be accessible even if your app hasn’t been deployed yet. We’ll deploy your app and connect the metrics shortly.)

6. Create Kubernetes manifests

k8s-manifest/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: todo-api
  template:
    metadata:
      labels:
        app: todo-api
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: "/metrics"
        prometheus.io/port: "80"
    spec:
      containers:
        - name: todo-api
          image: ghcr.io/cspiegelhalter/todo-api # Change to be your own
          ports:
            - containerPort: 80

k8s-manifest/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: todo-api
  labels:
    app: todo-api
spec:
  selector:
    app: todo-api
  ports:
    - name: http   # < MUST match the \`port:\` in the ServiceMonitor
      protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

k8s-manifest/servicemonitor.yaml

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: todo-api
  labels:
    release: monitoring  # must match your Prometheus release name
spec:
  selector:
    matchLabels:
      app: todo-api
  endpoints:
    - port: http
      path: /metrics
      interval: 15s
  namespaceSelector:
    matchNames:
      - default

7. Connect ArgoCD to your App

k8s-manifest/argocd_app.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: todo-api
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/CSpiegelhalter/CICDpipeline
    targetRevision: HEAD
    path: k8s-manifest
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Apply your manifests:

kubectl apply -f k8s-manifest/deployment.yaml
kubectl apply -f k8s-manifest/service.yaml
kubectl apply -f k8s-manifest/servicemonitor.yaml 
kubectl apply -f argocd-app.yaml

You now have:

  • A working CI/CD pipeline from GitHub to Kubernetes
  • Automated ArgoCD sync
  • Live Prometheus metrics
  • Grafana dashboards for observability

Here’s the repo: https://github.com/CSpiegelhalter/CICDpipeline

Let me know in the comments if you want a follow-up tutorial with Ingress, TLS, or external cloud hosting!

Software Engineer, Problem Solver, System Designer, Lifelong Learner

More from Curt Spiegelhalter

[

See more recommendations

](https://medium.com/?source=post_page---read_next_recirc--8fdcab6c12df---------------------------------------)