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.yamlHelm 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: 4GiManual 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: ClusterIPBackend 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: metricsChat 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: 5Ingress 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: 8001StatefulSets 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: NoneQdrant 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: 20GiHorizontal 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: 9000Pod 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: qdrantMonitoring 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: 9090Backup 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: OnFailureGitOps 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: 3mSecurity 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: trueRBAC 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: gopieDeployment 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
fiCanary 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 gopieCommon Issues
-
Pod stuck in Pending:
# Check node resources kubectl describe nodes # Check PVC status kubectl get pvc -n gopie -
CrashLoopBackOff:
# Check logs kubectl logs -p deployment/gopie-server -n gopie # Check liveness probe kubectl describe deployment gopie-server -n gopie -
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
- Configure Monitoring Setup
- Review Scaling Strategies
- Implement Security Best Practices
- Set up CI/CD Pipeline