Beyond Basic Containers: Orchestrating Microservices with Kubernetes on AWS for High-Performance Laravel Applications
Kubernetes Cluster Setup on AWS EKS
Deploying a high-performance Laravel application on Kubernetes necessitates a robust cluster infrastructure. Amazon Elastic Kubernetes Service (EKS) provides a managed Kubernetes control plane, abstracting away the complexities of managing etcd, API servers, and schedulers. This allows us to focus on application deployment and scaling.
We’ll start by provisioning an EKS cluster using eksctl, the official CLI for EKS. This tool simplifies cluster creation, node group management, and IAM role configuration.
Provisioning the EKS Cluster
Create a cluster configuration file (e.g., cluster.yaml) to define your cluster’s desired state:
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: laravel-perf-cluster
region: us-east-1
version: "1.28"
managedNodeGroups:
- name: worker-nodes
instanceType: m5.xlarge
desiredCapacity: 3
minSize: 1
maxSize: 5
volumeSize: 100
ssh:
allow: true
publicKeyName: my-ec2-keypair
tags:
nodegroup-role: worker
With the configuration in place, deploy the cluster:
eksctl create cluster -f cluster.yaml
This command will provision the EKS control plane and the specified managed node group. It will also configure your local kubectl context to point to the new cluster.
Containerizing the Laravel Application
A well-architected container image is crucial for microservices. For Laravel, this typically involves a multi-stage Dockerfile to keep the final image lean and secure. We’ll include PHP-FPM for application processing and Nginx as a reverse proxy.
Multi-Stage Dockerfile for Laravel
Consider the following Dockerfile:
# Stage 1: Builder
FROM php:8.2-fpm-alpine AS builder
WORKDIR /app
# Install dependencies
RUN apk add --no-cache \
git \
zip \
unzip \
icu-dev \
libzip-dev \
libpng-dev \
freetype-dev \
jpeg-dev \
oniguruma-dev \
postgresql-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install pdo pdo_pgsql zip opcache
COPY --chown=www-data:www-data . .
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Stage 2: Production
FROM php:8.2-fpm-alpine
WORKDIR /app
# Install runtime dependencies
RUN apk add --no-cache \
nginx \
icu \
libzip \
libpng \
freetype \
jpeg \
oniguruma \
postgresql-libs \
&& docker-php-ext-load gd \
&& docker-php-ext-load pdo pdo_pgsql zip opcache
# Copy compiled dependencies from builder stage
COPY --from=builder --chown=www-data:www-data /app/vendor /app/vendor
COPY --chown=www-data:www-data . .
# Copy compiled assets (if any)
# COPY --from=builder --chown=www-data:www-data /app/public/build /app/public/build
# Configure Nginx
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
# Expose port and set entrypoint
EXPOSE 80
CMD ["php-fpm"]
And the corresponding Nginx configuration (docker/nginx.conf):
server {
listen 80;
index index.php index.html;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /app/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm:9000; # Assumes a service named 'php-fpm'
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
Build and push the Docker image to a container registry accessible by EKS, such as Amazon ECR.
# Configure Docker to authenticate with ECR aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com # Build the image docker build -t [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/laravel-app:latest . # Push the image docker push [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/laravel-app:latest
Kubernetes Manifests for Deployment
We’ll define Kubernetes resources using YAML manifests. This includes Deployments for managing application pods, Services for network access, and Ingress for external traffic routing.
Deployment and Service Definitions
Create a deployment.yaml file:
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app-deployment
labels:
app: laravel-app
spec:
replicas: 3
selector:
matchLabels:
app: laravel-app
template:
metadata:
labels:
app: laravel-app
spec:
containers:
- name: laravel-app
image: [YOUR_AWS_ACCOUNT_ID].dkr.ecr.us-east-1.amazonaws.com/laravel-app:latest
ports:
- containerPort: 80
env:
- name: APP_ENV
value: "production"
- name: APP_LOG_LEVEL
value: "warning"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
- name: DB_PORT
valueFrom:
secretKeyRef:
name: db-credentials
key: port
- name: DB_DATABASE
valueFrom:
secretKeyRef:
name: db-credentials
key: database
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
readinessProbe:
httpGet:
path: /healthz # Assuming a /healthz endpoint in Laravel
port: 80
initialDelaySeconds: 15
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 30
periodSeconds: 20
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# Define a sidecar for Nginx if not baked into the app image
# - name: nginx
# image: nginx:alpine
# ports:
# - containerPort: 80
# volumeMounts:
# - name: nginx-config-volume
# mountPath: /etc/nginx/conf.d
# volumes:
# - name: nginx-config-volume
# configMap:
# name: nginx-configmap
---
apiVersion: v1
kind: Service
metadata:
name: laravel-app-service
spec:
selector:
app: laravel-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
And a secrets.yaml for database credentials (ensure this is handled securely, e.g., using AWS Secrets Manager with an external secrets store integration or Kubernetes Secrets with proper RBAC):
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: host: [BASE64_ENCODED_DB_HOST] port: [BASE64_ENCODED_DB_PORT] database: [BASE64_ENCODED_DB_NAME] username: [BASE64_ENCODED_DB_USERNAME] password: [BASE64_ENCODED_DB_PASSWORD]
Apply these manifests to your cluster:
kubectl apply -f secrets.yaml kubectl apply -f deployment.yaml
Ingress Controller and Configuration
To expose your Laravel application to the internet, an Ingress controller is required. AWS Load Balancer Controller is the recommended solution for EKS, integrating with AWS Elastic Load Balancing (ELB).
First, install the AWS Load Balancer Controller. This typically involves creating an IAM OIDC provider for your cluster and then applying the controller’s manifests:
# Enable IAM OIDC provider for your cluster (if not already enabled) eksctl utils associate-iam-oidc-provider --cluster laravel-perf-cluster --approve # Create IAM policy for the controller aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam-policy.json # (Refer to AWS documentation for the exact policy JSON) # Create Kubernetes Service Account and bind it to the IAM role eksctl create iamserviceaccount \ --cluster laravel-perf-cluster \ --namespace kube-system \ --name alb-ingress-controller \ --attach-policy-arn arn:aws:iam::[YOUR_AWS_ACCOUNT_ID]:policy/AWSLoadBalancerControllerIAMPolicy \ --override-existing-serviceaccounts # Install the AWS Load Balancer Controller kubectl apply -k "github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master" kubectl apply -f application.yaml # (Refer to AWS documentation for the exact application.yaml)
Next, define an Ingress resource to route external traffic to your Laravel service:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-app-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
# For HTTPS, you'd add:
# alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
# alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:[YOUR_AWS_ACCOUNT_ID]:certificate/[YOUR_CERT_ID]
spec:
rules:
- host: your-laravel-app.com # Replace with your domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-app-service
port:
number: 80
Apply the Ingress manifest:
kubectl apply -f ingress.yaml
The AWS Load Balancer Controller will provision an Application Load Balancer (ALB) and configure it to route traffic to your laravel-app-service. You can obtain the ALB’s DNS name using kubectl get ingress laravel-app-ingress.
Database and Caching Strategies
For high-performance Laravel applications, robust database and caching solutions are paramount. Leveraging AWS managed services is a common and effective approach.
Amazon RDS for Relational Data
Use Amazon Relational Database Service (RDS) for your primary database. Provision an RDS instance (e.g., PostgreSQL or MySQL) and configure its security group to allow inbound traffic from your EKS worker nodes’ CIDR range or a specific security group associated with your EKS nodes.
Ensure your Laravel application’s database credentials (stored in Kubernetes Secrets or AWS Secrets Manager) are correctly configured. The DB_HOST should point to the RDS endpoint.
Amazon ElastiCache for In-Memory Caching
For session storage and caching, Amazon ElastiCache (Redis or Memcached) is an excellent choice. Configure your Laravel application to use ElastiCache:
// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),
// config/session.php
'driver' => env('SESSION_DRIVER', 'redis'),
// .env file
CACHE_DRIVER=redis
SESSION_DRIVER=redis
REDIS_HOST=[ELASTICACHE_REDIS_ENDPOINT]
REDIS_PORT=6379
REDIS_PASSWORD=[ELASTICACHE_PASSWORD] # If password protection is enabled
Ensure your ElastiCache security group allows inbound traffic from your EKS worker nodes.
Monitoring, Logging, and Scaling
Production readiness demands comprehensive monitoring, centralized logging, and effective auto-scaling strategies.
Centralized Logging with Fluentd/Fluent Bit and CloudWatch Logs
Deploy Fluentd or Fluent Bit as a DaemonSet on your EKS cluster to collect logs from all pods. Configure it to forward logs to Amazon CloudWatch Logs for centralized storage and analysis.
# Example Fluent Bit DaemonSet configuration snippet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: kube-system
spec:
template:
spec:
containers:
- name: fluent-bit
image: fluent/fluent-bit:latest
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
# ... other configurations for CloudWatch output ...
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
Configure Fluent Bit’s output plugin to send logs to CloudWatch Logs, using an IAM role with appropriate permissions.
Metrics and Performance Monitoring
Utilize Prometheus and Grafana for cluster and application metrics. Deploy Prometheus as a cluster addon or via Helm. Integrate with the Kubernetes Metrics Server for Horizontal Pod Autoscaler (HPA) functionality.
For application-level performance monitoring (APM), consider integrating tools like Datadog, New Relic, or AWS X-Ray. Ensure your Laravel application is instrumented to send traces and metrics.
Horizontal Pod Autoscaler (HPA)
Configure HPA to automatically scale the number of application pods based on CPU or memory utilization. This ensures your application can handle varying loads efficiently.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: laravel-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-app-deployment
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
Apply the HPA configuration:
kubectl apply -f hpa.yaml
This setup provides a scalable, resilient, and observable platform for your high-performance Laravel applications on AWS EKS, moving far beyond basic container deployments.