DevOps

Laravel + Kubernetes: деплой масштабируемых PHP-приложений с нуля в 2026 году

Ruslan Ismailov Опубликовано 12 мин чтения
L

Введение: почему Kubernetes стал стандартом деплоя для PHP-приложений в 2026 году

Если в 2020 году Kubernetes воспринимался как инструмент для крупных компаний, то в 2026-м это де-факто стандарт для любого PHP-приложения, которое должно выдерживать нагрузку и легко масштабироваться. Managed-кластеры от GKE, EKS и AKS стали дешевле и проще в настройке. Экосистема вокруг Kubernetes — Helm, Argo CD, Kustomize — достигла зрелости. А главное: Laravel как фреймворк стал значительно лучше адаптирован к cloud-native среде благодаря Octane, нативной поддержке Redis-очередей и горизонтальному масштабированию воркеров.

В этой статье мы пройдём весь путь: от подготовки Laravel-приложения к stateless-архитектуре до написания production-ready Kubernetes-манифестов, настройки HPA и организации миграций. Целевая аудитория — middle и senior PHP-разработчики и DevOps-инженеры, которые уже работают с Docker, но хотят перейти на Kubernetes.

1. Подготовка Laravel-приложения к работе в Kubernetes

Stateless-архитектура

Kubernetes предполагает, что поды могут быть пересозданы в любой момент. Это значит, что Laravel-приложение не должно хранить состояние локально. Проверьте следующие моменты:

  • Сессии — переведите с драйвера file на redis или database.
  • Кеш — используйте Redis или Memcached вместо файлового кеша.
  • Очереди — Redis или Amazon SQS, не синхронный драйвер.
  • Хранение файлов — S3-совместимое хранилище (AWS S3, MinIO), не локальный диск.

Переменные окружения

В Kubernetes конфигурация передаётся через ConfigMap и Secret, а не через файл .env. Убедитесь, что приложение читает все настройки через env() и config(), а не из жёстко прописанных значений. Файл .env не должен попадать в Docker-образ.

Health Checks

Kubernetes требует эндпоинты для проверки состояния контейнера. Добавьте в маршруты простой health check:

// routes/web.php
Route::get('/healthz', function () {
    return response()->json(['status' => 'ok']);
});

Для более глубокой проверки (соединение с БД, Redis) используйте пакет spatie/laravel-health или напишите собственный контроллер, который проверяет все зависимости и возвращает HTTP 200 или 503.

2. Создание Docker-образа для Laravel с многоэтапной сборкой

Многоэтапная сборка (multi-stage build) позволяет получить компактный production-образ без dev-зависимостей и build-инструментов. Ниже — рабочий Dockerfile для Laravel в 2026 году:

# ---- Stage 1: Node build (для компиляции ассетов) ----
FROM node:20-alpine AS node-builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ---- Stage 2: Composer dependencies ----
FROM composer:2.7 AS composer-builder
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-interaction \
    --prefer-dist \
    --optimize-autoloader

# ---- Stage 3: Production image ----
FROM php:8.3-fpm-alpine

# Системные зависимости
RUN apk add --no-cache \
    nginx \
    supervisor \
    libpq-dev \
    libzip-dev \
    oniguruma-dev \
    && docker-php-ext-install \
        pdo_pgsql \
        pdo_mysql \
        zip \
        opcache \
        pcntl

# Копируем приложение
WORKDIR /var/www/html
COPY --from=composer-builder /app/vendor ./vendor
COPY --from=node-builder /app/public/build ./public/build
COPY . .

# Настройки прав
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache

# Оптимизация Laravel
RUN php artisan config:cache \
    && php artisan route:cache \
    && php artisan view:cache

EXPOSE 9000
CMD ["php-fpm"]

Обратите внимание: config:cache на этапе сборки имеет смысл только если переменные окружения не меняются между средами. В большинстве случаев лучше вызывать кеширование в entrypoint-скрипте после инъекции переменных из Kubernetes.

3. Написание Kubernetes-манифестов

ConfigMap

Не-секретные переменные окружения выносим в ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: laravel-config
  namespace: production
data:
  APP_ENV: production
  APP_DEBUG: "false"
  LOG_CHANNEL: stderr
  CACHE_DRIVER: redis
  SESSION_DRIVER: redis
  QUEUE_CONNECTION: redis
  REDIS_HOST: redis-service
  DB_CONNECTION: pgsql
  DB_HOST: postgres-service
  DB_PORT: "5432"
  DB_DATABASE: laravel_db

Secret

Секреты (пароли, ключи) — в Kubernetes Secret (в production используйте External Secrets Operator или Vault):

apiVersion: v1
kind: Secret
metadata:
  name: laravel-secrets
  namespace: production
type: Opaque
stringData:
  APP_KEY: base64:ВАШ_КЛЮЧ
  DB_PASSWORD: ВАШ_ПАРОЛЬ
  REDIS_PASSWORD: ВАШ_REDIS_ПАРОЛЬ

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: laravel-app
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: laravel-app
  template:
    metadata:
      labels:
        app: laravel-app
    spec:
      containers:
        - name: php-fpm
          image: your-registry/laravel-app:1.2.3
          ports:
            - containerPort: 9000
          envFrom:
            - configMapRef:
                name: laravel-config
            - secretRef:
                name: laravel-secrets
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: 1000m
              memory: 512Mi
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10

Service и Ingress

apiVersion: v1
kind: Service
metadata:
  name: laravel-service
  namespace: production
spec:
  selector:
    app: laravel-app
  ports:
    - port: 80
      targetPort: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: laravel-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - yourdomain.com
      secretName: laravel-tls
  rules:
    - host: yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: laravel-service
                port:
                  number: 80

