Beyond the Basics: Mastering Kubernetes Orchestration for Laravel Microservices on AWS EKS
Establishing a Robust EKS Foundation for Laravel Microservices
Deploying Laravel microservices on Amazon Elastic Kubernetes Service (EKS) demands more than just basic containerization. It requires a strategic approach to networking, security, and observability. This guide dives into advanced configurations and best practices for production-ready EKS deployments.
Containerizing Laravel Applications
The foundation of any Kubernetes deployment is the container image. For Laravel, this involves a multi-stage Dockerfile to optimize image size and security. We’ll include essential build tools and runtime dependencies only where necessary.
Consider a typical Laravel microservice, perhaps handling user authentication. The Dockerfile might look like this:
# Stage 1: Build
FROM composer:latest AS builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-interaction
COPY . .
RUN php artisan optimize:clear
RUN php artisan config:cache
RUN php artisan route:cache
RUN php artisan view:cache
# Stage 2: Production
FROM php:8.2-fpm-alpine
# Install necessary extensions
RUN apk add --no-cache \
git \
icu-dev \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install -j$(nproc) zip \
&& docker-php-ext-install -j$(nproc) intl
# Copy application files
WORKDIR /app
COPY --from=builder /app/vendor /app/vendor
COPY --from=builder /app/public /app/public
COPY --from=builder /app/app /app/app
COPY --from=builder /app/bootstrap /app/bootstrap
COPY --from=builder /app/config /app/config
COPY --from=builder /app/routes /app/routes
COPY --from=builder /app/.env.example /app/.env.example
COPY --from=builder /app/artisan /app/artisan
# Set permissions for storage and bootstrap/cache
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
# Expose port and set entrypoint
EXPOSE 9000
CMD ["php-fpm"]
This Dockerfile leverages Alpine Linux for a smaller footprint and `composer` as a build stage. It caches dependencies and application configurations to speed up builds and runtime. The final image contains only the necessary FPM runtime and application code.
Kubernetes Manifests: Deployments and Services
For each Laravel microservice, we’ll define a Kubernetes Deployment and a Service. The Deployment manages the Pods, ensuring the desired number of replicas are running and handling rolling updates. The Service provides a stable network endpoint for accessing the microservice.
Here’s a sample Deployment manifest for our authentication service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service-deployment
labels:
app: auth-service
spec:
replicas: 3
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- name: auth-service
image: YOUR_ECR_REPO/auth-service:latest
ports:
- containerPort: 9000
env:
- name: APP_ENV
value: "production"
- name: APP_URL
value: "http://auth.example.com" # Or internal service DNS
- name: DB_HOST
valueFrom:
secretKeyRef:
name: database-credentials
key: host
- name: DB_PORT
valueFrom:
secretKeyRef:
name: database-credentials
key: port
- name: DB_DATABASE
valueFrom:
secretKeyRef:
name: database-credentials
key: dbname
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: database-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: database-credentials
key: password
readinessProbe:
httpGet:
path: /healthz # Assuming a /healthz endpoint in Laravel
port: 9000
initialDelaySeconds: 15
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 9000
initialDelaySeconds: 30
periodSeconds: 20
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
imagePullSecrets:
- name: aws-ecr-credential
Key considerations here include:
- Image Registry: Replace
YOUR_ECR_REPO/auth-service:latestwith your Amazon Elastic Container Registry (ECR) path. - Environment Variables: Sensitive information like database credentials should be managed via Kubernetes Secrets. Non-sensitive variables can be directly defined or sourced from ConfigMaps.
- Probes:
readinessProbeandlivenessProbeare crucial for Kubernetes to manage Pod health. Ensure your Laravel application exposes a health check endpoint (e.g.,/healthz). - Resource Requests/Limits: Define these to ensure predictable performance and prevent resource starvation.
- Image Pull Secrets: For private ECR repositories, you’ll need an
imagePullSecretconfigured to authenticate with AWS.
The corresponding Service manifest:
apiVersion: v1
kind: Service
metadata:
name: auth-service
spec:
selector:
app: auth-service
ports:
- protocol: TCP
port: 80
targetPort: 9000 # The port your PHP-FPM container listens on
type: ClusterIP
This creates an internal service accessible only within the EKS cluster. For external access, an Ingress controller will be used.
Ingress Controller and AWS Load Balancer Integration
To expose your microservices to the internet, an Ingress controller is essential. AWS EKS integrates seamlessly with the AWS Load Balancer Controller, which provisions and manages AWS Application Load Balancers (ALBs) or Network Load Balancers (NLBs) based on Kubernetes Ingress resources.
First, ensure the AWS Load Balancer Controller is installed in your EKS cluster. This typically involves deploying it via Helm or directly applying its manifests, configuring it with an IAM role that has permissions to manage ELB resources.
# Example Helm installation (ensure you have Helm v3+ installed) helm upgrade --install aws-load-balancer-controller oci://public.ecr.aws/eks/aws-load-balancer-controller --namespace kube-system --version <controller-version> \ --set clusterName=<your-cluster-name> \ --set serviceAccount.create=false \ --set serviceAccount.name=aws-load-balancer-controller \ --set region=<your-aws-region> \ --set vpcId=<your-vpc-id>
Once the controller is running, you can define an Ingress resource to route traffic to your services. For multiple Laravel microservices, you’ll typically use host-based routing.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: microservices-ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip # Or 'instance' depending on your setup
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:123456789012:certificate/your-certificate-id # For HTTPS
alb.ingress.kubernetes.io/ssl-redirect: '443' # Redirect HTTP to HTTPS
spec:
rules:
- host: auth.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: auth-service
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
# Add more rules for other microservices
This Ingress resource will instruct the AWS Load Balancer Controller to provision an ALB. The ALB will have listeners for HTTP (port 80) and HTTPS (port 443, using the specified ACM certificate). It will then forward traffic to the respective Kubernetes Services based on the `host` header.
Database Management for Microservices
Each Laravel microservice might require its own database. For production, managed database services like Amazon RDS or Aurora are highly recommended. You’ll need to configure your EKS cluster to securely connect to these databases.
Security Group Configuration: Ensure the security group associated with your EKS worker nodes (or the VPC CNI’s security group) allows outbound traffic to your RDS instance’s port (e.g., 3306 for MySQL). Conversely, the RDS instance’s security group must allow inbound traffic from your EKS worker nodes’ security group.
Secrets Management: As shown in the Deployment manifest, use Kubernetes Secrets to store database credentials. These secrets can be created manually or, for enhanced security, integrated with AWS Secrets Manager or HashiCorp Vault.
# Example of creating a Kubernetes Secret from existing AWS Secrets Manager secret # Requires AWS CLI and kubectl configured aws secretsmanager get-secret-value --secret-id arn:aws:secretsmanager:us-east-1:123456789012:secret:my-db-credentials-AbCdEf \ --query SecretString --output text | \ jq -r '. | "apiVersion: v1\nkind: Secret\nmetadata:\n name: database-credentials\ntype: Opaque\ndata:\n host: \"" + .host + "\" \n port: \"" + (.port | tostring) + "\" \n dbname: \"" + .dbname + "\" \n username: \"" + .username + "\" \n password: \"" + .password + "\""' | \ kubectl apply -f -
This script fetches credentials from AWS Secrets Manager and creates a Kubernetes Secret. The `jq` command is used to parse the JSON output and format it into a Kubernetes Secret YAML. Ensure your EKS nodes have the necessary IAM permissions to access Secrets Manager.
Observability: Logging, Metrics, and Tracing
Effective observability is paramount for managing microservices. A robust solution involves collecting logs, metrics, and traces from your Laravel applications and Kubernetes infrastructure.
Logging:
- Fluentd/Fluent Bit DaemonSet: Deploy Fluentd or Fluent Bit as a DaemonSet on your EKS cluster. These agents run on each node, collect container logs (stdout/stderr), and forward them to a centralized logging backend like Amazon CloudWatch Logs, Elasticsearch, or Loki.
- Laravel Logging Configuration: Configure Laravel’s Monolog to output logs in a structured format (e.g., JSON) that is easily parsable by your log aggregation system.
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'json'], // 'json' is a custom channel
'ignore_exceptions' => false,
],
'json' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\StreamHandler::class,
'formatter' => Monolog\Formatter\JsonFormatter::class,
'with_context' => true,
'stream' => 'php://stdout', // Output to stdout for container log collection
],
// ... other channels
],
Metrics:
- Prometheus & Grafana: Deploy Prometheus for metrics collection and Grafana for visualization. Prometheus can scrape metrics from your applications (if instrumented) and Kubernetes components.
- Laravel Application Metrics: Instrument your Laravel application using libraries like `prometheus-client-php` to expose custom metrics (e.g., request counts, response times, error rates) via an HTTP endpoint (e.g.,
/metrics).
// Example route for metrics
use Prometheus\Render\RenderTextFormat;
use Prometheus\Storage\InMemory;
Route::get('/metrics', function () {
$registry = new \Prometheus\Registry(new InMemory());
// Register your custom metrics here
// $counter = $registry->registerCounter('http_requests_total', 'Total HTTP Requests', ['method', 'path']);
// $counter->incBy(1, ['GET', '/users']);
$renderer = new RenderTextFormat();
return response($renderer->render($registry->getMetricFamilySamples()), 200, ['Content-Type' => $renderer->getMimeType()]);
});
Tracing:
- Jaeger/OpenTelemetry: Integrate distributed tracing using OpenTelemetry. Instrument your Laravel applications to send trace data to a backend like Jaeger or AWS X-Ray. This is crucial for understanding request flows across multiple microservices.
CI/CD Pipeline for EKS Deployments
A robust CI/CD pipeline automates the build, test, and deployment process. For EKS, this typically involves:
- Source Code Management: GitHub, GitLab, AWS CodeCommit.
- CI Server: Jenkins, GitLab CI, GitHub Actions, AWS CodeBuild.
- Container Registry: Amazon ECR.
- Kubernetes Deployment Tool: kubectl, Helm, Argo CD, Flux CD.
A typical workflow:
- Developer pushes code to the repository.
- CI server triggers a build.
- Build process includes running unit and integration tests.
- If tests pass, a Docker image is built and pushed to ECR.
- The Kubernetes manifests (Deployments, Services, Ingress) are updated with the new image tag.
- A deployment tool (e.g., Helm or kubectl apply) applies the updated manifests to the EKS cluster.
- For GitOps workflows (Argo CD, Flux CD), changes to the Kubernetes manifests in a Git repository automatically trigger deployments.
Example GitHub Actions Workflow Snippet:
name: Deploy Laravel Microservice
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push Docker image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: auth-service
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- name: Update Kubernetes manifests
run: |
# Example: Using sed to update image tag in deployment.yaml
sed -i 's|image: YOUR_ECR_REPO/auth-service:latest|image: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}|g' kubernetes/deployment.yaml
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBECONFIG }} # Store your kubeconfig as a GitHub secret
- name: Deploy to EKS
run: kubectl apply -f kubernetes/deployment.yaml -f kubernetes/service.yaml -f kubernetes/ingress.yaml
Advanced Considerations: Autoscaling and Security Hardening
Horizontal Pod Autoscaler (HPA): Configure HPA to automatically scale the number of Pods for your microservices based on CPU or memory utilization. This ensures your application can handle varying loads efficiently.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: auth-service-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: auth-service-deployment
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Cluster Autoscaler: For scaling the underlying EKS worker nodes, the Cluster Autoscaler is essential. It adjusts the number of nodes in your node groups based on pending Pods that cannot be scheduled due to resource constraints.
Network Policies: Implement Kubernetes Network Policies to restrict network traffic between Pods. This is a critical security measure, enforcing the principle of least privilege. For example, you can ensure that only the API Gateway service can communicate with the auth service.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-service-allow-ingress
namespace: default
spec:
podSelector:
matchLabels:
app: auth-service
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-gateway # Assuming an API Gateway microservice
ports:
- protocol: TCP
port: 9000 # The port your PHP-FPM container listens on
Pod Security Standards (PSS): Leverage Kubernetes Pod Security Standards to enforce security best practices at the Pod level, such as disallowing privileged containers or restricting host filesystem access.
By implementing these advanced configurations, you can build a resilient, scalable, and secure platform for your Laravel microservices on AWS EKS, moving beyond basic deployments to a production-grade orchestration solution.