Kubernetes-Native PHP: Orchestrating High-Availability Laravel Applications with GitOps
Leveraging GitOps for High-Availability Laravel on Kubernetes
Orchestrating complex, high-availability PHP applications like Laravel on Kubernetes requires a robust, declarative approach. GitOps, with its emphasis on Git as the single source of truth for infrastructure and application state, provides an ideal paradigm. This post details a Kubernetes-native architecture for Laravel, focusing on high availability, automated deployments, and operational efficiency through GitOps principles.
Core Components: Kubernetes Resources for Laravel
A typical high-availability Laravel deployment on Kubernetes involves several key resource types:
- Deployments: Manage stateless application pods (your Laravel application).
- StatefulSets: Manage stateful components like databases (e.g., MySQL, PostgreSQL).
- Services: Provide stable network endpoints for your application pods and databases.
- Ingress: Expose your Laravel application to external traffic, handling routing and TLS termination.
- PersistentVolumes (PVs) & PersistentVolumeClaims (PVCs): Ensure data persistence for databases and other stateful services.
- ConfigMaps & Secrets: Store application configuration and sensitive credentials separately from container images.
- Horizontal Pod Autoscaler (HPA): Automatically scale your Laravel application pods based on CPU or memory utilization.
GitOps Workflow with Argo CD
We’ll use Argo CD as our GitOps engine. Argo CD continuously monitors a Git repository containing your Kubernetes manifests and ensures the cluster state matches the desired state defined in Git. Any drift is automatically reconciled.
Structuring Your Git Repository
A well-organized Git repository is crucial. A common structure separates application manifests from infrastructure configurations:
gitops-repo/
├── apps/
│ └── laravel-app/
│ ├── base/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ ├── ingress.yaml
│ │ ├── configmap.yaml
│ │ └── hpa.yaml
│ └── overlays/
│ ├── production/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── staging/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
├── databases/
│ └── mysql/
│ ├── statefulset.yaml
│ ├── service.yaml
│ ├── pvc.yaml
│ └── secrets.yaml
└── argocd/
└── applications.yaml
Example: Laravel Deployment Manifest
This manifest defines the Kubernetes Deployment for your Laravel application. It includes readiness and liveness probes, resource requests/limits, and environment variables sourced from ConfigMaps and Secrets.
# apps/laravel-app/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app
labels:
app: laravel-app
spec:
replicas: 3 # Default replica count, can be overridden by HPA or Kustomize patches
selector:
matchLabels:
app: laravel-app
template:
metadata:
labels:
app: laravel-app
spec:
containers:
- name: app
image: your-docker-registry/laravel-app:latest # Replace with your image
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: laravel-app-config
- secretRef:
name: laravel-app-secrets
livenessProbe:
httpGet:
path: /_healthz # A simple health check endpoint in your Laravel app
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /_healthz
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# Add imagePullSecrets if using a private registry
# imagePullSecrets:
# - name: regcred
Example: Laravel Service Manifest
A ClusterIP Service provides a stable internal IP address and DNS name for your Laravel application pods, allowing other services (like your Ingress controller) to reach it.
# apps/laravel-app/base/service.yaml
apiVersion: v1
kind: Service
metadata:
name: laravel-app-service
labels:
app: laravel-app
spec:
selector:
app: laravel-app
ports:
- protocol: TCP
port: 80
targetPort: 8000 # The port your Laravel app listens on inside the container
type: ClusterIP
Example: Laravel Ingress Manifest
The Ingress resource routes external HTTP(S) traffic to your Laravel application Service. This example assumes you have an Ingress controller (like Nginx Ingress or Traefik) installed in your cluster.
# apps/laravel-app/base/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-app-ingress
annotations:
# Example annotation for Nginx Ingress Controller
nginx.ingress.kubernetes.io/rewrite-target: /
# Add other annotations for SSL, CORS, etc. as needed
spec:
ingressClassName: nginx # Specify your Ingress Controller class
rules:
- host: your-laravel-app.example.com # Replace with your domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-app-service
port:
number: 80
# Optional: TLS configuration for HTTPS
# tls:
# - hosts:
# - your-laravel-app.example.com
# secretName: your-tls-secret # Kubernetes secret containing your TLS certificate
Example: Laravel ConfigMap and Secrets
Separating configuration and secrets is a best practice. ConfigMaps are for non-sensitive data, while Secrets are for sensitive information like database passwords and API keys. These should be managed securely, potentially with external secret management solutions integrated with Kubernetes.
# apps/laravel-app/base/configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: laravel-app-config data: APP_ENV: "production" APP_DEBUG: "false" APP_URL: "https://your-laravel-app.example.com" QUEUE_CONNECTION: "database" # Or redis, sqs, etc. # Add other non-sensitive Laravel config values here # Example: CACHE_DRIVER: "redis" --- # apps/laravel-app/base/secrets.yaml apiVersion: v1 kind: Secret metadata: name: laravel-app-secrets type: Opaque data: # Base64 encoded values APP_KEY: "base64:your_laravel_app_key_base64_encoded==" # Generate with `php artisan key:generate --show` DB_HOST: "base64:bXlzcWwtc2VydmljZQ==" # e.g., mysql-service DB_PORT: "base64:MzM0Ng==" # e.g., 3306 DB_DATABASE: "base64:bGFyYXZlbGRi" # e.g., laraveldb DB_USERNAME: "base64:dXNlcg==" # e.g., user DB_PASSWORD: "base64:cGFzc3dvcmQ=" # e.g., password # Add other sensitive Laravel config values here # Example: STRIPE_SECRET: "base64:sk_test_..."
Example: Horizontal Pod Autoscaler (HPA)
Configure HPA to automatically adjust the number of Laravel application pods based on resource utilization. This ensures your application can handle varying loads without manual intervention.
# apps/laravel-app/base/hpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: laravel-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-app
minReplicas: 3 # Minimum number of pods
maxReplicas: 10 # Maximum number of pods
targetCPUUtilizationPercentage: 70 # Scale up when CPU utilization reaches 70%
# targetMemoryUtilizationPercentage: 70 # Can also scale based on memory
Stateful Components: Database Example (MySQL)
For stateful services like databases, StatefulSets are preferred. They provide stable network identities, persistent storage, and ordered, graceful deployment and scaling.
# databases/mysql/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: "mysql-headless" # Headless service for stable DNS
replicas: 2 # For high availability, consider replication setup
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0 # Use a specific, stable version
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: root-password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-secrets
key: database
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secrets
key: user
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secrets
key: password
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-persistent-storage
spec:
accessModes: [ "ReadWriteOnce" ] # Or ReadWriteMany if your storage supports it
resources:
requests:
storage: 10Gi # Adjust storage size as needed
# storageClassName: your-storage-class # Specify if needed
# databases/mysql/service.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
clusterIP: None # This makes it a headless service
selector:
app: mysql
---
apiVersion: v1
kind: Service
metadata:
name: mysql-service # A regular service for easier access
labels:
app: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
# databases/mysql/secrets.yaml apiVersion: v1 kind: Secret metadata: name: mysql-secrets type: Opaque data: root-password: "base64:your_root_password==" database: "base64:bGFyYXZlbGRi" user: "base64:dXNlcg==" password: "base64:cGFzc3dvcmQ="
Kustomize for Environment-Specific Configurations
Kustomize allows you to customize base Kubernetes manifests without forking them. This is ideal for managing differences between environments (e.g., staging vs. production).
# apps/laravel-app/overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base # Include all base manifests patchesStrategicMerge: - patch-replicas.yaml # Apply environment-specific patches # Example: Overriding image tag for production # images: # - name: your-docker-registry/laravel-app # newTag: v1.2.3-production
# apps/laravel-app/overlays/production/patch-replicas.yaml apiVersion: apps/v1 kind: Deployment metadata: name: laravel-app spec: replicas: 5 # More replicas for production
Argo CD Application Definition
This Argo CD Application resource tells Argo CD which Git repository to monitor and which paths within that repository contain the manifests to deploy to a specific Kubernetes cluster and namespace.
# argocd/applications.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: laravel-app-production
namespace: argocd # Namespace where Argo CD is installed
spec:
project: default
source:
repoURL: https://github.com/your-org/gitops-repo.git # Your Git repository URL
targetRevision: HEAD # Or a specific branch/tag
path: apps/laravel-app/overlays/production # Path to Kustomize overlays for production
destination:
server: https://kubernetes.default.svc # Target cluster
namespace: laravel-prod # Target namespace in the cluster
syncPolicy:
automated:
prune: true # Automatically delete resources that are no longer in Git
selfHeal: true # Automatically reconcile drift
syncOptions:
- CreateNamespace=true # Create the namespace if it doesn't exist
High Availability Considerations
Achieving true high availability involves more than just multiple replicas:
- Database Replication: Implement primary-replica setups for your database (e.g., MySQL replication, PostgreSQL streaming replication) and ensure your Laravel application is configured to use read replicas where appropriate.
- Load Balancing: Kubernetes Services and Ingress controllers provide load balancing across application pods. Ensure your Ingress controller is also deployed in a highly available manner.
- Pod Disruption Budgets (PDBs): Define PDBs to ensure a minimum number of application pods are available during voluntary disruptions (e.g., node upgrades, deployments).
- Health Checks: Robust liveness and readiness probes are critical. Your Laravel application should expose a dedicated health check endpoint that verifies database connectivity and other essential services.
- Graceful Shutdowns: Ensure your Laravel application handles SIGTERM signals gracefully to finish in-flight requests before exiting.
- Multi-AZ/Region Deployments: For maximum resilience, deploy your Kubernetes cluster and its underlying infrastructure across multiple availability zones or regions.
Operationalizing with GitOps
The GitOps approach simplifies operations significantly:
- Automated Deployments: Pushing a commit to your Git repository automatically triggers deployments via Argo CD.
- Rollbacks: Reverting a commit in Git is a straightforward way to roll back to a previous stable version.
- Auditing: Git history provides a complete audit trail of all infrastructure and application changes.
- Disaster Recovery: Re-creating your entire environment is as simple as pointing Argo CD to your Git repository.
- Developer Experience: Developers can manage application deployments by simply updating configuration files in Git, reducing reliance on Ops teams for routine changes.
Next Steps and Advanced Topics
This architecture provides a solid foundation. For further enhancement, consider:
- CI/CD Integration: Integrate your CI pipeline (e.g., GitHub Actions, GitLab CI) to build Docker images, run tests, and then trigger a Git push to update the manifests in your GitOps repository.
- External Secret Management: Integrate tools like HashiCorp Vault or AWS Secrets Manager for more robust secret management.
- Service Mesh: Implement a service mesh (e.g., Istio, Linkerd) for advanced traffic management, observability, and security features.
- Monitoring and Alerting: Set up comprehensive monitoring (Prometheus, Grafana) and alerting for your Kubernetes cluster and Laravel application.
- Database Migrations: Develop a strategy for running database migrations in a Kubernetes-native way, often involving Kubernetes Jobs or specialized operators.