Go create an optimized image file and stepped pit

Go to create image files on Docker is not difficult, but a large file established close 1G, inconvenient to use. A major problem is how to optimize Docker image, create a small mirror. We can use multi-level building to create Docker image file, it is not complicated. However, due to the use of this method requires a simple version of Linux (Alpine), it brings a series of problems. Only 14M This article describes how to solve these problems and successfully create an optimized image file Go, after optimization.

Single-level building:

We show you how to create a mirror with a Go Go program as an example. Here is the directory structure of the program.

file

Go program details are not important, as long as the run on the line. We focus on the "docker" subdirectory ( "kubernetes" subdirectory of the file has other uses, will explain in another article). It contains three files. "Docker-backend.sh" is to create a mirror image of a command file, "Dockerfile-k8sdemo-backend" is a multi-stage build file, "Dockerfile-k8sdemo-backend-full" is a single-stage build file,

FROM golang:latest # 从Docker库中获取标准golang镜像
WORKDIR /app # 设置镜像内的当前工作目录
COPY go.mod go.sum ./ # 拷贝Go的包管理文件
RUN go mod download # 下载依赖包中的依赖库
COPY . . #从宿主机拷贝文件到镜像
WORKDIR /app/cmd # 设置新的镜像内的当前工作目录
RUN GOOS=linux go build -o main.exe #编译Go程序,并在生成可执行文件
CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait" # 保持镜像一直运行,容器不被停掉

Above is the "Dockerfile-k8sdemo-backend-full" image file. Please read the comments in the file for an explanation.

Creating a mirrored container

cd /home/vagrant/jfeng45/k8sdemo/
docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend-full -t k8sdemo-backend-full .

Run-time image of the container, "- name k8sdemo-backend-full" is to give the container a name (k8sdemo-backend-full), the last of the "k8sdemo-backend-full" is the name of the mirror

docker run -td --name k8sdemo-backend-full k8sdemo-backend-full

Log mirror container, wherein "a95c" is the ID of the container top four.

docker exec -it a95c /bin/bash

File has a statement requiring special explanation "COPY..", Which copy the files from the host to the mirror, the mirror image already use "WORKDIR" set the current working directory, then the host of "." (Current directory ) which directory it? It is not Dockerfile directory where the file, but you run the directory where the command "Docker build".

We want the whole program are copied to the mirror, the docker then when you run the command must be in the root directory of the program, which is "k8sdemo" directory. But with container-related files in a subdirectory "script" directory, then when you run the "Docker build" command, which is how to find Docekrfile it? There is an important concept is to "build cotext" (building context), it is determined by the default directory Dockerfile of. When you run the "docker build -t k8sdemo-backend." Create a mirror, it will go Dockerfile file from the "build cotext" the root directory, the default value is the directory you run docker command. However, due to our Dockerfile in another directory, it is necessary in order Riga a "-f" option to specify the location Dockerfile, the command is as follows. Where the "-t k8sdemo-backend-full" is specified image name, the format is "name: tag", we do not have tag, only mirror name.

docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend-full -t k8sdemo-backend-full .

For details, see Dockerfile reference

Such mirrors are created with the full version of the Linux system, and therefore relatively large, probably closer to 1G. If you want to optimize, it is necessary to build multistage.

Multi-stage builds (multistage Construction):

Single-level building is only a "From" statement, but in a multi-level building, there are multiple "From", each "From" constitute one. For example, there are the following two documents "From", it is a two constructs. Each one can choose the base (base) is configured to present a mirror image as desired level. After the completion of each level mirror, the mirror can select the next level to retain only build on a final document useful to themselves and to remove all of the intermediate products, thus saving space. For details, see Use multi-stage builds

Here is a multi-stage construction dockerfile ( "Dockerfile-k8sdemo-backend").

FROM golang:latest as builder # 本级镜像用“builder”标识
# Set the Current Working Directory inside the container
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
WORKDIR /app/cmd
# Build the Go app
#RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main.exe
RUN go build -o main.exe

######## Start a new stage from scratch #######
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
# Copy the Pre-built binary file from the previous stage
COPY --from=builder /app/cmd/main.exe . #把“/app/cmd/main.exe”文件从“builder”中拷贝到本级的当前目录
# Command to run the executable
CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"

Create a mirror:

cd /home/vagrant/jfeng45/k8sdemo/
docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend -t k8sdemo-backend .

Login Mirror:

docker run -it --name k8sdemo-backend k8sdemo-backend /bin/sh

The above configuration file to process into two parts, the first compiled executable file and generates Go, using a full version of Linux. The second part is a copy of the executable file to the appropriate directory and running holding container, using a simplified version of Linux. A first portion constructed of a single stage instruction command is basically the same, the second portion of the command explained later.

Using this method greatly reduces the footprint, create a mirror of Docker only 14M, but because it uses a simplified version of Linux (Alpine), led me to step on a lot of pits, these pits below to see how it is to fill in.

