Orchestrating Microservices with Kubernetes: A Deep Dive into PHP 9 and Laravel’s Micro-Frontends on AWS EKS
Kubernetes Cluster Setup on AWS EKS
Our foundation for orchestrating microservices will be Amazon Elastic Kubernetes Service (EKS). This managed Kubernetes service abstracts away the complexities of managing the control plane, allowing us to focus on deploying and scaling our applications. We’ll assume a pre-existing AWS account with appropriate IAM roles and VPC configurations.
The initial setup involves creating an EKS cluster. We’ll use the AWS CLI for this, ensuring we have the latest version installed and configured with our credentials.
Creating the EKS Cluster
This command initiates the cluster creation process. The `–name` flag defines the cluster’s identifier, `–role-arn` specifies the IAM role EKS will assume for managing AWS resources, and `–resources-vpc-config` dictates the subnets and security groups within our VPC that the cluster will utilize. The `–kubernetes-version` flag is crucial for compatibility with our chosen application stacks.
aws eks create-cluster \
--name my-php-microservices-cluster \
--role-arn arn:aws:iam::123456789012:role/EKSClusterRole \
--resources-vpc-config subnetIds=subnet-0123456789abcdef0,subnet-0fedcba9876543210,securityGroupIds=sg-0abcdef1234567890 \
--kubernetes-version 1.28
Configuring `kubectl` Access
Once the cluster is active, we need to configure `kubectl` to communicate with it. The `aws eks update-kubeconfig` command fetches the necessary cluster endpoint and certificate authority data, merging it into your local `~/.kube/config` file.
aws eks update-kubeconfig --region us-east-1 --name my-php-microservices-cluster
Verify the connection by listing the nodes in your cluster. Initially, this will be empty until we provision worker nodes.
kubectl get nodes
Provisioning Worker Nodes with Managed Node Groups
Managed Node Groups simplify the process of adding and managing worker nodes. They automatically provision and configure EC2 instances, handle Kubernetes version upgrades, and integrate with Auto Scaling Groups. We’ll create a node group that aligns with our cluster’s VPC configuration.
aws eks create-nodegroup \
--cluster-name my-php-microservices-cluster \
--nodegroup-name standard-workers \
--subnets subnet-0123456789abcdef0 subnet-0fedcba9876543210 \
--instance-types t3.medium \
--min-size 2 \
--max-size 5 \
--desired-size 3 \
--disk-size 100 \
--node-role arn:aws:iam::123456789012:role/EKSWorkerNodeRole \
--scaling-config minSize=2,maxSize=5,desiredSize=3
After the node group is active, running `kubectl get nodes` should now list the provisioned EC2 instances, ready to host our microservices.
PHP 9 and Laravel Microservices Deployment
We’ll deploy two distinct microservices: a core `UserService` and an `OrderService`. Both will be built using PHP 9 (hypothetical, assuming future release) and the Laravel framework. Each service will have its own Dockerfile, Kubernetes Deployment, and Service definitions.
Dockerfile for Microservices
A common Dockerfile can be used for both services, with minor adjustments for application-specific dependencies. We’ll leverage an official PHP 9 FPM image, install necessary extensions, and copy our application code.
# Use an official PHP 9 FPM image
FROM php:8.3-fpm
# Set working directory
WORKDIR /var/www/html
# 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 \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd pdo pdo_mysql zip bcmath opcache
# 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
# Expose port 9000 for PHP-FPM
EXPOSE 9000
# Start PHP-FPM
CMD ["php-fpm"]
Kubernetes Deployment Manifests
For each microservice, we’ll define a Kubernetes Deployment to manage stateless application pods and a Service to provide a stable network endpoint. We’ll use `app.kubernetes.io/name` and `app.kubernetes.io/instance` labels for clear identification.
UserService Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-deployment
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
tier: backend
template:
metadata:
labels:
app: user-service
tier: backend
spec:
containers:
- name: user-service
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/user-service:latest
ports:
- containerPort: 9000
env:
- name: DB_HOST
value: "mysql-service" # Assuming a separate MySQL service
- name: DB_DATABASE
value: "user_db"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
UserService Service
apiVersion: v1
kind: Service
metadata:
name: user-service
labels:
app: user-service
spec:
selector:
app: user-service
tier: backend
ports:
- protocol: TCP
port: 80
targetPort: 9000
type: ClusterIP
OrderService Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service-deployment
labels:
app: order-service
spec:
replicas: 3
selector:
matchLabels:
app: order-service
tier: backend
template:
metadata:
labels:
app: order-service
tier: backend
spec:
containers:
- name: order-service
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/order-service:latest
ports:
- containerPort: 9000
env:
- name: DB_HOST
value: "mysql-service"
- name: DB_DATABASE
value: "order_db"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
OrderService Service
apiVersion: v1
kind: Service
metadata:
name: order-service
labels:
app: order-service
spec:
selector:
app: order-service
tier: backend
ports:
- protocol: TCP
port: 80
targetPort: 9000
type: ClusterIP
Applying Kubernetes Manifests
Save these YAML definitions into separate files (e.g., `user-service.yaml`, `order-service.yaml`) and apply them to your EKS cluster using `kubectl apply`.
kubectl apply -f user-service.yaml kubectl apply -f order-service.yaml
Verify the deployments and services:
kubectl get deployments kubectl get pods kubectl get services
Laravel Micro-Frontends on EKS
For the micro-frontend architecture, we’ll use a single Nginx ingress controller to route traffic to different frontend applications. Each frontend application will be a separate Laravel project, potentially served from its own Docker image.
Nginx Ingress Controller Deployment
A robust ingress controller is essential for managing external access to our services. We’ll deploy the Nginx ingress controller using Helm, a package manager for Kubernetes.
# Add the ingress-nginx repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# Install the ingress-nginx controller
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2 \
--set controller.nodeSelector."kubernetes\.io/os"=linux \
--set defaultBackend.nodeSelector."kubernetes\.io/os"=linux
After installation, obtain the external IP address of the ingress controller. This IP will be used to configure DNS records for our micro-frontends.
kubectl get service -n ingress-nginx ingress-nginx-controller
Frontend Microservice Deployments
Each frontend microservice will be a separate Laravel application. For simplicity in this example, we’ll assume they are built into Docker images and deployed as standard Kubernetes Deployments. In a real-world scenario, you might use a more sophisticated frontend build and deployment pipeline.
Auth Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-frontend-deployment
labels:
app: auth-frontend
spec:
replicas: 2
selector:
matchLabels:
app: auth-frontend
tier: frontend
template:
metadata:
labels:
app: auth-frontend
tier: frontend
spec:
containers:
- name: auth-frontend
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/auth-frontend:latest
ports:
- containerPort: 80
Auth Frontend Service
apiVersion: v1
kind: Service
metadata:
name: auth-frontend-service
labels:
app: auth-frontend
spec:
selector:
app: auth-frontend
tier: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
Product Frontend Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-frontend-deployment
labels:
app: product-frontend
spec:
replicas: 2
selector:
matchLabels:
app: product-frontend
tier: frontend
template:
metadata:
labels:
app: product-frontend
tier: frontend
spec:
containers:
- name: product-frontend
image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/product-frontend:latest
ports:
- containerPort: 80
Product Frontend Service
apiVersion: v1
kind: Service
metadata:
name: product-frontend-service
labels:
app: product-frontend
spec:
selector:
app: product-frontend
tier: frontend
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
Ingress Resource Configuration
The Ingress resource defines the routing rules. We’ll map subdomains (or paths) to our frontend services. This example uses subdomains for clarity.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: microfrontend-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: auth.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: auth-frontend-service
port:
number: 80
- host: products.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: product-frontend-service
port:
number: 80
Apply this Ingress resource:
kubectl apply -f microfrontend-ingress.yaml
Ensure your DNS records for `auth.yourdomain.com` and `products.yourdomain.com` point to the external IP address of your Nginx ingress controller.
Inter-Service Communication and Data Management
With microservices deployed, inter-service communication and data consistency become critical. We’ll use Kubernetes Services for direct communication between backend microservices and a shared database solution.
Service Discovery and Communication
Kubernetes’ built-in DNS provides service discovery. Microservices can reach each other using their service names (e.g., `http://user-service`, `http://order-service`). For PHP applications, this translates to configuring database connection strings and API client endpoints with these service names.
Database Strategy: RDS and Shared Credentials
For production, we’ll leverage AWS RDS for managed database instances. Each microservice will have its own database schema within a single RDS instance or separate RDS instances, depending on isolation requirements. We’ll use Kubernetes Secrets to securely manage database credentials.
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: username:password:
The `valueFrom.secretKeyRef` in the Deployment manifests (shown previously) allows pods to securely access these credentials as environment variables.
API Gateway Pattern (Optional but Recommended)
While not strictly part of this initial setup, for a more robust architecture, consider implementing an API Gateway. This could be another microservice (e.g., built with Lumen or a dedicated gateway solution) responsible for request routing, authentication, rate limiting, and aggregating responses from multiple backend services before exposing them to the frontends. This decouples the frontend from the direct knowledge of backend microservices.
Monitoring, Logging, and CI/CD
A production-ready system requires robust monitoring, centralized logging, and an automated CI/CD pipeline.
Monitoring with Prometheus and Grafana
Deploy Prometheus for metrics collection and Grafana for visualization. These can be installed via Helm charts.
# Install Prometheus and Grafana via Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-grafana
helm repo update
helm install prometheus prometheus-community/prometheus \
--namespace monitoring \
--create-namespace
helm install grafana grafana/grafana \
--namespace monitoring \
--create-namespace
Configure Prometheus to scrape metrics from your application pods (requires instrumenting your PHP applications to expose metrics in Prometheus format) and the Nginx ingress controller.
Centralized Logging with EFK/Loki
Implement a centralized logging solution. The EFK stack (Elasticsearch, Fluentd, Kibana) or Loki with Promtail and Grafana are common choices. Fluentd or Promtail agents running as DaemonSets on each node will collect logs from pods and forward them to a central store.
CI/CD Pipeline with AWS CodePipeline/Jenkins
Automate your build, test, and deployment process. Integrate with AWS CodeCommit/GitHub, CodeBuild for building Docker images and pushing them to ECR, and CodePipeline to orchestrate the deployment to EKS using `kubectl apply` or Helm.
This comprehensive approach leverages Kubernetes for orchestration, AWS EKS for managed infrastructure, and a microservice architecture with micro-frontends to build scalable and maintainable PHP applications.