请选择 进入手机版 | 继续访问电脑版

马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

创建优化的Go镜像文件以及踩过的坑

[复制链接]
查看2013 | 回复0 | 2019-10-24 09:45:20 | 显示全部楼层 |阅读模式

在Docker上创建Go镜像文件并不困难,但创建的文件很大,靠近1G,利用起来不太方便。Docker镜像的一个告急困难就是怎样优化,创建小的镜像。我们可以用多级构建的方法来创建Docker镜像文件,它也不复杂。但由于利用这种方法时,需要用简版的Linux(Alpine),它带来了一系列的题目。本文陈诉怎样解决这些题目并乐成创建优化的Go镜像文件,优化之后只有14M。

单级构建:

我们用一个Go步伐作为例子来展示怎样创建Go镜像。下面就是这个步伐的目次结构。

094520nv7vmnvwvcmorymy.jpg

Go步伐的具体内容并不告急,只要能运行就行了。我们重点关注“docker”子目次(“kubernetes”子目次里的文件有别的用途,会在别的的文章中讲解)。它里面有三个文件。“docker-backend.sh”是创建镜像的下令文件,“Dockerfile-k8sdemo-backend”是多级构建文件,“Dockerfile-k8sdemo-backend-full”是单级构建文件,

  1. <code>FROM golang:latest # 从Docker库中获取尺度golang镜像
  2. WORKDIR /app # 设置镜像内的当前工作目次
  3. COPY go.mod go.sum ./ # 拷贝Go的包管理文件
  4. RUN go mod download # 下载依赖包中的依赖库
  5. COPY . . #从宿主机拷贝文件到镜像
  6. WORKDIR /app/cmd # 设置新的镜像内的当前工作目次
  7. RUN GOOS=linux go build -o main.exe #编译Go步伐,并在天生可实行文件
  8. CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait" # 保持镜像不停运行,容器不被停掉</code>
复制代码

上面就是“Dockerfile-k8sdemo-backend-full”镜像文件。请阅读文件中的表明以得到表明。

天生镜像容器

  1. <code>cd /home/vagrant/jfeng45/k8sdemo/
  2. docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend-full -t k8sdemo-backend-full .</code>
复制代码

运行镜像容器,“--name k8sdemo-backend-full”是给这个容器一个名字(k8sdemo-backend-full),最后的“k8sdemo-backend-full”是镜像的名字

  1. <code>docker run -td --name k8sdemo-backend-full k8sdemo-backend-full</code>
复制代码

登录镜像容器, 其中“a95c”是容器ID的前四位。

  1. <code>docker exec -it a95c /bin/bash</code>
复制代码

文件里有一条语句需要特殊表明一下“COPY . .”,它把文件从宿主机拷贝到镜像里,在镜像里已经用“WORKDIR”设置了当前工作目次,那么宿主机的“.”(当前目次)是哪个目次呢?它不是Dockerfile文件地点的目次,而是你运行“Docker build”下令时地点的目次。

我们要把整个步伐都拷贝到镜像里,那么在运行docker下令时一定是在步伐的根目次,也就是“k8sdemo”目次。但是与容器有关的文件都在“script”目次的子目次下,那么当你运行“Docker build”下令时,它是怎么找到Docekrfile的呢?这里有一个告急的概念就是“build cotext”(构建上下文),由它来决定Dockerfile的缺省目次。当你运行“docker build -t k8sdemo-backend .”创建镜像时,它会从“build cotext”的根目次去找Dockerfile文件,缺省值是你运行docker下令的目次。但由于我们的Dockerfile在别的的目次里,因此需要在下令里加一个“-f”选项来指定Dockerfile的位置,下令如下。 其中“-t k8sdemo-backend-full” 是指明镜像名,格式是“name:tag”, 我们这里没有tag,就只有镜像名。

  1. <code>docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend-full -t k8sdemo-backend-full .</code>
复制代码

详情请参见Dockerfile reference

如许创建的镜像用的是全版的Linux系统,因此比力大,大概靠近1G。如果要想优化,就要用多级构建。

Multi-stage builds(多级构建):

单级构建只有一个“From”语句,而在多级构建中,有多个“From”,每个“From”构成一级。例如,下面的文件有两个“From”,是一个二级构建。每一级都可以根据需要选择得当自己的底子(base)镜像来构造本级镜像。每级镜像完成之后,下一级镜像可选择只保留上一级构建中对自己有效的最终文件,而删除所有的中央产物,如许就大大节省了空间。详情请参见Use multi-stage builds

下面就是多级构建的dockerfile(“Dockerfile-k8sdemo-backend”).

  1. <code>FROM golang:latest as builder # 本级镜像用“builder”标识
  2. # Set the Current Working Directory inside the container
  3. WORKDIR /app
  4. COPY go.mod go.sum ./
  5. RUN go mod download
  6. COPY . .
  7. WORKDIR /app/cmd
  8. # Build the Go app
  9. #RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main.exe
  10. RUN go build -o main.exe
  11. ######## Start a new stage from scratch #######
  12. FROM alpine:latest
  13. RUN apk --no-cache add ca-certificates
  14. WORKDIR /root/
  15. RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
  16. # Copy the Pre-built binary file from the previous stage
  17. COPY --from=builder /app/cmd/main.exe . #把“/app/cmd/main.exe”文件从“builder”中拷贝到本级的当前目次
  18. # Command to run the executable
  19. CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"</code>
