nginx php fpm kubernetes
PHP-FPM, Nginx, Kubernetes, and Docker
This is a guide to running Nginx and PHP-FPM on Kubernetes. You’ll get an overview of each component in the environment, plus complete source code for running an application using PHP-FPM and Nginx on Kubernetes.
Overview
PHP-FPM
PHP is a scripting language used for web development. CGI scripts are a way to run a script on the server when that server receives a HTTP request. Fast-CGI is an improvement on CGI which is—yep—faster.
PHP-FPM is an implementation of Fast-CGI for PHP with improved capabilities around process management, logging, and high traffic situations.
Nginx
Kubernetes and Docker
Kubernetes and Docker run our Nginx and PHP-FPM processes in a Kubernetes cluster. We’ll create a Docker image that includes our application code, and configure a pod to run containers from that image in Kubernetes.
Together
Solution
PHP-FPM and Nginx need to have access to the same files on the filesystem. In Kubernetes, this means they need to be running on the same pod, and we’ll use a volume to share files between the two containers.
Step 1: the PHP app
Step 2: the Dockerfile
Now create a Dockerfile based off the FPM variant of PHP, which is php:7.2-fpm for us. Copy your PHP source code into a directory in that Docker image.
Step 3: the ConfigMap
Let’s get into Kubernetes-land.
We’re going to have two containers running in the pod. One runs PHP-FPM to handle dynamic PHP processing, and the other runs nginx to act as a web server. Both containers read from a shared volume.
We need to set up our configuration settings for the nginx container we’ll create.
Here, we tell nginx to send any request for a *.php file to our PHP-FPM application via localhost:9000.
Step 4: the Pod
Finally, we can create the Kubernetes pod that runs our application container and the nginx web server sidecar.
Be sure to read through the comments in the gist.
Learn Kube Faster.
Get the #1 guide.
Get my book on Kubernetes for software developers, used by engineers at Google, Microsoft, and IBM.
Meet the Author
Matthew Palmer is a software developer and author. He’s created popular desktop apps, scaled SaaS web services, and taught Computer Science students at the University of New South Wales.
Nginx php fpm kubernetes
Run Nginx + PHP (FPM) on your Kubernetes cluster!
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
Run Nginx + PHP (FPM) on your Kubernetes cluster!
This is an example project targeting anyone who is dying to find a pragmatic example and/or resources for getting started with Kubernetes.
This example works best (only?) for a Kubernetes setup on your local machine with Vagrant. Please follow the instructions on the Kubernetes website and return here after everything is set up.
For convenience, I suggest to also install the Kubernetes CLI aka kubectl on your machine. On OS X (with Homebrew), it’s as easy as
Get up and running within seconds! Just run kube-up.sh inside the kubernetes folder:
Here’s what it does in a nutshell: It creates two pods, each running a Docker container based on the b00gizm/php-nginx image (see Dockerfile inside this repo for details). One pod is responsible for running the PHP FPM process and the other one for running Nginx.
It also creates two services, one for PHP FPM and one for Nginx, for load balancing traffic between the different pods. Without any changes, each service is only responsible for one pod, but you can scale this just as you like by changing the value for the replication fields inside kubernetes/php-nginx-app.yml
(I will elaborate on the nitty gritty details some time later, so please refer to the official Kubernetes docs, if you want details right know.)
Check if your pods are ready (running):
And then hit the URL dumped by kube-up.sh above in your browser (please note that your actual IP address and port might and will vary!) and you’ll be greeted by a «Hello World» page.
Running «locally» on Docker
If you’re like me, you’ll also like to test this app on your local Docker host:
The app will be available in your browser via http:// :8080
When you’re done, just execute docker/stop-local.sh to stop it.
Copyright (c) 2015 Pascal Cremer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the «Software»), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
About
Run Nginx + PHP (FPM) on your Kubernetes cluster!
Kubernetes: NGINX/PHP-FPM graceful shutdown — избавляемся от 502 ошибок
Имеется PHP-приложение, работает в Kubernetes в подах с двумя контейнерами — NGINX и PHP-FPM.
Проблема: во время скейлинга приложения начинают проскакивать 502 ошибки. Т.е. при остановке подов — некорректно отрабатывает завершение подключений.
Рассмотрим процесс остановки подов вообще, и особенности NGINX и PHP-FPM в частности.
Тестировать будем приложение в AWS Elastic Kubernetes Service с помощью Yandex.Tank.
Ingress создаёт AWS Application Load Balancer с помощью AWS ALB Ingress Controller.
Для управления контейнерами на Kubernetes WorkerNodes испольузется Docker.
Pod Lifecycle — Termination of Pods
Посмотрим, как вообще происходит процесс остановки и удаления подов.
Итак, под — это процесс(ы), запущенные на WorkerNode, для остановки которых используются стандартные сигналы IPC (Inter Process Communication).
При этом, можно переопределить сигнал для мягкой остановки используя STOPSIGNAL в образе, из которого запускался контейнер.
Итак, процесс удаления пода выглядит следующим образом:
Собственно, тут возникает две проблемы:
Т.е. в первом случае, если у нас есть подключение к NGINX с keep-alive — то NGINX при выполнении fast shutdown просто обрубит его, а клиент получит 502 ошибку, см. Avoiding dropped connections in nginx containers with «STOPSIGNAL SIGQUIT».
NGINX STOPSIGNAL и 502
Попробуем воспроизвести проблему с NGINX.
Возмём пример из поста по ссылке выше, и задеплоим его в Кубер.
Тут NGINX выполняет proxy_pass на http://httpbin.org, который отвечает с задержкой в 10 секунд — эмулируем работу PHP-бекенда.
Собираем, пушим в репозиторий:
Пишем Deployment с 10 подами из собранного образа.
Тут приведу полный файл, с Namespace, Service и Ingress, далее только те части, которые будут меняться:
Kubernetes tips & tricks: особенности выполнения graceful shutdown в NGINX и PHP-FPM
Категории
Свежие записи
Наши услуги
Типовое условие при реализации CI/CD в Kubernetes: приложение должно уметь перед полной остановкой не принимать новые клиентские запросы, а самое главное — успешно завершать уже существующие.
Соблюдение такого условия позволяет достичь нулевого простоя во время деплоя. Однако, даже при использовании очень популярных связок (вроде NGINX и PHP-FPM) можно столкнуться со сложностями, которые приведут к всплеску ошибок при каждом деплое…
Теория. Как живёт pod
Также следует помнить, что grace period по умолчанию равен 30 секундам : после этого pod будет терминирован и приложение должно успеть обработать все запросы до этого периода. Примечание: хотя любой запрос, который выполняется более 5-10 секунд, уже является проблемным, и graceful shutdown ему уже не поможет…
Чтобы лучше понять, что происходит, когда pod завершает свою работу, достаточно изучить следующую схему:
А1, B1 — Получение изменений о состоянии пода
A2 — Отправление SIGTERM
B2 — Удаление pod’а из endpoints
B3 — Получение изменений (изменился список endpoints)
B4 — Обновление правил iptables
Обратите внимание: удаление endpoint pod’а и посылка SIGTERM происходит не последовательно, а параллельно. А из-за того, что Ingress получает обновленный список Endpoints не сразу, в pod будут отправляться новые запросы от клиентов, что вызовет 500 ошибки во время терминации pod’а (более подробный материал по этому вопросу мы переводили ). Решать эту проблему нужно следующими способами:
Теория. Как NGINX и PHP-FPM завершают свои процессы
NGINX
Дальше всё просто: требуется добавить в preStop-хук команду, которая будет посылать сигнал о graceful shutdown. Это можно сделать в Deployment, в блоке контейнера:
Теперь в момент завершения работы pod’а в логах контейнера NGINX мы увидим следующее:
А на данном этапе с NGINX закончили: как минимум по логам можно понять, что всё работает так, как надо.
Как обстоят дела с PHP-FPM? Как он обрабатывает graceful shutdown? Давайте разбираться.
PHP-FPM
В случае с PHP-FPM информации немного меньше. Если ориентироваться на официальный мануал по PHP-FPM, то в нём будет рассказано, что принимаются следующие POSIX-сигналы:
Остальные сигналы в данной задаче не требуются, поэтому их разбор опустим. Для корректного завершения процесса понадобится написать следующий preStop-хук:
На первый взгляд, это всё, что требуется для выполнения graceful shutdown в обоих контейнерах. Тем не менее, задача сложнее, чем кажется. Далее разобраны два случая, в которых graceful shutdown не работал и вызывал кратковременную недоступность проекта во время деплоя.
Практика. Возможные проблемы с graceful shutdown
NGINX
Мы можем наблюдать такую проблему, например, по ответам на нужном нам Ingress’e:
Показатели статус-кодов в момент деплоя
В данном случае мы получаем как раз 503 код ошибки от самого Ingress: он не может обратиться к контейнеру NGINX, так как тот уже недоступен. Если посмотреть в логи контейнера с NGINX, в них — следующее:
После изменения стоп-сигнала контейнер начинает останавливаться корректно: это подтверждается тем, что больше не наблюдается 503 ошибка.
Если вы встретились с похожей проблемой, есть смысл разобраться, какой стоп-сигнал используется в контейнере и как именно выглядит preStop-хук. Вполне возможно, что причина кроется как раз в этом.
PHP-FPM… и не только
Проблема с PHP-FPM описывается тривиально: он не дожидается завершения дочерних процессов, терминирует их, из-за чего возникают 502-е ошибки во время деплоя и других операций. На bugs.php.net с 2005 года есть несколько сообщений об ошибках (например, здесь и здесь ), в которых описывается данная проблема. А вот в логах вы, вероятнее всего, ничего не увидите: PHP-FPM объявит о завершении своего процесса без каких-либо ошибок или сторонних уведомлений.
Получается, что lifecycle для контейнера будет выглядеть следующим образом:
Однако, из-за указания 30-секундного sleep мы сильно увеличим время деплоя, так как каждый pod будет терминироваться минимум 30 секунд, что плохо. Что с этим можно сделать?
С этим знанием вернёмся к нашей последней проблеме. Как уже упоминалось, Kubernetes не является монолитной платформой: на взаимодействие между разными её компонентами требуется некоторое время. Это особенно актуально, когда мы рассматриваем работу Ingress’ов и других смежных компонентов, поскольку из-за такой задержки в момент деплоя легко получить всплеск 500-х ошибок. Например, ошибка может возникать на этапе отправки запроса к upstream’у, но сам «временной лаг» взаимодействия между компонентами довольно короткий — меньше секунды.
Поэтому, в совокупности с уже упомянутой директивой process_control_timeout можно использовать следующую конструкцию для lifecycle :
Вообще говоря, описанное поведение и соответствующий workaround касаются не только PHP-FPM. Схожая ситуация может так или иначе возникать при использовании других ЯП/фреймворков. Если не получается другими способами исправить graceful shutdown — например, переписать код так, чтобы приложение корректно обрабатывало сигналы завершения, — можно применить описанный способ. Пусть он не самый красивый, но работает.
Практика. Нагрузочное тестирование для проверки работы pod’а
Нагрузочное тестирование — один из способов проверки, как работает контейнер, поскольку эта процедура приближает к реальным боевым условиям, когда на сайт заходят пользователи. Для тестирования приведённых выше рекомендаций можно воспользоваться Яндекс.Танком : он отлично покрывает все наши потребности. Далее приведены советы и рекомендации по проведению тестирования с наглядным — благодаря графикам Grafana и самого Яндекс.Танка — примером из нашего опыта.
Самое главное здесь — проверять изменения поэтапно. После добавления нового исправления запускайте тестирование и смотрите, изменились ли результаты по сравнению с прошлым запуском. В противном случае будет сложно выявить неэффективные решения, а в перспективе можно и вовсе только навредить (например, увеличить время деплоя).
Другой нюанс — смотрите логи контейнера во время его терминации. Фиксируется ли там информация о graceful shutdown? Есть ли в логах ошибки при обращении к другим ресурсам (например, к соседнему контейнеру PHP-FPM)? Ошибки самого приложения (как в описанном выше случае с NGINX)? Надеюсь, что вводная информация из этой статьи поможет лучше разобраться, что же происходит с контейнером во время его терминирования.
Итак, первый запуск тестирования происходил без lifecycle и без дополнительных директив для сервера приложений ( process_control_timeout в PHP-FPM). Целью этого теста было выявление приблизительного количества ошибок (и есть ли они вообще). Также из дополнительной информации следует знать, что среднее время деплоя каждого пода составляло около 5-10 секунд до состояния полной готовности. Результаты таковы:
На информационной панели Яндекс.Танка виден всплеск 502 ошибок, который произошел в момент деплоя и продолжался в среднем до 5 секунд. Предположительно это обрывались существующие запросы к старому pod’у, когда он терминировался. После этого появились 503 ошибки, что стало результатом остановленного контейнера NGINX, который также оборвал соединения из-за бэкенда (из-за чего к нему не смог подключиться Ingress).
Посмотрим, как process_control_timeout в PHP-FPM поможет нам дожидаться завершения child-процессов, т.е. исправить такие ошибки. Повторный деплой уже с использованием этой директивы:
Во время деплоя 500-х ошибок больше нет! Деплой проходит успешно, graceful shutdown работает.
Однако стоит вспомнить момент с Ingress-контейнерами, небольшой процент ошибок в которых мы можем получать из-за временного лага. Чтобы их избежать, остается добавить конструкцию со sleep и повторить деплой. Впрочем, в нашем конкретном случае изменений не было видно (ошибок снова нет).
Заключение
Для корректного завершения процесса мы ожидаем от приложения следующего поведения:
Однако не все приложения умеют так работать. Одним из решений проблемы в реалиях Kubernetes является:
Пример с NGINX позволяет понять, что даже то приложение, которое изначально должно корректно отрабатывает сигналы к завершению, может этого не делать, поэтому критично проверять наличие 500 ошибок во время деплоя приложения. Также это позволяет смотреть на проблему шире и не концентрироваться на отдельном pod’е или контейнере, а смотреть на всю инфраструктуру в целом.
В качестве инструмента для тестирования можно использовать Яндекс.Танк совместно с любой системой мониторинга (в нашем случае для теста брались данные из Grafana с бэкендом в виде Prometheus). Проблемы с graceful shutdown хорошо видны при больших нагрузках, которую может генерировать benchmark, а мониторинг помогает детальнее разобрать ситуацию во время или после теста.
Отвечая на обратную связь по статье: стоит оговориться, что проблемы и пути их решения здесь описываются применительно к NGINX Ingress. Для других случаев есть иные решения, которые, возможно, мы рассмотрим в следующих материалах цикла.
Другое из цикла K8s tips & tricks:
Добавить комментарий Отменить ответ
Для отправки комментария вам необходимо авторизоваться.
Kubernetes tips & tricks: особенности выполнения graceful shutdown в NGINX и PHP-FPM
Типовое условие при реализации CI/CD в Kubernetes: приложение должно уметь перед полной остановкой не принимать новые клиентские запросы, а самое главное — успешно завершать уже существующие.
Соблюдение такого условия позволяет достичь нулевого простоя во время деплоя. Однако, даже при использовании очень популярных связок (вроде NGINX и PHP-FPM) можно столкнуться со сложностями, которые приведут к всплеску ошибок при каждом деплое…
Теория. Как живёт pod
Подробно о жизненном цикле pod’а мы уже публиковали эту статью. В контексте рассматриваемой темы нас интересует следующее: в тот момент, когда pod переходит в состояние Terminating, на него перестают отправляться новые запросы (pod удаляется из списка endpoints для сервиса). Таким образом, для избежания простоя во время деплоя, с нашей стороны достаточно решить проблему корректной остановки приложения.
Также следует помнить, что grace period по умолчанию равен 30 секундам: после этого pod будет терминирован и приложение должно успеть обработать все запросы до этого периода. Примечание: хотя любой запрос, который выполняется более 5-10 секунд, уже является проблемным, и graceful shutdown ему уже не поможет…
Чтобы лучше понять, что происходит, когда pod завершает свою работу, достаточно изучить следующую схему:
А1, B1 — Получение изменений о состоянии пода
A2 — Отправление SIGTERM
B2 — Удаление pod’а из endpoints
B3 — Получение изменений (изменился список endpoints)
B4 — Обновление правил iptables
Обратите внимание: удаление endpoint pod’а и посылка SIGTERM происходит не последовательно, а параллельно. А из-за того, что Ingress получает обновленный список Endpoints не сразу, в pod будут отправляться новые запросы от клиентов, что вызовет 500 ошибки во время терминации pod’а (более подробный материал по этому вопросу мы переводили). Решать эту проблему нужно следующими способами:
Теория. Как NGINX и PHP-FPM завершают свои процессы
NGINX
Дальше всё просто: требуется добавить в preStop-хук команду, которая будет посылать сигнал о graceful shutdown. Это можно сделать в Deployment, в блоке контейнера:
Теперь в момент завершения работы pod’а в логах контейнера NGINX мы увидим следующее:
А на данном этапе с NGINX закончили: как минимум по логам можно понять, что всё работает так, как надо.
Как обстоят дела с PHP-FPM? Как он обрабатывает graceful shutdown? Давайте разбираться.
PHP-FPM
В случае с PHP-FPM информации немного меньше. Если ориентироваться на официальный мануал по PHP-FPM, то в нём будет рассказано, что принимаются следующие POSIX-сигналы:
На первый взгляд, это всё, что требуется для выполнения graceful shutdown в обоих контейнерах. Тем не менее, задача сложнее, чем кажется. Далее разобраны два случая, в которых graceful shutdown не работал и вызывал кратковременную недоступность проекта во время деплоя.
Практика. Возможные проблемы с graceful shutdown
NGINX
Мы можем наблюдать такую проблему, например, по ответам на нужном нам Ingress’e:
Показатели статус-кодов в момент деплоя
В данном случае мы получаем как раз 503 код ошибки от самого Ingress: он не может обратиться к контейнеру NGINX, так как тот уже недоступен. Если посмотреть в логи контейнера с NGINX, в них — следующее:
После изменения стоп-сигнала контейнер начинает останавливаться корректно: это подтверждается тем, что больше не наблюдается 503 ошибка.
Если вы встретились с похожей проблемой, есть смысл разобраться, какой стоп-сигнал используется в контейнере и как именно выглядит preStop-хук. Вполне возможно, что причина кроется как раз в этом.
PHP-FPM… и не только
Проблема с PHP-FPM описывается тривиально: он не дожидается завершения дочерних процессов, терминирует их, из-за чего возникают 502-е ошибки во время деплоя и других операций. На bugs.php.net с 2005 года есть несколько сообщений об ошибках (например, здесь и здесь), в которых описывается данная проблема. А вот в логах вы, вероятнее всего, ничего не увидите: PHP-FPM объявит о завершении своего процесса без каких-либо ошибок или сторонних уведомлений.
Получается, что lifecycle для контейнера будет выглядеть следующим образом:
Однако, из-за указания 30-секундного sleep мы сильно увеличим время деплоя, так как каждый pod будет терминироваться минимум 30 секунд, что плохо. Что с этим можно сделать?
С этим знанием вернёмся к нашей последней проблеме. Как уже упоминалось, Kubernetes не является монолитной платформой: на взаимодействие между разными её компонентами требуется некоторое время. Это особенно актуально, когда мы рассматриваем работу Ingress’ов и других смежных компонентов, поскольку из-за такой задержки в момент деплоя легко получить всплеск 500-х ошибок. Например, ошибка может возникать на этапе отправки запроса к upstream’у, но сам «временной лаг» взаимодействия между компонентами довольно короткий — меньше секунды.
Поэтому, в совокупности с уже упомянутой директивой process_control_timeout можно использовать следующую конструкцию для lifecycle :
Вообще говоря, описанное поведение и соответствующий workaround касаются не только PHP-FPM. Схожая ситуация может так или иначе возникать при использовании других ЯП/фреймворков. Если не получается другими способами исправить graceful shutdown — например, переписать код так, чтобы приложение корректно обрабатывало сигналы завершения, — можно применить описанный способ. Пусть он не самый красивый, но работает.
Практика. Нагрузочное тестирование для проверки работы pod’а
Нагрузочное тестирование — один из способов проверки, как работает контейнер, поскольку эта процедура приближает к реальным боевым условиям, когда на сайт заходят пользователи. Для тестирования приведённых выше рекомендаций можно воспользоваться Яндекс.Танком: он отлично покрывает все наши потребности. Далее приведены советы и рекомендации по проведению тестирования с наглядным — благодаря графикам Grafana и самого Яндекс.Танка — примером из нашего опыта.
Самое главное здесь — проверять изменения поэтапно. После добавления нового исправления запускайте тестирование и смотрите, изменились ли результаты по сравнению с прошлым запуском. В противном случае будет сложно выявить неэффективные решения, а в перспективе можно и вовсе только навредить (например, увеличить время деплоя).
Другой нюанс — смотрите логи контейнера во время его терминации. Фиксируется ли там информация о graceful shutdown? Есть ли в логах ошибки при обращении к другим ресурсам (например, к соседнему контейнеру PHP-FPM)? Ошибки самого приложения (как в описанном выше случае с NGINX)? Надеюсь, что вводная информация из этой статьи поможет лучше разобраться, что же происходит с контейнером во время его терминирования.
Итак, первый запуск тестирования происходил без lifecycle и без дополнительных директив для сервера приложений ( process_control_timeout в PHP-FPM). Целью этого теста было выявление приблизительного количества ошибок (и есть ли они вообще). Также из дополнительной информации следует знать, что среднее время деплоя каждого пода составляло около 5-10 секунд до состояния полной готовности. Результаты таковы:
На информационной панели Яндекс.Танка виден всплеск 502 ошибок, который произошел в момент деплоя и продолжался в среднем до 5 секунд. Предположительно это обрывались существующие запросы к старому pod’у, когда он терминировался. После этого появились 503 ошибки, что стало результатом остановленного контейнера NGINX, который также оборвал соединения из-за бэкенда (из-за чего к нему не смог подключиться Ingress).
Посмотрим, как process_control_timeout в PHP-FPM поможет нам дожидаться завершения child-процессов, т.е. исправить такие ошибки. Повторный деплой уже с использованием этой директивы:
Во время деплоя 500-х ошибок больше нет! Деплой проходит успешно, graceful shutdown работает.
Однако стоит вспомнить момент с Ingress-контейнерами, небольшой процент ошибок в которых мы можем получать из-за временного лага. Чтобы их избежать, остается добавить конструкцию со sleep и повторить деплой. Впрочем, в нашем конкретном случае изменений не было видно (ошибок снова нет).
Заключение
Для корректного завершения процесса мы ожидаем от приложения следующего поведения:
В качестве инструмента для тестирования можно использовать Яндекс.Танк совместно с любой системой мониторинга (в нашем случае для теста брались данные из Grafana с бэкендом в виде Prometheus). Проблемы с graceful shutdown хорошо видны при больших нагрузках, которую может генерировать benchmark, а мониторинг помогает детальнее разобрать ситуацию во время или после теста.
Отвечая на обратную связь по статье: стоит оговориться, что проблемы и пути их решения здесь описываются применительно к NGINX Ingress. Для других случаев есть иные решения, которые, возможно, мы рассмотрим в следующих материалах цикла.