6

practice background

I worked day and night and worked overtime to develop the simplest Go Hello world application. Although I just ran and printed it and then quit, my boss also asked me to launch this only application that I could write.

The project structure is as follows:

 .
├── go.mod
└── hello.go

hello.go code is as follows:

 package main

func main() {
    println("hello world!")
}

Moreover, the boss asked to use docker to deploy, it seems that we keep up with the trend and be a little taller. . .

first try

After visiting some martial arts friends, I found that I just put the whole process into docker to compile it. After some thinking, I got the following Dockerfile :

 FROM golang:alpine

WORKDIR /build

COPY hello.go .

RUN go build -o hello hello.go

CMD ["./hello"]

Build the image:

 $ docker build -t hello:v1 .

Done, let's get a closer look.

 $ docker run -it --rm hello:v1 ls -l /build
total 1260
-rwxr-xr-x    1 root     root       1281547 Mar  6 15:54 hello
-rw-r--r--    1 root     root            55 Mar  6 14:59 hello.go

Good guy, the code I finally wrote is also in it. It seems that the code can't be written badly, otherwise the operation and maintenance girl will laugh at me when she peeks. . .

Let's take a look at how big the image is. It is said that pulling the image will be slower if it is large.

 $ docker images | grep hello
hello         v1    2783ee221014   44 minutes ago   314MB

Wow, there is actually 314MB, does docker build change to Java ? Not everything is bigger the better. . .

Let's see why it's so big!

Look, before we ran the first command ( WORKDIR ), it was already 300+MB, which is a bit fierce!

Anyway, let's run and see

 $ docker run -it --rm hello:v1
hello world!

It's alright, it works anyway~

second attempt

After some tobacco and alcohol, plus the advice of friends, I found that the basic image we used was really too big.

 $ docker images | grep golang
golang    alpine     d026981a7165   2 days ago          313MB

And my friend told me that I can compile the code first, and then copy it in, so I don't need the huge base image, but it's easier said than done, I still put a lot of effort into it, and finally Dockerfile like this:

 FROM alpine

WORKDIR /build

COPY hello .

CMD ["./hello"]

try running

 $ docker build -t hello:v2 .
...
=> ERROR [3/3] COPY hello .                         0.0s
------
 > [3/3] COPY hello .:
------
failed to compute cache key: "/hello" not found: not found

No, hello can't find it, forgot to compile it first hello.go , come again~

 $ go build -o hello hello.go

Run again docker build -t hello:v2 . , no problem, try two steps. . .

 $ docker run -it --rm hello:v2
standard_init_linux.go:228: exec user process caused: exec format error

fail! Well, the format is wrong, it turns out that our development machine is not linux ah, come again~

 $ GOOS=linux go build -o hello hello.go

Re docker build finally got it, run down

 $ docker run -it --rm hello:v2
hello world!

No problem, let's take a look at the content and size.

 $ docker run -it --rm hello:v2 ls -l /build
total 1252
-rwxr-xr-x    1 root     root       1281587 Mar  6 16:18 hello

There is only hello this executable file, no longer have to worry about others despising my code~

 $ docker images | grep hello
hello    v2   0dd53f016c93   53 seconds ago      6.61MB
hello    v1   ac0e37173b85   25 minutes ago      314MB

Wow, 6.61MB, absolutely!

Look, we run the first command ( WORKDIR ) and there is only 5.3MB in front of it, happy!

third attempt

After showing off, someone actually despised me and said that multi-stage construction is popular now, so what is the problem with the second method? After careful consideration, we found that we need to be able to construct the docker image from the Go code, which is divided into three steps:

  1. Native compilation Go code, if it involves cgo cross-platform compilation will be more troublesome
  2. Build the docker image with the compiled executable
  3. Write shell script or makefile and let these steps get by one command

Multi-stage build is to put all this into one Dockerfile , without source code leakage, without scripting for cross-platform compilation, and obtaining the smallest image.

I love learning and pursue perfection. I finally wrote the following Dockerfile , one more line is fat, and one less line is thin:

 FROM golang:alpine AS builder

WORKDIR /build

ADD go.mod .
COPY . .
RUN go build -o hello hello.go


FROM alpine

WORKDIR /build
COPY --from=builder /build/hello /build/hello

CMD ["./hello"]

