ADG Оставить заявку
Блог DevOps 5 мин чтения

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. Пара часов работы сейчас - против разбора несовместимостей в момент, когда новые машины уже у разработчиков на столе.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.