如何为你的Go应用建立轻量级Docker镜像?

介绍

多什么?

简单来说,多阶段。

多阶段容许在建立Dockerfile时使用多个from,它很是有用,由于它使咱们可以使用全部必需的工具构建应用程序。举个例子,首先咱们使用Golang的基础镜像,而后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到咱们本身的仓库或者是用于上线发布。

在上述的案例中,咱们总共有三个阶段:html

  • build编译阶段
  • certs(可选,无关紧要)证书认证阶段
  • prod生产阶段


在build阶段主要是编译咱们的应用程序,证书认证阶段将会安装咱们所须要的CA证书,最后的生产发布阶段会将咱们构建好的镜像推到镜像仓库中。并且发布阶段将会使用build阶段编译完毕的二进制文件和certs阶段安装的证书。linux

1.png


项目发布的多个build阶段git

示例工程

对于这个方法,咱们将使用一个很是简单的项目。它只是一个运行在8080端口的HTTP服务,而且返回结果为传递过去的URL的内容结果。github

举例

GET http://localhost:8080?url=https://google.com 返回结果为goole页面内容展现。

你也能够在这里找到代码仓库。

在master分支上只包含了应用程序,final分支上还包含本篇教程中使用的Dockerfile文件

若是你想跟着本教程来作,只须要拉下master上的代码而且跟着我来建立Dockerfile。golang

步骤1 - 编译阶段

第一阶段主要是使用Golang基础镜像来将咱们的应用程序打包为二进制文件。这个基础镜像包含了将咱们的应用程序编译成可执行二进制文件的全部工具。

下面是咱们最原始的Dockerfile:spring

1 #
2 # BUILD 阶段
3 # 
4 FROM golang:1.10 AS build
5
6 # 设置咱们应用程序的工做目录
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加全部须要编译的应用代码
10 ADD . .
11
12 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 设置咱们应用程序的启动命令
16 CMD ["./blog-multistage-go"]

 

  • 第4行:使用的基础镜像(golang:1.10)而且咱们使用as给当前阶段一个别名,也可使用阶段索引来引用前一阶段,但这使得它更清晰。
  • 第7行:咱们将工做目录设置为Golang基础镜像的默认$GOPATH中的应用程序目录。
  • 第10行:添加咱们的应用程序源文件。
  • 第13行:编译二进制文件。使用不一样的参数来建立一个完整的静态库,由于在生产环境拉取镜像时可能不必定须要全部的Golang VM以及C语言库。
  • 第16行:使用设定的命令来启动应用程序。


如今咱们进行编译并使用Docker容器,咱们的应用程序如咱们预期正常运行:docker

docker build -t scboffspring/blog-multistage-go .
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go


咱们可使用curl命令来请求,而且它会返回http://google.com页面内容。

在终端运行curl localhost:8080json

1 <html itemscope="" itemtype="http://schema.org/WebPage" lang="de-CH">
2  <head>
3  <meta content="text/html; charset=UTF-8" 
4      http-equiv="Content-Type">
5  <meta content="/images/branding/googleg/1x/googleg_standard_color_128dp.png" 
6  itemprop="image"><title>Google</title>
7 ....


让咱们使用docker images,来看看镜像的大小:服务器

REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 818MB


荒唐,太荒唐了,一个这么小的应用竟然占了磁盘818M内存空间。

推送到镜像仓库后,镜像大小被压缩到309M。app

2.png


docker hub 占用309M

接下来咱们来改善这种状况,把镜像的大小下降到10M!

步骤2 - 生产阶段

上面提供的镜像是彻底能够进行部署使用的,可是它真的是太大了。每次在Kubernetes上启动你的容器时须要拉取309M的镜像?真的是太浪费时间和带宽。

让咱们来为咱们的镜像构建一个生产阶段,正如上面解释的,这个阶段只是从build阶段拷贝二进制文件到容器中。

咱们新的Dockerfile将会以下所示:

1 #
2 # BUILD 阶段
3 # 
4 FROM golang:1.10 AS build
5
6 # 设置咱们应用程序的工做目录
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加全部须要编译的应用代码
10 ADD . .
11
12 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 设置咱们应用程序的启动命令
16 CMD ["./blog-multistage-go"]
17
18
19
20 #
21 # 生产阶段
22 # 
23 FROM scratch AS prod
24 
25 # 从buil阶段拷贝二进制文件
26 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .
27 CMD ["./blog-multistage-go"]


如你所见,同一个Dockerfile文件中咱们添加了第二个FROM语句。此次,咱们直接拉取二进制文件,不须要添加任何其余依赖。

  • 第23行:拉取基础镜像
  • 第26行:从/go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go拷贝build阶段编译的文件
  • 第27行:使用设定的命令来启动应用程序


简单吧。

让咱们像以前同样编译并使用Docker容器:

docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go


咱们能够看到服务正常启动,也就是意味着它正确的启动了!咱们完成了!

让咱们使用docker images,来看看镜像的大小:

REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 6.65MB


如咱们以前所说,镜像的大小变为10MB如下。并且镜像被推送到镜像仓库后,它只有2MB。当你启动容器时,只需下载2MB便可,相比于以前节省了大量的时间和带宽呢。

3.png


使用prod阶段编译的容器仅2MB

可是,它在咱们的例子中不起做用。 若是运行curl localhost:8080,你看到的返回的结果为500。

curl localhost:8080
500 - Something bad happened


若是你查看容器的日志,你能够找到以下错误:

发生了一个错误:Get  http://google.com:X509:加载系统根目录失败而且没有根目录可使用。

咱们尝试使用https来链接Goole服务器,可是咱们没有用于验证Google的SSL证书的CA(证书颁发机构)证书或是其余网站的CA证书。若是你的应用不须要使用SSL的话,能够选择跳到下一节,不然,让咱们来改善咱们的软件使得其能够进行访问。

阶段3 - (可选)认证阶段

1 #
2 # BUILD 阶段
3 # 
4 FROM golang:1.10 AS build
5
6 # 设置咱们应用程序的工做目录
7 WORKDIR /go/src/github.com/scboffspring/blog-multistage-go
8
9 # 添加全部须要编译的应用代码
10 ADD . .
11
12 # 编译一个静态的go应用(在二进制构建中包含C语言依赖库)
13 RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo .
14
15 # 设置咱们应用程序的启动命令
16 CMD ["./blog-multistage-go"]
17
18
19 # 
20 # CERTS Stage
21 #
22 FROM alpine:latest as certs
23 
24 # Install the CA certificates
25 RUN apk --update add ca-certificates
26 
27 #
28 # PRODUCTION STAGE
29 # 
30 FROM scratch AS prod
31 
32 # 从certs阶段拷贝CA证书
33 COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
34 # 从buil阶段拷贝二进制文件
35 COPY --from=build /go/src/github.com/scboffspring/blog-multistage-go/blog-multistage-go .
36 CMD ["./blog-multistage-go"]

 

  • 第23行:咱们新的certs阶段,使用alpine镜像
  • 第25行:安装最新版的CA证书
  • 第33行:从certs层拷贝证书,并保存为/etc/ssl/certs/ca-certificates.crt


让咱们再次编译并使用Docker容器:

docker build -t scboffspring/blog-multistage-go . 
docker run --rm -ti -p 8080:8080 \
scboffspring/blog-multistage-go


如今,curl localhost:8080将会返回真实的页面!它真的奏效了!

使用docker images查看,镜像依然仍是很是小的:

REPOSITORY                                       ... SIZE
scboffspring/blog-multistage-go                  ... 6.89MB

 

额外福利:在指定的阶段为镜像添加tag

有时候咱们可能会在各个阶段为镜像建立一个tag,在咱们的示例中,咱们可能也会将build阶段产生的结果发布到Docker,由于它对开发真的十分有用。

要想这样作的话,只须要在build镜像的时候简单的使用--target=NAMEOFTHESTAGE

举个例子:

docker build -t scboffspring/blog-multistage-go:build . --target=build

 

总结

如今你已经可以为你的Golang应用程序建立一个很是轻量级的应用程序。阶段构建的概念对其余许多案例也是很是有用的。

我在NodeJS世界中的一个用法是第一阶段编译TypeScript项目。而后第一个阶段编译以便使得该镜像能够运行测试。此镜像也可以用于开发环境,由于它包含了全部开发环境所需的依赖。

当第一阶段测试经过后,第二阶段只是简单的安装项目中的package.json中的依赖(并非测试环境依赖)。它只将编译和缩小的代码复制到镜像中,而后将该镜像推送并部署到生产中。

相关文章
相关标签/搜索