制作一個(gè)超級(jí)精簡(jiǎn)的 Docker 鏡像只需7步
目錄
介紹
鏡像層(Layers)
制作步驟
lab-1:初始化構(gòu)建 Redis 鏡像
lab-2:優(yōu)化基礎(chǔ)鏡像
lab-3:串聯(lián) Dockerfile 指令
lab-4:壓縮你的鏡像
lab-5:使用最精簡(jiǎn)的 base image
lab-6:提取動(dòng)態(tài)鏈接的 .so 文件
lab-7:為 Go 應(yīng)用構(gòu)建精簡(jiǎn)鏡像
總結(jié)

介紹
前段時(shí)間網(wǎng)易蜂巢曾經(jīng)推出蜂巢?Logo?T恤,用的正是 Docker 鏡像制作,最神奇的是,它最終的鏡像大小只有?585?字節(jié)。
$?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
有些鏡像都不是我們自己來打包的(比如下載公共鏡像),那是否有一些通用的精簡(jiǎn) Docker 鏡像的手段呢?答案是肯定的,甚至有的鏡像可以精簡(jiǎn) 98%。精簡(jiǎn)鏡像大小的好處不言而喻,既節(jié)省了存儲(chǔ)空間,又能節(jié)省帶寬,加快傳輸?shù)取D呛茫酉聛砭驼?qǐng)跟隨我來學(xué)習(xí)怎么制作精簡(jiǎn) Docker 鏡像。
鏡像層(Layers)
在開始制作鏡像之前,首先了解下鏡像的原理,而這其中最重要的概念就是鏡像層(Layers)。鏡像層依賴于一系列的底層技術(shù),比如文件系統(tǒng)(filesystems)、寫時(shí)復(fù)制(copy-on-write)、聯(lián)合掛載(union mounts)等,幸運(yùn)的是你可以在很多地方學(xué)習(xí)到這些技術(shù)[1],這里就不再贅述技術(shù)細(xì)節(jié)。
總的來說,你最需要記住這點(diǎn):
在 Dockerfile 中,?每一條指令都會(huì)創(chuàng)建一個(gè)鏡像層,繼而會(huì)增加整體鏡像的大小。
舉例來說:
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 干了幾件事:
基于一個(gè)官方的基礎(chǔ)鏡像 busybox(只有1M多) 創(chuàng)建一個(gè)文件夾(/tmp/foo)和一個(gè)文件(bar),該文件分配了100M大小 再把這個(gè)大文件刪除
實(shí)際上它最終什么也沒做,我們把它構(gòu)建成鏡像(構(gòu)建可以參考一期[2]):
docker?build?-t?busybox:test?.
再讓我們來對(duì)比下原生的 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,這是為何?這點(diǎn)和 Git 類似(都用到了Copy-On-Write技術(shù)),我用 git 做了如下兩次提交(添加了又刪除),請(qǐng)問?A_VERY_LARGE_FILE?還在 git 倉庫中嗎?
$?git?add??A_VERY_LARGE_FILE
$?git?commit
$?git?rm??A_VERY_LARGE_FILE
$?git?commit
答案是:在的,并且會(huì)占用倉庫的大小。Git 會(huì)保存每一次提交的文件版本,而 Dockerfile 中每一條指令都可能增加整體鏡像的大小,即使它最終什么事情都沒做。
制作步驟
了解了鏡像層知識(shí),有助于我們接下來制作精簡(jiǎn)鏡像。這里開始,以最常用的開源緩存軟件?Redis?為例,從一步步試驗(yàn),來介紹如何制作更精簡(jiǎn)的 Docker 鏡像。
lab-1:初始化構(gòu)建 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"]
結(jié)合注釋,讀起來并不困難,用到的都是常規(guī)的幾個(gè)命令,簡(jiǎn)要介紹如下:
FROM:頂頭寫,指定一個(gè)基礎(chǔ)鏡像,此處基于? ubuntu:trustyENV:設(shè)置環(huán)境變量,這里設(shè)置了? VER?和?TARBALL?兩個(gè)環(huán)境變量RUN:最常用的 Dockerfile 指令,用于運(yùn)行各種命令,這里調(diào)用了 8 次 RUN 指令 WORKDIR:指定工作目錄,相當(dāng)于指令? cdCMD:指定鏡像默認(rèn)執(zhí)行的命令,此處默認(rèn)執(zhí)行 redis-server 命令來啟動(dòng) redis
執(zhí)行構(gòu)建:
$?docker?build??-t?redis:lab-1??.
注:國(guó)內(nèi)網(wǎng)絡(luò),更新下載可能會(huì)較慢
查看大小:
| Lab | iamge | Base | Lang | .red[*] | Size (MB) | ?? Memo |
|---|---|---|---|---|---|---|
| 1 | redis | ubuntu | C | dyn | 347.3 | ?? base ubuntu |
動(dòng)輒就有 300多 M 的大小,不能忍,下面我們開始一步步優(yōu)化。
lab-2:優(yōu)化基礎(chǔ)鏡像
精簡(jiǎn)1:選用更小的基礎(chǔ)鏡像。
常用的 Linux 系統(tǒng)鏡像一般有?ubuntu、centos、debian,其中debian?更輕量,而且夠用,對(duì)比如下:
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?作為我們的基礎(chǔ)鏡像。
優(yōu)化 Dockerfile:
FROM?debian:jessie
#...
執(zhí)行構(gòu)建:
$?docker?build??-t?redis:lab-2??.
查看大小:
| Lab | image | Base | Lang | .red[*] | Size (MB) | ?? Memo |
|---|---|---|---|---|---|---|
| 01 | redis | ubuntu | C | dyn | 347.3 | ?? base ubuntu |
| 02 | redis | debian | C | dyn | 305.7 | ?? base debian |
減少了42M,稍有成效,但并不明顯。細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn),只有 122 MB 的?debian?基礎(chǔ)鏡像,構(gòu)建后增加到了 305 MB,看來這里面肯定有優(yōu)化的空間,如何優(yōu)化就要用到我們開頭說到的?Image Layer?知識(shí)了。
lab-3:串聯(lián) Dockerfile 指令
精簡(jiǎn)2:串聯(lián)你的 Dockerfile 指令(一般是?RUN?指令)。
Dockerfile 中的 RUN 指令通過?&&?和?/?支持將命令串聯(lián)在一起,有時(shí)能達(dá)到意想不到的精簡(jiǎn)效果。
優(yōu)化 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"]
構(gòu)建:
$?docker?build??-t?redis:lab-3??.
查看大小:
| Lab | Image | Base | Lang | .red[*] | Size (MB) | ?? Memo |
|---|---|---|---|---|---|---|
| 01 | redis | ubuntu | C | dyn | 347.3 | ?? base ubuntu |
| 02 | redis | debian | C | dyn | 305.7 | ?? base debian |
| 03 | redis | debian | C | dyn | 151.4 | ?? cmd chaining |
哇!一下子減少了 50%,效果明顯啊!這是最常用的一個(gè)精簡(jiǎn)手段了。
lab-4:壓縮你的鏡像
優(yōu)化3:試著用命令或工具壓縮你的鏡像。
docker 自帶的一些命令還能協(xié)助壓縮鏡像,比如?export?和?import
$?docker?run?-d?redis:lab-3
$?docker?export?71b1c0ad0a2b?|?docker?import?-?redis:lab-4
但麻煩的是需要先將容器運(yùn)行起來,而且這個(gè)過程中你會(huì)丟失鏡像原有的一些信息,比如:導(dǎo)出端口,環(huán)境變量,默認(rèn)指令。
所以一般通過命令行來精簡(jiǎn)鏡像都是實(shí)驗(yàn)性的,那么這里再推薦一個(gè)小工具:docker-squash[3]。用起來更簡(jiǎn)單方便,并且不會(huì)丟失原有鏡像的自帶信息。
下載安裝:
https://github.com/jwilder/docker-squash#installation
壓縮操作:
$?docker?save?redis:lab-3?\
??|?sudo?docker-squash?-verbose?-t?redis:lab-4??\
??|?docker?load
注:該工具在 Mac 下并不好使,請(qǐng)?jiān)?Linux 下使用
對(duì)比大小:
| Lab | Image | Base | PL | .red[*] | Size (MB) | ?? Memo |
|---|---|---|---|---|---|---|
| 01 | redis | ubuntu | C | dyn | 347.3 | ?? base ubuntu |
| 02 | redis | debian | C | dyn | 305.7 | ?? base debian |
| 03 | redis | debian | C | dyn | 151.4 | ?? cmd chaining |
| 04 | redis | debian | C | dyn | 151.4 | ?? docker-squash |
好吧,從這里看起來并沒有太大作用,所以我只能說試著,而不要報(bào)太大期望。
lab-5:使用最精簡(jiǎn)的 base image

使用?scratch?或者?busybox?作為基礎(chǔ)鏡像。
關(guān)于 scratch:
一個(gè)空鏡像,只能用于構(gòu)建鏡像,通過? FROM scratch在構(gòu)建一些基礎(chǔ)鏡像,比如? debian?、?busybox,非常有用用于構(gòu)建超少鏡像,比如構(gòu)建一個(gè)包含所有庫的二進(jìn)制文件
關(guān)于?busybox
只有 1~5M 的大小 包含了常用的 UNIX 工具 非常方便構(gòu)建小鏡像
這些超小的基礎(chǔ)鏡像,結(jié)合能生成靜態(tài)原生 ELF 文件的編譯語言,比如C/C++,比如 Go,特別方便構(gòu)建超小的鏡像。
cloudcomb-logo(C語言開發(fā)) 就是用到了該原理,才能構(gòu)建出 585 字節(jié)的鏡像。
redis?同樣使用 C語言 開發(fā),看來也有很大的優(yōu)化空間,下面這個(gè)實(shí)驗(yàn),讓我們介紹具體的操作方法。
lab-6:提取動(dòng)態(tài)鏈接的 .so 文件
實(shí)驗(yàn)上下文:
$?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"]
執(zhí)行構(gòu)建:
$?docker?build??-t?redis-05??.
查看大小:
| Lab | Base | PL | .red[*] | Size (MB) | ?? Memo | |
|---|---|---|---|---|---|---|
| 01 | redis | ubuntu | C | dyn | 347.3 | ?? base ubuntu |
| 02 | redis | debian | C | dyn | 305.7 | ?? base debian |
| 03 | redis | debian | C | dyn | 151.4 | ?? cmd chaining |
| 04 | redis | debian | C | dyn | 151.4 | ?? docker-squash |
| 05 | redis | scratch | C | dyn | 7.73 | ?? rootfs: .so |
哇!顯著提高啦!編寫 Dockerfile 最佳實(shí)踐
測(cè)試一下:
$?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)
總結(jié)一下:
用? ldd?查出所需的 .so 文件將所有依賴壓縮成? rootfs.tar?或?rootfs.tar.gz,之后打進(jìn)?scratch?基礎(chǔ)鏡像
lab-7:為 Go 應(yīng)用構(gòu)建精簡(jiǎn)鏡像
Go 語言天生就方便用來構(gòu)建精簡(jiǎn)鏡像,得益于它能方便的打包成包含靜態(tài)鏈接的二進(jìn)制文件。
打個(gè)比方,你有一個(gè) 4 MB 大小的包含靜態(tài)鏈接的 Go 二進(jìn)制,并且將其打進(jìn) scratch 這樣的基礎(chǔ)鏡像,你得到的鏡像大小也只有區(qū)區(qū)的 4 MB。這可是包含同樣功能的 Ruby 程序的百分之一啊。
這里再給大家介紹一個(gè)非常好用開源的 Go 編譯工具:golang-builder,并給大家實(shí)際演示一個(gè)例子
程序代碼:
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下測(cè)試):
$?docker?images
REPOSITORY???TAG??????IMAGE?ID???????CREATED??????????VIRTUAL?SIZE
hello????????latest???1a42948d3224???24?seconds?ago???1.59?MB
哇!這么省力,就能創(chuàng)建幾 M 大小的鏡像,Go 簡(jiǎn)介就是為 Docker 鏡像量身定做的!
總結(jié)
我們介紹了鏡像層的知識(shí),并且通過實(shí)驗(yàn),介紹三種如何精簡(jiǎn)鏡像的技巧。這里主要介紹了三種精簡(jiǎn)方法:選用更精小的鏡像,串聯(lián) Dockerfile 運(yùn)行指令,以及試著壓縮你的鏡像。通過這幾個(gè)技巧,已經(jīng)可以將 300M 大小的鏡像壓縮到 150M,壓縮率50%到98%,效果還是不錯(cuò)。
優(yōu)化基礎(chǔ)鏡像 串接 Dockerfile 命令: 壓縮 Docker images 優(yōu)化程序依賴 選用更合適的開發(fā)語言
- END -
?推薦閱讀? 基于Kubernetes構(gòu)建完整的DevOps體系 ?
在Kubernetes上部署一套 Redis 集群 在Kubernetes上搭建一套Mysql主從集群 20張最全的DevOps架構(gòu)師技術(shù)棧圖譜 K8s運(yùn)維錦囊,19個(gè)常見故障解決方法 Linux 系統(tǒng)日常巡檢腳本 Linux故障排查思路及常用命令(收藏了) 如何用 Kubernetes 實(shí)現(xiàn) CI/CD 發(fā)布流程? | 漫畫 K8s kubectl 常用命令總結(jié)(建議收藏) Gitlab+Jenkins+k8s+Helm 的自動(dòng)化部署實(shí)踐 12年資深運(yùn)維老司機(jī)的成長(zhǎng)感悟 搭建一套完整的企業(yè)級(jí) K8s 集群(v1.22,二進(jìn)制方式)
點(diǎn)亮,服務(wù)器三年不宕機(jī)


