Apps: "Jenkins", "Java 8/11", "Nginx", "Let`s Encrypt", "Docker", "Docker Compose".
Задача: развернуть несколько инстансов "Jenkins" в среде контейнеризации "Docker" с целью изоляции процедур CI/CD раздельно работающих команд разработки.
Прежде всего надо бы рассказать, отчего такая задача - изоляция команд разработки путём запуска для них отдельных CI/CD-серверов - возникла. Дело в том, что у "Jenkins", который вырос из маленького подручного инструмента, вообще нет механизмов ограничения доступа к его же внутренностям - практически любой полноценный пользователь web-интерфейса может получить доступ к другим проектам на этом сервере, даже несмотря на то, явно ему туда ходить не разрешено или даже запрещено плагинами. Об этом я ранее уже рассказывал в предыдущей инструкции по запуску одного экземпляра "Jenkins". Ввиду невозможности изолировать проектные группы в рамках одного инстанса "Jenkins" приходится запускать по экземпляру "Jenkins" для каждой проектной группы по отдельности. Это несложно, как станет понятно далее.
Перед тем, как перейти к дальнейшим инсталляционным работам важно учесть следующий нюанс. Работа "Jenkins" поддерживается только в среде исполнения "Java 8" и "Java 11 (LTS)" (причём не основной ветви "Sun/Oracle Java", а не бесплатном "OpenJDK JVM"). "Jenkins" запустится и будет работать внешне одинаково, с точки зрения конечного пользователя, на любой из этих двух версий "Java". Но из "Java 11" вырезана поддержка устаревшей технологии "WebStart (JNLP)" посредством которой быстро и непринуждённо подключаются к серверу "Jenkins" агенты исполнения "Slaves" в среде операционных систем "Microsoft Windows" - прямо из браузера, в пару щелчков мыши. Если "Jenkins" запустить в среде "Java 11", то подключение агентов на windows-системах придётся осуществлять так же, как и для linux-систем - через сеансы SSH-подключений. Это влечёт за собой необходимость устанавливать на windows-системах службы "OpenSSH". Таким образом, мы стоим перед выбором: работать на старье, но проще подключать агентов исполнения, или идти в ногу с прогрессом, усложняя себе задачу предварительной подготовки к CI/CD-процедурам на операционных системах "Microsoft Windows". Здесь рассматривается инсталляция "Jenkins" в среде "Java 11".
Добавлю ещё немного рассуждений об инсталяции. "Jenkins" представляет собой монолитное приложение, запускаемое как java-сервлет в любом современном сервере приложений, вроде "Jetty", "Apache Tomcat", "GlassFish" или "WildFly". Ещё три года назад я бы собрал из распространяемого командой разработчиков "Jenkins" WAR-файла и "Tomcat" свой docker-образ. Однако сейчас вся эа работу уже проделана людьми гораздо опытнее меня и на "Docker Hub" нас ждёт всегда актуальные сборки "Jenkins in Docker", за качество сборки которых волноваться не приходится, судя по простоте и аккуратности "Dockerfile". Процесс развёртывания и конфигурирования разработчиками "Jenkins" хорошо расписан (здесь и здесь, с вариациями), так что просто сделаем это.
Последовательность дальнейших действий такова:
1. Подготовка системного окружения (отдельная инструкция);
2. Установка системы контейнеризации "Docker" (отдельная инструкция);
3. Подготовка несущей файловой структуры для приложений "Jenkins";
4. Модификация официального docker-образа "Jenkins";
5. Подготовка среды и запрос "Let`s Encrypt" SSL-сертификатов;
6. Подготовка конфигурации для фронтального web-сервера "Nginx";
7. Наладка запуска посредством "Docker Compose" и "Systemd";
8. Конфигурирование инстансов web-приложения "Jenkins".
2. Установка системы контейнеризации "Docker" (отдельная инструкция);
3. Подготовка несущей файловой структуры для приложений "Jenkins";
4. Модификация официального docker-образа "Jenkins";
5. Подготовка среды и запрос "Let`s Encrypt" SSL-сертификатов;
6. Подготовка конфигурации для фронтального web-сервера "Nginx";
7. Наладка запуска посредством "Docker Compose" и "Systemd";
8. Конфигурирование инстансов web-приложения "Jenkins".
Подготовка несущей файловой структуры для приложений "Jenkins".
Создаём корневые директории для каждого инстанса "Jenkins" по отдельности, а также директорию для журналов событий docker-контейнеров (непосредственно у каждого инстанса будут ещё свои журналы, управлять которыми удобнее уже в контексте приложений):
# mkdir -p /var/opt/jenkins/group0
# mkdir -p /var/opt/jenkins/group1
....
# mkdir -p /var/opt/jenkins/groupX
# mkdir -p /var/opt/jenkins/logs
# mkdir -p /var/opt/jenkins/group1
....
# mkdir -p /var/opt/jenkins/groupX
# mkdir -p /var/opt/jenkins/logs
Из Dockerfile официального образа "Jenkins" легко узнать, что процесс внутри него запускается в контексте пользователя "jenkins" с UID/GID:1000. Это неудобно, так как в несущей системе наверняка уже имеется пользователь с таким UID/GID и не получится его задать новому пользователю "jenkins" в несущей системе для очевидного отображения назначаемых прав доступа к монтируемым внутрь файлам. Мне удобнее выбрать произвольный UID/GID для пользователя "jenkins", который позже можно будет задать и в docker-контейнере:
# groupadd --system --gid 500 jenkins
# useradd --system --home-dir /var/opt/jenkins --shell /bin/false --gid jenkins --uid 500 jenkins
# useradd --system --home-dir /var/opt/jenkins --shell /bin/false --gid jenkins --uid 500 jenkins
# chown -R jenkins:jenkins /var/opt/jenkins
# chmod -R o-rwx /var/opt/jenkins
# chmod -R o-rwx /var/opt/jenkins
Модификация официального docker-образа "Jenkins".
Как я выше отметил, по умолчанию "Jenkins" запускается в контексте пользователя с неудобными для эксплуатации UID:GID, и я предпочитаю на основе официального docker-образа делать свой:
Подготовим место в файловой системе для задач модификации dockder-образов:
# mkdir -p /usr/local/etc/devops/images/jenkins
# cd /usr/local/etc/devops/images/jenkins
# cd /usr/local/etc/devops/images/jenkins
Готовим описание docker-образа, предназначенного для модификации UID:GID, с которыми запускается "Jenkins":
# vi ./Dockerfile-jenkins-lts-jdk11-chuid
FROM jenkins/jenkins:lts-jdk11
LABEL maintainer="NSU, Andrey Narozhniy"
# set file paths used by default
ENV JENKINS_HOME /var/jenkins_home
ENV REF /usr/share/jenkins/ref
# use superuser to change configuration
USER root
# overriding file permissions
RUN mkdir -p "${JENKINS_HOME}" "${REF}" \
&& usermod --uid 500 jenkins && groupmod --gid 500 jenkins \
&& usermod -d "${JENKINS_HOME}" jenkins \
&& chown -R jenkins:jenkins "${JENKINS_HOME}" "${REF}"
# restore ENTRYPOINT to startup as jenkins user
VOLUME "${JENKINS_HOME}"
USER jenkins
LABEL maintainer="NSU, Andrey Narozhniy"
# set file paths used by default
ENV JENKINS_HOME /var/jenkins_home
ENV REF /usr/share/jenkins/ref
# use superuser to change configuration
USER root
# overriding file permissions
RUN mkdir -p "${JENKINS_HOME}" "${REF}" \
&& usermod --uid 500 jenkins && groupmod --gid 500 jenkins \
&& usermod -d "${JENKINS_HOME}" jenkins \
&& chown -R jenkins:jenkins "${JENKINS_HOME}" "${REF}"
# restore ENTRYPOINT to startup as jenkins user
VOLUME "${JENKINS_HOME}"
USER jenkins
Собираем "образ", явно отключив кеширование на основе слоёв от предыдущих попыток:
# docker build --no-cache --tag selfmade:jenkins-lts-jdk11-chuid --file ./Dockerfile-jenkins-lts-jdk11-chuid . >> ./build-jenkins-lts-jdk11-chuid.log
После успешной сборки можно видеть, что с перечне docker-образов с "Jenkins" появился и наш, кастомный, по размеру не практически отличающийся от исходного:
# docker images
REPOSITORY TAG IMAGE ID ... SIZE
selfmade jenkins-lts-jdk11-chuid 6576a6f378b3 ... 681MB
jenkins/jenkins lts-jdk11 f50504bf2e45 ... 681MB
selfmade jenkins-lts-jdk11-chuid 6576a6f378b3 ... 681MB
jenkins/jenkins lts-jdk11 f50504bf2e45 ... 681MB
Ради интереса можно запустить docker-контейнер на основе нашего "образа", чтобы убедится, что web-приложение успешно развернётся в желаемом нами контексте:
# mkdir -p /tmp/jenkins && chown -R jenkins:jenkins /tmp/jenkins
# docker run --rm --name test-jenkins -u jenkins -v /tmp/jenkins:/var/jenkins_home selfmade:jenkins-lts-jdk11-chuid
# docker run --rm --name test-jenkins -u jenkins -v /tmp/jenkins:/var/jenkins_home selfmade:jenkins-lts-jdk11-chuid
Если зайти внутрь тестового контейнера, то можно убедится в успешной замене UID:GID целевого пользователя:
# docker exec -ti test-jenkins bash
jenkins@test-jenkins:/$ id
jenkins@test-jenkins:/$ id
uid=500(jenkins) gid=500(jenkins) groups=500(jenkins)
Также при взгляде на контейнер с несущей операционной системы видно корректное сопоставление имени внешнего и внутреннего пользователя, в контексте которого исполняется приложение:
# docker top test-jenkins
UID ... CMD
jenkins ... java -Duser.home=/var/jenkins_home ... -jar /usr/share/jenkins/jenkins.war
jenkins ... java -Duser.home=/var/jenkins_home ... -jar /usr/share/jenkins/jenkins.war
Подготовка среды и запрос "Let`s Encrypt" SSL-сертификатов.
Если имеется wildcard-сертификат, который используется для терминирования SSL/TLS-подключений, то этот этап можно пропустить. Для мелких же проектов наладка работы с автоматически запрашиваемыми и продлеваемыми сертификатами от "Let`s Encrypt" в последние годы стала стандартом.
Создаём в файловой системе несущего сервера директорию для сертификатов и генерируем DH-файл:
# mkdir -p /var/opt/letsencrypt
# openssl dhparam -out /var/opt/letsencrypt/dhparam-2048.pem 2048
# chown -R root:root /var/opt/letsencrypt && chmod -R o-rwx /var/opt/letsencrypt
# openssl dhparam -out /var/opt/letsencrypt/dhparam-2048.pem 2048
# chown -R root:root /var/opt/letsencrypt && chmod -R o-rwx /var/opt/letsencrypt
Подготавливаем структуру для агента "certbot", запрашивающего и продлевающего сертификаты:
# mkdir -p /var/opt/certbot/nginx/etc/conf.d
# mkdir -p /var/opt/certbot/logs
# mkdir -p /var/opt/certbot/webroot/.well-known/acme-challenge
# mkdir -p /var/opt/certbot/logs
# mkdir -p /var/opt/certbot/webroot/.well-known/acme-challenge
Внутри docker-контейнера "Nginx" запущен в контексте пользователя с UID:101 - просто даём такому UID право чтения из директории "webroot" (процесс запроса и подтверждения владения web-ресурсом уже расписан на этом сайте):
# chown -R 101:root /var/opt/certbot && chmod -R o-rwx /var/opt/certbot
Опишем крайне простую конфигурацию сайта, предназначенного лишь для выдачи ключа валидации по запросу со стороны сервиса "Let`s Encrypt":
# vi /var/opt/certbot/nginx/etc/conf.d/group0.jenkins.example.net.conf
server {
listen 80;
listen [::]:80;
server_name group0.jenkins.example.net
group1.jenkins.example.net
groupX.jenkins.example.net;
server_tokens off;
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
}
listen 80;
listen [::]:80;
server_name group0.jenkins.example.net
group1.jenkins.example.net
groupX.jenkins.example.net;
server_tokens off;
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
}
Считая, что никаких других web-сервисов на несущем сервере ещё нет, запускаем docker-контейнер с "Nginx", только лишь для первичного запроса SSL-сертификатов:
# docker run -d --rm --name letsencrypt-nginx -p 80:80 -v /var/opt/certbot/nginx/etc/conf.d:/etc/nginx/conf.d -v /var/opt/certbot/webroot:/var/www/letsencrypt nginx:latest
Воспользовавшись готовой сборкой docker-образа агента "certbot" запрашиваем SSL-сертификаты у "Let`s Encrypt", пачкой для всех используемых инстансами "Jenkins" FQDN:
# docker run --rm --name letsencrypt-certbot -v /var/opt/certbot/webroot:/var/www/letsencrypt -v /var/opt/letsencrypt:/etc/letsencrypt certbot/certbot certonly --webroot --noninteractive --agree-tos --no-eff-email --register-unsafely-without-email -w /var/www/letsencrypt -d group0.jenkins.example.net group1.jenkins.example.net groupX.jenkins.example.net >> /var/opt/certbot/logs/docker-output-certbot.log 2>&1
После успешного получения SSL-сертификатов останавливаем вспомогательный инстанс "Nginx":
# docker stop letsencrypt-nginx
Автоматизация продления SSL-сертфикатов от "Let`s Encrypt".
Учитывая то, что в рабочем режиме фронтальный web-сервер всегда запущен, достаточно будет лишь наладить регулярный запуск агента "certbot" (в примере раз в неделю, каждый Вторник, в полночь):
# vi /etc/crontab
....
# Certificates renew using Let`s Encrypt "Certbot client" in Docker
0 0 * * 2 root docker run --rm --name letsencrypt-certbot -v /var/opt/certbot/webroot:/var/www/letsencrypt -v /var/opt/letsencrypt:/etc/letsencrypt certbot/certbot certonly --webroot --noninteractive --agree-tos --no-eff-email --register-unsafely-without-email -w /var/www/letsencrypt -d group0.jenkins.example.net group1.jenkins.example.net groupX.jenkins.example.net >> /var/opt/certbot/logs/docker-output-certbot.log 2>&1 &
# Certificates renew using Let`s Encrypt "Certbot client" in Docker
0 0 * * 2 root docker run --rm --name letsencrypt-certbot -v /var/opt/certbot/webroot:/var/www/letsencrypt -v /var/opt/letsencrypt:/etc/letsencrypt certbot/certbot certonly --webroot --noninteractive --agree-tos --no-eff-email --register-unsafely-without-email -w /var/www/letsencrypt -d group0.jenkins.example.net group1.jenkins.example.net groupX.jenkins.example.net >> /var/opt/certbot/logs/docker-output-certbot.log 2>&1 &
Подготовка конфигурации для фронтального web-сервера "Nginx".
Создадим в файловой системе несущего сервера структуры для конфигурации "Nginx":
# mkdir -p /var/opt/nginx/etc/conf.d
# mkdir -p /var/opt/nginx/logs
# mkdir -p /var/opt/nginx/logs
Конфигурация сайтов принимающего подключения пользователей web-сервера "Nginx" проста и сводится к описанию параметров "проксирования" всех запросов web-сервису, запущенному внутри docker-контейнера:
# vi /var/opt/nginx/etc/conf.d/group0.jenkins.example.net.conf
# Обслуживание запросов "Let`s Encrypt" и редирект остальных
server {
listen 80;
listen [::]:80;
server_name group0.jenkins.example.net;
server_tokens off;
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
location / {
return 307 https://$host$request_uri;
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name group0.jenkins.example.net;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/group0.jenkins.example.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/group0.jenkins.example.net/privkey.pem;
ssl_dhparam /etc/letsencrypt/dhparam-2048.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_buffer_size 8k;
ssl_stapling on;
ssl_stapling_verify on;
proxy_connect_timeout 3s; # (default: 60s)
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://jenkins-group0:8080;
proxy_redirect off;
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
....
server_name group1.jenkins.example.net;
....
location / {
....
proxy_pass http://jenkins-group1:8080;
....
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
....
server_name groupX.jenkins.example.net;
....
location / {
....
proxy_pass http://jenkins-groupX:8080;
....
}
}
server {
listen 80;
listen [::]:80;
server_name group0.jenkins.example.net;
server_tokens off;
location /.well-known/acme-challenge {
root /var/www/letsencrypt;
}
location / {
return 307 https://$host$request_uri;
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name group0.jenkins.example.net;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/group0.jenkins.example.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/group0.jenkins.example.net/privkey.pem;
ssl_dhparam /etc/letsencrypt/dhparam-2048.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_buffer_size 8k;
ssl_stapling on;
ssl_stapling_verify on;
proxy_connect_timeout 3s; # (default: 60s)
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://jenkins-group0:8080;
proxy_redirect off;
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
....
server_name group1.jenkins.example.net;
....
location / {
....
proxy_pass http://jenkins-group1:8080;
....
}
}
# Приём и доставка запросов соответствующему инстансу "Jenkins"
server {
....
server_name groupX.jenkins.example.net;
....
location / {
....
proxy_pass http://jenkins-groupX:8080;
....
}
}
По умолчанию "Nginx" внутри docker-контейнера стартует в контексте суперпользователя "root", так что убираем доступ к его конфигурации для всех остальных:
# chown -R root:root /var/opt/nginx
# chmod -R go-rwx /var/opt/nginx
# chmod -R go-rwx /var/opt/nginx
Наладка запуска посредством "Docker Compose".
Создаём директорию для размещения конфигурационных файлов "Docker Compose":
# mkdir -p /usr/local/etc/compose
# cd /usr/local/etc/compose
# cd /usr/local/etc/compose
Воспользуемся для единственного в нашей схеме конфигурационного файла именем "по умолчанию":
# vi ./docker-compose.yml
version: "3"
services:
nginx:
depends_on:
- jenkins-group0
- jenkins-group1
- jenkins-groupX
container_name: nginx
image: nginx:latest
networks:
jenkins:
aliases:
- "nginx"
ports:
- 80:80
- 443:443
environment:
TZ: "/etc/timezone"
volumes:
- "/var/opt/nginx/etc/conf.d:/etc/nginx/conf.d:ro"
- "/var/opt/letsencrypt:/etc/letsencrypt:ro"
jenkins-group0:
container_name: jenkins-group0
image: selfmade:jenkins-lts-jdk11-chuid
networks:
jenkins:
aliases:
- "jenkins-group0"
environment:
TZ: "/etc/timezone"
JAVA_OPTS: "-Djava.awt.headless=true -Dhudson.footerURL=https://group0.jenkins.example.net"
#JENKINS_SLAVE_AGENT_PORT: "50000"
working_dir: "/var/opt/jenkins/group0"
volumes:
- "/var/opt/jenkins/group0:/var/jenkins_home:rw"
jenkins-group1:
container_name: jenkins-group1
....
volumes:
- "/var/opt/jenkins/group1:/var/jenkins_home:rw"
jenkins-groupX:
container_name: jenkins-groupX
....
volumes:
- "/var/opt/jenkins/groupX:/var/jenkins_home:rw"
networks:
jenkins:
driver: bridge
internal: false
ipam:
driver: default
config:
- subnet: 100.127.255.0/24
services:
nginx:
depends_on:
- jenkins-group0
- jenkins-group1
- jenkins-groupX
container_name: nginx
image: nginx:latest
networks:
jenkins:
aliases:
- "nginx"
ports:
- 80:80
- 443:443
environment:
TZ: "/etc/timezone"
volumes:
- "/var/opt/nginx/etc/conf.d:/etc/nginx/conf.d:ro"
- "/var/opt/letsencrypt:/etc/letsencrypt:ro"
jenkins-group0:
container_name: jenkins-group0
image: selfmade:jenkins-lts-jdk11-chuid
networks:
jenkins:
aliases:
- "jenkins-group0"
environment:
TZ: "/etc/timezone"
JAVA_OPTS: "-Djava.awt.headless=true -Dhudson.footerURL=https://group0.jenkins.example.net"
#JENKINS_SLAVE_AGENT_PORT: "50000"
working_dir: "/var/opt/jenkins/group0"
volumes:
- "/var/opt/jenkins/group0:/var/jenkins_home:rw"
jenkins-group1:
container_name: jenkins-group1
....
volumes:
- "/var/opt/jenkins/group1:/var/jenkins_home:rw"
jenkins-groupX:
container_name: jenkins-groupX
....
volumes:
- "/var/opt/jenkins/groupX:/var/jenkins_home:rw"
networks:
jenkins:
driver: bridge
internal: false
ipam:
driver: default
config:
- subnet: 100.127.255.0/24
Запускаем посредством "Docker Compose" всю пачку контейнеров:
# cd /usr/local/etc/compose
# docker-compose up --remove-orphans --build --force-recreate -d
# docker-compose up --remove-orphans --build --force-recreate -d
Пример точечного запуска определённого контейнера:
# docker-compose up --no-start jenkins-group0
# docker-compose start jenkins-group0
# docker-compose start jenkins-group0
Пример точечной остановки и высвобождения ресурсов определённого контейнера:
# docker-compose stop jenkins-group0
# docker-compose rm -f -v jenkins-group0
# docker-compose rm -f -v jenkins-group0
Останавливаем разом все docker-контейнеры, описанные в конфигурации "Docker Compose":
# docker-compose down
Настройка автозапуска "Docker Compose" посредством "Systemd".
Создаём файл описания параметров запуска и остановки docker-контейнера посредством "Docker Compose" посредством короткоживущего сервиса "Systemd":
# vi /etc/systemd/system/jenkins-docker.service
[Unit]
Description=Jenkins in Docker Compose Service
Requires=network.target docker.service containerd.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/etc/compose
ExecStartPre=-/bin/bash -c 'chown -R jenkins:jenkins /var/opt/jenkins'
ExecStartPre=/usr/local/bin/docker-compose -f docker-compose.yml down --remove-orphans
ExecStart=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml up --remove-orphans --build --force-recreate --detach
#
ExecStop=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml down
[Install]
WantedBy=multi-user.target
Description=Jenkins in Docker Compose Service
Requires=network.target docker.service containerd.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/usr/local/etc/compose
ExecStartPre=-/bin/bash -c 'chown -R jenkins:jenkins /var/opt/jenkins'
ExecStartPre=/usr/local/bin/docker-compose -f docker-compose.yml down --remove-orphans
ExecStart=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml up --remove-orphans --build --force-recreate --detach
#
ExecStop=/usr/local/bin/docker-compose -f /usr/local/etc/compose/docker-compose.yml down
[Install]
WantedBy=multi-user.target
Указываем "Systemd" перечитать и принять новую конфигурацию, а потом явно активируем и запускаем новый сервис:
# systemctl daemon-reload
# systemctl enable jenkins-docker.service
# systemctl start jenkins-docker
# systemctl enable jenkins-docker.service
# systemctl start jenkins-docker
Смотрим журнал событий "Systemd" если "что-то пошло не так":
# systemctl status jenkins-docker.service
# journalctl -xe
# journalctl -xe
Наладка ротации файлов журнала событий.
Не забываем, что для самодельных сервисов ротация их журналов событий не ведётся, и нужно настраивать это отдельно:
# vi /etc/logrotate.d/docker-opt
/var/opt/*/logs/*.log {
size 30M
missingok
notifempty
rotate 5
compress
delaycompress
copytruncate
su root root
}
size 30M
missingok
notifempty
rotate 5
compress
delaycompress
copytruncate
su root root
}
Проверяем корректность конфигурации, не воздействуя при этом на файлы журналов:
# logrotate -d /etc/logrotate.d/docker-opt
Конфигурирование инстансов web-приложения "Jenkins".
В дальнейшем все необходимые настройки "Jenkins" делаются через несколько неудобный, но вполне функциональный web-интерфейс. Просто заходим по очереди на сайты инстансов, аутентифицируемся как суперпользователь "admin" (в первый раз, до обязательной смены, пароль достаём из файла вроде "/var/opt/jenkins/group0/secrets/initialAdminPassword") и производим первичное конфигурирование.
Инсталлятор "Jenkins" потребует задать пароль и почтовый адрес для администратора, а также указать корректный FQDN для web-сервиса. После этого будет предоставлена возможность выбора устанавливаемых плагинов.
После получения доступа в web-интерфейс "Jenkins" прежде всего есть смысл активировать управление правами доступа пользователей посредством плагина "Matrix Authorization Strategy Plugin":
Jenkins -> Manage Jenkins -> Configure Global Security -> Authorization:
Strategy: Matrix-based security
Strategy: Matrix-based security
Таблица распределения прав доступа простая и очевидная.
Учитывая то, что "Jenkins" запускается внутри docker-контейнера и потому самостоятельно необновляем, рекомендую отключить назойливое предупреждение о выходе новых релизов:
Jenkins -> Manage Jenkins -> Configure System -> Administrative monitors configuration:
Jenkins Update Notification: false
Jenkins Update Notification: false
На этом всё. О более детальной настройке "Jenkins" как такового можно почитать в предыдущей публикации "Инсталляция Jenkins".