Как стать автором
Обновить

Как тестировать контейнеры RoR с GitLab CI в контейнере

Время на прочтение 5 мин
Количество просмотров 5.2K
Чем хорош GitLab, так это тем, что будучи по габаритам слоном в посудной лавке, он умеет аккуратно устанавливаться и почти всегда работает с коробки. Но плохо умеет восстанавливаться и заботиться о себе, когда очень прямые руки вроде моих нарушают привычное ему окружение. Не буду углубляться в то, как мне удавалось убить его до состояния, когда даже удаление и установка с нуля не помогает, но во избежания очередной бесконечной эпопеи с дебагом и переустановкой сервера я вынес все это дело в Docker контейнер. Удобно — на рабочей машине нет миллиона зависимостей, примонтировал директории для репозиториев, логов и базы данных и все работает. Восстановление — пересоздать контейнер и скормить бэкап (кстати, не забудьте проверить свои бэкапы, как говорит опыт GitLab, это не лишнее).

С другой стороны, есть разрабатываемое Rails приложение, которое на реальной машине держит только код; Rails, gems, и все остальное покоится в Docker контейнере. Для своей работы оно использует Redis и Postgres, каждый находится в своем контейнере. Для каждого контейнера примонтирована директория, чтобы важные для приложения данные не оставались внутри.



Задача в том, чтобы Gitlab CI нормально отработал. Вроде все просто, но — он сам находится в контейнере.

Кстати, docker-compose.yml к rails приложению
services:
  postgres:
    image: postgres:9.4.5
    volumes:
      - /var/db/test/postres:/var/lib/postgresql/data
    env_file: .env
    ports:
      - "54321:5432"
  redis:
    image: redis:latest
    volumes:
      - /var/db/test/redis:/data
  app:
    build: .
    env_file: .env
    volumes:
      - /var/www/test:/var/www/test
    ports:
      - "3000"
    links:
      - postgres
      - redis

В .env файле лежит например пароль к базе данных, POSTGRES_PASSWORD. Здесь можно посмотреть, какие переменные используются в контейнере postgresql. Вместо ports можно использовать expose, но для мониторинга с хоста лучше открыть окно в мир.

К Redis'у замечаний нет вообще — подключил, он сам сделал expose на 6379, и работает. Всем бы так.

Первая мысль — инсталлировать Docker в Docker'е. Я верю в человечество, и в то, что в мире есть человек, который за разумное время сделал настоящую вложенность, с установкой Docker в контейнер, которая реально работает, но лично мне это не удалось. Значит, нужен второй вариант — тестировать извне: все контейнеры должны находиться рядом с контейнером GitLab, автоматически создаваться и уничтожаться системой CI изнутри контейнера.

Для тестов нам не нужно монтировать внешние директории, все равно данные одноразовые. После тестов все контейнеры автоматически останавливаются.

Шарим Docker


Не рекомендуется давать недоверенным программам в контейнерах доступ к Docker'у на хосте. Имея таковой, можно легко запустить создание другого контейнера на хосте, который примонтирует нужную директорию или даже целый том (или devices, к примеру веб-камеру или принтер), установит нужный злоумышленнику код, и неспешно вытянет все ваши биткоины, и все это не покидая уютной песочницы. Но поскольку я тестирую свой код, предостережением можно пренебречь.

Чтобы позволить контейнеру управлять своим отцом, нужно пробросить ему родительский сокет. В идеальном мире это делается так:

docker run .. -v /var/run/docker.sock:/var/run/docker.sock

но для тех, кто не ищет в жизни легких путей, оно не работает. Нужно во-первых добавить еще и

-v /usr/bin/docker:/usr/bin/docker

потом обнаружить, что это работает только если бинарники докера на хосте статические (не спрашивайте), иначе придется позаботиться о каждой библиотеке в этой директории отдельно. Если your host's docker binary is not static (не спрашивайте!), как у меня, значит работаем дальше.

Смотрим список библиотек в /usr/bin/docker:

ldd /usr/bin/docker

linux-vdso.so.1 (0x00007fffb9ff4000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f711fe27000)
libltdl.so.7 => /usr/lib/x86_64-linux-gnu/libltdl.so.7 (0x00007f711fc1d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f711f871000)
/lib64/ld-linux-x86-64.so.2 (0x000055a205b12000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f711f66d000)

