Настройка конвейерной сборки Java-проектов в GitLab
Автоматическая доставка проектных артефактов в тестовые и продуктивные среды является безусловной необходимостью современных процессов промышленной разработки ПО.
Мы пройдем полный процесс создания пайплайна для сборки и деплоя при помощи GitLab и сопутствующего ПО. Все операции мы проделаем на одном компьютере, хотя ничто не должно вам помешать сразу или в дальнейшем масштабировать полученное решение на один или несколько серверов. Для экспериментов лучше иметь достаточно современный компьютер с количеством оперативной памяти не менее 16 гигабайт, производительным процессором и хорошим интернет-каналом.
Предполагается, что у вас уже установлены Docker и ssh-сервер и вы немного умеете со всем этим обращаться.
Создаем локальный реестр образов
Вы можете использовать реестр DockerHub и пропустить этот шаг, а там, где надо будет вводить имя пользователя и пароль от реестра, указывать данные вашей регистрации.
В ином случае нам необходимо создать реестр докер-образов, который будет виден как с локального окружения, которое будет выполнять роль целевого сервера, так из окружений гитлаба.
Создадим каталог с докер-проектом:
mkdir docker-registry
cd docker-registry
mkdir auth
Для генерации пароля потребуется установить в систему пакет apache2-utils. Это можно сделать родным пакетным менеджером, если у вас Linux, или brew в MacOS и chocolately для Windows. В результате у нас должна начать срабатывать такая команда:
htpasswd -bnB user password > auth/htpasswd
Для того, чтобы докер увидел наш наспех развернутый без корректно настроенного HTTPS реестр, мы добавим исключение в его конфигурацию:
/etc/docker/daemon.json
{ "insecure-registries":["172.17.0.1:5000"] }
172.17.0.1 — стандартный ip-адрес гейтвея, который виден как с хостовой машины, так и из рантаймов докер-образов.
Перезапустим сервис, чтобы настройки применились:
sudo systemctl restart docker
Теперь мы готовы запустить сервис реестра докер-образов:
docker container run -d -p 5000:5000 --name registry -v "$(pwd)"/auth:/auth -e REGISTRY_AUTH=htpasswd -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd registry
Если что-то пошло не так, останавливайте контейнер, удаляйте:
docker stop registry
docker rm registry
и пробуйте заново. Если хотите, чтобы этот контейнер запускался автоматически, выполните:
docker update --restart unless-stopped registry
Устанавливаем GitLab
Вместо разворачивания локального гитлаба можно использовать уже существующий корпоративный или воспользоваться сервисом gitlab.com или его аналогами и этот шаг пропустить.
Для сохранения данных гитлаба между перезапусками нам потребуется смапить несколько каталогов из хостового контекста. В документации советуют задать и использовать переменную окружения GITLAB_HOME:
sudo echo "export GITLAB_HOME=/srv/gitlab" >> ~/.bash_profile
Обратите внимание, при выполнении команд из-под sudo эта переменная может оказаться еще не прочитанной. Если значение не задано, то будет использоваться путь /data/gitlab.
docker run --detach --hostname 172.17.0.1 --env GITLAB_OMNIBUS_CONFIG="external_url 'http://172.17.0.1'" --publish 443:443 --publish 80:80 --publish 22:22 --name gitlab --restart always --volume $GITLAB_HOME/config:/etc/gitlab --volume $GITLAB_HOME/logs:/var/log/gitlab --volume $GITLAB_HOME/data:/var/opt/gitlab --shm-size 256m gitlab/gitlab-ce:latest
При запуске был создан пользователь root с правами администратора, его сгенерированный пароль можно получить, выполнив следующую команду:
sudo docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Используйте пароль для входа сразу после запуска сервиса, открыв в браузере http://172.17.0.1
Создаем проект и добавляем ssh-ключ
Сгенерируем новый ключ, если у вас его еще нет:
ssh-keygen
В настройках профиля интерфейса гитлаба добавим ssh-ключ, чтобы фиксация изменений в репозиторий исходных кодов происходила без ввода логина и пароля и по протоколу ssh.
Создадим новый проект в интерфейсе гитлаба и запушим в него код проекта. Инструкции как это правильно сделать можно увидеть после создания репозитория в гитлабе.
Если с пушем с аутентификацией по ключу возникают сложности, в поисках ответа на вопрос «что же пошло не так?» можно покопаться в выводе команды:
ssh -vvvT git@172.17.0.1
Устанавливаем демона-сборщика
Запустим контейнер сборщика:
docker run -d --name gitlab-runner --restart always -v /data/gitlab-runner/config:/etc/gitlab-runner -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:latest
Добавим наш сборщик в секции CI/CD левой панели, перейдя в интерфейсе по ссылкам:
[Левая панель] CI/CD → Runners [Развернуть] → New project runner
указать имя, тег например jmix:
docker run --rm -it -v /srv/gitlab-runner/config:/etc/gitlab-runner gitlab/gitlab-runner register register -n --url "http://172.17.0.1" --registration-token glrt-KyCeiUQQVodooFk7m6wj --executor docker --description "My Docker Runner" --docker-image "docker:24.0.5" --docker-privileged
Параметр registration-token в команду выше вам надо подставить из того, что покажет гитлаб при создании конфигурации ранера в интерфейсе.
Если ранер подсветился зелененькой лампочкой, значит все прошло успешно, если нет — смотрите в логи:
docker logs gitlab-runner
Вы можете также использовать тип ранера shell, установив его из пакета на хостовый компьютер или сторонний сервер, виртуальную машину или контейнер. В таком случае управляться с ним вероятно будет даже проще и работать все будет побыстрее. Главное, чтобы между вашими серверами работали сетевые подключения.
Генерируем ssh-ключи для сборщика
ssh-keygen -t ed25519 -C "gitlab-runner" -f ~/.ssh/id_gitlab-runner
На все вопросы отвечаем нажатием Enter, т.е. мы создаем ключ без пароля.
Добавляем приватный ключ в гитлаб как переменную SSH_PRIVATE_KEY с File Type в CI/CD. На конце обязательно надо добавить пустую строку, без этого сборка будет заваливаться на этапе установки ключа в контейнер.
Увидеть ключ можно с помощью команды:
cat ~/.ssh/id_gitlab-runner
Публичный ключ мы пропишем в ~/.ssh/authorized_keys на хостовом сервере, чтобы ранер мог на него заливать файлы и выполнять команды.
Добавляем сборочную конфигурацию
Для пробной сборки мы будем использовать проект веб-приложения на фреймворке Jmix.
Для разработки на Jmix вам нужно установить JDK 17 или 21. Мы возьмем специально подготовленный для обучения проект jmix-onboarding, в котором уже есть небольшая модель, UI и тестовые данные: https://github.com/jmix-framework/jmix-onboarding-2.git
Наш ранее созданный репозиторий в гитлабе назывался testjmixci, а сам проект называется jmix-onboarding, вы можете создать новый репозиторий с таким же названием, но тогда учитывайте это в дальнейшем. Также вы можете создать новый проект с названием testjmixci в среде разработки IntelliJ IDEA с плагином Jmix, он будет сразу готов к сборке и запуску, но в примерах кода заменяйте jmix-onboarding на testjmixci.
В качестве альтернативы подойдет любой проект на Spring Boot.
В build.gradle надо добавить конфигурацию для публикации докер-образа:
bootBuildImage {
imageName = "172.17.0.1:5000/jmix-onboarding:0.0.1-SNAPSHOT"
publish = true
docker {
publishRegistry {
url = "https://172.17.0.1:5000/"
username = "user"
password = "password"
}
}
}
Обратите внимание, если вы не используете доменного имени для реестра образа, номер порта нужно указывать и в адресе репозитория, и в имени образа.
Конфигурацию необходимо закоммитить и запушить в репозиторий гитлаба, чтобы сборщик увидел параметры публикации образа.
Для проверки можно собрать и опубликовать образ вручную командой:
Теперь надо создать конфигурацию пайплайна, для этого откроем редактор пайплайнов, выбрав в интерфейсе:
[Левая панель] Build → Pipeline editor
и разместим в нем следующий код:
default:
image: docker:24.0.5
services:
- name: docker:dind
command: ["--insecure-registry=172.17.0.1:5000"]
before_script:
- docker info
stages:
- deploy
deploy:
tags:
- jmix
stage: deploy
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
GIT_STRATEGY: none
before_script:
- apk add git
- apk add openjdk17
- apk add nodejs npm
- apk add openssh-client
- eval $(ssh-agent -s)
- chmod 400 "$SSH_PRIVATE_KEY"
- ssh-add "$SSH_PRIVATE_KEY"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- CLONE_DIR="$BUILD_DIR/$CI_PROJECT_PATH"
- cd $BUILD_DIR
- rm -rf $CLONE_DIR
- mkdir -p $CLONE_DIR
- git -c core.sshCommand='ssh -o StrictHostKeyChecking=no' clone ssh://git@172.17.0.1:22/root/testjmixci.git $CLONE_DIR
- cd $CLONE_DIR
- git checkout main
only:
refs:
- main
allow_failure: true
script:
- ./gradlew -Pvaadin.productionMode=true bootBuildImage
В нашем примере мы использовали директиву GIT_STRATEGY: none, которая позволяет выключить стандартный механизм гитлаба, получающий исходники проекта, и заменить его собственным.
Также мы используем образы Docker-in-Docker, которые предоставляют окружение, подходящее для работы с докер-образами.
Мы указали тег jmix и т.к. он же указан в ранере, все изменения в данном репозитории будут запускать наш пайплайн.
Сохранив конфигурацию пайплайна, гитлаб коммитит ее в виде файла в репозиторий и почти сразу запускает сборку, а мы ждем, когда вверху появится ссылка на мониторинг выполнения запустившейся задачи.
Альтернативные способы сборки docker-образа
Если вам не подходит Paketo Buildpacks, которые использует инфраструктура Spring Boot для сборки образов, можно сделать простой docker-файл для самозапускающегося jar-ника примерно такого содержания:
FROM openjdk:17
EXPOSE 8080
ARG JAR=jmix-onboarding-0.0.1-SNAPSHOT.jar
COPY build/libs/$JAR /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Если сразу вы угадали с именем файла, назвав его Dockerfile, cобираться он будет простыми:
docker build .
Также перспективным способом собирать контейнерные образы является инструментарий от Google под названием Jib. Интересен он в первую очередь тем, что не требует наличия докер-демона для сборки и публикации, что в нашем случае значит и использования Docker-in-Docker.
Публикация его тоже будет производиться низкоуровнево, сначала надо авторизоваться в реестре:
docker login -u user -p password https://172.17.0.1:5000
и затем можно пушить образ:
docker push 172.17.0.1:5000/jmix-onboarding:0.0.1-SNAPSHOT
После того как сборка успешно завершена, можно проверить результаты на хостовой машине.
Проверка результатов сборки
В терминале хостовой машины авторизуемся клиентом
docker login -u user -p password https://172.17.0.1:5000
и проверим наличие образов с помощью команд:
docker pull 172.17.0.1:5000/jmix-onboarding
docker images | grep jmix-onboarding
Деплой сервиса
Добавим в самый конец конфигурации сборки команды, которые при помощи удаленного сеанса удалят старый сервис и создадут новый из образа автоматически:
- ssh -o StrictHostKeyChecking=no $username@172.17.0.1 -p 2222 'docker stop jmix-onboarding ; docker rm jmix-onboarding ; docker pull 172.17.0.1:5000/jmix-onboarding:0.0.1-SNAPSHOT ; docker run -d --restart=always -p 8080:8080 --name=jmix-onboarding 172.17.0.1:5000/jmix-onboarding:0.0.1-SNAPSHOT'
где вместо $username надо использовать свой логин в системе
Чтобы избежать конфликтов портов с гитлабовскими протоколами я перенастроил свой sshd на порт 2222. Вместо user@ вам следует использовать свою учетку.
Если все хорошо, после успешно отработавшего пайплайна, выполнение команды
docker ps
должно будет показывать контейнер со свежим временем старта.
Итак, мы получили пайплайн, который при фиксации изменений в репозитории запускает сборку проекта и затем докер-образа, публикует его в отдельном реестре, забирает и деплоит докером целевой машины. Благодаря гибкости докера все это мы проделали на локальном компьютере, но теперь, отладив процессы, легко сможем перенести их в производственную среду.