Вопрос или проблема
Мне удалось настроить приложение PHP Fast CGI с фронтендом на nginx. Если я обращаюсь к nginx напрямую, всё работает как ожидалось. Однако в производственной среде трафик проходит через HAProxy, и я не могу заставить эту конфигурацию работать.
При прохождении через HAProxy я получаю 502 Bad Gateway
и следующую ошибку от nginx:
[info] 7#7: *1 epoll_wait() сообщило, что клиент преждевременно закрыл соединение, поэтому upstream соединение тоже закрыто (104: Сброс соединения другой стороной) при чтении от upstream, клиент: 172.18.0.6, сервер: , запрос: "GET / HTTP/1.1", upstream: "fastcgi://172.18.0.3:9000", host: "localhost:8888"
Конфигурация
Вот мой docker-compose.yml, описывающий стек:
version: '2'
services:
logger:
image: colstrom/syslog
proxy:
build:
context: .
dockerfile: Dockerfile-haproxy
ports:
- "8888:12000"
web:
build:
context: .
dockerfile: Dockerfile-web
ports:
- "9999:80"
php:
build:
context: .
dockerfile: Dockerfile-app
Dockerfile-haproxy:
FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
EXPOSE 12000
haproxy.cfg
global
maxconn 4096
maxpipes 1024
log logger:514 local2
defaults
timeout client 50000
timeout connect 5000
timeout server 50000
frontend bak
bind *:12000
mode http
option httplog
log global
default_backend bak
backend bak
mode http
server web web:80
Dockerfile-web:
FROM nginx
COPY site.conf /etc/nginx/conf.d/default.conf
COPY src /var/www/html
site.conf (конфигурация nginx):
server {
index index.php index.html;
error_log /var/log/nginx/error.log info;
access_log /var/log/nginx/access.log;
root /var/www/html;
location / {
fastcgi_pass php:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME index.php;
}
}
Полный вывод журнала из docker-compose выглядит следующим образом:
php_1 | 172.18.0.2 - 11/Feb/2017:11:09:19 +0000 "GET /" 200
web_1 | 2017/02/11 11:09:19 [info] 7#7: *1 epoll_wait() сообщило, что клиент преждевременно закрыл соединение, поэтому upstream соединение тоже закрыто (104: Сброс соединения другой стороной) при чтении от upstream, клиент: 172.18.0.6, сервер: , запрос: "GET / HTTP/1.1", upstream: "fastcgi://172.18.0.3:9000", host: "localhost:8888"
web_1 | 172.18.0.6 - - [11/Feb/2017:11:09:19 +0000] "GET / HTTP/1.1" 200 8005 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, как Gecko) Chrome/55.0.2883.95 Safari/537.36"
logger_1 | Feb 11 11:09:19 a94d638768c9 user.notice root: <150>Feb 11 11:09:19 haproxy[9]: 172.18.0.1:54508 [11/Feb/2017:11:09:19.285] bak bak/web 0/0/1/-1/37 502 8524 - - PH-- 0/0/0/0/0 0/0 "GET / HTTP/1.1"
Похоже, что требуется дополнительная конфигурация в HAProxy или nginx, но я не стал мудрее в этом вопросе.
Обновления / Вещи, которые я пробовал
Установка nginx fastcgi_ignore_client_abort on;
является одной из предложенных поправок, с которыми я сталкивался. Итог тот же, но ошибка nginx немного другая:
2017/02/11 11:40:35 [info] 7#7: *3 writev() ошибка (104: Сброс соединения другой стороной) при отправке клиенту, клиент: 172.18.0.5, сервер: , запрос: "GET / HTTP/1.1", upstream: "fastcgi://172.18.0.2:9000", host: "localhost:8888"
Установка nginx proxy_buffering on;
. Это не оказывает никакого влияния.
Установка haproxy mode tcp
правильно обслуживает страницу. Однако мне нужен mode http
, потому что я делаю L7 балансировку нагрузки (не показано в этом тестовом случае). Это привело меня к пониманию, что логи HAProxy сообщают PH
, что, как я понимаю, означает плохой ответ от upstream.
Есть ли способ получить больше информации об этом? Если я обращаюсь напрямую к nginx, заголовки ответа выглядят нормально, насколько я могу судить:
HTTP/1.1 200 OK
Server: nginx/1.11.9
Date: Sat, 11 Feb 2017 12:05:07 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/5.4.45
Expires: Sat, 11 Feb 2017 12:15:07GMT
Cache-Control: public,max-age=600
В конце концов, PH--
в логах haproxy выявило проблему. Заголовки HTTP-ответа были недействительными. Я просто убрал Date
и Expires
, и теперь всё работает идеально.
Ответ или решение
Вопрос, с которым столкнулся автор, касается настройки цепочки HAProxy -> nginx -> Fast CGI, используемой для обработки PHP-приложений. Потребуется подробный анализ конфигурации и логов для выявления причины, по которой возникает ошибка 502 Bad Gateway при маршрутизации трафика через HAProxy.
Теория
Чтобы понять источник проблемы, необходимо кратко рассмотреть каждую часть связки: HAProxy, nginx и Fast CGI.
-
HAProxy: это мощное решение для балансировки нагрузки, которое используется для распределения входящих запросов на сервера приложений. Оно может работать в режимах TCP и HTTP, где каждый режим влияет на обработку сетевого трафика по-разному. Режим HTTP позволяет выполнять балансировку на уровне приложений, что критично для множества современных приложений.
-
nginx: это веб-сервер, который также способен выступать в роли прокси-сервера и FastCGI-прокси. Он получает HTTP запросы от клиентов и, в случае использования FastCGI, пересылает их на Fast CGI сервер (например, PHP-FPM).
-
FastCGI: это протокол, который используется для взаимодействия веб-серверов и программ исполнения, как PHP. В этой конфигурации он управляет выполнением PHP-скриптов.
Пример
Из представленной конфигурации видно, что при прямом обращении к nginx проблема отсутствует, что указывает на возможные конфликты в настройках HAProxy или на взаимодействие между HAProxy и nginx. Проблема проявляется, когда запросы проходят через HAProxy, выдавая ошибку 502 Bad Gateway.
Логи nginx указывают на ошибку [info] 7#7: *1 epoll_wait() reported that client prematurely closed connection
, что означает, что соединение с клиентом неожиданно закрылось.
Конфигурация docker-compose описывает три службы:
- proxy: изображение HAProxy, настроенное через файл
haproxy.cfg
. - web: изображение nginx с конфигурацией
site.conf
, где настроен upstream наphp:9000
. - php: PHP FastCGI сервер, обрабатывающий запросы.
Применение
Для решения проблемы следует рассмотреть несколько моментов:
-
Настройка HAProxy:
- Убедитесь, что режим HAProxy соответствует вашим нуждам. В данном случае требуется L7 (HTTP) балансировка, поэтому убедитесь, что конфигурация HAProxy выдерживается в режиме HTTP и корректно обрабатывает заголовки HTTP.
- В логах HAProxy видно код ошибки
PH--
, что указывает на проблему c заголовками HTTP. Стоит анализировать их на валидность и соответствие стандарту HTTP/1.1.
-
Настройка Nginx:
- Проверьте, что директива
fastcgi_pass
правильно указывает на адрес PHP сервера. - Убедитесь, что
include fastcgi_params;
используется корректно, что критично для передачи правильно заполненных заголовков на FastCGI сервер.
- Проверьте, что директива
-
Обработка ошибок и логгирование:
- Для выяснения деталей используемой версии софта и диагностики причины проблем, увеличьте уровень логирования как на nginx, так и на HAProxy.
- Можно временно настроить logging уровня debug в HAProxy, чтобы получить больше информации о проблемных заголовках.
-
Диагностика методами исключения:
- Проверьте возможность временного переключения HAProxy в TCP-режим для изоляции проблем HTTP уровня.
- Попробуйте отладить каждую часть связки (nginx и FastCGI) по отдельности до интеграции всех элементов вместе.
Таким образом, изначальная проблема оказалась в некорректных HTTP заголовках, из-за которых HAProxy отказался обрабатывать запросы. Удаление заголовков Date
и Expires
, как оказалось, решило проблему, что следует использовать при конфигурации сложных систем и отладки.
Этот случай демонстрирует важность детального подхода к конфигурации систем и необходимости глубокого понимания работы каждой из подсистем, чтобы успешно развернуть и настроить высоконагруженные приложения.