[KUBERNETES] Kubernetes Laravel — Full Production‐ready Setup - fourslickz/notes GitHub Wiki

Kubernetes + Laravel — Full Production-ready Setup

1. Struktur repository (direkomendasikan)

repo-root/
├─ Dockerfile
├─ docker-compose.yml (opsional, dev)
├─ src/ (kode laravel)
│ ├─ app/
│ ├─ bootstrap/
│ └─ ...
├─ k8s/
│ ├─ base/
│ │ ├─ namespace.yaml
│ │ ├─ secrets.env.example
│ │ ├─ configmap.yaml
│ │ └─ storage-class.yaml (opsional)
│ ├─ overlays/production/
│ │ ├─ deployment-app.yaml
│ │ ├─ deployment-worker.yaml
│ │ ├─ cronjob-scheduler.yaml
│ │ ├─ hpa-worker.yaml
│ │ ├─ service-app.yaml
│ │ ├─ ingress.yaml
│ │ └─ pvc.yaml
│ └─ helm-chart/ (alternatif)
│ └─ laravel-chart/
├─ .github/workflows/
│ └─ ci-cd.yaml
└─ README.md

2. Dockerfile (multi-stage production)

# Stage: build assets
FROM node:18-alpine AS node-builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run prod


# Stage: composer install
FROM composer:2 as composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist


# Stage: php
FROM php:8.1-fpm-alpine


# install system deps
RUN apk add --no-cache icu-dev libzip-dev zip libpng libpng-dev oniguruma-dev bash git openrc curl
# php extensions
RUN docker-php-ext-install pdo pdo_mysql mbstring exif pcntl bcmath gd zip


WORKDIR /var/www/html
# copy composer files & vendor
COPY --from=composer /app /var/www/html
# copy source
COPY . /var/www/html


# copy built assets
COPY --from=node-builder /app/public /var/www/html/public


# permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache


EXPOSE 9000
CMD ["php-fpm"]

3. Kubernetes: namespace + secrets

namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
name: laravel

Buat secret dari file .env (disarankan kunci minimal, jangan commit .env):

kubectl create secret generic laravel-env --from-env-file=.env -n laravel

Atau contoh secret manifest (jika perlu):

apiVersion: v1
kind: Secret
metadata:
name: laravel-env
namespace: laravel
type: Opaque
stringData:
APP_KEY: base64:xxx
APP_ENV: production
DB_HOST: mysql.example.local
DB_DATABASE: laravel
DB_USERNAME: laravel
DB_PASSWORD: s3cr3t
REDIS_HOST: redis
AWS_ACCESS_KEY_ID: "..."
AWS_SECRET_ACCESS_KEY: "..."

4. Deployment - Laravel App (Nginx + php-fpm)

Contoh pendekatan: Deployment dua kontainer (nginx + php-fpm) dalam satu pod. deployment-app.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app
namespace: laravel
spec:
replicas: 2
selector:
matchLabels:
app: laravel-app
template:
metadata:
labels:
app: laravel-app
spec:
containers:
- name: php-fpm
image: ghcr.io/yourrepo/laravel-app:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 9000
envFrom:
- secretRef:
name: laravel-env
livenessProbe:
exec:
command: ["/bin/sh", "-c", "pgrep php-fpm || exit 1"]
initialDelaySeconds: 30
periodSeconds: 20
readinessProbe:
httpGet:
path: /_health (buat route kecil yang return 200)
port: 9000
initialDelaySeconds: 5
periodSeconds: 10
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
volumeMounts:
- name: public
mountPath: /var/www/html/public
volumes:
- name: public
emptyDir: {}

service-app.yaml

apiVersion: v1
kind: Service
metadata:
name: laravel-app
namespace: laravel
spec:
type: ClusterIP
selector:
app: laravel-app
ports:
- name: http
port: 80
targetPort: 80

Ingress (contoh)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-ingress
namespace: laravel
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-app
port:
number: 80

5. Deployment - Queue Worker (mengganti Supervisor)

deployment-worker.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-worker
namespace: laravel
spec:
replicas: 2
selector:
matchLabels:
app: laravel-worker
template:
metadata:
labels:
app: laravel-worker
spec:
restartPolicy: Always
containers:
- name: worker
image: ghcr.io/yourrepo/laravel-app:latest
command: ["php"]
args: ["artisan", "queue:work", "redis", "--sleep=3", "--tries=3", "--timeout=90"]
envFrom:
- secretRef:
name: laravel-env
resources:
requests:
cpu: "100m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

Horizontal Pod Autoscaler (opsional, berdasarkan CPU atau metric kustom):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: laravel-worker-hpa
namespace: laravel
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-worker
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60

6. CronJob - Laravel Scheduler

cronjob-scheduler.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
name: laravel-scheduler
namespace: laravel
spec:
schedule: "* * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: scheduler
image: ghcr.io/yourrepo/laravel-app:latest
command: ["php", "artisan", "schedule:run"]
envFrom:
- secretRef:
name: laravel-env

Gunakan concurrencyPolicy: Forbid supaya job yang lama tidak bertabrakan.

7. Persistent Volume & Uploads

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: laravel-uploads-pvc
namespace: laravel
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 20Gi
storageClassName: rook-cephfs

8. Helm Chart Skeleton (optional)

laravel-chart/
├─ Chart.yaml
├─ values.yaml
├─ templates/
├─ deployment-app.yaml
├─ deployment-worker.yaml
├─ cronjob-scheduler.yaml
├─ service.yaml
├─ ingress.yaml
└─ pvc.yaml

values.yaml contoh (ringkasan):

replicaCount: 2
image:
repository: ghcr.io/yourrepo/laravel-app
tag: latest
service:
type: ClusterIP
port: 80
ingress:
enabled: true
hosts:
- host: example.com
paths: ["/"]
worker:
replicaCount: 2
autoscale:
enabled: true
minReplicas: 2
maxReplicas: 20
targetCPUUtilizationPercentage: 60

9. GitHub Actions (CI/CD) — build image & deploy to k8s

.github/workflows/ci-cd.yaml

name: CI CD
on:
push:
branches: [ main ]


jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Build assets
run: |
npm ci
npm run build
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- name: Composer install
run: composer install --no-dev --no-progress --prefer-dist
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Build and push
run: |
IMAGE=ghcr.io/${{ github.repository_owner }}/laravel-app:${{ github.sha }}
docker build -t $IMAGE .
docker push $IMAGE
- name: Deploy to k8s
uses: appleboy/[email protected]
with:
kubeconfig: ${{ secrets.KUBECONFIG }}
manifests: k8s/overlays/production