Довольно часто мы используем GitLab CI для докеризации наших приложений. Но как запустить контейнер Docker из GitLab Container Registry? Можно ли использовать Docker Compose? Делимся переводом статьи, в которой автор отвечает на эти вопросы, и рассказывает о функции services keyword в GitLab CI. Она позволяет запустить один или несколько образов Docker и связать их с вашим заданием.

Перед началом
Для этого сценария я создал приложение Node.js, которое предоставляет API.

Таким образом, текущий пайплайн состоит из следующих этапов:
build
, где устанавливаются все зависимостиtest
, на котором выполняются все модульные тестыpackage
, где приложение докеризируется, и образ отправляется в GitLab Container Registry.
Для справки, вот как выглядит .gitlab-ci.yml
на этом этапе:
stages:
- build
- test
- package
build:
stage: build
image: node:14-alpine
script:
- npm ci --only=production
artifacts:
paths:
- node_modules/
- server.js
unit tests:
stage: test
image: node:14-alpine
before_script:
- npm install
script:
- npm test
build docker image:
stage: package
image: docker
services:
- docker:dind
script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
- docker build -t $CI_REGISTRY_IMAGE .
- docker push $CI_REGISTRY_IMAGE
В этом сценарии используется shared runner инфраструктура GitLab.com, где GitLab раннеры используют Kubernetes executor.
Для некоторых Docker executor может потребоваться указать переменную DOCKER_HOST. Вы узнаете об этом, если получите эту ошибку:
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Этот подход НЕ подходит для shell executors.
variables:
DOCKER_HOST: tcp://docker:2375/
или
variables:
DOCKER_HOST: tcp://docker:2376/
Проблема
Следующим логичным шагом было бы проведение приемочных тестов, выполнение нескольких команд cURL в контейнере Docker или, что еще лучше, использование Postman/Newman для тестирования API.
Как же запустить Docker-контейнер в рамках конвейера GitLab CI?
Запуск контейнера с помощью docker run
Локально я собрал и протестировал контейнер с помощью docker build и docker run. То же самое должно быть возможно и в рамках конвейера, верно?
Задание build docker image
уже предоставляет очень хороший шаблон для использования Docker в GitLab. Поэтому я войду в GitLab Container Registry и запущу созданный ранее образ.
Чтобы упростить конвейер, я НЕ указывал никаких версий для образов docker
и docker:dind
и не создавал никаких тегов для созданного образа. В реальном сценарии я бы сделал и то, и другое.
stages:
- build
- test
- package
- acceptance
...
curl api testing:
stage: acceptance
image: docker
services:
- docker:dind
script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
- docker run -d -p 3000:3000 $CI_REGISTRY_IMAGE
- apk add curl
- curl http://localhost:3000/status | grep "UP"
К сожалению, следующая установка завершится с ошибкой:
curl: (7) Failed to connect to localhost port 3000 after 5 ms: Connection refused

Но почему? Локально все работало просто отлично. Чтобы объяснить причину, позвольте мне познакомить вас с сервисами GitLab CI.
Что такое GitLab CI services?
Как вы видели, задание, создающее конвейер, использовало ключевое слово services
для указания образа docker:dind
.
В двух словах, сервисы GitLab предоставляют вам возможность запускать дополнительные контейнеры Docker и связывать их с вашим образом (тем, который вы указали в ключевом слове image
).
Один из наиболее типичных случаев использования — когда вам нужна база данных, API или другой сервис, который можно вызвать по сети.
Вот несколько ключевых аспектов, которые вам необходимо запомнить:
Образ или образы, указанные в services, могут быть доступны только через сетевое соединение.
Образ или образы, указанные в services, должны открывать службу на заданном порту. В противном случае они бесполезны в данном контексте.
Вы не сможете подключаться или выполнять команды командной строки на контейнерах, запущенных как службы.
Использование сервисов НЕ похоже на определение файла
docker-compose.yaml
Зачем dockerу нужен docker:dind в качестве сервиса?
Если у вас локально установлен Docker, вы можете открыть окно терминала и ввести команду, например:
docker --version
На выходе получится что-то вроде