The first FROM starts with building a builder image, in which the purpose is to compile an executable file hello , the second From beginning part is from the first image copy out of the executable file hello , and use the smallest possible base image alpine to ensure the final image As small as possible, as for why not use smaller scratch , because scratch there is really nothing, and there is no chance to even take a look at it, and alpine It's only 5MB, so it won't have much impact on our services.

Let's run it first to verify:

 $ docker run -it --rm hello:v3
hello world!

No problem, as expected! See how the size looks like:

 $ docker images | grep hello
hello    v3     f51e1116be11   8 hours ago    6.61MB
hello    v2     0dd53f016c93   8 hours ago    6.61MB
hello    v1     ac0e37173b85   8 hours ago    314MB

The size of the image built by the second method is exactly the same. Take a look at the contents of the mirror:

 $ docker run -it --rm hello:v3 ls -l /build
total 1252
-rwxr-xr-x    1 root     root       1281547 Mar  6 16:32 hello

Also there is only one executable hello file, perfect!

It is basically the same as the second final image, but we simplified the process and only need one Dockerfile , just run one command, I don't need to fix those obscure shell makefile too.

Divine Skills

So far, the team members feel that it is perfect, and they all give me praise! However, as a person who pursues perfection and likes to be lazy (fishing), I feel that every time I am asked to write such a line that one line is more fat, and one line less is thin Dockerfile , I still find it annoying. Yes, so I wrote a tool without telling the boss, I will show it~~

 # 安装一下先
$ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest
# 一键编写 Dockerfile
$ goctl docker -go hello.go

Get it! Look at the generated Dockerfile ha

 FROM golang:alpine AS builder

LABEL stage=gobuilder

ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct

RUN apk update --no-cache && apk add --no-cache tzdata

WORKDIR /build

ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/hello ./hello.go


FROM alpine

RUN apk update --no-cache && apk add --no-cache ca-certificates
COPY --from=builder /usr/share/zoneinfo/Asia/Shanghai /usr/share/zoneinfo/Asia/Shanghai
ENV TZ Asia/Shanghai

WORKDIR /app
COPY --from=builder /app/hello /app/hello

CMD ["./hello"]

Some of them can be understood:

  • Disabled by default cgo
  • Enabled GOPROXY acceleration go mod download
  • Removed debug info -ldflags="-s -w" to reduce image size
  • Installed ca-certificates , so using TLS certificate is no problem
  • tzdata installed in builder mirror, and copied only the required time zone in the final mirror
  • The local time zone is automatically set, so what we see in the log is Beijing time

Let's take a look at the size of the image built with this automatically generated Dockerfile :

 $ docker images | grep hello
hello     v4    94ba3ece3071   4 hours ago     6.66MB
hello     v3    f51e1116be11   8 hours ago     6.61MB
hello     v2    0dd53f016c93   8 hours ago     6.61MB
hello     v1    ac0e37173b85   9 hours ago     314MB

Slightly larger because we copied ca-certificates and tzdata . Verify it:

Let's see what's in the mirror:

 $ docker run -it --rm hello:v4 ls -l /app
total 832
-rwxr-xr-x    1 root     root        851968 Mar  7 08:36 hello

It is also only hello executable file, and the file size has been reduced from 1281KB to 851KB. Take a run and see:

 $ docker run -it --rm hello:v4
hello world!

And you can specify the base image as scratch when generating Dockerfile ---, so the image will be smaller, but you can't log in directly through sh .

 $ goctl docker -base scratch -go hello.go

The size is also really small:

 $ docker images | grep hello
hello   v5   d084eed88d88   4 seconds ago   1.07MB
hello   v4   94ba3ece3071   15 hours ago    6.66MB
hello   v3   f51e1116be11   4 days ago      6.61MB
hello   v2   0dd53f016c93   4 days ago      6.61MB
hello   v1   ac0e37173b85   4 days ago      314MB

Look at what's in the mirror

I compiled the Macbook M1 linux/arm64 image on ---ab5fca42163ebffaae7d5c73d94fff1e---, I guess you usually want to play the image of linux/amd64 , just use the following command:

 $ docker build --rm --platform linux/amd64 -t hello:v6 .

Okay, no more entanglement Dockerfile , I'm going to learn new skills~

project address

https://github.com/zeromicro/go-zero

Feel good? Welcome to tip, tip just light up GitHub Little Star⭐️

WeChat exchange group

Follow the official account of " Microservice Practice " and click on the exchange group to get the QR code of the community group.


kevinwan
931 声望3.5k 粉丝

go-zero作者