您好,登錄后才能下訂單哦!
怎么看待Dockerfile最佳實(shí)踐,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。
雖然 Dockerfile 簡(jiǎn)化了鏡像構(gòu)建的過(guò)程,并且把這個(gè)過(guò)程可以進(jìn)行版本控制,但是不正當(dāng)?shù)?Dockerfile 使用也會(huì)導(dǎo)致很多問(wèn)題:
docker 鏡像太大。如果你經(jīng)常使用鏡像或者構(gòu)建鏡像,一定會(huì)遇到那種很大的鏡像,甚至有些能達(dá)到 數(shù)G
docker 鏡像的構(gòu)建時(shí)間過(guò)長(zhǎng)。每個(gè) build 都會(huì)耗費(fèi)很長(zhǎng)時(shí)間,對(duì)于需要經(jīng)常構(gòu)建鏡像(比如單元測(cè)試)的地方這可能是個(gè)大問(wèn)題
重復(fù)勞動(dòng)。多次鏡像構(gòu)建之間大部分內(nèi)容都是完全一樣而且重復(fù)的,但是每次都要做一遍,浪費(fèi)時(shí)間和資源
容器模型是進(jìn)程而不是機(jī)器,不需要開(kāi)機(jī)初始化。在需要時(shí)運(yùn)行,不需要時(shí)停止,能夠刪除后重建,并且配置和啟動(dòng)的最小化。
在 docker build 的時(shí)候,忽略部分無(wú)用的文件和目錄可以提高構(gòu)建的速度。比如.git目錄。dockerignore的定義類似gitignore。具體使用方式可以參考鏈接。
為了減少鏡像的復(fù)雜性、鏡像大小和構(gòu)建時(shí)間,應(yīng)該避免安裝無(wú)用的包。
一個(gè)容器只運(yùn)行一個(gè)進(jìn)程。容器起到了隔離應(yīng)用隔離數(shù)據(jù)的作用,不同的應(yīng)用運(yùn)行在不同的容器讓集群的縱向擴(kuò)展以及容器的復(fù)用都變的更加簡(jiǎn)單。需要多個(gè)應(yīng)用交互時(shí)請(qǐng)使用 link 命令進(jìn)行組合或者使用docker-compose。
需要掌握好Dockerfile的可讀性和鏡像層數(shù)之間的平衡。不推薦使用過(guò)多的鏡像層。
命令行按字母順序排序有助于避免重復(fù)執(zhí)行和提高Dockerfile可讀性。apt-get update 應(yīng)與 apt-get install 組合,換行使用反斜杠(\)。例如:
RUN apt-get update && apt-get install -y \ bzr \ cvs \ git \ mercurial \ subversion
Dockerfile的每條指令都會(huì)將結(jié)果提交為新的鏡像。下一條指令基于上一條指令的鏡像進(jìn)行構(gòu)建。如果一個(gè)鏡像擁有相同的父鏡像和指令(除了 ADD ),Docker將會(huì)使用鏡像而不是執(zhí)行該指令,即緩存。
因此,為了有效的利用緩存,盡量保持Dockerfile一致,并且將不變的放在前面而經(jīng)常改變放在末尾。
如不希望使用緩存,在執(zhí)行 docker build 的時(shí)候加上參數(shù) --no-cache=true 。
Docker匹配鏡像決定是否使用緩存的規(guī)則如下:
從緩存中存在的基礎(chǔ)鏡像開(kāi)始,比較所有子鏡像,檢查它們構(gòu)建的指令是否和當(dāng)前的是否完全一致。如果不一致則緩存不匹配。
多數(shù)情況中,比較Dockerfile中的指令是足夠的。然而,特定的指令需要做更多的判斷。
ADD COPY 指令中,將要添加到鏡像中的文件也要被檢查。通常是檢查文件的校驗(yàn)和(checksum)。
緩存匹配檢查并不檢查容器中的文件。例如,當(dāng)使用 RUN apt-get -y update 命令更新了容器中的文件,并不會(huì)被緩存檢查策略作為緩存匹配的依據(jù)。
官方Image 使用的時(shí)區(qū)基本上都是標(biāo)準(zhǔn)的 UTC 時(shí)間,如果容器想使用中國(guó)標(biāo)準(zhǔn)時(shí)間,基于Debian的系統(tǒng)在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime RUN echo "Asia/Shanghai" >> /etc/timezone
基于Centos的系統(tǒng)在Dockerfile中加入
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
有時(shí)候你可能感覺(jué)官方的源更新或者安裝軟件比較慢,可以在Dockerfile修改官方默認(rèn)源,例如alpine想使用阿里的源可以在Dockerfile中加入:
RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories
推薦使用官方倉(cāng)庫(kù)中的鏡像作為基礎(chǔ)鏡像。
把復(fù)雜的或過(guò)長(zhǎng)的 RUN 語(yǔ)句寫(xiě)成以 \ 結(jié)尾的多行的形式,以提高可讀性和可維護(hù)性。
apt-get update 和 apt-get install 一起執(zhí)行,否則 apt-get install 會(huì)出現(xiàn)異常。
避免運(yùn)行 apt-get upgrade 或 dist-upgrade ,在無(wú)特權(quán)的容器中,很多 必要 的包不能正常升級(jí)。如果基礎(chǔ)鏡像過(guò)時(shí)了,應(yīng)當(dāng)聯(lián)系維護(hù)者。 推薦 apt-get update && apt-get install -y package-a package-b 這種方式,先更新,之后安裝最新的軟件包。
RUN apt-get update && apt-get install -y \ aufs-tools \ automake \ build-essential \ curl \ dpkg-sig \ libcap-dev \ libsqlite3-dev \ mercurial \ reprepro \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.* \ && rm -rf /var/lib/apt/lists/*
此外,你可以通過(guò)移除/var/lib/apt/lists減少鏡像大小。
注意:官方的Ubuntu和Debian會(huì)自動(dòng)運(yùn)行apt-get clean,所以不需要顯式的調(diào)用
推薦使用 CMD ["executable","param1","param2"] 這樣的格式。如果鏡像是用來(lái)運(yùn)行服務(wù),需要使用 CMD["apache2","-DFOREGROUND"] ,這種格式的指令適用于任何服務(wù)性質(zhì)的鏡像。
ENTRYPOINT 應(yīng)該用于 鏡像的主命令,并使用 CMD 作為默認(rèn)設(shè)置,以 s3cmd 為例:
ENTRYPOINT ["s3cmd"] CMD ["--help"]
獲取幫助:
docker run s3cmd
或者執(zhí)行命令:
docker run s3cmd ls s3://mybucket
這在鏡像名與程序重名時(shí)非常有用。
ENTRYPOINT 也可以啟動(dòng)自定義腳本: 定義腳本:
#!/bin/bash set -e if [ "$1" = 'postgres' ]; then chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then gosu postgres initdb fi exec gosu postgres "$@" fi exec "$@"
注意:這段腳本使用了exec命令以確保最終應(yīng)用程序在容器內(nèi)啟動(dòng)的PID為1。這段腳本允許容器接收Unix signals。
COPY ./docker-entrypoint.sh / ENTRYPOINT ["/docker-entrypoint.sh"]
這段腳本為用戶提供了多種和 Postgres 交互的途徑:
你可以簡(jiǎn)單地啟動(dòng) Postgres:
docker run postgres。
或者運(yùn)行 postgres 并傳入?yún)?shù):
docker run postgres postgres --help。
你甚至可以從鏡像中啟動(dòng)一個(gè)完全不同的程序,比如 Bash:
docker run --rm -it postgres bash
應(yīng)該盡可能地使用默認(rèn)端口。例如Apache web服務(wù)使用EXPOSE 80,MongoDB使用EXPOSE 27017。
可以使用ENV更新PATH環(huán)境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH可以確保CMD [“nginx”]正常運(yùn)行。
ENV還可以提供程序所需要的環(huán)境變量,例如Postgres的 PGDATA。
ENV可以設(shè)置版本等信息。使版本信息更易于維護(hù)。
ENV PG_MAJOR 9.3 ENV PG_VERSION 9.3.4 RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && … ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
雖然 ADD 與 COPY 功能類似,但推薦使用 COPY 。 COPY 只支持基本的文件拷貝功能,更加的可控。而 ADD 具有更多特定,比如tar文件自動(dòng)提取,支持URL。 通常需要提取tarball中的文件到容器的時(shí)候才會(huì)用到 ADD 。
如果在Dockerfile中使用多個(gè)文件,每個(gè)文件應(yīng)使用單獨(dú)的 COPY 指令。這樣,只有出現(xiàn)文件變化的指令才會(huì)不使用緩存。
為了控制鏡像的大小,不建議使用 ADD 指令獲取URL文件。正確的做法是在 RUN 指令中使用 wget 或 curl 來(lái)獲取文件,并且在文件不需要的時(shí)候刪除文件。
RUN mkdir -p /usr/src/things \ && curl -SL http://example.com/big.tar.gz \ | tar -xJC /usr/src/things \ && make -C /usr/src/things all
VOLUME 通常用作數(shù)據(jù)卷,對(duì)于任何可變的文件,包括數(shù)據(jù)庫(kù)文件、代碼庫(kù)、或者容器所創(chuàng)建的文件/目錄等都應(yīng)該使用 VOLUME 掛載。
如果服務(wù)不需要特權(quán)來(lái)運(yùn)行,使用 USER 指令切換到非root用戶。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **之后用 USER mysql 切換用戶
要避免使用 sudo 來(lái)提升權(quán)限,因?yàn)樗鼛?lái)的問(wèn)題遠(yuǎn)比它能解決的問(wèn)題要多。如果你確實(shí)需要這樣的特性,那么可以選擇使用 gosu 。
最后,不要反復(fù)的切換用戶。減少不必要的layers。
為了清晰和可維護(hù)性,應(yīng)該使用WORKDIR來(lái)定義工作路徑。推薦使用WORKDIR來(lái)代替RUN cd … && do-something 這樣的指令。
Dockerfile 中的其它指令都是為了定制當(dāng)前鏡像而準(zhǔn)備的,唯有 ONBUILD 是為了幫助別人定制自己而準(zhǔn)備的。
ONBUILD指令用來(lái)設(shè)置一些觸發(fā)的指令,用于在當(dāng)該鏡像被作為基礎(chǔ)鏡像來(lái)創(chuàng)建其他鏡像時(shí)(也就是Dockerfile中的FROM為當(dāng)前鏡像時(shí))執(zhí)行一些操作,ONBUILD中定義的指令會(huì)在用于生成其他鏡像的Dockerfile文件的FROM指令之后被執(zhí)行,上述介紹的任何一個(gè)指令都可以用于ONBUILD指令,可以用來(lái)執(zhí)行一些因?yàn)榄h(huán)境而變化的操作,使鏡像更加通用。
注意:
ONBUILD中定義的指令在當(dāng)前鏡像的build中不會(huì)被執(zhí)行。
可以通過(guò)查看docker inspect <image>命令執(zhí)行結(jié)果的OnBuild鍵來(lái)查看某個(gè)鏡像ONBUILD指令定義的內(nèi)容。
ONBUILD中定義的指令會(huì)當(dāng)做引用該鏡像的Dockerfile文件的FROM指令的一部分來(lái)執(zhí)行,執(zhí)行順序會(huì)按ONBUILD定義的先后順序執(zhí)行,如果ONBUILD中定義的任何一個(gè)指令運(yùn)行失敗,則會(huì)使FROM指令中斷并導(dǎo)致整個(gè)build失敗,當(dāng)所有的ONBUILD中定義的指令成功完成后,會(huì)按正常順序繼續(xù)執(zhí)行build。
ONBUILD中定義的指令不會(huì)繼承到當(dāng)前引用的鏡像中,也就是當(dāng)引用ONBUILD的鏡像創(chuàng)建完成后將會(huì)清除所有引用的ONBUILD指令。
ONBUILD指令不允許嵌套,例如ONBUILD ONBUILD ADD . /data是不允許的。
ONBUILD指令不會(huì)執(zhí)行其定義的FROM或MAINTAINER指令。
例如,Dockerfile使用如下的內(nèi)容創(chuàng)建了鏡像 image-A :
[...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基于 image-A 創(chuàng)建新的鏡像時(shí),新的Dockerfile中使用FROM image-A指定基礎(chǔ)鏡像時(shí),會(huì)自動(dòng)執(zhí)行ONBUILD指令內(nèi)容,等價(jià)于在后面添加了兩條指令。
FROM image-A #Automatically run the following ADD . /app/src RUN /usr/local/bin/python-build --dir /app/src
假設(shè)我們要制作 Node.js 所寫(xiě)的應(yīng)用的鏡像。我們都知道 Node.js 使用 npm 進(jìn)行包管理,所有依賴、配置、啟動(dòng)信息等會(huì)放到 package.json 文件里。在拿到程序代碼后,需要先進(jìn)行 npm install 才可以獲得所有需要的依賴。然后就可以通過(guò) npm start 來(lái)啟動(dòng)應(yīng)用。因此,一般來(lái)說(shuō)會(huì)這樣寫(xiě) Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/ CMD [ "npm", "start" ]
把這個(gè) Dockerfile 放到 Node.js 項(xiàng)目的根目錄,構(gòu)建好鏡像后,就可以直接拿來(lái)啟動(dòng)容器運(yùn)行。但是如果我們還有第二個(gè) Node.js 項(xiàng)目也差不多呢?好吧,那就再把這個(gè) Dockerfile 復(fù)制到第二個(gè)項(xiàng)目里。那如果有第三個(gè)項(xiàng)目呢?再?gòu)?fù)制么?文件的副本越多,版本控制就越困難,讓我們繼續(xù)看這樣的場(chǎng)景維護(hù)的問(wèn)題。
如果第一個(gè) Node.js 項(xiàng)目在開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)這個(gè) Dockerfile 里存在問(wèn)題,比如敲錯(cuò)字了、或者需要安裝額外的包,然后開(kāi)發(fā)人員修復(fù)了這個(gè) Dockerfile,再次構(gòu)建,問(wèn)題解決。第一個(gè)項(xiàng)目沒(méi)問(wèn)題了,但是第二個(gè)項(xiàng)目呢?雖然最初 Dockerfile 是復(fù)制、粘貼自第一個(gè)項(xiàng)目的,但是并不會(huì)因?yàn)榈谝粋€(gè)項(xiàng)目修復(fù)了他們的 Dockerfile,而第二個(gè)項(xiàng)目的 Dockerfile 就會(huì)被自動(dòng)修復(fù)。
那么我們可不可以做一個(gè)基礎(chǔ)鏡像,然后各個(gè)項(xiàng)目使用這個(gè)基礎(chǔ)鏡像呢?這樣基礎(chǔ)鏡像更新,各個(gè)項(xiàng)目不用同步 Dockerfile 的變化,重新構(gòu)建后就繼承了基礎(chǔ)鏡像的更新?好吧,可以,讓我們看看這樣的結(jié)果。那么上面的這個(gè) Dockerfile 就會(huì)變?yōu)椋?/p>
FROM node:slim RUN mkdir /app WORKDIR /app CMD [ "npm", "start" ]
這里我們把項(xiàng)目相關(guān)的構(gòu)建指令拿出來(lái),放到子項(xiàng)目里去。假設(shè)這個(gè)基礎(chǔ)鏡像的名字為 my-node 的話,各個(gè)項(xiàng)目?jī)?nèi)的自己的 Dockerfile 就變?yōu)椋?/p>
FROM my-node COPY ./package.json /app RUN [ "npm", "install" ] COPY . /app/
基礎(chǔ)鏡像變化后,各個(gè)項(xiàng)目都用這個(gè) Dockerfile 重新構(gòu)建鏡像,會(huì)繼承基礎(chǔ)鏡像的更新。
那么,問(wèn)題解決了么?沒(méi)有。準(zhǔn)確說(shuō),只解決了一半。如果這個(gè) Dockerfile 里面有些東西需要調(diào)整呢?比如 npm install 需要統(tǒng)一加一些參數(shù),那怎么辦?這一行 RUN 是不可能放入基礎(chǔ)鏡像的,因?yàn)樯婕暗搅水?dāng)前項(xiàng)目的 ./package.json,難道又要一個(gè)個(gè)修改么?所以說(shuō),這樣制作基礎(chǔ)鏡像,只解決了原來(lái)的 Dockerfile 的前4條指令的變化問(wèn)題,而后面三條指令的變化則完全沒(méi)辦法處理。
ONBUILD 可以解決這個(gè)問(wèn)題。讓我們用 ONBUILD 重新寫(xiě)一下基礎(chǔ)鏡像的 Dockerfile:
FROM node:slim RUN mkdir /app WORKDIR /app ONBUILD COPY ./package.json /app ONBUILD RUN [ "npm", "install" ] ONBUILD COPY . /app/ CMD [ "npm", "start" ]
這次我們回到原始的 Dockerfile,但是這次將項(xiàng)目相關(guān)的指令加上 ONBUILD,這樣在構(gòu)建基礎(chǔ)鏡像的時(shí)候,這三行并不會(huì)被執(zhí)行。然后各個(gè)項(xiàng)目的 Dockerfile 就變成了簡(jiǎn)單地:
FROM my-node
是的,只有這么一行。當(dāng)在各個(gè)項(xiàng)目目錄中,用這個(gè)只有一行的 Dockerfile 構(gòu)建鏡像時(shí),之前基礎(chǔ)鏡像的那三行 ONBUILD 就會(huì)開(kāi)始執(zhí)行,成功的將當(dāng)前項(xiàng)目的代碼復(fù)制進(jìn)鏡像、并且針對(duì)本項(xiàng)目執(zhí)行 npm install,生成應(yīng)用鏡像。
類似Java,Go等編譯型項(xiàng)目,可以使用ONBUILD指令進(jìn)行優(yōu)化Dockerfile。
編寫(xiě)onbuild Dockerfile如下:
FROM maven:3-jdk-8 RUN mkdir -p /usr/src/app WORKDIR /usr/src/app ONBUILD ADD . /usr/src/app ONBUILD RUN mvn install
然后所有依賴maven編譯的項(xiàng)目Dockerfile可以簡(jiǎn)化成如下形式:
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
關(guān)于怎么看待Dockerfile最佳實(shí)踐問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。