”微服務(wù)一條龍“最佳指南-“最佳實(shí)踐”篇:Dockerfile

最佳指南.jpg

這是”微服務(wù)一條龍“的項(xiàng)目第三篇,最近有很多網(wǎng)友私聊我希望我能夠講講Dockerfile的相關(guān)注意事項(xiàng),畢竟Dockerfile也是微服務(wù)部署的一個(gè)最最最....基礎(chǔ)的一個(gè)組件,大家不要小看Dockerfile,一個(gè)容器到底是不是構(gòu)建的最合適,要看你一步一步構(gòu)建起來(lái)的命令是不是合適,好,話(huà)不多說(shuō),接下來(lái)我們來(lái)看看Dockerfile的最佳實(shí)踐:

1. 先把利弊談一談

雖然 Dockerfile 簡(jiǎn)化了鏡像構(gòu)建的過(guò)程,并且把這個(gè)過(guò)程可以進(jìn)行版本控制,但是不正當(dāng)?shù)?Dockerfile 使用也會(huì)導(dǎo)致很多問(wèn)題:
(1)docker 鏡像太大。如果你經(jīng)常使用鏡像或者構(gòu)建鏡像,一定會(huì)遇到那種很大的鏡像,甚至有些能達(dá)到數(shù)G,就像我這次想要構(gòu)建一個(gè)python+node的鏡像結(jié)果花了1.4G。
(2)docker 鏡像的構(gòu)建時(shí)間過(guò)長(zhǎng)。每個(gè) build 都會(huì)耗費(fèi)很長(zhǎng)時(shí)間,對(duì)于需要經(jīng)常構(gòu)建鏡像(比如單元測(cè)試)的地方這可能是個(gè)大問(wèn)題,就像你可能每次只改一兩行代碼,卻要花上10多分鐘來(lái)構(gòu)建新的鏡像,完全是浪費(fèi)時(shí)間。
(3)重復(fù)勞動(dòng)。多次鏡像構(gòu)建之間大部分內(nèi)容都是完全一樣而且重復(fù)的,但是每次都要做一遍,浪費(fèi)時(shí)間和資源。

2.最佳實(shí)踐指導(dǎo)方針和建議

1. 容器應(yīng)該是短暫的

容器模型是進(jìn)程而不是機(jī)器,不需要開(kāi)機(jī)初始化。在需要時(shí)運(yùn)行,不需要時(shí)停止,能夠刪除后重建,并且配置和啟動(dòng)的最小化。這就是Docker和虛擬機(jī)最大的區(qū)別,進(jìn)程化,說(shuō)白了就是使用宿主機(jī)上的虛擬內(nèi)核來(lái)起一個(gè)進(jìn)程,一個(gè)進(jìn)程代表一個(gè)容器,這樣可以做到即插即用。

2.使用.dockerignore文件

docker build 的時(shí)候,忽略部分無(wú)用的文件和目錄可以提高構(gòu)建的速度。比如.git目錄。dockerignore的定義類(lèi)似gitignore。具體使用方式可以參考鏈接。其實(shí)大家都是用過(guò)gitignore,這里的dockerignore不僅可以忽略部分無(wú)用的文件,而且還保證了構(gòu)建速度的提高,因?yàn)橐话銇?lái)說(shuō)使用 docker build的時(shí)候都會(huì)直接使用 docker build -t xx .這樣直接利用上下文,最好是定義dockerignore文件來(lái)忽略部分無(wú)用的文件。

3.避免安裝不必要的安裝包

為了減少鏡像的復(fù)雜性、鏡像大小和構(gòu)建時(shí)間,應(yīng)該避免安裝無(wú)用的包。例如Python一樣,你的工程目錄中的requirements文件應(yīng)該只包含你所需要的包,而不應(yīng)當(dāng)是包含一些無(wú)用的包,這樣不僅浪費(fèi)構(gòu)建時(shí)間還變相擴(kuò)大了整個(gè)鏡像的大小。

4.每個(gè)容器只運(yùn)行一個(gè)進(jìn)程

一個(gè)容器只運(yùn)行一個(gè)進(jìn)程。容器起到了隔離應(yīng)用隔離數(shù)據(jù)的作用,不同的應(yīng)用運(yùn)行在不同的容器讓集群的縱向擴(kuò)展以及容器的復(fù)用都變的更加簡(jiǎn)單。需要多個(gè)應(yīng)用交互時(shí)請(qǐng)使用 link 命令進(jìn)行組合或者使用docker-compose。這點(diǎn)是很多人沒(méi)有注意到的問(wèn)題,因?yàn)楸热缫粋€(gè)容器起了兩個(gè)進(jìn)程,我們下次需要對(duì)于一個(gè)進(jìn)程做擴(kuò)展時(shí)會(huì)以容器為粒度增加容器,實(shí)際了把另一個(gè)無(wú)關(guān)容器擴(kuò)展,這樣對(duì)于集群的縱向擴(kuò)展以及容器的復(fù)用都有限制,所以,最基本的理念,一個(gè)容器只起一個(gè)進(jìn)程。

5.最小化層數(shù)

需要掌握好Dockerfile的可讀性和鏡像層數(shù)之間的平衡。不推薦使用過(guò)多的鏡像層。記?。∫粋€(gè)指令就會(huì)創(chuàng)建一層,所以,盡最大可能精簡(jiǎn)你的Dockerfile。

6.多行命令按字母排序

命令行按字母順序排序有助于避免重復(fù)執(zhí)行和提高Dockerfile可讀性。apt-get update 應(yīng)與 apt-get install 組合,換行使用反斜杠(\)。例如:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion

這里要是把兩個(gè)命令分開(kāi)的話(huà)問(wèn)題有會(huì)兩個(gè),在docker build的時(shí)候使用--no-cache這樣會(huì)把這兩個(gè)命令當(dāng)成兩個(gè),也就是會(huì)多創(chuàng)建一層,增加系統(tǒng)構(gòu)建時(shí)間和大小,如果不使用,系統(tǒng)構(gòu)建時(shí)會(huì)把apt-get update當(dāng)成系統(tǒng)命令,默認(rèn)不執(zhí)行,因此可能會(huì)導(dǎo)致新的包不會(huì)被安裝。

7.構(gòu)建緩存

Dockerfile的每條指令都會(huì)將結(jié)果提交為新的鏡像。下一條指令基于上一條指令的鏡像進(jìn)行構(gòu)建。如果一個(gè)鏡像擁有相同的父鏡像和指令(除了 ADD ),Docker將會(huì)使用鏡像而不是執(zhí)行該指令,即緩存。

因此,為了有效的利用緩存,盡量保持Dockerfile一致,并且將不變的放在前面而經(jīng)常改變放在末尾。

如不希望使用緩存,在執(zhí)行 docker build 的時(shí)候加上參數(shù) --no-cache=true 。

Docker匹配鏡像決定是否使用緩存的規(guī)則如下:
(1)從緩存中存在的基礎(chǔ)鏡像開(kāi)始,比較所有子鏡像,檢查它們構(gòu)建的指令是否和當(dāng)前的是否完全一致。如果不一致則緩存不匹配。
(2)多數(shù)情況中,比較Dockerfile中的指令是足夠的。然而,特定的指令需要做更多的判斷。
(3)ADD COPY 指令中,將要添加到鏡像中的文件也要被檢查。通常是檢查文件的校驗(yàn)和(checksum)。
(4)緩存匹配檢查并不檢查容器中的文件。例如,當(dāng)使用 RUN apt-get -y update 命令更新了容器中的文件,并不會(huì)被緩存檢查策略作為緩存匹配的依據(jù)。這也就是說(shuō)雖然文件更新了,但是命令相同,依舊是使用緩存,所以會(huì)導(dǎo)致更新失效。

8.時(shí)區(qū)

官方Image 使用的時(shí)區(qū)基本上都是標(biāo)準(zhǔn)的 UTC 時(shí)間,如果容器想使用中國(guó)標(biāo)準(zhǔn)時(shí)間,基于Debian的系統(tǒng)在Dockerfile中加入

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" >> /etc/timezone

基于Centos的系統(tǒng)在Dockerfile中加入

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

9.修改默認(rèn)源

有時(shí)候你可能感覺(jué)官方的源更新或者安裝軟件比較慢,可以在Dockerfile修改官方默認(rèn)源,例如alpine想使用阿里的源可以在Dockerfile中加入:

RUN echo -e "http://mirrors.aliyun.com/alpine/v3.5/
main\nhttp://mirrors.aliyun.com/alpine/v3.5/community" > /etc/apk/repositories

都是知識(shí)點(diǎn)!??!

10.正確理解Dockerfile指令

首先說(shuō)說(shuō)很多人其實(shí)并不是一定理解Dockerfile里面每一個(gè)指令的含義,以及與其他指令的微小區(qū)別,比如addcopy等等。

FROM

推薦使用官方倉(cāng)庫(kù)中的鏡像作為基礎(chǔ)鏡像,不用說(shuō)大家都理解。

RUN

把復(fù)雜的或過(guò)長(zhǎng)的 RUN 語(yǔ)句寫(xiě)成以 \ 結(jié)尾的多行的形式,以提高可讀性和可維護(hù)性。

apt-get update 和 apt-get install 一起執(zhí)行,否則 apt-get install 會(huì)出現(xiàn)異常。
這個(gè)嚴(yán)重的問(wèn)題必須要好好提提~~~

避免運(yùn)行 apt-get upgradedist-upgrade,在無(wú)特權(quán)的容器中,很多必要的包不能正常升級(jí)。如果基礎(chǔ)鏡像過(guò)時(shí)了,應(yīng)當(dāng)聯(lián)系維護(hù)者。 推薦apt-get update && apt-get install -y package-a package-b這種方式,先更新,之后安裝最新的軟件包。

