- Home »

Как собрать исполняемые файлы Go для разных платформ на Ubuntu 24
Помню, как в начале моего пути с Go меня удивило, что можно собрать один исполняемый файл и запустить его на любой системе без дополнительных зависимостей. Это было магией для человека, который раньше мучался с Python виртуальными окружениями и Node.js модулями. Кросс-компиляция в Go — это не просто удобная фича, а настоящий game-changer для разработки серверных приложений.
Если вы деплоите приложения на разные платформы, работаете с контейнерами или просто хотите собрать бинарник для продакшена на другой архитектуре, то знание кросс-компиляции Go сэкономит вам массу времени. Особенно это актуально, когда разрабатываете на macOS или Windows, а запускаете на Linux-серверах, или когда нужно поддерживать ARM64 архитектуру для современных процессоров.
Как работает кросс-компиляция в Go
Go из коробки поддерживает кросс-компиляцию благодаря своей архитектуре. Компилятор написан на самом Go и может генерировать код для разных платформ без необходимости устанавливать дополнительные тулчейны. Это принципиально отличается от того же C/C++, где для каждой целевой платформы нужен свой компилятор.
Магия происходит через две переменные окружения:
GOOS
— операционная система (linux, windows, darwin, freebsd и другие)GOARCH
— архитектура процессора (amd64, arm64, 386, arm и другие)
Когда вы указываете эти переменные, Go компилятор автоматически выбирает нужный backend для генерации машинного кода. Никаких дополнительных настроек или установок не требуется.
Подготовка окружения на Ubuntu 24
Для начала убедимся, что у нас установлена актуальная версия Go. Ubuntu 24 поставляется с довольно свежей версией, но лучше проверить:
sudo apt update
sudo apt install golang-go
# Проверяем версию
go version
# Если нужна более свежая версия, можно поставить напрямую с официального сайта
wget https://go.dev/dl/go1.21.5.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.21.5.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
Создадим простое тестовое приложение для экспериментов:
mkdir cross-compile-test
cd cross-compile-test
# Инициализируем модуль
go mod init cross-compile-test
# Создаем простое приложение
cat > main.go << 'EOF'
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("Hello from %s/%s!\n", runtime.GOOS, runtime.GOARCH)
fmt.Printf("Go version: %s\n", runtime.Version())
}
EOF
Базовая кросс-компиляция: шаг за шагом
Теперь самое интересное — собираем бинарники для разных платформ. Начнем с основных комбинаций, которые покрывают 99% случаев:
# Linux AMD64 (обычные серверы)
GOOS=linux GOARCH=amd64 go build -o app-linux-amd64
# Linux ARM64 (современные серверы, Raspberry Pi 4+)
GOOS=linux GOARCH=arm64 go build -o app-linux-arm64
# Windows AMD64
GOOS=windows GOARCH=amd64 go build -o app-windows-amd64.exe
# macOS Intel
GOOS=darwin GOARCH=amd64 go build -o app-macos-intel
# macOS Apple Silicon
GOOS=darwin GOARCH=arm64 go build -o app-macos-m1
# FreeBSD (для тех, кто еще использует)
GOOS=freebsd GOARCH=amd64 go build -o app-freebsd-amd64
Проверяем результат:
ls -la app-*
file app-linux-amd64
file app-linux-arm64
Если у вас есть доступ к VPS с разными архитектурами, можете протестировать каждый бинарник на соответствующей платформе.
Продвинутые техники сборки
Обычная сборка — это только начало. В реальных проектах часто нужны дополнительные настройки:
Статическая линковка
Для максимальной портабельности, особенно при работе с контейнерами, используйте статическую линковку:
# Полностью статический бинарник
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -o app-static
# Или упрощенный вариант для большинства случаев
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-static
Оптимизация размера
Go бинарники могут быть довольно большими. Вот несколько способов их сжать:
# Убираем отладочную информацию и таблицы символов
go build -ldflags="-s -w" -o app-small
# Дополнительно можно использовать UPX (если установлен)
sudo apt install upx-ucl
upx --best app-small
Внедрение метаданных
Полезно встраивать информацию о версии и сборке прямо в бинарник:
# Создаем переменные для метаданных
cat > version.go << 'EOF'
package main
var (
version = "dev"
commit = "unknown"
date = "unknown"
)
func init() {
fmt.Printf("Version: %s, Commit: %s, Date: %s\n", version, commit, date)
}
EOF
# Собираем с метаданными
VERSION=$(git describe --tags --always --dirty)
COMMIT=$(git rev-parse HEAD)
DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GOOS=linux GOARCH=amd64 go build -ldflags="-X main.version=$VERSION -X main.commit=$COMMIT -X main.date=$DATE" -o app-versioned
Автоматизация с помощью Makefile
Ручная сборка для каждой платформы быстро надоедает. Создадим Makefile для автоматизации:
cat > Makefile << 'EOF'
.PHONY: build clean all
APP_NAME = myapp
VERSION ?= $(shell git describe --tags --always --dirty)
COMMIT ?= $(shell git rev-parse HEAD)
DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
LDFLAGS = -ldflags="-s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE)"
PLATFORMS = linux/amd64 linux/arm64 windows/amd64 darwin/amd64 darwin/arm64
BINDIR = bin
all: clean build
build:
@mkdir -p $(BINDIR)
@for platform in $(PLATFORMS); do \
os=$$(echo $$platform | cut -d'/' -f1); \
arch=$$(echo $$platform | cut -d'/' -f2); \
output=$(BINDIR)/$(APP_NAME)-$$os-$$arch; \
if [ "$$os" = "windows" ]; then \
output="$$output.exe"; \
fi; \
echo "Building $$output..."; \
CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch go build $(LDFLAGS) -o $$output; \
done
clean:
rm -rf $(BINDIR)
install:
go install $(LDFLAGS)
EOF
# Теперь можно собрать все платформы одной командой
make build
Использование с Docker
Кросс-компиляция отлично сочетается с Docker. Можно собирать бинарники в контейнере и получать предсказуемый результат:
cat > Dockerfile.build << 'EOF'
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG GOOS=linux
ARG GOARCH=amd64
ARG VERSION=dev
RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build \
-ldflags="-s -w -X main.version=${VERSION}" \
-o /app/binary
FROM scratch
COPY --from=builder /app/binary /
ENTRYPOINT ["/binary"]
EOF
# Собираем для разных архитектур
docker build --build-arg GOOS=linux --build-arg GOARCH=amd64 -t myapp:linux-amd64 -f Dockerfile.build .
docker build --build-arg GOOS=linux --build-arg GOARCH=arm64 -t myapp:linux-arm64 -f Dockerfile.build .
Скрипт для массовой сборки
Для более сложных сценариев можно создать bash-скрипт:
cat > build.sh << 'EOF'
#!/bin/bash
set -e
APP_NAME="myapp"
VERSION=${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo "dev")}
COMMIT=${COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo "unknown")}
DATE=${DATE:-$(date -u +"%Y-%m-%dT%H:%M:%SZ")}
LDFLAGS="-s -w -X main.version=$VERSION -X main.commit=$COMMIT -X main.date=$DATE"
declare -A PLATFORMS=(
["linux/amd64"]="Linux 64-bit"
["linux/arm64"]="Linux ARM64"
["linux/386"]="Linux 32-bit"
["windows/amd64"]="Windows 64-bit"
["darwin/amd64"]="macOS Intel"
["darwin/arm64"]="macOS Apple Silicon"
["freebsd/amd64"]="FreeBSD 64-bit"
)
echo "Building $APP_NAME version $VERSION..."
echo "=================================="
mkdir -p dist
for platform in "${!PLATFORMS[@]}"; do
IFS='/' read -r os arch <<< "$platform"
output="dist/${APP_NAME}-${os}-${arch}"
if [ "$os" = "windows" ]; then
output="${output}.exe"
fi
echo -n "Building for ${PLATFORMS[$platform]}... "
if CGO_ENABLED=0 GOOS="$os" GOARCH="$arch" go build -ldflags="$LDFLAGS" -o "$output" 2>/dev/null; then
echo "✓ $(du -h "$output" | cut -f1)"
else
echo "✗ Failed"
fi
done
echo
echo "Build completed! Files in dist/ directory:"
ls -la dist/
EOF
chmod +x build.sh
./build.sh
Работа с CGO и внешними зависимостями
Если ваше приложение использует CGO (например, для работы с SQLite), кросс-компиляция усложняется. Рассмотрим несколько подходов:
Отключение CGO
Самый простой способ — использовать pure Go альтернативы:
# Вместо github.com/mattn/go-sqlite3 используйте modernc.org/sqlite
go get modernc.org/sqlite
# Тогда можно собирать с CGO_ENABLED=0
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-nocgo
Использование xgo
Для проектов с CGO можно использовать xgo:
# Установка xgo
go install github.com/karalabe/xgo@latest
# Сборка с CGO для разных платформ
xgo --targets=linux/amd64,linux/arm64,windows/amd64,darwin/amd64 .
Тестирование собранных бинарников
Важно проверить, что собранные бинарники действительно работают. Создадим простой тест-скрипт:
cat > test-binaries.sh << 'EOF'
#!/bin/bash
echo "Testing binaries..."
# Тестируем Linux бинарники на текущей системе
if [ -f "dist/myapp-linux-amd64" ]; then
echo -n "Testing Linux AMD64... "
if ./dist/myapp-linux-amd64 >/dev/null 2>&1; then
echo "✓"
else
echo "✗"
fi
fi
# Проверяем, что Windows бинарник имеет правильный формат
if [ -f "dist/myapp-windows-amd64.exe" ]; then
echo -n "Checking Windows binary format... "
if file dist/myapp-windows-amd64.exe | grep -q "PE32+"; then
echo "✓"
else
echo "✗"
fi
fi
# Можно добавить проверку через Docker для других архитектур
if command -v docker >/dev/null 2>&1; then
echo -n "Testing ARM64 in Docker... "
if docker run --rm --platform linux/arm64 -v "$(pwd)/dist:/app" alpine:latest /app/myapp-linux-arm64 >/dev/null 2>&1; then
echo "✓"
else
echo "✗"
fi
fi
EOF
chmod +x test-binaries.sh
./test-binaries.sh
Интеграция с CI/CD
Кросс-компиляция отлично подходит для автоматизации. Пример для GitHub Actions:
cat > .github/workflows/build.yml << 'EOF'
name: Build and Release
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.21
- name: Build binaries
run: |
platforms=(
"linux/amd64"
"linux/arm64"
"windows/amd64"
"darwin/amd64"
"darwin/arm64"
)
for platform in "${platforms[@]}"; do
os=${platform%/*}
arch=${platform#*/}
output="myapp-$os-$arch"
if [ "$os" = "windows" ]; then
output="$output.exe"
fi
CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -o $output
done
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: myapp-*
EOF
Сравнение с альтернативами
Вот сравнение Go кросс-компиляции с другими подходами:
Критерий | Go кросс-компиляция | Docker multi-stage | Традиционные toolchain |
---|---|---|---|
Простота настройки | Очень простая | Средняя | Сложная |
Скорость сборки | Быстрая | Медленная | Средняя |
Размер зависимостей | Нет | Образы Docker | Большие toolchain |
Поддержка CGO | Ограниченная | Полная | Полная |
Воспроизводимость | Высокая | Очень высокая | Средняя |
Продвинутые кейсы использования
Сборка для встраиваемых систем
Go можно компилировать даже для экзотических архитектур:
# Для MIPS (роутеры)
GOOS=linux GOARCH=mips go build -o app-mips
# Для RISC-V
GOOS=linux GOARCH=riscv64 go build -o app-riscv
# Посмотреть все поддерживаемые платформы
go tool dist list
Условная компиляция
Можно включать разный код для разных платформ:
# config_unix.go
//go:build unix
package main
const ConfigPath = "/etc/myapp/config.yaml"
# config_windows.go
//go:build windows
package main
const ConfigPath = "C:\\Program Files\\MyApp\\config.yaml"
Мониторинг и метрики
Для серверных приложений полезно добавить эндпоинт с информацией о сборке:
cat > info.go << 'EOF'
package main
import (
"encoding/json"
"net/http"
"runtime"
)
var (
version = "dev"
commit = "unknown"
date = "unknown"
)
type BuildInfo struct {
Version string `json:"version"`
Commit string `json:"commit"`
Date string `json:"date"`
GoVersion string `json:"go_version"`
OS string `json:"os"`
Arch string `json:"arch"`
}
func buildInfoHandler(w http.ResponseWriter, r *http.Request) {
info := BuildInfo{
Version: version,
Commit: commit,
Date: date,
GoVersion: runtime.Version(),
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
}
func main() {
http.HandleFunc("/info", buildInfoHandler)
http.ListenAndServe(":8080", nil)
}
EOF
Безопасность и лучшие практики
Несколько важных моментов для продакшена:
- Всегда используйте статическую линковку для серверных приложений — это избавит от проблем с зависимостями
- Убирайте отладочную информацию из продакшен-сборок флагами
-ldflags="-s -w"
- Встраивайте метаданные о сборке — это поможет в диагностике проблем
- Тестируйте на целевой платформе — кросс-компиляция не гарантирует совместимость с системными вызовами
- Используйте воспроизводимые сборки — зафиксируйте версии Go и зависимостей
Интеграция с системами развертывания
Кросс-компиляция отлично интегрируется с современными подходами к деплою. Например, можно собирать бинарники локально и отправлять их на выделенные серверы без необходимости устанавливать Go на продакшене:
cat > deploy.sh << 'EOF'
#!/bin/bash
# Собираем для продакшена
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o myapp-prod
# Загружаем на сервер
scp myapp-prod user@production-server:/opt/myapp/
# Перезапускаем сервис
ssh user@production-server "sudo systemctl restart myapp"
EOF
Заключение и рекомендации
Кросс-компиляция в Go — это мощный инструмент, который должен быть в арсенале каждого разработчика серверных приложений. Она решает множество проблем: от простого развертывания до поддержки множества платформ без дополнительных зависимостей.
Когда использовать:
- Разработка CLI-утилит для разных ОС
- Деплой на серверы с отличающейся архитектурой
- Создание микросервисов для контейнеров
- Распространение приложений без установки runtime
Основные преимущества:
- Нет необходимости настраивать toolchain для каждой платформы
- Статические бинарники работают без дополнительных зависимостей
- Быстрая сборка для всех поддерживаемых платформ
- Отличная интеграция с CI/CD системами
Ограничения:
- Проблемы с CGO — нужны альтернативные решения
- Большой размер бинарников по сравнению с интерпретируемыми языками
- Некоторые системные функции могут работать по-разному
Начните с простых примеров, автоматизируйте процесс с помощью Makefile или скриптов, и вскоре кросс-компиляция станет естественной частью вашего workflow. Это особенно важно в эпоху микросервисов и контейнеризации, где каждый сервис может работать на своей оптимальной архитектуре.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.