How To build Multistage Dockerfile for GO

Share on facebook
Share on google
Share on twitter
Share on linkedin
Durch Multistage builds in Dockerfiles ein leichtgewichtiges Image erstellen für eine Go Anwendung. ~16MB

Einleitung

Mit Docker kann man leichtgewichtige Images mit einer Applikation erstellen um diese schnellst möglich von A nach B zu bringen.

Ein solches Image sollte im Idealfall wenige Megabytes gross sein und schnellst möglich starten. Genau für dies eignet sich Docker.

Doch Vorsichtig, auch mit Docker hat man einiges zu Beachten, den aus einer Applikation welche nicht grösser ist als wenige MB, kann schnell ein Image entstehen mit mehreren hundert MB bis hin zu über 1Gb

Ziele

Am Ende dieses Atickels:

  • Verstehst du die Basics für den Einsatz von Multistage builds
  • Weisst du wie du das nächste Dockerfile schreibst für deine Anwendung(en)

Voraussetzungen

Als erstes benötigst du:

Du findest den ganzen Code in dem go-multistage-builder Repository

Erstellen des ersten Dockerfiles (Schlechte Variante)

Erstellt einen Projektordner und in diesem eine .Dockerfile. In meine fall nenne ich diese .Dockerfile_Bad.

FROM golang:1.14.2
WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o main .
EXPOSE 8080
ENTRYPOINT ["./main"]

Ich gehe hier nicht auf die einzelnen Commands ein da diese Steps selbsterklärend sind und zum anderen möchte ich nicht das Rad neu erfinden und empfehle denjenigen, die nicht alle ausdrücke verstehen, diese auf docs.Docker selbst nachzuschlagen.

Damit wir dies nutzen können benötigt es auch noch etwas an Code, den wir dann auch builden und starten können. Erstellt dazu eine main.go und fügt folgendes ein

package main

import "github.com/gin-gonic/gin"

func main() {
   r := gin.Default()
   r.GET("/ping", func(c *gin.Context) {
      c.JSON(200, gin.H{
         "message": "pong",
      })
   })
   r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

Dies ist nur eine Simple Go Anwendung mit dem Einsatz des Gin Frameworks welches für Web-Anwendungen sehr verbreitet ist.

Öffnet nun ein Terminal und gebt folgende commands ein:

go mod init
go mod tidy

Diese erstellen euch 2 neue Files go.mod und go.sum. dies ist das selbe wie bei npm die package.json und package.lock.

Nun habt ihr alles um euer erstes Image zu erstellen. Dies könnt ihr mit folgender Zeile erstellen

docker build -f .Dockerfile_Bad -t bad_go_image .

Heraus kommt dabei ein Dockerimage mit dem Namen bad_go_image und dem tag latest. Nun schauen wir und mal die Grösse von diesem Image an.

Grosses Image

Wie ihr Seht, das Image ist „nur“ 625MB gross. Würde man dies mit einer VM erstellen und z.b. ein Debian oder Ubuntu Betriebsystem benutzen, so wären es mehrere GB die benötigt werden. Aber, dies darf man auch nicht ganz so vergleichen, da die 2 unterschiedliche Aufbauarten sind. mehr dazu könnt ihr hier nachlesen: Docker vs. VM

Erstellen des zweiten Dockerfiles (bessere Variante)

Erstellt in dem selben Ordner ein zweites .Dockerfile, bei mir .Dockerfile_Good mit folgendem Inhalt.

# Obtain certs for final stage
FROM alpine:3.11.5 as authority
RUN mkdir /user && \
    echo 'appuser:x:1000:1000:appuser:/:' > /user/passwd && \
    echo 'appgroup:x:1000:' > /user/group
RUN apk --no-cache add ca-certificates

# Build app binary for final stage
FROM golang:1.14.2-alpine3.11 AS builder
WORKDIR /app
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o /main .

# Final stage
FROM scratch
COPY --from=authority /user/group /user/passwd /etc/
COPY --from=authority /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /main ./
USER appuser:appgroup
EXPOSE 8080
ENTRYPOINT ["./main"]

Hier werden im Gegensatz zum anderen Dockerfile gleich 3 Images beschrieben.

Zum ersten das Image welches Zertifikate erstellt, zum zweiten das builder Image welches euren Code kopiert die GO-Module herunterlädt und die Anwendung erstellt. Als letztes wird ein etwas „spezielleres“ Image genommen FROM scratch was soviel bedeutet wie „von Grund auf“. Sprich wir nehmen ein Blankes Image beidem noch niemand etwas vorinstalliert hat und es daher sehr klein ist.

Speziel zusehen ist, das wir in diesem Image nichts ausführen wie etwa apt update oder apt install etc. Sondern wir Kopieren uns die Daten von den vorhergehenden Images in das Blanke Image hinein, Öffnen den Port 8080 und als Entrypoint starten wir die Applikation.

Nun habt ihr alles um das zweite Image zu erstellen. Dies könnt ihr mit folgender Zeile erstellen

docker build -f .Dockerfile_Good -t good_go_image .

Heraus kommt dabei ein Dockerimage mit dem Namen good_go_image und dem tag latest. Nun schauen wir und mal die Grösse von diesem Image an.

Besseres Image

Dieses Image ist mit 15.24MB „etwas“ kleiner 98.4% kleiner um genau zu sein. Dies bedeutet, ihr müsst nur noch 15 MB Pullen oder Pushen, wen ihr diese App/Image in einem Cluster Betreibt benötigt ihr Weniger Speicher, Wen ihr diese auf 100 Pods ausrollen wollt, seit ihr wesentlich schneller da ihr nur 1’500MB verschiebt und nicht wie mit dem ersten Image 977’000MB

Hier nochmals Beide Images zusammen

Übersicht beider Images

Diese Art des Builden, kann man auch für andere Programmiersprachen benutzen wie z.b. Node C C++ uvm.

Den gesamten Code Welche ich hier Gepostet habe, findet ihr auch im Git

Achim

Achim

Die reinste Form des Wahnsinns ist es, alles beim Alten zu lassen und gleichzeitig zu hoffen, dass sich etwas ändert!

Dieser Beitrag hat einen Kommentar

Schreibe einen Kommentar

Haben Sie Fragen

Zögern Sie nicht und Kontaktieren Sie und über unser Kontaktformular