Dockerfile 慣用法,應該分發更小的容器

Dockerfile Idiomshtml

分發更小的容器,是一種應該被推薦的行爲。java

更小的容器,啓動會更快,分發會更快,重用會更快,……。對於生命來講,快是一種態度,表明你的時間被消費的更有價值:節約時間老是正確的。node

幾年前,其實我就想寫有關容器縮減的內容,不過那時候 alpine 還不被重視,它本身也很難用,因此咱們更多地是在 ubuntu 這樣的大塊頭上研究怎麼削減最終尺寸。python

值得欣慰的是,那些曾經用到的原則一直都是對的,儘管這個世界、這些生態在變,但準則沒有變。linux

如今,關於容器尺寸削減的問題,文章多得很,問答也不少。git

但我仍是打算寫一篇。寫者嘛,老是以爲本身寫的內容對一點,邏輯對一點,用詞對一點,覆蓋面對一點,限定語對一點。等等。github

不過,此次準備隨便寫,不打算整構邏輯了。golang

Checklist

削減容器的最終尺寸,首先考慮以下的 Checklist:docker

  • 採用更小的基準包shell

    • 在絕大多數狀況下,alpine是最佳選擇
    • 極端狀況下可使用 distroless,但後果是沒有shell,沒法進入容器
    • 有的時候你可使用 busybox
  • 選取最恰當的基準包。

    • 對於 golang 服務來講,alpine:latest 是最佳選擇
      • 但若是你須要golang構建操做,則 golang:alpine 可能纔是對的
    • 對於 java 系列來講,這些都是好選擇:
    • 對於 nodejs 來講,alpine 版本是最佳的:node:8-alpine
    • 等等,無法一一按語言枚舉。
  • 使用包安裝命令時,記住清除包安裝過程所下載的索引、安裝包

    後面我會在慣用法中更多介紹這一點。

  • 去掉內存交換機制,去掉交換分區

  • 不要安裝帶有 ncurse 依賴的工具,例如 mc

  • 不要安裝帶有調試工具或者調試工具性質的工具,例如 vim,curl。必定要用,使用 nano 和 wget 替代它們

  • 調整命令順序,合併相同命令,使得產生更少的層

  • 使用記憶功能以便去掉打包過程當中纔會使用的包,從而縮減最終容器尺寸

    這個記憶功能,主要是指 alpine apk --virtual 功能;對於 apt 則有一個 apt-mark 工具。

  • 對於 apt 使用 apt install --no-install-recommends -y 方式

  • 使用多遍構建過程,將打包和中間內容排除在最終容器以外,以縮減其尺寸

下面都是基於 voxr vdeps-base 來介紹。

能夠查閱 github.com/hedzr/docke…

各類慣用法

alpine apk 的慣用法

較典型的作法是這樣子:

RUN fetchDeps=" \ ca-certificates \ bash less nano iputils bind-tools busybox-extras \ wget lsof unzip \ "; \ apk update \ && apk --update add ${fetchDeps} \ && apk info -vv | sort \ && apk -v cache clean && rm /var/cache/apk/* # 摘自 https://github.com/hedzr/docker-basics/blob/master/alpine-base/Dockerfile
複製代碼

apk -v cache cleanrm /var/cache/apk/* 二者選一就能夠了,這裏只是爲了示例。

比上例更嚴格精確、也更節省空間的辦法是:

RUN build-deps="gcc
      freetype-dev \
      musl-dev \
      "; \
    apl add --update --no-cache bash less nano unzip && \
    apk add --no-cache --virtual .build-deps ${buildDeps} && \
    pip install --no-cache-dir requests && \
    apk del .build-deps && \
    rm /var/cache/apk/*
複製代碼

採用國內鏡像服務器加速,更溫馨的結構:

# 改編自 hedzr/docker-basics/golang-builder
RUN fetchDeps=" \ ca-certificates \ "; \ buildDeps=" tig "; \ cp /etc/apk/repositories /etc/apk/repositories.bak; \ echo "http://mirrors.aliyun.com/alpine/v3.10/main/" > /etc/apk/repositories; \ apk update \ && apk add --virtual .build-deps ${buildDeps} \ && apk add ${fetchDeps} \ && echo     && echo "Put your building scripts HERE" \
    && apk del .build-deps \
    && rm /var/cache/apk/* \
複製代碼

debian apt 的慣用法

RUN fetchDeps=" \ ca-certificates \ wget nano vim.tiny net-tools iputils-ping lsof \ dnsutils inetutils-telnet locales \ "; \ TZ=Etc/UTC; LOCALE=en_US.UTF-8; \ apt update \ && DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends ${fetchDeps} \ && locale-gen $LOCALE \ && cat /etc/default/locale && echo "Original TimeZone is: $(locale -a)" && date +'%z' \ && ln -s /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ | tee /etc/timezone \ && echo "Current TimeZone updated: $(locale -a)" && date +'%z' \ # && apt-get purge -y --auto-remove ${fetchDeps} \ && rm -rf /var/lib/apt/lists/* # 時鐘時區部分能夠去掉
# 摘自 https://github.com/hedzr/docker-basics/blob/master/ubuntu-mod/Dockerfile
複製代碼

對於用到 python pip 的場景還能夠這樣:

RUN buildDeps="curl python-pip" && \ apt-get update && \ apt-get install -y --no-install-recommends $buildDeps && \ pip install requests && \ apt-get purge -y --auto-remove $buildDeps && \ rm -rf /var/lib/apt/lists/* 
複製代碼

用到 build-essential 或者 gcc 系的也能夠相似地處理:

RUN buildDeps="curl wget build-essentials flex bison make cmake autoconf automake git libtool" && \ fetchDeps="nano wget curl"     apt-get update && \
    apt-get install -y --no-install-recommends $fetchDeps && \
    AUTO_ADDED_PACKAGES=`apt-mark showauto` && \
    apt-get install -y --no-install-recommends $buildDeps && \
    mkdir build && cd build && cmake .. && make && make install && \
    apt-get purge -y --auto-remove $buildDeps $AUTO_ADDED_PACKAGES && \
    rm -rf /var/lib/apt/lists/*
複製代碼

注意到咱們採用了 AUTO_ADDED_PACKAGES 機制,這是一種 Debian 包管理系的記憶功能,能夠被用來很好地削減尺寸。

centos 的 yum 系慣用法

相似 apt,再也不贅述了

多遍構建

儘管包管理的記憶功能可以完美地削減容器尺寸,但它並不是是沒有缺點的:

  1. 你必須在單句 RUN 中寫出記憶以及消除記憶的所有腳本,若是分割到多句指令,那麼容器中的 OS的佔地面積依然能被收縮,但容器的尺寸可能並不能被削減。

  2. 若是你在單句 RUN 指令中完成了你的整個容器構建腳本的話,構建的開發過程將會很是痛苦,由於冗長的指令序列不能被緩存到多層中,因此每一次微小的變化都會致使 docker build 去完整地重建你的這個容器。

    因此縮減容器尺寸,應該是當你的容器構建過程已經開發完成以後纔去作的事情。

好的,記憶功能有點點不完美,可是多遍構建可以很好地平衡這一切問題。

以 golang 應用的容器化爲例,下面是一個多遍構建的例子:

FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/ RUN go get -d -v golang.org/x/net/html COPY app.go . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . 
FROM alpine:latest  
RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /go/src/github.com/alexellis/href-counter/app . CMD ["./app"] # 摘自 https://github.com/alexellis/href-counter
複製代碼

好吧,我本身的寫的複雜得多,但暫時還不能展現,此外,複雜的版本也不利於闡述骨架結構。

結束

寫到這裏,暫時告一段落了。

關於縮減尺寸以及 Dockerfile 的慣用寫法,也就先說這麼多了。再要釋出點什麼也不是不能夠,但可能涉及到的就不是僅僅 Docker 的知識了。

結果仍是分了分章節,哎呀德性了

相關文章
相關標籤/搜索