За последнюю неделю один из наших унаследованных основных серверов стал выдавать ошибки со все более тревожной скоростью.
Быть стабильным унаследованным сервером, обслуживающим несколько корпоративных приложений, разработанных более двух лет назад - в условиях замораживания кода - это казалось довольно странным.
Я немедленно запрыгнул в коробку и проверил кричащие журналы ошибок Nginx и о чудо меня встретил быстро прокручивающийся отчет о тайм-аутах восходящего потока:
«Странно, - подумал я. Проведя базовую проверку работоспособности - включая идентификаторы фиксации git HEAD для сервисов и время безотказной работы процесса, все было так, как должно быть: заморожено во времени и работает без сбоев, но это не так.
Обратите внимание, что этот сервис является частью Atajo, который использует исключительно веб-сокеты для связи между устройствами и серверными модулями в реальном времени. Это «устаревшее» ядро по-прежнему использовало socket.io (с опросом), как вы можете видеть на изображении выше.
Затем я сосредоточился на главной внешней переменной - клиентских соединениях.
Мне нравится следующая команда, которая дает мне довольно хорошее представление о состояниях TCP-соединения на коробке:
netstat -tan | awk '{print $6}' | sort | uniq -c
Что в данном случае дало мне:
11948 ESTABLISHED 145 FIN_WAIT1 1425 FIN_WAIT2 9 LAST_ACK 118 LISTEN 69 SYN_RECV 60523 TIME_WAIT
Боже мой, я подумал… 60k подключений в состоянии TIME_WAIT. Это почти все, что выделено на виртуальной машине. Что-то пошло не так.
Была ли это DoS-атака (D)?
Проверив брандмауэр iptables
и пропускную способность с помощью iftop
- я мог видеть, что весь трафик был законным, а IP-адреса запрашиваемых из разрешенных подсетей.
Я бросился к телефону, чтобы поговорить с одним из наших инженеров службы поддержки - спросить его, есть ли еще какие-то пользователи, выделенные для этого приложения, и были ли они.
Обычно наши корпоративные клиенты уведомляют нас, если они ожидают / выделяют больше пользователей для / для приложения, но в этом случае, поскольку все работало без сбоев более 2 лет - почти забыто - они просто добавили еще одно подразделение (~ 1000 пользователей) в приложение, не задумываясь.
Честно говоря, 1000 пользователей - это не так уж и много - но что-то из-за этого вызывало таймауты восходящего потока.
Итак, вернемся к журналу ошибок Nginx, я мог видеть все попытки подключения к опросу.
По сути, socket.io на клиентском устройстве запускается с HTTP-рукопожатия и пытается перейти на WebSocket, если он поддерживается (некоторые устройства в полях, где Android 4.0 или ниже). Если нет, сокеты «имитируются» HTTP-опросом. .
Итак, у меня возникло два вопроса: почему сокеты не обновляются, и, во-вторых, почему законно не поддерживающие веб-сокеты приложения внезапно запускали поток соединений из-за непрерывных опросов после многих лет стабильности.
Я заподозрил, что эти двое связаны, и вернулся в журналы, чтобы посмотреть, когда это началось, когда стало хуже и когда стало лучше.
Короче:
- Я понял, что во-первых, эта виртуальная машина никогда не была настроена с моей стандартной настройкой флага ядра TCP (это все еще был стандартный образ Ubuntu).
- Во-вторых, из-за этого, когда Nginx начал насыщать восходящие потоки, обновление веб-сокета не произошло, в результате чего все больше и больше устройств возвращались к опросу (который является своего рода петлей обратной связи), пока мы не увидим около 500 запросов на опрос в секунду (на виртуальная машина с 2 процессорами / 16 ГБ ОЗУ), что, очевидно, не будет работать.
Итак, я поделюсь с вами примером конфигурации флага ядра TCP (добавьте его в /etc/sysctl.d/100-socket.io.conf
и обновите с помощью sysctl -p
)
net.ipv4.ip_local_port_range='1024 65000' net.ipv4.tcp_tw_reuse='1' net.ipv4.tcp_fin_timeout='15' net.core.netdev_max_backlog='4096' net.core.rmem_max='16777216' net.core.somaxconn='4096' net.core.wmem_max='16777216' net.ipv4.tcp_max_syn_backlog='20480' net.ipv4.tcp_max_tw_buckets='400000' net.ipv4.tcp_no_metrics_save='1' net.ipv4.tcp_rmem='4096 87380 16777216' net.ipv4.tcp_syn_retries='2' net.ipv4.tcp_synack_retries='2' net.ipv4.tcp_wmem='4096 65536 16777216' vm.min_free_kbytes='65536'
А для большего удовольствия добавьте высокий уровень keepalive в конфигурацию восходящего потока Nginx:
upstream atajo { hash $arg_UUID consistent; server 127.0.0.1:30000; server 127.0.0.1:30001; keepalive 512; }
Интересно отметить, что часть
hash $arg_UUID consistent
предназначена для жесткой балансировки между восходящими потоками (поскольку socket.io требует закрепленных сеансов, чтобы пакеты рукопожатия направлялись в тот же восходящий поток). Вместо использования IP-адреса (поскольку через APN или внутреннюю корпоративную сеть с одного и того же IP-адреса могут подключаться сотни устройств) я использую фактический UUID, предоставленный Android и iOS через стандартные API. Это позволяет более плавно распределять соединения.
В любом случае урок состоит в том, что ВСЕГДА проверяйте TCP-флаги ядра!
Всего хорошего.