製作一個超級精簡的 Docker 鏡像只需7步

2021-12-26 DevOps技術棧
來源:https://github.com/bingohuang/play-docker-images

目錄

介紹

鏡像層(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%,效果還是不錯。

點亮,伺服器三年不宕機

相關焦點

  • 自製GreatSQL Docker鏡像
    0、提綱0、提綱1、準備工作2、開始製作docker鏡像2.1 準備安裝包及配套2.2 編輯Dockerfile2.3 製作鏡像2.4 保存鏡像到本地2.5 發布docker鏡像近期打算製作一個GreatSQL的docker鏡像,方便社區用戶使用GreatSQL。
  • 3步製作win10精簡版,運行比win7還快,簡直不要太香
    這裡大白菜就要出一個大招了,既然Win10企業版LTSC無法滿足你,乾脆自己製作一個win10精簡版!其實,win10系統中,有很多我們都不怎麼用的系統程序組件之類的,我們可以藉助NTLite工具,根據自己的使用習慣進行刪減使用,製作一個專屬的win10精簡版,簡直是舊電腦的福音呀!
  • Docker用法
    :倉庫名稱DESCRIPTION:鏡像描述STARS:用戶評價,反應一個鏡像的受歡迎程度OFFICIAL:是否官方AUTOMATED:自動構建,表示該鏡像由Docker Hub自動構建流程創建的拉取鏡像#拉取鏡像就是從中央倉庫中下載鏡像到本地docker pull 鏡像名稱#例如,我要下載centos7鏡像
  • Docker Hub鏡像中首次發現名為Graboid的加密挖礦蠕蟲病毒
    攻擊者選取某個不安全的docker主機作為攻擊目標,然後遠程發布指令下載並部署惡意Docker鏡像pocosow/centos:7.6.1810.該鏡像含有一個用來與其他Docker主機通信的 docker client工具  2. pocosow/centos 容器中的入口腳本/var/sbin/bash會從C2伺服器下載4個shell腳本並逐個運行,這4個腳本分別是live.sh, worm.sh, cleanxmr.sh, xmr.sh  3.
  • qBittorrent 4.2.1 docker鏡像更新說明
    其實熟悉的朋友都知道,qBittorrent 4.2.1 docker鏡像老燈在很久之前就發布了。
  • Docker操作必看,原來這才是正確打開Docker的新方式!
    你可以從本地構建一個鏡像,使用 docker build 命令,下文我們會說到如何構建自己的鏡像,我們先從鏡像倉庫中拉取鏡像,使用 docker pull 命令進行拉取,如下圖所示:docker pull nginx首先我們得找到 docker 的鏡像倉庫,網址:hub.docker.com
  • Dockerfile 最佳實踐
    然後將構建鏡像所需要的文件添加到該目錄中。為了提高構建鏡像的效率,你可以在目錄下新建一個 .dockerignore文件來指定要忽略的文件和目錄。.dockerignore 文件的排除模式語法和 Git 的 .gitignore 文件相似。使用多階段構建在 Docker 17.05 以上版本中,你可以使用 多階段構建 來減少所構建鏡像的大小。上一節課我們已經重點講解過了。
  • Docker 基礎用法和命令幫助
    rm        Remove one or more containers                # 移除一個或者多個容器rmi       Remove one or more images                 # 移除一個或多個鏡像[無容器使用該鏡像才可刪除,  否則需刪除相關容器才可繼續或 -f 強制刪除]run       Run a command
  • Docker命令大整理,附示例!
    1.1 docker run 運行一個新的容器# 參數說明## 常用參數-d         # 後臺運行容器,並返回容器ID-p         # 指定埠映射,格式為:主機(宿主)埠:容器埠--name     # 為容器指定一個名稱;-v         # 綁定一個卷,映射本地實際路徑
  • 學習Docker就應該掌握的dockerfile語法與指令
    Dockerfile 是由一系列指令和參數構成的腳本,一個 Dockerfile 裡面包含了構建整個鏡像的完整命令。Docker 通過docker build執行 Dockerfile 中的一系列指令自動構建鏡像。
  • SAST Weekly | Docker--新興的虛擬化技術
    3 Docker的基本指令3.1 Docker中的一些概念Docker包括3個基本概念:鏡像(Image),容器(Container),倉庫(Repository)Docker 鏡像就是一個只讀的模板。例如:一個鏡像可以包含一個完整的 ubuntu 作業系統環境,裡面僅安裝了 Apache 或用戶需要的其它應用程式。
  • 經驗總結 | Docker 使用筆記
    docker run ubuntu echo "helloworld"2、安裝 Docker-Composesudo curl -L "https://github.com/docker/compose/releases/download/1.23.2
  • Dockerfile企業實戰教程
    一、dockerfile語法詳解Dockerfile 是一個用來構建鏡像的文本文件,文本內容包含了一條條構建鏡像所需的指令和說明。基於Dockerfile構建鏡像可以使用docker build命令。["/usr/sbin/nginx","-g","daemon off;"]dockerfile構建過程:從基礎鏡像運行一個容器執行一條指令,對容器做出修改執行類似docker commit 的操作,提交一個新的鏡像層再基於剛提交的鏡像運行一個新的容器執行dockerfile中的下一條指令,直至所有指令執行完畢(1)FROM
  • Docker - 常用命令
    鏡像管理# 查看所有鏡像$ docker images# 搜索鏡像$ docker search nginx
  • 推薦給IT新手的11個Docker免費上手項目
    Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的鏡像中,然後發布到任何流行的 Linux或Windows 機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何接口。
  • 編寫 Dockerfile 最佳實踐
    /yum/*RUN wget http://docs.php.net/distributions/php-5.6.36.tar.gz && \    tar zxf php-5.6.36.tar.gz && \
  • 12個docker常用命令!以tomcat為例
    1:docker查找鏡像$ docker search tomcat4:啟動一個鏡像$ mkdir -p /usr/local/docker/tomcat/logs /usr/local/docker/tomcat/webapps$ docker run -d -p
  • Dockerfile 文件全面詳解
    Dockerfile 是一個文本文檔,其中包含了用戶創建鏡像的所有命令和說明。變量用 $variable_name 或者 ${variable_name} 表示。變量前加 \ 可以轉義成普通字符串:\$foo or \${foo},表示轉換為 $foo 和 ${foo} 文字。
  • 編寫Dockerfiles最佳實踐(Docker 18.09) | 附PDF下載
    /appRUN make /appCMD python/app/app.py每條指令創建一個層:當你運行一個鏡像生成一個容器,在底層的頂部添加一個可寫層(容器層)。對正在運行容器所做的所有更改,比如寫文件,修改已存在的文件,和刪除文件,都被寫入這個可寫空口層。
  • docker安裝Nginx和Tomcat
    -name 給這個容器起一個名字 叫做nginx11 # -p 指定外部埠給容器裡80 (也可以理解把80埠映射為3388埠) 最後一個nginx就是容器的名字[root@localhost ~]# docker run -d --name nginx11 -p 3388:80 nginx9f435eb63bed278a0bf6e0c0c5500834ac416342c6b2e42d0e304f9dfbb11cb9