Назад | Перейти на главную страницу

случайный CONNECTION_RESET на apache2.4 debian 9

Мой сервер ведет себя странно, и я просто не могу найти причину. Я везде искал.

Я заплачу биткойны на 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 секунд.

serveride.pcap

clientide.pcap

Если бы кто-нибудь со знаниями мог быстро взглянуть на это и рассказать мне, что происходит, я был бы в восторге.

РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ:

Мне удалось сделать ошибку воспроизводимой, по крайней мере, с включенным KeepAlive. Когда запрос завершен и контент обслуживается, tcp-соединение закрывается с помощью FIN-ACK через 5 секунд. При повторном запросе во временном окне 5-12 секунд после FIN-ACK соединение зависает.

Однако с выключенным KeepAlive этого больше не происходит, хотя ошибка возникает еще чаще при загрузке нескольких ресурсов одновременно. Но тогда это уже невозможно воспроизвести.

Я почти уверен, что нашел проблему :-), потому что со мной случилось то же самое.

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. Решение

  1. Если у вас есть два процесса Apache, сначала выясните, какой из них является «зомби». Для этого остановите свой обычный сервер Apache (service apache2 stop) и проверьте, какой из них остался (netstat -tupan | grep ":80.*LISTEN"). Это «зомби». Обратите внимание на его PID.

  2. Чтобы узнать больше о том, кто или что запустило этот «зомби-процесс»:

    • Выполнить cat /proc/<pid>/loginuid с PID этого "зомби" процесса. Если это показывает 4294967295 значит, его запустила система, а не пользователь (причина). В противном случае вы можете найти UID пользователя.

    • Выполнить ps auxf и определить время безотказной работы вашего «зомби» процесса. Если он соответствует времени безотказной работы системы, это означает, что процесс каким-то образом был запущен во время загрузки.

  3. Чтобы (возможно) узнать больше о том, что происходит внутри этого «зомби-процесса», вы можете присоединиться к нему с помощью strace. Это создаст множество трудных для чтения журналов, но, поскольку воспроизвести проблему наличия этого «зомби-процесса» может быть непросто, кажется целесообразным хотя бы собрать некоторые из этих журналов (особенно HTTP-запросов, идущих в этот процесс), прежде чем мы убиваем процесс. Вы бы выполнили с PID вашего процесса вместо $PID:

    strace -o strace.log -f -p $PID
    
  4. Чтобы решить проблему на данный момент, завершите процесс "зомби", указав его PID для $PID: kill $PID или при необходимости kill -9 $PID.

  5. Убедитесь, что этот процесс "зомби" снова запущен после перезагрузки, и если да, вам нужно будет исследовать и устранить причину который.

3. Воспроизведение причины

Возможно (но не тривиально) вручную создать процесс «зомби» Apache2, который будет работать параллельно с обычным сервером Apache и просто «ничего не отвечать». Вот почти, но не совсем полные инструкции:

  1. Создайте копии соответствующих файлов конфигурации:

    cp /etc/apache2/envvars /etc/apache2/envvars-zombie
    cp /etc/apache2/apache2.conf /etc/apache2/apache2-zombie.conf
    
  2. редактировать /etc/apache2/envvars-zombie и в начале скрипта статически установлен SUFFIX="-zombie", отменяя условное присвоение в нем.

  3. редактировать /etc/apache2/apache2-zombie.conf и предотвратить включение любых файлов конфигурации VirtualHost. В моем случае я бы изменил соответствующую строку на:

    # IncludeOptional sites-enabled/
    
  4. Убедитесь, что порты прослушивания по умолчанию включены в ваш apache2-zombie.conf файл. В моем случае это уже произошло через Include ports.conf.

  5. Создайте файлы блокировки и журналы, необходимые для нового экземпляра 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/
    
  6. Теперь вы сможете запустить свой «зомби» процесс Apache следующим образом:

    cd /etc/apache2/
    source envvars-zombie
    /usr/sbin/apache2 -f apache2-zombie.conf -k start
    
  7. Убедитесь, что теперь на стандартных портах Apache2 действительно запущен второй процесс: netstat -tupan | grep ":80.*LISTEN".

  8. Этот второй сервер Apache2 еще не является «зомби», потому что он все равно будет отвечать «404 Not Found» или (поскольку мы не настроили SSL) приведет к ошибке SSL при запросе на порт 443. Но вы уже можете наблюдать эффект который немного запросы поступают на этот новый сервер и приводят к этим ошибкам недетерминированным образом. (Дошел до этого на практике…)

  9. Чтобы создать «настоящий» зомби-Apache, настройте простой скрипт, который будет принимать HTTP-запрос и ничего не делать (sleep()) на несколько минут, чтобы браузер отказался, соответственно. чтобы время ожидания TCP-соединения истекло. Установите его для хоста Apache по умолчанию. Таким образом, он будет использоваться для всех HTTP-запросов к порту, поскольку мы отключили все конфигурации VirtualHost, поэтому Apache не сможет найти более подходящий хост для любого запроса и выберет хост по умолчанию.

Я бы проверил размер TCP-пакетов, проходящих между сервером и клиентом. ЕСЛИ они приближаются к размеру 1500, есть вероятность, что они упадут из-за множества возможностей:

  1. Если в пакете установлен бит DNF, и пакет где-то фрагментируется, это может быть проблемой, из-за которой пакет теряется.

  2. Если для MTU установлено значение 1500, а пакеты проходят через туннели, шифрование и т. Д., Что приводит к добавлению дополнительных заголовков к пакету, то это также приведет к падению ваших пакетов. Попробуйте установить mtu на обоих концах интерфейсов, которые вы используете, на значение ниже 1500, возможно, 1420 или даже ниже.