Однако если я попытаюсь выполнить такие команды, как docker pull
, docker build
или docker run
, то получу ошибку, подобную этой:
Error response from daemon: dial unix docker.raw.sock: connect: connection refusedCannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
На этом этапе позвольте мне процитировать документацию Docker:
Docker использует клиент-серверную архитектуру. Клиент Docker общается с демоном Docker, который выполняет всю работу по созданию, запуску и распространению ваших контейнеров Docker. Клиент Docker и демон могут работать в одной системе, или вы можете подключить клиента Docker к удаленному демону Docker. Клиент Docker и демон взаимодействуют с помощью REST API, через сокеты UNIX или сетевой интерфейс.
На локальном уровне вы не задумываетесь об этом, поскольку и клиент, и демон установлены на одной машине. Но в GitLab все обстоит несколько иначе.
Итак, в GitLab образ docker
— это просто клиент. Образ docker:dind
- это демон Docker, который запускается как служба, предлагая доступные по сети сервисы. Клиент может взаимодействовать с демоном через сетевой интерфейс.
Доступ к контейнеру Docker, запущенному как служба
В предыдущем примере приложение Node.js, открывающее API на порту 3000, было запущено демоном Docker в контейнере docker:dind
.
Поэтому использование localhost не сработает, так как API запущен в другом контейнере. К счастью, GitLab автоматически генерирует имя хоста для соответствующей службы. В данном случае это имя хоста — docker
.
Поэтому мы можем получить доступ к приложению с помощью cURL, адаптировав команду для использования docker
вместо localhost
.
curl http://docker:3000/status | grep "UP"

Однако такой подход громоздок, поскольку теперь мы запускаем Docker в Docker в Docker. Время выполнения только этого задания составляет 61 секунду. Должен быть лучший способ.
Запуск частного контейнера Docker с помощью GitLab services
Ключевое слово services позволяет нам указать как публичный образ Docker, доступный на Dockerhub, так и использовать наш частный GitLab Container Registry.
curl api testing:
stage: acceptance
image: curlimages/curl
services:
- name: $CI_REGISTRY_IMAGE
alias: banking-api
script:
- curl http://banking-api:3000/status | grep "UP"
Я расширил конфигурацию, указав alias
. Я не был уверен, какое имя хоста сгенерирует GitLab, и предпочел указать его самостоятельно.
Запуск приемочных тестов Postman/Newman на основе образа Docker
Следуя принципам, использованным в cURL, я могу добавить новое задание, которое использует публичный образ Newman Docker для запуска существующей коллекции Postman.
postman api testing:
stage: acceptance
image:
name: postman/newman
entrypoint: [""]
services:
- name: $CI_REGISTRY_IMAGE
alias: banking-api
script:
- newman run test/collection.json --env-var "baseUrl=banking-api:3000"
Обратите внимание, что я ввел переменную окружения Postman baseUrl во время выполнения.
Устранение неполадок
Не удается подключиться к демону Docker по адресу unix:///var/run/docker.sock. Запущен ли демон Docker?
Для некоторых исполнителей Docker может потребоваться указать переменную DOCKER_HOST
на уровне конвейера или задания.
variables:
DOCKER_HOST: tcp://docker:2375/
или
variables:
DOCKER_HOST: tcp://docker:2376/
Заключение
Надеюсь, это руководство помогло вам получить доступ к вашему приложению dockerize из вашего CI-конвейера GitLab.
Полезные ссылки:
Получить больше навыков работы с Docker и GitLab CI/CD, пополнить портфолио ценным кейсом и вырасти до нового уровня в своей профессии вы можете на видеокурсах Слёрма:
Обучение начнётся сразу после оплаты, график и темп обучения вы выбираете сами 👌