68

如何使用多阶段构建来为你的Go应用程序创建轻量级docker镜像

 5 years ago
source link: http://dockone.io/article/8196?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

恭喜!你已经创建了一个出色的go应用程序,现在你想创建一个docker容器来分发你的应用。

但是,如何尽可能为你的Golang应用程序打造一个最小的镜像呢?提示:我们将会使用多阶段构建(自从Docker 17.05版本提供的 方法)

介绍

多什么?

简单来讲,多阶段。

多阶段允许在创建Dockerfile时使用多个from,并且这种方法是十分有效的,因为它允许我们使用所有必需的工作来构建我们的应用。举个例子,首先问使用golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。

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

1. build 编译阶段

2. certs(可选,可有可无)证书认证阶段

3. prod 生产阶段

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

BvqyaeJ.png!web

项目发布的多个build阶段

示例工程

谈到如何去做的时候,我们将会使用已经创建好的简单的工程。它只是一个运行在8080端口的HTTP 服务,并且返回结果为传递过去的URL的内容结果。

举例

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

你也可以在 这里 找到 代码仓库

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

如果你想跟着本教程来做,只需要拉下master上的代码并且跟着我来创建Dockerfile

步骤1 - 编译阶段

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

下面是我们最原始的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"]

第4行:使用的基础镜像(golang:1.10)并且我们使用as给当前阶段一个别名,也可以使用阶段索引来引用前一阶段,但这使得它更清晰。

第7行:我们将工作目录设置为golang基础镜像的默认$GOPATH中的应用程序目录

第10行:添加我们的应用程序源文件

第13行:编译二进制文件。使用不同的参数来创建一个完整的静态库,因为在生产环境拉取镜像时可能不一定需要需要所有的Golang VM以及C语言库

第16行:使用设定的命令来启动应用程序

现在我们进行编译并使用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:8080

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

QJV7ZnB.png!web

docker hub 占用309M

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

步骤2 - 生产阶段

上面的提供的镜像时完全可以进行部署使用的,但是它真的是太大了。每次在Kubernates上启动你的容器时需要拉取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

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

7jqaU3j.png!web

使用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 中的依赖(并不是测试环境依赖)。它只将编译和缩小的代码复制到镜像中,然后将该镜像推送并部署到生产中


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK