本文是《Docker必知必會(huì)系列》第六篇侧但,原文發(fā)布于個(gè)人博客:悟塵紀(jì)。
上一篇:Docker必知必會(huì)系列(五):Docker 數(shù)據(jù)持久化存儲(chǔ)與性能調(diào)優(yōu)
一航罗、引言
如何減小所構(gòu)建鏡像的體積最非常具有挑戰(zhàn)性的事情禀横。Docker 17.05版本以后,新增了Dockerfile多階段構(gòu)建粥血。所謂多階段構(gòu)建柏锄,實(shí)際上是允許一個(gè)Dockerfile 中出現(xiàn)多個(gè) FROM
指令。
二复亏、單 Dockerfile 構(gòu)建鏡像
如果將所有的構(gòu)建過(guò)程都包含在一個(gè) Dockerfile
中趾娃,包括項(xiàng)目及其依賴庫(kù)的編譯、測(cè)試蜓耻、打包等流程茫舶,這樣會(huì)帶來(lái)的一些問(wèn)題:
- 鏡像層次多,鏡像體積較大刹淌,部署時(shí)間變長(zhǎng)
- 源代碼存在泄露的風(fēng)險(xiǎn)
下面是一個(gè)簡(jiǎn)單示例:
FROM golang:1.14-alpine
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/lixl.cn/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/lixl.cn/helloworld/app /root
WORKDIR /root/
CMD ["./app"]
構(gòu)建鏡像:docker build -t go/helloworld:1 -f Dockerfile1 .
三、Builder 模式構(gòu)建
為了解決上面提到的問(wèn)題讥耗,可以采用 Builder 模式:創(chuàng)建兩個(gè) Dockerfile有勾,一個(gè)用于開(kāi)發(fā)(包含構(gòu)建應(yīng)用程序所需的一切),另一個(gè)用于生產(chǎn)(僅包含您的應(yīng)用程序以及運(yùn)行該應(yīng)用程序所需的內(nèi)容)古程,然后用編譯腳本將其整合:
-
Dockerfile.build
文件:FROM golang:1.14-alpine RUN apk --no-cache add git ca-certificates WORKDIR /go/src/github.com/go/lixl.cn/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 .
-
Dockerfile2
文件:FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
-
build.sh
文件:#!/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/lixl.cn/helloworld/app ./app docker rm -f extract echo Building go/helloworld:2 docker build --no-cache -t go/helloworld:2 . -f Dockerfile2 rm ./app
構(gòu)建鏡像:chmod u+x build.sh && sh ./build.sh
這種方式生成的鏡像會(huì)很小蔼卡,不過(guò)過(guò)程比較復(fù)雜,而且生成的多個(gè)鏡像都會(huì)占用系統(tǒng)空間挣磨。
四雇逞、多階段構(gòu)建方式
為了解決這些問(wèn)題,自 Docker v17.05 開(kāi)始支持多階段構(gòu)建茁裙。每一條 FROM
指令都是一個(gè)構(gòu)建階段塘砸,多條 FROM
就是多階段構(gòu)建,雖然最后生成的鏡像只能是最后一個(gè)階段的結(jié)果晤锥,但是掉蔬,能夠?qū)⑶爸秒A段中的文件拷貝到后邊的階段中廊宪,這就是多階段構(gòu)建的最大意義。示例如下:
FROM golang:1.14-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/lixl.cn/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 as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/lixl.cn/helloworld/app .
CMD ["./app"]
第二FROM
條指令以alpine:latest
為基礎(chǔ)開(kāi)始新的構(gòu)建階段女轿。COPY --from=0
行僅將先前階段中構(gòu)建的工件復(fù)制到新階段(第一個(gè)FROM
條指令的起始編號(hào)為 0)箭启,Go SDK 和任何中間工件都不會(huì)保存在最終鏡像中。
接下來(lái)蛉迹,使用 docker build -t go/helloworld:3 .
構(gòu)建鏡像傅寡,然后對(duì)比三種方式生成的鏡像大小。
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 5fb7cd98ef33 2 minutes ago 8.22MB
go/helloworld 2 7c30b66f73f9 2 minutes ago 8.22MB
go/helloworld 1 28fb4443a052 2 hours ago 401MB
可以看出北救,單 Dockerfile 方式構(gòu)建的鏡像非常大赏僧。后兩種方式構(gòu)建的鏡像大小一致,但多階段構(gòu)建大大降低了復(fù)雜性扭倾。
使用多階段構(gòu)建:
可以在
Dockerfile
中使用多個(gè)FROM
語(yǔ)句淀零,每個(gè)FROM
指令可以使用不同的基礎(chǔ)鏡像。每個(gè)
FORM
都會(huì)開(kāi)始新的構(gòu)建階段膛壹,可以有選擇地將工件從一個(gè)階段復(fù)制到另一個(gè)階段驾中。通過(guò)在
FROM
指令中添加AS name
來(lái)可以命名階段,然后使用COPY --from=name
而非數(shù)字來(lái)引用模聋。可以指定目標(biāo)階段來(lái)構(gòu)建鏡像(使用
--target name
指令)肩民,而不是必須構(gòu)建整個(gè) Dockerfile。可以直接引用外部的鏡像链方,如:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
持痰。可以在使用
FROM
指令時(shí)引用前一個(gè)階段,從上一個(gè)階段結(jié)束祟蚀。例如:FROM alpine:latest as builder RUN apk --no-cache add build-base FROM builder as build1 COPY source1.cpp source.cpp RUN g++ -o /binary source.cpp FROM builder as build2 COPY source2.cpp source.cpp RUN g++ -o /binary source.cpp
更詳細(xì)的介紹工窍,可以參考:https://docs.docker.com/develop/develop-images/multistage-build/