Kubernetes Deployment

Deploying GoPie on Kubernetes with Helm and manifests

This guide covers deploying GoPie on Kubernetes, including Helm charts, manifests, and production best practices.

Prerequisites

  • Kubernetes cluster 1.24+
  • kubectl configured
  • Helm 3.0+
  • 4 CPU cores and 8GB RAM minimum per node
  • Storage class for persistent volumes

Quick Start with Helm

Install GoPie Helm Chart

# Add GoPie Helm repository
helm repo add gopie https://charts.gopie.io
helm repo update

# Install with default values
helm install gopie gopie/gopie \
  --namespace gopie \
  --create-namespace

# Install with custom values
helm install gopie gopie/gopie \
  --namespace gopie \
  --create-namespace \
  --values values.yaml

Helm Values Configuration

# values.yaml
global:
  environment: production
  domain: gopie.example.com
  
  # Image configuration
  imageRegistry: docker.io
  imagePullSecrets:
    - name: docker-registry-secret

# Web Frontend
web:
  enabled: true
  replicaCount: 3
  image:
    repository: gopie/web
    tag: latest
    pullPolicy: IfNotPresent
  
  resources:
    requests:
      cpu: 500m
      memory: 512Mi
    limits:
      cpu: 1000m
      memory: 1Gi
  
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 10
    targetCPUUtilizationPercentage: 70
    targetMemoryUtilizationPercentage: 80
  
  ingress:
    enabled: true
    className: nginx
    annotations:
      cert-manager.io/cluster-issuer: letsencrypt-prod
    hosts:
      - host: gopie.example.com
        paths:
          - path: /
            pathType: Prefix
    tls:
      - secretName: gopie-tls
        hosts:
          - gopie.example.com

# Go Backend Server
server:
  enabled: true
  replicaCount: 3
  image:
    repository: gopie/server
    tag: latest
  
  resources:
    requests:
      cpu: 1000m
      memory: 1Gi
    limits:
      cpu: 2000m
      memory: 2Gi
  
  env:
    - name: DATABASE_URL
      valueFrom:
        secretKeyRef:
          name: gopie-secrets
          key: database-url
    - name: S3_ACCESS_KEY
      valueFrom:
        secretKeyRef:
          name: gopie-secrets
          key: s3-access-key
  
  service:
    type: ClusterIP
    port: 8000

# Python Chat Server
chatServer:
  enabled: true
  replicaCount: 2
  image:
    repository: gopie/chat-server
    tag: latest
  
  resources:
    requests:
      cpu: 2000m
      memory: 2Gi
    limits:
      cpu: 4000m
      memory: 4Gi
  
  env:
    - name: OPENAI_API_KEY
      valueFrom:
        secretKeyRef:
          name: gopie-secrets
          key: openai-api-key

# PostgreSQL
postgresql:
  enabled: true
  auth:
    postgresPassword: changeme
    username: gopie
    password: changeme
    database: gopie
  
  primary:
    persistence:
      enabled: true
      size: 50Gi
      storageClass: fast-ssd
  
  metrics:
    enabled: true
    serviceMonitor:
      enabled: true

# MinIO
minio:
  enabled: true
  auth:
    rootUser: admin
    rootPassword: changeme
  
  persistence:
    enabled: true
    size: 100Gi
    storageClass: fast-ssd
  
  defaultBuckets: "gopie-datasets,gopie-exports"

# Qdrant
qdrant:
  enabled: true
  replicaCount: 3
  persistence:
    enabled: true
    size: 20Gi
    storageClass: fast-ssd
  
  resources:
    requests:
      cpu: 1000m
      memory: 2Gi
    limits:
      cpu: 2000m
      memory: 4Gi

Manual Kubernetes Manifests

Namespace and ConfigMap

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: gopie
  labels:
    name: gopie

---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: gopie-config
  namespace: gopie
data:
  ENVIRONMENT: "production"
  LOG_LEVEL: "INFO"
  BACKEND_URL: "http://gopie-server:8000"
  CHAT_SERVER_URL: "http://gopie-chat:8001"
  QDRANT_URL: "http://qdrant:6333"
  S3_ENDPOINT: "http://minio:9000"
  S3_REGION: "us-east-1"
  S3_BUCKET_NAME: "gopie-datasets"

