Случайный 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 секунд.

serverside.pcap

clientside.pcap

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

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

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

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

2 ответа

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

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

  2. Если для 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. Решение

  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. Чтобы создать "правильного" зомби-апача, настройте простой скрипт, который будет принимать HTTP-запрос, а затем ничего не делать (sleep()) в течение нескольких минут, чтобы браузер сдался чтобы истекло время соединения TCP. Установите его для хоста Apache по умолчанию. Таким образом, он будет использоваться для всех HTTP-запросов к порту, так как мы отключили все конфигурации VirtualHost, чтобы Apache не мог найти более подходящий хост для любого запроса и выберет хост по умолчанию.

Другие вопросы по тегам