Home » Как собрать исполняемые файлы Go для разных платформ на Ubuntu 24
Как собрать исполняемые файлы Go для разных платформ на Ubuntu 24

Как собрать исполняемые файлы 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. Это особенно важно в эпоху микросервисов и контейнеризации, где каждый сервис может работать на своей оптимальной архитектуре.


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked