Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multistage builds #247

Merged
merged 6 commits into from
Nov 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* [HEALTHCHECK 健康检查](image/dockerfile/healthcheck.md)
* [ONBUILD 为他人作嫁衣裳](image/dockerfile/onbuild.md)
* [参考文档](image/dockerfile/references.md)
* [Dockerfile 多阶段构建](image/multistage-builds.md)
* [其它制作镜像的方式](image/other.md)
* [删除本地镜像](image/rmi.md)
* [实现原理](image/internal.md)
Expand Down
8 changes: 4 additions & 4 deletions container/daemon.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 后台(background)运行
## 后台运行

更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 `-d` 参数来实现。

Expand All @@ -7,7 +7,7 @@
如果不使用 `-d` 参数运行容器。

```bash
$ docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
$ docker run ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
Expand All @@ -19,7 +19,7 @@ hello world
如果使用了 `-d` 参数运行容器。

```bash
$ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
$ docker run -d ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
```

Expand All @@ -32,7 +32,7 @@ $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep
```bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77b2dc01fe0f ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
77b2dc01fe0f ubuntu:17.10 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
```

要获取容器的输出信息,可以通过 `docker logs` 命令。
Expand Down
1 change: 1 addition & 0 deletions image/demo/multistage-builds/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
app
21 changes: 21 additions & 0 deletions image/demo/multistage-builds/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM golang:1.9-alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld/

RUN go get -d -v github.com/go-sql-driver/mysql

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/go/helloworld/app .

CMD ["./app"]
10 changes: 10 additions & 0 deletions image/demo/multistage-builds/Dockerfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang:1.9-alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
9 changes: 9 additions & 0 deletions image/demo/multistage-builds/Dockerfile.copy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]
15 changes: 15 additions & 0 deletions image/demo/multistage-builds/Dockerfile.one
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.9-alpine

RUN apk --no-cache add git ca-certificates

WORKDIR /go/src/github.com/go/helloworld/

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root

WORKDIR /root/

CMD ["./app"]
7 changes: 7 additions & 0 deletions image/demo/multistage-builds/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "fmt"

func main(){
fmt.Printf("Hello World!");
}
14 changes: 14 additions & 0 deletions image/demo/multistage-builds/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

echo Building go/helloworld:build

docker build -t go/helloworld:build . -f Dockerfile.build

docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract

echo Building go/helloworld:2

docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app
176 changes: 176 additions & 0 deletions image/multistage-builds.md
Original file line number Diff line number Diff line change
@@ -1 +1,177 @@
## 多阶段构建

### 之前的做法

在 Docker 17.05 版本之前,我们构建 Docker 镜像时,通常会采用两种方式:

#### 全部放入一个 Dockerfile

一种方式是将所有的构建过程编包含在一个 `Dockerfile` 中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:

* `Dockerfile` 特别长,可维护性降低

* 镜像层次多,镜像体积较大,部署时间变长

* 源代码存在泄露的风险

例如

编写 `app.go` 文件,该程序输出 `Hello World!`

```go
package main

import "fmt"

func main(){
fmt.Printf("Hello World!");
}
```

编写 `Dockerfile.one` 文件

```docker
FROM golang:1.9-alpine

RUN apk --no-cache add git ca-certificates

WORKDIR /go/src/github.com/go/helloworld/

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root

WORKDIR /root/

CMD ["./app"]
```

构建镜像

```bash
$ docker build -t go/helloworld:1 -f Dockerfile.one .
```

#### 分散到多个 Dockerfile

另一种方式,就是我们事先在一个 `Dockerfile` 将项目及其依赖库编译测试打包好后,再将其拷贝到运行环境中,这种方式需要我们编写两个 `Dockerfile` 和一些编译脚本才能将其两个阶段自动整合起来,这种方式虽然可以很好地规避第一种方式存在的风险,但明显部署过程较复杂。

例如

编写 `Dockerfile.build` 文件

```docker
FROM golang:1.9-alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld

COPY app.go .

RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
```

编写 `Dockerfile.copy` 文件

```docker
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]
```

新建 `build.sh`

```bash
#!/bin/sh
echo Building go/helloworld:build

docker build -t go/helloworld:build . -f Dockerfile.build

docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract

echo Building go/helloworld:2

docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app
```

现在运行脚本即可构建镜像

```bash
$ chmod +x build.sh

$ ./build.sh
```

对比两种方式生成的镜像大小

```bash
$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
```

### 使用多阶段构建

为解决以上问题,Docker v17.05 开始支持多阶段构建 (`multistage builds`)。使用多阶段构建我们就可以很容易解决前面提到的问题,并且只需要编写一个 `Dockerfile`:

例如

编写 `Dockerfile` 文件

```docker
FROM golang:1.9-alpine

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld/

RUN go get -d -v github.com/go-sql-driver/mysql

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/go/helloworld/app .

CMD ["./app"]
```

构建镜像

```bash
$ docker build -t go/helloworld:3 .
```

对比三个镜像大小

```bash
$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 d6911ed9c846 7 seconds ago 6.47MB
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
```

很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。
Loading