Docker и ARM: готовим мультиплатформенные сборки под Apple Silicon заранее
Apple анонсировала M1 - и Docker уже работает над ARM-поддержкой. Настраиваем buildx с QEMU и matrix build в GitHub Actions для linux/amd64 и linux/arm64.
Apple анонсировала переход Mac на собственные ARM-чипы (Apple Silicon); Docker начинает работу над поддержкой ARM-архитектуры для Desktop и сборочных инструментов
На прошлой неделе Apple официально объявила о переходе Mac на собственные чипы - ARM-архитектура, семейство Apple Silicon. Первые машины ожидаются в ноябре. Docker тем временем выкатил заметку о том, что работает над поддержкой нового железа. Что это значит для нас на практике - не в далёком горизонте, а прямо сейчас.
Если среди ваших разработчиков появятся машины на M1, а CI/CD продолжает собирать только linux/amd64, сценариев несколько: либо образы не запустятся на девелоперской машине нативно, либо придётся поддерживать два набора образов, либо разработчики будут жить под x86-эмуляцией Rosetta 2. Всё это - не катастрофа, но и не то, о чём хочется думать постфактум. Разбираемся, как добавить linux/arm64 в CI-пайплайн уже сейчас, пока это не стало срочно.
buildx и QEMU: что за механизм
Docker buildx - это расширение команды docker build, реализующее BuildKit под капотом. Главное что нас интересует: buildx умеет собирать образы для чужих архитектур через QEMU-эмуляцию. То есть на обычной x86-машине (GitHub Actions runner, ваш CI-сервер) можно получить честный linux/arm64-образ без ARM-железа.
Работает это через binfmt_misc в ядре Linux - механизм, который позволяет запускать бинарники чужих архитектур через указанный интерпретатор. QEMU при этом выступает интерпретатором для ARM. Медленнее нативной сборки - да. Но для большинства приложений, где в образе не компилируется ничего тяжёлого, разница терпимая.
Чтобы всё это заработало, нужен buildx-builder с поддержкой multiplatform:
# Регистрируем QEMU-обработчики через образ tonistiigi/binfmt
docker run --privileged --rm tonistiigi/binfmt --install all
# Создаём builder с поддержкой нескольких платформ
docker buildx create --name multiarch --use
docker buildx inspect --bootstrap
После этого docker buildx build --platform linux/amd64,linux/arm64 начинает работать. Первый прогон покажет поддерживаемые платформы.
GitHub Actions: matrix build для amd64 и arm64
На GitHub Actions это ложится в matrix стратегию. Два подхода: либо один джоб с --platform linux/amd64,linux/arm64 и push обоих слоёв сразу через манифест, либо матрица из двух джобов с объединением в манифест на финальном шаге.
Первый подход проще, но медленнее - сборка идёт последовательно. Матрица параллельная. Покажем вариант с параллельной матрицей:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform:
- linux/amd64
- linux/arm64
steps:
- uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare platform tag
id: prep
run: |
PLATFORM_TAG=$(echo "${{ matrix.platform }}" | tr '/' '-')
echo "::set-output name=tag::ghcr.io/${{ github.repository }}:${{ github.sha }}-${PLATFORM_TAG}"
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: ${{ matrix.platform }}
push: true
tags: ${{ steps.prep.outputs.tag }}
manifest:
needs: build
runs-on: ubuntu-latest
steps:
- name: Login to Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and push manifest
run: |
docker buildx imagetools create \
-t ghcr.io/${{ github.repository }}:${{ github.sha }} \
ghcr.io/${{ github.repository }}:${{ github.sha }}-linux-amd64 \
ghcr.io/${{ github.repository }}:${{ github.sha }}-linux-arm64
imagetools create собирает multi-arch манифест из двух уже запушенных образов. После этого docker pull на amd64-машине получит x86-слой, на ARM-машине - ARM-слой. Всё прозрачно.
Что проверить в Dockerfile
Не все Dockerfile переезжают на ARM без правок.
Base-образы. Официальные образы (node, python, alpine, debian) давно публикуются как multi-arch - FROM node:14-alpine сработает корректно для обеих платформ. Проблемы возникают с образами сторонних вендоров или самодельными base-образами, где тег один, а архитектура зашита под x86.
Бинарные зависимости. Если в Dockerfile скачивается бинарник через curl - это почти всегда архитектурозависимо. Нужно добавить условие:
ARG TARGETARCH
RUN if [ "$TARGETARCH" = "amd64" ]; then \
curl -L https://example.com/tool-linux-amd64 -o /usr/local/bin/tool; \
elif [ "$TARGETARCH" = "arm64" ]; then \
curl -L https://example.com/tool-linux-arm64 -o /usr/local/bin/tool; \
fi
BuildKit автоматически выставляет TARGETARCH при кросс-платформенной сборке. Удобно.
Скомпилированные зависимости через pip/npm. Пакеты с нативными расширениями (numpy, Pillow, некоторые npm-пакеты) потребуют компиляцию под ARM. При QEMU-эмуляции это работает, но медленно - иногда очень медленно. На это стоит закладываться при оценке времени сборки.
Что сейчас работает, а что нет
Docker Desktop для Mac на ARM официально ещё не вышел - он в разработке. Но сборка образов для ARM на x86 через buildx + QEMU работает уже сейчас. Это означает, что CI/CD можно подготовить заранее, а когда разработчики появятся с новыми Mac, образы в реестре уже будут мультиплатформенными.
На наш взгляд, сейчас разумный момент пройти по репозиториям, выявить несовместимые curl-вставки в Dockerfile и добавить matrix build в пайплайн. Не авральный переезд, а спокойная подготовка.
Для проектов на managed-сопровождении мы это делаем в рамках плановых работ по CI: проверяем Dockerfile, добавляем buildx, смотрим на time-to-build. Пара часов работы сейчас - против разбора несовместимостей в момент, когда новые машины уже у разработчиков на столе.