Warum Go und Docker das ideale Duo sind?
\n\nGo (oder Golang) ist eine Sprache, die von Anfang an für moderne Cloud-Architekturen entwickelt wurde. Noch bevor Docker zum De-facto-Standard wurde, konnte Go bereits in eine statische Binärdatei kompiliert werden, die keinerlei Abhängigkeiten benötigt. Und Docker ist eine Technologie, die es ermöglicht, eine Anwendung mit ihrer gesamten Umgebung in einen leichten Container zu packen. Wenn Sie diese beiden Technologien kombinieren, erhalten Sie eine nahezu ideale Lösung: minimale Image-Größe, blitzschneller Start und keinerlei Abhängigkeitskonflikte.
\n\nIn diesem Artikel halten wir keine Vorträge über die Theorie der Containerisierung. Stattdessen schreiben wir eine einfache Go-Anwendung, bauen sie in ein Docker-Image, optimieren sie mit einem Multi-Stage-Build und stellen sicher, dass unser Container produktionsbereit ist. All dies anhand von echten Codebeispielen, die Sie sofort ausführen können.
\n\nSchritt 1: Eine einfache Go-Anwendung schreiben
\n\nZunächst erstellen wir einen einfachen HTTP-Server in Go. Dies wird unser „Versuchskaninchen“-Programm. Erstellen Sie eine Datei main.go mit folgendem Inhalt:
package main\n\nimport (\n "fmt"\n "log"\n "net/http"\n "os"\n)\n\nfunc main() {\n port := os.Getenv("PORT")\n if port == "" {\n port = "8080"\n }\n\n http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {\n fmt.Fprintf(w, "Привет из Go-контейнера! 🐹")\n })\n\n log.Printf("Сервер запущен на порту %s", port)\n log.Fatal(http.ListenAndServe(":"+port, nil))\n}\n\n\nDieser Code ist ein Klassiker: Er horcht auf einem Port (der über die Umgebungsvariable PORT festgelegt werden kann) und antwortet auf jede Anfrage mit einer einfachen Begrüßung. Nun möchten wir, dass diese Anwendung in Docker läuft, unabhängig vom Betriebssystem, auf dem der Container gestartet wird.
Schritt 2: Erstes Dockerfile – der naive Ansatz
\n\nDer einfachste Weg, eine Go-Anwendung zu „containerisieren“, ist, ein vollständiges Go-Image zu nehmen, den Code hineinzukopieren und zu starten. Erstellen Sie eine Datei Dockerfile:
# Используем официальный образ Go\nFROM golang:1.22\n\n# Устанавливаем рабочую директорию\nWORKDIR /app\n\n# Копируем файлы проекта\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\n# Собираем приложение\nRUN go build -o main .\n\n# Запускаем\nCMD ["./main"]\n\n\nLassen Sie uns das Image bauen und starten:
\n\ndocker build -t go-app:v1 .\ndocker run -p 8080:8080 go-app:v1\n\n\nÖffnen Sie den Browser und gehen Sie zu http://localhost:8080. Funktioniert es? Großartig. Aber es gibt ein Problem: die Image-Größe. Überprüfen wir das:
docker images go-app:v1\n\n\nSie werden etwas wie 1.2 GB sehen. Das ist eine Katastrophe! Das Image enthält einen ganzen Go-Compiler, einen Paketmanager, Systemdienstprogramme – all das, was für den Betrieb der fertigen Binärdatei nicht benötigt wird. In der Produktion würde ein solches Image ewig laden und jede Menge Speicherplatz beanspruchen.
Schritt 3: Multi-Stage-Build – das Geheimnis leichter Images
\n\nDocker unterstützt den Multi-Stage-Build. Dabei verwenden Sie mehrere FROM-Anweisungen in einem Dockerfile, und nur die letzte Stufe geht in das endgültige Image ein. Die erste Stufe – die „Build“-Stufe – enthält den Compiler und alle Werkzeuge. Die zweite Stufe ist minimal, z. B. scratch (leeres Image) oder alpine.
Lassen Sie uns unser Dockerfile umschreiben:
\n\n# Стадия 1: Сборка\nFROM golang:1.22 AS builder\n\nWORKDIR /app\n\n# Кэшируем зависимости\nCOPY go.mod go.sum ./\nRUN go mod download\n\n# Копируем исходный код и собираем\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux go build -o main .\n\n# Стадия 2: Финальный образ\nFROM alpine:latest\n\nRUN apk --no-cache add ca-certificates\n\nWORKDIR /root/\n\n# Копируем только бинарник из первой стадии\nCOPY --from=builder /app/main .\n\nEXPOSE 8080\n\nCMD ["./main"]\n\n\nWas ist hier passiert? Wir haben zwei Stufen erstellt: