摘要:本文將介紹精簡(jiǎn)容器鏡像的必要性并以基于的應(yīng)用為例描述最小化容器鏡像的常用技巧。經(jīng)過這一優(yōu)化,最終鏡像的大小為。
隨著容器技術(shù)的普及,越來越多的應(yīng)用被容器化。人們使用容器的頻率越來越高,但常常忽略一個(gè)基本但又非常重要的問題 - 容器鏡像的體積。本文將介紹精簡(jiǎn)容器鏡像的必要性并以基于 spring boot 的 java 應(yīng)用為例描述最小化容器鏡像的常用技巧。
精簡(jiǎn)容器鏡像是非常必要的,下面分別從安全性和敏捷性兩個(gè)角度進(jìn)行闡釋。
安全性
基于安全方面的考慮,將不必要的組件從鏡像中移除可以減少攻擊面、降低安全風(fēng)險(xiǎn)。雖然 docker 支持用戶通過?Seccomp?限制容器內(nèi)可以執(zhí)行操作或者使用?AppArmor?為容器配置安全策略,但它們的使用門檻較高,要求用戶具備安全領(lǐng)域的專業(yè)素養(yǎng)。
敏捷性
精簡(jiǎn)的容器鏡像能提高容器的部署速度。假設(shè)某一時(shí)刻訪問流量激增,您需要通過增加容器副本數(shù)以應(yīng)對(duì)突發(fā)壓力。如果某些宿主機(jī)不包含目標(biāo)鏡像,需要先拉取鏡像,然后啟動(dòng)容器,這時(shí)使用體積較小的鏡像能加速這一過程、縮短擴(kuò)容時(shí)間。另外,鏡像體積越小,其構(gòu)建速度也越快,同時(shí)還能減少存儲(chǔ)和傳輸?shù)某杀尽?/p>
將一個(gè) java 應(yīng)用容器化所需的步驟可歸納如下:
編譯 java 源碼并生成 jar 包。
將應(yīng)用 jar 包和依賴的第三方 jar 包移動(dòng)到合適的位置。
本章所用的樣例是一個(gè)基于 spring boot 的 java 應(yīng)用?spring-boot-docker,所用的未經(jīng)優(yōu)化的?dockerfile?如下:
FROM maven:3.5-jdk-8 COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package ENTRYPOINT ["java","-jar","/usr/src/app/target/spring-boot-docker-1.0.0.jar"]
由于應(yīng)用使用 maven 構(gòu)建,dockerfile 中指定maven:3.5-jdk-8作為基礎(chǔ)鏡像,該鏡像的大小為 635MB。通過這種方式最終構(gòu)建出的鏡像非常大,達(dá)到了 719MB,這是因?yàn)橐环矫婊A(chǔ)鏡像本身就很大,另一方面 maven 在構(gòu)建過程中會(huì)下載許多用于執(zhí)行構(gòu)建任務(wù)的 jar 包。
多階段構(gòu)建
Java 程序的運(yùn)行只依賴 JRE,并不需要 maven 或者 JDK 中眾多用于編譯、調(diào)試、運(yùn)行的工具,因此一個(gè)明顯的優(yōu)化方法是將用于編譯構(gòu)建 java 源碼的鏡像和用于運(yùn)行 java 應(yīng)用的鏡像分開。為了達(dá)到這一目的,在 docker 17.05 版本之前需要用戶維護(hù) 2 個(gè) dockerfile 文件,這無疑增加了構(gòu)建的復(fù)雜性。好在自 17.05 開始,docker 引入了多階段構(gòu)建的概念,它允許用戶在一個(gè) dockerfile 中使用多個(gè) From 語句。每個(gè) From 語句可以指定不同的基礎(chǔ)鏡像并將開啟一個(gè)全新的構(gòu)建流程。您可以選擇性地將前一階段的構(gòu)建產(chǎn)物復(fù)制到另一個(gè)階段,從而只將必要的內(nèi)容保留在最終的鏡像里。優(yōu)化后的?dockerfile?如下:
FROM maven:3.5-jdk-8 AS build COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package FROM openjdk:8-jre ARG DEPENDENCY=/usr/src/app/target/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
該 dockerfile 選用maven:3.5-jdk-8作為第一階段的構(gòu)建鏡像,選用openjdk:8-jre作為運(yùn)行 java 應(yīng)用的基礎(chǔ)鏡像并且只拷貝了第一階段編譯好的.claass文件和依賴的第三方 jar 包到最終的鏡像里。通過這種方式優(yōu)化后的鏡像大小為 459MB。
使用 distroless 作為基礎(chǔ)鏡像
雖然通過多階段構(gòu)建能減小最終生成的鏡像的大小,但 459MB 的體積仍相對(duì)過大。經(jīng)調(diào)查發(fā)現(xiàn),這是因?yàn)槭褂玫幕A(chǔ)鏡像openjdk:8-jre體積過大,到達(dá)了 443MB,因此下一步的優(yōu)化方向是減小基礎(chǔ)鏡像的體積。
Google 開源的項(xiàng)目?distroless?正是為了解決基礎(chǔ)鏡像體積過大這一問題。Distroless 鏡像只包含應(yīng)用程序及其運(yùn)行時(shí)依賴項(xiàng),不包含包管理器、shell 以及在標(biāo)準(zhǔn) Linux 發(fā)行版中可以找到的任何其他程序。目前,distroless 為依賴?java、python、nodejs、dotnet?等環(huán)境的應(yīng)用提供了基礎(chǔ)鏡像。
使用 distroless 的?dockerfile?如下:
FROM maven:3.5-jdk-8 AS build COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package FROM gcr.io/distroless/java ARG DEPENDENCY=/usr/src/app/target/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
該 dockerfile 和上一版的唯一區(qū)別在于將運(yùn)行階段依賴的基礎(chǔ)鏡像由openjdk:8-jre(443 MB)替換成了gcr.io/distroless/java(119 MB)。經(jīng)過這一優(yōu)化,最終鏡像的大小為 135MB。
使用 distroless 的唯一不便是您無法 attach 到一個(gè)正在運(yùn)行的容器上排查問題,因?yàn)殓R像中不包含 shell。雖然 distroless 的?debug 鏡像提供 busybox shell,但需要用戶重新打包鏡像、部署容器,對(duì)于那些已經(jīng)基于非 debug 鏡像部署的容器無濟(jì)于事。 但從安全角度來看,無法 attach 容器并不完全是壞事,因?yàn)楣粽邿o法通過 shell 進(jìn)行攻擊。
使用 alpine 作為基礎(chǔ)鏡像
如果您確實(shí)有 attach 容器的需求,又希望最小化鏡像的大小,可以選用?alpine?作為基礎(chǔ)鏡像。Alpine 鏡像的特點(diǎn)是體積非常下,基礎(chǔ)款鏡像的體積僅 4 MB 左右。
使用 alpine 后的?dockerfile?如下:
FROM maven:3.5-jdk-8 AS build COPY src /usr/src/app/src COPY pom.xml /usr/src/app RUN mvn -f /usr/src/app/pom.xml clean package FROM openjdk:8-jre-alpine ARG DEPENDENCY=/usr/src/app/target/dependency COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]
這里并未直接繼承基礎(chǔ)款 alpine,而是選用從 alpine 構(gòu)建出的包含 java 運(yùn)行時(shí)的openjdk:8-jre-alpine(83MB)作為基礎(chǔ)鏡像。使用該 dockerfile 構(gòu)建出的鏡像體積為 99.2MB,比基于 distroless 的還要小。
執(zhí)行命令docker exec -ti
distroless vs alpine
既然 distroless 和 alpine 都能提供非常小的基礎(chǔ)鏡像,那么在生產(chǎn)環(huán)境中到底應(yīng)該選擇哪一種呢?如果安全性是您的首要考慮因素,建議選用 distroless,因?yàn)樗ㄒ豢蛇\(yùn)行的二進(jìn)制文件就是您打包的應(yīng)用;如果您更關(guān)注鏡像的體積,可以選用 alpine。
其他技巧
除了可以通過上述技巧精簡(jiǎn)鏡像外,還有以下方式:
將 dockerfile 中的多條指令合并成一條,通過減少鏡像層數(shù)的方式達(dá)到精簡(jiǎn)鏡像體積的目的。
將穩(wěn)定且體積較大的內(nèi)容置于鏡像下層,將變動(dòng)頻繁且體積較小的內(nèi)容置于鏡像上層。雖然該方式無法直接精簡(jiǎn)鏡像體積,但充分利用了鏡像的緩存機(jī)制,同樣可以達(dá)到加快鏡像構(gòu)建和容器部署的目的。
想了解更多優(yōu)化 dockerfile 的小竅門可參考教程?Best practices for writing Dockerfiles。
本文通過一系列的優(yōu)化,將 java 應(yīng)用的鏡像體積由最初的 719MB 縮小到 100MB 左右。如果您的應(yīng)用依賴其他環(huán)境,也可以用類似的原則進(jìn)行優(yōu)化。
針對(duì) java 鏡像,google 提供的另一款工具?jib?能為您屏蔽鏡像構(gòu)建過程中的復(fù)雜細(xì)節(jié),自動(dòng)構(gòu)建出精簡(jiǎn)的 java 鏡像。使用它您無須編寫 dockerfile,甚至不需要安裝 docker。
對(duì)于類似 distroless 這樣無法 attach 或者不方便 attach 的容器,建議您將它們的日志中心化存儲(chǔ),以便問題的追蹤和排查。具體方法可參考文章面向容器日志的技術(shù)實(shí)踐。
閱讀原文
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/11444.html
摘要:今天我寫一點(diǎn)在容器中使用的要不要在生產(chǎn)環(huán)境使用運(yùn)行數(shù)據(jù)庫這么深?yuàn)W的問題,等我踩足夠的坑再來寫吧。這是日常使用的狀態(tài),此處省略了別的服務(wù)。初始化容器在首次啟動(dòng)的時(shí)候,必須指定一個(gè)密碼才能啟動(dòng),指定的方式是聲明環(huán)境變量。 今天我寫一點(diǎn)在 Docker 容器中使用 MYSQL 的 tips.要不要在生產(chǎn)環(huán)境使用 Docker 運(yùn)行數(shù)據(jù)庫這么深?yuàn)W的問題,等我踩足夠的坑再來寫吧。但是至少在開發(fā)和...
摘要:聯(lián)調(diào)測(cè)試,無需依賴他人。針對(duì)以上問題,有兩種解決方法,一個(gè)是自己搭建私有服務(wù),另一個(gè)是用云服務(wù)的鏡像管理平臺(tái)如阿里云的容器鏡像服務(wù)。利用,先對(duì)阿里云的服務(wù)進(jìn)行登錄。推送后,就能在阿里云的倉庫上看到這個(gè)鏡像。 Docker簡(jiǎn)述 Docker是一種OS虛擬化技術(shù),是一個(gè)開源的應(yīng)用容器引擎。它可以讓開發(fā)者將應(yīng)用打包到一個(gè)可移植的容器中,并且該容器可以運(yùn)行在幾乎所有l(wèi)inux系統(tǒng)中(Windo...
摘要:續(xù)前文后端好書閱讀與推薦,幾十天過去了,又看了兩本好書還有以前看過的書,這里依然把它們總結(jié)歸納一下,加入一些自己的看法有用的鏈接和可能的延伸閱讀,并推薦給需要的同學(xué)。 續(xù)前文 后端好書閱讀與推薦 - Mageek`s Wonderland ,幾十天過去了,又看了兩本好書(還有以前看過的書),這里依然把它們總結(jié)歸納一下,加入一些自己的看法、有用的鏈接和可能的延伸閱讀,并推薦給需要的同學(xué)。...
摘要:續(xù)前文后端好書閱讀與推薦,幾十天過去了,又看了兩本好書還有以前看過的書,這里依然把它們總結(jié)歸納一下,加入一些自己的看法有用的鏈接和可能的延伸閱讀,并推薦給需要的同學(xué)。 續(xù)前文 后端好書閱讀與推薦 - Mageek`s Wonderland ,幾十天過去了,又看了兩本好書(還有以前看過的書),這里依然把它們總結(jié)歸納一下,加入一些自己的看法、有用的鏈接和可能的延伸閱讀,并推薦給需要的同學(xué)。...
閱讀 2535·2023-04-25 14:54
閱讀 606·2021-11-24 09:39
閱讀 1815·2021-10-26 09:51
閱讀 3866·2021-08-21 14:10
閱讀 3492·2021-08-19 11:13
閱讀 2696·2019-08-30 14:23
閱讀 1813·2019-08-29 16:28
閱讀 3362·2019-08-23 13:45