Как создавать контейнеры на базе Arm и мультиархитектурных процессоров уже сегодня

Использование процессоров Arm продолжает расти благодаря таким вещам, как экземпляры EC2 на базе AWS Graviton и ноутбуки Apple M1 MacBook. Это имеет очевидные преимущества, такие как лучшая производительность, повышенная эффективность и более низкие затраты.

Разработчики, использующие Docker локально или в производстве, сталкиваются с новыми проблемами при переходе на Arm. Они хотят создавать образы, которые будут работать на своей машине M1. В то же время компании хотели бы предоставлять образы, которые можно создать один раз и запустить в любом месте (т.е. мультиархитектурные/мультиплатформенные).

Существующие сегодня методы создания образов Arm или мультиархитектурных образов имеют громоздкие проблемы и компромиссы.

В этом посте мы рассмотрим существующие варианты создания контейнеров Arm и мультиархитектурных контейнеров, а также то, как мы упростили эти варианты с помощью Depot.

Спойлер: мы предоставляем полностью управляемые, размещенные на хостинге сборщики Docker для процессорных платформ x86 и Arm, поэтому ваши сборки для любой или обеих архитектур всегда будут изначально быстрыми, без дополнительных настроек с вашей стороны!

Текущее состояние дел

Современный движок, поддерживающий Docker, BuildKit, может создавать образ для архитектуры, соответствующей вашей машине. Если вы запустите docker buildx build локально, BuildKit создаст образ, который будет работать на вашей машине.

Если вы хотите собрать образ для архитектуры, которая не соответствует архитектуре вашей машины, вы можете использовать флаг --platform для выбора целевой архитектуры:

# build for a single architecture
docker buildx build --platform=linux/arm64 .
Войти в полноэкранный режим Выйти из полноэкранного режима

По умолчанию BuildKit выбирает эмуляцию для сборки образа Docker, если целевая архитектура отличается от вашей машины. BuildKit использует проект QEMU для эмуляции различных целевых архитектур, когда это необходимо.

BuildKit также поддерживает многоплатформенные сборки, когда вы можете собрать один манифест образа, содержащий два или более образов, ориентированных на разные архитектуры. Это задается в CLI аналогично описанному выше, но с несколькими архитектурами в списке:

# build for multiple architectures
docker buildx build --platform=linux/amd64,linux/arm64 .
Войти в полноэкранный режим Выйти из полноэкранного режима

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

Эмуляция может быть очень медленной. Поэтому BuildKit также поддерживает разгрузку сборки на удаленные экземпляры сборщика через SSH-соединение с другим экземпляром Docker или подключение к стручку, запущенному в Kubernetes.

Примечание: BuildKit также поддерживает запуск контейнерной версии себя на локальном хосте, хотя это не совсем полезно для нашего кроссплатформенного случая.

Передав флаг CLI, вы можете создавать образы для процессоров Intel или Arm, или для обоих, независимо от родной архитектуры вашей текущей машины. Но каждый подход имеет свои компромиссы, связанные с производительностью сборки и сложностью инфраструктуры.

Давайте рассмотрим эти подходы.

Вариант №1: Медленная эмуляция

Если сборка запрашивает архитектуру, отличную от архитектуры родного хоста, BuildKit использует свою копию QEMU для эмуляции целевого процессора. Этот вариант не требует изменений в Dockerfile.

Давайте посмотрим, что происходит в нашем примере с несколькими архитектурами, описанном выше.

docker build --platform=linux/arm64,linux/amd64 .
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы запускаете эту сборку на обычном CI-провайдере, то, скорее всего, вы собираете на процессоре x86. Мы также знаем из предыдущего примера, что образ собирается для каждой платформы, а затем результат объединяется. В этом сценарии образ linux/amd64 собирается нативно, а образ linux/arm64 собирается с помощью эмуляции.

Это функционально, но может быть болезненно медленным. Эмуляция сильно замедляет сборку в зависимости от того, что находится в Dockerfile, иногда даже на порядок или больше. Разумная 4-минутная сборка может занять 40+ минут с эмуляцией!

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

Альтернатива: Кросс-компиляция

Один из способов полностью избежать эмуляции — использовать одну из более продвинутых функций Dockerfile: кросс-компиляцию. В основном она актуальна для упаковки скомпилированных языков, таких как Go, Rust, C/C++ и т.д.

