Введение: Зачем деплоить PHP в Kubernetes?
Многие разработчики до сих пор воспринимают PHP как язык для «классического» хостинга с FTP и phpMyAdmin. Однако в современной DevOps-культуре PHP-приложения всё чаще запускаются в контейнерах, а управление ими ложится на плечи Kubernetes (K8s). Это даёт не только автоматическое масштабирование и отказоустойчивость, но и единообразие окружения на всех этапах — от разработки до продакшена.
В этом руководстве мы на практике разберём, как подготовить PHP-проект к деплою в Kubernetes: от создания Docker-образа до написания манифестов и запуска в кластере. Вы узнаете, как сделать контейнер лёгким, как правильно настроить php-fpm и как связать всё с Nginx. Поехали!
Шаг 1: Подготовка PHP-приложения и Docker-образа
Перед тем как загружать код в K8s, нужно упаковать его в Docker-образ. Рассмотрим типовой PHP-проект на чистом PHP (без фреймворка) — простой скрипт, который выводит приветствие и phpinfo.
1.1. Структура проекта
Создадим минимальную структуру:
my-php-app/
├── src/
│ └── index.php
├── docker/
│ ├── php.Dockerfile
│ └── nginx.conf
└── k8s/
├── deployment.yaml
└── service.yaml
1.2. Код PHP-приложения
Файл src/index.php:
<?php
echo "<h1>Привет, Kubernetes!</h1>";
echo "<p>Версия PHP: " . phpversion() . "</p>";
echo "<p>Сервер: " . gethostname() . "</p>";
?>
1.3. Dockerfile для PHP-FPM
Создаём docker/php.Dockerfile:
FROM php:8.2-fpm-alpine
# Устанавливаем зависимости (если нужны)
RUN docker-php-ext-install pdo pdo_mysql
# Копируем код приложения
COPY ./src /var/www/html
# Права доступа (важно для FPM)
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
EXPOSE 9000
CMD ["php-fpm"]
Почему alpine? Он даёт образ размером ~30 МБ вместо 200+ МБ на Debian. Это критично для быстрой загрузки в кластере.
1.4. Конфигурация Nginx
PHP-FPM не умеет отдавать статику напрямую — нужен веб-сервер. Создадим docker/nginx.conf:
server {
listen 80;
server_name _;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
В Kubernetes мы запустим Nginx и PHP-FPM в одном Pod'е (как sidecar-контейнеры) или как отдельные сервисы. Для простоты используем один Pod с двумя контейнерами.
Шаг 2: Сборка и публикация образов
Соберём образ PHP-FPM и образ Nginx с нашей конфигурацией. В реальном проекте можно использовать один образ, объединив Nginx и PHP в supervisord, но мы сделаем «правильно» — разделим.
Сборка PHP-образа:
docker build -t my-php-app:latest -f docker/php.Dockerfile .
Сборка Nginx-образа:
Создадим отдельный Dockerfile для Nginx:
FROM nginx:alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./src /var/www/html
docker build -t my-nginx:latest -f docker/nginx.Dockerfile .
Затем загрузите образы в registry (Docker Hub, GitLab Container Registry и т.д.):
docker tag my-php-app:latest your-registry/my-php-app:latest
docker push your-registry/my-php-app:latest
Шаг 3: Манифесты Kubernetes
Теперь самое интересное — опишем, как Kubernetes будет запускать наше приложение.
3.1. Deployment (основной манифест)
Создаём k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-app-deployment
spec:
replicas: 2
selector:
matchLabels:
app: php-app
template:
metadata:
labels:
app: php-app
spec:
containers:
- name: php-fpm
image: your-registry/my-php-app:latest
ports:
- containerPort: 9000
- name: nginx
image: your-registry/my-nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: shared-files
mountPath: /var/www/html
volumes:
- name: shared-files
emptyDir: {}
Обратите внимание: emptyDir — это временный том, который позволяет контейнерам обмениваться файлами. Мы копируем код в оба образа, но через emptyDir синхронизируем их.
3.2. Service (доступ к приложению)
Создаём k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: php-app-service
spec:
selector:
app: php-app
ports:
- protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
type: LoadBalancer даст внешний IP (если ваш кластер поддерживает это, например, в minikube — minikube service).
Шаг 4: Деплой в кластер
Убедитесь, что у вас есть доступ к кластеру (кубеконфиг). Примените манифесты:
kubectl apply -f k8s/deployment.yaml
kub