Jenkins: Миграция в облако

Жил был Jenkins в локальной сети. Когда офисы стали геораспределенными встала необходимость обеспечить всем более-менее одинаковую скорость доступа к мастеру. Потому было решено перенести мастер на Amazon Elastic Cloud.

Спецификация виртуальной машины

Поскольку на мастере хранится некоторое количество данных, то объем хранилища должен быть довольно большим. Вычислительной мощи не нужно, но на всякий случай взяли с запасом. Поскольку приложение работает на Java, то при запуске оно создает некоторый буфер, стеки и прочее в памяти для повышения производительности. В итоге получилась следующая конфигурация:

CPU Cores x2
RAM 4GB
NVME 5GB
STORAGE 3TB

Старая схема работы

По старой схеме Jenkins крутился на виртуальной машине KVM с аналогичными характеристиками. Офисы расположены как на дальнем востоке так и в европейской части страны и в ряде других стран. Все сети соединены посредством OpenVPN у которой конечно наблюдаются проблемы с производительностью, а поскольку большая часть производства на дальнем востоке, то и Jenkins там же был развернут. По мере роста компании доступ к нему понадобился со всех офисов. Здесь сразу несколько причин для переезда в облако. Это и нестабильный интернет на дальнем востоке и низкая скорость передачи данных с европейской частью страны и еще хуже связь с другими странами даже с европейской части страны.

Новая схема работы

Методом тыка определили, что скорость передачи данных с амазоновскими виртуалками плюс-минус одинаковая со всех стран и офисов. Поскольку в Jenkins не хранятся персональные данные о пользователях, то не имеет значения где находится сервер, но ноды должны быть в том же офисе с которого и будут отправлять проекты на сборку и тестирование. Это обусловлено тем что если что-то случится в одном из офисов (сломался интернет, ушел свет, случился пожар и т.д.), то это не никак не скажется на работе остальных офисов. Ноды на linux и macos будут связываться с мастером по протоколу ssh, а виндовые по JNLP. Таким образом не придется в каждом офисе перенастраивать NAT и прокидывать over-дохрена портов на ноды поскольку протокол ssh не является мультиплексируемым. Также таким образом избавляемся от необходимости использования OpenVPN что дает +1 к производительности в передаче данных. Да, да, по идее мастер подключается к нодам и на первый взгляд проброс over-дохрена портов при такой схеме не избежен, но чтобы избавиться от этого мы воспользовались возможностью ssh выстраивать reverse tunnel.

Миграция Jenkins

Самый простой способ это установить Jenkins с репозитория и перенести данные с старой установки. Погнали:

wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install jenkins

Если в /var/lib/jenkins что-то есть то можно смело удалить все содержимое.

На старом сервере генерируем ssh ключ и регистрируем на новом сервере публичный ключ:

su jenkins
ssh-keygen -t rsa
ssh-copy-id jenkins@amazon_vm

На новом сервере настраиваем ssh сервер:

vi /etc/ssh/sshd_config

#добавляем директиву AllowUsers
AllowUsers admin
#добавляем аналогичную директиву с проверкой адреса
Match Address old_server_ip
	AllowUsers admin jenkins

systemctl reload sshd

Теперь выполняем перенос данных с старого сервера на новый:

