Apps: "Jenkins v.2", "Java", "Lighttpd", SSH.
Задача: установить и предварительно настроить сервер "Jenkins", с последующим подключением программных агентов "Jenkins Slaves" на серверах, предназначенных для автоматизированного развёртывания приложений, а также протестировать работу пары простейших CI/CD-сценариев.
Для справки (из "Википедии"): "Jenkins" - программная система с открытым исходным кодом на "Java", предназначенная для обеспечения процесса непрерывной интеграции и доставки программного обеспечения. Ответвлена в 2008 году от проекта "Hudson", принадлежащего компании Oracle, основным его автором - Косукэ Кавагути. Распространяется под лицензией MIT.
По моему, "Jenkins" коряв до невозможности, хотя формально на данный момент в нём реализован наиболее богатый набор поддерживаемых возможностей CI/CD, и это совершенно бесплатно (не считая многих часов, потраченных на то, чтобы вникнуть в зачастую нелепую логику функциональности, накрученной разномастными плагинами).
Если у вас уже есть "Bamboo", "TeamCity", "GitLab" - оставайтесь с ними. Если хочется странного, то можно попробовать "Jenkins".
Установка и первичное конфигурирование"Jenkins".
Установка "Jenkins Server" по сути сводится к разворачиванию WAR (Web Application Resource) и наладке его запуска посредством java-интерпретатора. Это просто делается, но мы ускорим процесс и воспользуемся готовым дистрибутивом, адаптированным для "Linux Debian/Ubuntu".
Подключаем ATP-репозиторий от разработчиков:
# wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | sudo apt-key add -
# echo -e "# APT-repo "Jenkins" for "Debian/Ubuntu"\ndeb https://pkg.jenkins.io/debian-stable binary/" > /etc/apt/sources.list.d/jenkins.list
# echo -e "# APT-repo "Jenkins" for "Debian/Ubuntu"\ndeb https://pkg.jenkins.io/debian-stable binary/" > /etc/apt/sources.list.d/jenkins.list
Инсталлируем последовательно "Java" и "Jenkins":
# apt-get update && apt-get install openjdk-11-jre && apt-get install jenkins
По умолчанию в "Linux Debian/Ubuntu" файлы данных "Jenkins" сохраняются в директории "/var/lib/jenkins".
Сразу после развёртывания из дистрибутива "Jenkins" доступен на всех сетевых интерфейсах через порт TCP:8080. При первом обращении к web-интерфейсу сервиса (например "http://jenkins.example.net:8080") автоматически запустится "мастер настройки". Всё просто. Вначале потребуется указать пароль разблокировки, который выводится инсталлятором в указанный текстовый файл. Следом определимся с набором устанавливаемых плагинов (в примере я привожу минимальный, но достаточно функциональный). Потом создание аккаунта для администратора сервиса и на этом всё:
Getting Started:
Unlock Jenkins:
# cat /var/lib/jenkins/secrets/initialAdminPassword
Customize Jenkins:
Select plugins to install:
....
Build Features:
Credentials Binding (default),
Timestamper (default),
Workspace Cleanup (default),
....
Pipelines and Continuous Delivery:
Pipeline (default),
Pipeline: Stage View (default),
....
Source Code Management:
Git (default),
....
Distributed Builds:
SSH Slaves (default),
Command Agent Launcher Plugin (default),
....
User Management and Security:
Role-based Authorization Strategy (manual),
....
Notifications and Publishing:
Email Extension (default),
....
Create First Admin User:
Username: admin
....
Unlock Jenkins:
# cat /var/lib/jenkins/secrets/initialAdminPassword
Customize Jenkins:
Select plugins to install:
....
Build Features:
Credentials Binding (default),
Timestamper (default),
Workspace Cleanup (default),
....
Pipelines and Continuous Delivery:
Pipeline (default),
Pipeline: Stage View (default),
....
Source Code Management:
Git (default),
....
Distributed Builds:
SSH Slaves (default),
Command Agent Launcher Plugin (default),
....
User Management and Security:
Role-based Authorization Strategy (manual),
....
Notifications and Publishing:
Email Extension (default),
....
Create First Admin User:
Username: admin
....
Разработчики "Jenkins" желают получать статистические сведения использования их приложений, но я сразу после запуска сервиса предпочитаю запрещать высылать какие-либо сведения о наших производственных делах наружу:
Jenkins -> Manage Jenkins -> Configure System:
Usage Statistics (sending anonymous usage statistics): off
Usage Statistics (sending anonymous usage statistics): off
В общем, "Jenkins" уже готов к работе: можно создавать задачи, подключать репозитории, собирать приложения и развёртывать проекты - но производственный сервис на "Java" лучше спрятать за более подходящим для терминирования HTTP-запросов легковесным фронтальным web-сервером, заодно наладив шифрование соединений посредством SSL.
Прячем "Jenkins" за web-прокси.
В качестве фронтального web-сервера используем "Lighttpd", просто ради развлечения - не только же "Nginx" в такой роли выступать:
# apt-get install --no-install-recommends lighttpd
Подготовим место для SSL/TLS-сертификатов, сгенерируем ключ "Diffie-Hellman" и пару "самоподписанных" сертификатов:
# mkdir -p -m 500 /etc/ssl/lighttpd && cd /etc/ssl/lighttpd
# openssl dhparam -out ./dhparam-2048.pem 2048
# openssl req -new -x509 -keyout ./jenkins.example.net-ss.pem -out ./jenkins.example.net-ss.pem -days 365 -nodes
# openssl req -new -x509 -keyout ./www.jenkins.example.net-ss.pem -out ./www.jenkins.example.net-ss.pem -days 365 -nodes
# openssl dhparam -out ./dhparam-2048.pem 2048
# openssl req -new -x509 -keyout ./jenkins.example.net-ss.pem -out ./jenkins.example.net-ss.pem -days 365 -nodes
# openssl req -new -x509 -keyout ./www.jenkins.example.net-ss.pem -out ./www.jenkins.example.net-ss.pem -days 365 -nodes
В отдельном конфигурационном файле описываем поведение "Lighttpd" при обработке поступающих к сайту "Jenkins" запросов:
# vi /etc/lighttpd/conf-available/99-jenkins.example.net.conf
# Активируем модули проксирования и журналирования
server.modules += ( "mod_proxy", "mod_accesslog" )
# Захватываем запросы определённого FQDN
$HTTP["host"] =~ "(^|www\.)jenkins\.example\.net$" {
# Отлавливаем нешифрованные запросы и перенаправляем на HTTPS
$HTTP["scheme"] == "http" {
$HTTP["host"] =~ ".*" {
url.redirect = ( ".*" => "https://%0$0" )
}
}
# Активируем прослушивание порта для HTTPS
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
# Параметры SSL/TLS для сайта по умолчанию
ssl.dh-file = "/etc/ssl/lighttpd/dhparam-2048.pem"
ssl.pemfile = "/etc/ssl/lighttpd/jenkins.example.net-ss.pem"
# Для префикса "www." указываем свой сертификат и перенаправляем на основной FQDN
$HTTP["host"] =~ "^www\.(.*)" {
ssl.pemfile = "/etc/ssl/lighttpd/www.jenkins.example.net-ss.pem"
url.redirect = ( "^/(.*)" => "https://%1/$1" )
}
# Весь рабочий трафик проксируем в "Jenkins"
proxy.server = ( "" => (
( "host" => "127.0.0.1", "port" => 8080 ) )
)
# Сервис важный, так что пишем журнал посещений
accesslog.filename = "/var/log/lighttpd/jenkins.example.net.log"
}
}
server.modules += ( "mod_proxy", "mod_accesslog" )
# Захватываем запросы определённого FQDN
$HTTP["host"] =~ "(^|www\.)jenkins\.example\.net$" {
# Отлавливаем нешифрованные запросы и перенаправляем на HTTPS
$HTTP["scheme"] == "http" {
$HTTP["host"] =~ ".*" {
url.redirect = ( ".*" => "https://%0$0" )
}
}
# Активируем прослушивание порта для HTTPS
$SERVER["socket"] == ":443" {
ssl.engine = "enable"
# Параметры SSL/TLS для сайта по умолчанию
ssl.dh-file = "/etc/ssl/lighttpd/dhparam-2048.pem"
ssl.pemfile = "/etc/ssl/lighttpd/jenkins.example.net-ss.pem"
# Для префикса "www." указываем свой сертификат и перенаправляем на основной FQDN
$HTTP["host"] =~ "^www\.(.*)" {
ssl.pemfile = "/etc/ssl/lighttpd/www.jenkins.example.net-ss.pem"
url.redirect = ( "^/(.*)" => "https://%1/$1" )
}
# Весь рабочий трафик проксируем в "Jenkins"
proxy.server = ( "" => (
( "host" => "127.0.0.1", "port" => 8080 ) )
)
# Сервис важный, так что пишем журнал посещений
accesslog.filename = "/var/log/lighttpd/jenkins.example.net.log"
}
}
Активируем нашу конфигурацию, проверяем её корректность и применяем:
# ln -s /etc/lighttpd/conf-available/99-jenkins.example.net.conf /etc/lighttpd/conf-enabled/99-jenkins.example.net.conf
# lighttpd -t -f /etc/lighttpd/lighttpd.conf
# systemctl restart lighttpd
# systemctl status lighttpd
# lighttpd -t -f /etc/lighttpd/lighttpd.conf
# systemctl restart lighttpd
# systemctl status lighttpd
В конфигурационном файле запуска "Jenkins" задаём параметры, указывающие работать в стеке TCPv4, принимать запросы только на "локальной петле" и отключить широковещательные запросы поиска соседей:
# vi /etc/default/jenkins
JAVA_ARGS="... -Djava.net.preferIPv4Stack=true -Dhudson.udp=-1"
....
JENKINS_ARGS="... --httpListenAddress=127.0.0.1"
....
JENKINS_ARGS="... --httpListenAddress=127.0.0.1"
Перезапускаем "Jenkins":
# /etc/init.d/jenkins restart
# systemctl status jenkins
# systemctl status jenkins
Проверяем, все ли необходимые сетевые порты прослушиваются:
# netstat -apn | grep -i listen | grep -i "^tcp" | grep -iE "lighttpd|java"
tcp ... 127.0.0.1:8080 0.0.0.0:* LISTEN .../java
tcp ... 0.0.0.0:80 0.0.0.0:* LISTEN .../lighttpd
tcp ... 0.0.0.0:443 0.0.0.0:* LISTEN .../lighttpd
tcp ... 0.0.0.0:80 0.0.0.0:* LISTEN .../lighttpd
tcp ... 0.0.0.0:443 0.0.0.0:* LISTEN .../lighttpd
Если вынос за web-прокси делается после конфигурирования "Jenkins", то изменяем фронтальный FQDN:
Jenkins -> Manage Jenkins -> Configure System:
Jenkins Location:
Jenkins URL: https://jenkins.example.net/
Jenkins Location:
Jenkins URL: https://jenkins.example.net/
Аутентификация через внешние сервисы вроде LDAP/AD.
Главная проблема аутентификации через LDAP (и вообще, любые внешние сервисы) в том, что "Jenkins" не умеет (2021 год - 10 лет продукту!) сочетать несколько источников аутентификации, и при переключении на LDAP, например, локальные пользователи (вроде "admin") перестают действовать, что блокирует вход в web-сервис напрочь в случае недоступности внешнего сервиса LDAP. Это удивляет. По этой причине я не пользуюсь в "Jenkins" ничем, кроме его встроенной "базы пользователей".
Включение режима ролевого разграничения доступа.
В конфигурации по умолчанию сразу после развёртывания из дистрибутива "Jenkins" позволяет всем и всё. Для тестового стенда или команды их пары-тройки разработчиков это удобно, но на предприятии с десятками сотрудников, группами работающих над не связанными между собой программными продуктами, тестируемыми и публикуемыми на множестве серверов, доступ к ресурсам без ограничений неминуемо приведёт к проблемам - кто-нибудь случайно нажмёт не ту кнопку или, запутавшись, изменит не свою конфигурацию.
Задача разграничения доступа к ресурсам пользователям или группам таковых отчасти решается плагином "Role-based Authorization Strategy". Считая, что плагин мы установили в процессе развёртывания "Jenkins", активируем режим доступа, им предоставляемый:
Jenkins -> Manage Jenkins -> Configure Global Security:
Enable security: on
Access Control:
Security Realm: Jenkins’ own user database
Authorization: Role-Based Strategy
Access Control for Builds:
Strategy: Run as SYSTEM
Enable security: on
Access Control:
Security Realm: Jenkins’ own user database
Authorization: Role-Based Strategy
Access Control for Builds:
Strategy: Run as SYSTEM
Первым делом заведём глобальную роль, предоставляющую доступ на чтение корня конфигурации и представлений - без этой минимальной роли пользователь ничего не увидит вообще:
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Manage Roles -> Global roles -> Add:
Role: everyone
Overall: Read
View: Read
Role: everyone
Overall: Read
View: Read
Для корректной работы описываемого далее функционала мы должны принять за данность, что объекты конфигурации "Jenkins" будут именоваться по определённой схеме. Например:
Items:
project0
project0_site0.example.net
project0_site1.example.net
Credentials:
....
Nodes:
node0.example.net
node1.example.net
....
project0
project0_site0.example.net
project0_site1.example.net
Credentials:
....
Nodes:
node0.example.net
node1.example.net
....
Именуя объекты (задачи) таким образом, логично потом будет их группировать представлениями:
Jenkins -> New View -> List View:
View name: project0
Job Filters:
Regular expression: (?i)project0_.*
View name: project0
Job Filters:
Regular expression: (?i)project0_.*
Следуя вышеприведённой схеме именования достаточно просто наладить выделение разрешений доступа к иерархии объектов, с вычленением набора таковых из общей массы по регулярному выражению, применяемому к имени.
Заведём роль, дающую полный доступ к набору объектов (группе задач, проекту) - для администратора:
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Manage Roles -> Item roles -> Add:
Role: project0_adm
Patern: (?i)project0_.*
All: *
Role: project0_adm
Patern: (?i)project0_.*
All: *
Заведём роль, предоставляющую возможность просматривать и запускать уже настроенные процедуры - для разработчика:
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Manage Roles -> Item roles -> Add:
Role: project0_dev
Patern: (?i)project0_.*
Job: Build, Cancel, Read
Run: Delete, Replay, Update
Role: project0_dev
Patern: (?i)project0_.*
Job: Build, Cancel, Read
Run: Delete, Replay, Update
Очевидно, что круги доступа для ролей можно очерчивать более детально, уточняя регулярное выражение.
Теперь создадим учётную запись для разработчика проекта:
Jenkins -> Manage Jenkins -> Manage Users -> Create User:
Username: developer0
....
Username: developer0
....
Наделим учётную запись разработчика соответствующими ему ролями:
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Assign Roles -> Global roles:
developer0: everyone
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Assign Roles -> Item roles:
developer0: project0_developer
developer0: everyone
Jenkins -> Manage Jenkins -> Manage and Assign Roles -> Assign Roles -> Item roles:
developer0: project0_developer
О несанкционированном доступе к данным "Jenkins Server".
Не могу не отметить, что у "Jenkins" в борту практически незакрываемая дыра с неконтролируемым доступом к конфигурациям и отчасти данным проектов. Например, простейшая команда "ls /var/lib/jenkins" в блоке "Build" или "Pipeline" любой исполняемой на "Jenkins Server" задачи покажет листинг корневой директории сервиса.
Самый распространённый и рекомендуемый плагин "Role-Based Authorization Strategy" не закрывает всех возможных с несанкционированным доступом проблем. Дело в том, что этот плагин накладывает ограничения только на функциональность web-интерфейса, а сборка и развёртывание таковых осуществляется в контексте единого пользователя несущей операционной системы "jenkins", что позволяет с лёгкостью получить доступ к файлам других проектов "под капотом" web-интерфейса.
Опосредованно проблему бесконтрольного доступа к файлам конфигурации сервера можно было бы снять с повестки запретом запуска задач непосредственно на нём, вынеся все работы на "ноды", считая "Jenkins Server" лишь управляющим инструментом. Для этого вроде как предназначается плагин "Job Restrictions". И да, применение в конфигурации "ноды" ограничения вроде "Restrict jobs execution at node: Regular Expression: (?!).*" действительно приведёт к тому, что задания будут направляться на первую доступную "ноду" или оставаться в ожидании таковой, но на самом сервере "Jenkins" не отработают. Однако, действие плагина "Job Restrictions" не распространяется на задачи типа "Pipeline", которая вначале всегда стартует на "Jenkins Server" и только после этого по желанию может быть отправлена на произвольную "ноду" - а что за CI/CD без "pipeline"?
Кое-как задачу типа "Pipeline" можно загнать с помощью корявого функционала плагина "Node and Label parameter" на произвольную "ноду" сразу, без предварительного запуска на "Jenkins Server", но это делается в конфигурации самой задачи, что лишает действие смысла.
Кроме вышеприведённого рекомендуют ещё воспользоваться плагином "Authorize Project", который вроде как позволяет запускать задачи в контексте произвольного пользователя - но у меня это вульгарно не работало - чтобы я ни делал, задачи типа "Pipeline" стартовали или от имени пользователя "jenkins" или "anonymous".
Ещё больше возможностей разграничения доступа предоставляет плагин "Job and Node ownership", дополняющий и расширяющий функционал трёх других: "Role-Based Authorization Strategy", "Job Restrictions" и "Authorize Project" - но правда, накрученное месиво из плагинов и разбросанных в самых неожиданных местах параметров простейшую задачу глобальной блокировки доступа к данным сервиса так и не решает.
Печально, что в свете обозначенной проблемы "Jenkins" небезопасно применять для поддержания CI/CD проектов сразу нескольких команд. Можно позиционировать этот программный продукт как подсобный инструмент для решения конкретных задач одного проекта, где все в курсе всего и ничего друг от друга не скрывают. В конце концов ничто не мешает запускать на каждый проект по серверу "Jenkins" - это действительно несложно.
Подключение "Jenkins Slaves".
Сам сервер "Jenkins" полезен для CI-процедур, но нам требуется доставка приложений на площадки тестирования и публикации (CD-процедуры; Continuous Delivery & Deployment), для чего предназначается удобная функциональность "Jenkins Slaves" - запускаемые в целевых системах агенты исполнения заданий.
Прежде всего генерируем SSH-ключи, используемые впоследствии только в "Jenkins Server" при подключении к "Jenkins Slave" (для простоты сохраняем их в произвольной директории):
# mkdir -p -m 700 /usr/local/etc/jenkins/.ssh/node-one.example.net && cd /usr/local/etc/jenkins/.ssh/node-one.example.net && ssh-keygen -q -t rsa -b 2048 -f ./id_rsa -P "" -C "User \"jenkins\" for node \"node-one.example.net\"."
В "Jenkins v.2" для хранения частных переменных окружения, паролей, ключей и сертификатов сделали специальную централизованную подсистему "Credentials Provider". Прежде всего добавляем подраздел для хранения "ключей", доступных только при явном указании на связь с указанным именем области:
Jenkins -> Credentials -> System -> Add domain:
Domain Name: node-one.example.net
Domain Name: node-one.example.net
Добавляем ssh-ключи в область домена подключаемой "ноды" ("закрытый ключ" достаём из файла "./id_rsa", полученного в примере выше):
Jenkins -> Credentials -> System -> node-one.example.net -> Add Credentials:
Kind: SSH Username with private key
Scope: System (Jenkins and nodes only)
Description: User for node "node-one.example.net"
Username: jenkins
Private Key:
Enter directly:
Key: -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3nh9A9eCwl03...
....
Kind: SSH Username with private key
Scope: System (Jenkins and nodes only)
Description: User for node "node-one.example.net"
Username: jenkins
Private Key:
Enter directly:
Key: -----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA3nh9A9eCwl03...
....
На стороне "Jenkins Slave" устанавливаем требуемую среду исполнения "Java" и создаём пользователя, в контексте которого будут исполняться задачи и обеспечиваем возможность подключения с его SSH-ключём:
root@node-one:/# apt-get update && apt-get install openjdk-11-jre
root@node-one:/# groupadd --force --system jenkins
root@node-one:/# useradd --system --shell /bin/bash --create-home --home-dir /var/lib/jenkins --gid jenkins jenkins
root@node-one:/# groupadd --force --system jenkins
root@node-one:/# useradd --system --shell /bin/bash --create-home --home-dir /var/lib/jenkins --gid jenkins jenkins
На стороне "Jenkins Slave" добавляем "открытый ключ" из пары созданных выше в перечень разрешённых к аутентификации для пользователя "jenkins":
root@node-one:/# mkdir -p -m 700 /var/lib/jenkins/.ssh && chown -R jenkins:jenkins /var/lib/jenkins/.ssh
root@node-one:/# vi /var/lib/jenkins/.ssh/authorized_keys
root@node-one:/# vi /var/lib/jenkins/.ssh/authorized_keys
ssh-rsa AAA...cA5 User "jenkins" for node "node-one.example.net".
Теперь настроим подключение "Jenkins Server" к "Jenkins Slave":
Jenkins -> Manage Jenkins -> Manage Nodes -> Permanent Agent:
Node name: node-one.example.net
Remote root directory: /var/lib/jenkins
Labels: shell node-one master
Usage: Only build jobs with label expressions matching this node
Launch method: Launch agent via SSH
Host: node-one.example.net
Credentials: jenkins (User for node "node-one.example.net")
Host Key Verification Strategy: Manuality trusted key Verification Strategy
Availability: Keep this agent online
Node name: node-one.example.net
Remote root directory: /var/lib/jenkins
Labels: shell node-one master
Usage: Only build jobs with label expressions matching this node
Launch method: Launch agent via SSH
Host: node-one.example.net
Credentials: jenkins (User for node "node-one.example.net")
Host Key Verification Strategy: Manuality trusted key Verification Strategy
Availability: Keep this agent online
В соответствии с выбранным методом дополнительной проверки подлинности соединения SSH-подключение к системе "Jenkins Slave" не состоится до тех пор, пока мы не подтвердим доверие к полученному от удалённого сетевого узла "fingerprint"-у:
Jenkins -> Manage Jenkins -> Manage Nodes -> node-one.example.net -> Trust SSH Host Key:
Do you want to trust the SSH Host Key with fingerprint ... for future connections to this host? yes
Do you want to trust the SSH Host Key with fingerprint ... for future connections to this host? yes
Как вариант, можно было предварительно подключиться к "Jenkins Slave", используя известные нам ssh-ключи (например командой "sudo -u jenkins ssh -i /usr/local/etc/jenkins/.ssh/node-one.example.net/id_rsa jenkins@node-one.example.net", в результате чего на стороне "Jenkins Server" в файле "/var/lib/jenkins/.ssh/known_hosts" добавилась бы строка с типом используемого для шифрования алгоритмом и открытым ключём "ноды", что позволило бы использовать защиту от атак "man-in-the-middle" для соединения сервера с клиентом типа "Known hosts file Verification Strategy" - но это несколько менее удобно, так как требует действий в командной строке несущего сервера.
По звершению настройки подключения к "Jenkins Slave" велим запустить агента, после чего "Jenkins Server" соединиться с целевой "нодой" по SSH, загрузит туда посредством SFTP исполняемый файл агента "remoting.jar" и запустит его в режиме ожидания команд.
Выдержка из журнала успешного подключения:
....
... [SSH] Opening SSH connection to node-one.example.net:22.
... [SSH] SSH host key matches key seen previously for this host. Connection will be allowed.
... [SSH] Authentication successful.
... [SSH] The remote user's environment is:
BASH=/bin/bash
....
HOME=/var/lib/jenkins
....
USER=jenkins
....
... [SSH] java -version returned 11.0.5.
... [SSH] Starting sftp client.
... [SSH] Copying latest remoting.jar...
... [SSH] Copied 876,668 bytes.
... [SSH] Starting agent process: cd "/var/lib/jenkins" && java -jar remoting.jar -workDir /var/lib/jenkins -jar-cache /var/lib/jenkins/remoting/jarCache
....
Agent successfully connected and online
... [SSH] Opening SSH connection to node-one.example.net:22.
... [SSH] SSH host key matches key seen previously for this host. Connection will be allowed.
... [SSH] Authentication successful.
... [SSH] The remote user's environment is:
BASH=/bin/bash
....
HOME=/var/lib/jenkins
....
USER=jenkins
....
... [SSH] java -version returned 11.0.5.
... [SSH] Starting sftp client.
... [SSH] Copying latest remoting.jar...
... [SSH] Copied 876,668 bytes.
... [SSH] Starting agent process: cd "/var/lib/jenkins" && java -jar remoting.jar -workDir /var/lib/jenkins -jar-cache /var/lib/jenkins/remoting/jarCache
....
Agent successfully connected and online
Создание задачи типа "Freestyle", исполняемой на "ноде".
New Item (Job):
Item name: project0_site0.example.net
Freestyle project:
General:
Restrict where this project can be run:
Label Expression: shell && node-one && master
....
Source Code Management:
Git:
Repositories:
ssh://git-user@git.example.net/~/repo-test.git
Credentials: git-user
Branches to build: */master
....
Build Environment:
Add timestamps to the Console Output: on
....
Build:
Execute shell:
Command:
echo "123" >> ${WORKSPACE}/123
ls -l ${WORKSPACE}
echo 'Show'
....
Post-build Actions:
....
Item name: project0_site0.example.net
Freestyle project:
General:
Restrict where this project can be run:
Label Expression: shell && node-one && master
....
Source Code Management:
Git:
Repositories:
ssh://git-user@git.example.net/~/repo-test.git
Credentials: git-user
Branches to build: */master
....
Build Environment:
Add timestamps to the Console Output: on
....
Build:
Execute shell:
Command:
echo "123" >> ${WORKSPACE}/123
ls -l ${WORKSPACE}
echo 'Show'
....
Post-build Actions:
....
Создание задачи типа "Pipeline", исполняемой на "ноде".
В отличии от задачи типа "Freestyle", здесь мы всю автоматизацию и организацию очерёдности процедур сборки и развёртывания возлагаем на подсистему "pipeline", руководствующуюся описанием в специальном формате. В "Jenkins" возможны два варианта применения инструкций: их можно расписать прямо в задаче или скачать из репозитория (по умолчанию это текстовый файл с именем "Jenkinsfile").
Для простых проектов возможно нет смысла возиться с отдельным файлом, размещаемым во внешнем репозитории, а проще описать "pipeline" (формат "Declarative") прямо в теле задачи "Jenkins":
New Item (Job):
Item name: project0_site1.example.net
Pipeline project:
....
Pipeline:
Definition: Pipeline script
Script:
// Jenkins Declarative Pipeline
pipeline {
agent {label "master && one"}
stages {
stage('Git') {
steps {
git url: 'ssh://git-user@git.example.net/~/repo-test.git',
credentialsId: '0c0...f60',
branch: 'master'
}
}
stage('Show') {
steps {
sh "echo '123' >> ${WORKSPACE}/123"
sh "ls -l ${WORKSPACE}"
echo 'Show'
}
}
}
}
....
Item name: project0_site1.example.net
Pipeline project:
....
Pipeline:
Definition: Pipeline script
Script:
// Jenkins Declarative Pipeline
pipeline {
agent {label "master && one"}
stages {
stage('Git') {
steps {
git url: 'ssh://git-user@git.example.net/~/repo-test.git',
credentialsId: '0c0...f60',
branch: 'master'
}
}
stage('Show') {
steps {
sh "echo '123' >> ${WORKSPACE}/123"
sh "ls -l ${WORKSPACE}"
echo 'Show'
}
}
}
}
....
Если проект большой, или в команде заведено строгое правило хранить абсолютно все конфигурации и исходный код в репозитории, то укажем в задаче "Jenkins" источник получения файла описания "pipeline":
New Item (Job):
Item name: project0_site1.example.net
Pipeline project:
....
Pipeline:
Definition: Pipeline script from SCM
SCM: Git
Repositories:
ssh://git-user@git.example.net/~/repo-test.git
Credentials: git-user
Branches to build: */master
Script Path: Jenkinsfile
Item name: project0_site1.example.net
Pipeline project:
....
Pipeline:
Definition: Pipeline script from SCM
SCM: Git
Repositories:
ssh://git-user@git.example.net/~/repo-test.git
Credentials: git-user
Branches to build: */master
Script Path: Jenkinsfile
Очевидно, что содержимое выделенного из конфигурации задачи описания "pipeline" полностью переносится в "Jenkinsfile":
$ vi ~/repo-test.git/Jenkinsfile
// Jenkins Declarative Pipeline
pipeline {
agent {label "master && one"}
stages {
stage('Git') {
steps {
git url: 'ssh://git-user@git.example.net/~/repo-test.git',
credentialsId: '0c0...f60',
branch: 'master'
}
}
stage('Show') {
steps {
sh '''
echo '123' >> ${WORKSPACE}/123
ls -l ${WORKSPACE}
'''
echo 'Show'
}
}
}
}
pipeline {
agent {label "master && one"}
stages {
stage('Git') {
steps {
git url: 'ssh://git-user@git.example.net/~/repo-test.git',
credentialsId: '0c0...f60',
branch: 'master'
}
}
stage('Show') {
steps {
sh '''
echo '123' >> ${WORKSPACE}/123
ls -l ${WORKSPACE}
'''
echo 'Show'
}
}
}
}
Резервное копирование "Jenkins".
Как выше уже упоминалось, все файлы "Jenkins", в том числе и конфигурационные, по умолчанию размещаются в директории "/var/lib/jenkins". Для резервного копирования достаточно сохранить их всех, за исключением временных и пользовательских данных (на вскидку это директории ".cache", ".java", "caches", "logs", "updates", "userContent" и "workflow-libs").