The science of uniting the background

I worked overtime day and night to develop the simplest Go Hello World app. Although I just ran and printed and quit, my boss also required me to launch the only app I could write.

The project structure is as follows:

.Exercises ── goCopy the code

Hello. Go looks like this:

package main

func main(a) {
    println("hello world!")}Copy the code

In addition, the boss required to use Docker deployment, it seems that we follow the trend, a little higher…

First try

After visiting some martial arts friends, I found that it would be better to throw the whole process into docker and 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"]
Copy the code

Build an image:

$ docker build -t hello:v1 .
Copy the code

There we go. Let’s take 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
Copy the code

Dude, the code THAT I wrote with great difficulty is also in there, it seems that the code can’t be bad, or the operation and maintenance girl will laugh at me…

Let’s see how big the mirror image is, because they say it takes longer to pull the mirror image

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

Wow, it’s 314MB. Did Docker build Java? Bigger is not always better…

Let’s see why it’s so big!

Let’s see, we already run 300+MB before the first instruction (WORKDIR).

Anyway, why don’t we take a run

$ docker run -it --rm hello:v1
hello world!
Copy the code

No problem, at least you can work

Second attempt

After a cigarette and wine, plus a friend to give advice, found that we used the basic mirror is too big.

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

And my friend told me that I could compile the code first, and then copy it in, so that there is no need for the huge base image, but it is easy to say, I still spend some time, finally Dockerfile looks like this:

FROM alpine

WORKDIR /build

COPY hello .

CMD ["./hello"]
Copy the code

Give it a run

$ 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
Copy the code

Hello. Go = hello. Go = hello

$ go build -o hello hello.go
Copy the code

Docker build-t hello:v2.

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

Failure! Well, the format is not correct, originally our development machine is not Linux ah, again ~

$ GOOS=linux go build -o hello hello.go
Copy the code

The docker build is finally done. Run

$ docker run -it --rm hello:v2
hello world!
Copy the code

No problem. Let’s 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
Copy the code

I don’t have to worry about anyone looking down on my code anymore

Docker images | grep hello hello v2 0 dd53f016c93 53 seconds line 6.61 MB hello v1 ac0e37173b85 25 minutes line 314 MBCopy the code

Wow, 6.61MB, absolutely!

Look, we only have 5.3MB ahead of the first instruction (WORKDIR). Happy!

Third attempt

After a bit of show-off, someone looked down on me and said that multi-phase builds are all the rage, so what’s wrong with the second approach? After careful consideration, we found that we need to be able to build a Docker image from the Go code, which is divided into three steps:

  1. The native compilationGoCode, if it involvescgoCross-platform compilation is a bit trickier
  2. Build with compiled executable filesdockerThe mirror
  3. writeshellScript ormakefileMake these steps available through a command

A multi-stage build is all in one Dockerfile, with no source leaks, no scripting to compile across platforms, and minimal mirroring.

Loving learning and pursuing perfection, I finally wrote the following Dockerfile: One more line means fat, one less line means 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"]
Copy the code

The first FROM FROM section is to build a Builder image in which to compile the executable hello, the second FROM section is to copy executable Hello FROM the first image, And mirroring Alpine as small a base as possible to make sure that the final image is as small as possible. As for why not use smaller Scratch, scratch really doesn’t have anything to look at, and Alpine is only 5MB, so it doesn’t have much impact on our service.

We ran first to verify:

$ docker run -it --rm hello:v3
hello world!
Copy the code

No problem, just as expected! Take a look at the size:

$ docker images | grep helloHello v3 F51e1116be11 8 hours ago 6.61MB Hello V2 0dd53f016c93 8 hours ago 6.61MB Hello V1 AC0e37173b85 8 hours ago 314MBCopy the code

The size of the image is exactly the same as that of the second method. Take a look at the mirror again:

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

Also only one executable Hello file, perfect!

It’s pretty much the same as the second final image, but we’ve simplified the process by simply using a Dockerfile and running a command instead of all the arcane shells and makefiles I need.

Siddhi practice

At this point, all the team members thought it was perfect and gave me thumbs up one after another! However, both the pursuit of perfection, but also like lazy (touch fish) I think it, every time LET me write such a line of fat, thin line, I still feel very tired, so I wrote a tool to hide the boss, I will show ~~

#Let's set it up
$ GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latestGoctl migrate - verbose - version v1.3.1#Write a Dockerfile with one click
$ goctl docker -go hello.go
Copy the code

Done! Look at the generated Dockerfile ha

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"]
Copy the code

Some of them can be understood:

  • Disabled by defaultcgo
  • To enable theGOPROXY
  • The debugging information is deleted-ldflags="-s -w"To reduce the image size
  • Installation of theca-certificates, so usedTLSThe certificate is fine
  • The local time zone is automatically set so that we see Beijing time in the log

Let’s look at the size of the image built with this auto-generated Dockerfile:

$ docker images | grep helloHello V4 v7c3baed2706 4 seconds ago 7.97MB Hello V3 F51e1116be11 8 hours ago 6.61MB Hello V2 0dd53f016c93 8 hours ago 6.61MB Hello v1 AC0e373b85 9 hours ago 362 MBCopy the code

Slightly larger because we have ca-Certificates and TZData installed. Verify:

Let’s see what’s in the mirror image:

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

There are only Hello executables, and the file size has been reduced from 1281KB to 851KB. Take a run:

$ docker run -it --rm hello:v4
hello world!
Copy the code

Ok, ok, no more entangling Dockerfile, I am going to learn something else ~

The project address

Github.com/zeromicro/g…

Do you think it’s good? Welcome to the tip. Simply light up the GitHub star ⭐️

Wechat communication group

Pay attention to the public account of “micro-service Practice” and click on the exchange group to obtain the QR code of the community group.