目錄
介紹
鏡像層(Layers)
製作步驟
lab-1:初始化構建 Redis 鏡像
lab-2:優化基礎鏡像
lab-3:串聯 Dockerfile 指令
lab-4:壓縮你的鏡像
lab-5:使用最精簡的 base image
lab-6:提取動態連結的 .so 文件
lab-7:為 Go 應用構建精簡鏡像
總結
介紹
前段時間網易蜂巢曾經推出蜂巢 Logo T恤,用的正是 Docker 鏡像製作,最神奇的是,它最終的鏡像大小只有 585 字節。
$ docker images | grep hub.c.163.com/public/logo
REPOSITORY TAG IMAGE ID CREATED SIZE
hub.c.163.com/public/logo latest 6fbdd13cd204 11 days ago 585 B有些鏡像都不是我們自己來打包的(比如下載公共鏡像),那是否有一些通用的精簡 Docker 鏡像的手段呢?答案是肯定的,甚至有的鏡像可以精簡 98%。精簡鏡像大小的好處不言而喻,既節省了存儲空間,又能節省帶寬,加快傳輸等。那好,接下來就請跟隨我來學習怎麼製作精簡 Docker 鏡像。
鏡像層(Layers)在開始製作鏡像之前,首先了解下鏡像的原理,而這其中最重要的概念就是鏡像層(Layers)。鏡像層依賴於一系列的底層技術,比如文件系統(filesystems)、寫時複製(copy-on-write)、聯合掛載(union mounts)等,幸運的是你可以在很多地方學習到這些技術[1],這裡就不再贅述技術細節。
總的來說,你最需要記住這點:
在 Dockerfile 中, 每一條指令都會創建一個鏡像層,繼而會增加整體鏡像的大小。舉例來說:
FROM busybox
RUN mkdir /tmp/foo
RUN dd if=/dev/zero of=/tmp/foo/bar bs=1048576 count=100
RUN rm /tmp/foo/bar以上 Dockerfile 幹了幾件事:
基於一個官方的基礎鏡像 busybox(只有1M多)創建一個文件夾(/tmp/foo)和一個文件(bar),該文件分配了100M大小實際上它最終什麼也沒做,我們把它構建成鏡像(構建可以參考一期[2]):
docker build -t busybox:test .再讓我們來對比下原生的 busybox 鏡像大小和我們生成的鏡像大小:
$ docker images | grep busybox
busybox test 896c63dbdb96 2 seconds ago 106 MB
busybox latest 2b8fd9751c4c 9 weeks ago 1.093 MB出乎意料的是,卻生成了 106 MB 的鏡像。
多出了 100 M,這是為何?這點和 Git 類似(都用到了Copy-On-Write技術),我用 git 做了如下兩次提交(添加了又刪除),請問 A_VERY_LARGE_FILE 還在 git 倉庫中嗎?
$ git add A_VERY_LARGE_FILE
$ git commit
$ git rm A_VERY_LARGE_FILE
$ git commit答案是:在的,並且會佔用倉庫的大小。Git 會保存每一次提交的文件版本,而 Dockerfile 中每一條指令都可能增加整體鏡像的大小,即使它最終什麼事情都沒做。
製作步驟了解了鏡像層知識,有助於我們接下來製作精簡鏡像。這裡開始,以最常用的開源緩存軟體 Redis 為例,從一步步試驗,來介紹如何製作更精簡的 Docker 鏡像。
lab-1:初始化構建 Redis 鏡像直接上 Dockerfile :
FROM ubuntu:trusty
ENV VER 3.0.0
ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz
# ==> Install curl and helper tools...
RUN apt-get update
RUN apt-get install -y curl make gcc
# ==> Download, compile, and install...
RUN curl -L $TARBALL | tar zxv
WORKDIR redis-$VER
RUN make
RUN make install
#...
# ==> Clean up...
WORKDIR /
RUN apt-get remove -y --auto-remove curl make gcc
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/* /redis-$VER
#...
CMD ["redis-server"]結合注釋,讀起來並不困難,用到的都是常規的幾個命令,簡要介紹如下:
FROM:頂頭寫,指定一個基礎鏡像,此處基於 ubuntu:trustyENV:設置環境變量,這裡設置了 VER 和 TARBALL 兩個環境變量RUN:最常用的 Dockerfile 指令,用於運行各種命令,這裡調用了 8 次 RUN 指令CMD:指定鏡像默認執行的命令,此處默認執行 redis-server 命令來啟動 redis執行構建:
$ docker build -t redis:lab-1 .註:國內網絡,更新下載可能會較慢
查看大小:
LabiamgeBaseLang.red[*]Size (MB) Memo1redisubuntuCdyn347.3 base ubuntu動輒就有 300多 M 的大小,不能忍,下面我們開始一步步優化。
lab-2:優化基礎鏡像精簡1:選用更小的基礎鏡像。
常用的 Linux 系統鏡像一般有 ubuntu、centos、debian,其中debian 更輕量,而且夠用,對比如下:
REPOSITORY TAG IMAGE ID VIRTUAL SIZE
- -- --
centos 7 214a4932132a 215.7 MB
centos 6 f6808a3e4d9e 202.6 MB
ubuntu trusty d0955f21bf24 188.3 MB
ubuntu precise 9c5e4be642b7 131.9 MB
debian jessie 65688f7c61c4 122.8 MB
debian wheezy 1265e16d0c28 84.96 MB替換 debian:jessie 作為我們的基礎鏡像。
優化 Dockerfile:
FROM debian:jessie
#...執行構建:
$ docker build -t redis:lab-2 .查看大小:
LabimageBaseLang.red[*]Size (MB) Memo01redisubuntuCdyn347.3 base ubuntu02redisdebianCdyn305.7 base debian減少了42M,稍有成效,但並不明顯。細心的同學應該發現,只有 122 MB 的 debian 基礎鏡像,構建後增加到了 305 MB,看來這裡面肯定有優化的空間,如何優化就要用到我們開頭說到的 Image Layer 知識了。
lab-3:串聯 Dockerfile 指令精簡2:串聯你的 Dockerfile 指令(一般是 RUN 指令)。
Dockerfile 中的 RUN 指令通過 && 和 / 支持將命令串聯在一起,有時能達到意想不到的精簡效果。
優化 Dockerfile:
FROM debian:jessie
ENV VER 3.0.0
ENV TARBALL http://download.redis.io/releases/redis-$VER.tar.gz
RUN echo "==> Install curl and helper tools..." && \
apt-get update && \
apt-get install -y curl make gcc && \
\
echo "==> Download, compile, and install..." && \
curl -L $TARBALL | tar zxv && \
cd redis-$VER && \
make && \
make install && \
...
echo "==> Clean up..." && \
apt-get remove -y --auto-remove curl make gcc && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /redis-$VER
#...
CMD ["redis-server"]構建:
$ docker build -t redis:lab-3 .查看大小:
LabImageBaseLang.red[*]Size (MB) Memo01redisubuntuCdyn347.3 base ubuntu02redisdebianCdyn305.7 base debian03redisdebianCdyn151.4 cmd chaining哇!一下子減少了 50%,效果明顯啊!這是最常用的一個精簡手段了。
lab-4:壓縮你的鏡像優化3:試著用命令或工具壓縮你的鏡像。
docker 自帶的一些命令還能協助壓縮鏡像,比如 export 和 import
$ docker run -d redis:lab-3
$ docker export 71b1c0ad0a2b | docker import - redis:lab-4但麻煩的是需要先將容器運行起來,而且這個過程中你會丟失鏡像原有的一些信息,比如:導出埠,環境變量,默認指令。
所以一般通過命令行來精簡鏡像都是實驗性的,那麼這裡再推薦一個小工具:docker-squash[3]。用起來更簡單方便,並且不會丟失原有鏡像的自帶信息。
下載安裝:
https://github.com/jwilder/docker-squash#installation
壓縮操作:
$ docker save redis:lab-3 \
| sudo docker-squash -verbose -t redis:lab-4 \
| docker load註:該工具在 Mac 下並不好使,請在 Linux 下使用
對比大小:
LabImageBasePL.red[*]Size (MB) Memo01redisubuntuCdyn347.3 base ubuntu02redisdebianCdyn305.7 base debian03redisdebianCdyn151.4 cmd chaining04redisdebianCdyn151.4 docker-squash好吧,從這裡看起來並沒有太大作用,所以我只能說試著,而不要報太大期望。
lab-5:使用最精簡的 base image使用 scratch 或者 busybox 作為基礎鏡像。
關於 scratch:
一個空鏡像,只能用於構建鏡像,通過 FROM scratch在構建一些基礎鏡像,比如 debian 、 busybox,非常有用用於構建超少鏡像,比如構建一個包含所有庫的二進位文件關於 busybox
這些超小的基礎鏡像,結合能生成靜態原生 ELF 文件的編譯語言,比如C/C++,比如 Go,特別方便構建超小的鏡像。
cloudcomb-logo(C語言開發) 就是用到了該原理,才能構建出 585 字節的鏡像。
redis 同樣使用 C語言 開發,看來也有很大的優化空間,下面這個實驗,讓我們介紹具體的操作方法。
lab-6:提取動態連結的 .so 文件實驗上下文:
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="14.04.2 LTS, Trusty Tahr"$ uname -a
Linux localhost 3.13.0-46-generic #77-Ubuntu SMP
Mon Mar 2 18:23:39 UTC 2015
x86_64 x86_64 x86_64 GNU/Linux隆重推出 ldd:列印共享的依賴庫
$ ldd redis-3.0.0/src/redis-server
linux-vdso.so.1 => (0x00007fffde365000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f307d5aa000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f307d38c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f307cfc6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f307d8b9000)將所有需要的 .so 文件打包:
$ tar ztvf rootfs.tar.gz
4485167 2015-04-21 22:54 usr/local/bin/redis-server
1071552 2015-02-25 16:56 lib/x86_64-linux-gnu/libm.so.6
141574 2015-02-25 16:56 lib/x86_64-linux-gnu/libpthread.so.0
1840928 2015-02-25 16:56 lib/x86_64-linux-gnu/libc.so.6
149120 2015-02-25 16:56 lib64/ld-linux-x86-64.so.2再製作成 Dockerfile:
FROM scratch
ADD rootfs.tar.gz /
COPY redis.conf /etc/redis/redis.conf
EXPOSE 6379
CMD ["redis-server"]執行構建:
$ docker build -t redis-05 .查看大小:
Lab
BasePL.red[*]Size (MB) Memo01redisubuntuCdyn347.3 base ubuntu02redisdebianCdyn305.7 base debian03redisdebianCdyn151.4 cmd chaining04redisdebianCdyn151.4 docker-squash05redisscratchCdyn7.73 rootfs: .so哇!顯著提高啦!編寫 Dockerfile 最佳實踐
測試一下:
$ docker run -d --name redis-05 redis-05
$ redis-cli -h \
$(docker inspect -f '{{.NetworkSettings.IPAddress}}' redis-05)
$ redis-benchmark -h \
$(docker inspect -f '{{.NetworkSettings.IPAddress}}' redis-05)總結一下:
將所有依賴壓縮成 rootfs.tar 或 rootfs.tar.gz,之後打進 scratch 基礎鏡像lab-7:為 Go 應用構建精簡鏡像Go 語言天生就方便用來構建精簡鏡像,得益於它能方便的打包成包含靜態連結的二進位文件。
打個比方,你有一個 4 MB 大小的包含靜態連結的 Go 二進位,並且將其打進 scratch 這樣的基礎鏡像,你得到的鏡像大小也只有區區的 4 MB。這可是包含同樣功能的 Ruby 程序的百分之一啊。
這裡再給大家介紹一個非常好用開源的 Go 編譯工具:golang-builder,並給大家實際演示一個例子
程序代碼:
package main // import "github.com/CenturyLinkLabs/hello"
import "fmt"
func main() {
fmt.Println("Hello World")
}Dockerfile:
FROM scratch
COPY hello /
ENTRYPOINT ["/hello"]通過 golang-builder 打包成鏡像:
docker run --rm \
-v $(pwd):/src \
-v /var/run/docker.sock:/var/run/docker.sock \
centurylink/golang-builder查看鏡像大小(Mac下測試):
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
hello latest 1a42948d3224 24 seconds ago 1.59 MB哇!這麼省力,就能創建幾 M 大小的鏡像,Go 簡介就是為 Docker 鏡像量身定做的!
總結我們介紹了鏡像層的知識,並且通過實驗,介紹三種如何精簡鏡像的技巧。這裡主要介紹了三種精簡方法:選用更精小的鏡像,串聯 Dockerfile 運行指令,以及試著壓縮你的鏡像。通過這幾個技巧,已經可以將 300M 大小的鏡像壓縮到 150M,壓縮率50%到98%,效果還是不錯。
點亮,伺服器三年不宕機