su jenkins
rsync -avz --exclude '*.log' --exclude '*.tmp' /var/lib/jenkins/* jenkins@amazon_vm:/var/lib/jenkins

Настройка IPTABLES

Далее настройка фаервола:

mkdir /root/bin
vi /root/bin/iptables.sh

#!/bin/bash

# ENVIRONMENT VARIABLES #
PATH=$PATH:/sbin
IP_LIST="тут список IP адресов офисов"

# INITIALIZATION #
iptables -F
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

iptables -I INPUT -i lo -j ACCEPT
iptables -I OUTPUT -o lo -j ACCEPT

# SERVICE RULES #

#ICMP
#iptables -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
#iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT
#iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
#iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
#iptables -A OUTPUT -p icmp --icmp-type destination-unreachable -j ACCEPT
#iptables -A OUTPUT -p icmp --icmp-type time-exceeded -j ACCEPT
#iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
#iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT

#DNS
iptables -A INPUT -p tcp --sport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A INPUT -p udp --sport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT

#SSH
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 22 -j ACCEPT
iptables -A INPUT -p tcp --sport 22 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT

#NTP
iptables -A INPUT -p udp --sport 123 -j ACCEPT
iptables -A OUTPUT -p udp --dport 123 -j ACCEPT
iptables -A INPUT -p tcp --sport 123 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 123 -j ACCEPT

#NGINX
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -p tcp --sport 443 -j ACCEPT

#HTTP/TLS
iptables -A INPUT -p tcp --sport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --sport 443 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT

#JNLP
for IP in $IP_LIST
do
        iptables -A INPUT -s $IP -p tcp --dport 5000 -j ACCEPT
        iptables -A OUTPUT -d $IP -p tcp --sport 5000 -j ACCEPT
done

#SMTP/TLS
iptables -A INPUT -p tcp --sport 587 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 587 -j ACCEPT

# END OF SERVICE RULES #

#deny all
iptables -A INPUT -p all -j DROP
iptables -A OUTPUT -p all -j DROP

chmod +x /root/bin/iptables.sh
/root/bin/iptables.sh
chmod a+x /etc/rc.local
echo "/root/bin/iptables.sh" >> /etc/rc.local

Настройка NGINX

Подразумевается что nginx уже скомпилен и готов к работе. Запилим конфиг:

server {
        listen 80;
        rewrite ^(.*) https://$host$1;
        server_name myjenkins.com;
        server_tokens off;
}

server {
        listen 443 ssl;
        server_name myjenkins.com;
        server_tokens off;
        #ssl on;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        ssl_certificate /etc/letsencrypt/live/myjenkins.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/myjenkins.com/privkey.pem;
        add_header Strict-Transport-Security max-age=2592000;
        access_log logs/jenkins_access.log;
        error_log logs/jenkins_error.log;
        location / {
                proxy_pass http://127.0.0.1:8080;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Scheme $scheme;
                client_max_body_size 3G;
                client_body_buffer_size 128k;
                proxy_connect_timeout 1800;
                proxy_send_timeout 360m;
                proxy_read_timeout 360m;
                proxy_temp_file_write_size 64k;
                proxy_max_temp_file_size 0;
                proxy_http_version 1.1;
                proxy_request_buffering off;
                proxy_buffering off;
        }
        location ^~ /.well-known {
                alias /var/www/html/.well-known;
                charset off;
                add_header Content-Type text/plain;
        }
}

Запуск мастера

Перед запуском еще пройдемся по конфигу. Конфигурации используемые при запуске лежат в /etc/sysconfig/jenkins. Если по какой-то причине директория с данными отличается и почему-то нет возможности сделать симлинк, то надо подправить директиву JENKINS_HOME. JENKINS_LISTEN_ADDRESS лучше поменять на 127.0.0.1.

systemctl start nginx
systemctl start jenkins

AUTOSSH

Изначально пробовали все настроить на OpenVPN, но столкнулись с тем, что когда идут потери пакетов, то иногда tcp-соединение зависает. AutoSSH умеет в таких случаях перезапускать туннель. Настроим ноду на ubuntu:

apt install autossh
vi /etc/systemd/system/autossh-jenkins-tunnel.service

[Unit]
Description=autossh
After=network.target

[Service]
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -N -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -R 5043:localhost:22 -i /var/lib/jenkins/.ssh/id_rsa jenkins@amazon_vm

[Install]
WantedBy=multi-user.target

systemctl daemon-reload
systemctl enble autossh-jenkins-tunnel
systemctl start autossh-jenkins-tunnel

В настройках ноды на мастере указываем localhost.

Jenkins: Миграция в облако 1

Теперь тоже самое, но для macos:

brew install autossh
vi /Library/LaunchDaemons/com.autossh.agent.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.autossh.agent</string>
        <key>RunAtLoad</key>
        <true/>
        <key>KeepAlive</key>
        <true/>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/local/bin/autossh</string>

                <string>-N</string>

                <string>-M</string>
                <string>0</string>

                <string>-o</string>
                <string>ServerAliveInterval 30</string>

                <string>-o</string>
                <string>ServerAliveCountMax 3</string>

                <string>-R</string>
                <string>2210:localhost:22</string>

                <string>-i</string>
                <string>/Users/jenkins/.ssh/id_rsa</string>

                <string>jenkins@amazon_vm</string>
        </array>
        <key>UserName</key>
        <string>jenkins</string>
        <key>GroupName</key>
        <string>wheel</string>
        <key>StandardOutPath</key>
        <string>/Users/jenkins/.logs/autossh.output.log</string>
        <key>StandardErrorPath</key>
        <string>/Users/jenkins/.logs/autossh.output.log</string>
</dict>
</plist>

sudo launchctl load -w /Library/LaunchDaemons/com.autossh.agent.plist

JNLP

Первым делом скачиваем сам JNLP на ноду по ссылке https://myjenkins.com/computer/win10jnlp/slave-agent.jnlp где win10jnlp это название ноды для сборки проектов под windows 10. Открываем JNLP к примеру тем же notepad и убедитесь что все ссылки верные (используем порт 5000).

Создаем директорию C:\jnkns, запускаем JNLP и жмем Install as Windows Service. Настройки виндовой ноды на мастере выглядят следующим образом:

Jenkins: Миграция в облако 2