Secrets

# k8s/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: gopie-secrets
  namespace: gopie
type: Opaque
stringData:
  database-url: "postgres://gopie:password@postgres:5432/gopie?sslmode=require"
  s3-access-key: "minioadmin"
  s3-secret-key: "minioadmin"
  openai-api-key: "sk-your-api-key"
  jwt-secret: "your-jwt-secret"

Web Frontend Deployment

# k8s/web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gopie-web
  namespace: gopie
  labels:
    app: gopie-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: gopie-web
  template:
    metadata:
      labels:
        app: gopie-web
    spec:
      containers:
      - name: web
        image: gopie/web:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
          name: http
        env:
        - name: NODE_ENV
          value: "production"
        - name: NEXT_PUBLIC_BACKEND_URL
          value: "https://api.gopie.example.com"
        - name: NEXT_PUBLIC_CHAT_SERVER_URL
          value: "https://chat.gopie.example.com"
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi
        livenessProbe:
          httpGet:
            path: /api/health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /api/ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        securityContext:
          runAsNonRoot: true
          runAsUser: 1000
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: cache
          mountPath: /.next/cache
      volumes:
      - name: tmp
        emptyDir: {}
      - name: cache
        emptyDir: {}
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - gopie-web
              topologyKey: kubernetes.io/hostname

---
apiVersion: v1
kind: Service
metadata:
  name: gopie-web
  namespace: gopie
spec:
  selector:
    app: gopie-web
  ports:
  - port: 80
    targetPort: 3000
    name: http
  type: ClusterIP

Backend Server Deployment

# k8s/server-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gopie-server
  namespace: gopie
spec:
  replicas: 3
  selector:
    matchLabels:
      app: gopie-server
  template:
    metadata:
      labels:
        app: gopie-server
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      initContainers:
      - name: migrate
        image: gopie/server:latest
        command: ["/app/migrate.sh"]
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: gopie-secrets
              key: database-url
      containers:
      - name: server
        image: gopie/server:latest
        ports:
        - containerPort: 8000
          name: http
        - containerPort: 9090
          name: metrics
        envFrom:
        - configMapRef:
            name: gopie-config
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: gopie-secrets
              key: database-url
        - name: S3_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: gopie-secrets
              key: s3-access-key
        - name: S3_SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: gopie-secrets
              key: s3-secret-key
        resources:
          requests:
            cpu: 1000m
            memory: 1Gi
          limits:
            cpu: 2000m
            memory: 2Gi
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: gopie-server
  namespace: gopie
spec:
  selector:
    app: gopie-server
  ports:
  - port: 8000
    targetPort: 8000
    name: http
  - port: 9090
    targetPort: 9090
    name: metrics

Chat Server Deployment

# k8s/chat-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gopie-chat
  namespace: gopie
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gopie-chat
  template:
    metadata:
      labels:
        app: gopie-chat
    spec:
      containers:
      - name: chat
        image: gopie/chat-server:latest
        ports:
        - containerPort: 8001
          name: http
        envFrom:
        - configMapRef:
            name: gopie-config
        env:
        - name: OPENAI_API_KEY
          valueFrom:
            secretKeyRef:
              name: gopie-secrets
              key: openai-api-key
        resources:
          requests:
            cpu: 2000m
            memory: 2Gi
          limits:
            cpu: 4000m
            memory: 4Gi
        livenessProbe:
          httpGet:
            path: /health
            port: 8001
          initialDelaySeconds: 45
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8001
          initialDelaySeconds: 30
          periodSeconds: 5

Ingress Configuration

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gopie-ingress
  namespace: gopie
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/proxy-body-size: "100m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, PUT, POST, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://gopie.example.com"
spec:
  tls:
  - hosts:
    - gopie.example.com
    - api.gopie.example.com
    - chat.gopie.example.com
    secretName: gopie-tls
  rules:
  - host: gopie.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gopie-web
            port:
              number: 80
  - host: api.gopie.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gopie-server
            port:
              number: 8000
  - host: chat.gopie.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: gopie-chat
            port:
              number: 8001

