一对嚼、什么是Dockerfile夹抗?
??鏡像的定制實(shí)際上就是定制每一層所添加的配置、文件纵竖。我們可以把每一層修改漠烧、安裝、構(gòu)建靡砌、操作的命令都寫入一個(gè)腳本已脓,這個(gè)腳本就是Dockerfile。
二通殃、使用Dockerfile定制鏡像
??下面以nginx為基礎(chǔ)鏡像度液,演示基于nginx使用Dockerfile來(lái)定制鏡像。
1画舌、創(chuàng)建Dockerfile
??在一個(gè)空白目錄中堕担,建立一個(gè)文本文件,并命名為Dockerfile
mkdir mynginx
cd mynginx
touch Dockerfile
??其內(nèi)容為:
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
??nginx默認(rèn)的訪問(wèn)頁(yè)面路徑為/usr/share/nginx/html/index.html
曲聂,Dockerfile很簡(jiǎn)單霹购,一共就兩行,涉及到了兩條指令朋腋,FROM
和RUN
齐疙。
(1)FROM指定基礎(chǔ)鏡像
??所謂定制鏡像膜楷,一定是以一個(gè)鏡像為基礎(chǔ),在其上進(jìn)行定制贞奋《奶基礎(chǔ)鏡像是必須指定的,而FROM
就是指定基礎(chǔ)鏡像忆矛,因此一個(gè)Dockerfile中FROM
是必備的指令察蹲,并且必須是第一條指令。在Docker Hub上有非常多的高質(zhì)量的官方鏡像催训,有可以直接拿來(lái)使用的服務(wù)類的鏡像洽议,如nginx
、redis
漫拭、mysql
亚兄、tomcat
等;可以在其中尋找一個(gè)最符合我們最終目標(biāo)的鏡像為基礎(chǔ)鏡像進(jìn)行定制采驻。审胚、
??如果沒(méi)有找到對(duì)應(yīng)服務(wù)的鏡像,官方鏡像中還提供了一些更為基礎(chǔ)的操作系統(tǒng)鏡像礼旅,如ubuntu
膳叨、debian
、centos
痘系、alpine
等菲嘴,這些操作系統(tǒng)的軟件庫(kù)為我們提供了更廣闊的擴(kuò)展空間。
??除了選擇現(xiàn)有鏡像為基礎(chǔ)鏡像外汰翠,Docker還存在一個(gè)特殊的鏡像龄坪,名為scratch
,這個(gè)鏡像是虛擬的該概念复唤,并不實(shí)際存在健田,它表示一個(gè)空白的鏡像。
FROM scratch
...
??如果以scratch
為基礎(chǔ)鏡像佛纫,意味著不以任何鏡像為基礎(chǔ)妓局,接下來(lái)所寫的指令將作為鏡像第一層開(kāi)始存在。
??對(duì)于Linux下靜態(tài)編譯的程序來(lái)說(shuō)呈宇,并不需要有操作系統(tǒng)提供運(yùn)行時(shí)支持跟磨,所需的一切庫(kù)都已經(jīng)在可執(zhí)行文件里了,因此直接FROM scratch
會(huì)讓鏡像體積更加小巧攒盈。使用Go語(yǔ)言開(kāi)發(fā)的應(yīng)用很多會(huì)使用這種方式來(lái)制作鏡像抵拘,這也是為什么有人認(rèn)為Go是特別適合容器微服務(wù)架構(gòu)的語(yǔ)言的原因之一。
(2)RUN執(zhí)行命令
??RUN
指令是用來(lái)執(zhí)行命令行命令的型豁,由于命令行的強(qiáng)大能力僵蛛,RUN
指令在定制鏡像時(shí)是最常用的指令之一尚蝌,其格式有兩種:
shell格式:RUN <命令>
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec指令:RUN ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"]、
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
2充尉、構(gòu)建鏡像
docker build -t [鏡像名]:[鏡像版本] [Dockerfile文件所在目錄](méi)
實(shí)例演示:
??從命令的輸出結(jié)果中飘言,可以清晰地看到鏡像的構(gòu)建過(guò)程。在Step 2中驼侠,
RUN
指令啟動(dòng)了一個(gè)容器7b7ca18940f0
姿鸿,執(zhí)行了所要求的命令,并最后提交了這一層的鏡像6f518c774d7e
倒源,隨后刪除了所用到的這個(gè)容器7b7ca18940f0
苛预,查看鏡像列表,可以看到我們剛才定制的鏡像笋熬,鏡像ID為6f518c774d7e
热某。三、Dockerfile指令詳解
1胳螟、COPY復(fù)制文件
格式:
COPY <源路徑>...<目標(biāo)路徑>
COPY ["<源路徑>",..."<目標(biāo)路徑>"]
??COPY
指令將從構(gòu)建上下文目錄中<源路徑>的文件/目錄復(fù)制到新的一層的鏡像內(nèi)的<目標(biāo)路徑>位置昔馋。比如:
COPY package.json /usr/src/app
??<源路徑>可以是多個(gè),甚至可以是通配符糖耸,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
實(shí)例演示:
??來(lái)運(yùn)行以下這個(gè)新創(chuàng)建的鏡像秘遏,命名新創(chuàng)建的容器,綁定端口嘉竟,指定使用的鏡像邦危,如下所示:
??查看新增的容器
??進(jìn)入容器內(nèi)部,查看Dockerfile中的
COPY
指令是否有生效拷貝文件到指定目錄中2周拐、ADD更高級(jí)地復(fù)制文件
??ADD
指令和COPY
的格式和性質(zhì)基本一致铡俐,但是在COPY
基礎(chǔ)上增加了一些功能凰兑。比如<源路徑>可以是一個(gè)URL妥粟,這種情況下,Docker引擎會(huì)試圖去下載這個(gè)鏈接的文件放到<目標(biāo)路徑>去吏够。
??在Docker官方的Dockerfile最佳時(shí)間文檔中要求勾给,盡可能地使用COPY
,因?yàn)?code>COPY的語(yǔ)義很明確锅知,就是復(fù)制文件而已播急,而ADD
則包含了更復(fù)雜的功能,其行為也不一定很清晰售睹。最適合使用ADD
的場(chǎng)合桩警,就是所提及的需要自動(dòng)解壓縮的場(chǎng)合。
??因此在COPY
和ADD
指令中選擇的時(shí)候昌妹,可以遵循這樣的原則捶枢,所有的文件復(fù)制均使用COPY
指令握截,僅在需要自動(dòng)壓縮的場(chǎng)合使用ADD
。
3烂叔、CMD容器啟動(dòng)命令
??CMD
指令的格式和RUN
相似谨胞,也是兩種格式:
(1)shell格式
CMD <命令>
實(shí)例演示:
(2)exec格式
CMD ["可執(zhí)行文件", "參數(shù)1", "參數(shù)2"...]
實(shí)例演示:
注意:docker run命令如果指定了參數(shù)會(huì)把CMD里的參數(shù)覆蓋: (這里說(shuō)明一下,如:docker run -it ubuntu /bin/bash 命令的參數(shù)是指/bin/bash 而非 -it ,-it只是docker 的參數(shù)蒜鸡,而不是容器的參數(shù)胯努,以下所說(shuō)參數(shù)均如此。)
??同樣是上面的鏡像啟動(dòng)逢防,如果命令是docker run -it imageecho:2.0 /bin/bash
叶沛,則并不會(huì)輸出test CMD command,因?yàn)镃MD命令被"/bin/bash"
覆蓋了胞四。
(3)參數(shù)列表格式
CMD ["參數(shù)1", "參數(shù)2"...]
??在指定了ENTRYPOINT
指令后恬汁,用CMD指定具體的參數(shù)。
??Docker不是虛擬機(jī)辜伟,容器就是進(jìn)程氓侧。既然是進(jìn)程,name在啟動(dòng)容器的時(shí)候导狡,需要指定所運(yùn)行的程序及參數(shù)约巷。CMD
指令就是用于指定默認(rèn)的容器主進(jìn)程啟動(dòng)命令的。
ENTRYPOINT官方定義(詳細(xì)用法參考)
An ENTRYPOINT allows you to configure a container that will run as an executable.(它可以讓你的容器功能表現(xiàn)得像一個(gè)可執(zhí)行程序一樣旱捧。)
??如果使用下面的腳本構(gòu)建鏡像:
...(pre shell)
ENTRYPOINT ["/bin/echo"]
??那么docker build出來(lái)的鏡像以后的容器功能就像一個(gè)/bin/echo程序独郎。比如我build出來(lái)的鏡像名稱叫imageecho,那么我可以這樣用它:
docker run -it imageecho “this is a test”
??這里就會(huì)輸出”this is a test”這串字符枚赡,而這個(gè)imageecho鏡像對(duì)應(yīng)的容器表現(xiàn)出來(lái)的功能就像一個(gè)echo程序一樣氓癌。 你添加的參數(shù)“this is a test”會(huì)添加到ENTRYPOINT后面滩字,就成了這樣/bin/echo “this is a test”
咽安。
實(shí)例演示:
如果不需要?jiǎng)討B(tài)外部傳參,則可以使用以下命令:
ENTRYPOINT ["命令", "參數(shù)1", "參數(shù)2"]
要達(dá)到上面的效果咒钟,腳本修改成ENTRYPOINT ["/bin/echo", "this is a test"]
卢肃,如圖所示:
??既然
ENTRYPOINT
指令本身可以傳參疲迂,為什么還要有一種通過(guò)CMD
指令傳參的方式呢?區(qū)別在于莫湘,ENTRYPOINT
指令指定的參數(shù)是綁定的尤蒿,不能在啟動(dòng)容器時(shí)傳參覆蓋,而CMD
指令的參數(shù)可以被動(dòng)態(tài)覆蓋幅垮,類似默認(rèn)值的效果腰池,如下所示:
RUN
和CMD
的區(qū)別?
- RUN命令執(zhí)行命令并創(chuàng)建新的鏡像層,通常用于安裝軟件包示弓。
- CMD命令設(shè)置容器啟動(dòng)后默認(rèn)執(zhí)行的命令及其參數(shù)演怎,但CMD設(shè)置的命令能夠被docker run命令后面的命令行參數(shù)替換。
4避乏、ENV設(shè)置環(huán)境變量
??命令格式有兩種:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>
??這個(gè)指令很簡(jiǎn)單爷耀,就是設(shè)置環(huán)境變量而已,無(wú)論是后面的其他指令拍皮,如RUN
歹叮,還是 運(yùn)行時(shí)的應(yīng)用,都可以直接使用這里定義的環(huán)境變量铆帽。
ENV VERSION=1.0 DEBUG=on
$VERSION # 使用環(huán)境變量
??下列指令可以支持環(huán)境變量展開(kāi):ADD
咆耿、COPY
、ENV
爹橱、EXPOSE
萨螺、LABEL
、WORKDIR
愧驱、VOLUME
慰技、STOPSIGNAL
、ONBUILD
组砚。
實(shí)例演示:
5吻商、ARG構(gòu)建參數(shù)
格式:
ARG <參數(shù)名>[=<默認(rèn)值>]
??構(gòu)建參數(shù)和ENV
的效果一樣,都是設(shè)置環(huán)境變量糟红。所不同的是艾帐,ARG
所設(shè)置的構(gòu)建環(huán)境的環(huán)境變量,在將來(lái)容器運(yùn)行時(shí)是不會(huì)存在這些環(huán)境變量的盆偿。但是不要因此就使用ARG
保存密碼之類的信息柒爸,因?yàn)?code>docker history還是可以看到所有值的。
??Dockerfile中的ARG
指令是定義參數(shù)名稱事扭,以及定義其默認(rèn)值捎稚。該默認(rèn)值可以在構(gòu)建命令docker build
中用--build-arg <參數(shù)名>=<值>
來(lái)覆蓋。
6句旱、VOLUME定義匿名卷
格式:
VOLUME ["<路徑1>", "<路徑2>"]
VOLUME <路徑>
??容器運(yùn)行時(shí)應(yīng)該盡量保持容器存儲(chǔ)層不發(fā)生寫操作阳藻,對(duì)于數(shù)據(jù)庫(kù)類需要保存動(dòng)態(tài)數(shù)據(jù)的應(yīng)用晰奖,其數(shù)據(jù)庫(kù)文件應(yīng)該保存于卷(volume)中谈撒,為了防止運(yùn)行時(shí)用戶忘記將動(dòng)態(tài)文件所保存目錄掛載為卷,在Dockerfile中匾南,我們可以事先指定某些目錄掛載為匿名卷啃匿,這樣在運(yùn)行時(shí)如果用戶不指定掛載,其應(yīng)用也可以正常運(yùn)行,不會(huì)向容器存儲(chǔ)層寫入大量數(shù)據(jù)溯乒。
??簡(jiǎn)單地說(shuō)夹厌,就是將容器運(yùn)行時(shí)產(chǎn)生的一些有用的數(shù)據(jù)保存在宿主機(jī)中而不是容器中,這樣數(shù)據(jù)操作就跟容器狀態(tài)無(wú)關(guān)裆悄,如下所示:
??Dockerfile腳本中矛纹,指定了容器中的
/data
目錄為docker的匿名卷,產(chǎn)生的效果就是光稼,docker會(huì)在宿主機(jī)的/var/lib/docker/volumes/
目錄下創(chuàng)建一個(gè)對(duì)應(yīng)的文件夾或南,文件夾名是一串字符串(應(yīng)該是根據(jù)容器ID、鏡像名版本等計(jì)算出來(lái)的)艾君,里面有個(gè)_data
目錄采够,存放這對(duì)應(yīng)容器運(yùn)行時(shí)的/data
目錄下產(chǎn)生的數(shù)據(jù)。
??當(dāng)然冰垄,運(yùn)行時(shí)可以覆蓋這個(gè)掛載設(shè)置蹬癌,比如:
docker run -d -v mydata:/data
??在這行命令中,就是用了mydata這個(gè)命名卷掛載到了/data
這個(gè)位置虹茶,替代了Dockerfile中定義的匿名卷的掛載配置逝薪,如下圖所示:
7、EXPOSE聲明端口
格式:
EXPOSE <端口1> [<端口2>...]
??EXPOSE
指令是聲明運(yùn)行時(shí)容器提供服務(wù)端口蝴罪,這只是一個(gè)聲明翼闽,表示可以在運(yùn)行命令中使用-P
或-p
參數(shù)使用和映射端口,在運(yùn)行時(shí)并不會(huì)因?yàn)檫@個(gè)聲明應(yīng)用就會(huì)開(kāi)啟這個(gè)端口的服務(wù)洲炊。
在Dockerfile中寫入這樣的聲明有兩個(gè)好處:
(1)幫助鏡像的使用者如運(yùn)維人員理解這個(gè)鏡像服務(wù)的守護(hù)端口感局,以方便配置映射;
(2)在運(yùn)行時(shí)使用隨機(jī)映射端口映射時(shí)暂衡,也就是
docker run -P
時(shí)询微,會(huì)自動(dòng)隨機(jī)映射EXPOSE
的端口。8狂巢、WORKDIR指定工作目錄
格式:
WORKDIR <工作目錄路徑>
??使用WORKDIR
指令可以來(lái)指定工作目錄(或者稱為當(dāng)前目錄)撑毛,以后各層的當(dāng)前目錄就被改為指定的目錄,如該目錄不存在唧领,WORKDIR
會(huì)幫你建立目錄藻雌。
??初學(xué)者常犯的錯(cuò)誤是把Dockerfile當(dāng)做Shell腳本來(lái)寫,這種錯(cuò)誤的理解還可能會(huì)導(dǎo)致出現(xiàn)蝦米那這樣的錯(cuò)誤:
RUN cd /app
RUN echo "hello" > world.txt
??如果將這個(gè)Dockerfile進(jìn)行構(gòu)建鏡像運(yùn)行后斩个,會(huì)發(fā)現(xiàn)找不到/app/world.txt
文件胯杭。
錯(cuò)誤分析:
- 在Shell中,連續(xù)兩行是同一個(gè)進(jìn)程執(zhí)行環(huán)境受啥,因此前一個(gè)命令修改的內(nèi)存狀態(tài)做个,會(huì)直接影響后一個(gè)命令
- 而在Dockerfile中鸽心,這兩行
RUN
命令的執(zhí)行環(huán)境根本不同,是兩個(gè)完全不同的容器居暖,RUN
執(zhí)行的過(guò)程實(shí)際上是構(gòu)建新的鏡像層的過(guò)程顽频。每一個(gè)
RUN
都是啟動(dòng)一個(gè)容器、執(zhí)行命令太闺、然后提交存儲(chǔ)層文件變更糯景。
第一層RUN cd /app
的執(zhí)行僅僅是當(dāng)前進(jìn)程的工作目錄變更,一個(gè)內(nèi)存上的變化而已省骂,其結(jié)果不會(huì)造成任何文件變更莺奸。而到第二層的時(shí)候,啟動(dòng)的是一個(gè)全新的容器冀宴,跟第一層的容器更完全沒(méi)關(guān)系灭贷,自然不可能繼承前一層構(gòu)建過(guò)程中的內(nèi)存變化。因此如果需要改變以后各層的工作目錄的位置略贮,name應(yīng)該使用
WORKDIR
指令甚疟。
9、USER指定當(dāng)前用戶
格式:
USER <用戶名>
??USER
和WORKDIR
類似逃延,都是改變環(huán)境狀態(tài)并影響以后的層览妖。WORKDIR
是改變工作目錄,USER
則是改變之后層的執(zhí)行RUN
揽祥、CMD
以及ENTRYPOINT
這類命令的身份讽膏。
??當(dāng)然,和WORKDIR
一樣拄丰,USER
只是幫助你切換到指定用戶而已府树,這個(gè)用戶必須是事先建立好的,否則無(wú)法切換料按。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN ["redis-server"]
10奄侠、HEALTHCHECK健康檢查
格式:
HEALTHCHECK [選項(xiàng)] CMD <命令>:設(shè)置檢查容器健康狀況的命令
HEALTHCHECK NONE:如果基礎(chǔ)鏡像有健康檢查指令,可以屏蔽掉其健康檢查指令
??HEALTHCHECK
指令是高速Docker應(yīng)該如何進(jìn)行判斷容器的狀態(tài)是否正常载矿,這是Docker 1.12引入的新指令垄潮。通過(guò)該指令指定一行命令,用這行命令來(lái)判斷容器主進(jìn)程的服務(wù)狀態(tài)是否還正常闷盔,從而比較真實(shí)地反映容器實(shí)際狀態(tài)弯洗。
一個(gè)鏡像指定了HEALTHCHECK指令后,用其啟動(dòng)容器逢勾,初始狀態(tài)會(huì)為starting牡整,在執(zhí)行健康檢查成功后變>為healthy,如果連續(xù)一定次數(shù)失敗敏沉,則會(huì)變?yōu)閡nhealthy果正。
HEALTHCHECK支持下列選項(xiàng):
- --interval=<間隔>:兩次健康檢查的間隔,默認(rèn)為30秒盟迟;
- --timeout=<時(shí)長(zhǎng)>:健康檢查命令運(yùn)行超時(shí)時(shí)間秋泳,如果超過(guò)這個(gè)時(shí)間,本次健康檢查就被視為失敗攒菠,默認(rèn)30秒迫皱;
- --retries=<次數(shù)>:當(dāng)連續(xù)失敗指定次數(shù)后,則將容器狀態(tài)視為unhealthy辖众,默認(rèn)3次卓起。
??為了幫助排障,健康檢查命令的輸出(包括stdout以及stderr)都會(huì)被存儲(chǔ)于健康狀態(tài)里凹炸,可以用docker inspect
來(lái)查看戏阅。
11、ONBUILD為他人做嫁衣
格式:
ONBUILD <其他指令>
??ONBUILD
是一個(gè)特殊的指令啤它,它后面跟的是其他指令奕筐,比如RUN
、COPY
等变骡,而這些指令离赫,在當(dāng)前鏡像構(gòu)建時(shí)并不會(huì)被執(zhí)行,只有當(dāng)以當(dāng)前鏡像為基礎(chǔ)鏡像塌碌,去構(gòu)建下一級(jí)鏡像的時(shí)候才會(huì)被執(zhí)行渊胸。
??Dockerfile中的其他指令都是為了定制當(dāng)前鏡像而準(zhǔn)備的,唯有ONBUILD
是為了幫助別人定制自己而準(zhǔn)備的台妆。
12翎猛、docker save & docker load
??Docker還提供了docker load
和docker save
命令,用以將鏡像保存為一個(gè)tar文件接剩,然后傳輸?shù)搅硪粋€(gè)位置上办成,再加載進(jìn)來(lái)。這是在沒(méi)有Docker Registry時(shí)的做法搂漠,現(xiàn)在已經(jīng)不推薦迂卢,鏡像遷移應(yīng)該直接使用Docker Registry,無(wú)論是直接使用Docker Hub還是使用內(nèi)網(wǎng)私有Registry都可以桐汤。
例如:保存nginx鏡像:
docker save nginx | gzip > nginx-latest.tar.gz
然后我們將nginx-latest.tar.gz文件復(fù)制到了另一個(gè)機(jī)器上而克,再次加載鏡像:
docker load -i nginx-latest.tar.gz
注意:跟docker export
和docker import
的區(qū)別,前者是對(duì)容器的操作怔毛,docker save
和docker load
是對(duì)鏡像的操作员萍。