Создание скомпилированного приложения с помощью Docker

Я создаю сервер, написанный на С++, и хочу развернуть его с помощью Docker с docker-compose. Что такое "правильный путь"? Должен ли я вызывать make из Dockerfile или создавать вручную, загружать на какой-то сервер, а затем COPY двоичные файлы из Dockerfile?

Ответ 1

У меня были трудности с автоматизацией нашей сборки с помощью docker-compose, и я в итоге использовал docker build для всего:

Три слоя для строительства

Выполнить → разработать → построить

Затем я копирую результаты сборки в образ 'deploy':

Выполнить → развернуть

Четыре слоя для игры:

Бежать
  • Содержит все пакеты, необходимые для запуска приложения - например, libsqlite3-0
развивать
  • FROM <projname>:run
  • Содержит пакеты, необходимые для сборки
    • например, g++, cmake, libsqlite3-dev
  • Dockerfile выполняет любые внешние сборки
    • например, шаги для сборки boost-python3 (не в репозиториях менеджера пакетов)
строить
  • FROM <projname>:develop
  • Содержит источник
  • Dockerfile выполняет внутреннюю сборку (код, который часто меняется)
  • Встроенные двоичные файлы копируются из этого образа для использования при развертывании
развертывание
  • FROM <projname>:run
  • Вывод сборки скопирован в образ и установлен
  • RUN или ENTRYPOINT используется для запуска приложения

Структура папок выглядит следующим образом:

.
├── run
│   └── Dockerfile
├── develop
│   └── Dockerfile
├── build
│   ├── Dockerfile
│   └── removeOldImages.sh
└── deploy
    ├── Dockerfile
    └── pushImage.sh

Настройка сервера сборки означает выполнение:

docker build -f run -t <projName>:run
docker build -f develop -t <projName>:develop

Каждый раз, когда мы делаем сборку, это происходит:

# Execute the build
docker build -f build -t <projName>:build

# Install build outputs
docker build -f deploy -t <projName>:version

# If successful, push deploy image to dockerhub
docker tag <projName>:<version> <projName>:latest
docker push <projName>:<version>
docker push <projName>:latest

Я отсылаю людей к Dockerfiles как документацию о том, как собрать/запустить/установить проект.

Если сборка не удалась и выходной файл недостаточен для исследования, я могу запустить /bin/bash в <projname>:build и <projname>:build что пошло не так.


Я собрал GitHub репозиторий вокруг этой идеи. Это хорошо работает для C++, но вы, вероятно, можете использовать его для чего угодно.


Я не исследовал эту функцию, но @TaylorEdmiston указал, что мой шаблон здесь очень похож на многоэтапные сборки, о которых я не знал, когда придумал это. Это выглядит как более элегантный (и лучше задокументированный) способ достижения того же самого.

Ответ 2

Я рекомендую полностью разработать, собрать и протестировать сам контейнер. Это подтверждает философию Docker о том, что среда разработчика аналогична производственной среде, см . Рабочая станция современного разработчика на MacOS с Docker.

Особенно в случае приложений C++, где обычно есть зависимости с общими библиотеками/объектными файлами.

Я не думаю, что пока существует стандартизированный процесс разработки для разработки, тестирования и развертывания приложений C++ в Docker.

Чтобы ответить на ваш вопрос, то, как мы делаем это сейчас, так это то, что контейнер воспринимается как среда разработки и применяется ряд практических мер в команде, таких как:

  1. Наша кодовая база (кроме конфигурационных файлов) всегда живет на общем томе (на локальной машине) (версия Git)
  2. Общие/зависимые библиотеки, двоичные файлы и т.д. Всегда находятся в контейнере
  3. Создайте и протестируйте в контейнере и перед фиксацией изображения очистите ненужные объектные файлы, библиотеки и т.д. И убедитесь, что изменения в docker diff соответствуют ожидаемым.
  4. Изменения/обновления среды, включая общие библиотеки, зависимости, всегда документируются и сообщаются команде.

Ответ 3

То, как я буду делать это, - это запустить сборку за пределами вашего контейнера и только скопировать выходные данные сборки (ваши двоичные и любые необходимые библиотеки) в ваш контейнер. Затем вы можете загрузить свой контейнер в реестр контейнера (например, использовать размещенный или запустить свой собственный), а затем вытащить из этого реестра на свои производственные машины. Таким образом, поток может выглядеть следующим образом:

  • построить двоичный
  • проверка/проверка работоспособности самого двоичного файла
  • создать изображение контейнера с двоичным
  • test/sanity - проверьте изображение контейнера с помощью двоичного файла
  • загрузить в реестр контейнеров
  • развертывание на стадии/тестирование/qa, извлечение из реестра
  • развертывание в prod, извлечение из реестра

Поскольку важно, чтобы вы тестировали перед производственным развертыванием, вы хотите протестировать точно то же самое, что вы будете развертывать в процессе производства, поэтому вы не хотите извлекать или модифицировать изображение Docker каким-либо образом после его создания.

Я бы не запускал сборку внутри контейнера, который вы планируете развернуть в prod, так как тогда ваш контейнер будет иметь всевозможные дополнительные артефакты (такие как выходы временной сборки, инструменты и т.д.), которые вам не нужны в производстве и без необходимости увеличивайте изображение контейнера с помощью вещей, которые вы не будете использовать для развертывания.

Ответ 4

Хотя решения, представленные в других ответах - и, в частности, предложение Мишы Брукман в комментариях к этому ответу об использовании одного Dockerfile для разработки и одного для производства - будут считаться идиоматическими в момент написания вопроса, следует Следует отметить, что проблемы, которые они пытаются решить, и, в частности, проблема очистки среды сборки для уменьшения размера изображения при сохранении возможности использования одной и той же среды контейнера при разработке и производстве, были эффективно решены несколькими Этап сборки, которые были введены в Docker 17.05.

Идея состоит в том, чтобы разделить Dockerfile на две части, одна из которых основана на вашей любимой среде разработки, такой как полноценный базовый образ Debian, который связан с созданием двоичных файлов, которые вы хотите развернуть в конце день, и другой, который просто запускает встроенные двоичные файлы в минимальной среде, такой как Alpine.

Таким образом, вы избегаете возможных несоответствий между средой разработки и производством, на которые ссылается blueskin в одном из комментариев, при этом гарантируя, что ваш производственный образ не будет загрязнен инструментами разработки.

В документации приведен следующий пример многоэтапной сборки приложения Go, которое вы затем перенесете в среду разработки C++ (с одной оговоркой, что Alpine использует musl, поэтому вам следует быть осторожным при компоновке в среде разработки).

FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html  
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]