Cultivation background
I worked overtime day and night to develop the simplest Go Hello world application. Although I just quit after printing, my boss also asked me to go online, the only application I can 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 us to deploy with docker, which makes us follow the trend and be taller...
First attempt
After visiting some Wulin friends, I found that it would be better to put the whole process into docker and compile it. After thinking about it, I got the following Dockerfile:
FROM golang:alpine WORKDIR /build COPY hello.go . RUN go build -o hello hello.go CMD ["./hello"]
Build image:
$ docker build -t hello:v1 .
Done. Let's get closer.
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 sister will peek and laugh at me...
Let's see how big the image is. It's said that pulling the image will be slower when it's big
$ docker docker images | grep hello hello v1 2783ee221014 44 minutes ago 314MB
Wow, there's 314MB. Has docker build changed into Java? Not everything is as big as possible...
Let's see why it's so big!
Look, we had 300+MB before the first instruction (WORKDIR). It's a little fierce!
Anyway, let's run first
$ docker run -it --rm hello:v1 hello world!
No problem, at least you can work ~
Second attempt
After some smoking and drinking, plus the advice of friends, we found that the basic image we used was 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 basic image. However, it's easy to say, I still spent some time. Finally, the Dockerfile looks like this:
FROM alpine WORKDIR /build COPY hello . CMD ["./hello"]
Run and try
$ 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 be found. Forget to compile hello first Go, come again~
$ go build -o hello hello.go
Then run docker build -t hello:v2, No problem, take two steps to try...
$ docker run -it --rm hello:v2 standard_init_linux.go:228: exec user process caused: exec format error
Failed! Well, the format is wrong. Originally, our development machine is not linux. Come again~
$ GOOS=linux go build -o hello hello.go
The docker build is finally finished. Run down quickly
$ docker run -it --rm hello:v2 hello world!
No problem. Let's see 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 the executable file hello, so I don't have to worry about being despised by others~
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 only have 5.3MB in front of the first instruction (WORKDIR). Happy!
Third attempt
After a show off, someone despised me and said that multi-stage construction is popular now. What's the problem with the second method? After careful consideration, we found that we should be able to build a docker image from the Go code, which is divided into three steps:
- If cgo cross platform compilation is involved in the native compilation of Go code, it will be more troublesome
- Build docker image with compiled executable file
- Write a shell script or makefile so that these steps can be obtained through a command
Multistage construction is to put all this into a Dockerfile. There is no source code leakage, and there is no need to use scripts to compile across platforms. It also obtains the smallest image.
Loving learning and pursuing 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 part is to build a builder image, in which the purpose is to compile the executable file hello. The second from part is to copy the executable file Hello from the first image, and use the smallest possible basic image Alpine to ensure that the final image is as small as possible. As for why not use a smaller scratch, It's because scratch really doesn't have anything. If there's a problem, there's no chance to take a look, and alpine is only 5MB, which won't have much impact on our service.
We ran first to verify:
$ docker run -it --rm hello:v3 hello world!
No problem, as expected! Look at the size:
$ 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
It is exactly the same size as the image built by the second method. Look at the content in the image:
$ docker run -it --rm hello:v3 ls -l /build total 1252 -rwxr-xr-x 1 root root 1281547 Mar 6 16:32 hello
There is only one executable hello file, perfect!
It is basically the same as the second final image, but we have simplified the process. We only need a Dockerfile and run a command. I don't need to fix those obscure shell s and makefile s.
Practice divine skill
So far, the team partners feel perfect and praise me one after another! However, I think I'm not only pursuing perfection but also lazy (fishing). Every time I'm asked to write such a Dockerfile that one line is fat and one line is thin, I still feel very annoyed, so I wrote a tool without telling my boss, and I'll show it~~
# Install it first $ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest goctl migrate —verbose —version v1.3.1 # Write Dockerfile with one click $ goctl docker -go hello.go
Done! Look at the generated Dockerfile
FROM golang:alpine AS builder LABEL stage=gobuilder ENV CGO_ENABLED 0 ENV GOOS linux ENV GOPROXY https://goproxy.cn,direct 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 tzdata ENV TZ Asia/Shanghai WORKDIR /app COPY --from=builder /app/hello /app/hello CMD ["./hello"]
Here are some points:
- cgo is disabled by default
- GOPROXY enabled
- Debugging information - ldflags="-s -w" is removed to reduce the image size
- CA certificates is installed so that you can use TLS certificates
- The local time zone is automatically set, so what we see in the log is Beijing time
Let's take a look at the image size built with this automatically generated Dockerfile:
$ docker images | grep hello hello v4 a7c3baed2706 4 seconds ago 7.97MB 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 installed CA certificates and tzdata. Verify:
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
There are only hello executable files, and the file size has been reduced from 1281KB to 851KB. Run and see:
$ docker run -it --rm hello:v4 hello world!
Well, well, I'm no longer entangled in Dockerfile. I'm going to learn other knowledge ~
Project address
https://github.com/zeromicro/go-zero
https://gitee.com/kevwan/go-zero
Do you think it's good? Welcome to call for rewards. Just light up the little star of GitHub ⭐ ️
Wechat communication group
Focus on the "micro service practice" official account and click on the exchange group to get the community community's two-dimensional code.