程序員如何編寫優(yōu)雅的Dockerfile
Kubernetes要從容器化開始,而容器又需要從Dockerfile開始,本文將介紹如何寫出一個(gè)優(yōu)雅的Dockerfile文件。
文章主要內(nèi)容包括:
- Docker容器
- Dockerfile
- 使用多階構(gòu)建
感謝公司提供大量機(jī)器資源及時(shí)間讓我們可以實(shí)踐,感謝在此專題上不斷實(shí)踐的部分項(xiàng)目及人員的支持。
一、Docker容器
1. 容器的特點(diǎn)
我們都知道容器就是一個(gè)標(biāo)準(zhǔn)的軟件單元,它有以下特點(diǎn):
- 隨處運(yùn)行:容器可以將代碼與配置文件和相關(guān)依賴庫進(jìn)行打包,從而確保在任何環(huán)境下的運(yùn)行都是一致的。
- 高資源利用率:容器提供進(jìn)程級的隔離,因此可以更加精細(xì)地設(shè)置CPU和內(nèi)存的使用率,進(jìn)而更好地利用服務(wù)器的計(jì)算資源。
- 快速擴(kuò)展:每個(gè)容器都可作為單獨(dú)的進(jìn)程予以運(yùn)行,并且可以共享底層操作系統(tǒng)的系統(tǒng)資源,這樣一來可以加快容器的啟動和停止效率。
2. Docker容器
目前市面上的主流容器引擎有Docker、Rocket/rkt、OpenVZ/Odin等等,而獨(dú)霸一方的容器引擎就是使用最多的Docker容器引擎。
Docker容器是與系統(tǒng)其他部分隔離開的一系列進(jìn)程,運(yùn)行這些進(jìn)程所需的所有文件都由另一個(gè)鏡像提供,從開發(fā)到測試再到生產(chǎn)的整個(gè)過程中,Linux 容器都具有可移植性和一致性。相對于依賴重復(fù)傳統(tǒng)測試環(huán)境的開發(fā)渠道,容器的運(yùn)行速度要快得多,并且支持在多種主流云平臺(PaaS)和本地系統(tǒng)上部署。Docker容器很好地解決了“開發(fā)環(huán)境能正常跑,一上線就各種崩”的尷尬。
Docker容器的特點(diǎn):
- 輕量:容器是進(jìn)程級的資源隔離,而虛擬機(jī)是操作系統(tǒng)級的資源隔離,所以Docker容器相對于虛擬機(jī)來說可以節(jié)省更多的資源開銷,因?yàn)镈ocker容器不再需要GuestOS這一層操作系統(tǒng)了。
- 快速:容器的啟動和創(chuàng)建無需啟動GuestOS,可以實(shí)現(xiàn)秒級甚至毫秒級的啟動。
- 可移植性:Docker容器技術(shù)是將應(yīng)用及所依賴的庫和運(yùn)行時(shí)的環(huán)境技術(shù)改造包成容器鏡像,可以在不同的平臺運(yùn)行。
- 自動化:容器生態(tài)中的容器編排工作(如:Kubernetes)可幫助我們實(shí)現(xiàn)容器的自動化管理。
二、Dockerfile
Dockerfile是用來描述文件的構(gòu)成的文本文檔,其中包含了用戶可以在使用行調(diào)用以組合Image的所有命令,用戶還可以使用Docker build實(shí)現(xiàn)連續(xù)執(zhí)行多個(gè)命令指今行的自動構(gòu)建。
通過編寫Dockerfile生磁鏡像,可以為開發(fā)、測試團(tuán)隊(duì)提供基本一致的環(huán)境,從而提升開發(fā)、測試團(tuán)隊(duì)的效率,不用再為環(huán)境不統(tǒng)一而發(fā)愁,同時(shí)運(yùn)維也能更加方便地管理我們的鏡像。
Dockerfile的語法非常簡單,常用的只有11個(gè):
1. 編寫優(yōu)雅地Dockerfile
編寫優(yōu)雅的Dockerfile主要需要注意以下幾點(diǎn):
- Dockerfile文件不宜過長,層級越多最終制作出來的鏡像也就越大。
- 構(gòu)建出來的鏡像不要包含不需要的內(nèi)容,如日志、安裝臨時(shí)文件等。
- 盡量使用運(yùn)行時(shí)的基礎(chǔ)鏡像,不需要將構(gòu)建時(shí)的過程也放到運(yùn)行時(shí)的Dockerfile里。
只要記住以上三點(diǎn)就能寫出不錯(cuò)的Dockerfile。
為了方便大家了解,我們用兩個(gè)Dockerfile實(shí)例進(jìn)行簡單的對比:
- FROM ubuntu:16.04
- RUN apt-get update
- RUN apt-get install -y apt-utils libjpeg-dev \
- python-pip
- RUN pip install --upgrade pip
- RUN easy_install -U setuptools
- RUN apt-get clean
- FROM ubuntu:16.04
- RUN apt-get update && apt-get install -y apt-utils \
- libjpeg-dev python-pip \
- && pip install --upgrade pip \
- && easy_install -U setuptools \
- && apt-get clean
我們看***個(gè)Dockerfile,乍一看條理清晰,結(jié)構(gòu)合理,似乎還不錯(cuò)。再看第二個(gè)Dockerfile,緊湊,不易閱讀,為什么要這么寫?
- ***個(gè)Dockerfile的好處是:當(dāng)正在執(zhí)行的過程某一層出錯(cuò),對其進(jìn)行修正后再次Build,前面已經(jīng)執(zhí)行完成的層不會再次執(zhí)行。這樣能大大減少下次Build的時(shí)間,而它的問題就是會因?qū)蛹壸兌嗔硕圭R像占用的空間也變大。
- 第二個(gè)Dockerfile把所有的組件全部在一層解決,這樣做能一定程度上減少鏡像的占用空間,但在制作基礎(chǔ)鏡像的時(shí)候若其中某個(gè)組編譯出錯(cuò),修正后再次Build就相當(dāng)于重頭再來了,前面編譯好的組件在一個(gè)層里,得全部都重新編譯一遍,比較消耗時(shí)間。
從下表可以看出兩個(gè)Dockerfile所編譯出來的鏡像大小:
- $ docker images | grep ubuntu
- REPOSITORY TAG IMAGE ID CREATED SIZE
- ubuntu 16.04 9361ce633ff1 1 days ago 422MB
- ubuntu 16.04-1 3f5b979df1a9 1 days ago 412MB
呃…. 好像并沒有特別的效果,但若Dockerfile非常長的話可以考慮減少層次,因?yàn)镈ockerfile***只能有127層。
三、使用多階構(gòu)建
Docker在升級到Docker 17.05之后就能支持多階構(gòu)建了,為了使鏡像更加小巧,我們采用多階構(gòu)建的方式來打包鏡像。在多階構(gòu)建出現(xiàn)之前我們通常使用一個(gè)Dockerfile或多個(gè)Dockerfile來構(gòu)建鏡像。
1. 單文件構(gòu)建
在多階構(gòu)建出來之前使用單個(gè)文件進(jìn)行構(gòu)建,單文件就是將所有的構(gòu)建過程(包括項(xiàng)目的依賴、編譯、測試、打***程)全部包含在一個(gè)Dockerfile中之下:
- FROM golang:1.11.4-alpine3.8 AS build-env
- ENV GO111MODULE=off
- ENV GO15VENDOREXPERIMENT=1
- ENV BUILDPATH=github.com/lattecake/hello
- RUN mkdir -p /go/src/${BUILDPATH}
- COPY ./ /go/src/${BUILDPATH}
- RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v
- CMD [/go/bin/hello]
這種的做法會帶來一些問題:
- Dockerfile文件會特別長,當(dāng)需要的東西越來越多的時(shí)候可維護(hù)性指數(shù)級將會下降;
- 鏡像層次過多,鏡像的體積會逐步增大,部署也會變得越來越慢;
- 代碼存在泄漏風(fēng)險(xiǎn)。
以Golang為例,它運(yùn)行時(shí)不依賴任何環(huán)境,只需要有一個(gè)編譯環(huán)境,那這個(gè)編譯環(huán)境在實(shí)際運(yùn)行時(shí)是沒有任務(wù)作用的,編譯完成后,那些源碼和編譯器已經(jīng)沒有任務(wù)用處了也就沒必要留在鏡像里。
上表可以看到,單文件構(gòu)建最終占用了312MB的空間。
2. 多文件構(gòu)建
在多階構(gòu)建出來之前有沒有好的解決方案呢?有,比如采用多文件構(gòu)建或在構(gòu)建服務(wù)器上安裝編譯器,不過在構(gòu)建服務(wù)器上安裝編譯器這種方法我們就不推薦了,因?yàn)樵跇?gòu)建服務(wù)器上安裝編譯器會導(dǎo)致構(gòu)建服務(wù)器變得非常臃腫,需要適配各個(gè)語言多個(gè)版本、依賴,容易出錯(cuò),維護(hù)成本高。所以我們只介紹多文件構(gòu)建的方式。
多文件構(gòu)建,其實(shí)就是使用多個(gè)Dockerfile,然后通過腳本將它們進(jìn)行組合。假設(shè)有三個(gè)文件分別是:Dockerfile.run、Dockerfile.build、build.sh。
- Dockerfile.run就是運(yùn)行時(shí)程序所必須需要的一些組件的Dockerfile,它包含了最精簡的庫;
- Dockerfile.build只是用來構(gòu)建,構(gòu)建完就沒用了;
- build.sh的功能就是將Dockerfile.run和Dockerfile.build進(jìn)行組成,把Dockerfile.build構(gòu)建好的東西拿出來,然后再執(zhí)行Dockerfile.run,算是一個(gè)調(diào)度的角色。
Dockerfile.build
- FROM golang:1.11.4-alpine3.8 AS build-env
- ENV GO111MODULE=off
- ENV GO15VENDOREXPERIMENT=1
- ENV BUILDPATH=github.com/lattecake/hello
- RUN mkdir -p /go/src/${BUILDPATH}
- COPY ./ /go/src/${BUILDPATH}
- RUN cd /go/src/${BUILDPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install –v
Dockerfile.run
- FROM alpine:latest
- RUN apk –no-cache add ca-certificates
- WORKDIR /root
- ADD hello .
- CMD ["./hello"]
Build.sh
- #!/bin/sh
- docker build -t –rm hello:build . -f Dockerfile.build
- docker create –name extract hello:build
- docker cp extract:/go/bin/hello ./hello
- docker rm -f extract
- docker build –no-cache -t –rm hello:run . -f Dockerfile.run
- rm -rf ./hello
執(zhí)行build.sh完成項(xiàng)目的構(gòu)建。
從上表可以看到,多文件構(gòu)建大大減小了鏡像的占用空間,但它有三個(gè)文件需要管理,維護(hù)成本也更高一些。
3. 多階構(gòu)建
***我們來看看萬眾期待的多階構(gòu)建。
完成多階段構(gòu)建我們只需要在Dockerfile中多次使用FORM聲明,每次FROM指令可以使用不同的基礎(chǔ)鏡像,并且每次FROM指令都會開始新的構(gòu)建,我們可以選擇將一個(gè)階段的構(gòu)建結(jié)果復(fù)制到另一個(gè)階段,在最終的鏡像中只會留下***一次構(gòu)建的結(jié)果,這樣就可以很容易地解決前面提到的問題,并且只需要編寫一個(gè)Dockerfile文件。這里值得注意的是:需要確保Docker的版本在17.05及以上。下面我們來說說具體操作。
在Dockerfile里可以使用as來為某一階段取一個(gè)別名”build-env”:
- FROM golang:1.11.2-alpine3.8 AS build-env
- COPY –from=build-env /go/bin/hello /usr/bin/hello
看一個(gè)簡單的例子:
- FROM golang:1.11.4-alpine3.8 AS build-env
- ENV GO111MODULE=off
- ENV GO15VENDOREXPERIMENT=1
- ENV GITPATH=github.com/lattecake/hello
- RUN mkdir -p /go/src/${GITPATH}
- COPY ./ /go/src/${GITPATH}
- RUN cd /go/src/${GITPATH} && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v
- FROM alpine:latest
- ENV apk –no-cache add ca-certificates
- COPY --from=build-env /go/bin/hello /root/hello
- WORKDIR /root
- CMD ["/root/hello"]
執(zhí)行docker build -t –rm hello3 .后再執(zhí)行docker images ,然后我們來看鏡像的大?。?/p>
多階構(gòu)建給我們帶來很多便利,***的優(yōu)勢是在保證運(yùn)行鏡像足夠小的情況下還減輕了Dockerfile的維護(hù)負(fù)擔(dān),因此我們極力推薦使用多階構(gòu)建來將你的代碼打包成Docker 鏡像。
【本文是51CTO專欄機(jī)構(gòu)宜信技術(shù)學(xué)院的原創(chuàng)文章,微信公眾號“宜信技術(shù)學(xué)院( id: CE_TECH)”】