[TOC]linux
筆者在此以前徹底沒有在乎過 Docker 鏡像的大小...git
(或許 副標題能夠取成 《如何將你的 Docker 鏡像大小縮減 99%!!XD》:joy: 太羞恥了233 )github
在書寫 Dockerfile 構建鏡像並放到 Kubernetes 中運行以後, 發現 Worker Node
的磁盤容量被消耗的很快, 發現是 Pod 內的 Docker Container
過大致使. 一個僅僅 用於運行 micro/micro
的 Micro Api 的容器佔地面積 1G+
...., 讓人有些汗顏, 遂開始嘗試 對容器的 Image 進行瘦身.算法
以下面這個不作任何清理的 DockerFile 所示, 讓咱們看看構建後, 構建出來的 Image 佔多少空間docker
FROM alpine:latest
USER root
RUN apk update && apk add go git musl-dev
# 添加 go 環境變量
RUN echo "export PATH=\$PATH:/root/go/bin" >> /etc/profile \ && echo "export GO111MODULE=on" >> /etc/profile \ && echo "export GOPATH=/root/go" >> /etc/profile \ && echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> /etc/profile \ && source /etc/profile
# 安裝 micro
RUN cd \ && go get -u -v github.com/micro/micro \ && go install github.com/micro/micro
EXPOSE 8080
CMD ["micro","api"] 複製代碼
構建 Docker Imageapi
$ docker build . --tag=micro:v1.01
# 查看鏡像大小
$ docker image ls | grep micro
micro v1.01 2b1330a05f90 27 seconds ago 948MB
複製代碼
ok, 構建出來的鏡像佔地面積 948 MB
, 接近 1G 的體積. 可是 go 編譯的二進制文件頂多才幾十 M 纔對鴨, 必定是有一些沒必要要的佔用, 那麼接下來咱們進入容器看看主要的空間佔用集中在哪些位置緩存
$ docker run -it --rm micro:v1.01 /bin/sh
# 由於 busybox 的 du 命令版本較低, 這裏使用 ncdu
/ apk add ncdu
/ ncdu
複製代碼
而後咱們能夠觀察到 大小主要集中在bash
簡單分析一下, 主要是咱們上面 apk 命令安裝的軟件和 lib , 以及 go 編譯 micro 所需的依賴.工具
實際上咱們編譯獲得 Micro 二進制文件徹底不須要這些就能運行.性能
因此咱們改造一下上面的 Dockerfile , 把 apk 安裝的軟件以及 Go 的依賴所有刪掉, 以縮小鏡像體積
FROM alpine:latest
USER root
# 添加 go 環境變量
RUN echo "export PATH=\$PATH:/root/go/bin" >> /etc/profile \ && echo "export GO111MODULE=on" >> /etc/profile \ && echo "export GOPATH=/root/go" >> /etc/profile \ && echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> /etc/profile \ && source /etc/profile
# 安裝 micro
RUN apk update && apk add go git musl-dev \ && cd \ && go get -u -v github.com/micro/micro \ && go install github.com/micro/micro \ && apk del go git musl-dev \ ; rm /root/go/pkg -rf \ ; rm /root/go/src -rf \ ; rm /root/.cache -rf \ ; rm -rf /var/cache/apk/* /tmp/*
EXPOSE 8080
CMD ["micro", "api"] 複製代碼
咱們構建看看效果.
$ docker build . --tag=micro:v1.02
# 查看鏡像大小
$ docker image ls | grep micro
micro v1.02 03e65f4ba3ed 8 seconds ago 44.1MB
複製代碼
此次的優化效果顯著 , 構建後的鏡像大小 44.1MB
, 僅爲原來的 4.6% ! 第一階段工做完成!
PS 這裏有個小插曲, 第一次優化的時候犯傻.... 我把 dockerfile 寫成了這樣...
FROM alpine:latest
USER root
## 安裝 軟件環境
RUN apk update && apk add go git musl-dev
# 添加 go 環境變量 並 安裝 micro
RUN echo "export PATH=\$PATH:/root/go/bin" >> /etc/profile \ && echo "export GO111MODULE=on" >> /etc/profile \ && echo "export GOPATH=/root/go" >> /etc/profile \ && echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> /etc/profile \ && source /etc/profile \ && go get -u -v github.com/micro/micro \ && go install github.com/micro/micro \ # 刪除沒必要要的東西
RUN cd \ && && apk del go git musl-dev \
; rm /root/go/pkg -rf \
; rm /root/go/src -rf \
; rm /root/.cache -rf \
; rm -rf /var/cache/apk/* /tmp/*
EXPOSE 8080
CMD ["micro", "api"] 複製代碼
咋眼一看可能感受沒啥問題, 邏輯清晰明瞭, 可是構建以後, 鏡像體積徹底沒有縮小, 那麼問題出如今呢?
問題就出如今這清晰明瞭上 :joy:, 由以下兩個緣由組合而成:
- Docker 使用 aufs, 也就是聯合文件系統
- Dockerfile 中, 一個 命令 即爲一層
那麼也就是說, 在這個 Dockerfile 中, 咱們妄圖在下面的層刪除上面的層的東西, 這樣固然是刪不到的: joy:
在上一階段中, 咱們將鏡像體積控制到 44+MB
, 效果不錯. 可是有個很麻煩且不優雅的地方. 就是咱們每次寫 Dockerfile 難道都要這樣手動刪除安裝的東西嗎?!! .
答案是 否認
的.
在 Docker 17.05
版本以後, Docker 支持了 多階段構建 (multistage builds)
, 容器僅僅保存最後一個階段構建的內容, 咱們在前面的階段能夠隨便寫, 安裝十個軟件寫十一個 Run apk add foo
都行 :joy:(開玩笑的). 因而咱們就把 Dockerfile 改形成了這樣.
FROM alpine:latest
USER root
# 添加 go 環境變量 和 alpine 鏡像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \ && echo "export GO111MODULE=on" >> /etc/profile \ && echo "export GOPATH=/root/go" >> /etc/profile \ && echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> /etc/profile \ && source /etc/profile
# 安裝 micro
RUN apk update && apk add go git musl-dev xz binutils \ && cd \ && go get -u -v github.com/micro/micro \ && go install github.com/micro/micro
#1 ----------------------------
FROM alpine:latest
COPY --from=0 /root/go/bin/micro /usr/local/bin/
EXPOSE 8080
CMD ["micro", "api"] 複製代碼
讓咱們構建鏡像驗證一下
$ docker build . --tag=micro:v1.03
# 查看鏡像大小
$ docker image ls | grep micro
micro v1.03 8529f0d7aaca 7 seconds ago 43.7MB
複製代碼
能夠看到看到, 容器體積和前面差很少, 甚至更小了.
這樣能基本達到咱們的預期, 可是還有沒有辦法變得更小呢, 固然是有的!!!!
如下方法針對 Go 這種編譯型語言, PHP/Python 這種動態語言可能須要用其餘思路.
通過 Google 的檢索, 咱們知道了 strip
和 upx
兩種工具.
這裏簡單介紹一下 strip 和 upx
strip 經過刪除可執行文件中 ELF 頭的 typchk 段、符號表、字符串表、行號信息、調試段、註解段、重定位信息等來實現縮減程序體積的目的。簡而言之, 你能夠直譯理解成給程序脫衣服...
UPX 原本是一個 BIN 文件加殼器, 可是他有一種 叫作 UCL 的壓縮算法, 能夠進一步減少體積, 運行時先在內存中解壓, 對性能影響很是小 (反作用是 會增長程序的啓動時間, 建議謹慎使用)
咱們接下來把 strip + upx 應用到咱們的 Dockerfile 中.
FROM alpine:latest
USER root
# 添加 go 環境變量 和 alpine 鏡像
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \ && echo "export GO111MODULE=on" >> /etc/profile \ && echo "export GOPATH=/root/go" >> /etc/profile \ && echo "export GOPROXY=https://mirrors.aliyun.com/goproxy/" >> /etc/profile \ && source /etc/profile
# 安裝 micro
RUN apk update && apk add go git musl-dev xz binutils \ && cd \ && go get -u -v github.com/micro/micro \ && go install github.com/micro/micro
# 壓縮 和 加殼
RUN wget https://github.com/upx/upx/releases/download/v3.95/upx-3.95-amd64_linux.tar.xz \ && xz -d upx-3.95-amd64_linux.tar.xz \ && tar -xvf upx-3.95-amd64_linux.tar \ && cd upx-3.95-amd64_linux \ && chmod a+x ./upx \ && mv ./upx /usr/local/bin/ \ && cd /root/go/bin \ && strip --strip-unneeded micro \ && upx micro \ && chmod a+x ./micro \ && cp micro /usr/local/bin
#1 ----------------------------
FROM alpine:latest
COPY --from=0 /usr/local/bin/micro /usr/local/bin/
EXPOSE 8080
CMD ["micro", "api"] 複製代碼
讓咱們構建鏡像看看效果
$ docker build . --tag=micro:v1.04
# 查看鏡像大小
$ docker image ls | grep micro
micro v1.04 1d22ac38352c 5 seconds ago 14.2MB
複製代碼
鏡像的體積進一步降低到 14.2 MB, 讓咱們進入 容器看看 編譯出來的 micro 二進制文件大小,
$ docker run -it --rm micro:v1.04 /bin/sh
/ ls -hal /usr/local/bin/micro
-rwxr-xr-x 1 root root 8.2M Aug 12 07:59 micro
複製代碼
縮小到了 8.2M, 那麼讓咱們算一下, 還有那些多餘的體積.
使用 docker image 命令看看咱們的基礎鏡像 Alpine 的大小
$ docker image ls | grep alpine
alpine latest b7b28af77ffe 5 weeks ago 5.58MB
複製代碼
8.2 + 5.58 = 13.78 MB
, 也就是 還有 14.2 - 13.78 = 0.42 MB
的多餘佔用, 咱們已經基本接近極致. 優化工做能夠到此基本告一段落.
到目前爲止, 咱們將容器鏡像體積從一開始的 948MB 降到 14+MB, 減小了將近 98.5% :joy: 的鏡像體積, 可喜可賀!
可是咱們也看到還有 0.42MB 的多餘佔用等待着咱們去優化, 多是 計算錯誤 或者是對 Docker 的 多階段構建以及 aufs 的理解不夠到位, 致使認爲有多餘的佔用空間, 這即是未了的事.
(我也想像 Docker 鏡像同樣,瘦的這麼快鴨:joy:)
原文首發於 kuricat.com
歡迎到 Kuri-su/KBlog 開 Issue 討論