Files
flaskpaste/documentation/kubernetes-deployment.md

10 KiB

Kubernetes Deployment

Deploy FlaskPaste to a Kubernetes cluster using images from Harbor registry.


Quick Deploy (k1s)

Single-node k1s cluster deployment:

# Restart deployment to pull latest image
ssh k1s "kubectl -n flaskpaste rollout restart deployment/flaskpaste"

# Watch rollout progress
ssh k1s "kubectl -n flaskpaste rollout status deployment/flaskpaste"

# Verify deployment
ssh k1s "kubectl -n flaskpaste get pods"
ssh k1s "curl -s http://\$(kubectl -n flaskpaste get pod -o jsonpath='{.items[0].status.podIP}'):5000/health"
Property Value
Host k1s (192.168.122.241)
Namespace flaskpaste
Image harbor.mymx.me/library/flaskpaste:latest
Pull Policy Always

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│ Traffic Flow                                                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Internet → odin (public IP) → WireGuard → mymx HAProxy                     │
│                                               │                             │
│                                               ↓                             │
│                                    K3s NodePort :30500                      │
│                                               │                             │
│                                    ┌──────────┼──────────┐                  │
│                                    ↓          ↓          ↓                  │
│                              k3s-master  k3s-worker-1  k3s-worker-2         │
│                                    │          │          │                  │
│                                    └──────────┼──────────┘                  │
│                                               ↓                             │
│                                      flaskpaste pod                         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Prerequisites

  • Kubernetes cluster with kubectl access
  • Harbor registry with flaskpaste image (see harbor-registry.md)
  • local-path StorageClass (or equivalent)
  • Network access from HAProxy to K8s nodes

Deployment

1. Create Namespace and Secrets

# Create namespace
kubectl create namespace flaskpaste

# Create Harbor pull secret
kubectl create secret docker-registry harbor-creds \
  --namespace flaskpaste \
  --docker-server=192.168.122.154:30443 \
  --docker-username=<harbor-username> \
  --docker-password=<harbor-password> \
  --docker-email=<your-email>

2. Apply Manifest

kubectl apply -f kubernetes.yaml

3. Verify Deployment

# Check pod status
kubectl get pods -n flaskpaste

# Check service
kubectl get svc -n flaskpaste

# View logs
kubectl logs -n flaskpaste -l app=flaskpaste

# Test API
kubectl run test --rm -it --image=alpine --restart=Never -- \
  wget -qO- http://flaskpaste.flaskpaste.svc:80/

Manifest Components

Namespace

apiVersion: v1
kind: Namespace
metadata:
  name: flaskpaste

ConfigMap

Environment configuration:

apiVersion: v1
kind: ConfigMap
metadata:
  name: flaskpaste-config
  namespace: flaskpaste
data:
  FLASK_ENV: "production"
  FLASKPASTE_URL_PREFIX: "/paste"
  FLASKPASTE_EXPIRY_ANON: "432000"
  FLASKPASTE_MAX_ANON: "3145728"
  FLASKPASTE_MAX_AUTH: "52428800"

PersistentVolumeClaim

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: flaskpaste-data
  namespace: flaskpaste
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flaskpaste
  namespace: flaskpaste
spec:
  replicas: 1
  selector:
    matchLabels:
      app: flaskpaste
  template:
    metadata:
      labels:
        app: flaskpaste
    spec:
      securityContext:
        runAsUser: 999
        runAsGroup: 999
        fsGroup: 999
      imagePullSecrets:
        - name: harbor-creds
      containers:
        - name: flaskpaste
          image: harbor.mymx.me/library/flaskpaste:latest
          ports:
            - containerPort: 5000
          envFrom:
            - configMapRef:
                name: flaskpaste-config
          volumeMounts:
            - name: data
              mountPath: /app/data
          resources:
            limits:
              memory: "256Mi"
              cpu: "1000m"
            requests:
              memory: "64Mi"
              cpu: "250m"
          livenessProbe:
            httpGet:
              path: /health
              port: 5000
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 5000
            initialDelaySeconds: 5
            periodSeconds: 10
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: flaskpaste-data

Service (NodePort)

apiVersion: v1
kind: Service
metadata:
  name: flaskpaste
  namespace: flaskpaste
