Кросскомпиляция приложения на Go с использованием Docker для Alpine Linux (musl) aarch64 с нативными библиотеками, чтобы CGO было включено.

Вопрос или проблема

Предыстория

Я пишу клиент open-source GTK go app subsonic API, который:

  • нацелен в первую очередь на мобильные Linux системы, такие как postmarketOS (alpine linux), Mobian (debian)
  • в будущем будет также расширен на настольные Linux, Windows и Mac OS, но не Android или iOS, так как у них уже есть клиенты.
  • должен быть доступен по крайней мере на aarch64 и x86_64
  • зависит от нативных библиотек, таких как portaudio, libasound, libopus
  • поэтому требуется включить CGO

Я пока не опубликовал приложение, так как размещаю свой git самостоятельно и мне нужно сделать дополнительную настройку. Оно не будет доступно до конца октября 2021 года.

Приложение успешно компилируется (на хосте) и запускается на моем основном ПК с x86_64 fedora.

Приложение успешно компилируется (в docker) и запускается на aarch64 дистрибутивах с использующей библиотекой glibc, таких как Mobian

Вот Dockerfile:

FROM golang:1.17-bullseye
LABEL os=linux
LABEL arch=arm64
ENV GOOS=linux
ENV GOARCH=arm64
ENV CGO_ENABLED=1
ENV CC=aarch64-linux-gnu-gcc
ENV PATH="/go/bin/${GOOS}_${GOARCH}:${PATH}"
ENV PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
# установка зависимостей для сборки и выполнения
RUN dpkg --add-architecture arm64 
RUN apt update && apt upgrade -y 
RUN apt install -y --no-install-recommends \
        protobuf-compiler \
        upx \
        gcc-aarch64-linux-gnu \
        libc6-dev-arm64-cross \
        pkg-config \
        libasound2-dev:arm64 \
        libgtk-3-dev:arm64 \
        libcairo2-dev:arm64 \
        libglib2.0-dev:arm64 \
        libgdk-pixbuf2.0-dev:arm64 \
        libsamplerate0:arm64 \
        libsamplerate0-dev:arm64 \
        libopusfile0:arm64 \
        libopusfile-dev:arm64 \
        libopus0:arm64 \
        libopus-dev:arm64 \
        libportaudio2:arm64 \
        portaudio19-dev:arm64 
# установка зависимостей для сборки (генераторы кода)
RUN go get github.com/hajimehoshi/oto \
    && go get github.com/faiface/beep \
    && go get github.com/faiface/beep/flac \
    && go get github.com/faiface/beep/speaker \
    && go get github.com/faiface/beep/mp3 \
    && go get github.com/faiface/beep/vorbis \
    && go get github.com/faiface/beep/wav\
    && go get github.com/gotk3/gotk3 \
    && go get github.com/delucks/go-subsonic \
    && go get github.com/hashicorp/go-retryablehttp \
    && go get github.com/zalando/go-keyring \
    && go get github.com/emirpasic/gods/lists/ \
    && go get github.com/emirpasic/gods/lists/arraylist \

Проблемы

  1. Я не могу запустить рабочий бинарный файл Mobian на postmarketOS, так как alpine linux использует musl вместо этого. Вывод file: ELF 64-битное LSB исполняемое, ARM aarch64, версия 1 (SYSV), динамически связанное, интерпретатор /lib/ld-linux-aarch64.so.1, (...) для GNU/Linux 3.7.0. не обрезано.
  2. Мне не удалось использовать инструментарий musl в docker debian, так как это 32-битный исполняемый файл, а образ 64-битный.
  3. Я не смог найти информацию о multiarch в alpine, поэтому, вероятно, я не могу использовать образ golang:1.17-alpine на x86_64 (тогда мне также нужно найти нативные библиотеки в пакетах)

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

Ресурсы, идеи, обходные пути

  • У меня есть raspberry pi с установленным debian и некоторыми сервисами. Я мог бы использовать нативный инструментарий musl и docker-контейнер debian aarch64, но это неудобно в производственном цикле. К тому же, я еще не пробовал это.

Рекомендуется прочитать

  1. Кросс-компиляция проектов CGO (также с использованием docker)
  2. Кросс-компиляция с платформо-независимыми инструментальными наборами musl
  3. Связанная ветка stackoverflow о alpine linux и go бинарных файлах

