1. 程式人生 > >Docker容器映象瘦身的三種構建方法整理

Docker容器映象瘦身的三種構建方法整理

 1  基本的構建方式,就是依賴各種資源的傳統方式構建,分層較多,一個容器至少在600M左右,但是各方的依賴工具都很全面,對於除錯,以及內部操作都 相對簡單方便。

2  使用Distroless移除容器中的所有累贅 

目前的映象不僅含有Node.js,還含有yarn、npm、bash以及大量其他二進位制檔案。同時,它是基於Ubuntu的。因此擁有一個完整的作業系統以及所有的二進位制檔案和實用程式。

這些在執行容器時都不是必需的。我們唯一的依賴項是Node.js。

Docker容器應封裝在單一程序中,且只包含執行所需的最精簡內容。我們不需要一個作業系統。

實際上,除了Node.js,其他都可以移除。

那麼要怎麼做呢?

幸運的是,Google也有同樣的想法,他們帶來了GoogleCloudPlatform/distroless[1]。

有如其倉庫說明所述:

“Distroless”映象只包含應用程式及其執行時依賴。不包含包管理器、Shell以及其他標準Linux發行版中能找到的其他程式。

這正是我們所需要的!

我們可以調整Dockerfile檔案來使用這個新的基礎映象:

FROM node:8 as build

WORKDIR /app
COPY package.json index.js ./
RUN npm install

FROM gcr.io/distroless/nodejs

COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]

然後像平常那樣編譯映象:

$ docker build -t node-distroless .

應用程式應能正常執行。要驗證這一點,可以像這樣執行容器:

$ docker run -p 3000:3000 -ti --rm --init node-distroless

訪問http://localhost:3000頁面即可。

這個未包含額外程式的映象會多小呢?

$ docker images | grep node-distroless
node-distroless   7b4db3b7f1e5   76.7MB

僅僅76.76MB!

比前一個映象少了600MB!

真是個好訊息!不過在使用Distroless時有些事項需要注意。

容器執行時,如果想對其進行檢查,可以這麼做:

$ docker exec -ti <替換成_docker_id> bash

上述命令將附加到容器中並執行bash,這與發起一個SSH會話相近。

不過由於Distroless是原始作業系統的精簡版本,不包含額外的程式。容器裡並沒有Shell!

如果沒有Shell,要如何附加到執行的容器中呢?

好訊息和壞訊息是,做不到。

壞訊息是我們只能執行容器中的二進位制程式。這裡能執行的只有Node.js:

$ docker exec -ti <替換成_docker_id> node

好訊息是因為沒有Shell,如果黑客入侵了我們的應用程式並獲取了容器的訪問許可權,他也無法造成太大的損害。也就是說,程式越少則尺寸越小也越安全。不過,代價是除錯更麻煩。

需要注意的是,我們不應該在生產環境中附加到容器中進行除錯,而應依靠正確的日誌和監控。

如果我們既希望能除錯,又關心尺寸大小,又該怎麼辦?

3  使用Alpine作為更小的基礎映象

我們可以使用Alpine取代Distroless來作為基礎映象。

Alpine Linux[2]是:

一個基於musl libc[3]和busybox[4]、面向安全的輕量級Linux發行版。

換言之,它是一個尺寸更小、更安全的Linux發行版。

是否言過其實,我們來檢查一下這個映象是否更小。

修改之前的Dockerfile並使用node:8-alpine:

FROM node:8 as build

WORKDIR /app
COPY package.json index.js ./
RUN npm install

FROM node:8-alpine

COPY --from=build /app /
EXPOSE 3000
CMD ["npm", "start"]

構建該映象:

$ docker build -t node-alpine .

現在看一下它的大小:

$ docker images | grep node-alpine
node-alpine   aa1f85f8e724   69.7MB

69.7MB!

甚至比Distroless映象還要小!

我們來看看能不能附加執行中的容器。

首先,啟動容器:

$ docker run -p 3000:3000 -ti --rm --init node-alpine
Example app listening on port 3000!

現在附加到容器中:

$ docker exec -ti 9d8e97e307d7 bash
OCI runtime exec failed: exec failed: container_linux.go:296: starting container process caused "exec: \"bash\": executable file not found in $PATH": unknown

運氣不佳。但或許容器有sh這個Shell?

$ docker exec -ti 9d8e97e307d7 sh
/ #

很好!我們既可以附加到執行的容器中,得到的映象尺寸也很小。

聽起來很棒,不過有一個小問題。

Alpine基礎映象是基於muslc的,這是一個C的替代標準庫。

但是,多數Linux發行版,比如Ubuntu、Debian及CentOS都是基於glibc的。這兩個庫照理應該實現了相同的介面。

不過,它們的目標不同:

  • glibc最常用,速度更快

  • muslc佔用空間更少,以安全為核心

在編譯應用程式時,多數情況下是使用某個libc來編譯的。如果想在其他libc中使用,只能重新編譯。

也就是說,使用Alpine映象來構建容器可能會造成不可預期的問題,因為使用的是不同的C標準庫。

特別是在處理預編譯的二進位制檔案時,比如Node.js的C++擴充套件,這個差異更明顯。

舉個例子,PhantomJS預置包就無法在Alpine中工作。

怎麼選擇基礎映象?

Alpine、Distroless或是原生映象到底用哪個?

如果是在生產環境中執行,並且注重安全性, Distroless映象可能會更合適。

Docker映象中每增加一個二進位制程式,就會給整個應用程式帶來一定的風險。

在容器中只安裝一個二進位制程式即可降低整體風險。

舉個例子,如果黑客在運行於Distroless的應用中發現了一個漏洞,他也無法在容器中建立Shell,因為根本就沒有。

注意:最小化攻擊面是OWASP的推薦做法[5]。

如果更在意要是大小,則可以換成Alpine基礎映象。

這兩個都很小,代價是相容性。Alpine用了一個稍稍有點不一樣的C標準庫——muslc。時不時會碰到點相容性的問題。比如這個[6]和這個[7]。

原生基礎映象非常適合用於測試和開發。

它的尺寸比較大,不過用起來就像你主機上安裝的Ubuntu一樣。並且,你能訪問該作業系統裡有的所有二進位制程式。

下面,回顧一下各個映象大小:

node:8 681MB

node:8結合多階段構建 678MB

gcr.io/distroless/nodejs 76.7MB

node:8-alpine 69.7MB

相關連結:

  1. https://github.com/GoogleCloudPlatform/distroless

  2. https://alpinelinux.org

  3. https://www.musl-libc.org

  4. https://www.busybox.net

  5. https://www.owasp.org/index.php/Minimize_attack_surface_area

  6. https://github.com/grpc/grpc/issues/8528

  7. https://github.com/grpc/grpc/issues/6126