W tym artykule
Koniec z instalowaniem Go na hoście. Bez zaśmiecania
GOPATH, bez „u mnie działa” i bez rozjazdu wersji. Według tego wzorca przepisaliśmy agentów Java dla raportów BigBlueButton Learning Analytics i zbudowaliśmy bezpiecznego agenta Piler Go. Oto jak dostarczać statyczne pliki binarne, korzystając wyłącznie z Dockera.
Ghazi Triki, RIADVICE
Problem z podejściem „po prostu zainstaluj Go”
Gdy utrzymywaliśmy agentów napisanych w Javie dla raportów BigBlueButton Learning Analytics, zmagaliśmy się z klasycznym „piekłem zależności”. Przejście na Go obiecywało prostotę, ale tylko pod warunkiem, że nie powtórzymy tych samych błędów. Potrzebowaliśmy procesu budowania równie hermetycznego jak pliki binarne, które z niego wychodzą.
Prędzej czy później każdy zespół napotyka te same przeszkody:
- Rozjazd wersji. Ahmed ma 1.24, Omar ma 1.26, a serwer produkcyjny 1.25. Drobna zmiana w kompilatorze psuje build, ale tylko u Ahmeda.
- Zaśmiecone hosty.
GOPATH,GOMODCACHEi globalnie instalowane narzędzia (staticcheck,gofumpt), które na każdej stacji roboczej wyglądają inaczej. - Trudny start nowych osób. Nowy inżynier spędza pierwszy dzień na walce ze zmiennymi środowiskowymi i łańcuchami narzędzi C dla
cgo.
W RIADVICE cenimy dyscyplinę i porządek. Chcieliśmy procesu budowania, który to odzwierciedla: czystego, przewidywalnego i wspólnego dla wszystkich.
Standardowa odpowiedź brzmi „użyj Dockera”, ale większość poradników pokazuje jedynie, jak zbudować obraz kontenera. Nie pokazują, jak uzyskać zwykły, statycznie linkowany plik binarny na hoście, czyli artefakt, którego faktycznie potrzeba dla usługi systemd lub wdrożenia w środowisku odizolowanym od sieci, bez instalowania kompilatora Go lokalnie.
docker run --rm -v "$(pwd):/app" -w /app golang:1.26.5-trixie \
go build -o dist/myapp ./cmd/myappOficjalny obraz golang pełni rolę kompilatora. Kod źródłowy jest montowany (bind mount) w /app. Plik binarny trafia do dist/myapp na hoście. Po zakończeniu polecenia kontener jest usuwany (--rm), a host pozostaje tak czysty jak wcześniej.
Doświadczenia z praktyki: od BigBlueButton do Pilera
To nie jest wyłącznie teoria. Zastosowaliśmy ten wzorzec w dwóch kluczowych projektach:
- BigBlueButton Learning Analytics Reports: przepisaliśmy ciężkich agentów Java na lekkie pliki binarne Go. Budowanie w Dockerze pozwoliło nam celować dokładnie w środowisko Debiana na serwerach BBB, bez potrzeby odtwarzania go na naszych laptopach.
- Piler Go Agent: dla naszego rozwiązania do archiwizacji poczty e-mail zbudowaliśmy agenta o wzmocnionych zabezpieczeniach. Proces budowania musiał być równie bezpieczny jak sam kod. Przypinając kompilator do konkretnego SHA, mieliśmy pewność, że każdy bit pliku binarnego ma znane pochodzenie.
Dlaczego to lepsze niż instalowanie Go
| Kwestia | go install na hoście | docker run golang:... |
|---|---|---|
| Wersja Go | Ta, którą zainstalowano ostatnio | Przypięta, identyczna dla wszystkich |
| GOPATH / cache | Zaśmieca ~/go | Hermetyczne warstwy kontenera |
| Toolchain cgo | Wymaga konfiguracji hosta | Zawarty w oficjalnym obrazie |
| Zgodność z CI | „U mnie działa” | Identyczny obraz, identyczny plik binarny |
BINARY := mini-api
DIST := dist
# Version metadata derived from git
VERSION := $(shell tmp=$$(git describe --tags --always --dirty 2>/dev/null) && echo "$${tmp#v}" || echo dev)
COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X example.com/mini-api/internal/version.Version=$(VERSION) \
-X example.com/mini-api/internal/version.Commit=$(COMMIT) \
-X example.com/mini-api/internal/version.Branch=$(BRANCH) \
-X example.com/mini-api/internal/version.BuildDate=$(BUILD_DATE)
# Pinned toolchain image
GO_IMAGE := golang:1.26.5-trixie
# Run any command inside the Go toolchain container
define run_in_docker
docker run --rm -v "$(PWD):/app" -w /app $(GO_IMAGE)
endef
.PHONY: build test lint
build:
@mkdir -p $(DIST)
$(call run_in_docker) bash -c 'CGO_ENABLED=0 go build -buildvcs=false -ldflags="$(LDFLAGS)" -o $(DIST)/$(BINARY) ./cmd/$(BINARY)'
@echo "Built $(DIST)/$(BINARY) (version $(VERSION))"
test:
$(call run_in_docker) go test -race ./...
lint:
$(call run_in_docker) go vet ./...Makefile: źródło dyscypliny
Kroki budowania zapisujemy w pliku Makefile. Proszę zwrócić uwagę, że GO_IMAGE jest przypięty do konkretnej wersji. Nigdy nie używamy latest.
Szczegóły techniczne, które mają znaczenie
1. CGO_ENABLED=0 dla statycznych plików binarnych
W agencie Piler potrzebowaliśmy plików binarnych, które uruchomią się wszędzie, od nowoczesnego Debiana po stary serwer. Wyłączenie CGO sprawia, że plik binarny zawiera wszystko, czego potrzebuje, bez dynamicznych zależności od bibliotek hosta.
2. -ldflags -X dla pochodzenia
Plik binarny powinien umieć dokładnie powiedzieć, skąd pochodzi. W momencie linkowania wstrzykujemy commit z gita i datę budowania. Okazało się to kluczowe przy debugowaniu agentów BigBlueButton w dziesiątkach różnych klastrów.
3. Przypinanie obrazu kompilatora
W naszej kulturze stawiamy na spójność. Przypięcie golang:1.26.5-trixie zamiast latest oznacza, że jeśli Ahmed zbuduje plik binarny dziś, a Omar za pół roku, wynik będzie identyczny co do bitu.
Podsumowanie
Kompilator to narzędzie, a każde narzędzie powinno mieć swoje miejsce. Przenosząc łańcuch narzędzi Go do Dockera, wyeliminowaliśmy całą klasę błędów typu „u mnie działa” i uprościliśmy wdrażanie każdego nowego inżyniera, który dołącza do RIADVICE.
Niezależnie od tego, czy przepisują Państwo agentów Java, czy budują bezpieczne API dla Pilera, ten wzorzec zapewnia dyscyplinę i niezawodność, jakich wymagają systemy produkcyjne.
Ghazi Triki jest założycielem RIADVICE. Budujemy wydajne i bezpieczne integracje dla BigBlueButton, Pilera i nie tylko. Zbudujmy razem coś wartościowego.





