AWS EKS
Kubernetes Deployment
Deploy FraiseQL to Kubernetes for enterprise-grade scalability and reliability.
Prerequisites
Section titled “Prerequisites”- Kubernetes cluster (1.24+)
kubectlCLI configured- Docker image pushed to registry
- PostgreSQL database (managed or deployed in cluster)
Quick Start (Helm)
Section titled “Quick Start (Helm)”For fastest setup, use Helm:
# Add FraiseQL Helm repositoryhelm repo add fraiseql https://charts.fraiseql.iohelm repo update
# Install FraiseQLhelm install fraiseql fraiseql/fraiseql \ --namespace fraiseql \ --create-namespace \ --set database.url=postgresql://user:pass@postgres:5432/db \ --set jwt.secret=$(openssl rand -base64 32) \ --set image.tag=1.0.0 \ --set replicas=3
# Verifykubectl get pods -n fraiseqlkubectl get svc -n fraiseqlManual Kubernetes Setup
Section titled “Manual Kubernetes Setup”-
Create Namespace
fraiseql-namespace.yaml apiVersion: v1kind: Namespacemetadata:name: fraiseqllabels:name: fraiseqlTerminal window kubectl apply -f fraiseql-namespace.yaml -
Configure Secrets
Store sensitive data in Kubernetes Secrets:
Terminal window # Create from literalskubectl create secret generic fraiseql-secrets \--from-literal=database-url='postgresql://user:pass@postgres:5432/db' \--from-literal=jwt-secret=$(openssl rand -base64 32) \--from-literal=cors-origins='https://example.com' \-n fraiseql# Or from filekubectl create secret generic fraiseql-secrets \--from-file=.env \-n fraiseql -
Create ConfigMap
Store non-sensitive configuration:
fraiseql-config.yaml apiVersion: v1kind: ConfigMapmetadata:name: fraiseql-confignamespace: fraiseqldata:ENVIRONMENT: "production"LOG_LEVEL: "info"LOG_FORMAT: "json"PGBOUNCER_MIN_POOL_SIZE: "5"PGBOUNCER_MAX_POOL_SIZE: "20"RATE_LIMIT_REQUESTS: "10000"RATE_LIMIT_WINDOW_SECONDS: "60"Terminal window kubectl apply -f fraiseql-config.yaml -
Create Deployment
fraiseql-deployment.yaml apiVersion: apps/v1kind: Deploymentmetadata:name: fraiseqlnamespace: fraiseqllabels:app: fraiseqlversion: v1spec:replicas: 3strategy:type: RollingUpdaterollingUpdate:maxSurge: 1maxUnavailable: 0 # Zero downtime deploymentsselector:matchLabels:app: fraiseqltemplate:metadata:labels:app: fraiseqlversion: v1annotations:prometheus.io/scrape: "true"prometheus.io/port: "9000"prometheus.io/path: "/metrics"spec:serviceAccountName: fraiseqlinitContainers:- name: migrateimage: your-registry/fraiseql:1.0.0imagePullPolicy: IfNotPresentenv:- name: DATABASE_URLvalueFrom:secretKeyRef:name: fraiseql-secretskey: database-urlcommand:- python- -m- fraiseql- migratecontainers:- name: fraiseqlimage: your-registry/fraiseql:1.0.0imagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 8000protocol: TCP- name: metricscontainerPort: 9000protocol: TCPenv:- name: DATABASE_URLvalueFrom:secretKeyRef:name: fraiseql-secretskey: database-url- name: JWT_SECRETvalueFrom:secretKeyRef:name: fraiseql-secretskey: jwt-secret- name: CORS_ORIGINSvalueFrom:secretKeyRef:name: fraiseql-secretskey: cors-origins- name: ENVIRONMENTvalueFrom:configMapKeyRef:name: fraiseql-configkey: ENVIRONMENT- name: LOG_LEVELvalueFrom:configMapKeyRef:name: fraiseql-configkey: LOG_LEVEL- name: LOG_FORMATvalueFrom:configMapKeyRef:name: fraiseql-configkey: LOG_FORMATresources:requests:cpu: 500mmemory: 512Miephemeral-storage: 100Milimits:cpu: 2000mmemory: 2Giephemeral-storage: 500MistartupProbe:httpGet:path: /health/liveport: httpfailureThreshold: 30periodSeconds: 2livenessProbe:httpGet:path: /health/liveport: httpinitialDelaySeconds: 10periodSeconds: 30timeoutSeconds: 10failureThreshold: 3readinessProbe:httpGet:path: /health/readyport: httpinitialDelaySeconds: 5periodSeconds: 10timeoutSeconds: 5failureThreshold: 2lifecycle:preStop:exec:command:- sh- -c- sleep 15 && kill -TERM 1securityContext:runAsNonRoot: truerunAsUser: 1000allowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities:drop:- ALLvolumeMounts:- name: tmpmountPath: /tmp- name: var-tmpmountPath: /var/tmpaffinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:- weight: 100podAffinityTerm:labelSelector:matchExpressions:- key: appoperator: Invalues:- fraiseqltopologyKey: kubernetes.io/hostnamevolumes:- name: tmpemptyDir: {}- name: var-tmpemptyDir: {}terminationGracePeriodSeconds: 40dnsPolicy: ClusterFirstsecurityContext:fsGroup: 1000seccompProfile:type: RuntimeDefaultTerminal window kubectl apply -f fraiseql-deployment.yaml -
Create Service
fraiseql-service.yaml apiVersion: v1kind: Servicemetadata:name: fraiseqlnamespace: fraiseqllabels:app: fraiseqlspec:type: ClusterIPselector:app: fraiseqlports:- name: httpport: 8000targetPort: 8000protocol: TCP- name: metricsport: 9000targetPort: 9000protocol: TCPsessionAffinity: NoneTerminal window kubectl apply -f fraiseql-service.yaml -
Create Ingress
For external access with TLS:
fraiseql-ingress.yaml apiVersion: networking.k8s.io/v1kind: Ingressmetadata:name: fraiseqlnamespace: fraiseqlannotations:cert-manager.io/cluster-issuer: "letsencrypt-prod"nginx.ingress.kubernetes.io/rate-limit: "1000"nginx.ingress.kubernetes.io/ssl-redirect: "true"spec:ingressClassName: nginxtls:- hosts:- api.example.comsecretName: fraiseql-tlsrules:- host: api.example.comhttp:paths:- path: /pathType: Prefixbackend:service:name: fraiseqlport:number: 8000Terminal window kubectl apply -f fraiseql-ingress.yaml
Auto-Scaling
Section titled “Auto-Scaling”Horizontal Pod Autoscaler (HPA)
Section titled “Horizontal Pod Autoscaler (HPA)”apiVersion: autoscaling/v2kind: HorizontalPodAutoscalermetadata: name: fraiseql namespace: fraiseqlspec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: fraiseql 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 - type: Pods pods: metric: name: http_requests_per_second target: type: AverageValue averageValue: "1000" behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 30 - type: Pods value: 2 periodSeconds: 30kubectl apply -f fraiseql-hpa.yaml
# View HPA statuskubectl get hpa -n fraiseql --watchPod Disruption Budget
Section titled “Pod Disruption Budget”Ensure minimum availability during maintenance:
apiVersion: policy/v1kind: PodDisruptionBudgetmetadata: name: fraiseql namespace: fraiseqlspec: minAvailable: 2 selector: matchLabels: app: fraiseqlkubectl apply -f fraiseql-pdb.yamlDatabase in Kubernetes
Section titled “Database in Kubernetes”apiVersion: apps/v1kind: StatefulSetmetadata: name: postgres namespace: fraiseqlspec: serviceName: postgres replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:16-alpine ports: - containerPort: 5432 env: - name: POSTGRES_DB value: fraiseql - name: POSTGRES_USER value: fraiseql - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: postgres-secret key: password volumeMounts: - name: data mountPath: /var/lib/postgresql/data subPath: postgres volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 50GiFor production, point to a managed database:
kubectl set env deployment/fraiseql \ DATABASE_URL="postgresql://user:pass@managed-rds.amazonaws.com:5432/fraiseql" \ -n fraiseqlMonitoring
Section titled “Monitoring”Prometheus ServiceMonitor
Section titled “Prometheus ServiceMonitor”apiVersion: monitoring.coreos.com/v1kind: ServiceMonitormetadata: name: fraiseql namespace: fraiseql labels: release: prometheusspec: selector: matchLabels: app: fraiseql endpoints: - port: metrics interval: 30s path: /metricsLogging
Section titled “Logging”Fluent Bit DaemonSet
Section titled “Fluent Bit DaemonSet”apiVersion: apps/v1kind: DaemonSetmetadata: name: fluent-bit namespace: fraiseqlspec: selector: matchLabels: app: fluent-bit template: metadata: labels: app: fluent-bit spec: containers: - name: fluent-bit image: fluent/fluent-bit:latest volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true - name: fluent-bit-config mountPath: /fluent-bit/etc/ volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers - name: fluent-bit-config configMap: name: fluent-bit-configRolling Updates
Section titled “Rolling Updates”Zero-downtime deployments:
# Set new imagekubectl set image deployment/fraiseql \ fraiseql=your-registry/fraiseql:2.0.0 \ -n fraiseql
# Monitor rolloutkubectl rollout status deployment/fraiseql -n fraiseql
# View historykubectl rollout history deployment/fraiseql -n fraiseql
# Rollback if neededkubectl rollout undo deployment/fraiseql -n fraiseqlNetwork Policies
Section titled “Network Policies”Restrict traffic between pods:
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: fraiseql namespace: fraiseqlspec: podSelector: matchLabels: app: fraiseql policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8000 egress: - to: - namespaceSelector: {} ports: - protocol: UDP port: 53 - to: - podSelector: matchLabels: app: postgres ports: - protocol: TCP port: 5432 - to: - namespaceSelector: {} ports: - protocol: TCP port: 443Create a service account with minimal permissions:
apiVersion: v1kind: ServiceAccountmetadata: name: fraiseql namespace: fraiseql
---apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: fraiseql namespace: fraiseqlrules: [] # Add rules only if your app needs to access the K8s API
---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: fraiseql namespace: fraiseqlroleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: fraiseqlsubjects: - kind: ServiceAccount name: fraiseql namespace: fraiseqlBackup Strategy
Section titled “Backup Strategy”apiVersion: batch/v1kind: CronJobmetadata: name: fraiseql-backup namespace: fraiseqlspec: schedule: "0 2 * * *" # Daily at 2 AM jobTemplate: spec: template: spec: serviceAccountName: fraiseql containers: - name: backup image: postgres:16-alpine env: - name: DATABASE_URL valueFrom: secretKeyRef: name: fraiseql-secrets key: database-url command: - /bin/sh - -c - | pg_dump $DATABASE_URL | \ gzip > /backups/fraiseql-$(date +%Y%m%d-%H%M%S).sql.gz volumeMounts: - name: backups mountPath: /backups volumes: - name: backups persistentVolumeClaim: claimName: backups-pvc restartPolicy: OnFailureTroubleshooting
Section titled “Troubleshooting”View Pod Logs
Section titled “View Pod Logs”# Current logskubectl logs deployment/fraiseql -n fraiseql
# Previous logs (if pod crashed)kubectl logs deployment/fraiseql -n fraiseql --previous
# Follow logskubectl logs deployment/fraiseql -n fraiseql -f
# Logs from all podskubectl logs -l app=fraiseql -n fraiseql --all-containersDebug a Pod
Section titled “Debug a Pod”# Describe pod (events, status)kubectl describe pod fraiseql-abc123-def456 -n fraiseql
# Execute command in podkubectl exec -it fraiseql-abc123-def456 -n fraiseql -- bash
# Check environment variableskubectl exec fraiseql-abc123-def456 -n fraiseql -- env | grep DATABASECommon Issues
Section titled “Common Issues”CrashLoopBackOff: Pod keeps crashing
kubectl describe pod <pod-name> -n fraiseqlkubectl logs <pod-name> -n fraiseql --previous# Check database connectivity and environment variablesImagePullBackOff: Cannot pull Docker image
# Verify image exists and add registry credentialskubectl create secret docker-registry regcred \ --docker-server=your-registry \ --docker-username=user \ --docker-password=pass# Add imagePullSecrets to deploymentPending: Pod cannot be scheduled
# Check resource availabilitykubectl top nodeskubectl describe node <node-name># May need to increase resource requests in deploymentProduction Checklist
Section titled “Production Checklist”- Use managed database (RDS, Cloud SQL)
- Configure HPA with appropriate metrics
- Set resource requests and limits
- Configure readiness and liveness probes
- Use Pod Disruption Budget
- Configure Network Policies
- Set up monitoring (Prometheus)
- Set up logging (ELK, CloudWatch, etc.)
- Configure automatic backups
- Set up SSL/TLS with cert-manager
- Test rolling updates
- Document runbooks for common issues