Случайный CONNECTION_RESET на apache2.4 Debian 9
У моего сервера странное поведение, и я просто не могу найти причину. Я искал везде.
Я заплачу биткойны на 200$ любому, кто сможет это понять.
Эта проблема:
При запросе любого ресурса из apache (page, image, css, js) иногда требуется очень много времени для ответа. Примерно в половине случаев соединение сбрасывается. (в Chrome: net::ERR_CONNECTION_RESET) Это происходит редко, случайно и абсолютно непредсказуемо. Еще более запутанно, хотя один запрос кажется зависшим, я могу сделать дополнительные запросы, которые отлично работают.
О сервере:
Я использую apache2.4 mpm-prefork с php7.0 на debian 9. Модуль apache использует mod_rewrite и ssl-сертификат от certbot. В некоторых случаях php вызывает inkscape для рендеринга svgs в png.
Загрузка сервера очень низкая (0,02), и на нем работает только Apache.
Вещи проверены:
- проверил все логи сервера. (системный журнал, журнал Apache) - ничего
- увеличил пределы apache mpm-prefork - нет
- проверил на возможные проблемы с DNS - ничего
- Я даже перешел на совершенно новый корневой сервер (от другого провайдера) - все тот же
Я продолжил и проанализировал tcp-трафик с помощью Wireshark, и обнаружилось подозрительное поведение. Когда соединение замораживается, есть некоторые неупорядоченные сегменты пакетов TCP Out-of-Order, Retransmission и ACKed... но у меня нет необходимых знаний низкого уровня, чтобы сказать, что происходит.
Любые намеки будут очень оценены!
РЕДАКТИРОВАТЬ:
Это конфигурация mpm_prefork:
<IfModule mpm_prefork_module>
StartServers 10
MinSpareServers 10
MaxSpareServers 50
MaxRequestWorkers 300
MaxConnectionsPerChild 0
</IfModule>
РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ:
Мне повезло, и я получил tcp sniffer на сервере и клиенте, когда это случилось снова. Вот файлы pcap, обрезанные за последние ~30 секунд.
Если бы кто-нибудь со знанием мог быстро взглянуть на это и сказать мне, что происходит, я был бы в восторге.
РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ:
Мне удалось сделать ошибку воспроизводимой, по крайней мере, с KeepAlive. Когда запрос завершен и контент обработан, соединение tcp закрывается с FIN-ACK через 5 секунд. При повторном запросе через 5-12 секунд после FIN-ACK соединение останавливается.
Однако, если KeepAlive отключен, этого больше не происходит, так как ошибка возникает все чаще при одновременной загрузке нескольких ресурсов. Но тогда это уже не воспроизводимо.
2 ответа
Я бы проверил размер TCP-пакетов, идущих между сервером и клиентом. Если они приближаются к размеру 1500, есть вероятность, что они упадут из-за многочисленных возможностей:
Если в пакете установлен бит DNF и пакет где-то фрагментируется, это может быть проблемой, из-за которой пакет отбрасывается
Если для MTU установлено значение 1500 и пакеты проходят через туннели, шифрование и т. Д., Что приводит к добавлению в пакет дополнительных заголовков, то это также может привести к сбросу пакетов. Попробуйте установить mtu на обоих концах интерфейсов, которые вы используете, на значение ниже 1500, возможно, 1420 или даже ниже.
Уверен, что нашел проблему:-), так как со мной случилось то же самое.
1. Причина
Я думаю, что у вас есть два или более процессов, обслуживающих порт 80 (или 443, если речь идет о соединениях SSL). Вы можете проверить это следующим образом, с помощью команды для порта 80 и вывода из моей системы, в которой возникла проблема:
# netstat -tupan | grep ":80.*LISTEN"
Proto Recv-Q Send-Q Local Foreign State PID/Program name
Address Address
tcp6 0 0 :::80 :::* LISTEN 22718/apache2
tcp6 0 0 :::80 :::* LISTEN 1794/apache2
Два процесса, обслуживающие одинаковые IP-адреса с одного и того же порта, действительно возможны с опциями порта SO_REUSEADDR
а также SO_REUSEPORT
смотрите здесь и здесь (раздел о "Linux> = 3.9").
Что делает ядро SO_REUSEPORT
должен распределять входящие TCP-соединения на процессы, обслуживающие этот порт, недетерминированным образом. Один процесс - это ваш Apache, который правильно обрабатывает запрос, а другой - "что-то другое", которое никогда не отвечает ни на что. В моем случае это был другой процесс Apache2.
2. Решение
Если у вас есть два процесса Apache, сначала выясните, какой из них является "зомби". Для этого остановите ваш обычный сервер Apache (
service apache2 stop
) и проверьте, какой из них остается (netstat -tupan | grep ":80.*LISTEN"
). Это "зомби". Обратите внимание на его PID.Чтобы узнать больше о том, кто или что начал этот процесс "зомби":
казнить
cat /proc/<pid>/loginuid
с PID этого процесса "зомби". Если это показывает4294967295
это означает, что система запустила его, а не пользователя ( причина). В противном случае вы можете посмотреть UID пользователя.казнить
ps auxf
и определить время безотказной работы вашего "зомби" процесса. Если это соответствует времени работы системы, это означает, что процесс был каким-то образом запущен во время загрузки.
Чтобы (возможно) узнать больше о том, что происходит внутри этого "зомби" процесса, вы можете присоединиться к нему с помощью
strace
, Это создаст много трудных для чтения журналов, но, поскольку воспроизвести проблему, связанную с этим процессом "зомби", может быть нелегко, кажется хорошим, по крайней мере, собрать некоторые из этих журналов (особенно HTTP-запросы, идущие к этому процессу) до того, как мы убиваем процесс. Вы выполняете с PID вашего процесса вместо$PID
:strace -o strace.log -f -p $PID
Чтобы решить проблему на данный момент, убейте процесс "зомби", предоставив его PID для
$PID
:kill $PID
или при необходимостиkill -9 $PID
,Проверьте, запущен ли этот процесс "зомби" после перезагрузки, и если да, вам придется выяснить и устранить причину этого.
3. Воспроизведение причины
Можно (но не тривиально) вручную создать процесс "зомби" Apache2, который будет работать параллельно обычному серверу Apache и просто "ничего не отвечать". Вот почти полные инструкции:
Создайте копии соответствующих конфигурационных файлов:
cp /etc/apache2/envvars /etc/apache2/envvars-zombie cp /etc/apache2/apache2.conf /etc/apache2/apache2-zombie.conf
редактировать
/etc/apache2/envvars-zombie
и в начале сценария статически установленSUFFIX="-zombie"
, отменяя условное присвоение в нем.редактировать
/etc/apache2/apache2-zombie.conf
и предотвратить включение любых файлов конфигурации VirtualHost. В моем случае я бы изменил соответствующую строку так:# IncludeOptional sites-enabled/
Убедитесь, что порты прослушивания по умолчанию включены в ваш
apache2-zombie.conf
файл. В моем случае это уже произошло черезInclude ports.conf
,Создайте файл блокировки и каталоги журналов, необходимые для нового экземпляра Apache2, и сделайте их доступными для пользователя, с которым будет работать ваш новый Apache2:
mkdir /var/log/apache2-zombie chown www-data /var/log/apache2-zombie/ mkdir /var/lock/apache2-zombie chown www-data /var/lock/apache2-zombie/
Теперь у вас должна получиться запустить процесс Apache "зомби" следующим образом:
cd /etc/apache2/ source envvars-zombie /usr/sbin/apache2 -f apache2-zombie.conf -k start
Убедитесь, что на стандартных портах Apache2 действительно запущен второй процесс:
netstat -tupan | grep ":80.*LISTEN"
,Этот второй сервер Apache2 еще не является "зомби", поскольку он все равно будет отвечать "404 Not Found" или (поскольку мы не настроили SSL) приведет к ошибке SSL при выполнении запроса на порт 443. Но вы уже можете наблюдать эффект что несколько запросов поступают на этот новый сервер и приводят к этим ошибкам недетерминированным образом. (Я дошел до этого момента на практике...)
Чтобы создать "правильного" зомби-апача, настройте простой скрипт, который будет принимать HTTP-запрос, а затем ничего не делать (
sleep()
) в течение нескольких минут, чтобы браузер сдался чтобы истекло время соединения TCP. Установите его для хоста Apache по умолчанию. Таким образом, он будет использоваться для всех HTTP-запросов к порту, так как мы отключили все конфигурации VirtualHost, чтобы Apache не мог найти более подходящий хост для любого запроса и выберет хост по умолчанию.