Jenkins Проблемы выполнения удалённых команд в сетях со stateful firewall и способы их решения

Периодическая посылка пакетов keepalive предотвратит разрыв ssh-сеанса.

2020-05-31

Описание проблемы

Для запуска скрипта на удалённом узле могут использоваться два jenkins-плагина:
- "Publish over SSH plugin", который не имеет встроенного keepalive-механизма.
- "SSH plugin", который имеет возможность посылки keepalive-пакетов, но требует настройки, так как по умолчанию это функция выключена.

В процессе выполнения задач с использованием вышеуказанных плагинов, особенно если задача выполняется десятки минут или больше, то в дженкинсе наблюдалось прекращение вывода stdout в консоль, хотя скрипт, выполняемый на удалённом сервере, продолжал работу. Так как на дженкинсе оставался открытый ssh-сеанс в подвешенном состоянии, то дженкинс не мог определить завершения шага. Через некоторое время срабатывал принудительный останов задачи по таймауту и задача завершалась с Finished: FAILURE.

Причина

Для контроля трафика между подсетями, VMWare использует по умолчанию stateful firewall, в настройках которых стоит параметр tcpTimeoutEstablished равный 3600 секунд, по истечении которых, tcp-сеансы с нулевым трафиком будут забыты. То есть, если выполняющаяся jenkins-задача не выдаёт никакой информации в stdout довольно продолжительное время, то tcp-сеанс будет прерван файрволом. В этом случае, после окончания работы скрипта на удалённом сервере, мы можем наблюдать всё ещё открытый ssh-сеанс на дженкинс-сервере, тогда как на втором конце ssh-сеанс будет давно закрыт.

Далее опишу различия в файрволах.

Stateless firewall

Файрволы без сохранения состояния используют для описания прохождения трафика незамысловатые правила, типа разрешить трафик из одной подсети с портов выше 1024 в другую подсеть на порт 22.

Файрвол не смотрит внутрь пакетов и не отслеживает состояние tcp-сеансов, то есть можно направить миллионы пакеты с установленным флагом SYN, что заставит целевой сервер открыть миллион tcp-сеансов с выделением каждому кусочка памяти, и файрвол пропустит их все.

Stateful firewall

Файрволы с сохранением состояния, в сравнении с Stateless firewall, используют для описания прохождения трафика правила посложнее. Например, разрешить трафик из одной подсети с портов выше 1024 в другую подсеть на порт 22 с установленным флагом SYN. Если хэндшейк свершился, то пропускать на этот же порт все следующие пакеты в рамках открытой ssh-сессии.

Для обеспечения такой работы с запоминанием состояния tcp-сеансов, необходимо выделить ресурсы. Поэтому, чтобы освобождать память от бесполезной информации, незакрытые сеансы сбрасываются и их состояние забывается.

В результате, когда ранее молчавший скрипт через час выдаст в stdout какую-нибудь информацию, то stateful firewall не пропустит пакет без установленного флага SYN.

Способы решения

Способ решения давно известен — это периодическая посылка keepalive-пакетов.

Включить такой способ предохранения от сброса tcp-сеанса можно несколькими способами.

Включение keepalive в настройках sshd удалённого сервера

Это будет работать с обоими плагинами. Указываем на удалённом сервера в /etc/ssh/sshd_config следующие строки:

#TCPKeepAlive yes # эта опция включена по умолчанию
ClientAliveInterval 15
ClientAliveCountMax 3

После добавления опций не забываем перезапустить sshd:

sudo systemctl restart sshd

Включение keepalive в настройках "SSH plugin"

Заходим в "настройки дженкинса" — "конфигурация системы", где можно наблюдать параграф "SSH remote hosts". В полях "serverAliveInterval" и "timeout" устанавливаем время в миллисекундах. Для первого поля я выбрал значение 15000, а для второго 60000.

Jenkins_SSH_plugin_serverAliveInterval

Понятно, что это будет работать только с плагином "SSH plugin". В этом случае, плагин "Publish over SSH plugin" используется только для заброски файлов на удалённые сервера.

Использование стандартного ssh-клиента в "Execute shell"

Никто не запрещает выполнять стандартные rsync и ssh для заброски скрипта на удалённый сервер и его выполнения там же. Не забываем добавить на jenkins-сервере в /etc/ssh/ssh_config или в /var/lib/jenkins/.ssh/config строчки активизирующие keepalive со стороны клиента:

Host *
    ServerAliveInterval 15
    ServerAliveCountMax 3

Также можно выполнять команду ssh с опциями ssh -oServerAliveInterval=15 -oServerAliveCountMax=3 для этих же целей.