Довольно часто мы используем 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. 

Полезные ссылки:


Окунись в мир практических задач по CI/CD, пополни портфолио реальными кейсами
и вырасти до нового уровня в своей профессии на Навыкуме по CI/CD в Слёрме.

Навыкум позволит тебе:

  • Наработать около 20 часов практики.

  • Добавить в своё резюме и базовые, и реальные кейсы по CI/CD.

  • «Подсмотреть» лучшие практики в свою копилку знаний.

  • Попасть в комьюнити сильных специалистов с чатом поддержки. Доступ к нему навсегда.

???? Оставить заявку

Комментарии (2)


  1. vovochkin
    05.12.2023 17:11
    +1

    Изображение или изображения, указанные в службах, должны открывать службу на заданном порту

    Интересно, как же изображения смогут открыть службу на заданном порту? Такой себе перевод:(


  1. mmaks17
    05.12.2023 17:11

    зачем запускать контейнер полы в раннее к8с и так контейнеры ?

    не проще на 1 этапе билдить образ

    а на втором запускать пайплайн с image:¢собранный образ ?