Comments 12
Лучше же таких ситуаций избегать, использовать например kaniko https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#docker-alternatives
Можно ещё удаленно выполнять Docker-команды. Например, чтобы задеплоить приложение на сервер:
.deploy:
stage: deploy
image: docker:20.10.16-alpine3.16
before_script:
- eval $(ssh-agent -s)
- echo "$DEPLOYMENT_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $DEPLOYMENT_HOST >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- DOCKER_HOST="ssh://$DEPLOYMENT_USER@$DEPLOYMENT_HOST" docker-compose -f docker-compose.yml --env-file .env.$CI_ENVIRONMENT_NAME up --build -d
deploy-staging:
extends: .deploy
only:
- master
environment: staging
Качество и вычитка статьи удручает.
"изображений в реджестри.", хотя отсутствует пометка про перевод.
И в этом же абзаце аж 7 раз слово docker без явной необходимости.
В данный момент пытаюсь разобраться как правильно настроить и использовать gitlab-runnerы для сборки docker образов, публикации их в registry, и последующий деплой на DEV, TEST, PROD. Интересует у кого есть реальный опыт как делать не надо и как по вашему мнению правильнее ? хотелось бы получить изоляцию, чтоб могли одновременно отработаться несколько pipeline и друг другу не мешали, и после завершения pipeline, чтоб не осталось мусора (в том числе и docker контейнеры, которые могли стартануть для интеграционых тестов)
А есть смысл публиковать образы в docker registry? Почему бы сразу не деплоить на стенд?
Не очень понимаю как pipeline могут мешать друг другу... У нас, например, есть следующий сценарий. В каждой ветке собирается приложение, деплоится на тестовый стенд и на нём запускаются e2e-тесты. При этом если две ветки будут собираться одновременно, то, да, возможна ситуация, что они одновременно будут деплоить приложение и запускать тесты. Чтобы избежать этого мы используем параметр resource_group. Благодаря этому сначала запускаются тесты в одном pipeline, затем в другом. Но это нужно если разные pipeline используют один и тот же сервер. А если они просто собирают приложение и деплоят его на свой стенд, то и так всё работает.
Насчет удаления мусора... У вас runner запускаются в docker? Или без docker в самой ОС (в шеле)? Если в docker, то весь мусор в нём и остаётся и можно время от времени просто чистить эти контейнеры и образы. Если в шеле, то наверное это не очень и лучше запускать в докере. Мы у себя время от времени запускаем docker system prune, этого хватает.
А есть смысл публиковать образы в docker registry? Почему бы сразу не деплоить на стенд?
Конечно! Суть тестов, что вы тестируете определённую версию приложения и если всё ок, отправляете в продакшен. А если не использовать docker container registry, то получается для теста собирается образ и при деплои на ПРОД также собирается образ. И тут может произойти не хорошая ситуация, что ваши образы могут отличаться. Вышли какие-то обновления зависимостей и т.д. и т.п. и может оказаться, что в тестах всё хорошо, а в продакшене не работает. В этом то и вся суть тестирования, что протестировали, то в продакшен и отправили. т.е. собрали образ, повесили на него тег версии и запушили в registry. Из реестра взяли, запустили в тестовом окружении, всё гут, тот-же докер образ деплоится в продакшен.
Не очень понимаю как pipeline могут мешать друг другу...
Предположим у нас есть репозитории
serviceA - одно приложение
serviceB - второе приложение
serviceApp - некий репозиторий с предписанием деплоймента
мы коммитнули изменения в serviceA и serviceB и ждём их сборки, чтоб через serviceApp потом их задеплоить на DEV и TEST окружение. и на обеих репозиториях serviceA и serviceB запустились pipelines. В рамках unit тестов запускаются тестовые контейнеры например с postgresql, kafka, ещё что-то. и если эти pipeline будут работать в одном и том-же окружении, то потенциально возникнет конфликт интересов, что обеим pipeline надо будет запустить одинаковые образы и возможно может остаться мусор в виде непогашенного docker контейнера, запущеного для тестов, например с kafka.
Хотелось бы получить для каждой pipeline изолированное окружение, чтоб они не могли друг другу помешать. чтоб допустим могло спокойно одновременно отработаться 5 различных pipeline. по сути логика docker in docker.
У вас runner запускаются в docker? Или без docker в самой ОС (в шеле)?
Ситуация следующая. У нас в компании работаем в Azure, и там с CI/CD всё чётко, проблем особо нету, используются Microsoft агенты. Каждый запуск pipeline в изолированном окружении. В общем всё как требуется.
GitLab я установил для себя, на своём сервере, для своих личных проектов. GitLab Runner пока не настраивал, пытаюсь разобраться как правильнее сделать. CI/CD в гитлабе отличается от того, как это решено в Azure. плодить виртуалки для обеспечения изоляции среды для pipeline мне кажется немного странным. Использование докера более правильный подход, но вот каким образом реализовать, т.к. если замапить docker socket в контейнер, это в принципе не безопасно и приведёт к конфликтами при параллельном запуске pipeline.
А если не использовать docker container registry, то получается для теста собирается образ и при деплои на ПРОД также собирается образ. И тут может произойти не хорошая ситуация, что ваши образы могут отличаться.
Я сначала пытался настроить сборку полностью через Docker. Сделал многошаговый Dockerfile, в котором отдельными шагами прописал скачивание зависимостей, сборку бэкенда, фронтенда, запуск тестов и остальное. И у меня была идея, что можно в GitLab сделать несколько шагов (stage), на каждом из которых вызывать соответствующие шаги из Dockerfile. Тогда весь этот процесс был бы описан в одном месте (в Dockerfile), использовался бы механизм кэширования Docker.
В итоге это оказалось утопией. Потому что в GitLab уже предусмотрен свой механизм описания шагов сборки, кэширования, раннеры запускаются в Docker. Куча проблем с запуском Docker внутри Docker. Короче я понял, что GitLab для таких сценариев не очень предназначен. Возможно два варианта:
1) Либо мы описываем шаги сборки в GitLab. А Docker используем только в конце для деплоя уже собранного приложения на стенд. Иными словами мы основную логику описываем в .gitlab-ci.yml, а Dockerfile просто копирует уже собранное приложение в образ, который потом деплоится
2) Либо мы описываем сборку в Dockerfile, но при этом в .gitlab-ci.yml у нас грубо говоря 1-2 шага, которые просто запускают Dockerfile целиком
Попытка сделать какой-то гибридный вариант - это мучение, в том числе описанное в этой статье. Всё это docker in docker, docker registry и т.п.
У нас эта проблема решается просто:
1) Процесс сборки прописан в .gitlab-ci.yml
2) В .gitlab-ci.yml прописаны правила кэширования, зависимости между шагами. Всякие resource_group, чтобы не шёл одновременный деплой на один стенд.
3) А в Dockerfile тупо копируются уже собранные файлы в образ и всё. Соответственно на разные стенды будет деплоиться один и тот же код, потому что он собран GitLab раннером, сохранен в качестве артефакта, для которого можно задать какое угодно время хранения.
В общем перефразирую эту мысль иначе. Не нужно пытаться скрестить между собой GitLab и Docker. Только что-то одно из этого можно использовать на полную, а второй инструмент будет вспомогательным. У нас основной инструмент это GitLab, а Docker используется на столько минимально на сколько это возможно. Поэтому мы не используем Docker Registry, да и в нём просто нет необходимости, нам достаточно хранилища артефактов GitLab.
Вышли какие-то обновления зависимостей и т.д. и т.п. и может оказаться, что в тестах всё хорошо, а в продакшене не работает.
У нас такой проблемы нет, потому что приложение собрано GitLab ранером, сохранено как артефакт, его можно скачать, посмотреть, задать время хранения и т.п., а Docker его только копирует на стенд. Поэтому на разные стенды копируется одна и та же версия приложения.
Плюс конечно же у нас жестко прописаны все версии зависимостей. И то, что при пересборке они изменятся - это какая-то невероятная ситуация, баг. Это прямо очень строгое правило, что ни при каких обстоятельствах нельзя указывать плавающие версии зависимостей.
потенциально возникнет конфликт интересов, что обеим pipeline надо будет запустить одинаковые образы и возможно может остаться мусор в виде непогашенного docker контейнера, запущеного для тестов, например с kafka
В GitLab конфликты решаются с помощью resource_group.
Удаление контейнеров можно или прописать в .gitlab-ci.yml (и если используется resource_group, то по идее не должно быть конфликтов, что один pipeline удалил контейнер, созданный другим pipeline). Или можно просто забить на это и время от времени запускать на стенде docker system prune.
плодить виртуалки для обеспечения изоляции среды для pipeline мне кажется немного странным. Использование докера более правильный подход
И не нужны виртуалки. Каждый раннер и так запускается в своём Docker контейнере и pipeline никак не пересекаются. Пока вы прописываете процесс сборки, запуска тестов, деплоя и остальное в .gitlab-ci.yml, то всё работает нормально. Всё это и так уже запускается в Docker, в изолированном окружении. А когда вы пытаетесь существенную часть логики перенести в Dockerfile, то начинаются все эти танцы с бубнами вокруг Docker in Docker, для которых GitLab изначально не предназначен, это костыли. Возможно я ошибаюсь, но я потратил на это определенное время и пришёл к тому, что ну его нафиг этот Docker in Docker. Проблем полно, профита ноль.
За наводку по поводу resource_group благодарю, посмотрю.
В остальном я возможно не достаточно смог объяснить в чём трудность. Тестирование через docker так себе затея, для этого описываются шаги в CI/CD. Живой пример. Допустим у нас есть приложение на Java (Spring Boot + Maven). Для того , чтобы нам собрать приложение мы должны запустить команду
mvn clean compile package
Maven тянет зависимости, компилирует код и запускает unit тесты, после собирает готовый jar, а после уже собираем образ и ложем его в registry. В юнит тестах используется test containers для запуска postgresql, и стартует оно в docker контейнере, отпрабатывает тестовые сценарии и всё гасит за собой.
Если Gitlab Runner запущен в своём контейнере, что в принципе логично, то получаем ситуацию docker in docker. Если мы в докер пробрасываем socket, чтоб всё сработало как надо, то получаем конфликтную ситуацию, когда одновременно отрабатываются две pipeline с тестовыми сценариями. Из этого и возникает вопрос, а как правильно использовать Gitlab Runnerы ?
Далее про docker container registry. Из личного опыта, это как минимум удобно и по моему мнению правильно. Вы можете разворачивать всю инфраструктуру через IaC и можете управлять версиями приложения в любом отрезке времени, в любом окружении.
Я ни в коем случае не осуждаю ваш подход через артефакты, но вижу в нём недостаток. Получается, что вы докер образ, который запускаете создаёте на лету, размещая в него содержимое вашего артефакта т.е. какую-то определённую версию вашей программы. Нюанс в том, что ваше окружение, оно другое, отличное от того, в котором вы тестировали и могут возникнуть нюансы.
Живой пример из опыта. Java приложение, которое через JDBC общается с Microsoft SQLServer (не самой свежей версии, изза определённых причин заказчик не мог себе позволить upgrade). Java приложение было написано на java8. Всё отлично работает. Спустя какое-то время, ничего не меняли, собираем docker образ, а приложение уже не работает. Почему? А потому-что приехало обновление java 8, где было по-умолчанию отключен TLS 1.1, и всё, приложение с базой уже общаться не смогло. Образ из docker container registry, продолжал работать, т.к. его состояние осталось не изменным.
Maven тянет зависимости, компилирует код и запускает unit тесты, после собирает готовый jar, а после уже собираем образ и ложем его в registry. В юнит тестах используется test containers для запуска postgresql, и стартует оно в docker контейнере, отпрабатывает тестовые сценарии и всё гасит за собой.
Если Gitlab Runner запущен в своём контейнере, что в принципе логично, то получаем ситуацию docker in docker.
С test containers у нас были сложности... Изначально проект собирался очень просто - это shell-скрипт, который вызывал maven, npm, копировал разные файлы и т.п. Для тестов использовался test containers.
Затем вместо shell-скриптов решили перенести всё это в Docker. Написали Dockerfile, который делал примерно то же самое, но зато теперь с кэшированием, в управляемом окружении. Если я правильно помню, то уже на этом этапе пришлось сначала помучиться с Docker in Docker, а потом отказаться от test containers и заменить их на zonky. О чём я не жалею, потому что всё стало гораздо проще.
Потом мы начали переносить сборку в GitLab. И тут я снова столкнулся с Docker in Docker. Помучился и в итоге пришел к тому, что проще перенести правила сборки в конфиг GitLab. Чтобы Dockerfile просто копировал уже собранный jar-файл и остальное. Таким образом у нас нет Docker in Docker и на мой взгляд это всё упрощает.
Вообще если использовать test containers, то это будет уже Docker in Docker in Docker :) В первом Docker запускается GitLab runner, во втором - сборка приложения, в третьем - тесты на test containers. Для меня это было перебором.
Нюанс в том, что ваше окружение, оно другое, отличное от того, в котором вы тестировали и могут возникнуть нюансы.
Java приложение было написано на java8. Всё отлично работает. Спустя какое-то время, ничего не меняли, собираем docker образ, а приложение уже не работает. Почему? А потому-что приехало обновление java 8, где было по-умолчанию отключен TLS 1.1, и всё, приложение с базой уже общаться не смогло.
Чтобы избежать этого у нас везде жестко прописаны версии: 1) в .gitlab-ci.yml версии docker-образов 2) в Dockerfile версии docker-образов 3) в зависимостях приложения. На некоторых проектах в pom.xml даже прописан вендор и версия JDK, версия Maven, чтобы никто не пытался их собирать с другими версиями.
Если Gitlab Runner запущен в своём контейнере, что в принципе логично, то получаем ситуацию docker in docker. Если мы в докер пробрасываем socket, чтоб всё сработало как надо, то получаем конфликтную ситуацию, когда одновременно отрабатываются две pipeline с тестовыми сценариями. Из этого и возникает вопрос, а как правильно использовать Gitlab Runnerы ?
Как я уже описал у нас не используется Docker in Docker, чтобы избежать разные проблемы и костыли. Но я всё равно не очень понимаю какая конфликтная ситуация здесь возникает... Каждый pipeline запустит свой docker-контейнер со своим экземпляром тестовой базы на test containers. Они вроде никак не должны мешать друг другу.
Но если они обращаются к каким-то внешним ресурсам. Например, к какому-то сервису, запущенному отдельно, то, да, тут могут быть конфликты. Я думаю, это тоже решается через resource_group, который просто запретит параллельный запуск этих pipeline.
ещё вопрос. допустим я делаю 3-4 разные pipeline, которые в репозитории реагируют на изменения в репозитории на определённые папки или файлы. как в gitlab реализовать подобный триггер, чтоб в зависимости от тригера срабатывали разные pipeline?
trigger:
batch: true
branches:
include:
- master
paths:
include:
- config/test/*
- config/common/*
- config/~manifest/manifest.test.properties
- factory/charts/*
либо такой вариант
trigger:
batch: true
branches:
include:
- releases/*
paths:
include:
- config/prod/*
- config/common/*
- config/~manifest/manifest.prod.properties
- factory/charts/*
либо вариант, когда pipeline проверяет код на его соберабельность , но при этом ничего никуда не деплоит и срабатывает на любых ветках, кроме основной. т.е. по факту на фючер ветках. Но не нерагирует, если изменили просто README.md
trigger:
branches:
exclude:
- master
paths:
exclude:
- README.md
Никогда не пользовался триггерами и сборкой в зависимости от измененных файлов. Проще пересобрать весь проект. У нас следующая схема:
1) При любых коммитах в ветку, для которой создан merger request, запускается сборка, анализ кода, деплой на тестовый стенд, запуск интеграционных тестов и т.п.
2) При любых коммитах в master выполняется всё то же самое плюс деплой на dev-стенд
3) При необходимости можно вручную (по нажатию кнопки) запустить деплой на другие стенды
Для большинства шагов (stage) задано такое условие (они выполняются для коммитов в ветку или в master):
build-backend:
only:
- master
- merge_requests
Для некоторых шагов - такое условие (выполняется только для коммитов в master):
deploy-staging:
extends: .deploy
only:
- master
resource_group: staging
environment: staging
Для некоторых шагов - запуск вручную:
deploy-production:
extends: .deploy
only:
- master
when: manual
resource_group: production
environment: production
Довольно наглядная статья, но как уже написали ранее, есть и другие способы, которые тоже хорошо работают.
А еще при переводе статей не стоит забывать об адаптации, ибо в данном случае image скорее образ, а не изображение.
Использование Docker in Docker в GitLab