StatefulSets for Databases

PostgreSQL StatefulSet

# k8s/postgres-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: gopie
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16-alpine
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_DB
          value: gopie
        - name: POSTGRES_USER
          value: gopie
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            cpu: 1000m
            memory: 2Gi
          limits:
            cpu: 2000m
            memory: 4Gi
  volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 50Gi

---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: gopie
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432
  clusterIP: None

Qdrant StatefulSet

# k8s/qdrant-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: qdrant
  namespace: gopie
spec:
  serviceName: qdrant
  replicas: 3
  selector:
    matchLabels:
      app: qdrant
  template:
    metadata:
      labels:
        app: qdrant
    spec:
      containers:
      - name: qdrant
        image: qdrant/qdrant:latest
        ports:
        - containerPort: 6333
          name: http
        - containerPort: 6334
          name: grpc
        env:
        - name: QDRANT__CLUSTER__ENABLED
          value: "true"
        - name: QDRANT__CLUSTER__P2P__PORT
          value: "6335"
        - name: QDRANT__LOG_LEVEL
          value: "INFO"
        volumeMounts:
        - name: qdrant-storage
          mountPath: /qdrant/storage
        resources:
          requests:
            cpu: 1000m
            memory: 2Gi
          limits:
            cpu: 2000m
            memory: 4Gi
  volumeClaimTemplates:
  - metadata:
      name: qdrant-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 20Gi

Horizontal Pod Autoscaling

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gopie-web-hpa
  namespace: gopie
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gopie-web
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15
      - type: Pods
        value: 2
        periodSeconds: 15
      selectPolicy: Max

---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gopie-server-hpa
  namespace: gopie
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gopie-server
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"

Network Policies

# k8s/network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: gopie-web-netpol
  namespace: gopie
spec:
  podSelector:
    matchLabels:
      app: gopie-web
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 3000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: gopie-server
    ports:
    - protocol: TCP
      port: 8000
  - to:
    - podSelector:
        matchLabels:
          app: gopie-chat
    ports:
    - protocol: TCP
      port: 8001
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: gopie-backend-netpol
  namespace: gopie
spec:
  podSelector:
    matchLabels:
      app: gopie-server
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: gopie-web
    - podSelector:
        matchLabels:
          app: gopie-chat
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8000
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - podSelector:
        matchLabels:
          app: minio
    ports:
    - protocol: TCP
      port: 9000

Pod Disruption Budgets

# k8s/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: gopie-web-pdb
  namespace: gopie
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: gopie-web

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: gopie-server-pdb
  namespace: gopie
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: gopie-server

---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: qdrant-pdb
  namespace: gopie
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: qdrant

Monitoring with Prometheus

# k8s/prometheus-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: gopie-metrics
  namespace: gopie
  labels:
    app: gopie
spec:
  selector:
    matchLabels:
      app: gopie
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics

---
apiVersion: v1
kind: Service
metadata:
  name: gopie-metrics
  namespace: gopie
  labels:
    app: gopie
spec:
  selector:
    app: gopie-server
  ports:
  - name: metrics
    port: 9090
    targetPort: 9090

Backup and Recovery

CronJob for Database Backup

# k8s/backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: postgres-backup
  namespace: gopie
spec:
  schedule: "0 2 * * *"  # Daily at 2 AM
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: postgres:16-alpine
            env:
            - name: PGPASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secret
                  key: password
            command:
            - /bin/sh
            - -c
            - |
              DATE=$(date +%Y%m%d_%H%M%S)
              pg_dump -h postgres -U gopie -d gopie | gzip > /backup/gopie_$DATE.sql.gz
              
              # Upload to S3
              aws s3 cp /backup/gopie_$DATE.sql.gz s3://gopie-backups/postgres/
              
              # Keep only last 7 days locally
              find /backup -name "gopie_*.sql.gz" -mtime +7 -delete
            volumeMounts:
            - name: backup
              mountPath: /backup
          volumes:
          - name: backup
            persistentVolumeClaim:
              claimName: backup-pvc
          restartPolicy: OnFailure

