- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Dockerfile中multi-stage(多阶段构建)详解由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
前言 。
docker的口号是build,ship,and run any app,anywhere,在我们使用 docker 的大部分时候,的确能感觉到其优越性,但是往往在我们 build 一个应用的时候,是将我们的源代码也构建进去的,这对于类似于 golang 这样的编译型语言肯定是不行的,因为实际运行的时候我只需要把最终构建的二进制包给你就行,把源码也一起打包在镜像中,需要承担很多风险,即使是脚本语言,在构建的时候也可能需要使用到一些上线的工具,这样无疑也增大了我们的镜像体积.
在应用了容器技术的软件开发过程中,控制容器镜像的大小可是一件费时费力的事情。如果我们构建的镜像既是编译软件的环境,又是软件最终的运行环境,这是很难控制镜像大小的。所以常见的配置模式为:分别为软件的编译环境和运行环境提供不同的容器镜像。比如为编译环境提供一个 dockerfile.build,用它构建的镜像包含了编译软件需要的所有内容,比如代码、sdk、工具等等。同时为软件的运行环境提供另外一个单独的 dockerfile,它从 dockerfile.build 中获得编译好的软件,用它构建的镜像只包含运行软件所必须的内容。这种情况被称为构造者模式(builder pattern),本文将介绍如何通过 dockerfile 中的 multi-stage 来解决构造者模式带来的问题.
常见的容器镜像构建过程 。
比如我们创建了一个 go 语言编写了一个检查页面中超级链接的程序 app.go(请从 (sparkdev )获取本文相关的代码):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
package main
import
(
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"strings"
"golang.org/x/net/html"
)
type
scrapedatastore struct {
internal int `json:
"internal"
`
external int `json:
"external"
`
}
func isinternal(parsedlink *url.url, siteurl *url.url, link string) bool {
return
parsedlink.host == siteurl.host || strings.index(link,
"#"
) == 0 || len(parsedlink.host) == 0
}
func main() {
urlin := os.getenv(
"url"
)
if
len(urlin) == 0 {
urlin =
"https://www.cnblogs.com/"
}
resp, err := http.get(urlin)
scrapedata := &scrapedatastore{}
tokenizer := html.newtokenizer(resp.body)
end :=
false
for
{
tt := tokenizer.next()
switch {
case
tt == html.starttagtoken:
token := tokenizer.token()
switch token.data {
case
"a"
:
for
_, attr := range token.attr {
if
attr.key ==
"href"
{
link := attr.val
parsedlink, parselinkerr := url.parse(link)
if
parselinkerr == nil {
if
isinternal(parsedlink, siteurl, link) {
scrapedata.internal++
}
else
{
scrapedata.external++
}
}
if
parselinkerr != nil {
fmt
.println(
"can't parse: "
+ token.data)
}
}
}
break
}
case
tt == html.errortoken:
end =
true
break
}
if
end {
break
}
}
data, _ := json.marshal(&scrapedata)
fmt
.println(string(data))
}
|
下面我们通过容器来构建它,并把它部署到生产型的容器镜像中.
首先构建编译应用程序的镜像:
1
2
3
4
5
|
from golang:1.7.3
workdir
/go/src/github
.com
/sparkdevo/href-counter/
run go get -d -
v
golang.org
/x/net/html
copy app.go .
run cgo_enabled=0 goos=linux go build -a -installsuffix cgo -o app .
|
把上面的内容保存到 dockerfile.build 文件中.
接着把构建好的应用程序部署到生产环境用的镜像中:
1
2
3
4
5
|
from alpine:latest
run apk --no-cache add ca-certificates
workdir
/root/
copy app .
cmd [
"./app"
]
|
把上面的内容保存到 dockerfile 文件中.
最后需要使用一个脚本把整个构建过程整合起来:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#!/bin/sh
echo
building sparkdevo
/href-counter
:build
# 构建编译应用程序的镜像
docker build --no-cache -t sparkdevo
/href-counter
:build . -f dockerfile.build
# 创建应用程序
docker create --name extract sparkdevo
/href-counter
:build
# 拷贝编译好的应用程序
docker
cp
extract:
/go/src/github
.com
/sparkdevo/href-counter/app
.
/app
docker
rm
-f extract
echo
building sparkdevo
/href-counter
:latest
# 构建运行应用程序的镜像
docker build --no-cache -t sparkdevo
/href-counter
:latest .
|
把上面的内容保存到 build.sh 文件中。这个脚本会先创建出一个容器来构建应用程序,然后再创建最终运行应用程序的镜像.
把 app.go、dockerfile.build、dockerfile 和 build.sh 放在同一个目录下,然后进入这个目录执行 build.sh 脚本进行构建。构建后的容器镜像大小:
从上图中我们可以观察到,用于编译应用程序的容器镜像大小接近 700m,而用于生产环境的容器镜像只有 10.3 m,这样的大小在网络间传输的效率是很高的.
运行下面的命令可以检查我们构建的容器是否可以正常的工作:
1
2
|
$ docker run -e url=https:
//www
.cnblogs.com/ sparkdevo
/href-counter
:latest
$ docker run -e url=http:
//www
.cnblogs.com
/sparkdev/
sparkdevo
/href-counter
:latest
|
ok,我们写的程序正确的统计了博客园首页和笔者的首页中超级链接的情况.
采用上面的构建过程,我们需要维护两个 dockerfile 文件和一个脚本文件 build.sh。能不能简化一些呢? 下面我们看看 docker 针对这种情况提供的解决方案:multi-stage.
在 dockerfile 中使用 multi-stage 。
multi-stage 允许我们在 dockerfile 中完成类似前面 build.sh 脚本中的功能,每个 stage 可以理解为构建一个容器镜像,后面的 stage 可以引用前面 stage 中创建的镜像。所以我们可以使用下面单个的 dockerfile 文件实现前面的需求:
1
2
3
4
5
6
7
8
9
10
11
|
from golang:1.7.3
workdir
/go/src/github
.com
/sparkdevo/href-counter/
run go get -d -
v
golang.org
/x/net/html
copy app.go .
run cgo_enabled=0 goos=linux go build -a -installsuffix cgo -o app .
from alpine:latest
run apk --no-cache add ca-certificates
workdir
/root/
copy --from=0
/go/src/github
.com
/sparkdevo/href-counter/app
.
cmd [
"./app"
]
|
把上面的内容保存到文件 dockerfile.multi 中。这个 dockerfile 文件的特点是同时存在多个 from 指令,每个 from 指令代表一个 stage 的开始部分。我们可以把一个 stage 的产物拷贝到另一个 stage 中。本例中的第一个 stage 完成了应用程序的构建,内容和前面的 dockerfile.build 是一样的。第二个 stage 中的 copy 指令通过 --from=0 引用了第一个 stage ,并把应用程序拷贝到了当前 stage 中。接下来让我们编译新的镜像:
1
|
$ docker build --no-cache -t sparkdevo
/href-counter
:multi . -f dockerfile.multi
|
这次使用 href-counter:multi 镜像运行应用:
1
2
|
$ docker run -e url=https:
//www
.cnblogs.com/ sparkdevo
/href-counter
:multi
$ docker run -e url=http:
//www
.cnblogs.com
/sparkdev/
sparkdevo
/href-counter
:multi
|
结果和之前是一样的。那么新生成的镜像有没有特别之处呢:
好吧,从上图我们可以看到,除了 sparkdevo/href-counter:multi 镜像,还生成了一个匿名的镜像。因此,所谓的 multi-stage 不过时多个 dockerfile 的语法糖罢了。但是这个语法糖还好很诱人的,现在我们维护一个结构简洁的 dockerfile 文件就可以了! 。
使用命名的 stage 。
在上面的例子中我们通过 --from=0 引用了 dockerfile 中第一个 stage,这样的做法会让 dockerfile 变得不容易阅读。其实我们是可以为 stage 命名的,然后就可以通过名称来引用 stage 了。下面是改造后的 dockerfile.mult 文件:
1
2
3
4
5
6
7
8
9
10
11
|
from golang:1.7.3 as builder
workdir
/go/src/github
.com
/sparkdevo/href-counter/
run go get -d -
v
golang.org
/x/net/html
copy app.go .
run cgo_enabled=0 goos=linux go build -a -installsuffix cgo -o app .
from alpine:latest
run apk --no-cache add ca-certificates
workdir
/root/
copy --from=builder
/go/src/github
.com
/sparkdevo/href-counter/app
.
cmd [
"./app"
]
|
我们把第一个 stage 使用 as 语法命名为 builder,然后在后面的 stage 中通过名称 builder 进行引用 --from=builder。通过使用命名的 stage, dockerfile 更容易阅读了.
总结 。
dockerfile 中的 multi-stage 虽然只是些语法糖,但它确实为我们带来了很多便利。尤其是减轻了 dockerfile 维护者的负担(要知道实际生产中的 dockerfile 可不像 demo 中的这么简单)。需要注意的是旧版本的 docker 是不支持 multi-stage 的,只有 17.05 以及之后的版本才开始支持。好了,是不是该去升级你的 docker 版本了?
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我的支持.
参考:
原文链接:http://www.cnblogs.com/sparkdev/p/8508435.html 。
最后此篇关于Dockerfile中multi-stage(多阶段构建)详解的文章就讲到这里了,如果你想了解更多关于Dockerfile中multi-stage(多阶段构建)详解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我将多阶段Dockerfile与阶段Build和Start一起使用,如下所示:。我想知道,在Start阶段,我是否需要再次执行Copy--from=Build<...。换句话说,是不是开始重用文件系统
我将多阶段Dockerfile与阶段Build和Start一起使用,如下所示:。我想知道,在Start阶段,我是否需要再次执行Copy--from=Build<...。换句话说,是不是开始重用文件系统
我的作业配置如下,我正在尝试对我的 hadoop 作业进行简单的两步链接, public int run(String[] args) throws Exception { Confi
我正在尝试编写一个简单的方法来切换我的窗口是否最小化(即图标化)。我有奇怪的行为。下面是说明问题的可运行代码。 我在 Gnome 3.20.4 和 XFCE 4.12 上得到了相同的结果。我还没有在任
在 ActionScript 3 (as3) 中 调用 stage.width 和 stage.stageWidth 有什么区别 这是我记得我过去很困惑的事情(Adobe 的 api 文档是一种混淆的
我在新的 Java SDK 11 中基于 this 创建了项目示例: EntryPoint.java package com.example; import javafx.stage.Stage; i
两年半前,Adobe宣布FlashPlayer 10将支持色彩校正。不可否认,该实现实际上是最基本的,因为它将始终假定所有内容均为sRGB编码,并将该内容转换为系统上正在使用的当前显示配置文件。 Th
在as3中调用stage.width和stage.stageWidth有什么区别 我在某处读到,如果舞台上什么都没有,那么 stage.width 的值为 0,但是当我在舞台上什么都没有但在舞台上动态
当我暂存至少一个跟踪文件时,以下 block 起作用。但是当我只暂存未跟踪的文件时,repo.RetrieveStatus().Staged.Count 等于零(我希望它会随着暂存文件的数量增加),因
作为两者 node步和stage步骤提供范围{}语法,在 groovy 代码中定义拓扑的最佳实践是什么? 附件A node ("NodeName") { stage ("a stage ins
我尝试关注 these fairly simple instructions for integrating Static Application Security Testing (SAST)进入我
我尝试关注 these fairly simple instructions for integrating Static Application Security Testing (SAST)进入我
我有一个主舞台,想创建多个额外的舞台 (Windows)。这些就像 Photoshop 中控制主舞台的调色板,但我想要具有标题栏、调整大小和能够将它们拖动到多个监视器上的任何位置的功能(Popup 类
我正在尝试将下面代码中的字符串显示到其他类中的 Pane 。 public WebEngine helloWebEngine(Stage stage) { WebView wv = getWe
package { import flash.display.Stage; public class MyGlobal { public static var CX:Number =
根据我的标题,我的 jenkins 设置收到以下错误: Unknown stage section "stage". Starting with version 0.5, steps in a sta
当您在 ASP.NET Core 站点上的 Visual Studio 中单击“添加 Docker 支持”时,这是默认的多阶段 Dockerfile。 FROM microsoft/aspnetcor
我的问题是:babel-preset-stage-0 之间有什么区别? , babel-preset-stage-1 , babel-preset-stage-2和 babel-preset-stag
我最近对有关“新式”JavaScript 的文章中的以下术语感到困惑: ES6 ES7(有时,尽管很少,ES8 和更大版本) ES2015(有时是 ES2016 及更高版本) 第 0 阶段(和第 1
2014-04-04 16:02:31.633 java[44631:1903] Unable to load realm info from SCDynamicStore 14/04/04 16:0
我是一名优秀的程序员,十分优秀!