Ленивым
Мне понадобилась только ibltdl.so.7 библиотека, все остальное в контейнере было, все таки Gitlab это вам не Alphine Linux. Проверить можно с помощью консоли контейнера, вызывая в ней docker, она вам скажет, какой библиотеки нет. Подключите нужную и пробуйте еще. Если вам не кажется, что такой путь проще, монтируйте все. ОН простит.

Прописав в docker-compose.yml Gitlab'а (не проекта, не перепутайте) пути, которые кажутся интуитивными, и попробовав стартовать контейнер Gitlab:

volumes:
    ...
    - /var/run/docker.sock:/var/run/docker.sock
    - /usr/bin/docker:/usr/bin/docker
    - /usr/bin/docker/libltdl.so.7:/usr/bin/docker/libltdl.so.7

docker при старте разразился страшной руганню, которая была столь груба, что почти каждое слово экранировалось семижды!

ERROR: for gitlab  Cannot start service gitlab: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"process_linux.go:359: container init caused \\\"rootfs_linux.go:53: mounting \\\\\\\"/usr/bin/docker/libltdl.so.7\\\\\\\" to rootfs \\\\\\\"/var/lib/docker/devicemapper/mnt/2df228e042aed186eebbb484989e44cee3126c0a3bfb42d25c8998ada5afb9bd/rootfs\\\\\\\" at \\\\\\\"/usr/bin/docker/libltdl.so.7\\\\\\\" caused \\\\\\\"stat /usr/bin/docker/libltdl.so.7: not a directory\\\\\\\"\\\"\"\n"

Решение «в лоб»:

volumes:
    ...
    - /var/run/docker.sock:/var/run/docker.sock
    - /usr/bin/docker:/usr/bin/docker
    - /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7

То есть нам нужны пути к файлам с правой части таблички с библиотеками.

docker-compose.yml от Gitlab
gitlab:
    image: 'gitlab/gitlab-ce:8.14.5-ce.0'
    restart: unless-stopped
    hostname: 'git.habrahabr.ru'
    environment:
        GITLAB_OMNIBUS_CONFIG: "external_url 'http://git.habrahabr.ru'"
    ports:
        - "3456:80" # если ваш контейнер не один претендует на 80-й порт.
        - "52022:22"
    volumes:
        - /docker/gitlab/data/conf:/etc/gitlab
        - /docker/gitlab/data/logs:/var/log/gitlab
        - /docker/gitlab/data/data:/var/opt/gitlab
        - /var/run/docker.sock:/var/run/docker.sock
        - /usr/bin/docker:/usr/bin/docker
        - /usr/lib/x86_64-linux-gnu/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7

Создаем .gitlab-ci.yml


image: "ruby:2.3"
services:
  - redis:latest
  - postgres:9.4.5

cache:
  paths:
    - vendor/ruby

before_script:
  - apt-get update -q && apt-get install nodejs -yqq # стоит это откоментирровать сразу. Сохраните 5-10 минут жизни.
  - gem install bundler  --no-ri --no-rdoc
  - bundle install -j $(nproc) --path vendor

rspec:
  stage: test
  script:
    - bundle exec rake db:create
    - bundle exec rake db:migrate
    - bundle exec rake db:seed
    - rspec spec

Чтобы верно создать этот файл, лучше всего смотреть на docker-compose.yml приложения. К примеру, контейнер postgres — подключать volumes нам не нужно, env файл с паролем к базе данных не нужен (нам ведь все равно, какой пароль в базы, которая живет 10 секунд), порты наружу тоже. То есть остается только указать имя образа (image). Redis: volumes не нужны, так что только имя образа. Главный образ это ruby, в который Gitlab смонтирует код, и запустит before_script.

Вместо эпилога


На настроенный подобным образом GitLab можно навернуть что угодно, от Registry образов до Docker-in-docker решений. Например, вместо разворачивания кода в ruby контейнере средствами GitLab создать решение на основе gitlab/dind, в котором запустить docker build. Но это предмет совсем другой статьи.
Теги:
Хабы:
+8
Комментарии 2
Комментарии Комментарии 2

Публикации

Истории

Работа

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн