Як тестувати контейнери RoR з GitLab CI у контейнері

Чим гарний 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. Але це предмет зовсім іншої статті.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.