У меня была аналогичная проблема, когда мне нужно было создать статический Go бинарный файл с cgo, который в итоге будет работать в контейнере alpine с архитектурой arm64, но должен быть собран в контейнере golang:alpine с архитектурой x86_64 (я не контролировал архитектуру CI/CD runner).

Мне удалось это сделать, загрузив кросс-компилятор (CC) и указав, что он должен использоваться во время сборки Go.

Вот пример загрузки/распаковки CC в домашнем каталоге пользователя контейнера и его использования в сборке go из /usr/src/example, которая производит исполняемый файл с именем exampleApp.

wget -P ~ https://musl.cc/aarch64-linux-musl-cross.tgz
tar -xvf ~/aarch64-linux-musl-cross.tgz -C ~
cd /usr/src/example
GOOS=linux GOARCH=arm64 CGO_ENABLED=1 CC=~/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc go build -o exampleApp -a -ldflags=-extldflags=-static . 

Запуск file на выходе exe подтвердил, что он был статически связан для aarch64. Затем я смог запустить его в контейнере arm64 alpine в другом месте.

Ответ или решение

Кросс-компиляция приложений Go с поддержкой CGO для Alpine Linux (musl) и архитектуры aarch64 может быть вызовом, особенно в случае использования Docker. В данной задаче мы имеем дело с приложением, которое зависит от нескольких нативных библиотек, таких как portaudio, libasound и libopus. Эти зависимости требуют включения CGO, что добавляет сложности при работе в среде, отличной от целевой.

Шаги для кросс-компиляции с использованием Docker

1. Использование musl Toolchain

Вам понадобится musl-cross заголовочный файл и соответствующий компилятор, чтобы скомпилировать приложение для musl libc в Alpine Linux. Даже если вы начинаете с образа на основе Debian, вы сможете скачать и установить musl toolchain:

wget -P ~/ https://musl.cc/aarch64-linux-musl-cross.tgz
tar -xvf ~/aarch64-linux-musl-cross.tgz -C ~

2. Настройка среды Docker

Ваш Dockerfile должен быть изменен, чтобы использовать musl cross-compiler для аrch64. Это можно сделать, установив переменные окружения и модифицируя команды сборки именно так:

FROM golang:1.17-alpine

RUN apk add --no-cache \
    wget \
    build-base \
    musl-dev \
    git

# Установка musl toolchain
RUN wget -P ~/ https://musl.cc/aarch64-linux-musl-cross.tgz && \
    tar -xvf ~/aarch64-linux-musl-cross.tgz -C ~ && \
    rm ~/aarch64-linux-musl-cross.tgz

ENV GOOS=linux
ENV GOARCH=arm64
ENV CGO_ENABLED=1
ENV CC=/root/aarch64-linux-musl-cross/bin/aarch64-linux-musl-gcc

# Установка зависимостей через go get
RUN go get -v github.com/hajimehoshi/oto \
    && go get -v github.com/faiface/beep \
    && go get -v github.com/faiface/beep/flac \
    && go get -v github.com/faiface/beep/speaker \
    && go get -v github.com/faiface/beep/mp3 \
    && go get -v github.com/faiface/beep/vorbis \
    && go get -v github.com/faiface/beep/wav \
    && go get -v github.com/gotk3/gotk3 \
    && go get -v github.com/delucks/go-subsonic \
    && go get -v github.com/hashicorp/go-retryablehttp \
    && go get -v github.com/zalando/go-keyring \
    && go get -v github.com/emirpasic/gods/lists/arraylist

# Копирование исходных кодов и сборка приложения
WORKDIR /usr/src/yourapp
COPY . .

# Сборка приложения
RUN go build -o yourAppName -a -ldflags=-extldflags=-static .

3. Верификация и запуск

После сборки используйте file yourAppName, чтобы убедиться, что бинарный файл скомпилирован корректно как статически связанный ELF для aarch64:

file yourAppName
# ожидаемый вывод: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), ...

Заключение

Этот процесс позволяет создать статически связанный бинарный файл, совместимый с musl, который может быть развёрнут и выполнен в Alpine Linux на архитектуре arm64. Это решение интегрирует использование Docker для автоматизации сборки, что идеально подходит для будущего применения в CI/CD. Этот процесс позволяет использовать кросс-компиляцию и CGO с Alpine, предоставляя универсальный подход для разработки и развертывания приложений Go.

Оцените материал
Добавить комментарий

Капча загружается...