Pull to refresh

Comments 12

Можно ещё удаленно выполнять 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 скорее образ, а не изображение.

Sign up to leave a comment.