4. Горизонтальное масштабирование (HPA) для PHP-воркеров и очередей

HPA (Horizontal Pod Autoscaler) автоматически изменяет количество реплик в зависимости от нагрузки. Для Laravel-приложения настраивают HPA по CPU, а для Queue Workers — по длине очереди через KEDA (Kubernetes Event-Driven Autoscaling).

HPA по CPU для web-приложения

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

Queue Workers с KEDA

Для масштабирования воркеров Laravel Queue используйте KEDA со ScaledObject, который реагирует на длину очереди в Redis:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: laravel-queue-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: laravel-queue-worker
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
    - type: redis
      metadata:
        address: redis-service:6379
        listName: queues:default
        listLength: "50"

Deployment для Queue Worker должен использовать команду php artisan queue:work с флагом --max-time или --max-jobs, чтобы воркер корректно перезапускался при обновлениях.

5. Управление миграциями базы данных в Kubernetes

Запуск php artisan migrate в Kubernetes — нетривиальная задача. Есть два подхода:

Kubernetes Job

Job запускается один раз и завершается. Подходит для CD-пайплайнов, где миграция выполняется перед обновлением Deployment:

apiVersion: batch/v1
kind: Job
metadata:
  name: laravel-migrate
  namespace: production
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: your-registry/laravel-app:1.2.3
          command: ["php", "artisan", "migrate", "--force"]
          envFrom:
            - configMapRef:
                name: laravel-config
            - secretRef:
                name: laravel-secrets

Init Container

Init Container запускается до основного контейнера пода. Удобно для автоматического применения миграций при деплое:

initContainers:
  - name: migrate
    image: your-registry/laravel-app:1.2.3
    command: ["php", "artisan", "migrate", "--force"]
    envFrom:
      - configMapRef:
          name: laravel-config
      - secretRef:
          name: laravel-secrets

Важно: при использовании Init Container миграция запустится для каждого пода. Чтобы избежать конфликтов при параллельном деплое нескольких реплик, убедитесь, что ваши миграции идемпотентны, или используйте Job + PreSync Hook в Argo CD.

6. Мониторинг Laravel-приложения в кластере

Для полноценного наблюдения за PHP-приложением в Kubernetes используйте следующий стек:

  • Prometheus + Grafana — сбор метрик кластера и приложения. Используйте пакет spatie/laravel-prometheus или экспортируйте метрики через /metrics эндпоинт.
  • Loki — агрегация логов. Настройте Laravel на вывод логов в stderr (LOG_CHANNEL=stderr), и Loki автоматически соберёт их через Promtail.
  • OpenTelemetry — распределённая трассировка запросов. Пакет open-telemetry/opentelemetry-php интегрируется с Laravel и отправляет трейсы в Jaeger или Tempo.
  • Kubernetes Events + Alertmanager — алерты при падении подов, OOMKill, ошибках liveness probe.

Настройте дашборд Grafana с ключевыми метриками: RPS, p95/p99 latency, количество ошибок 5xx, использование памяти PHP-FPM, длина очереди Redis.

7. Типичные ошибки и как их избежать

  • Хранение сессий в файлах — при перезапуске пода пользователи теряют сессии. Решение: Redis-драйвер.
  • APP_KEY не задан или разный в разных репликах — данные, зашифрованные в одном поде, не расшифруются в другом. Решение: единый Secret с APP_KEY.
  • config:cache при сборке образа — кешированный конфиг содержит значения из среды сборки, не из production. Решение: кешировать конфиг в entrypoint или не кешировать вовсе.
  • Отсутствие resource limits — один под может потребить все ресурсы ноды. Всегда задавайте requests и limits.
  • Queue workers без graceful shutdown — при обновлении деплоя воркер убивается на середине задачи. Решение: terminationGracePeriodSeconds: 60 и флаг --stop-when-empty.
  • Нет readiness probe — трафик идёт на под, который ещё не готов. Всегда настраивайте readinessProbe отдельно от livenessProbe.
  • Образ без pinned-тегов — использование тега latest делает деплой непредсказуемым. Всегда тегируйте образы конкретным хешем или версией.

Заключение и итоговые рекомендации

Деплой Laravel в Kubernetes в 2026 году — это не сложно, если подходить системно. Вот краткий чеклист для production-готового деплоя:

  1. Переведите приложение на stateless-архитектуру: Redis для сессий/кеша/очередей, S3 для файлов.
  2. Соберите компактный Docker-образ с multi-stage build на базе PHP 8.3 FPM Alpine.
  3. Разделите конфигурацию на ConfigMap (не-секретное) и Secret (пароли, ключи).
  4. Настройте livenessProbe и readinessProbe для всех контейнеров.
  5. Задайте resource requests и limits для предсказуемого планирования подов.
  6. Используйте HPA для web-реплик и KEDA для queue workers.
  7. Запускайте миграции через Job (в CI/CD) или Init Container с идемпотентными миграциями.
  8. Настройте сбор логов в stderr, метрики через Prometheus и трассировку через OpenTelemetry.
  9. Используйте Argo CD или Flux для GitOps-деплоя — никаких ручных kubectl apply в production.

Kubernetes даёт Laravel-приложению горизонтальное масштабирование, самовосстановление и предсказуемые деплои. Инвестиция в правильную начальную настройку окупается при первом же всплеске нагрузки. Начните с локального кластера через kind или k3d, перенесите один микросервис или Laravel-монолит, и постепенно выстройте полноценный production-пайплайн.

Технологии

Теги

Руслан Исмаилов

Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →