新聞中心
?譯者 | 陳峻

成都創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、惠城網(wǎng)絡(luò)推廣、微信小程序、惠城網(wǎng)絡(luò)營(yíng)銷、惠城企業(yè)策劃、惠城品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);成都創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供惠城建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com
審校 | 孫淑娟
眾所周知,作為一個(gè)文本文檔,Dockerfile包含了用戶創(chuàng)建鏡像的所有命令和說(shuō)明。Docker可以通過(guò)讀取Dockerfile中指令的方式,去自動(dòng)構(gòu)建鏡像。因此,大家往往認(rèn)為編寫(xiě)Dockerfile理應(yīng)非常簡(jiǎn)單,只需從互聯(lián)網(wǎng)上選擇一個(gè)示例,并通過(guò)自定義來(lái)滿足實(shí)際需求即可。然而,事實(shí)并非如此。
由于生產(chǎn)環(huán)境有著嚴(yán)格的要求,特別是在安全方面,因此雖然有許多示例能夠適合開(kāi)發(fā)環(huán)境,但不一定在生產(chǎn)環(huán)境中也合適。另外,由于Docker也提供了一套編寫(xiě)Dockerfile的指導(dǎo)策略,這就導(dǎo)致了Dockerfile像編寫(xiě)代碼那樣,您可能知道了相關(guān)語(yǔ)法,卻不一定能夠用特定的編程語(yǔ)言寫(xiě)出干凈、簡(jiǎn)潔的代碼。下面,我將和您探討7項(xiàng)在編寫(xiě)Dockerfile時(shí),比較實(shí)用的優(yōu)秀策略與理論實(shí)踐。
一、簡(jiǎn)介
首先,讓我們來(lái)看一個(gè)典型的Dockerfile示例:
Dockerfile
FROM eclipse-temurin:17
RUN mkdir /opt/app
ARG JAR_FILE
ADD target/${JAR_FILE} /opt/app/app.jar
CMD ["java", "-jar", "/opt/app/app.jar"]
根據(jù)其內(nèi)容,該Dockerfile會(huì)執(zhí)行以下操作:
- lFROM:將Java Docker鏡像--eclipse-temurin:17作為基本鏡像;
- lRUN:為jar文件創(chuàng)建一個(gè)目錄;
- lARG:通過(guò)提供一個(gè)參數(shù)--JAR_FILE,避免將jar文件名被硬編碼到Dockerfile中;
- lADD:將jar文件添加到Docker鏡像中;
- lCMD:包含了在運(yùn)行容器時(shí)必須執(zhí)行的命令。
可見(jiàn),上述每個(gè)段落生成的Dockerfile,都可以在Git存儲(chǔ)庫(kù)的Dockerfiles目錄下被找到。而且在每個(gè)段落的末尾處,相應(yīng)的Dockerfile名稱也會(huì)在適用的地方被提及。下面,我們將通過(guò)修改該Dockerfile來(lái)實(shí)現(xiàn)七種優(yōu)秀實(shí)踐。
二、先決條件
在繼續(xù)閱讀下文之前,您需要具備的先決條件是:
- 基本的Linux知識(shí)
- 基本的Java和Spring Boot知識(shí)
- 基本的Docker知識(shí)
三、應(yīng)用示例
為了展示各項(xiàng)優(yōu)秀實(shí)踐,我事先創(chuàng)建了一個(gè)包含Spring Web依賴項(xiàng)的基本Spring Boot應(yīng)用。該應(yīng)用可以通過(guò)在存儲(chǔ)庫(kù)的根目錄中調(diào)用以下命令來(lái)運(yùn)行:
Shell
$ MVN spring-boot:run
而為了構(gòu)建Docker鏡像,我將使用Spotify的dockerfile-maven-plugin的一個(gè)分支。為此,我會(huì)將如下代碼段添加到pom文件中。
XMLcom.xenoamess.docker dockerfile-maven-plugin 1.4.25 mydeveloperplanet/dockerbestpractices ${project.version} ${project.build.finalName}.jar
使用該插件的好處在于,您可以輕松地重用配置。同時(shí),為了實(shí)現(xiàn)通過(guò)Maven命令來(lái)創(chuàng)建Docker鏡像,您可以通過(guò)調(diào)用如下命令來(lái)構(gòu)建jar文件:
Shell
$ mvn clean verify
接著,請(qǐng)通過(guò)調(diào)用如下命令來(lái)構(gòu)建Docker鏡像:
Shell
$ mvn dockerfile:build
如下命令可讓您運(yùn)行Docker鏡像:
Shell
$ docker run --name dockerbestpractices mydeveloperplanet/dockerbestpractices:0.0.1-SNAPSHOT
然后,請(qǐng)通過(guò)如下代碼來(lái)找到運(yùn)行中的容器的IP地址:
Shell
$ docker inspect dockerbestpractices | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.3",
"IPAddress": "172.17.0.3"
本例的IP地址為172.17.0.3。同時(shí),該應(yīng)用還包含一個(gè)只用來(lái)響應(yīng)hello消息的HelloController。而且,Hello端點(diǎn)可以通過(guò)如下方式被調(diào)用:
Shell
$ curl http://172.17.0.3:8080/hello
Hello Docker!
至此,一切就緒了。
四、各項(xiàng)優(yōu)秀實(shí)踐
1.該使用哪個(gè)鏡像
在前文中,我們提到了本例Dockerfile中使用到的鏡像是eclipse-temurin:17。下面,我們來(lái)看看該鏡像是如何被構(gòu)建的:
- 請(qǐng)?jiān)L問(wèn)DockerHub的鏈接;
- 搜索‘eclipse-temurin’;
- 導(dǎo)航到“標(biāo)簽”;
- 搜索17;
- 按A-Z排序;
- 單擊標(biāo)簽17。
如果您仔細(xì)觀察頁(yè)面每個(gè)層的細(xì)節(jié),并將其與標(biāo)簽17-JRE進(jìn)行比較,就會(huì)注意到標(biāo)簽17包含了一個(gè)完整的JDK,而標(biāo)簽17-JRE只是包含了JRE。當(dāng)然,后者對(duì)于運(yùn)行Java應(yīng)用來(lái)說(shuō)已經(jīng)足夠了,畢竟在生產(chǎn)環(huán)境中運(yùn)行各種應(yīng)用是不需要整個(gè)JDK的。而且,由于開(kāi)發(fā)工具可能會(huì)被濫用,因此JDK在使用中也帶有一定的安全問(wèn)題。此外,標(biāo)簽17的鏡像在壓縮后的尺寸為235MB,而17-jre的壓縮后尺寸只有89MB。
為了進(jìn)一步減小鏡像的尺寸,我們可以使用經(jīng)“瘦身”的鏡像:17-jre-alpine。該鏡像的壓縮后尺寸為59MB,足足比17-jre減少了30MB,因此它更容易被分發(fā)。
值得注意到是,以上使用的標(biāo)簽均為通用標(biāo)簽,且指向的是最新版本。這對(duì)于開(kāi)發(fā)環(huán)境來(lái)說(shuō)可能沒(méi)有問(wèn)題,但是對(duì)于生產(chǎn)環(huán)境而言,您需要事先明確所使用的版本。本例中使用的標(biāo)簽便是17.0.5_8-jre-alpine。如您想進(jìn)一步加固安全,則可以將SHA256散列添加到鏡像的版本中。SHA256散列可以在包含了這些層的頁(yè)面上找到。當(dāng)SHA256的哈希值與Dockerfile中定義的哈希值無(wú)法對(duì)應(yīng)時(shí),構(gòu)建Docker鏡像的過(guò)程將會(huì)失敗。
在本例中,Dockerfile的第一行為:
Dockerfile
FROM eclipse-temurin:17
有了上面的知識(shí),我們可以將該行更改為:
Dockerfile
FROM eclipse-temurin: 17.0.5_8-jre-alpine@sha256:02c04793fa49ad5cd193c961403223755f9209a67894622e05438598b32f210e
如下代碼所示,在Docker鏡像完成構(gòu)建后,您會(huì)注意到,(曾經(jīng)未壓縮的)鏡像從以前的475MB縮小到現(xiàn)在的188MB。
Shell
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/dockerbestpractices 0.0.1-SNAPSHOT 0b8d89616602 3 seconds ago 188MB
生成的Dockerfile在Git存儲(chǔ)庫(kù)中被命名為1-Dockerfile-specific-image。
2.不要以Root用戶運(yùn)行
默認(rèn)情況下,應(yīng)用程序在容器中會(huì)以Root用戶身份運(yùn)行。這顯然會(huì)暴露許多漏洞風(fēng)險(xiǎn),并且也不是必要的。對(duì)此,您應(yīng)該為應(yīng)用定義一個(gè)系統(tǒng)級(jí)用戶。如下代碼所示,在啟動(dòng)容器時(shí)的第一行日志中,您可以看到該應(yīng)用是由Root啟動(dòng)的。
Shell
2022-11-26 09:03:41.210 INFO 1 --- [ main] m.MyDockerBestPracticesPlanetApplication : Starting MyDockerBestPracticesPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.5 on 3b06feee6c65 with PID 1 (/opt/app/app.jar started by root in /)
我們可以通過(guò)向Dockerfile中添加組javauser和用戶javauser,來(lái)創(chuàng)建系統(tǒng)級(jí)用戶。然后,通過(guò)向Dockerfile中添加如下指令來(lái)實(shí)現(xiàn)。其中,javauser是一個(gè)系統(tǒng)級(jí)用戶,并不具備登錄權(quán)限。注意,為了只創(chuàng)建一個(gè)層面,組和用戶的創(chuàng)建步驟都被&符號(hào)組合到了一行之中。
Dockerfile
RUN addgroup——system javauser && adduser -S -S /usr/sbin/nologin -G javauser javauser
下表列出了可用于adduser的完整參數(shù)集:
- -h,即DIR主目錄
- -g,即GECOS字段
- -s,即登錄SHELL
- -G,即組
- -S,即創(chuàng)建系統(tǒng)級(jí)用戶
- -D,即不用設(shè)置密碼
- -H,即不要?jiǎng)?chuàng)建主目錄
- -u,即UID,用戶id
- -k,即Skeleton目錄(/etc/SKEL)
同時(shí),您也可以通過(guò)添加如下一行,將目錄/opt/apt的所有者更改為新的javauser,否則javauser將無(wú)法訪問(wèn)該目錄:
Dockerfile
RUN chown -R javauser:javauser /opt/app
最后,您需要確保通過(guò)USER命令在容器中實(shí)際使用了javauser。其對(duì)應(yīng)的完整Dockerfile為:
Dockerfile
FROM eclipse-temurin:17.0.5_8-jre-alpine@sha256:02c04793fa49ad5cd193c961403223755f9209a67894622e05438598b32f210e
RUN mkdir /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
ARG JAR_FILE
ADD target/${JAR_FILE} /opt/app/app.jar
RUN chown -R javauser:javauser /opt/app
USER javauser
CMD ["java", "-jar", "/opt/app/app.jar"]
為了測(cè)試這個(gè)新的鏡像,您首先需要通過(guò)如下命令,停止并刪除正在運(yùn)行的容器。
Shell
$ docker stop dockerbestpractices
$ docker rm dockerbestpractices
完成重新構(gòu)建并再次運(yùn)行容器后,如下代碼所示,您可以在第一行日志中看到,該應(yīng)用程序是由javauser啟動(dòng)的。
Shell
2022-11-26 09:06:45.227 INFO 1 --- [ main] m.MyDockerBestPracticesPlanetApplication : Starting MyDockerBestPracticesPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.5 on ab1bcd38dff7 with PID 1 (/opt/app/app.jar started by javauser in /)
同樣,生成的Dockerfile在Git存儲(chǔ)庫(kù)中被命名為2-Dockerfile-do-not-run-as-root。
3.使用WORKDIR
在您使用的Dockerfile中,目錄/opt/app是被一次性創(chuàng)建的,畢竟這是您的工作目錄。就算它并不存在,Docker也會(huì)幫您默認(rèn)創(chuàng)建。因此,您不必每一次都重復(fù)這條路徑。例如,您會(huì)看到Dockerfile的第二行包含了如下RUN指令:
Dockerfile
RUN mkdir /opt/app
我們可以通過(guò)使用WORKDIR指令來(lái)稍作改變:
Dockerfile
WORKDIR /opt/app
由于WORKDIR指令已經(jīng)確保了您在該目錄下,因此您完全可以刪除每一個(gè)/opt/app的引用。因此,新的Dockerfile如下代碼所示:
Dockerfile
FROM eclipse-temurin:17.0.5_8-jre-alpine@sha256:02c04793fa49ad5cd193c961403223755f9209a67894622e05438598b32f210e
WORKDIR /opt/app
RUN addgroup --system javauser && adduser -S -s /usr/sbin/nologin -G javauser javauser
ARG JAR_FILE
ADD target/${JAR_FILE} app.jar
RUN chown -R javauser:javauser .
USER javauser
CMD ["java", "-jar", "app.jar"]
完成構(gòu)建并重新運(yùn)行容器后,您可以在如下日志中看到,jar文件仍然在/opt/app目錄中被執(zhí)行:
Shell
2022-11-26 16:07:18.503 INFO 1 --- [ main] m.MyDockerBestPracticesPlanetApplication : Starting MyDockerBestPracticesPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.5 on fe5cf9223143 with PID 1 (/opt/app/app.jar started by javauser in /opt/app)
同樣,生成的Dockerfile在Git存儲(chǔ)庫(kù)中被命名為3-Dockerfile-use-workdir。
4.使用ENTRYPOINT
CMD指令和ENTRYPOINT指令之間是存在區(qū)別的。簡(jiǎn)而言之,兩者的使用場(chǎng)景分別是:
ENTRYPOINT:當(dāng)您總需要執(zhí)行各種命令,才能構(gòu)建出可執(zhí)行的Docker鏡像時(shí),只要您愿意,完全可以將參數(shù)附加到命令中。
CMD:當(dāng)您想提供一個(gè)默認(rèn)的參數(shù)集,且允許在容器運(yùn)行時(shí)被命令行覆蓋時(shí)。
那么,在運(yùn)行Java應(yīng)用的情況下,請(qǐng)最好使用ENTRYPOINT。例如,原本Dockerfile的最后一行為:
Dockerfile
CMD ["java", "-jar", "app.jar"]
現(xiàn)在可以變?yōu)椋?/p>
Dockerfile
ENTRYPOINT ["java", "-jar", "app.jar"]
完成構(gòu)建并重新運(yùn)行容器,您并不會(huì)注意到有任何特定的差異,容器仍然會(huì)照常運(yùn)行。生成的Dockerfile在Git存儲(chǔ)庫(kù)中被命名為4-Dockerfile-use-entrypoint。
5.使用COPY代替ADD
COPY和ADD指令也似乎比較類似。然而,COPY要比ADD更好,畢竟COPY只是復(fù)制文件到鏡像,而ADD還有一些額外的特性,比如添加來(lái)自遠(yuǎn)程資源的文件。
Dockerfile中的ADD命令行為:
Dockerfile
ADD target/${JAR_FILE} app.jar
如果改用COPY命令,則為:
Dockerfile
COPY target/${JAR_FILE} app.jar
重新構(gòu)建并運(yùn)行容器,您同樣看不出顯著變化,除了在構(gòu)建日志中顯示的是COPY命令,而不是ADD命令。生成的Dockerfile在Git存儲(chǔ)庫(kù)中可用,名稱為5-Dockerfile-use-copy-instead-of-add。
6.使用.dockerignore
為了防止Docker鏡像被意外地添加文件,您可以使用.dockerignore文件來(lái)指定哪些文件可以被發(fā)送到Docker守護(hù)進(jìn)程中,或者是在鏡像中被使用。一種值得推薦的方法是:忽略所有的文件,僅顯式地添加那些您允許的文件。通過(guò)在.dockerignore文件中添加星號(hào),我們可以排除所有的子目錄和文件。當(dāng)然,為了將jar文件放入構(gòu)建的上下文,您也可以使用感嘆號(hào)來(lái)避免忽略jar文件。如下dockerignore文件所示,我們可以將它添加到運(yùn)行Docker命令的目錄中。例如,在本例中,我們將其添加到Git存儲(chǔ)庫(kù)的根目錄上。
Plain Text
**/**
!target/*.jar
完成構(gòu)建并重新運(yùn)行容器后,其變化可能并不顯著。但是當(dāng)您使用npm開(kāi)發(fā)時(shí),由于node_modules目錄不再被復(fù)制到Docker構(gòu)建的上下文中,因此您能夠明顯地感受到創(chuàng)建Docker鏡像的過(guò)程被縮短了。注意,您可以直接在Git存儲(chǔ)庫(kù)的Dockerfiles目錄下找到dockerignore文件。
7.以non-root方式運(yùn)行Docker守護(hù)進(jìn)程
默認(rèn)情況下,Docker守護(hù)進(jìn)程是以Root身份運(yùn)行的。通過(guò)前文的討論,您一定覺(jué)察到了潛在的安全問(wèn)題。慶幸的是,從Docker v20.10開(kāi)始,我們可以non-root用戶運(yùn)行Docker守護(hù)進(jìn)程了。
此外,您也可以利用無(wú)守護(hù)進(jìn)程(daemonless)的容器引擎--Podman(https://podman.io/)。以默認(rèn)non-root方式運(yùn)行。雖然有人認(rèn)為Podman是Docker的臨時(shí)替代品,但是它們?cè)谌萜髦袙燧d卷的方面有所區(qū)別。
五、小結(jié)
在上文中,我們介紹了7種編寫(xiě)Dockerfile和運(yùn)行容器的最佳實(shí)踐。雖然編寫(xiě)Dockerfile并不復(fù)雜,但是若想正確、規(guī)范地編寫(xiě),還是需要您花些時(shí)間去研究和理解其使用說(shuō)明的。
新聞標(biāo)題:Docker的七項(xiàng)優(yōu)秀實(shí)踐
文章路徑:http://www.dlmjj.cn/article/dhshgoh.html


咨詢
建站咨詢
