歡迎光臨
每天分享高質量文章

Docker鏡像瘦身與優化

為什麼在儲存如此便宜的今天我們仍然需要對Docker鏡像進行瘦身?
小鏡像的優點

 

加速構建/部署。雖然儲存資源較為廉價,但是網絡IO是有限的,在帶寬有限的情況下,部署一個1G的鏡像和10M的鏡像帶來的時間差距可能就是分鐘級和秒級的差距。特別是在出現故障,服務被調度到其他節點時,這個時間尤為寶貴。
提高安全性,減少攻擊面積。越小的鏡像表示無用的程式越少,可以大大的減少被攻擊的標的。
減少儲存開銷。
小鏡像的製作原則

 

選用最小的基礎鏡像
減少層,去除非必要的檔案
在實際製作鏡像的過程中,一味的合併層不可取,需要學會充分的利用Docker的快取機制,提取公共層,加速構建。
  • 依賴檔案和實際的代碼檔案單獨分層

  • 團隊/公司採用公共的基礎鏡像等

使用多階段構建
往往我們在構建階段和實際運行階段需要的依賴環境是不同的,例如Golang編寫的程式實際運行的時候僅僅需要一個二進制檔案即可,對於Node來說,可能最後運行的只是一些打包之後的js檔案而不需要包含node_modules里成千上萬的依賴。
基礎鏡像

 

Distroless,https://github.com/GoogleCloudPlatform/distroless
“Distroless” images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.
Distroless是Google推出的一個僅僅包含運行時環境,不包含包管理器,shell等其他程式。如果你的程式沒有其他依賴的話,這是一個不錯的選擇。
Alpine,https://hub.docker.com/_/alpine
Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.
Alpine 是一個基於MUSL,Busybox的安全的Linux發行版。麻雀雖小五臟俱全,雖然不到10M, 但是包含了一個包管理器和shell環境,這在我們實際的使用除錯當中將非常有用。
但是請註意,由於Alpine使用了更小的muslc替代glibc,會導致某些應用無法使用,需要重新編譯。
Scratch,https://hub.docker.com/_/scratch
Scratch是空白鏡像,一般用於基礎鏡像構建,例如Alpine鏡像的Dockerfile便是從Scratch開始的:
  1. FROM scratch
  2. ADD alpine-minirootfs-20190228-x86_64.tar.gz /
  3. CMD ["/bin/sh"]
Busybox,https://hub.docker.com/_/busybox
一般而言,Distroless相對會更加的安全,但是在實際使用的過程中可能會遇到添加依賴以及除錯方面的問題,alpine更小,自帶包管理器,更加貼合使用習慣,但是muslc可能會帶來兼容性的問題,一般而言我會選擇alpine作為基礎鏡像使用。
除此之外,在Docker Hub當中我們可以發現常用的Debian的鏡像也會提供的只包含基礎功能的小鏡像。
基礎鏡像對比
此處直接拉取基礎鏡像,查看鏡像大小, 通過觀察我們可以發現,alpine只有5M左右為debian的20分之一:
  1. alpine latest 5cb3aa00f899 3 weeks ago 5.53MB
  2. debian latest 0af60a5c6dd0 3 weeks ago 101MB
  3. ubuntu 18.04 47b19964fb50 7 weeks ago 88.1MB
  4. ubuntu latest 47b19964fb50 7 weeks ago 88.1MB
  5. alpine 3.8 3f53bb00af94 3 months ago 4.41MB
似乎從上面看,感覺差距不大,實踐中,不同語言的基礎鏡像都會提供一些採用不同基礎鏡像製作的tag,下麵我們以Ruby的鏡像為例,查看不同基礎鏡像的差異。可以看到預設的latest鏡像881MB而alpine僅僅只有不到50MB這個差距就十分的可觀了:
  1. ruby latest a5d26127d8d0 4 weeks ago 881MB
  2. ruby alpine 8d8f7d19d1fa 4 weeks ago 47.8MB
  3. ruby slim 58dd4d3c99da 4 weeks ago 125MB
減少層,去除非必要的檔案

 

刪除檔案不要跨行
  1. # dockerfile 1
  2. FROM alpine
  3. RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip && rm 1.0.0.zip
  4. # dockerfile 2
  5. FROM alpine
  6. RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip
  7. RUN rm 1.0.0.zip
  8. # dockerfile 3
  9. FROM alpine
  10. RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip && rm 1.0.0.zip
  1. test 3 351a80e99c22 5 seconds ago 5.53MB
  2. test 2 ad27e625b8e5 49 seconds ago 6.1MB
  3. test 1 165e2e0df1d3 About a minute ago 6.1MB
可以發現1,2兩個大小一樣,但是3小了0.5MB,這是因為Docker幾乎每一行命令都會生成一個層,刪除檔案的時候:因為底下各層都是只讀的,當需要刪除這些層中的檔案時,AUFS 使用whiteout機制,它的實現是通過在上層的可寫的目錄下建立對應的whiteout隱藏檔案來實現的,所以在當前層去刪除上一層的檔案,只是會把這個檔案隱藏掉罷了。
使用單行命令
除了刪除陳述句需要放在一行以外,由於層的機制,我們安裝依賴的一些公共的陳述句最好也使用條RUN命令生成,減少最終的層數。
分離依賴包,以及原始碼程式,充分利用層的快取
這是一個最佳實踐,在實際的開發過程中,我們的依賴包往往是變動不大的,但是我們正在開發的原始碼的變動是較為頻繁,如果我們實際的代碼只有10M,但是依賴項有1G,如果在COPY的時候直接 COPY..,會導致每次修改代碼都會使這一層的快取失效,導致浪費複製以及推送到鏡像倉庫的時間,將COPY陳述句分開,每次push就可以只變更我們頻繁修改的代碼層,而不是連著依賴一起。
使用.dockerignore
在使用Git時,我們可以通過.gitignore忽略檔案,在docker build的時候也可以使用.dockerignore在Docker背景關係中忽略檔案,這樣不僅可以減少一些非必要檔案的匯入,也可以提高安全性,避免將一些配置檔案打包到鏡像中。
多階段構建

 

多階段構建其實也是減少層的一種,通過多階段構建,最終鏡像可以僅包含最後生成的可執行檔案,和必須的運行時依賴,大大減少鏡像體積。
以Go語言為例,實際運行的過程中只需要最後編譯生成的二進制檔案即可,而Go語言本省以及擴展包,代碼檔案都是不必要的,但是我們在編譯的時候這些依賴又是必須的,這時候就可以使用多階段構建的方式,減少最終生成的鏡像體積。
  1. # 使用golang鏡像作為builder鏡像
  2. FROM golang:1.12 as builder
  3. WORKDIR /go/src/github.com/go/helloworld/
  4. COPY app.go .
  5. RUN go build -o app .
  6. # 編譯完成之後使用alpine鏡像作為最終的基礎鏡像
  7. FROM alpine:latest as prod
  8. RUN apk --no-cache add ca-certificates
  9. WORKDIR /root/
  10. # 從builder中複製編譯好的二進制檔案
  11. COPY --from=builder /go/src/github.com/go/helloworld/app .
  12. CMD ["./app"]
由於本文篇幅較長,這裡不對多階段構建展開講解,詳情可以參考多階段構建[1]。
奇淫技巧

 

使用dive[2]查看Docker鏡像的層,可以幫助你分析減少鏡像體積。
使用docker-slim[3]可以自動幫助你減少鏡像體積,對於Web應用較為有用。
安裝軟體時去除依賴:
  1. # ubuntu
  2. apt-get install -y — no-install-recommends
  3. #alpine
  4. apk add --no-cache && apk del build-dependencies
  5. # centos
  6. yum install -y ... && yum clean all
使用 –flatten引數,減少層(不推薦)。
使用docker-squash[4]壓縮層。
不同語言的示例

 

Ruby(Rails)
只安裝生產所需的依賴。
刪除不需要的依賴檔案:
  1. bundle install --without development:test:assets -j4 --retry 3 --path=vendor/bundle \
  2. # Remove unneeded files (cached *.gem, *.o, *.c)
  3. && rm -rf vendor/bundle/ruby/2.5.0/cache/*.gem \
  4. && find vendor/bundle/ruby/2.5.0/gems/ -name "*.c" -delete \
  5. && find vendor/bundle/ruby/2.5.0/gems/ -name "*.o" -delete
刪除前端的node_modules以及快取檔案:
  1. rm -rf node_modules tmp/cache app/assets vendor/assets spec
上述內容可以結合多階段構建實現。
Golang
Golang在使用多階段構建之後,只剩下了一個二進制檔案,這時候再要優化,就只有使用upx之類的工具壓縮二進制檔案的體積了。
添加中……
相關鏈接:
  1. https://yeasy.gitbooks.io/docker_practice/image/multistage-builds/#%E5%A4%9A%E9%98%B6%E6%AE%B5%E6%9E%84%E5%BB%BA

  2. https://github.com/wagoodman/dive

  3. https://github.com/docker-slim/docker-slim

  4. https://github.com/jwilder/docker-squash

原文鏈接:https://lailin.xyz/post/notes/docker%E9%95%9C%E5%83%8F%E7%98%A6%E8%BA%AB/

已同步到看一看
赞(0)

分享創造快樂