制作一個(gè)超級(jí)精簡(jiǎn)的 Docker 鏡像只需7步

文章來(lái)自黃慶兵老師的網(wǎng)易蜂巢《玩轉(zhuǎn) Docker 鏡像》系列https://github.com/bingohuang/play-docker-images,原本分為上下兩篇,這里由生態(tài)君整合成一篇便于閱讀。希望能對(duì)大家有所幫助。

目錄

介紹

鏡像層(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é)

參考

作者簡(jiǎn)介:黃慶兵,畢業(yè)于浙大,工作于網(wǎng)易,從事云計(jì)算、Docker和Go相關(guān)開發(fā)及布道工作;喜歡開源,樂于分享,勤于布道,折騰過(guò)開源小工具,制作過(guò)Docker課程,分享過(guò) Gopher Meetup。我的 Github 賬號(hào):https://github.com/bingohuang,歡迎一起來(lái)寫 Go 玩 Docker!

介紹

前段時(shí)間網(wǎng)易蜂巢曾經(jīng)推出蜂巢LogoT恤,用的正是 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

有些鏡像都不是我們自己來(lái)打包的(比如下載公共鏡像),那是否有一些通用的精簡(jiǎn) Docker 鏡像的手段呢?答案是肯定的,甚至有的鏡像可以精簡(jiǎn) 98%。精簡(jiǎn)鏡像大小的好處不言而喻,既節(jié)省了存儲(chǔ)空間,又能節(jié)省帶寬,加快傳輸?shù)?。那好,接下?lái)就請(qǐng)跟隨我來(lái)學(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é)。

總的來(lái)說(shuō),你最需要記住這點(diǎn):

在 Dockerfile 中,?每一條指令都會(huì)創(chuàng)建一個(gè)鏡像層,繼而會(huì)增加整體鏡像的大小。

舉例來(lái)說(shuō):

FROMbusybox

RUN?mkdir?/tmp/foo

RUNddif=/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.

再讓我們來(lái)對(duì)比下原生的 busybox 鏡像大小和我們生成的鏡像大小:

$?docker?images?|?grep?busybox

busyboxtest896c63dbdb96????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 倉(cāng)庫(kù)中嗎?

$?git?add??A_VERY_LARGE_FILE

$?git?commit

$?git?rm??A_VERY_LARGE_FILE

$?git?commit

答案是:在的,并且會(huì)占用倉(cāng)庫(kù)的大小。Git 會(huì)保存每一次提交的文件版本,而 Dockerfile 中每一條指令都可能增加整體鏡像的大小,即使它最終什么事情都沒做。

制作步驟

了解了鏡像層知識(shí),有助于我們接下來(lái)制作精簡(jiǎn)鏡像。這里開始,以最常用的開源緩存軟件Redis為例,從一步步試驗(yàn),來(lái)介紹如何制作更精簡(jiǎn)的 Docker 鏡像。

lab-1:初始化構(gòu)建 Redis 鏡像

直接上Dockerfile:

FROMubuntu:trusty

ENVVER3.0.0

ENVTARBALL?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...

RUNcurl?-L$TARBALL|?tar?zxv

WORKDIRredis-$VER

RUN?make

RUN?make?install

#...

#?==>?Clean?up...

WORKDIR?/

RUN?apt-get?remove?-y?--auto-remove?curl?make?gcc

RUN?apt-get?clean

RUNrm?-rf?/var/lib/apt/lists/*??/redis-$VER

#...

CMD["redis-server"]

結(jié)合注釋,讀起來(lái)并不困難,用到的都是常規(guī)的幾個(gè)命令,簡(jiǎn)要介紹如下:

FROM:頂頭寫,指定一個(gè)基礎(chǔ)鏡像,此處基于ubuntu:trusty

ENV:設(shè)置環(huán)境變量,這里設(shè)置了VER和TARBALL兩個(gè)環(huán)境變量

RUN:最常用的 Dockerfile 指令,用于運(yùn)行各種命令,這里調(diào)用了 8 次 RUN 指令

WORKDIR:指定工作目錄,相當(dāng)于指令cd

CMD:指定鏡像默認(rèn)執(zhí)行的命令,此處默認(rèn)執(zhí)行 redis-server 命令來(lái)啟動(dòng) redis

執(zhí)行構(gòu)建:

$?docker?build??-t?redis:lab-1??.

注:國(guó)內(nèi)網(wǎng)絡(luò),更新下載可能會(huì)較慢

查看大?。?/b>

LabiamgeBaseLang.red[*]Size (MB)?? Memo

1redisubuntuCdyn347.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:

FROMdebian:jessie

#...

執(zhí)行構(gòu)建:

$?docker?build??-t?redis:lab-2??.

查看大?。?/b>

LabimageBaseLang.red[*]Size (MB)?? Memo

01redisubuntuCdyn347.3?? base ubuntu

02redisdebianCdyn305.7?? base debian

減少了42M,稍有成效,但并不明顯。細(xì)心的同學(xué)應(yīng)該發(fā)現(xiàn),只有 122 MB 的debian基礎(chǔ)鏡像,構(gòu)建后增加到了 305 MB,看來(lái)這里面肯定有優(yōu)化的空間,如何優(yōu)化就要用到我們開頭說(shuō)到的Image Layer知識(shí)了。

lab-3:串聯(lián) Dockerfile 指令

精簡(jiǎn)2:串聯(lián)你的 Dockerfile 指令(一般是RUN指令)。

Dockerfile 中的 RUN 指令通過(guò)&&和/支持將命令串聯(lián)在一起,有時(shí)能達(dá)到意想不到的精簡(jiǎn)效果。

優(yōu)化 Dockerfile:

FROMdebian:jessie

ENVVER3.0.0

ENVTARBALL?http://download.redis.io/releases/redis-$VER.tar.gz

RUNecho"==>?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??&&?\

cdredis-$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??.

查看大?。?/p>

LabImageBaseLang.red[*]Size (MB)?? Memo

01redisubuntuCdyn347.3?? base ubuntu

02redisdebianCdyn305.7?? base debian

03redisdebianCdyn151.4?? cmd chaining

哇!一下子減少了 50%,效果明顯??!這是最常用的一個(gè)精簡(jiǎn)手段了。

lab-4:壓縮你的鏡像

優(yōu)化3:試著用命令或工具壓縮你的鏡像。

docker 自帶的一些命令還能協(xié)助壓縮鏡像,比如export和import

$?docker?run?-d?redis:lab-3

$?dockerexport71b1c0ad0a2b?|?docker?import?-?redis:lab-4

但麻煩的是需要先將容器運(yùn)行起來(lái),而且這個(gè)過(guò)程中你會(huì)丟失鏡像原有的一些信息,比如:導(dǎo)出端口,環(huán)境變量,默認(rèn)指令。

所以一般通過(guò)命令行來(lái)精簡(jiǎn)鏡像都是實(shí)驗(yàn)性的,那么這里再推薦一個(gè)小工具:docker-squash[3]。用起來(lái)更簡(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ì)比大?。?/b>

LabImageBasePL.red[*]Size (MB)?? Memo

01redisubuntuCdyn347.3?? base ubuntu

02redisdebianCdyn305.7?? base debian

03redisdebianCdyn151.4?? cmd chaining

04redisdebianCdyn151.4?? docker-squash

好吧,從這里看起來(lái)并沒有太大作用,所以我只能說(shuō)試著,而不要報(bào)太大期望。

lab-5:使用最精簡(jiǎn)的 base image

使用scratch或者busybox作為基礎(chǔ)鏡像。

關(guān)于 scratch:

一個(gè)空鏡像,只能用于構(gòu)建鏡像,通過(guò)FROM scratch

在構(gòu)建一些基礎(chǔ)鏡像,比如debian、busybox,非常有用

用于構(gòu)建超少鏡像,比如構(gòu)建一個(gè)包含所有庫(kù)的二進(jìn)制文件

關(guān)于busybox

只有 1~5M 的大小

包含了常用的 UNIX 工具

非常方便構(gòu)建小鏡像

這些超小的基礎(chǔ)鏡像,結(jié)合能生成靜態(tài)原生 ELF 文件的編譯語(yǔ)言,比如C/C++,比如 Go,特別方便構(gòu)建超小的鏡像。

cloudcomb-logo(C語(yǔ)言開發(fā)) 就是用到了該原理,才能構(gòu)建出 585 字節(jié)的鏡像。

redis同樣使用 C語(yǔ)言 開發(fā),看來(lái)也有很大的優(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:打印共享的依賴庫(kù)

$?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:

FROMscratch

ADD??rootfs.tar.gz??/

COPY?redis.conf?????/etc/redis/redis.conf

EXPOSE6379

CMD["redis-server"]

執(zhí)行構(gòu)建:

$?docker?build??-t?redis-05??.

查看大?。?/p>

LabBasePL.red[*]Size (MB)?? Memo

01redisubuntuCdyn347.3?? base ubuntu

02redisdebianCdyn305.7?? base debian

03redisdebianCdyn151.4?? cmd chaining

04redisdebianCdyn151.4?? docker-squash

05redisscratchCdyn7.73?? rootfs: .so

哇!顯著提高啦!

測(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 語(yǔ)言天生就方便用來(lái)構(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è)例子

程序代碼:

packagemain//?import?"github.com/CenturyLinkLabs/hello"

import"fmt"

funcmain(){

fmt.Println("Hello?World")

}

Dockerfile:

FROMscratch

COPY?hello?/

ENTRYPOINT["/hello"]

通過(guò) 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í),并且通過(guò)實(shí)驗(yàn),介紹三種如何精簡(jiǎn)鏡像的技巧。這里主要介紹了三種精簡(jiǎn)方法:選用更精小的鏡像,串聯(lián) Dockerfile 運(yùn)行指令,以及試著壓縮你的鏡像。通過(guò)這幾個(gè)技巧,已經(jīng)可以將 300M 大小的鏡像壓縮到 150M,壓縮率50%到98%,效果還是不錯(cuò)。

優(yōu)化基礎(chǔ)鏡像

串接 Dockerfile 命令:

壓縮 Docker images

優(yōu)化程序依賴

選用更合適的開發(fā)語(yǔ)言

參考

scratch in Docker Hub[4]

Make FROM scratch a special cased 'no-base' spec[5]

vDSO (virtual dynamic shared object)[6]

Small Docker Images For Go Apps[7](withgolang-builder[8])

Building Docker Images for Static Go Binaries[9]

Dockerfile Best Practices - take 2[10]- by Michael Crosby, 2014-03-09.

Optimizing Docker Images[11]- by Brian DeHamer, 2014-07-28.

Squashing Docker Images[12]- by Jason Wilder, 2014-08-19.

參考資料

[1] 這些技術(shù):https://docs.docker.com/engine/userguide/storagedriver/imagesandcontainers/

[2] 一期:https://github.com/bingohuang/play-docker-images/tree/master/stage-01

[3] docker-squash:https://github.com/jwilder/docker-squash

[4] scratch in Docker Hub:https://registry.hub.docker.com/_/scratch/

[5] Make FROM scratch a special cased 'no-base' spec:https://github.com/docker/docker/pull/8827

[6] vDSO (virtual dynamic shared object):http://en.wikipedia.org/wiki/VDSO

[7] Small Docker Images For Go Apps:http://www.centurylinklabs.com/small-docker-images-for-go-apps/

[8] golang-builder:https://github.com/CenturyLinkLabs/golang-builder

[9] Building Docker Images for Static Go Binaries:https://medium.com/@kelseyhightower/optimizing-docker-images-for-static-binaries-b5696e26eb07

[10] Dockerfile Best Practices - take 2:http://crosbymichael.com/dockerfile-best-practices-take-2.html

[11] Optimizing Docker Images:http://www.centurylinklabs.com/optimizing-docker-images/

[12] Squashing Docker Images:http://jasonwilder.com/blog/2014/08/19/squashing-docker-images/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容