Stepped pit:

1. File Not Found

After the image is successfully created, log mirroring:

docker run -it --name k8sdemo-backend k8sdemo-backend /bin/sh

Go run the compiled executable file "main.exe", the following error message:

~ # ./main.exe
./main.exe not found

Go is a statically compiled language, that is inventory at compile time needed to put in place the compiled program, so do not need to when performing other dynamic link library, makes it very easy to run up. But not all cases are under way, for example, but when you use cgo (Let Go program can call C program), usually requires a dynamic link library libc (in Linux is glibc). Go inside the net and os / user libraries used cgo. But because there is no Linux version Apline libc library, so it can not be found at runtime dynamic linking, and therefore error. There are two ways to solve:

  • CGO_ENABLED = 0: When you add this parameter when compiling Go, compile time will not use cgo, of course, means that the use of the library cgo not be used. This is the easiest way, but it does to your program restrictions.
  • Use musl: musl is a lightweight libc library. Linux version comes with musl Apline's library, you simply add the following command on the line.
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2

For details on musl, see Statically compiled Go programs, always, even with cgo, using musl

Discussion about this error, see Installed Go binary not found in path on Alpine Linux Docker

2. Zap error

Zap is a very popular Go logging library, I use it to output log in the program. When combined with the above statement, the original error is gone, but we have a new fault. It is produced by the Zap.

~ # ./main.exe
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6a37ab]

goroutine 1 [running]:
github.com/jfeng45/k8sdemo/config.initLog(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
        /app/config/zap.go:94 +0x1fb
github.com/jfeng45/k8sdemo/config.RegisterLog(0x0, 0x0)
        /app/config/zap.go:42 +0x42
github.com/jfeng45/k8sdemo/config.BuildRegistrationInterface(0x751137, 0x5, 0x43ab77, 0x984940, 0xc00002c750, 0xc000074f50)
        /app/config/appConfig.go:23 +0x26
main.testRegistration()
        /app/cmd/main.go:18 +0x3a
main.main()
        /app/cmd/main.go:11 +0x20

I am not very clear reason for the error, it should be with Musl libraries. Zap estimate is used in a library is not compatible with Musl. I log into another library Logrus problem does not exist. This is indeed a little regret, Zap Go is the best so far I have found the log database. If you stick with Zap, then you can only use the full version of Linux, put up large image files; or use Logrus logging library, so that you can enjoy a small image file.

3. k8s unsuccessful deployment

After replaced Logrus, then there is no error, Docker in the normal operating procedures. But if you use this image to create k8s deployment time and out of the question.

Here is k8s create a deployment command:

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/backend$ kubectl get pod k8sdemo-backend-deployment-6b99dc6b8c-2fwnm
NAME                                          READY   STATUS             RESTARTS   AGE
k8sdemo-backend-deployment-6b99dc6b8c-2fwnm   0/1     CrashLoopBackOff   42         3h10m

The error message is "CrashLoopBackOff". The reason it is produced inside the container requirements of the program has been run, once the end of the run, the container will be stopped. k8s found to redeploy the vessel after the vessel stopped, and then was stopped, so that in an infinite loop.
The solution is to add the following command in the image file:

CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"

详情请参见How can I keep a container running on Kubernetes?My kubernetes pods keep crashing with “CrashLoopBackOff” but I can't find any log

4. Pod Error

Join command, and then rebuild the mirror, it really solves the problem of the cycle of death, k8s deployment not being given, but Pod has a new error as follows, "k8sdemo-backend-deployment-6b99dc6b8c-n6bnt" of "STATUS" is "Error" .

vagrant@ubuntu-xenial:~/jfeng45/k8sdemo/script/kubernetes/backend$ kubectl get pod
NAME                                           READY   STATUS    RESTARTS   AGE
envar-demo                                     1/1     Running   8          16d
k8sdemo-backend-deployment-6b99dc6b8c-n6bnt    0/1     Error     1          6s
k8sdemo-database-deployment-578fc88c88-mm6x8   1/1     Running   2          4d21h
nginx-deployment-77fff558d7-84z9z              1/1     Running   3          10d
nginx-deployment-77fff558d7-dh2ms              1/1     Running   3          10d

The reason is to run the following command in the Docker file:

CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"

Alpine but there are no "/ bin / bash" need to change "/ bin / sh", need to be modified into the following command:

CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"

After modification, k8s deployment is successful, the program runs normally.

Source:

Complete source code github link

index

  1. Dockerfile reference
  2. Use multi-stage builds
  3. Statically compiled Go programs, always, even with cgo, using musl
  4. Installed Go binary not found in path on Alpine Linux Docker
  5. How can I keep a container running on Kubernetes?
  6. My kubernetes pods keep crashing with “CrashLoopBackOff” but I can't find any log
  7. Building Docker Containers for Go Applications

This article from the blog article multiple platforms OpenWrite release!

Guess you like

Origin www.cnblogs.com/code-craftsman/p/11730136.html