diff --git a/documentation/kubernetes-deployment.md b/documentation/kubernetes-deployment.md new file mode 100644 index 0000000..3af79de --- /dev/null +++ b/documentation/kubernetes-deployment.md @@ -0,0 +1,387 @@ +# Kubernetes Deployment + +Deploy FlaskPaste to a Kubernetes cluster using images from Harbor registry. + +--- + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Traffic Flow │ +├─────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Internet → odin (public IP) → WireGuard → mymx HAProxy │ +│ │ │ +│ ↓ │ +│ K8s NodePort :30500 │ +│ │ │ +│ ┌──────────┼──────────┐ │ +│ ↓ ↓ ↓ │ +│ k8s-master k8s-worker-1 k8s-worker-2 │ +│ │ │ │ │ +│ └──────────┼──────────┘ │ +│ ↓ │ +│ flaskpaste pod │ +│ │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Prerequisites + +- Kubernetes cluster with kubectl access +- Harbor registry with flaskpaste image (see [harbor-registry.md](harbor-registry.md)) +- `local-path` StorageClass (or equivalent) +- Network access from HAProxy to K8s nodes + +--- + +## Deployment + +### 1. Create Namespace and Secrets + +```bash +# 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=admin \ + --docker-password=Harbor12345 \ + --docker-email=admin@example.com +``` + +### 2. Apply Manifest + +```bash +kubectl apply -f kubernetes.yaml +``` + +### 3. Verify Deployment + +```bash +# 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 + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: flaskpaste +``` + +### ConfigMap + +Environment configuration: + +```yaml +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 + +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: flaskpaste-data + namespace: flaskpaste +spec: + storageClassName: local-path + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +``` + +### Deployment + +```yaml +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: 192.168.122.154:30443/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: / + port: 5000 + initialDelaySeconds: 10 + periodSeconds: 30 + readinessProbe: + httpGet: + path: / + port: 5000 + initialDelaySeconds: 5 + periodSeconds: 10 + volumes: + - name: data + persistentVolumeClaim: + claimName: flaskpaste-data +``` + +### Service (NodePort) + +```yaml +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: + +```haproxy +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 + + # K8s NodePort backends with failover + server k8s-master 192.168.122.154:30500 check + server k8s-worker-1 192.168.122.80:30500 check backup + server k8s-worker-2 192.168.122.219:30500 check backup +``` + +### Verify HAProxy Backend Status + +```bash +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: + +```toml +# /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: + +```bash +sudo systemctl restart containerd +``` + +--- + +## Operations + +### Scale Deployment + +```bash +kubectl scale deployment flaskpaste -n flaskpaste --replicas=3 +``` + +### Rolling Update + +```bash +# Update image +kubectl set image deployment/flaskpaste \ + -n flaskpaste \ + flaskpaste=192.168.122.154:30443/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 + +```bash +kubectl get events -n flaskpaste --sort-by='.lastTimestamp' +``` + +### Shell Access + +```bash +kubectl exec -it -n flaskpaste deploy/flaskpaste -- /bin/sh +``` + +### Database Backup + +```bash +# 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# 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 + +```bash +# Test from HAProxy host to NodePort +curl -s http://192.168.122.154:30500/ + +# 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 | + +--- + +## Related Documentation + +- [Harbor Registry](harbor-registry.md) - Building and pushing images +- [API Reference](api.md) - FlaskPaste API documentation +- [Deployment Guide](deployment.md) - Non-K8s deployment options