介绍

多什么?

简单来讲,多阶段。

多阶段允许在创建Dockerfile时使用多个from,它非常有用,因为它使我们能够使用所有必需的工具构建应用程序。举个例子,首先我们使用Golang的基础镜像,然后在第二阶段的时候使用构建好的镜像的二进制文件,最后阶段构建出来的镜像用于发布到我们自己的仓库或者是用于上线发布。

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

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

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

项目发布的多个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。

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即可,相比于之前节省了大量的时间和带宽呢。

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

如何为你的Go应用创建轻量级Docker镜像?的更多相关文章

  1. 为你的Go应用创建轻量级Docker镜像?

    缩小Go二进制文件大小 环境 youmen@youmendeMacBook-Pro % gcc -dumpversion 12.0.5 youmen@youmendeMacBook-Pro % go ...

  2. Dockerfile创建自定义Docker镜像以及CMD与ENTRYPOINT指令的比较

    1.概述 创建Docker镜像的方式有三种 docker commit命令:由容器生成镜像: Dockerfile文件+docker build命令: 从本地文件系统导入:OpenVZ的模板. 关于这 ...

  3. 根据Dockerfile创建hello docker镜像

    一.编写hello可执行c文件: 1.安装:gcc glibc glibc-static yum install -y gcc glibc glibc-static 2.编写hello.c:vim h ...

  4. docker学习系列(二):使用Dockerfile创建自己的镜像

    dockerfile可以允许我们自己创建镜像,通过编写里面的下载软件命令,执行docker build 即可生成镜像文件. 初尝dockerfile 新建一个目录test,然后进入这个目录,创建一个名 ...

  5. Docker笔记--镜像&基于GO项目创建Docker镜像

    Docker笔记--镜像&基于GO项目创建Docker镜像 核心概念 Doker镜像--包含一个基本的操作系统运行环境和应用程序,镜像是创建Docker容器的基础. Docker容器--如果把 ...

  6. docker快速创建轻量级的可移植的容器(一)

    系列其他内容 docker快速创建轻量级的可移植的容器✓ docker&flask快速构建服务接口 docker&uwsgi高性能WSGI服务器生产部署必备 docker&gu ...

  7. docker2-镜像原理及创建新的镜像

    1,镜像是什么 镜像是一种轻量级.可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码.运行时.库.环境变量和配置文件 在docker中所有应用 ...

  8. docker学习(5) 在mac中创建mysql docker容器

    github上有一个专门的docker-libary项目,里面有各种各样常用的docker镜像,可以做为学习的示例,今天研究下其中mysql镜像的用法,国内镜像daocloud.io也能找到mysql ...

  9. Docker镜像的管理和创建

    1. Docker镜像和Docker容器:      Docker镜像实际上是一系列的文件系统,通常的Linux系统一般是两层文件系统,bootfs和rootfs,bootfs就是bootloader ...

随机推荐

  1. 关于fragment+viewpager的优化

    上次写了一个问答项目,用的fragment+viewpager架构,后来发现,划了几次之后,再划回来,会重新加载布局,重新获取数据,这样整个程序和卡,并且占用太多的网络资源. 当时的解决办法是,自己重 ...

  2. 类模板成员函数默认值问题:an out-of-line definition of a member of a class template cannot have default arguments

    template <typename T> class A { ); }; template<typename T> ) { /* */ } 对于类似上文代码,VS编译器会报 ...

  3. 10JDBC、CURD、XML、XPath

    10JDBC.CURD.XML.XPath-2018/07/20 1.JDBC JDBC:java database connectivity JDBC与数据库驱动的关系:接口与实现的关系. JDBC ...

  4. 微信小程序开发过程中tabbar页面显示的相关问题及解决办法!

    在微信小程序的开发过程中如果有使用过tabbar的同学,我相信一定会遇到一些困扰.为什么有些时候代码中明明已经在app.json里面增加了tabbar,可以页面中就是不显示呢?可不可以有些页面显示ta ...

  5. 用Python实现阿里钉钉机器人读取数据库内容自动发群通知

    最近想把一些预警数据信息按照一定的要求自动发送到移动端APP,最终把目标放在了腾讯的微信和阿里的钉钉软件上,由于刚开始学习python,于是编程工具想用python来实现.微信使用群体最广,通过一天的 ...

  6. 第十五节:Web爬虫之selenium动态渲染爬取

    selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firef ...

  7. 3.3.5 boolean类型

        boolean(布尔)类型有两个值:false 和 true ,用来判定逻辑条件.与Python不同的是,Java中的boolean值与整型值之间进行互相转换.       Python中Tu ...

  8. VM 与 与 Linux 的安装

    [VMWare  安装] 输入后, [CentOS ] 1  检查 BIOS  虚拟化 2.新建虚拟机 3.新建虚拟机向导 4创建虚拟空盘 5  安装 Linux  系统对应的 CentOS 6  虚 ...

  9. POJ 2065 高斯消元求解问题

    题目大意: f[k] = ∑a[i]*k^i % p 每一个f[k]的值就是字符串上第 k 个元素映射的值,*代表f[k] = 0 , 字母代表f[k] = str[i]-'a'+1 把每一个k^i求 ...

  10. COJ 1156 Switching bulbs

    一道模拟题目 对于所有0 还是 1 我们都可以想象做均为 0 的状态 v[i]表示原来的值 但是对于原来为1的要加上其所在的值作为初始值 然后转化后 a[i] = -v[i]  , 如果原来为0 , ...