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

Поскольку на мастере хранится некоторое количество данных, то объем хранилища должен быть довольно большим. Вычислительной мощи не нужно, но на всякий случай взяли с запасом. Поскольку приложение работает на 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.

Теперь тоже самое, но для 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. Настройки виндовой ноды на мастере выглядят следующим образом: