docker鏡像瘦身&優化

原文首發於個人博客: lailin.xyz/post/51252.…html

爲何在存儲如此便宜的今天咱們仍然須要對 Docker 鏡像進行瘦身?前端

小鏡像的優勢

  1. 加速構建/部署node

    雖然存儲資源較爲廉價,可是網絡 IO 是有限的,在帶寬有限的狀況下,部署一個 1G 的鏡像和 10M 的鏡像帶來的時間差距可能就是分鐘級和秒級的差距。特別是在出現故障,服務被調度到其餘節點時,這個時間尤其寶貴。linux

  2. 提升安全性,減小攻擊面積git

    越小的鏡像表示無用的程序越少,能夠大大的減小被攻擊的目標github

  3. 減小存儲開銷golang

小鏡像的製做原則

  1. 選用最小的基礎鏡像docker

  2. 減小層,去除非必要的文件shell

    在實際製做鏡像的過程當中,一味的合併層不可取,須要學會充分的利用 Docker 的緩存機制,提取公共層,加速構建。ubuntu

    • 依賴文件和實際的代碼文件單獨分層
    • 團隊/公司採用公共的基礎鏡像等
  3. 使用多階段構建

    每每咱們在構建階段和實際運行階段須要的依賴環境是不一樣的,例如golang編寫的程序實際運行的時候僅僅須要一個二進制文件便可,對於Node來講,可能最後運行的只是一些打包以後的js文件而不須要包含node_modules裏成千上萬的依賴

基礎鏡像

  • 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

    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

    scratch 是空白鏡像,通常用於基礎鏡像構建,例如alpine鏡像的dockerfile即是從scratch開始的

    FROM scratch
    ADD alpine-minirootfs-20190228-x86_64.tar.gz / CMD ["/bin/sh"] 複製代碼
  • busybox

通常而言,distroless相對會更加的安全,可是在實際使用的過程當中可能會遇到添加依賴以及調試方面的問題,alpine更小,自帶包管理器,更加貼合使用習慣,可是muslc可能會帶來兼容性的問題,通常而言我會選擇alpine做爲基礎鏡像使用。

除此以外,在Docker Hub當中咱們能夠發現經常使用的Debian的鏡像也會提供的只包含基礎功能的小鏡像

基礎鏡像對比

此處直接拉取基礎鏡像,查看鏡像大小, 經過觀察咱們能夠發現,alpine只有 5M 左右爲debian的 20 分之一

alpine      latest    5cb3aa00f899        3 weeks ago         5.53MB
debian      latest    0af60a5c6dd0        3 weeks ago         101MB
ubuntu      18.04     47b19964fb50        7 weeks ago         88.1MB
ubuntu      latest    47b19964fb50        7 weeks ago         88.1MB
alpine      3.8       3f53bb00af94        3 months ago        4.41MB
複製代碼

彷佛從上面看,感受差距不大,實踐中,不一樣語言的基礎鏡像都會提供一些採用不一樣基礎鏡像製做的 tag,下面咱們以ruby的鏡像爲例,查看不一樣基礎鏡像的差別。能夠看到默認的 latest 鏡像881MBalpine僅僅只有不到50MB這個差距就十分的可觀了

ruby   latest   a5d26127d8d0        4 weeks ago         881MB
ruby   alpine   8d8f7d19d1fa        4 weeks ago         47.8MB
ruby   slim     58dd4d3c99da        4 weeks ago         125MB
複製代碼

減小層,去除非必要的文件

  1. 刪除文件不要跨行
# dockerfile 1
FROM alpine

RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip 
# dockerfile 2
FROM alpine

RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip RUN rm 1.0.0.zip 
# dockerfile 3
FROM alpine

RUN wget https://github.com/mohuishou/scuplus-wechat/archive/1.0.0.zip && rm 1.0.0.zip 複製代碼
test   3  351a80e99c22        5 seconds ago        5.53MB
test   2  ad27e625b8e5        49 seconds ago       6.1MB
test   1  165e2e0df1d3        About a minute ago   6.1MB
複製代碼

能夠發現 1,2 兩個大小同樣,可是 3 小了 0.5MB,這是由於 docker 幾乎每一行命令都會生成一個層,刪除文件的時候:由於底下各層都是隻讀的,當須要刪除這些層中的文件時,AUFS 使用 whiteout 機制,它的實現是經過在上層的可寫的目錄下創建對應的 whiteout 隱藏文件來實現的,因此在當前層去刪除上一層的文件,只是會把這個文件隱藏掉罷了

  1. 使用單行命令

除了刪除語句須要放在一行之外,因爲層的機制,咱們安裝依賴的一些公共的語句最好也使用條RUN命令生成,減小最終的層數

  1. 分離依賴包,以及源代碼程序,充分利用層的緩存

    這是一個最佳實踐,在實際的開發過程當中,咱們的依賴包每每是變更不大的,可是咱們正在開發的源碼的變更是較爲頻繁,若是咱們實際的代碼只有10M,可是依賴項有1G, 若是在COPY的時候直接COPY . .會致使每次修改代碼都會時這一層的緩存失效,致使浪費複製以及推送到鏡像倉庫的時間,將 COPY 語句分開,每次 push 就能夠只變動咱們頻繁修改的代碼層,而不是連着依賴一塊兒

  2. 使用.dockerignore

    在使用Git時,咱們能夠經過.gitignore忽略文件,在 docker build 的時候也可使用.dockerignore在 Docker 上下文中忽略文件,這樣不只能夠減小一些非必要文件的導入,也能夠提升安全性,避免將一些配置文件打包到鏡像中

多階段構建

多階段構建其實也是減小層的一種,經過多階段構建,最終鏡像能夠僅包含最後生成的可執行文件,和必須的運行時依賴,大大減小鏡像體積。

GO語言爲例,實際運行的過程當中只須要最後編譯生成的二進制文件便可,而GO語言本省以及擴展包,代碼文件都是沒必要要的,可是咱們在編譯的時候這些依賴又是必須的,這時候就可使用多階段構建的方式,減小最終生成的鏡像體積

# 使用golang鏡像做爲builder鏡像
FROM golang:1.12 as builder

WORKDIR /go/src/github.com/go/helloworld/ 
COPY app.go . 
RUN go build -o app . 
# 編譯完成以後使用alpine鏡像做爲最終的基礎鏡像
FROM alpine:latest as prod

RUN apk --no-cache add ca-certificates 
WORKDIR /root/ 
# 從builder中複製編譯好的二進制文件
COPY --from=builder /go/src/github.com/go/helloworld/app . 
CMD ["./app"] 複製代碼

因爲本文篇幅較長,這裏不對多階段構建展開講解,詳情能夠參考多階段構建

奇淫技巧

  1. 使用dive查看 docker 鏡像的層,能夠幫助你分析減小鏡像體積

  2. 使用docker-slim 能夠自動幫助你減小鏡像體積,對於 Web 應用較爲有用

  3. 安裝軟件時去除依賴

# ubuntu
apt-get install -y — no-install-recommends

#alpine
apk add --no-cache &&  apk del build-dependencies

# centos
yum install -y ... && yum clean all
複製代碼
  1. 使用--flatten參數,減小層(不推薦)

  2. 使用docker-squash壓縮層

不一樣語言的示例

添加中......

Ruby(Rails)

  1. 只安裝生產所需的依賴

  2. 刪除不須要的依賴文件

bundle install --without development:test:assets -j4 --retry 3 --path=vendor/bundle \
    # Remove unneeded files (cached *.gem, *.o, *.c)
    && rm -rf vendor/bundle/ruby/2.5.0/cache/*.gem \
    && find vendor/bundle/ruby/2.5.0/gems/ -name "*.c" -delete \
    && find vendor/bundle/ruby/2.5.0/gems/ -name "*.o" -delete
複製代碼
  1. 刪除前端的node_modules以及緩存文件
rm -rf node_modules tmp/cache app/assets vendor/assets spec
複製代碼

上述內容能夠結合多階段構建實現

Golang

Golang 在使用多階段構建以後,只剩下了一個二進制文件,這時候再要優化,就只有使用upx之類的工具壓縮二進制文件的體積了

參考資料

  1. Docker 容器鏡像瘦身的三個小竅門
  2. 基礎鏡像 | 再談 Docker 瘦身
  3. Docker —— 從入門到實踐這是一本很不錯的 Docker 開源書
  4. Docker 基本原理簡析
  5. Ruby on Rails — Smaller docker images

License

相關文章
相關標籤/搜索