За последнюю неделю один из наших унаследованных основных серверов стал выдавать ошибки со все более тревожной скоростью.

Быть стабильным унаследованным сервером, обслуживающим несколько корпоративных приложений, разработанных более двух лет назад - в условиях замораживания кода - это казалось довольно странным.

Я немедленно запрыгнул в коробку и проверил кричащие журналы ошибок 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-флаги ядра!

Всего хорошего.