主頁(yè) > 知識(shí)庫(kù) > 詳解六種減小Docker鏡像大小的方法

詳解六種減小Docker鏡像大小的方法

熱門標(biāo)簽:長(zhǎng)沙回?fù)芡夂粝到y(tǒng) 比較穩(wěn)定的外呼系統(tǒng) 信貸電銷機(jī)器人系統(tǒng) 山東電信外呼系統(tǒng)靠譜嗎 ai電話機(jī)器人營(yíng)銷 鸚鵡螺號(hào)航海地圖標(biāo)注時(shí)間 400 電話 申請(qǐng)費(fèi)用 江蘇自動(dòng)外呼系統(tǒng)一般多少錢 云南云電銷機(jī)器人招商

我從2017年做Vulhub開始,一直在和一個(gè)麻煩的問(wèn)題做斗爭(zhēng):在編寫Dockerfile的時(shí)候, 如何減小 docker build 生成的鏡像大小 ?這篇文章就給大家總結(jié)一下我自己使用過(guò)的六種減小鏡像大小的方法。

1. 使用Alpine Linux

Alpine Linux是一個(gè)基于BusyBox和Musl Libc的Linux發(fā)行版,其最大的優(yōu)勢(shì)就是小。一個(gè)純的基礎(chǔ)Alpine Docker鏡像在壓縮后僅有2.67MB。

不少Docker官方鏡像都有Alpine版本,比如PHP:

比較之下就可以發(fā)現(xiàn),alpine版本鏡像大小是普通版本的1/5左右。

但是在Docker Hub中,大部分鏡像是沒(méi)有Alpine版本的,比如Mysql和PHP-Apache,如果我們需要基于這些環(huán)境開發(fā),就不得不自己編寫Alpine版本,或者找一些第三方鏡像。

另外,Alpine的另一個(gè)缺點(diǎn)是,其使用了Musl Libc作為傳統(tǒng)的glibc的替代,編譯軟件的時(shí)候可能會(huì)遇到一些不可預(yù)知的問(wèn)題,這一點(diǎn)會(huì)導(dǎo)致我們耗費(fèi)不少不必要的時(shí)間。

2. 只安裝最少的依賴

apt-get、yum、apk等軟件包管理器是我們編譯鏡像時(shí)必然需要用到的工具,純凈的Docker基礎(chǔ)鏡像通常會(huì)缺少wget、curl、git、gcc等工具,需要我們手工來(lái)安裝。

我們以apt為例,apt-get在安裝軟件的時(shí)候,可以指定一個(gè)選項(xiàng): --no-install-recommends ,指定這個(gè)參數(shù)后,有一些非必須的依賴將不會(huì)被一起安裝。比如,我們安裝wget時(shí),如果增加這個(gè)選項(xiàng),待安裝的包將從6個(gè)減少為3個(gè):

這在一定程度上縮小了鏡像的大小,但這樣做帶來(lái)的副作用就是,可能導(dǎo)致目標(biāo)軟件缺少一些功能。

比如,此時(shí)的wget將無(wú)法驗(yàn)證服務(wù)器證書的真?zhèn)危瑢?dǎo)致命令出錯(cuò):

所以,我們一般的做法是,使用apt時(shí)盡量增加 --no-install-recommends ,等后面出現(xiàn)一些錯(cuò)誤再及時(shí)糾正。像wget這種已知的問(wèn)題,可以提前預(yù)判并進(jìn)行處理:

apt-get install --no-install-recommends wget ca-certificates

3. 為apt擦屁股

某些工具只有編譯階段使用,我不希望它們占用我寶貴的鏡像容量,就可以在鏡像編譯完成后,將這些中間依賴刪掉。

