Kubernetes-Native PHP: Orchestrating High-Availability Laravel Applications with GitOps
Leveraging Kubernetes for High-Availability Laravel Deployments
Orchestrating modern, high-availability web applications demands robust infrastructure. For PHP applications, particularly those built with frameworks like Laravel, Kubernetes has emerged as the de facto standard for container orchestration. This post details a Kubernetes-native approach to deploying and managing Laravel applications, focusing on GitOps principles for declarative, automated, and auditable deployments.
Core Components: Docker, Kubernetes, and GitOps
Our strategy hinges on three key technologies:
- Docker: Encapsulating the Laravel application and its dependencies into immutable container images.
- Kubernetes: Providing the runtime environment for these containers, managing scaling, self-healing, and service discovery.
- GitOps: Using Git as the single source of truth for declarative infrastructure and application configurations, enabling automated deployments via tools like Argo CD or Flux CD.
Crafting the Dockerfile for Laravel
A well-structured Dockerfile is crucial for efficient and secure Laravel deployments. We’ll aim for a multi-stage build to keep the final image lean.
Stage 1: Builder
This stage installs PHP, Composer, and other build dependencies. It then installs application dependencies and compiles assets.
Stage 2: Runtime
This stage uses a minimal PHP-FPM image, copies the compiled application code, and sets up the necessary configurations.
Here’s an example Dockerfile:
Example Dockerfile
# Stage 1: Builder
FROM php:8.2-fpm AS builder
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libonig-dev \
libxml2-dev \
zip \
acl \
supervisor \
cron \
&& rm -rf /var/lib/apt/lists/*
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install -j$(nproc) gd mbstring exif pcntl bcmath opcache zip xml \
&& pecl install redis \
&& docker-php-ext-enable redis
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy composer.json and composer.lock
COPY composer.json composer.lock ./
# Install dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Copy application code
COPY . .
# Compile assets (if using Laravel Mix/Vite)
RUN npm install && npm run build
# Clear cache
RUN php artisan cache:clear && php artisan config:clear && php artisan route:clear && php artisan view:clear
# Stage 2: Runtime
FROM php:8.2-fpm-alpine
# Install system dependencies for runtime
RUN apk update && apk add --no-cache \
libzip \
libpng \
libjpeg-turbo \
freetype \
oniguruma \
libxml2 \
acl \
supervisor \
cron \
&& rm -rf /var/cache/apk/*
# Install PHP extensions for runtime
RUN docker-php-ext-configure gd --with-freetype --with-jpeg && docker-php-ext-install -j$(nproc) gd mbstring exif pcntl bcmath opcache zip xml \
&& pecl install redis \
&& docker-php-ext-enable redis
# Copy compiled dependencies and application code from builder stage
COPY --from=builder /var/www/html /var/www/html
# Ensure correct permissions for storage and bootstrap/cache
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache && chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache
# Copy supervisor configuration
COPY docker/supervisor/app.conf /etc/supervisor/conf.d/app.conf
# Copy cron configuration (if applicable)
COPY docker/cron/cronjobs /etc/cron.d/cronjobs
RUN chmod 0644 /etc/cron.d/cronjobs && crontab /etc/cron.d/cronjobs
# Expose port
EXPOSE 9000
# Set user
USER www-data
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
Kubernetes Manifests for Deployment
We’ll define our Laravel application’s deployment using Kubernetes manifests. This includes Deployments, Services, Ingresses, and potentially PersistentVolumeClaims for stateful data.
Deployment Manifest
This manifest defines how to deploy and update our Laravel application pods. It specifies the Docker image, replica count, and rolling update strategy.
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app
labels:
app: laravel
spec:
replicas: 3 # Adjust based on expected load
selector:
matchLabels:
app: laravel
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: laravel
spec:
containers:
- name: laravel-app
image: your-docker-registry/laravel-app:latest # Replace with your image
ports:
- containerPort: 9000
env:
- name: APP_NAME
value: "My Laravel App"
- name: APP_ENV
value: "production"
- name: APP_KEY
valueFrom:
secretKeyRef:
name: laravel-secrets
key: APP_KEY
- name: APP_DEBUG
value: "false"
- name: DB_CONNECTION
value: "mysql"
- name: DB_HOST
value: "mysql-service" # Kubernetes Service name for your DB
- name: DB_PORT
value: "3306"
- name: DB_DATABASE
valueFrom:
secretKeyRef:
name: laravel-secrets
key: DB_DATABASE
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: laravel-secrets
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: laravel-secrets
key: DB_PASSWORD
- name: REDIS_HOST
value: "redis-service" # Kubernetes Service name for your Redis
- name: REDIS_PORT
value: "6379"
livenessProbe:
httpGet:
path: /healthz # Define a health check endpoint in your Laravel app
port: 9000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: 9000
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
# If using persistent storage for uploads, uncomment and configure
# volumes:
# - name: storage-volume
# persistentVolumeClaim:
# claimName: laravel-storage-pvc
Service Manifest
This manifest exposes our application pods as a network service, allowing other services within the cluster and external traffic to reach it.
apiVersion: v1
kind: Service
metadata:
name: laravel-app-service
spec:
selector:
app: laravel
ports:
- protocol: TCP
port: 80
targetPort: 9000 # Port your container listens on
type: ClusterIP # Or LoadBalancer if exposing directly
Ingress Manifest
An Ingress resource manages external access to services in a cluster, typically HTTP. It provides routing, SSL termination, and load balancing.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # Example for Nginx Ingress Controller
spec:
rules:
- host: your-app.example.com # Replace with your domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-app-service
port:
number: 80
# If using TLS, uncomment and configure
# tls:
# - hosts:
# - your-app.example.com
# secretName: your-tls-secret # Kubernetes secret containing your TLS certificate
Secrets Manifest
Sensitive information like database credentials and the application key should be stored in Kubernetes Secrets.
apiVersion: v1 kind: Secret metadata: name: laravel-secrets type: Opaque data: APP_KEY: YOUR_BASE64_ENCODED_APP_KEY DB_DATABASE: YOUR_BASE64_ENCODED_DB_NAME DB_USERNAME: YOUR_BASE64_ENCODED_DB_USERNAME DB_PASSWORD: YOUR_BASE64_ENCODED_DB_PASSWORD
To generate the base64 encoded values, use:
echo -n "your_value" | base64
Database and Cache Considerations
For high availability, your database and cache layers should also be managed. This typically involves deploying managed services (like AWS RDS, Google Cloud SQL) or running stateful sets within Kubernetes for databases like MySQL and Redis.
Database Configuration
Ensure your Laravel application’s .env file (or environment variables injected via Kubernetes Secrets) correctly points to your database service. For example, if you have a MySQL deployment with a service named mysql-service:
DB_CONNECTION=mysql DB_HOST=mysql-service DB_PORT=3306 DB_DATABASE=your_database DB_USERNAME=your_user DB_PASSWORD=your_password
Cache Configuration
Similarly, configure Redis for caching. If your Redis service is named redis-service:
CACHE_DRIVER=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis REDIS_HOST=redis-service REDIS_PORT=6379
Implementing GitOps with Argo CD
GitOps automates deployments by continuously reconciling the desired state defined in Git with the actual state of your Kubernetes cluster. Argo CD is a popular choice for this.
Argo CD Setup
1. **Install Argo CD:** Follow the official Argo CD documentation to install it into your cluster.
2. **Create a Git Repository:** Store all your Kubernetes manifests (Deployment, Service, Ingress, Secrets, etc.) in a dedicated Git repository. Structure it logically, perhaps by application or environment.
3. **Create an Argo CD Application:** This defines how Argo CD syncs your Git repository with your Kubernetes cluster.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: laravel-app-gitops
namespace: argocd # Namespace where Argo CD is installed
spec:
project: default
source:
repoURL: https://github.com/your-username/your-k8s-repo.git # Your Git repo URL
targetRevision: HEAD # Or a specific branch/tag
path: /path/to/your/laravel/manifests # Path within the repo
destination:
server: https://kubernetes.default.svc # Target Kubernetes cluster
namespace: default # Target namespace for the Laravel app
syncPolicy:
automated:
prune: true # Automatically delete resources that are removed from Git
selfHeal: true # Automatically fix drift between Git and cluster state
syncOptions:
- CreateNamespace=true # Create the namespace if it doesn't exist
Apply this manifest to your cluster:
kubectl apply -f argo-app.yaml -n argocd
High-Availability and Scalability Features
Kubernetes provides several built-in mechanisms for achieving high availability:
- Replicas: The
replicasfield in the Deployment ensures that a specified number of pod instances are always running. If a pod fails, Kubernetes automatically replaces it. - Liveness and Readiness Probes: These probes allow Kubernetes to determine if a pod is alive and ready to serve traffic. This prevents traffic from being sent to unhealthy pods and ensures quick recovery.
- Rolling Updates: The
RollingUpdatestrategy ensures zero-downtime deployments by gradually replacing old pods with new ones. - Horizontal Pod Autoscaler (HPA): For dynamic scaling based on CPU or memory utilization, configure an HPA.
Example Horizontal Pod Autoscaler
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: laravel-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-app
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale up when CPU utilization reaches 70%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # Scale up when Memory utilization reaches 70%
Conclusion
By adopting a Kubernetes-native approach with GitOps, you can achieve robust, scalable, and highly available deployments for your Laravel applications. This strategy centralizes configuration, automates deployments, and provides a clear audit trail, significantly improving operational efficiency and reliability.