Кросс-компиляция строится поверх многоэтапных сборок и работает следующим образом: на одном этапе происходит компиляция кода для целевой архитектуры, а на другом — настройка среды выполнения для экспорта в конечный образ.

Этот подход требует модификации вашего Dockerfile для использования директив FROM --platform=$BUILDPLATFORM и FROM --platform=$TARGETPLATFORM. Первый указывает BuildKit работать на родной архитектуре хоста, а второй эмулирует целевую архитектуру сборки.

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

Например, вот как может выглядеть Dockerfile кросс-компиляции Rust:

# Base builder ---------------------------

FROM --platform=$BUILDPLATFORM rust:1.61 AS rust-builder
RUN apt-get update && apt-get install -y 
    g++-x86-64-linux-gnu libc6-dev-amd64-cross 
    g++-aarch64-linux-gnu libc6-dev-arm64-cross && 
    rm -rf /var/lib/apt/lists/*
RUN rustup target add 
    x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
RUN rustup toolchain install 
    stable-x86_64-unknown-linux-gnu stable-aarch64-unknown-linux-gnu
RUN rustup component add rustfmt
ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc 
    CC_x86_64_unknown_linux_gnu=x86_64-linux-gnu-gcc 
    CXX_x86_64_unknown_linux_gnu=x86_64-linux-gnu-g++ 
    CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc 
    CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc 
    CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ 
    CARGO_INCREMENTAL=0

# amd64 build ----------------------------

FROM --platform=$BUILDPLATFORM rust-builder AS build-amd64
WORKDIR /app
COPY . .
RUN cargo install --target x86_64-unknown-linux-gnu --path .
RUN mv ./target/x86_64-unknown-linux-gnu/release/example-app /usr/bin/example-app

# arm64 build ----------------------------

FROM --platform=$BUILDPLATFORM rust-builder AS build-arm64
WORKDIR /app
COPY . .
RUN cargo install --target aarch64-unknown-linux-gnu --path .
RUN mv ./target/aarch64-unknown-linux-gnu/release/example-app /usr/bin/example-app

# Final arch images ----------------------

FROM --platform=amd64 debian:bullseye AS final-amd64
COPY --from=build-amd64 /usr/bin/example-app /usr/bin/example-app

FROM --platform=arm64 debian:bullseye AS final-arm64
COPY --from=build-arm64 /usr/bin/example-app /usr/bin/example-app

# Final image ----------------------------

FROM final-${TARGETARCH}
Вход в полноэкранный режим Выход из полноэкранного режима

Собирая приложение с использованием цепочек инструментов кросс-компиляции x86_64 и aarch64, мы можем гарантировать, что фактический этап компиляции (самый медленный) будет выполняться с использованием родной архитектуры хоста, а затем результат будет упакован в контейнер для целевой архитектуры.

Поддержание цепочек инструментов кросс-компиляции может быть утомительным. Этот подход требует тщательной модификации вашего Dockerfile для поддержки нескольких архитектур. Он также не поможет, если часть сборки должна выполняться на финальной стадии, например, если вам нужно apt-get install некоторых пакетов операционной системы, эти шаги всегда будут требовать эмуляции.

Кросс-компиляция не является необходимой, как мы рассмотрим ниже.

Вариант № 2: CI-провайдеры с поддержкой Arm

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

К сожалению, большинство распространенных CI-провайдеров не предлагают Arm runners, а те, что предлагают, не интегрируются с многоплатформенными сборками.

На момент написания этой статьи только CircleCI предлагает возможность запуска заданий на Arm. GitHub Actions, Google Cloud Build и GitLab hosted CI вообще не предлагают хостируемых машин на Arm. Travis CI тестирует бета-версию Arm runners для проектов с открытым исходным кодом.

Даже если вы используете CircleCI и имеете возможность перенаправлять задания на экземпляры Arm, многоплатформенные сборки все равно представляют собой проблему. Даже если сборка выполняется на Arm, образ Intel придется создавать с помощью эмуляции.

Что вам действительно нужно для многоплатформенных сборок, так это активное подключение к двум экземплярам сборщика. Один Arm, а другой Intel, чтобы вы могли воспользоваться преимуществами docker buildx ‘способности направлять задания сборки на нужный хост для данной цели.

Вариант №3: Запуск собственных экземпляров сборщика

Эмуляция выполняется медленно, но ее можно избежать, направляя сборки на экземпляры сборщика, соответствующие целевой архитектуре. Хотя обычные провайдеры CI не предоставляют такой возможности, это можно сделать самостоятельно.

Docker Buildx поддерживает подключение к удаленным инстансам по SSH или через Kubernetes. Так, вы можете запустить два экземпляра EC2 в AWS, один с процессорами Intel, другой с Arm. Вы установите Docker на эти два экземпляра и настроите SSH-доступ с локальной рабочей станции. Затем зарегистрируйте обе ВМ в экземпляре сборки Buildx, используя флаг --append, чтобы объединить два экземпляра сборки в один экземпляр многоплатформенной сборки:

docker buildx create --name multi 
    --driver docker-container
    --platform linux/arm64
    ssh://something@arm-instance

docker buildx create --append --name multi 
    --driver docker-container
    --platform linux/amd64
    ssh://something@intel-instance

docker buildx use multi
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Теперь, когда две ваши виртуальные машины зарегистрированы в BuildKit, а локальная машина настроена на использование именованного сборщика, вы можете собрать мультиплатформенный образ без какой-либо эмуляции!

docker build --platform=linux/arm64,linux/amd64 .
Вход в полноэкранный режим Выход из полноэкранного режима

Эта конфигурация является самой быстрой по скорости сборки, однако она сопряжена с дополнительными сложностями:

  • Вам нужно запускать и оплачивать собственные облачные виртуальные машины.
  • Вам придется обслуживать эти виртуальные машины, обеспечивая надлежащий контроль доступа и брандмауэры, обновление пакетов ОС и т.д.
  • Это работает для вас и вашей машины в отдельности, если вы хотите поделиться сборщиками с несколькими членами команды или с вашим существующим поставщиком CI, вам нужно будет управлять или автоматизировать инфраструктуру ключей SSH или сертификатов SSH.

Полностью управляемые нативные сборщики с Depot

В Depot мы хотели получить доступ к собственным экземплярам сборки для Arm и Linux, а также избежать всех сложностей, связанных с запуском собственных виртуальных машин сборщиков.

Depot — это удаленный сервис сборки Docker, который управляет парком машин сборщиков для обеих архитектур. Машины имеют более высокие характеристики, чем традиционные CI-провайдеры, и запускаются с постоянным SSD-кэшем.

Наш CLI, depot build, функционирует точно так же, как docker buildx build, но интегрирован с размещенным сервисом. Сборки выполняются удаленно на удаленных экземплярах сборщика, при этом выбирается экземпляр, соответствующий целевой платформе сборки. Если вы запрашиваете многоплатформенную сборку, два экземпляра выполняют сборку параллельно. Все сборки всегда являются нативными!

# replacing `docker buildx build` with `depot build`:

depot build -t repo/image:tag . 
    --platform linux/arm64,linux/amd64
Вход в полноэкранный режим Выход из полноэкранного режима

Depot предлагает полностью управляемую версию варианта 3 выше. Мы управляем всем контролем доступа, автоматизацией, кэшированием, обслуживанием и т.д. экземпляров сборки. Мы запускаем последнюю версию BuildKit на каждом экземпляре конструктора, а depot build встраивает соответствующие библиотеки BuildKit для взаимодействия с экземплярами конструктора, используя ваш токен доступа Depot.

Вы можете использовать depot build на своей локальной машине — по умолчанию вы получите сборку, соответствующую архитектуре вашего хоста — но вы также можете запустить ее из существующего CI-провайдера. Поскольку наши экземпляры билдеров также управляют постоянным SSD-кэшем для контекста сборки, сборка в CI часто происходит во много раз быстрее, чем может обеспечить обычный провайдер.

Сборщики Depot не требуют никакой конфигурации с вашей стороны! Depot — единственный поставщик CI, который предлагает встроенные сборки Docker как для Linux, так и для Arm, с поддержкой нескольких архитектур.

Более подробную информацию о начале работы см. в нашем руководстве по быстрому запуску.

Наша философия в Depot заключается в том, чтобы устранить как можно больше сложностей из процесса сборки образа контейнера. Мы создали хостинговые сборщики Depot и поддержку нескольких платформ как сервис, который мы хотели бы иметь, и мы рады поделиться им с вами!

Оцените статью
Procodings.ru
Добавить комментарий