使用Docker构建容器可以极大的下降运维成本,提升部署效率,同时很是方便对服务的平行扩展。然而在构建容器镜像过程当中的,存在着一个难以免的问题,就是若是使用常见的发行版本做为程序运行的基础环境,那么即便一个服务自己的运行文件很是小,最终构建的镜像也可能会有会在运行环境的镜像的基础上变得更大,动不动就是数百M的体积。java
以最经常使用于微服务开发的golang为例,golang的二进制程序能够一次开发跨平台编译,处处运行,所以其自己的程序自洽性其实很是完善,也不多会依赖复杂的外部环境,所以经常使用的发行版本镜像硕大的体积其实很不必。所以,alpine成为了一个很是好的选择。最终alpine也不负众望,将最终的镜像体积减少到了10M+左右,已经压缩了很是大的空间了,结果还算理想。linux
然而,能不能更小呢?git
docker自带的scratch镜像给了我一个思路:没有任何镜像会比空镜像更小。那使用scratch制做出来的镜像也必然是最小的。那么scratch是否是一个好的选择呢?scratch镜像是docker自带的空镜像,我也曾见过数篇文章推荐使用其做为golang的运行镜像,然而,在最终实践的时候,遇到的bug却会让人倍感疑惑。golang
# 1. 使用官方的golang镜像构建运行的容器docker
golang程序很是简单:windows
package mainbash
import "fmt"服务器
func main(){运维
fmt.Println("你好,世界!")微服务
}
Dockerfile也不难:
FROM library/golang:alpine as build
MAINTAINER fanxiaoqiang <fan_xq@live.com>
ADD . /data/
WORKDIR /data/
RUN export GO111MODULE=on
RUN export GOSUMDB=off
RUN unset GOPATH
RUN go env -w GOPROXY=https://goproxy.cn
RUN go build -o server helloworld.go
# deploy-image
FROM scratch
#FROM alpine
COPY --from=build /data/server /data/server
#COPY --from=build /data/entrypoint.sh /data/entrypoint.sh
WORKDIR /data/
EXPOSE 9090
CMD ["/data/server"]
构建,运行!
docker build -t hello .
&& docker run
-p 9090:9090
hello
容器成功的输出了helloword,最终的镜像大小只有2.068MB,效果很是理想。而后scratch是否真的可以知足golang容器化的全部需求呢?下面咱们继续看。
# 2. 使用scratch构建稍复杂的golang的运行容器
这一次咱们构建一个golang实现的http服务器。在开始以前,首先强调一下scratch是一个空镜像,意味这Docker内部不存在任何环境和依赖库。机智的读者可能已经想到我想说什么,接下来开始进行实验。
golang程序:
package main
import (
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
server := http.Server{
Addr: ":9090",
ReadTimeout: 10 * time.Second,
}
//log.Println("start running")
log.Println("start running")
server.ListenAndServe()
//合建chan
c := make(chan os.Signal)
//监听指定信号 ctrl+c kill
signal.Notify(c, os.Interrupt, os.Kill)
//阻塞直到有信号传入
//阻塞直至有信号传入
s := <-c
log.Println("exit!", s)
}
Docker文件第一节相同,这里就不放了。
如今让咱们尝试 运行,
docker build -t hello .
&& docker run
-p 9090:9090
hello
构建镜像的过程依旧轻松愉快:
Building image...
Preparing build context archive...
[==================================================>]9/9 files
Done
Sending build context to Docker daemon...
[==================================================>] 2.859kB
Done
Step 1/14 : FROM library/golang:alpine as build
---> dda4232b2bd5
Step 2/14 : MAINTAINER fanxiaoqiang <fan_xq@live.com>
---> Using cache
---> 546d5bcb606b
Step 3/14 : ADD . /data/
---> d6bcfc3f9976
Step 4/14 : WORKDIR /data/
---> Running in 4a8f0fa4c9c4
Removing intermediate container 4a8f0fa4c9c4
---> 6f6092bc91a8
Step 5/14 : RUN export GO111MODULE=on
---> Running in 44a83bb9c9a9
Removing intermediate container 44a83bb9c9a9
---> ea199d64e9d9
Step 6/14 : RUN export GOSUMDB=off
---> Running in df368787ddd7
Removing intermediate container df368787ddd7
---> c338c09c4980
Step 7/14 : RUN unset GOPATH
---> Running in c6016dd29cd8
Removing intermediate container c6016dd29cd8
---> 8f7004cb8ed5
Step 8/14 : RUN go env -w GOPROXY=https://goproxy.cn
---> Running in 237a89c7a644
Removing intermediate container 237a89c7a644
---> 5b5b9b8efb43
Step 9/14 : RUN go build -o server http_server.go
---> Running in 27a5afb6b775
Removing intermediate container 27a5afb6b775
---> 8e0771380586
Step 10/14 : FROM scratch
--->
Step 11/14 : COPY --from=build /data/server /data/server
---> 76dc69f34774
Step 12/14 : WORKDIR /data/
---> Running in 8550a1a7b8ee
Removing intermediate container 8550a1a7b8ee
---> 269d3ee7bb29
Step 13/14 : EXPOSE 9090
---> Running in 2a3f21f67f90
Removing intermediate container 2a3f21f67f90
---> 79640d9e743a
Step 14/14 : CMD ["/data/server"]
---> Running in 39581ed1d208
Removing intermediate container 39581ed1d208
---> e30b2238a606
Successfully built e30b2238a606
Successfully tagged hello:latest
Existing container found: 8b31d39f149117566da56be2796418089c47509018857427559600f1ba7c7982, removing...
Creating container...
Container Id: 20d38a265fe3496b5a4b6c3742740c6c517b7d449250ab0be246688973212079
Container name: '/vibrant_hodgkin'
Attaching to container '/vibrant_hodgkin'...
Starting container '/vibrant_hodgkin'
'<unknown> Dockerfile: Dockerfile' has been deployed successfully.
然而查看容器的日志输出:
standard_init_linux.go:211: exec user process caused "no such file or directory"
???
是个人二进制程序没有编译成功吗?其实不是。从构建日志中,咱们能够清楚的看到,程序实际上是编译成功的,也成功的COPY到了最终的运行镜像中,而后启动的时候就是出错了。因此首先咱们就排除了代码和Dockerfile的问题。
此处我曾经疑惑了好久,由于容器运行报上述错误是让人很是摸不着头脑的。我尝试用搜索引擎进行搜索,确实搜到告终果:
docker启动报错:standard_init_linux.go:211: exec user process caused "no such file or directory"
如题所示,根据本身构建的镜像启动docker容器,直接退出,查看容器日志报错信息,没有任何别的信息。网上搜索这个问题,发现不少人都遇到过,解决办法也各不相同,最后发现一篇文章。受到启发,个人项目是java项目,经过ENTRYPOINT命令启动脚本docker-entrypoint.sh来构建一个在后台运行的服务。而个人docker-entrypoint.sh是在windows下编辑的,天然fileformat是dos,这里须要修改成unix,修改办法也很简答,无需再在linux下操做,咱们通常机器上安装了git工具,自带了git bash命令行工具,进入git bash,找到该文件docker-entrypoint.sh,而后使用vi编辑,修改fileformat=unix,以下所示。
————————————————
原文连接:https://blog.csdn.net/feinifi/java/article/details/102910715
然而这个问题却与咱们无关,由于咱们根本没有用到entrypoint的功能。
如今咱们回到本节开始的地方:scratch是一个空镜像,意味这Docker内部不存在任何环境和依赖库。这就意味着即便是最多见的依赖,在scratch中也是不存在的,那么咱们检查一下helloworld和httpserver两个二进制文件的依赖,看看是否是能看出一些端倪。
$ go build http_server.go
$ ldd http_server
linux-vdso.so.1 (0x00007fffc4eaf000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fecea090000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fece9c90000)
/lib64/ld-linux-x86-64.so.2 (0x00007fecea400000)
真相大白,即便golang宣传了二进制文件不须要依赖任何外部文件,可是即便是程序运行最基础的libc,scratch也是不包含的。这直接致使了编译完成的httpserver没法运行,可是容器的报错倒是找不到文件,报错让人摸不着头脑,但愿这篇文章能提供一些小小的帮助。
理论上来讲,若是在scratch中添加须要的动态库,最终是可让程序正常运行的,但这违背了简化开发流程的原则,同时会在代码中增长没必要要的负担。所以,常见的golang程序使用alpine做为最终的运行环境的基础镜像已是一个很是折衷和合适的方案,不建议再去scratch上折腾。