diff --git a/image/demo/multistage-builds/.gitignore b/image/demo/multistage-builds/.gitignore new file mode 100644 index 0000000..b80f0bd --- /dev/null +++ b/image/demo/multistage-builds/.gitignore @@ -0,0 +1 @@ +app diff --git a/image/demo/multistage-builds/Dockerfile b/image/demo/multistage-builds/Dockerfile new file mode 100644 index 0000000..5516262 --- /dev/null +++ b/image/demo/multistage-builds/Dockerfile @@ -0,0 +1,12 @@ +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"] diff --git a/image/demo/multistage-builds/Dockerfile.build b/image/demo/multistage-builds/Dockerfile.build new file mode 100644 index 0000000..60e97ba --- /dev/null +++ b/image/demo/multistage-builds/Dockerfile.build @@ -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 . diff --git a/image/demo/multistage-builds/Dockerfile.copy b/image/demo/multistage-builds/Dockerfile.copy new file mode 100644 index 0000000..53f65df --- /dev/null +++ b/image/demo/multistage-builds/Dockerfile.copy @@ -0,0 +1,9 @@ +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +COPY app . + +CMD ["./app"] diff --git a/image/demo/multistage-builds/Dockerfile.one b/image/demo/multistage-builds/Dockerfile.one new file mode 100644 index 0000000..79694e3 --- /dev/null +++ b/image/demo/multistage-builds/Dockerfile.one @@ -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"] diff --git a/image/demo/multistage-builds/app.go b/image/demo/multistage-builds/app.go new file mode 100644 index 0000000..0141105 --- /dev/null +++ b/image/demo/multistage-builds/app.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main(){ + fmt.Printf("Hello World!"); +} diff --git a/image/demo/multistage-builds/build.sh b/image/demo/multistage-builds/build.sh new file mode 100755 index 0000000..596a1e7 --- /dev/null +++ b/image/demo/multistage-builds/build.sh @@ -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 diff --git a/image/multistage-builds.md b/image/multistage-builds.md index 4d474e1..046b6ad 100644 --- a/image/multistage-builds.md +++ b/image/multistage-builds.md @@ -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 +``` + +很明显使用多阶段构建的镜像体积小,同时也完美解决了上边提到的问题。