RUN apt-get update && apt-get install -y \
    aufs-tools \
    automake \
    build-essential \
    curl \
    dpkg-sig \
    libcap-dev \
    libsqlite3-dev \
    mercurial \
    reprepro \
    ruby1.9.1 \
    ruby1.9.1-dev \
    s3cmd=1.1.* \
 && rm -rf /var/lib/apt/lists/*

此外,你可以通過(guò)移除/var/lib/apt/lists減少鏡像大小。

    注意:官方的Ubuntu和Debian會(huì)自動(dòng)運(yùn)行apt-get clean,所以不需要顯式的調(diào)用

CMD

推薦使用 CMD ["executable","param1","param2"] 這樣的格式。
如果鏡像是用來(lái)運(yùn)行服務(wù),需要使用 CMD["apache2","-DFOREGROUND"],這種格式的指令適用于任何服務(wù)性質(zhì)的鏡像。

ENTRYPOINT

ENTRYPOINT 應(yīng)該用于鏡像的主命令,并使用 CMD 作為默認(rèn)設(shè)置,以 s3cmd 為例:

ENTRYPOINT ["s3cmd"]
CMD ["--help"]

獲取幫助:

docker run s3cmd

或者執(zhí)行命令:

docker run s3cmd ls s3://mybucket

這在鏡像名與程序重名時(shí)非常有用。

ENTRYPOINT 也可以啟動(dòng)自定義腳本: 定義腳本:

#!/bin/bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"
注意:這段腳本使用了exec命令以確保最終應(yīng)用程序在容器內(nèi)啟動(dòng)的PID為1。這段腳本允許容器接收Unix signals。
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]

這段腳本為用戶(hù)提供了多種和 Postgres 交互的途徑:

你可以簡(jiǎn)單地啟動(dòng) Postgres:

docker run postgres。

或者運(yùn)行 postgres 并傳入?yún)?shù):

docker run postgres postgres --help。

你甚至可以從鏡像中啟動(dòng)一個(gè)完全不同的程序,比如 Bash:

docker run --rm -it postgres bash

EXPOSE

應(yīng)該盡可能地使用默認(rèn)端口。例如Apache web服務(wù)使用EXPOSE 80,MongoDB使用EXPOSE 27017。

ENV

可以使用ENV更新PATH環(huán)境變量。例如,ENV PATH /usr/local/nginx/bin:$PATH可以確保CMD [“nginx”]正常運(yùn)行。
ENV還可以提供程序所需要的環(huán)境變量,例如Postgres的 PGDATA。
ENV可以設(shè)置版本等信息。使版本信息更易于維護(hù)。

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD or COPY

雖然 ADD 與 COPY 功能類(lèi)似,但推薦使用 COPY 。 COPY 只支持基本的文件拷貝功能,更加的可控。而 ADD 具有更多特定,比如tar文件自動(dòng)提取,支持URL。 通常需要提取tarball中的文件到容器的時(shí)候才會(huì)用到 ADD 。

如果在Dockerfile中使用多個(gè)文件,每個(gè)文件應(yīng)使用單獨(dú)的 COPY 指令。這樣,只有出現(xiàn)文件變化的指令才會(huì)不使用緩存。

為了控制鏡像的大小,不建議使用 ADD 指令獲取URL文件。正確的做法是在 RUN 指令中使用 wget 或 curl 來(lái)獲取文件,并且在文件不需要的時(shí)候刪除文件。

RUN mkdir -p /usr/src/things \
    && curl -SL http://example.com/big.tar.gz \
    | tar -xJC /usr/src/things \
    && make -C /usr/src/things all

一句話(huà),COPY透明度好,它只能干復(fù)制的工作,ADD鬼知道他還能干什么~

VOLUME

VOLUME 通常用作數(shù)據(jù)卷,對(duì)于任何可變的文件,包括數(shù)據(jù)庫(kù)文件、代碼庫(kù)、或者容器所創(chuàng)建的文件/目錄等都應(yīng)該使用 VOLUME 掛載。這點(diǎn)有點(diǎn)生產(chǎn)環(huán)境體驗(yàn)的同學(xué)相信不會(huì)陌生。

USER

如果服務(wù)不需要特權(quán)來(lái)運(yùn)行,使用 USER 指令切換到非root用戶(hù)。使用 **RUN groupadd -r mysql && useradd -r -g mysql mysql **之后用**USER mysql**切換用戶(hù)

要避免使用 sudo 來(lái)提升權(quán)限,因?yàn)樗鼛?lái)的問(wèn)題遠(yuǎn)比它能解決的問(wèn)題要多。如果你確實(shí)需要這樣的特性,那么可以選擇使用 gosu 。

最后,不要反復(fù)的切換用戶(hù)。減少不必要的layers。

WORKDIR

為了清晰和可維護(hù)性,應(yīng)該使用WORKDIR來(lái)定義工作路徑。推薦使用WORKDIR來(lái)代替RUN cd … && do-something 這樣的指令。
]

3.總結(jié)

廢話(huà)不多說(shuō),Docker這東西你用起來(lái)才知道真的好用,上五樓都不費(fèi)勁了??!

最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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