复制代码

创建镜像:

  1. <code>cd /home/vagrant/jfeng45/k8sdemo/
  2. docker build -f ./script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend -t k8sdemo-backend .</code>
复制代码

登录镜像:

  1. <code>docker run -it --name k8sdemo-backend k8sdemo-backend /bin/sh</code>
复制代码

上面的文件把构造过程分成两部分,第一部分编译并天生Go可实行文件,用的是是全版Linux. 第二部分是拷贝可实行文件到合适的目次并保持容器运行,用的是简化版Linux。第一部分的下令与单级构建指令根本雷同,第二部分的下令会在反面表明。

利用这种方法大大淘汰了空间占用,创建的Docker镜像只有14M,但由于它利用的简化版的Linux(Alpine),导致我踩了很多坑,下面看看这些坑是怎样被填上的。

踩过的坑:

1. 找不到文件

创建镜像乐成后,登录镜像:

  1. <code>docker run -it --name k8sdemo-backend k8sdemo-backend /bin/sh</code>
复制代码

运行编译后的Go可实行文件“main.exe”,错误信息如下:

  1. <code>~ # ./main.exe
  2. ./main.exe not found</code>
复制代码

Go是一个静态编译的语言,也就是说在编译时就把需要的库存放在编译好的步伐里了,如许在实行时就不需要再动态链接别的库,使得运行起来非常方便。但并不是所有环境下都是如许,例如但当你利用了cgo(让Go步伐可以调用C步伐)时,通常需要动态链接libc库(在Linux里是glibc)。Go里的net和os/user库都用了cgo。但由于Apline的Linux版本没有libc库,如许在运行时就找不到动态链接,因此报错。它有两种办法来解决:

  • CGO_ENABLED=0:当你在编译Go时加了这个参数,编译时就不会利用cgo,当然也就意味着利用cgo的库都不能用了。这是最简单的办法,但它对你的步伐有所限定。
  • 利用musl:musl是一个轻量级的libc库。Apline的Linux版本里自带musl库,你只要参加如下下令就行了。
  1. <code>RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2</code>
复制代码

关于musl的详情请参见Statically compiled Go programs, always, even with cgo, using musl

关于这个错误的讨论请参见Installed Go binary not found in path on Alpine Linux Docker

2. Zap报错

Zap是一个很盛行的Go日志库,我在步伐里用它来输出日志。当加上上面的语句后,原来的错误消散了,但又有一个新的错。它是由Zap产生的。

  1. <code>~ # ./main.exe
  2. panic: runtime error: invalid memory address or nil pointer dereference
  3. [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x6a37ab]
  4. goroutine 1 [running]:
  5. github.com/jfeng45/k8sdemo/config.initLog(0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
  6. /app/config/zap.go:94 +0x1fb
  7. github.com/jfeng45/k8sdemo/config.RegisterLog(0x0, 0x0)
  8. /app/config/zap.go:42 +0x42
  9. github.com/jfeng45/k8sdemo/config.BuildRegistrationInterface(0x751137, 0x5, 0x43ab77, 0x984940, 0xc00002c750, 0xc000074f50)
  10. /app/config/appConfig.go:23 +0x26
  11. main.testRegistration()
  12. /app/cmd/main.go:18 +0x3a
  13. main.main()
  14. /app/cmd/main.go:11 +0x20</code>
复制代码

我如今也不非常清楚出错的原因,应该是跟Musl库有关。估计是Zap用到的某个库与Musl不兼容。我把日志换成另一个库Logrus题目就不存在了。这确实有点小遗憾,Zap是迄今为止我发现的最好的Go日志库。如果你坚持用Zap的话就只能用全版Linux,忍受大的镜像文件;大概改用Logrus日志库,如许就可以享受小的镜像文件。

3. k8s摆设不乐成

换成Logrus之后,就没再报错,Docker里的步伐运行正常。但如果你用这个镜像创建k8s摆设时又出了题目。

下面是k8s创建摆设的下令:

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

错误信息是“CrashLoopBackOff”。它产生的原因是容器要求里面的步伐不停运行,一旦运行竣事,容器就会停掉。k8s发现容器停掉之后会重新摆设容器,然后又被停掉,如许就陷入了死循环。
解决的办法是在镜像文件里参加如下下令:

  1. <code>CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"</code>
复制代码

详情请参见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出错

参加下令,重新天生镜像之后,果然解决了死循环的题目,k8s摆设没有报错,但Pod又有了新的错误如下,“k8sdemo-backend-deployment-6b99dc6b8c-n6bnt”的“STATUS”是“Error”。

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

原因是在Docker文件里运行了如下下令:

  1. <code>CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"</code>
复制代码

但Alpine里没有“/bin/bash”.需要改成“/bin/sh”,需要修改成如下下令:

  1. <code>CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"</code>
复制代码

修改之后,k8s摆设乐成,步伐运行正常。

源码:

完备源码的github链接

索引

  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

本文由博客一文多发平台 OpenWrite 发布!







来源:https://www.cnblogs.com/code-craftsman/p/11730136.html
C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则