Docker build t что это
Компиляция контейнеров — Dockerfiles, LLVM и BuildKit
Эта статья конкатенация двух статей Адама Гордона Белла (Adam Gordon Bell) из Earthly. Добавил в основную статью про компиляцию контейнеров BuildKit выдержки из его другой статьи про BuildKit для понимания как это работает и как использовать BuildKit напрямую.
Тынис Тийги (Tõnis Tiigi), сотрудник Docker и основной разработчик BuildKit, создал BuildKit, чтобы отделить логику построения образов от основного проекта moby и обеспечить возможность дальнейшего развития. BuildKit поддерживает подключаемые интерфейсы, которые позволяют создавать не только образы docker из Dockerfiles. С помощью BuildKit мы можем заменить синтаксис Dockerfile на hlb и заменить формат образа докера на вывод в виде чистого tar-файла. Это лишь одна из возможных комбинаций, которые открывает BuildKit с его подключаемыми бэкэндами и интерфейсами.
Введение
docker build
Мы можем создавать образы контейнеров несколькими способами. Мы можем использовать пакеты сборки, мы можем использовать инструменты сборки, такие как Bazel или sbt, но, безусловно, наиболее распространенным способом создания образов является использование docker build с Dockerfile. Таким образом создаются знакомые базовые образы Alpine, Ubuntu и Debian.
Вот пример Dockerfile:
В этом руководстве мы будем использовать вариации этого Dockerfile.
Мы можем собрать его так:
Но что происходит, когда вы вызываете docker build? Чтобы понять это, нам понадобится немного предыстории.
Background
Образ докера состоит из слоев. Эти слои образуют неизменную файловую систему. Образ контейнера также содержит некоторые описательные данные, такие как команда запуска, открываемые порты и монтируемые тома. Когда вы делаете docker run образа, он запускается внутри среды выполнения контейнера.
Docker build использует BuildKit, чтобы превратить Dockerfile в образ докера, образ OCI или другой формат образа. Здесь мы в основном будем использовать BuildKit напрямую.
Как работают компиляторы?
Компиляция классического «Hello, World» на С в ассемблерный код x86 с использованием интерфейса Clang для LLVM выглядит так:
Создание образа из файла докеров работает аналогичным образом:
BuildKit передается Dockerfile и контексту сборки, который является текущим рабочим каталогом на приведенной выше схеме. Проще говоря, каждая строка в файле Dockerfile превращается в слой в итоговом изображении. Одним из существенных отличий построения образа от компиляции является контекст сборки. Входные данные компилятора ограничены исходным кодом, тогда как docker build принимает ссылку на файловую систему хоста в качестве входных данных и использует ее для выполнения таких действий, как COPY.
В чем подвох
На предыдущей диаграмме, где компиляции «Hello, World» отображена за один шаг, упущена важная деталь. Если бы каждый компилятор представлял собой вручную закодированный маппинг с языка высокого уровня на машинный код x86, то переход на процессор Apple M1 был бы довольно сложным, поскольку он имеет другой набор инструкций.
Такой поэтапный подход означает, что вам не нужен новый компилятор для каждой новой машинной архитектуры. Вместо этого вам просто нужен новый backend. Вот пример того, как это выглядит в LLVM:
Промежуточные представления
Когда у вас есть этот IR, у вас есть протокол, который различные фазы компилятора могут использовать в качестве интерфейса, и вы можете создавать не только множество бэкэндов, но и множество интерфейсов (frontend). LLVM имеет интерфейсы для множества языков, включая C, Julia, Objective-C, Rust и Swift.
На практике дело не только в этом. Интерфейсы должны токенизировать и анализировать входные файлы, и они должны возвращать нужные ошибки. Бэкенды часто требуют оптимизации для конкретных целей и эвристики. Но для нас сейчас критическим моментом является то, что стандартное представление превращается в мост, соединяющий многие интерфейсы со многими внутренними интерфейсами. Этот общий интерфейс устраняет необходимость в создании компилятора для каждой комбинации языка и машинной архитектуры. Это простой, но очень полезный трюк!
BuildKit
Образы, в отличие от исполняемых файлов, имеют собственную изолированную файловую систему. Тем не менее, задача создания образа очень похожа на компиляцию исполняемого файла. Они могут иметь различный синтаксис (dockerfile1.0, dockerfile1.2), и результат должен быть нацелен на несколько архитектур компьютеров (arm64 против x86_64).
Это сходство не ускользнуло от внимания создателей BuildKit. BuildKit имеет собственное промежуточное представление LLB. И там, где LLVM IR имеет такие вещи, как вызовы функций и стратегии сборки мусора, LLB имеет монтируемые файловые системы и выполнение операторов.
LLB определяется как буфер протокола, а это означает, что внешние интерфейсы BuildKit могут делать запросы GRPC к buildkitd для непосредственного создания контейнера.
Программное создание образа
Давайте программно сгенерируем LLB для образа, а затем соберем образ.
В этом примере мы будем использовать Go, который позволяет нам использовать существующие библиотеки BuildKit, но это можно сделать на любом языке с поддержкой Protocol Buffer.
Создадим LLB для образа Alpine:
Последнее, что нам нужно сделать, это превратить это в протокол-буфер и выдать его в стандартный вывод:
Давайте посмотрим что сгенерируется, используя параметр dump-llb в buildctl:
Мы получаем этот LLB в формате JSON:
Просматривая вывод, мы видим, как наш код отображается на LLB.
Вот наша COPY как часть FileOp:
Вот отображение контекста нашей сборки для использования в нашей команде COPY:
Точно так же вывод содержит LLB, который соответствует нашим командам RUN и FROM.
Создание нашего LLB
Buildkitd отвечает за создание образа, но фактическое выполнение каждого шага выполняет runc. Runc выполняет каждую команду RUN в вашем файле докеров в отдельном процессе. Runc требует ядра Linux 5.2 или более поздней версии с поддержкой cgroups, поэтому buildkitd не может работать изначально в macOS или Windows.
Как устанавливать описано тут — не будем останавливаться. В примере ниже описан способ через docker как более универсальный, так как запустить buildcid в macOS или Windows.
В реальном инструменте мы могли бы захотеть программно убедиться, что buildkitd запущен, и отправить RPC-запрос прямо ему, а также предоставить понятные сообщения об ошибках. Сейчас мы все это пропустим.
Мы можем запустить с тем же результатом:
Затем мы можем увидеть результаты наших программных команд COPY и RUN:
Пример полного кода может стать отличной отправной точкой для создания вашего собственного программного образа докера.
frontend для BuildKit
Создание собственного примера внешнего интерфейса для docker build
Создание токенизатора и парсера как службы gRPC выходит за рамки этой статьи. Но мы можем научится этому, извлекая и изменяя существующий интерфейс. Стандартный интерфейс Dockerfile легко отделить от проекта moby. Я вытащил соответствующие части в отдельный репозиторий. Давайте внесем в него несколько изменений и проверим.
До сих пор мы использовали только команды докера FROM, RUN и COPY. Поверхностно синтаксис Dockerfile с его командами, начинающимися с заглавной буквы, очень похож на язык программирования INTERCAL. Давайте изменим эти команды на их эквивалент INTERCAL и разработаем наш собственный формат Ickfile.
Погружаемся в Docker: Dockerfile и коммуникация между контейнерами
В прошлой статье мы рассказали, что такое Docker и как с его помощью можно обойти Vendor–lock. В этой статье мы поговорим о Dockerfile как о правильном способе подготовки образов для Docker. Также мы рассмотрим ситуацию, когда контейнерам нужно взаимодействовать друг с другом.
В InfoboxCloud мы сделали готовый образ Ubuntu 14.04 с Docker. Не забудьте поставить галочку «Разрешить управление ядром ОС» при создании сервера, это требуется для работы Docker.
Dockerfile
Подход docker commit, описанный в предыдущей статье, не является рекомендованным для Docker. Его плюс состоит в том, что мы настраиваем контейнер практически так, как привыкли настраивать стандартный сервер.
Вместо этого подхода мы рекомендуем использовать подход Dockerfile и команду docker build. Dockerfile использует обычный DSL с инструкциями для построения образов Docker. После этого выполняется команда docker build для построения нового образа с инструкциями в Dockerfile.
Написание Dockerfile
Давайте создадим простой образ с веб-сервером с помощью Dockerfile. Для начала создадим директорию и сам Dockerfile.
Созданная директория — билд-окружение, в которой Docker вызывает контекст или строит контекст. Docker загрузит контекст в папке в процессе работы Docker–демона, когда будет запущена сборка образа. Таким образом будет возможно для Docker–демона получить доступ к любому коду, файлам или другим данным, которые вы захотите включить в образ.
Добавим в Dockerfile информацию по построению образа:
Также Dockerfile поддерживает комментарии. Любая строчка, начинающаяся с # означает комментарий.
Первая инструкция в Dockerfile всегда должна быть FROM, указывающая, из какого образа нужно построить образ. В нашем примере мы строим образ из базового образа ubuntu версии 14:04.
Далее мы указываем инструкцию MAINTAINER, сообщающую Docker автора образа и его email. Это полезно, чтобы пользователи образа могли связаться с автором при необходимости.
Инструкция RUN исполняет команду в конкретном образе. В нашем примере с помощью ее мы обновляем APT репозитории и устанавливаем пакет с NGINX, затем создаем файл /usr/share/nginx/html/index.html.
Мы используем этот формат для указания массива, содержащего команду для исполнения и параметры команды.
Далее мы указываем инструкцию EXPOSE, которая говорит Docker, что приложение в контейнере должно использовать определенный порт в контейнере. Это не означает, что вы можете автоматически получать доступ к сервису, запущенному на порту контейнера (в нашем примере порт 80). По соображениям безопасности Docker не открывает порт автоматически, но ожидает, когда это сделает пользователь в команде docker run. Вы можете указать множество инструкций EXPOSE для указания, какие порты должны быть открыты. Также инструкция EXPOSE полезна для проброса портов между контейнерами.
Строим образ из нашего файла
, где trukhinyuri – название репозитория, где будет храниться образ, nginx – имя образа. Последний параметр — путь к папке с Dockerfile. Если вы не укажете название образа, он автоматически получит название latest. Также вы можете указать git репозиторий, где находится Dockerfile.
В данном примере мы строим образ из Dockerfile, расположенном в корневой директории Docker.
Что произойдет, если инструкция не исполнится?
Давайте переименуем в Dockerfile nginx в ngin и посмотрим.
Использования кеша сборок для шаблонизации
Используя кеш сборок можно строить образы из Dockerfile в форме простых шаблонов. Например шаблон для обновления APT-кеша в Ubuntu:
Инструкция ENV устанавливает переменные окружения в образе. В данном случае мы указываем, когда шаблон был обновлен. Когда необходимо обновить построенный образ, просто нужно изменить дату в ENV. Docker сбросит кеш и версии пакетов в образе будут последними.
Инструкции Dockerfile
Давайте рассмотрим и другие инструкции Dockerfile. Полный список можно посмотреть тут.
Инструкция CMD указывает, какую команду необходимо запустить, когда контейнер запущен. В отличие от команды RUN указанная команда исполняется не во время построения образа, а во время запуска контейнера.
ENTRYPOINT
Часто команду CMD путают с ENTRYPOINT. Разница в том, что вы не можете перегружать ENTRYPOINT при запуске контейнера.
При запуске контейнера параметры передаются команде, указанной в ENTRYPOINT.
Можно комбинировать ENTRYPOINT и CMD.
WORKDIR
С помощью WORKDIR можно установить рабочую директорию, откуда будут запускаться команды ENTRYPOINT и CMD.
Специфицирует пользователя, под которым должен быть запущен образ. Мы можем указать имя пользователя или UID и группу или GID.
VOLUME
Инструкция VOLUME добавляет тома в образ. Том — папка в одном или более контейнерах или папка хоста, проброшенная через Union File System (UFS).
Тома могут быть расшарены или повторно использованы между контейнерами. Это позволяет добавлять и изменять данные без коммита в образ.
В примере выше создается точка монтирования /opt/project для любого контейнера, созданного из образа. Таким образом вы можете указывать и несколько томов в массиве.
Инструкция ADD добавляет файлы или папки из нашего билд-окружения в образ, что полезно например при установке приложения.
Источником может быть URL, имя файла или директория.
В последнем примере архив tar.gz будет распакован в /var/www/wordpress. Если путь назначения не указан — будет использован полный путь включая директории.
Инструкция COPY отличается от ADD тем, что предназначена для копирования локальных файлов из билд-контекста и не поддерживает распаковки файлов:
ONBUILD
Инструкция ONBUILD добавляет триггеры в образы. Триггер исполняется, когда образ используется как базовый для другого образа, например, когда исходный код, нужный для образа еще не доступен, но требует для работы конкретного окружения.
Коммуникация между контейнерами
В предыдущей статье было показано, как запускать изолированные контейнеры Docker и как пробрасывать файловую систему в них. Но что, если приложениям нужно связываться друг с другом. Есть 2 способа: связь через проброс портов и линковку контейнеров.
Проброс портов
Такой способ связи уже был показан ранее. Посмотрим на варианты проброса портов чуть шире.
Когда мы используем EXPOSE в Dockerfile или параметр -p номер_порта – порт контейнера привязывается к произвольному порту хоста. Посмотреть этот порт можно командой docker ps или docker port имя_контейнера номер_порта_в_контейнере. В момент создания образа мы можем не знать, какой порт будет свободен на машине в момент запуска контейнера.
Можно привязать UDP порты, указав /udp:
Линковка контейнеров
Связь через сетевые порты — лишь один способ коммуникации. Docker предоставляет систему линковки, позволяющую связать множество контейнеров вместе и отправлять информацию о соединении от одного контейнера другому.
Префикс DB_ был взят из alias контейнера.
Можно просто использовать информацию из hosts, например команда ping db (где db – alias) будет работать.
Заключение
В этой статье мы научились использовать Dockerfile и организовывать связь между контейнерами. Это только вершина айсберга, очень многое осталось за кадром и будет рассмотрено в будущем. Для дополнительного чтения рекомендуем книгу The Docker Book.
Готовый образ с Docker доступен в облаке InfoboxCloud.
В случае, если вы не можете задавать вопросы на Хабре, можно задать в Сообществе InfoboxCloud.
Если вы обнаружили ошибку в статье, автор ее с удовольствием исправит. Пожалуйста напишите в ЛС или на почту о ней.
Как работать с Docker: упаковка Spring Boot приложения в контейнер
Немного разбираемся с теорией и проверяем на практике.
Эта статья посвящена основам Docker и раскрывает азы работы с контейнерами. Мы изучим базовые определения и самые необходимые команды и даже разработаем и развернём простейшее Java-приложение.
Что такое Docker
Docker — инструмент, предназначенный для быстрой разработки, доставки и развёртывания приложений. Он позволяет упаковать приложение вместе со всеми его зависимостями в так называемый контейнер, а затем запустить его в любой среде.
Идея контейнеризации состоит в том, что на одной машине может разворачиваться множество таких контейнеров с приложениями. Для каждого из них в операционной системе выделяется изолированная область — осуществляется виртуализация на уровне ОС.
Важный момент: все контейнеры запускаются одинаковым способом вне зависимости от того, что находится внутри. Это напоминает контейнеры для морских перевозок — с виду они одинаковы, но внутри могут храниться совершенно разные грузы.
Разрабатывает приложения на Java, воспитывает двух котов: Котлин и Монго.
Основные понятия
Образ — некий шаблон, на основе которого создаются контейнеры. Содержит всё необходимое для запуска приложения. Сюда относятся код, системные утилиты, библиотеки, настройки и так далее. Образ можно представить в виде набора слоёв, которые накладываются друг на друга. Каждый последующий добавляет, изменяет или удаляет файлы предыдущего слоя.
DockerfIle — текстовый файл с набором инструкций по созданию образа, каждая из которых добавляет к образу новый слой.
Контейнер — конкретный экземпляр приложения, созданный на основе образа. Причём из одного образа можно создать сколько угодно контейнеров. Технически контейнер создаётся путём добавления к образу нового слоя, содержащего результаты работы приложения.
Реестр — хранилище образов (как GitHub для кода приложений). Образы можно скачивать из реестра и создавать на их основе контейнеры. Также в реестр можно загружать новые или изменённые образы для дальнейшего использования.
Пример использования
Давайте разработаем простое Spring Boot приложение, создадим на его основе образ и развернём контейнер на локальной машине. Это делается в три простых шага:
Что такое Docker, и как его использовать? Подробно рассказываем
Разберем по косточкам, ведь Docker – это мощный инструмент, и огромное количество информации по работе с ним вряд ли уместится в брошюрку.
Что такое Docker?
Это ПО с открытым кодом, принцип работы которого проще всего сравнить с транспортными контейнерами. Только подумайте, ведь когда-то транспортные компании сталкивались с похожими проблемами:
С введением контейнеров стало возможным перевозить вместе кирпичи и стекло, химикаты и еду, а также многое другое. Груз разного размера может быть распределен по стандартизированным контейнерам, которые загружаются/выгружаются одним и тем же транспортным средством.
Но вернемся же к контейнерам. Когда вы разрабатываете приложение, вам нужно предоставить код вместе со всеми его составляющими, такими как библиотеки, сервер, базы данных и т. д. Вы можете оказаться в ситуации, когда приложение работает на вашем компьютере, но отказывается включаться на устройстве другого пользователя.
Эта проблема решается через создание независимости ПО от системы.
В чем отличие от виртуализации?
Изначально виртуализация была призвана избавить от подобных проблем, но в ней есть существенные недостатки:
Докер же просто разделяет ядро ОС на все контейнеры (Docker container), работающие как отдельные процессы. Это не единственная подобная платформа, но, бесспорно, одна из самых популярных и востребованных.
Какие очевидные плюсы?
К его преимуществам относятся:
Поддерживаемые платформы
Докер работает не только на его родной ОС, Linux, но также поддерживается Windows и macOS. Единственное отличие от взаимодействия с Linux в том, что на macOS и Windows платформа инкапсулируется в крошечную виртуальную машину. На данный момент Докер для macOS и Windows достиг значительного уровня удобства в использовании.
Кроме того, существует множество дополнительных приложений, таких как Kitematic или Docker Machine, которые помогают устанавливать и использовать Докер на платформах, отличных от Linux.
Установка
Здесь можно посмотреть подробную инструкцию по установке. Если вы работаете с Докером на ОС Linux, вам нужно выполнить несколько несложных действий и повторно войти в систему:
Терминология
1. Контейнер – это исполняемый экземпляр, который инкапсулирует требуемое программное обеспечение. Он состоит из образов. Его можно легко удалить и снова создать за короткий промежуток времени.
2. Образ – базовый элемент каждого контейнера. В зависимости от образа, может потребоваться некоторое время для его создания.
3. Порт – это порт TCP/UDP в своем первоначальном значении. Чтобы все было просто, предположим, что порты могут быть открыты во внешнем мире или подключены к контейнерам (доступны только из этих контейнеров и невидимы для внешнего мира).
4. Том – описывается как общая папка. Тома инициализируются при создании контейнера и предназначены для сохранения данных, независимо от жизненного цикла контейнера.
5. Реестр – это сервер, на котором хранятся образы. Сравним его с GitHub: вы можете вытащить образ из реестра, чтобы развернуть его локально, и так же локально можете вносить в реестр созданные образы.
6. Docker Hub – публичный репозиторий с интерфейсом, предоставляемый Докер Inc. Он хранит множество образов. Ресурс является источником «официальных» образов, сделанных командой Докер или созданных в сотрудничестве с разработчиком ПО. Для официальных образов перечислены их потенциальные уязвимости. Эта информация открыта для любого зарегистрированного пользователя. Доступны как бесплатные, так и платные аккаунты.
Пример 1: Hello World
Пришло время запустить наш первый контейнер:
Теперь попробуем создать интерактивную оболочку внутри контейнера:
Если вы хотите, чтобы контейнер работал после окончания сеанса, вам необходимо его «демонизировать»:
Давайте посмотрим, какие контейнеры у нас есть на данный момент:
ps показывает нам, что у нас есть два контейнера:
Давайте проверим журналы и посмотрим, что делает контейнер-демон прямо сейчас:
Теперь давайте остановим контейнер-демон:
Проверяем его остановку:
Контейнер остановлен. Давайте запустим его снова:
Убедимся, что он запущен:
Теперь остановим его и удалим все контейнеры вручную:
Чтобы удалить все контейнеры, мы можем использовать следующую команду:
Пример 2: Nginx
Начиная с этого примера, вам понадобятся дополнительные файлы, которые вы можете найти в репозитории GitHub. Как вариант, загрузите образцы файлов по ссылке.
Пришло время создать и запустить более важный контейнер, такой как Nginx.
Измените каталог на examples/nginx:
Теперь проверьте этот URL-адрес в своем веб-браузере.
Еще мы можем попробовать изменить /example/nginx/index.html (который добавляется в каталог /usr/share/nginx/html внутри контейнера) и обновить страницу.
Получим информацию о контейнере test-nginx:
Эта команда отображает системную информацию об установке Докер. Она включает версию ядра, количество контейнеров и образов, открытые порты и т. д.
Пример 3: запись Dockerfile
Чтобы создать образ, сперва вам нужно создать Dockerfile: это текстовый файл с инструкциями и аргументами. Краткое описание инструкций, которые мы собираемся использовать в примере:
Более подробная информация здесь.
Давайте создадим образ, который получит содержимое сайта и сохранит его в текстовом файле. Нам нужно передать URL-адрес через переменную SITE_URL. Результирующий файл будет помещен в каталог, установленный как том:
Dockerfile готов, пришло время создать образ.
Создание образа
Перейдите к examples/curl и выполните следующую команду:
Теперь у нас есть новый образ, и мы можем его увидеть в списке существующих:
Мы можем создавать и запускать контейнер из образа. Давайте попробуем сделать это с параметрами по умолчанию:
Чтобы просмотреть результаты, сохраненные в файле:
Попробуем с facebook.com:
Чтобы просмотреть результаты, сохраненные в файле:
Рекомендации по созданию образов
Соединение между контейнерами
Пример 4: Python + Redis
В этом примере мы подключим контейнеры Python и Redis.
Перейдем к examples/compose и выполним команду:
Текущий пример увеличит счетчик просмотров в Redis. Откройте ссылку и убедитесь в этом.
Использование docker-compose – это тема для целого учебника. Чтобы начать работу, вы можете поиграться с некоторыми образами из Docker Hub, а если хотите создать свои собственные – следуйте рекомендациям, перечисленным выше. Единственное, что можно добавить с точки зрения использования docker-compose – всегда давайте явные имена вашим томам. Это простое правило избавит вас от проблемы в будущем.
В этом случае redis_data будет именем внутри файла docker-compose.yml.
Смотрим выполнение тома:
Без явного имени тома будет UUID. И вот пример:
В заключение
Докер стал одним из важнейших инструментов современного разработчика. Да, он имеет некоторые ограничения и требования в зависимости от архитектуры вашей системы, но немного усидчивости – и мир контейнеров обязательно будет приручен!