Beyond the Basics: Mastering Kubernetes-Native PHP Deployments with Laravel Octane and GitOps
Leveraging Laravel Octane for High-Performance PHP in Kubernetes
Traditional PHP deployments, often characterized by their stateless, request-per-process model, can become a bottleneck in high-throughput, low-latency environments. Laravel Octane fundamentally shifts this paradigm by keeping your application’s workers alive between requests, drastically reducing overhead and improving response times. When combined with Kubernetes, this offers a powerful, scalable, and resilient architecture. This section details the core Octane setup and its integration with a Kubernetes deployment strategy.
The primary goal is to run Octane’s application servers (Swoole, RoadRunner, or OpenSwoole) within Kubernetes pods. This requires careful configuration of the Octane worker process and its interaction with Kubernetes’s health checks and scaling mechanisms.
Octane Server Configuration for Production
For production, we’ll focus on Swoole or OpenSwoole due to their robust feature sets and widespread adoption. The key is to configure Octane to run as a long-lived process, managed by Kubernetes. We’ll use the swoole driver for this example.
First, ensure Octane is installed and configured in your Laravel project:
composer require laravel/octane php artisan octane:install
Next, configure Octane for production. The config/octane.php file is crucial. We’ll set the number of workers and the maximum requests per worker to prevent memory leaks and ensure stability.
In config/octane.php:
<?php
return [
'workers' => env('OCTANE_WORKERS', 4), // Number of worker processes
'max_requests' => env('OCTANE_MAX_REQUESTS', 1000), // Max requests before workers restart
'swoole' => [
'listen' => env('OCTANE_LISTEN', '0.0.0.0'),
'port' => env('OCTANE_PORT', 8000),
'options' => [
// Example Swoole options for production
'worker_num' => env('SWOOLE_WORKER_NUM', 8), // Often higher than octane 'workers'
'max_coro' => 20000,
'enable_coroutine' => true,
'http_compression' => true,
'ssl_cert_file' => env('SWOOLE_SSL_CERT'),
'ssl_key_file' => env('SWOOLE_SSL_KEY'),
],
],
// ... other Octane configurations
];
The OCTANE_WORKERS environment variable in .env (or set via Kubernetes secrets/configmaps) controls the number of Octane worker processes managed by the supervisor. The SWOOLE_WORKER_NUM within the swoole.options array configures the actual Swoole worker processes, which can be significantly higher to leverage multi-core CPUs effectively. The max_requests setting is vital for long-running processes to periodically restart workers, preventing memory bloat.
Containerizing Octane Applications
A robust Dockerfile is essential for packaging your Octane-enabled Laravel application. It needs to install Swoole extensions and ensure the application starts correctly.
Example Dockerfile:
# Use an official PHP image with Swoole pre-installed or install it
# For simplicity, we'll use a base image and install Swoole.
# Consider using official Swoole images for production.
FROM php:8.2-fpm
# Install necessary extensions and Swoole
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libpq-dev \
zlib1g-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 bcmath sockets \
&& pecl install swoole \
&& docker-php-ext-enable swoole
# Set working directory
WORKDIR /var/www/html
# Copy application files
COPY . /var/www/html
# Install Composer dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Expose the port Octane will listen on
EXPOSE 8000
# Start Octane with Swoole
# The 'php artisan octane:start' command will use the config/octane.php settings.
# We use 'supervisor' or similar process manager in production to keep this running.
# For Kubernetes, we'll rely on the entrypoint/command to manage this.
CMD ["php", "artisan", "octane:start", "--host=0.0.0.0", "--port=8000", "--workers=4", "--max-requests=1000"]
Note: The CMD in the Dockerfile is a starting point. In a real Kubernetes deployment, you’d typically use an entrypoint script that handles environment variable overrides and potentially starts a process manager like supervisord to ensure the Octane process stays alive. However, Kubernetes’s own restart policies and health checks can often manage this directly.
Kubernetes Deployment Strategy
Deploying Octane applications in Kubernetes requires a Deployment object that specifies the container image, resource requests/limits, and importantly, readiness and liveness probes. These probes are critical for Kubernetes to manage the Octane pods effectively.
Example deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-octane-app
labels:
app: laravel-octane
spec:
replicas: 3 # Adjust based on load
selector:
matchLabels:
app: laravel-octane
template:
metadata:
labels:
app: laravel-octane
spec:
containers:
- name: app
image: your-docker-registry/laravel-octane-app:latest # Replace with your image
ports:
- containerPort: 8000
env:
- name: APP_ENV
value: "production"
- name: APP_DEBUG
value: "false"
- name: OCTANE_WORKERS
value: "4" # Override Dockerfile CMD if needed
- name: OCTANE_MAX_REQUESTS
value: "1000"
- name: SWOOLE_WORKER_NUM
value: "8" # Example: 2x CPU cores
# Add other environment variables for database, cache, etc.
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
livenessProbe:
httpGet:
path: /octane-health # Custom health check endpoint
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /octane-health # Custom health check endpoint
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
# Consider using an initContainer for database migrations if needed
# initContainers:
# - name: migrate
# image: your-docker-registry/laravel-octane-app:latest
# command: ['php', 'artisan', 'migrate', '--force']
# env:
# - name: DB_HOST
# value: "your-db-service"
# # ... other DB credentials
The livenessProbe and readinessProbe are critical. They should point to a custom health check endpoint in your Laravel application. This endpoint should verify that the Octane server is responsive and healthy.
Implementing a Custom Health Check Endpoint
Create a simple route in your Laravel application (e.g., in routes/web.php or routes/api.php) that returns a 200 OK status if the application is healthy. This endpoint should be lightweight and not trigger heavy operations.
// routes/web.php or routes/api.php
use Illuminate\Support\Facades\Route;
use Illuminate\Http\JsonResponse;
Route::get('/octane-health', function () {
// Basic check: ensure Octane is running and responsive.
// More advanced checks could include database connectivity, cache status, etc.
// For Octane, simply being able to respond is often sufficient.
try {
// A very basic check, could be expanded.
// For instance, checking if a cache key can be set/get.
// Cache::put('health_check', true, 1);
// if (!Cache::has('health_check')) {
// throw new \Exception('Cache not responding');
// }
return new JsonResponse(['status' => 'ok'], 200);
} catch (\Exception $e) {
// Log the error for debugging
\Log::error("Health check failed: " . $e->getMessage());
return new JsonResponse(['status' => 'error', 'message' => $e->getMessage()], 503); // 503 Service Unavailable
}
});
This endpoint, when hit by Kubernetes, will determine if the pod is ready to receive traffic (readinessProbe) and if it’s still running correctly (livenessProbe). If the readiness probe fails, Kubernetes will stop sending traffic to the pod. If the liveness probe fails, Kubernetes will restart the pod.
GitOps Integration with Argo CD
Adopting a GitOps workflow with tools like Argo CD streamlines the deployment and management of your Kubernetes resources. All desired states of your application and infrastructure are declared in Git, and Argo CD synchronizes the cluster state with the Git repository.
Argo CD Application Manifests
Your Kubernetes manifests (Deployment, Service, Ingress, etc.) should reside in a dedicated Git repository. Argo CD will monitor this repository and apply changes to your cluster.
Example kustomization.yaml for Argo CD:
# apps/laravel-octane/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml # If using Ingress
images:
- name: your-docker-registry/laravel-octane-app
newTag: ${TAG} # Argo CD will substitute this with the actual image tag
# You can also use patches to override specific values for different environments
# patchesStrategicMerge:
# - deployment-staging.yaml
And a corresponding Argo CD Application manifest:
# argocd-apps/laravel-octane-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: laravel-octane-app
namespace: argocd # Namespace where Argo CD is installed
spec:
project: default
source:
repoURL: 'https://github.com/your-org/your-k8s-gitops-repo.git' # Your Git repo URL
targetRevision: HEAD # Or a specific branch/tag
path: apps/laravel-octane # Path to your kustomization.yaml
# If using Helm charts:
# chart: laravel-octane
# repoURL: 'https://your-helm-repo.com/'
# targetRevision: '1.0.0'
# helm:
# values: |
# replicaCount: 3
# image:
# repository: your-docker-registry/laravel-octane-app
# tag: latest
destination:
server: 'https://kubernetes.default.svc' # Your cluster's API server
namespace: default # Target namespace for the application
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
When you push a new Docker image tag (e.g., `v1.2.3`) to your container registry, you would update the `newTag` in your Kustomization or Helm values. Argo CD detects this change in Git (if your Kustomization/Helm values are also managed in Git) or can be configured to trigger syncs based on image updates. The `automated` sync policy ensures that Argo CD automatically applies the changes to your Kubernetes cluster.
Managing Environment-Specific Configurations
For different environments (staging, production), you’ll need to manage distinct configurations. Kustomize’s patching or Helm’s value files are excellent for this. For instance, you might increase replica counts, adjust resource limits, or change database connection strings.
Example: Overriding deployment replicas for production using Kustomize patches:
# apps/laravel-octane/deployment-production.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-octane-app
spec:
replicas: 5 # Increased replicas for production
template:
spec:
containers:
- name: app
resources: # Adjust resources for production
requests:
cpu: "1000m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "2Gi"
And in your kustomization.yaml:
# apps/laravel-octane/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
images:
- name: your-docker-registry/laravel-octane-app
newTag: ${TAG}
# Apply production-specific patches if the environment is production
patchesStrategicMerge:
- deployment-production.yaml # Apply this patch if targeting production
Argo CD can be configured to deploy different branches or directories of your GitOps repository to different environments, effectively managing staging and production deployments from a single source of truth.
Advanced Considerations: Scaling and Observability
While Octane and Kubernetes provide a solid foundation, advanced scaling and observability are crucial for production readiness.
Horizontal Pod Autoscaler (HPA)
To automatically scale your Octane pods based on load, configure an HPA. This works by monitoring CPU or memory utilization (or custom metrics) and adjusting the number of replicas in your Deployment.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: laravel-octane-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-octane-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%
Ensure your Deployment has appropriate CPU/memory requests and limits defined for the HPA to function correctly. The `averageUtilization` target is based on the `requests.cpu` defined in the container spec.
Observability: Logging and Metrics
For effective debugging and performance monitoring, integrate robust logging and metrics collection.
- Logging: Configure your application to log to
stdoutandstderr. Kubernetes can then collect these logs using agents like Fluentd, Filebeat, or the built-in containerd/docker logging drivers, forwarding them to a centralized logging system (e.g., Elasticsearch, Loki). - Metrics: Octane itself can expose metrics. For Swoole, you can leverage the
swoole_tableor integrate with Prometheus client libraries. A common approach is to have a dedicated metrics endpoint in your Laravel app that exposes Prometheus-compatible metrics.
Example of a Prometheus metrics endpoint (requires a Prometheus client library for PHP, e.g., promphp/prometheus_client_php):
// routes/web.php or routes/api.php
use Illuminate\Support\Facades\Route;
use Prometheus\Render\RenderTextFormat;
use Prometheus\Storage\InMemory;
use Prometheus\CollectorRegistry;
// Initialize Prometheus client
$registry = new CollectorRegistry(new InMemory());
// Example: A counter for total requests handled by Octane
$counter = $registry->registerCounter(
'php_octane_requests_total',
'Total number of requests handled by Octane',
['method', 'code']
);
// In your Octane application's request handling, increment the counter:
// $counter->inc(['method' => $request->method(), 'code' => $response->getStatusCode()]);
// This would typically be done in a middleware.
Route::get('/metrics', function () use ($registry) {
$renderer = new RenderTextFormat();
header('Content-type: ' . RenderTextFormat::MIME_TYPE);
echo $renderer->render($registry->getMetricFamilySamples());
exit;
});
You would then configure Prometheus to scrape the /metrics endpoint of your Octane pods. This provides invaluable insights into application performance, request rates, error counts, and resource utilization.
By combining Laravel Octane’s performance enhancements with Kubernetes’s orchestration capabilities and a GitOps workflow, you can build highly scalable, resilient, and maintainable PHP applications.