unholy-js посты

arm docker образ на amd инфраструктуре?

May 18, 2025

·

4 min read

tl;dr: Сборка ARM Docker-образов на AMD CI с kaniko без эмуляции и RUN

Для начала пара слов о том, какую проблему я хотел решить.

Представьте, у вас есть docker образ для сборки статического вебсайта которым пользуется много народу. На вход образ принимает набор пользовательских файлов и на выходе получает статический вебсайт со всякими картинками и диаграммами. Образ содержит код приложения для сборки вебсайта, nodejs и еще несколько apt пакетов. CI агенты и сам образ работают на linux/amd64. Для отладки хочется собирать вебсайт и локально.

Именно в этом месте и возникает проблема. Большое количество пользователей сидит на arm макбуках (M чипы), и на больших проектах, amd64 образы работают очень не быстро, потому что используют “эмуляцию/виртуализацию”. И собрав локально arm образ и замерив скорость на своих примерах, я увидел прирост скорости в среднем в 6-7 раз! Для того чтобы не собирать и не публиковать эти образы со своего компьютера, я решил попробовать автоматизировать этот процесс на CI, о чем дальше и пойдет история 😅.

Варианты решения?

0. kaniko —custom-platform=linux/arm64

В самом начале я попробовал запустить сборку Dockerfile с флагом нужной мне платформы и получил ошибку несовместимых архитектуры. После небольшого исследования, стало понятно, что простым способом запускать arm код на amd хосте не получится. Двигаемся дальше.

1. arm сборочные агенты

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

2. Сборка arm образов на amd хосте при помощи эмуляции/виртуализации

docker buildx и kaniko при помощи qemu позволяют собирать arm образы. Но этот вариант тоже не подошел, потому что оба инструмента требуют привилегированный запуск для регистрации эмулятора в качестве обработчика неизвестных запускаемых архитектур. А buildx вдвойне не подходит так как требует демона docker, и на CI обычно работает в docker in docker, которого в моем случае тоже нет. Секьюрная инфраструктура накладывает свои ограничения 😅.

3. Вариант X

Тут я сделаю небольшое отступление. Вы возможно подумали, что я сидел в изоляции и не рылся в интернете/gptшках, но это не так, большинство вариантов которые предлагались были плюс минус одним из описанных выше. Совсем отчаявшись это реализовать я в очередной раз обратился к братьям по цеху, возможно кто-то сталкивался с похожей проблемой. Нашелся код, который делает ровно то что я хотел, а именно собирает arm образы на amd хосте! Изучив его я заметил, что в нем нет ни одной команды RUN в Dockerfile. Мне эта команды была нужна как минимум для установки нескольких apt-пакетов (конечно некоторые из них можно просто скачать руками, но это привносит лишние хлопоты). Тем не менее я решил урезать функционал образа и собрать его без RUN команд, копированием файлов. И она заработало!

Сборка запускается примерно следующим скриптом в gitlab ci

executor --dockerfile ./arm64local.Dockerfile --context .
    --custom-platform=linux/arm64
    --destination mydocs-image:latest

Возвращаясь к флагу --custom-platform, как вы могли заметить я уже пробовал такой вариант в пункте 0 😅. Если прочитать документацию kaniko по этому флагу, то там явно написано что kaniko не предоставляет никаких инструментов виртуализации и ваш хост должен поддерживать собираемую архитектуру сам (см выше запуск при помощи эмуляции/виртуализация).

This is not virtualization and cannot help to build an architecture not natively supported by the build host. This is used to build i386 on an amd64 Host for example, or arm32 on an arm64 host.

Почему же в этот раз у мены получилось собрать образ? На самом деле есть еще один “секретный ингридиент” без которого такая магия не сработает 😅. И так встречайте, мультиплатформенные docker образы. Не буду сильно углубляться, но docker и большинство docker registry (типо docker hub, gcr) поддерживают публикацию мультиплатформенных образов. Для примера можно взять образ nodejs где мы видим что он поддерживает ряд платформ, например, amd64, arm64, s390x и тд. Так же через консоль можно получить эту информацию глянув манифест образа.

docker manifest inspect node:latest

Вернет нам json файл в котором будет поле manifest содержащее записи следующего вида:

{
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "size": 2495,
  "digest": "sha256:5ff2254b17716757414eb871df9b8170829900cf19e1021f9ff414b4b2e6a017",
  "platform": {
    "architecture": "arm",
    "os": "linux",
    "variant": "v7"
  }
}

Для нас интересно поле platform.architecture. И “секретный ингридиент”, это использование базового образа у которого есть манифест с нужной нам архитектурой. Меня очень позабавила вся эта “магия”, поэтому захотелось покапаться дальше.

Заключение

Но давайте подведем некоторый итог того, “как собрать arm образ на ограниченной инфраструктуре с amd хостом?“.

  1. Берем базовый образ с поддержкой arm
  2. В Dockerfile не используем RUN команды, только копирование файлов
  3. Запускаем kaniko с флагом --custom-platform=linux/arm64

Наверно кто-то из вас уже догадался, почему это работает без RUN команд и кидает ошибку при их наличие. Секрет кроется в двух моментах:

  • Что такое docker образ
  • Отличие в сборке образов при помощи docker и kaniko

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