Kubernetes Deployment
Kubernetes Deployment
Deploy PHP applications with Cbox Init on Kubernetes using ConfigMaps for configuration and HorizontalPodAutoscaler for dynamic scaling.
Use Cases
- ✅ Production Kubernetes deployments
- ✅ Multi-environment configuration (dev/staging/prod)
- ✅ Auto-scaling based on CPU/memory
- ✅ ConfigMap-based PHP-FPM tuning
- ✅ Zero-downtime deployments
Architecture Overview
┌────────────────────────────────────────────┐
│ Kubernetes Namespace: production │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ ConfigMap: cbox-config │ │
│ │ - php_fpm_profile: medium │ │
│ │ - cbox_init_config: (YAML) │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Deployment: php-app (replicas:3)│ │
│ │ ┌──────────────┐ │ │
│ │ │ Pod 1 │ PHP-FPM + Nginx │ │
│ │ ├──────────────┤ + Horizon + Queue │ │
│ │ │ Pod 2 │ (2Gi RAM, 2 CPU) │ │
│ │ ├──────────────┤ │ │
│ │ │ Pod 3 │ Auto-tuned PHP │ │
│ │ └──────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ HPA: php-app-hpa │ │
│ │ Min: 3, Max: 10 │ │
│ │ Target: 70% CPU, 80% Memory │ │
│ └──────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Service: php-app-svc │ │
│ │ Type: ClusterIP │ │
│ │ Port: 80 │ │
│ └──────────────────────────────────────┘ │
└────────────────────────────────────────────┘
Complete Kubernetes Configuration
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: cbox-config
namespace: production
data:
# PHP-FPM auto-tune profile
php_fpm_profile: "medium"
# Cbox Init configuration
cbox_init_config: |
version: "1.0"
global:
shutdown_timeout: 60
log_format: json
log_level: info
metrics_enabled: true
metrics_port: 9090
hooks:
pre-start:
- name: config-cache
command: ["php", "artisan", "config:cache"]
timeout: 60
- name: migrate
command: ["php", "artisan", "migrate", "--force"]
timeout: 300
processes:
php-fpm:
enabled: true
command: ["php-fpm", "-F", "-R"]
restart: always
health_check:
type: tcp
address: 127.0.0.1:9000
nginx:
enabled: true
command: ["nginx", "-g", "daemon off;"]
depends_on: [php-fpm]
health_check:
type: http
url: http://localhost/health
horizon:
enabled: true
command: ["php", "artisan", "horizon"]
shutdown:
pre_stop_hook:
command: ["php", "artisan", "horizon:terminate"]
timeout: 60
queue-default:
enabled: true
command: ["php", "artisan", "queue:work"]
scale: 2
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
namespace: production
labels:
app: php
tier: backend
spec:
replicas: 3
selector:
matchLabels:
app: php
template:
metadata:
labels:
app: php
tier: backend
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
containers:
- name: app
image: myapp:v1.2.3
imagePullPolicy: IfNotPresent
# PHP-FPM auto-tuning from ConfigMap
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
valueFrom:
configMapKeyRef:
name: cbox-config
key: php_fpm_profile
# Application environment
- name: APP_ENV
value: "production"
- name: APP_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: app-key
- name: DB_HOST
value: "mysql-service"
- name: REDIS_HOST
value: "redis-service"
# Mount Cbox Init config
volumeMounts:
- name: cbox-config
mountPath: /etc/cbox-init
readOnly: true
# Container resource limits (auto-tuner uses these)
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2"
# Ports
ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 9090
name: metrics
protocol: TCP
# Readiness probe
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
# Liveness probe
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Volumes
volumes:
- name: cbox-config
configMap:
name: cbox-config
items:
- key: cbox_init_config
path: cbox-init.yaml
# Security
securityContext:
fsGroup: 1000
runAsUser: 1000
runAsNonRoot: true
Service
apiVersion: v1
kind: Service
metadata:
name: php-app-svc
namespace: production
labels:
app: php
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 9090
targetPort: 9090
protocol: TCP
name: metrics
selector:
app: php
HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale up at 70% CPU
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # Scale up at 80% memory
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: php-app-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.com
secretName: myapp-tls
rules:
- host: myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: php-app-svc
port:
number: 80
Multi-Environment Setup
Development Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-dev
namespace: development
spec:
replicas: 1 # Single replica for dev
template:
spec:
containers:
- name: app
image: myapp:dev
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
value: "dev" # Minimal resources
- name: CBOX_INIT_GLOBAL_LOG_LEVEL
value: "debug"
- name: CBOX_INIT_PROCESS_QUEUE_DEFAULT_SCALE
value: "1"
resources:
limits:
memory: "512Mi"
cpu: "500m"
Production with Heavy Traffic
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-prod
namespace: production
spec:
replicas: 5
template:
spec:
containers:
- name: app
image: myapp:v1.2.3
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
value: "heavy" # High traffic profile
- name: CBOX_INIT_PROCESS_QUEUE_DEFAULT_SCALE
value: "10"
resources:
limits:
memory: "8Gi"
cpu: "8"
Pod Scaling Strategy
Two-Layer Scaling
Layer 1: Kubernetes HPA (Pod-level)
# Scales number of pods
HPA:
minReplicas: 3
maxReplicas: 10
targetCPUUtilization: 70%
Layer 2: PHP-FPM Auto-Tuning (Per-pod)
# Scales workers within each pod
PHP_FPM_AUTOTUNE_PROFILE: medium
# With 2Gi memory → ~16 workers per pod
Total Capacity:
- 3 pods × 16 workers = 48 concurrent PHP requests (minimum)
- 10 pods × 16 workers = 160 concurrent PHP requests (maximum)
Queue Worker Scaling
# Per-pod queue workers
CBOX_INIT_PROCESS_QUEUE_DEFAULT_SCALE: 3
# With HPA
# Minimum: 3 pods × 3 workers = 9 total workers
# Maximum: 10 pods × 3 workers = 30 total workers
Monitoring Integration
Prometheus Scraping
# Pod annotations
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: php-app-metrics
namespace: production
spec:
selector:
matchLabels:
app: php
endpoints:
- port: metrics
interval: 30s
path: /metrics
Grafana Dashboard
# Import dashboard
# Metrics available:
# - cbox_init_process_up
# - cbox_init_process_restarts_total
# - cbox_init_process_health_status
# - cbox_init_manager_uptime_seconds
Resource Planning
Memory Calculation
Per Pod with Medium Profile:
Container memory: 2Gi
PHP-FPM auto-tune: medium profile
Calculation:
- Available: 2048 × 0.75 = 1536MB
- Reserved: 192MB (system) + 128MB (OPcache) = 320MB
- Worker memory: 1536 - 320 = 1216MB
- Workers: 1216 / 42MB = ~28 workers
- CPU limit: 2 cores × 4 = 8 workers (CPU LIMITED)
- Final: 8 workers per pod
With HPA (3-10 pods):
- Minimum capacity: 3 × 8 = 24 workers
- Maximum capacity: 10 × 8 = 80 workers
CPU Allocation
resources:
requests:
cpu: "500m" # 0.5 cores guaranteed
limits:
cpu: "2" # Max 2 cores
Recommendations:
requests: What pod needs to run (guaranteed)limits: Maximum pod can use (bursts)- Set
requeststo 50-70% oflimits
Deployment Strategies
Rolling Update (Zero Downtime)
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # Keep at least 4 pods running
maxSurge: 2 # Add up to 2 extra pods during update
Update flow:
- Create 2 new pods (total: 7)
- Wait for new pods to be healthy
- Terminate 1 old pod (total: 6)
- Repeat until all old pods replaced
Blue-Green Deployment
# Blue deployment (current)
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-blue
spec:
replicas: 5
selector:
matchLabels:
app: php
version: blue
# Green deployment (new)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-green
spec:
replicas: 5
selector:
matchLabels:
app: php
version: green
# Service switches between blue/green
---
apiVersion: v1
kind: Service
metadata:
name: php-app-svc
spec:
selector:
app: php
version: blue # Change to 'green' to switch
Secrets Management
Kubernetes Secrets
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
app-key: base64:your-app-key-here
db-password: your-db-password
redis-password: your-redis-password
api-token: your-cbox-api-token
Use in Deployment:
env:
- name: APP_KEY
valueFrom:
secretKeyRef:
name: app-secrets
key: app-key
- name: CBOX_INIT_GLOBAL_API_AUTH
valueFrom:
secretKeyRef:
name: app-secrets
key: api-token
External Secrets Operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: app-secrets
data:
- secretKey: app-key
remoteRef:
key: production/laravel/app-key
- secretKey: db-password
remoteRef:
key: production/database/password
Multi-Profile Deployments
Standard Profile
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
spec:
template:
spec:
containers:
- name: app
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
value: "medium"
resources:
limits:
memory: "2Gi"
cpu: "2"
Heavy Traffic Profile
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-heavy
spec:
template:
spec:
containers:
- name: app
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
value: "heavy"
resources:
limits:
memory: "8Gi"
cpu: "8"
Persistent Storage
Logs Volume
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-logs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
# Mount in Deployment
spec:
template:
spec:
containers:
- name: app
volumeMounts:
- name: logs
mountPath: /var/www/storage/logs
volumes:
- name: logs
persistentVolumeClaim:
claimName: app-logs
Shared Storage (Sessions, Cache)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-storage
spec:
accessModes:
- ReadWriteMany # Multiple pods can write
storageClassName: efs # AWS EFS or similar
resources:
requests:
storage: 50Gi
Health Checks
Kubernetes Probes
# Readiness: Is pod ready to receive traffic?
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# Liveness: Is pod still alive?
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
How they work together:
- Readiness: Removes pod from Service when unhealthy
- Liveness: Restarts pod if continuously unhealthy
- Both use Cbox Init's health endpoint
Complete Deployment
# Apply all resources
kubectl apply -f - <<EOF
---
apiVersion: v1
kind: Namespace
metadata:
name: production
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cbox-config
namespace: production
data:
php_fpm_profile: "medium"
cbox_init_config: |
version: "1.0"
# ... (full config from above)
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
namespace: production
type: Opaque
stringData:
app-key: ${APP_KEY}
db-password: ${DB_PASSWORD}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app
namespace: production
spec:
# ... (full deployment from above)
---
apiVersion: v1
kind: Service
metadata:
name: php-app-svc
namespace: production
spec:
# ... (full service from above)
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-app-hpa
namespace: production
spec:
# ... (full HPA from above)
EOF
Deployment Commands
# Create namespace
kubectl create namespace production
# Apply configuration
kubectl apply -f configmap.yaml
kubectl apply -f secrets.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f hpa.yaml
kubectl apply -f ingress.yaml
# Verify deployment
kubectl get pods -n production
kubectl get svc -n production
kubectl get hpa -n production
# Check logs
kubectl logs -n production deployment/php-app -f
# Check pod health
kubectl get pods -n production -o wide
# Describe pod
kubectl describe pod -n production php-app-xxx
Monitoring
Check Metrics
# Port-forward to metrics endpoint
kubectl port-forward -n production deployment/php-app 9090:9090
# Query metrics
curl http://localhost:9090/metrics
Check API
# Port-forward to API
kubectl port-forward -n production deployment/php-app 8080:8080
# Get process status
curl http://localhost:9180/api/v1/processes
View Logs
# All pods
kubectl logs -n production -l app=laravel --tail=100 -f
# Specific pod
kubectl logs -n production php-app-xxx -f
# Previous container (if crashed)
kubectl logs -n production php-app-xxx --previous
Troubleshooting
Pods Not Scaling
Check HPA status:
kubectl get hpa -n production
kubectl describe hpa -n production php-app-hpa
Common issues:
- Metrics server not installed
- Resource requests not set
- Target metrics unreachable
OOM Kills
Symptom:
kubectl describe pod php-app-xxx
# Reason: OOMKilled
Solutions:
# Option 1: Increase memory limits
resources:
limits:
memory: "4Gi" # Was 2Gi
# Option 2: Reduce PHP-FPM workers
env:
- name: PHP_FPM_AUTOTUNE_PROFILE
value: "light" # Was medium
Readiness Probe Failing
Check pod logs:
kubectl logs -n production php-app-xxx | grep health
Adjust probe:
readinessProbe:
initialDelaySeconds: 30 # Was 10, app needs more time
failureThreshold: 5 # Was 3, tolerate more failures
ConfigMap Not Updating
Problem: Changed ConfigMap but pods still use old config
Solution: Restart pods to pick up changes
kubectl rollout restart deployment/php-app -n production
Best Practices
Resource Limits
# Always set requests and limits
resources:
requests: # Guaranteed resources
memory: "1Gi"
cpu: "500m"
limits: # Maximum allowed
memory: "2Gi"
cpu: "2"
Health Probes
# Start liveness later than readiness
readinessProbe:
initialDelaySeconds: 10
livenessProbe:
initialDelaySeconds: 30 # Give app time to start
Pod Disruption Budget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: php-app-pdb
spec:
minAvailable: 2 # Always keep at least 2 pods running
selector:
matchLabels:
app: php
Security Context
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
capabilities:
drop:
- ALL
See Also
- PHP-FPM Auto-Tuning - Worker optimization for K8s
- Docker Integration - Container patterns
- Health Checks - Probe configuration
- Prometheus Metrics - Metrics integration