Автоматическая доставка проектных артефактов в тестовые и продуктивные среды является безусловной необходимостью современных процессов промышленной разработки ПО.
Мы пройдем полный процесс создания пайплайна для сборки и деплоя при помощи 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 user@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'
Чтобы избежать конфликтов портов с гитлабовскими протоколами я перенастроил свой sshd на порт 2222. Вместо user@ вам следует использовать свою учетку.
Если все хорошо, после успешно отработавшего пайплайна, выполнение команды
docker ps
должно будет показывать контейнер со свежим временем старта.
Итак, мы получили пайплайн, который при фиксации изменений в репозитории запускает сборку проекта и затем докер-образа, публикует его в отдельном реестре, забирает и деплоит докером целевой машины. Благодаря гибкости докера все это мы проделали на локальном компьютере, но теперь, отладив процессы, легко сможем перенести их в производственную среду.
Комментарии (14)
KXB
24.04.2024 16:08Не думаю, что во всех командах/проектах есть и разрабы и девопс.
Однако достаточно команд где разраб и сисадмины. Последние так привыкли всё делать только кликами мышки в UI ну или в крайнем случае давно написанными кем-то шелл-скриптами. В девопс идти не хотят.
И получается, что разраб вынужден в этой ситуации сидеть на двух-трех стульях постигая смежные области кроме весьма глубокой своей, повышая time-to-market.
Экономия времени на разворачивание тестовых окружений весьма кстати. Автоматизация одинаковых и рутинных действий сводит к минимуму ошибки. Бегать за девопс’ом чтобы он развернул тестовое окружение от которого мало что зависит, такое себе.
Да вероятно производственные среды лучше разворачивать под контролем человека, но не завязывать на него.
Данная статья - мануал для тех кто входит «в реку». И для нерадивых сисадминов и для разрабов столкнувшихся с «голой» инфраструктурой один-на-один при недостатке времени. По мере опыта и костыли уйдут и лучшие практики применятся. Но начинать с чего-то надо. И такие статьи часто являются отправной точкой.
Как классно когда только одним коммитом в определенную ветку за несколько минут собирается проект, прогоняются тесты, поднимается сервис и все это без участия кого-либо, пока ты ходил за чашечкой чая, а не к девопсу «на поклон». Ведь это только первый шаг к тому чтобы качественно подготовиться к релизу. И таких шагов может быть увы много …
P.S.
Автору респект, однако согласен в тексте есть шероховатости и есть куда рости чтобы мануал стал безупречным.
SimSonic
Я дико извиняюсь, но, имхо, вся эта статья пропитана костылями.
Попробую защитить своё мнение.
1) Я сторонник DevOps, но самостоятельное развёртывание и администрирование инстанса GitLab, имхо, не должно касаться стороны Dev. Это задача исключительно инфраструктурной команды. Я развёртывал в НИИ в 2015 году инстанс и пару лет админил его, так что это не диванная аналитика =)
1.1) Однако, ничего не имею против развёртывания своих раннеров. Настолько, что мне кажется более стройной идея установки раннера на целевой хост и запуск джобы с деплоем прямо на нём, без всех этих приключений с SSH-ключами.
2) Про поднятие своего реестра -- не уверен, кто-нибудь ниже мб меня поправит. Но если инстанс GitLab развёрнут нормально, в нём же есть встроенный и Image Registry, и Maven / npm Registry и т.п. (честно не помню, может что-то в платных Tier, но образы точно есть в бесплатном).
3) Зачем нам игнорировать базовый функционал раннера по скачиванию репы? Ради чего? Потому что у нас кривая среда?
4) У вас only.refs: main, а если MR из фича ветки в main кривой и всё ломает?
5) В таком виде процессы не "отлажены", я бы на прод это не допускал =)
Раз уж покритиковал, напишу свой вариант. Он, конечно, может быть к вашей ситуации неприменим, я не знаю всех тонкостей.
1) GitLab у нас один и управляется специалистами нужного профиля. Они же предоставляют общие раннеры (и желательно не privileged).
2) У нас есть джоба, которая собирает приложение, прогоняет тесты, пакует всё в образ и пушит в registry под управлением специалистов нужного профиля. На общем раннере, для любой ветки.
3) Для веток, соответствующим стендам (тест/прод), запускаются джобы с тегами раннеров, стоящих в этих средах. Раннеры на месте делают pull нового образа и рестарт приложа.
ant1free2e Автор
это статья-туториал(к сожалению не нашел такой опции при публикации, хотя раньше она была), т.е. этакий практикум для тех кто еще не знаком с темой, поэтому одной из главных ее задач является отработка потенциальных проблемных зон. Если их не рассмотреть, специалисту придется искать другие статьи, копаться на стековерфлоу в гугле и ассистентах. Мне в свое время как раз приходилось решать не только упомянутые проблемы, но и многие другие. Конечно, лучше быть здоровым и богатым, но в реальности все бывает по-разному. А для совсем стандартных решений типа использования встроенных или докерхабовских реестров существует официальная документация, правда в ней тоже не всегда могут быть такие квикстарты для новичков закрывающие сразу некоторый полный минимальный набор. А что-бы побеждать в борьбе с деревом официальной документации уже требуются некоторые знания и опыт. Поэтому для освоения технологии достаточно эффективным оказывается пройти сначала некий базовый туториал, а потом с пониманием идти в сторону улучшений. Программистам тоже приходится в этом ориентироваться, сейчас у каждого локально могут быть развернуты кластеры, каталоги, субд, что-бы разбираться и решать проблемы их использование часто оказывается необходимым и оправданным как раз в ситуациях с большими компаниями, когда хождение в официальную инфраструктуру требует долгих согласований, а сроки горят как по-аджайлу;)
Vamp
1. Гитлаб, по моему опыту, - самое беспроблемное приложение, не требующее особого ухода. По крайней мере в команде из 10 человек. Я трачу на его сопровождение всего 10 минут в неделю: делаю бэкап, скачиваю новую версию образа (если успела выйти) и перезапускаю на новом образе.
1.1. Ваше предложение не масштабируется. Придется переделывать, когда возникнет необходимость деплоить более чем на один сервер. Плюс есть проблемы с безопасностью - прод не должен иметь доступ к деву, а в вашей схеме потребуется пробить дырку из прода к гитлабу.
2. Встроенный container registry появился в 2016 году и сразу был доступен для всех. А вот package registry (maven, npm и прочие) был изначально платным, но переехал во free tier в 2020 году. Видимо, автор в 2015 настроил CI до состояния "работает - не трожь" и теперь делится хоть и готовым, но, увы, устаревшим рецептом. А может он поленился узнать какими фичами вообще обладает гитлаб. Или просто теряется в имеющейся документации (как он пишет в комментарии выше).
SimSonic
Имея достаточно устаревший опыт (развёртывание omnibus в 2015-2017), я, скорее, соглашусь с этим. Действительно, установка и администрирование были простыми и времени особо не требовали.
Ну вообще нет, т.к. я перестал регулярно читать ежемесячные дайджесты по 22 числам только год назад, и сейчас читаю их просто нерегулярно.
Но хочу отметить, что я не являюсь сис.админом, в данный момент я исключительно разработчик. И считаю избыточным для разработчика самостоятельно развёртывать и поддерживать GitLab.
Я продолжаю придерживаться мысли, что GitLab в компании должен быть один и управляться сис.админами, их не должно быть по штуке на разработчика (локально в докере), и разделять инстансы гитлаба на дев/прод среду тоже не нужно. Если речь в статье про разработчика-одиночку, почему бы ему не воспользоваться облачным GitLab.com? Если статья написана про ещё более узкую ситуацию, то, имхо, это должно быть обозначено, а не приведено как универсальный quickstart для любой ситуации.
Извините, но не вижу, где оно не масштабируется. Я применяю эту схему на разных проектах. GitLab это не часть ни dev-среды, ни prod-среды, это инфраструктурный компонент, такой же, как и вероятно установленный где-то рядом Registry. Поэтому речь о пробитии дырки прод->дев некорректна, это дырка prod->infra.
Деплойте на сколько угодно серверов, из каких угодно веток. Разделите ветки стендов на protected и нет, чтобы обезопасить прод. Собирайте и пуште образы на общих раннерах, деплойте на "стендовых" раннерах, фильтруя джобы деплоя через фильтры only:refs и tags. Запретите Dev мержить в protected, только Maintainer.
ant1free2e Автор
А что делать если вам надо отладить саму сборку или пайплайн и сисадмины такого точно не смогут?
SimSonic
Смотря, что вы имеете ввиду под этими словами. Сборка — скрипт, запускаемый в джобе? Часто это минимум команд, которые можно просто запустить из IDE, например та же сборка спринговым плагином сразу образа. Если сложнее, можно вынести все команды в .sh, и запускать его. Вариантов очень много, на самом деле.
Что значит отладить пайплайн? Если вы отлаживаете их на локальном инстансе, вы гарантируете, что настройки раннеров и всех других важных административных настроек идентичны тем, что на удаленной среде?
ant1free2e Автор
некий набор операций которые необходимы для преобразования исходников в артефакты для запуска на тестовых или продуктивных серверах: получение, сборка, упаковка, прогон тестов. Все они могут принадлежать разным источникам и иметь разную природу. Кроме того, это может быть какая-то новая для вашей ситуации технология или контекст. Девопсам или сисадминам, как минимум, могут потребоваться конкретные инструкции по настройке сложного процесса полностью понимать который будет только разработчик.
SimSonic
Полностью согласен с таким определением.
Да. Но мне ещё здесь хочется добавить, что удобнее общаться в стиле декларативных зависимостей, а не инструкций. "Мне нужен доступ в интернет с общего раннера, чтобы скачать образ с gradle и jdk; мне нужен docker, чтобы собирать образы; мне нужен registry, чтобы хранить их; мне нужен shell-runner на таком-то стенде с тегом prod, чтобы деплоить туда". Если процесс убер-сложный, нужно его документировать, обсуждать, чтобы обе стороны понимали, что и как работает. Тогда это и есть DevOps.
ant1free2e Автор
просто не всегда бывает так, что вы можете все определить самостоятельно и решить задачу "с нуля", реестр может потребоваться использовать уже готовый и сторонний, например из артифактори или нексуса или того же докерхаба или аналога. Конкретно для программиста отдельный реестр удобнее в том плане, что гитлаб наигравшись можно выключить, а реестр оставить для поддержки других своих задач.
девопс это почти сисадмин готовый заниматься вопросами выше уровней сетевой маршрутизации, но он не программист и может и не знать для чего надо запускать препроцессоры или с какими профилями собирать. Конечно, надо ему эти знания как-то передать, но прежде чем это получится сделать этот процесс желательно проработать разработчику, т.к. если с какой-то проблемой застрянет девопс ему разобраться в особенностях наделанного программистами будет гораздо сложнее. Ну и я совершенно не возражаю против того, что бы этой статьей воспользовались сисадмины и девопсы, даже хочу им помочь как в плане получения базовых рецептов, так и специфики типа развертывания джава-приложений в контейнерах.
ant1free2e Автор
хорошим тоном в таких процессах являются "чистые" сборки, не зависимые даже от историчности запусков не говоря уже о особенностях внешней среды, а одинаковость настроек не требуется, т.к. они определяются внешними по отношению к артефакту/контейнеру параметрами. Но вообще, обычно, тестовая среда максимально вариативна для лучшего выявления проблем, а стейдж - максимально приближен к продуктиву, иногда делается прямо на основе него.
SimSonic
Полностью согласен.
Согласен, конечно, 12factor app никто не отменял =) Приложение отдельно, конфигурация отдельно.
Смотрите, я триггерю на то, что в статье описывается развёртывание инстанса GitLab локально, и на то, что вы написали "отлаживать пайплайны". Всё это звучит так, будто это туториал по размножению инстансов гитлабов на своей машине, на тестовом стенде, на продовом стенде. Причём локально только для того, чтобы посмотреть, как запустился и отработал пайплайн — то есть фактически протестировать сам .gitlab-ci.yaml. Я здесь прав или ошибаюсь?
ant1free2e Автор
В том числе протестировать пайплайн, ситуация может быть различной, у вас могут быть отдельные виртуалки или железяки под все включая реестр, надо быть готовым ко всему. Специфика данной статьи - джава-проекты которые надо деплоить автоматически, докер как среда их выполнения, гитлаб как система неприрывной интеграции. Вы не становитесь сисадмином от того, что выполнеяте docker start локально или на выделенном сервере.
SimSonic
Я понял вашу мысль. Я согласен с тем, чем больше всего протестировано комплексно, тем лучше. Но при этом в ряде моментов я остался при своём, мы видимо на разных языках говорим, у каждого своё понимание ситуации и людей, которые в ней оказались, так что давайте остановимся =)