Записная книжка     О блоге     Архив записей     Лента

Чтобы самому не забыть и другим рассказать

Использование Docker для кросскомпиляции проектов

Docker как незаметный и незаменимый центральный элемент в разработке, Continuous Integration под множество разных целевых платформ. Как не проходить квест по настройке окружения каждый раз.


Важно: все нижесказаное описывает мой опыт работы в основном с embedded linux C/C++ проектами. Просьба помнить это во время чтения.

Содержание

Проблема

Сколько времени должен потратить новый сотрудник на то, чтобы собрать и запустить незнакомый ему проект? Обычно это суммарное время, необходимое на следующие действия:

По дороге может выясниться, что где-то чего-то не хватает в зависимостях, неправильно прописан путь к либе/утилите/тулчейну, не та версия библиотеки установлена, где-то не хватает симлинка, где-то лажа с правами. И пока решишь все эти мелкие проблемы - пройдет значительный кусок времени. Плюс еще по любому будут привлекаться сотрудники, которые уже давно работают с проектом. А те, в свою очередь, со скрипом вспоминают в чем был вопрос, так как проблема была решена полгода назад и о ней уже успели забыть.

Обычно для того, чтобы каждый раз не набивать шишки, пишется некий README/вики-статья, где прописываются точные инструкции, по выполнению которых можно добиться воспроизводимого результата. За пару человек эти инструкции отлаживаются, и наступает счастье.

В идеале хочется, чтобы сборка проекта выглядела подобным образом:

    install all required software, SDKs, libs
    git clone git@project.git
    cd project_dir
    make/cmake/build_project.sh
    deploy
    run

Какие варианты решений я встречал/проходил:

Постановка задачи

Представим следующую ситуацию:

Эволюционно решение предстало в виде контейнеризации окружения. Одна платформа - один контейнер со всем необходимым содержимым. Каждый разработчик может легко скопировать его себе на машину и тут же начать работать. Это несомненно проще, чем проходить квест по настройке/обновлению окружения в соответствии с инструкцией.

Сразу же на поверхность выплыл еще один вопрос: распространение контейнеров. Бегать с флешкой/ходить по сети куда-то - не классно, плюс человеческая натура такова, что даже если десять раз скомандовать в рабочем чатике: “обновляемся!”, - момент действия будет отложен как можно дальше. Возможность централизованной раздачи контейнеров, да еще если это можно сделать автоматически, “незаметно” для разработчика - это будет просто песня!

Из всех вариантов(LXC, OpenVZ, Docker и т.д.), которые были доступны на момент исследований, лишь Docker с его инфраструктурой подошел целиком и полностью.

Решение

Docker

Цитата с вики:

Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы. Позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер, который может быть перенесён на любую Linux-систему с поддержкой cgroups в ядре, а также предоставляет среду по управлению контейнерами.

Как было до внедрения docker’a: были серверы, где располагались настроенные/собранные sdk/библиотеки под конкретные таргеты. Все заинтересованные разработчики логинились туда по ssh, монтировали с сервера на свой локальный хост по nfs/samba/sshfs папки с проектами, открывали их в локальном любимом редакторе. На локальном хосте редактировали, на удаленном сервере - компилировали. Это работало: у всех единое окружение, настраивать его всем каждый раз не надо, для нового разработчика просто заводим новый аккаунт на сервере. Но медленно: проекты большие, IDE постоянно подвисает на сетевых операциях, на сервере всегда кто-то компилирует - ты ждешь пока скомпилятся твои изменения, в то время как твой хост с 4хядерным Intel Core i7 и 16 Gb ОЗУ просто простаивает, печаль:(.

После введения контейнеров, как у нас в компании получилось:

Docker Registry

С офф. сайта:

The Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images. The Registry is open-source, under the permissive Apache license.

В общем-то все сказано:). Централизованное хранение и раздача docker-образов. Можно скидывать образы на какой-то ftp/web-сервер, облако. При таком варианте каждый разработчик должен сам сознательно скачать на свою машину необходимый образ; что-то вроде следующих действий:

wget https://cloud.company.com/dockers/specific_img_version.tar
docker load specific_img_version.tar
rm -f specific_img_version.tar

Где specific_img_version.tar - название экспортированного docker образа.

А можно поднять свой Docker Registry, откуда сам docker клиент будет тянуть требуемый образ.

В общем случае мне кажется логичным использовать для этих целей Docker Hub - аналог GitHub в мире docker контейнеров. Там можно размещать неограниченное количество публичных образов и только один приватный. Большее количество приватных образов можно добавлять за отдельную плату. Если нет ничего секретного - заводим аккаунт, выкладываем образы, которые публичны и доступны всем. Если хотим “приватности” - покупается подписка и можно размещать приватные образы, т.е. публично не доступные. Эти два варианта клевые тем, что сразу снимается вопрос поддержки - за нас это делают админы Docker Hub. В нашем случае это не подходило(политика компании, есть компоненты от третьих сторон, которые мы не имеем права хранить за пределами компании), потому подняли собственный docker registry. Он не такой крутой, как Docker Hub. Нет, например, какого либо веб-интефейса. “Общаться” с сервером можно через rest api. Например, вот как получить список репозиториев при помощи curl’a:

Сам сервер поставляется в виде, что не удивительно, docker-образа и запускается следующим образом:

где:

После выполнения вышеуказанной команды, у вас есть настроенный и готовый для работы registry.

Важное примечание: docker client по умолчанию работает через https. Если у вас нет/не планируется SSL для домена, на котором будет крутиться registry, то на каждом хосте, где будет запускаться docker client, необходимо прописать адрес “небезопасного” docker registry в /etc/docker/daemon.json:

{
  "insecure-registries" : ["registry_ip_without_ssl.com:5000"]
}

По умолчанию docker client всегда “смотрит” на Docker Hub. Для того чтобы он мог пушить и пулить образы с других хранилищ, в имя образа всегда надо добавлять адрес docker registry. Пример имени, который используется у нас в компании:

hub.company.com:5000/chipmaker_name/platform_name:latest

где hub.company.com:5000 - url и порт, на котором работает docker registry, если эта часть пустая, то запросы будут уходить на Docker Hub. Вся дальнейшая часть имени может быть абсолютно произвольной.

Отправить образ в хранилище:

docker push registry_ip:registry_port/image_name:image_version

Получить образ из хранилища:

docker pull registry_ip:registry_port/image_name:image_version

В общем-то, это все команды, которые используются у нас в компании для взаимодействия между docker client и docker registry.

Схема работы такова:

docker push registry_ip:registry_port/image_name:image_version
docker pull registry_ip:registry_port/image_name:image_version
docker rmi e90ae3da554e

Можно, конечно, и по имени:тегу, но у одного образа может быть несколько имен, а хеш - он уникальный.

Запуск:

docker run -it --rm -v /path/to/project/sources:/path/of/sources/inside/container registry_ip:registry_port/image_name:image_version bash

В результате получаем обычную командную строку(в данном случае bash), в которой можно перейти в папку с проектом и вызвать make/cmake/build.sh Можно запустить сразу билд, чтобы ручками не ходить куда-то:

docker run -it --rm -v /path/to/project/sources:/path/of/sources/inside/container registry_ip:registry_port/image_name:image_version bash -c "cd /path/of/sources/inside/container; make/cmake/build.sh;"

Команду выше можно использовать для интеграции с IDE: в каждой приличной IDE есть что-то вроде custom build steps, куда приведенную команду можно прописать.

Все вышеуказанные команды имеют право на жизнь и использование, но … большинству разработчиков они не нужны/интересны. Потому вокруг докера была написана простая обертка на bash.

Скрипт-обертка для запуска docker образов

скрипт запуска:

Как используем скрипт:

cd project_dir
./dockerBuild.sh platformName mode path_to_project optional_cmd

где:

Контейнеры и Continuous Integration(на базе GitLab)

Легкость разворачивания окружения на любом линуксе “спровоцировала” логичное продолжение в виде Continuous Integration. В строю был GitLab, Docker Registry. Для полноценного счастья не хватало GitLab Runner‘a, на котором собственно будет происходить сборка проекта под разные платформы в окружениях, которые будут вытягиваться с Docker Registry: Что происходит по push’у разработчика в репозиторий проекта

Спустя время, на настроенный Runner была навешена еще функция еженочных тестов на текущем срезе кода.

Автоматическая сборка образов

Спустя некоторое время, после того как использование docker образов было поставленно на поток, сборка новых версий стала отнимать много времени. Каждый раз необходимо было делать одни и те же действия по выпуску нового окружения. Эволюционно пришли к мысли: нужен CI для окружений!

Итого:

Patches_repo  
    |-->Platform1  
    |       |-->SDKv1  
    |       |     |-->01_patch_name.patch  
    |       |     |-->02_patch_name.patch  
    |       |     |-->.....  
    |       |     |-->nn_patch_name.patch  
    |       |-->SDKv1.5  
    |             |-->01_patch_name.patch  
    |             |-->02_patch_name.patch  
    |             |-->.....  
    |             |-->nn_patch_name.patch  
    |-->Platform2  
    |       |-->SDKv1  
    |       |     |-->...  
    |       |-->SDKv1.7  
    |             |-->...  
    |-->....  
    |-->PlatformN  
            |-->...  

Как собирается docker образ

Минусы и их решение

Заключение

Теперь алгоритм входа нового сотрудника в проект выглядит так:

Презенташка от доклада для С++ CoreHard Spring 2018 conference: