虽然曾有一些文章介绍了如何创建一个最小的 Go Docker 镜像, 我也曾写过一篇文章, 但是随着 Go 的新的版本的发布, 以及 docker 本身的进化, 有些技巧已经发生了变化, 本文介绍了最新的创建超小的 Go 镜像的方法.
一个简单 Go 程序的镜像
首先让我们创建一个很简单的 Go 程序:
- package main
- import "fmt"
- func main() { fmt.Println("hello world")
- }
运行下面的命令会创建一个超小的镜像, 这是我们的 第一种 方式:
GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o app app.go && tar c app | docker import - app:latest
下一节介绍其中的编译参数
查看镜像, 生成的镜像只有 1.21MB :
- # docker images app
- REPOSITORY TAG IMAGE ID CREATED SIZE
- app latest b716e13758cd 11 seconds ago 1.21MB
这命令将编译, 打包, 输入镜像集成到一条命令了.
第二种方式是使用一个 Dockerfile 文件:
- FROM scratch
- ADD app /
- CMD ["/app"]
运行下面的命令创建一个镜像:
docker build -t app2 .
查看生成的镜像, 也是 1.21MB :
- # docker images app2
- REPOSITORY TAG IMAGE ID CREATED SIZE
- app2 latest 4e2af2ffb695 4 seconds ago 1.21MB
第三种方式是利用 Docker 的 multistage 功能, 在镜像中编译, Dockerfile 文件:
- Dockerfile.multistage
- docker build -t app3 -f Dockerfile.multistage .
查看生成的镜像, 也是 ``:
- # docker images app3
- REPOSITORY TAG IMAGE ID CREATED SIZE
- app3 latest 9177859dad64 16 seconds ago 1.21MB
你可以结合你的情况选择一种生成镜像的方式.
编译 Go 程序
上面的例子中我们使用下面的命令编译 Go 程序:
GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -o app app.go
你可能在其它一些文章中还看到 installsuffix 参数:
GOOS=linux CGO_ENABLED=0 go build -ldflags="-s -w" -installsuffix cgo -o app app.go
自 Go 1.10 以后, 你不必再使用 installsuffix 参数 (或许更早的版本),Go 的核心开发人员 Ian Lance Taylor 已经 确认 https://plus.google.com/117192131596509381660/posts/eNnNePihYnK 了这一点.
你可能有人还使用 -a 参数, 它强制重新编译相关的包, 一般你不会使用它.
-s 忽略符号表和调试信息, -w 忽略 DWARF 符号表, 通过这两个参数, 可以进一步减少编译的程序的尺寸, 更多的参数可以参考 go link https://golang.org/cmd/link/ , 或者 go tool link -help (另一个有用的命令是
- go tool compile -help
- ).
你也可以使用 strip 工具对编译的 Go 程序进行裁剪.
本身 Go 是静态编译的, 对于 CGO, 如果设置 CGO_ENABLED=0 , 则完全静态编译, 不会再依赖动态库.
如果设置 CGO_ENABLED=0 , 并且你的代码中使用了标准库的 net 包的话, 有可能编译好的镜像无法运行, 报 sh: /app: not found 的错误, 尽管 /app 这个文件实际存在, 并且如果讲基础镜像换为 centos 或者 ubuntu 的话就能执行. 这是一个奇怪的错误, 原因在于:
默认情况下 net 包会使用静态链接库, 比如 libc
知道了原因, 解决办法也很简单, 就是完全静态链接或者在基础镜像中加入 libc 库.
下面是几种解决办法:
设置 CGO_ENABLED=0
编译是使用纯 go 的 net:
go build -tags netgo -a -v
使用基础镜像加 glibc(或等价库 musl,uclibc), 比如 busybox:glibc https://hub.docker.com/_/busybox/ ,alpine +
- RUN apk add --no-cache libc6-compat
- , https://hub.docker.com/r/frolvlad/alpine-glibc/
基础镜像
其实前面已经列出了一些常用的基础镜像:
scratch: 空的基础镜像, 最小的基础镜像
busybox: 带一些常用的工具, 方便调试, 以及它的一些扩展 busybox:glibc
alpine: 另一个常用的基础镜像, 带包管理功能, 方便下载其它依赖的包
显然. 你应该只在编译阶段使用 Go 的镜像 https://hub.docker.com/_/golang/ , 这样才能将你的镜像减小到最小.
来源: http://www.tuicool.com/articles/QJjqMzZ