Мой сервер ведет себя странно, и я просто не могу найти причину. Я везде искал.
Я заплачу биткойны на 200 долларов каждому, кто сможет это понять.
При запросе любого ресурса из apache (страница, изображение, 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.
Я продолжил и проанализировал 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, работающий как на сервере, так и на клиенте, когда это случилось снова. Вот файлы pcap, обрезанные до последних ~ 30 секунд.
Если бы кто-нибудь со знаниями мог быстро взглянуть на это и рассказать мне, что происходит, я был бы в восторге.
Мне удалось сделать ошибку воспроизводимой, по крайней мере, с включенным KeepAlive. Когда запрос завершен и контент обслуживается, tcp-соединение закрывается с помощью FIN-ACK через 5 секунд. При повторном запросе во временном окне 5-12 секунд после FIN-ACK соединение зависает.
Однако с выключенным KeepAlive этого больше не происходит, хотя ошибка возникает еще чаще при загрузке нескольких ресурсов одновременно. Но тогда это уже невозможно воспроизвести.
Я почти уверен, что нашел проблему :-), потому что со мной случилось то же самое.
Я думаю у тебя есть ДВА или более процессов, обслуживающих порт 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.
Если у вас есть два процесса 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
.
Убедитесь, что этот процесс "зомби" снова запущен после перезагрузки, и если да, вам нужно будет исследовать и устранить причину который.
Возможно (но не тривиально) вручную создать процесс «зомби» 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. Но вы уже можете наблюдать эффект который немного запросы поступают на этот новый сервер и приводят к этим ошибкам недетерминированным образом. (Дошел до этого на практике…)
Чтобы создать «настоящий» зомби-Apache, настройте простой скрипт, который будет принимать HTTP-запрос и ничего не делать (sleep()
) на несколько минут, чтобы браузер отказался, соответственно. чтобы время ожидания TCP-соединения истекло. Установите его для хоста Apache по умолчанию. Таким образом, он будет использоваться для всех HTTP-запросов к порту, поскольку мы отключили все конфигурации VirtualHost, поэтому Apache не сможет найти более подходящий хост для любого запроса и выберет хост по умолчанию.
Я бы проверил размер TCP-пакетов, проходящих между сервером и клиентом. ЕСЛИ они приближаются к размеру 1500, есть вероятность, что они упадут из-за множества возможностей:
Если в пакете установлен бит DNF, и пакет где-то фрагментируется, это может быть проблемой, из-за которой пакет теряется.
Если для MTU установлено значение 1500, а пакеты проходят через туннели, шифрование и т. Д., Что приводит к добавлению дополнительных заголовков к пакету, то это также приведет к падению ваших пакетов. Попробуйте установить mtu на обоих концах интерфейсов, которые вы используете, на значение ниже 1500, возможно, 1420 или даже ниже.