GitOps with ArgoCD

# argocd/gopie-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: gopie
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/your-org/gopie-k8s
    targetRevision: HEAD
    path: k8s
    helm:
      valueFiles:
      - values-prod.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: gopie
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Security Hardening

Pod Security Policy

# k8s/pod-security-policy.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: gopie-psp
spec:
  privileged: false
  allowPrivilegeEscalation: false
  requiredDropCapabilities:
  - ALL
  volumes:
  - 'configMap'
  - 'emptyDir'
  - 'projected'
  - 'secret'
  - 'downwardAPI'
  - 'persistentVolumeClaim'
  hostNetwork: false
  hostIPC: false
  hostPID: false
  runAsUser:
    rule: 'MustRunAsNonRoot'
  seLinux:
    rule: 'RunAsAny'
  supplementalGroups:
    rule: 'RunAsAny'
  fsGroup:
    rule: 'RunAsAny'
  readOnlyRootFilesystem: true

RBAC Configuration

# k8s/rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gopie-sa
  namespace: gopie

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: gopie-role
  namespace: gopie
rules:
- apiGroups: [""]
  resources: ["configmaps", "secrets"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: gopie-rolebinding
  namespace: gopie
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: gopie-role
subjects:
- kind: ServiceAccount
  name: gopie-sa
  namespace: gopie

Deployment Strategies

Blue-Green Deployment

#!/bin/bash
# blue-green-deploy.sh

CURRENT_COLOR=$(kubectl get service gopie-web -o jsonpath='{.spec.selector.version}')
NEW_COLOR=$([[ "$CURRENT_COLOR" == "blue" ]] && echo "green" || echo "blue")

echo "Deploying $NEW_COLOR version..."

# Deploy new version
kubectl set image deployment/gopie-web-$NEW_COLOR \
  web=gopie/web:$NEW_VERSION \
  -n gopie

# Wait for rollout
kubectl rollout status deployment/gopie-web-$NEW_COLOR -n gopie

# Run smoke tests
if ./smoke-tests.sh $NEW_COLOR; then
  echo "Switching traffic to $NEW_COLOR..."
  kubectl patch service gopie-web \
    -p '{"spec":{"selector":{"version":"'$NEW_COLOR'"}}}' \
    -n gopie
  
  echo "Scaling down $CURRENT_COLOR..."
  kubectl scale deployment gopie-web-$CURRENT_COLOR --replicas=0 -n gopie
else
  echo "Smoke tests failed, rolling back..."
  kubectl scale deployment gopie-web-$NEW_COLOR --replicas=0 -n gopie
  exit 1
fi

Canary Deployment

# k8s/canary-deployment.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: gopie-web
  namespace: gopie
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gopie-web
  service:
    port: 80
    targetPort: 3000
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 30s
    webhooks:
    - name: load-test
      url: http://loadtester/
      timeout: 5s
      metadata:
        cmd: "hey -z 1m -q 10 -c 2 http://gopie-web-canary.gopie:80/"

Troubleshooting

Debug Commands

# Get pod logs
kubectl logs -f deployment/gopie-server -n gopie

# Describe pod issues
kubectl describe pod -l app=gopie-server -n gopie

# Execute commands in pod
kubectl exec -it deployment/gopie-server -n gopie -- /bin/sh

# Check resource usage
kubectl top pods -n gopie

# View events
kubectl get events -n gopie --sort-by='.lastTimestamp'

# Port forward for debugging
kubectl port-forward svc/gopie-server 8000:8000 -n gopie

Common Issues

  1. Pod stuck in Pending:

    # Check node resources
    kubectl describe nodes
    
    # Check PVC status
    kubectl get pvc -n gopie
  2. CrashLoopBackOff:

    # Check logs
    kubectl logs -p deployment/gopie-server -n gopie
    
    # Check liveness probe
    kubectl describe deployment gopie-server -n gopie
  3. Service unavailable:

    # Check endpoints
    kubectl get endpoints -n gopie
    
    # Test service connectivity
    kubectl run test-pod --image=busybox -it --rm -- wget -O- http://gopie-server:8000/health

Next Steps