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