我們以apt為例,在使用完成后,我們需要做的事情有:

  • 刪除那些 不需要 的依賴: apt-get pruge --autoremove ...
  • 刪除本地的軟件包列表: rm -rf /var/lib/apt/lists/*

這個(gè)過(guò)程中我們會(huì)遇到一個(gè)非常難解的問(wèn)題,究竟哪些依賴是“不需要”的?

比如,在編譯PHP時(shí),我們可能會(huì)用到三個(gè)工具:wget、libxml、gcc。這三個(gè)工具,在編譯PHP前都需要安裝。但是在編譯完成后,我們可以卸載wget和gcc,但不能卸載libxml。

原因是,libxml為PHP所依賴的一個(gè)動(dòng)態(tài)鏈接庫(kù),如果我們將其卸載,將會(huì)出現(xiàn)找不到共享鏈接庫(kù)的錯(cuò)誤:

root@8eab53da8d5b:/# php -v
php: error while loading shared libraries: libxml2.so.2: cannot open shared object file: No such file or directory

那么,有沒(méi)有一個(gè)比較方便的辦法,我自動(dòng)只找出那些不是“共享鏈接庫(kù)”的依賴并刪除他們呢?

當(dāng)然有,比較簡(jiǎn)單的辦法是,我們遍歷剛編譯的可執(zhí)行文件,使用ldd命令列出其依賴的共享鏈接庫(kù)文件名,并在源中搜索這個(gè)文件名對(duì)應(yīng)的包名:

這些包就是PHP依賴的所有動(dòng)態(tài)鏈接庫(kù),接著我們將這些包用 apt-mark 聲明為“手工安裝的包”,即可阻止 apt purge 的自動(dòng)卸載。

然后,我們?cè)僮詣?dòng)卸載其余沒(méi)有用到的包即可。完整shell腳本如下:

find /usr/local -type f -executable -exec ldd '{}' ';' \

 | awk '/=>/ { print $(NF-1) }' \

 | sort -u \

 | xargs -r dpkg-query --search \

 | cut -d: -f1 \

 | sort -u \

 | xargs -r apt-mark manual \

; \

apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false;

4. 盡量將中間依賴的安裝與卸載操作放在一個(gè)步驟中

docker鏡像是一個(gè)由“層”來(lái)堆疊起來(lái)的“千層餅”,我們可以使用 docker history <image name> 這條命令來(lái)查看任意一個(gè)鏡像是由哪些層組成的,以及每一層的大?。?/p>

對(duì)于Dockerfile來(lái)說(shuō),這些層的數(shù)據(jù)都將會(huì)被保存在鏡像中,即使后一層刪除了前一層內(nèi)保存的文件。

比如,我們有如下Dockerfile:

FROM alpine:3.12
RUN truncate -s 50M /sample.dat
RUN rm -rf /sample.dat

我們可以試試看這個(gè)鏡像編譯出來(lái)有多大,58MB:

相比起來(lái),正常的alpine:3.12只有5.57MB,說(shuō)明即使我們已經(jīng)刪除了 /sample.dat 文件,在最后的鏡像中也沒(méi)有這個(gè)內(nèi)容,但是它永遠(yuǎn)留在了鏡像的history中。

所以,在刪除上文說(shuō)到的“中間依賴”時(shí),我們需要將安裝、使用、卸載三個(gè)部分寫在一個(gè)步驟中,才能保證空間被釋放。比如:

FROM debian:buster

RUN apt-get update \

 && apt-get install gcc \

 && gcc ... \

 && apt-get purge --autoremove gcc \

 && rm -rf /var/lib/apt/lists/*

5. 多階段編譯

在Docker 17.05版本以后,新引入了 multi-stage builds 這一概念,這將會(huì)極大地簡(jiǎn)化我們上述的所有操作。

簡(jiǎn)單來(lái)說(shuō),multi-stage builds支持我們將Docker鏡像的編譯分成多個(gè)“階段”。比如常見的軟件編譯的情況,我們可以將編譯階段單獨(dú)提出來(lái),軟件編譯完成后直接將二進(jìn)制文件拷貝到一個(gè)新的基礎(chǔ)鏡像中,這樣做最大的好處就是,第二個(gè)鏡像不再包含任何編譯階段使用的中間依賴,干干凈凈明明白白。

以最常見的Java項(xiàng)目為例,編譯Jar包的時(shí)候,我們需要使用到JDK、Maven等工具,但在實(shí)際運(yùn)行階段,我們只需要JRE環(huán)境即可。簡(jiǎn)單比較下 maven:3-openjdk-8openjdk:8-jre 兩個(gè)鏡像的大小:

差別一倍有余。

以Vulhub中的Shiro 1.2.4環(huán)境為例,在其Dockerfile中可以看到兩個(gè) FROM 命令:

FROM maven:3-jdk-8 AS builder

LABEL MAINTAINER="phithon <root@leavesongs.com>"

COPY ./code/ /usr/src/

WORKDIR /usr/src

RUN cd /usr/src; \

 mvn -U clean package -Dmaven.test.skip=true

FROM openjdk:8u102-jre

LABEL MAINTAINER="phithon <root@leavesongs.com>"

COPY --from=builder /usr/src/target/shirodemo-1.0-SNAPSHOT.jar /shirodemo-1.0-SNAPSHOT.jar

EXPOSE 8080

CMD ["java", "-jar", "/shirodemo-1.0-SNAPSHOT.jar"]

第一個(gè) FROM 用來(lái)進(jìn)入 maven:3-jdk-8 環(huán)境,使用maven對(duì)源碼進(jìn)行編譯;第二個(gè) FROM 進(jìn)入較小的 openjdk:8u102-jre 環(huán)境,使用 COPY --from= 語(yǔ)法,從前一個(gè)階段的編譯結(jié)果中將jar文件復(fù)制到j(luò)re的環(huán)境中。

最后,在機(jī)器上將會(huì)留下兩個(gè)鏡像,一個(gè)是builder,一個(gè)是最終我們需要的那個(gè)shiro 1.2.4的環(huán)境,后者可以被其他任何用戶獨(dú)立使用,而前者可以直接刪除。

對(duì)于使用者來(lái)說(shuō),我們無(wú)需再糾結(jié)編譯軟件時(shí)中間依賴如何刪除才能讓鏡像比較小的問(wèn)題,反正第一階段使用的任何依賴多不會(huì)被遺留到正式的生產(chǎn)環(huán)境中。

但多階段編譯對(duì)于動(dòng)態(tài)鏈接庫(kù)的依賴仍然有上述的問(wèn)題,如果我們拷貝編譯成果時(shí)只拷貝了可執(zhí)行文件,在新環(huán)境下運(yùn)行仍然會(huì)出現(xiàn)找不到共享鏈接庫(kù)的錯(cuò)誤。所以個(gè)人覺(jué)得,多段式編譯僅適合于Java、golang等能夠跨平臺(tái)或靜態(tài)編譯的語(yǔ)言,對(duì)于C、Python這些依賴較多的項(xiàng)目仍然不友好。

6. 使用slim版本的鏡像

細(xì)心的同學(xué)可能注意過(guò),Docker官方的Debian鏡像有個(gè)slim版本,這個(gè)版本的大小比默認(rèn)的版本要小一倍多:

slim的中文意思就是“苗條的”,顧名思義, debian:stretch-slim 確實(shí)苗條的多,原因是其刪除了man文檔等許多不會(huì)在容器里用到的文件。

有一些上層的鏡像會(huì)基于slim版本的debian進(jìn)行編寫,比如python。如果我們開發(fā)python的項(xiàng)目,可以使用 python:slim 這個(gè)基礎(chǔ)鏡像。

總結(jié)一下,六種方法,互相不會(huì)影響,我們可以同時(shí)使用。但第5個(gè),多階段編譯將會(huì)是以后的主流方式。

到此這篇關(guān)于詳解六種減小Docker鏡像大小的方法的文章就介紹到這了,更多相關(guān)減小Docker鏡像大小內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

標(biāo)簽:運(yùn)城 烏海 齊齊哈爾 衡陽(yáng) 拉薩 嘉興 亳州 澳門

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《詳解六種減小Docker鏡像大小的方法》,本文關(guān)鍵詞  詳解,六種,減小,Docker,鏡像,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《詳解六種減小Docker鏡像大小的方法》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于詳解六種減小Docker鏡像大小的方法的相關(guān)信息資訊供網(wǎng)民參考!
  • 企业400电话

    智能AI客服机器人
    15000

    在线订购

    合计11份范本:公司章程+合伙协议+出资协议+合作协议+股权转让协议+增资扩股协议+股权激励+股东会决议+董事会决议

    推薦文章