哈Ha! 我提请您注意Meetspaceapp Nick Gauthier服务创始人
“为Go应用程序构建最小的Docker容器”的文章翻译。
10分钟阅读有许多官方和社区支持的容器,用于各种编程语言(包括Go)。 但是这些容器可能很大。 让我们首先比较Go应用程序的标准容器构建方法,然后向您展示如何创建非常小的静态容器化Go应用程序
第1部分:我们的“应用程序”
为了进行测试,我们需要一些小的应用程序。 让我们分叉google.com并输出HTML大小。
package main import ( "fmt" "io/ioutil" "net/http" "os" ) func main() { resp, err := http.Get("https://google.com") check(err) body, err := ioutil.ReadAll(resp.Body) check(err) fmt.Println(len(body)) } func check(err error) { if err != nil { fmt.Println(err) os.Exit(1) } }
如果开始,我们只会得到一些数字。 我大约有17,000。 我故意决定使用SSL,但稍后会解释原因。
第2部分:码头化
使用官方的Go镜像,我们编写“ onbuild” Dockerfile:
FROM golang:onbuild
“ Onbuild”图像假定您的项目具有标准结构,并将创建标准Go应用程序。 如果您需要更大的灵活性,则可以使用标准的Go映像并自己进行编译:
FROM golang:latest RUN mkdir /app ADD . /app/ WORKDIR /app RUN go build -o main . CMD ["/app/main"]
在这里创建一个Makefile或用于应用程序构建的类似文件会很好。 我们可以从CDN加载一些资源,或者从另一个项目导入它们,或者我们想在容器中运行测试...
如您所见,Go dockerization非常简单,尤其是当您考虑到我们不使用我们需要连接的服务和端口时。 但是
官方图像存在一个严重缺陷-它们确实很大。 让我们看看:
REPOSITORY SIZE TAG IMAGE ID CREATED VIRTUAL SIZE example-onbuild latest 9dfb1bbac2b8 19 minutes ago 520.7MB example-golang latest 02e19291523e 19 minutes ago 520.7MB golang onbuild 3be7ee2ec1ae 9 days ago 514.9MB golang 1.4.2 121a93c90463 9 days ago 514.9MB golang latest 121a93c90463 9 days ago 514.9MB
基本映像占用514.9 MB,而我们的应用程序又增加了5.8 MB。 我们的已编译应用程序如何需要515MB的依赖关系?
事实是我们的应用程序是在容器内编译的。 这意味着该容器需要安装Go。 因此,他需要Go依赖项,以及程序包管理器和整个操作系统。 实际上,如果您查看用于golang:1.4的Dockerfile,它随Debian Jessie一起提供,安装GCC编译器和构建工具,下载Go并安装。 因此,我们获得了整个Debian服务器和Go工具箱,用于启动我们的小型应用程序。 该怎么办?
第3部分:编译!
您可以通过稍微偏离通常的方法来改善情况。 为此,我们将在工作目录中编译Go,然后将二进制文件添加到容器中。 这意味着简单的docker构建将无法正常工作。 我们需要一个多步骤容器组装:
go build -o main . docker build -t example-scratch -f Dockerfile.scratch .
还有一个简单的Dockerfile.scratch:
FROM scratch ADD main / CMD ["/main"]
什么是刮擦? Scratch是docker中的特殊空白图像。 它的大小是0B:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE example-scratch latest ca1ad50c9256 About a minute ago 5.60MB scratch latest 511136ea3c5a 22 months ago 0B
因此,我们的容器仅占用5.6 MB。 太好了! 但是有一个问题:
$ docker run -it example-scratch no such file or directory
这是什么意思? 我花了一段时间才意识到我们的Go二进制文件正在运行它的操作系统上寻找库。 我们已经编译了我们的应用程序,但是它仍然动态链接到需要启动的库(即,所有C库)。 不幸的是,暂存是空的,因此没有库或加载路径。 我们需要修改构建脚本,以使用所有内置库静态编译我们的应用程序:
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
我们禁用cgo,这会给我们一个静态二进制文件。 我们还指定Linux为操作系统(以防有人在Mac或Windows上构建它)。 -a标志意味着重建我们使用的所有软件包,这将在禁用cgo的情况下重建所有导入。 现在我们有了一个静态二进制文件。 让我们运行:
$ docker run -it example-scratch Get https://google.com: x509: failed to load system roots and no roots provided
这是什么 这就是为什么我决定在示例中使用SSL的原因。 对于这种情况,这是一个非常普遍的“窍门”:要完成SSL请求,我们需要root SSL证书。 那么我们如何将它们添加到我们的容器中呢?
取决于操作系统,证书可能位于不同的位置。 对于许多Linux发行版,这是
/etc/ssl/certs/ca-certificates.crt 。 因此,首先,我们将
ca-certificates.crt从我们的计算机(或Linux虚拟机,或在线证书提供者)复制到我们的存储库。 然后,将
ADD添加到Dockerfile中,以将该文件移动到Go期望的位置:
FROM scratch ADD ca-certificates.crt /etc/ssl/certs/ ADD main / CMD ["/main"]
现在,只需重新创建我们的图像并启动它即可。 有效! 现在让我们看看应用程序的大小:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE example-scratch latest ca1ad50c9256 About a minute ago 6.12MB example-onbuild latest 9dfb1bbac2b8 19 minutes ago 520.7MB example-golang latest 02e19291523e 19 minutes ago 520.7MB golang onbuild 3be7ee2ec1ae 9 days ago 514.9MB golang 1.4.2 121a93c90463 9 days ago 514.9MB golang latest 121a93c90463 9 days ago 514.9MB scratch latest 511136ea3c5a 22 months ago 0B
我们增加了略微超过一半的兆字节(其中大部分来自静态文件,而不是来自根证书)。 我们有一个非常小的容器-在注册表之间移动它会非常方便。
结论
我们的目标是减少Go应用程序的容器大小。 Go的独特之处在于它可以创建一个完全包含应用程序的静态链接二进制文件。 其他语言也可以做到这一点,但绝不是全部。 应用类似的技术来减小其他语言的容器大小将取决于它们的最低要求。 例如,可以在容器外部编译Java或JVM应用程序,然后将其嵌入仅包含JVM(及其依赖项)的容器中。 但是即使这样,它也不会比带有JDK的容器少。