spec:
  selector:
    app: flaskpaste
  ports:
    - protocol: TCP
      port: 80
      targetPort: 5000
      nodePort: 30500
  type: NodePort

HAProxy Configuration

Route /paste path to K8s NodePort with failover:

frontend https
    bind *:443 ssl crt /etc/haproxy/certs/

    acl is_paste path_beg /paste
    use_backend backend-flaskpaste if is_paste { req.hdr(Host) -i mymx.me }

backend backend-flaskpaste
  mode http
  timeout connect 5000
  timeout server 30000

  # Strip /paste prefix before forwarding
  http-request replace-path /paste(.*) \1
  http-request set-path / if { path -m len 0 }

  # Request tracing
  http-request set-header X-Request-ID %[uuid()] unless { req.hdr(X-Request-ID) -m found }
  http-request set-var(txn.request_id) req.hdr(X-Request-ID)
  http-response set-header X-Request-ID %[var(txn.request_id)]

  # Proxy trust secret
  http-request set-header X-Proxy-Secret <your-secret-here>

  # K3s NodePort backends with failover
  server k3s-master 192.168.122.186:30500 check
  server k3s-worker-1 192.168.122.97:30500 check backup
  server k3s-worker-2 192.168.122.221:30500 check backup

Verify HAProxy Backend Status

echo "show stat" | nc -U /run/haproxy/admin.sock | grep flaskpaste

containerd Registry Configuration

For self-signed Harbor certificates, configure containerd on all K8s nodes:

# /etc/containerd/config.toml

[plugins."io.containerd.grpc.v1.cri".registry]
  [plugins."io.containerd.grpc.v1.cri".registry.configs]
    [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.122.154:30443"]
      [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.122.154:30443".tls]
        insecure_skip_verify = true

Restart containerd after changes:

sudo systemctl restart containerd

Operations

Scale Deployment

kubectl scale deployment flaskpaste -n flaskpaste --replicas=3

Rolling Update

# Update image
kubectl set image deployment/flaskpaste \
  -n flaskpaste \
  flaskpaste=harbor.mymx.me/library/flaskpaste:v2.0.0

# Watch rollout
kubectl rollout status deployment/flaskpaste -n flaskpaste

# Rollback if needed
kubectl rollout undo deployment/flaskpaste -n flaskpaste

View Events

kubectl get events -n flaskpaste --sort-by='.lastTimestamp'

Shell Access

kubectl exec -it -n flaskpaste deploy/flaskpaste -- /bin/sh

Database Backup

# Copy database from pod
kubectl cp flaskpaste/$(kubectl get pod -n flaskpaste -l app=flaskpaste -o jsonpath='{.items[0].metadata.name}'):/app/data/pastes.db ./backup-pastes.db

Troubleshooting

Image Pull Errors

# Check pod events
kubectl describe pod -n flaskpaste -l app=flaskpaste | grep -A10 Events

# Common issues:
# - x509 certificate error → configure containerd insecure registry
# - ImagePullBackOff → check harbor-creds secret exists
# - ErrImagePull → verify image exists in Harbor

Pod Not Starting

# Check logs
kubectl logs -n flaskpaste -l app=flaskpaste --previous

# Check PVC binding
kubectl get pvc -n flaskpaste

# Check resource constraints
kubectl describe pod -n flaskpaste -l app=flaskpaste

Health Probe Failures

# Test endpoint directly
kubectl exec -n flaskpaste deploy/flaskpaste -- wget -qO- http://localhost:5000/

# Check probe configuration
kubectl get deployment flaskpaste -n flaskpaste -o yaml | grep -A5 Probe

HAProxy Connection Issues

# Test from HAProxy host to NodePort
curl -s http://192.168.122.186:30500/health

# Check HAProxy backend status
echo "show stat" | nc -U /run/haproxy/admin.sock | grep -E "flaskpaste.*(UP|DOWN)"

# Check HAProxy logs
journalctl -u haproxy -f

Resource Summary

Resource Name Namespace
Namespace flaskpaste -
ConfigMap flaskpaste-config flaskpaste
PVC flaskpaste-data flaskpaste
Deployment flaskpaste flaskpaste
Service flaskpaste (NodePort 30500) flaskpaste
Secret harbor-creds flaskpaste