Beyond Containers: Orchestrating High-Availability PHP 8 Applications with Kubernetes and Managed Databases
Kubernetes Deployment Strategy for PHP 8 Applications
Orchestrating modern, high-availability PHP 8 applications requires a robust strategy that goes beyond simple containerization. Kubernetes, with its declarative configuration and self-healing capabilities, provides the ideal platform. We’ll focus on a multi-tier architecture, separating the PHP application, web server (Nginx), and managed database into distinct Kubernetes Deployments and StatefulSets.
PHP Application Deployment with PHP-FPM and Nginx
A common and performant pattern for PHP on Kubernetes involves using PHP-FPM as the application server and Nginx as the reverse proxy. This separation allows for independent scaling and configuration. We’ll define a Kubernetes Deployment for our PHP application, leveraging a custom Docker image that includes PHP 8, necessary extensions, and our application code. The Nginx pods will be deployed separately, configured to proxy requests to the PHP-FPM pods.
First, let’s outline a sample Dockerfile for our PHP application. This image will run PHP-FPM on port 9000.
# Use an official PHP 8.2 image as a parent image
FROM php:8.2-fpm
# Set the working directory in the container
WORKDIR /var/www/html
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
zip \
unzip \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libonig-dev \
libxml2-dev \
libxslt1-dev \
libicu-dev \
acl \
libcurl4-openssl-dev \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libonig-dev \
libxml2-dev \
libxslt1-dev \
libicu-dev \
acl \
libcurl4-openssl-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install pdo pdo_mysql zip exif pcntl opcache sockets intl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
# Copy application code
COPY . .
# Install Composer dependencies
RUN composer install --no-dev --optimize-autoloader
# Set permissions for the application directory
RUN chown -R www-data:www-data /var/www/html
# Expose port 9000 for PHP-FPM
EXPOSE 9000
Next, we define the Kubernetes Deployment for our PHP application. This will manage the PHP-FPM pods. We’ll use a Service to expose the PHP-FPM pods internally within the cluster.
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-deployment
labels:
app: php-app
spec:
replicas: 3 # Start with 3 replicas for HA
selector:
matchLabels:
app: php-app
template:
metadata:
labels:
app: php-app
spec:
containers:
- name: php-app
image: your-docker-registry/your-php-app:latest # Replace with your image
ports:
- containerPort: 9000
name: php-fpm
livenessProbe:
exec:
command: ["kill", "-0", "1"] # Basic check, can be improved with a health check script
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
exec:
command: ["kill", "-0", "1"] # Basic check, can be improved with a health check script
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: php-app-service
spec:
selector:
app: php-app
ports:
- protocol: TCP
port: 9000
targetPort: php-fpm
Now, let’s configure the Nginx Ingress Controller or a dedicated Nginx Deployment to act as the web server and reverse proxy. For simplicity, we’ll show a basic Nginx Deployment configuration that proxies to the PHP-FPM service. In a production environment, you’d likely use an Ingress Controller for more advanced routing and TLS management.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2 # Nginx replicas for HA
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest # Use a specific version in production
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config-volume
mountPath: /etc/nginx/conf.d
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumes:
- name: nginx-config-volume
configMap:
name: nginx-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
server {
listen 80;
server_name localhost;
root /var/www/html; # Assuming your PHP app serves from here
location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php-app-service:9000; # Points to the PHP-FPM service
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
}
# Deny access to hidden files
location ~ /\.ht {
deny all;
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer # Or ClusterIP if using an Ingress Controller
Managed Database Integration (e.g., AWS RDS, Google Cloud SQL)
For high availability and simplified management, we strongly recommend using managed database services (like AWS RDS, Google Cloud SQL, Azure Database for MySQL/PostgreSQL). These services handle replication, backups, patching, and failover, significantly reducing operational overhead. Your PHP application pods will connect to these external databases using their provided connection strings and credentials.
The key is to securely manage database credentials. Kubernetes Secrets are the standard way to store sensitive information like database passwords. Your PHP application should be configured to read these secrets.
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: DB_HOST: [base64-encoded-db-host] # e.g., "rds.amazonaws.com" DB_PORT: [base64-encoded-db-port] # e.g., "3306" DB_DATABASE: [base64-encoded-db-name] # e.g., "myappdb" DB_USERNAME: [base64-encoded-db-user] # e.g., "myappuser" DB_PASSWORD: [base64-encoded-db-password] # e.g., "supersecretpassword"
Your PHP application code (or framework configuration) will then retrieve these values from environment variables injected from the Kubernetes Secret. For example, in a Laravel application, your `.env` file might look like this, and the deployment would inject these as environment variables.
DB_CONNECTION=mysql
DB_HOST=${DB_HOST}
DB_PORT=${DB_PORT}
DB_DATABASE=${DB_DATABASE}
DB_USERNAME=${DB_USERNAME}
DB_PASSWORD=${DB_PASSWORD}
And in your Kubernetes Deployment for the PHP app, you’d add the `envFrom` section:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-deployment
labels:
app: php-app
spec:
replicas: 3
selector:
matchLabels:
app: php-app
template:
metadata:
labels:
app: php-app
spec:
containers:
- name: php-app
image: your-docker-registry/your-php-app:latest
ports:
- containerPort: 9000
name: php-fpm
envFrom:
- secretRef:
name: db-credentials # Reference to the Kubernetes Secret
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
High Availability and Scalability Considerations
Replicas: The `replicas` field in Deployments (e.g., `php-app-deployment` and `nginx-deployment`) dictates the number of pods running. Setting this to 3 or more ensures that if one pod fails, others can continue serving traffic. Kubernetes will automatically reschedule failed pods.
Probes: livenessProbe and readinessProbe are critical. A failing liveness probe will cause Kubernetes to restart the container. A failing readiness probe will remove the pod from service endpoints, preventing traffic from being sent to it. The example probes are basic; for production, implement more sophisticated checks (e.g., hitting a health check endpoint that verifies database connectivity).
# Example of a more robust readinessProbe
readinessProbe:
httpGet:
path: /healthz # A dedicated health check endpoint in your app
port: 80
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
Resource Requests and Limits: Properly setting CPU and memory requests and limits is crucial for stability and efficient resource utilization. Requests ensure pods get the minimum resources they need, while limits prevent runaway processes from consuming all node resources.
Horizontal Pod Autoscaler (HPA): For dynamic scaling based on load, configure an HPA. This will automatically adjust the number of replicas for your `php-app-deployment` based on CPU or memory utilization.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-app-deployment
minReplicas: 3
maxReplicas: 10 # Scale up to 10 replicas
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Scale up when CPU utilization reaches 70%
Ingress Controller: For external access, an Ingress Controller (like Nginx Ingress, Traefik, or cloud-provider specific ones) is recommended. It provides a single entry point, handles TLS termination, and offers advanced routing rules. The `nginx-service` with `type: LoadBalancer` is a simpler alternative for basic external access but lacks the features of a full Ingress Controller.
Monitoring and Logging
A production-ready setup necessitates robust monitoring and logging. Integrate with a cluster-wide logging solution (e.g., Elasticsearch, Fluentd, Kibana – EFK stack, or Loki, Promtail, Grafana – PLG stack) to aggregate logs from all pods. Prometheus and Grafana are excellent choices for metrics collection and visualization, allowing you to track CPU/memory usage, request latency, and error rates.
Ensure your PHP application logs errors and important events to standard output (stdout) and standard error (stderr), as these are captured by Kubernetes and can be forwarded by your logging agents.