溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么看待Dockerfile最佳實(shí)踐

發(fā)布時(shí)間:2021-10-12 09:46:40 來(lái)源:億速云 閱讀:89 作者:柒染 欄目:云計(jì)算

怎么看待Dockerfile最佳實(shí)踐,針對(duì)這個(gè)問(wèn)題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問(wèn)題的小伙伴找到更簡(jiǎn)單易行的方法。

雖然 Dockerfile 簡(jiǎn)化了鏡像構(gòu)建的過(guò)程,并且把這個(gè)過(guò)程可以進(jìn)行版本控制,但是不正當(dāng)?shù)?Dockerfile 使用也會(huì)導(dǎo)致很多問(wèn)題:

  • docker 鏡像太大。如果你經(jīng)常使用鏡像或者構(gòu)建鏡像,一定會(huì)遇到那種很大的鏡像,甚至有些能達(dá)到 數(shù)G

  • docker 鏡像的構(gòu)建時(shí)間過(guò)長(zhǎng)。每個(gè) build 都會(huì)耗費(fèi)很長(zhǎng)時(shí)間,對(duì)于需要經(jīng)常構(gòu)建鏡像(比如單元測(cè)試)的地方這可能是個(gè)大問(wèn)題

  • 重復(fù)勞動(dòng)。多次鏡像構(gòu)建之間大部分內(nèi)容都是完全一樣而且重復(fù)的,但是每次都要做一遍,浪費(fèi)時(shí)間和資源

一般指導(dǎo)方針和建議

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

容器模型是進(jìn)程而不是機(jī)器,不需要開(kāi)機(jī)初始化。在需要時(shí)運(yùn)行,不需要時(shí)停止,能夠刪除后重建,并且配置和啟動(dòng)的最小化。

使用.dockerignore文件

在 docker build 的時(shí)候,忽略部分無(wú)用的文件和目錄可以提高構(gòu)建的速度。比如.git目錄。dockerignore的定義類似gitignore。具體使用方式可以參考鏈接。

避免安裝不必要的安裝包

為了減少鏡像的復(fù)雜性、鏡像大小和構(gòu)建時(shí)間,應(yīng)該避免安裝無(wú)用的包。

每個(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。

最小化層數(shù)

需要掌握好Dockerfile的可讀性和鏡像層數(shù)之間的平衡。不推薦使用過(guò)多的鏡像層。

多行命令按字母排序

命令行按字母順序排序有助于避免重復(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ò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ī)則如下:

  • 從緩存中存在的基礎(chǔ)鏡像開(kāi)始,比較所有子鏡像,檢查它們構(gòu)建的指令是否和當(dāng)前的是否完全一致。如果不一致則緩存不匹配。

  • 多數(shù)情況中,比較Dockerfile中的指令是足夠的。然而,特定的指令需要做更多的判斷。

  • ADD COPY 指令中,將要添加到鏡像中的文件也要被檢查。通常是檢查文件的校驗(yàn)和(checksum)。

  • 緩存匹配檢查并不檢查容器中的文件。例如,當(dāng)使用 RUN apt-get -y update 命令更新了容器中的文件,并不會(huì)被緩存檢查策略作為緩存匹配的依據(jù)。

時(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

修改默認(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

Dockerfile指令

FROM

推薦使用官方倉(cāng)庫(kù)中的鏡像作為基礎(chǔ)鏡像。

RUN

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

apt-get update 和 apt-get install 一起執(zhí)行,否則 apt-get install 會(huì)出現(xiàn)異常。

避免運(yùn)行 apt-get upgrade 或 dist-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"]

這段腳本為用戶提供了多種和 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 功能類似,但推薦使用 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

VOLUME

VOLUME 通常用作數(shù)據(jù)卷,對(duì)于任何可變的文件,包括數(shù)據(jù)庫(kù)文件、代碼庫(kù)、或者容器所創(chuàng)建的文件/目錄等都應(yīng)該使用 VOLUME 掛載。

USER

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

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

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

WORKDIR

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

ONBUILD

Dockerfile 中的其它指令都是為了定制當(dāng)前鏡像而準(zhǔn)備的,唯有 ONBUILD 是為了幫助別人定制自己而準(zhǔn)備的。

ONBUILD指令用來(lái)設(shè)置一些觸發(fā)的指令,用于在當(dāng)該鏡像被作為基礎(chǔ)鏡像來(lái)創(chuàng)建其他鏡像時(shí)(也就是Dockerfile中的FROM為當(dāng)前鏡像時(shí))執(zhí)行一些操作,ONBUILD中定義的指令會(huì)在用于生成其他鏡像的Dockerfile文件的FROM指令之后被執(zhí)行,上述介紹的任何一個(gè)指令都可以用于ONBUILD指令,可以用來(lái)執(zhí)行一些因?yàn)榄h(huán)境而變化的操作,使鏡像更加通用。

注意:

  • ONBUILD中定義的指令在當(dāng)前鏡像的build中不會(huì)被執(zhí)行。

  • 可以通過(guò)查看docker inspect <image>命令執(zhí)行結(jié)果的OnBuild鍵來(lái)查看某個(gè)鏡像ONBUILD指令定義的內(nèi)容。

  • ONBUILD中定義的指令會(huì)當(dāng)做引用該鏡像的Dockerfile文件的FROM指令的一部分來(lái)執(zhí)行,執(zhí)行順序會(huì)按ONBUILD定義的先后順序執(zhí)行,如果ONBUILD中定義的任何一個(gè)指令運(yùn)行失敗,則會(huì)使FROM指令中斷并導(dǎo)致整個(gè)build失敗,當(dāng)所有的ONBUILD中定義的指令成功完成后,會(huì)按正常順序繼續(xù)執(zhí)行build。

  • ONBUILD中定義的指令不會(huì)繼承到當(dāng)前引用的鏡像中,也就是當(dāng)引用ONBUILD的鏡像創(chuàng)建完成后將會(huì)清除所有引用的ONBUILD指令。

  • ONBUILD指令不允許嵌套,例如ONBUILD ONBUILD ADD . /data是不允許的。

  • ONBUILD指令不會(huì)執(zhí)行其定義的FROM或MAINTAINER指令。

例如,Dockerfile使用如下的內(nèi)容創(chuàng)建了鏡像 image-A :

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

如果基于 image-A 創(chuàng)建新的鏡像時(shí),新的Dockerfile中使用FROM image-A指定基礎(chǔ)鏡像時(shí),會(huì)自動(dòng)執(zhí)行ONBUILD指令內(nèi)容,等價(jià)于在后面添加了兩條指令。

FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用場(chǎng)景
Node.js

假設(shè)我們要制作 Node.js 所寫(xiě)的應(yīng)用的鏡像。我們都知道 Node.js 使用 npm 進(jìn)行包管理,所有依賴、配置、啟動(dòng)信息等會(huì)放到 package.json 文件里。在拿到程序代碼后,需要先進(jìn)行 npm install 才可以獲得所有需要的依賴。然后就可以通過(guò) npm start 來(lái)啟動(dòng)應(yīng)用。因此,一般來(lái)說(shuō)會(huì)這樣寫(xiě) Dockerfile:

FROM node:slim
RUN mkdir /app
WORKDIR /app
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/
CMD [ "npm", "start" ]

把這個(gè) Dockerfile 放到 Node.js 項(xiàng)目的根目錄,構(gòu)建好鏡像后,就可以直接拿來(lái)啟動(dòng)容器運(yùn)行。但是如果我們還有第二個(gè) Node.js 項(xiàng)目也差不多呢?好吧,那就再把這個(gè) Dockerfile 復(fù)制到第二個(gè)項(xiàng)目里。那如果有第三個(gè)項(xiàng)目呢?再?gòu)?fù)制么?文件的副本越多,版本控制就越困難,讓我們繼續(xù)看這樣的場(chǎng)景維護(hù)的問(wèn)題。

如果第一個(gè) Node.js 項(xiàng)目在開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)這個(gè) Dockerfile 里存在問(wèn)題,比如敲錯(cuò)字了、或者需要安裝額外的包,然后開(kāi)發(fā)人員修復(fù)了這個(gè) Dockerfile,再次構(gòu)建,問(wèn)題解決。第一個(gè)項(xiàng)目沒(méi)問(wèn)題了,但是第二個(gè)項(xiàng)目呢?雖然最初 Dockerfile 是復(fù)制、粘貼自第一個(gè)項(xiàng)目的,但是并不會(huì)因?yàn)榈谝粋€(gè)項(xiàng)目修復(fù)了他們的 Dockerfile,而第二個(gè)項(xiàng)目的 Dockerfile 就會(huì)被自動(dòng)修復(fù)。

那么我們可不可以做一個(gè)基礎(chǔ)鏡像,然后各個(gè)項(xiàng)目使用這個(gè)基礎(chǔ)鏡像呢?這樣基礎(chǔ)鏡像更新,各個(gè)項(xiàng)目不用同步 Dockerfile 的變化,重新構(gòu)建后就繼承了基礎(chǔ)鏡像的更新?好吧,可以,讓我們看看這樣的結(jié)果。那么上面的這個(gè) Dockerfile 就會(huì)變?yōu)椋?/p>

FROM node:slim
RUN mkdir /app
WORKDIR /app
CMD [ "npm", "start" ]

這里我們把項(xiàng)目相關(guān)的構(gòu)建指令拿出來(lái),放到子項(xiàng)目里去。假設(shè)這個(gè)基礎(chǔ)鏡像的名字為 my-node 的話,各個(gè)項(xiàng)目?jī)?nèi)的自己的 Dockerfile 就變?yōu)椋?/p>

FROM my-node
COPY ./package.json /app
RUN [ "npm", "install" ]
COPY . /app/

基礎(chǔ)鏡像變化后,各個(gè)項(xiàng)目都用這個(gè) Dockerfile 重新構(gòu)建鏡像,會(huì)繼承基礎(chǔ)鏡像的更新。

那么,問(wèn)題解決了么?沒(méi)有。準(zhǔn)確說(shuō),只解決了一半。如果這個(gè) Dockerfile 里面有些東西需要調(diào)整呢?比如 npm install 需要統(tǒng)一加一些參數(shù),那怎么辦?這一行 RUN 是不可能放入基礎(chǔ)鏡像的,因?yàn)樯婕暗搅水?dāng)前項(xiàng)目的 ./package.json,難道又要一個(gè)個(gè)修改么?所以說(shuō),這樣制作基礎(chǔ)鏡像,只解決了原來(lái)的 Dockerfile 的前4條指令的變化問(wèn)題,而后面三條指令的變化則完全沒(méi)辦法處理。

ONBUILD 可以解決這個(gè)問(wèn)題。讓我們用 ONBUILD 重新寫(xiě)一下基礎(chǔ)鏡像的 Dockerfile:

FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

這次我們回到原始的 Dockerfile,但是這次將項(xiàng)目相關(guān)的指令加上 ONBUILD,這樣在構(gòu)建基礎(chǔ)鏡像的時(shí)候,這三行并不會(huì)被執(zhí)行。然后各個(gè)項(xiàng)目的 Dockerfile 就變成了簡(jiǎn)單地:

FROM my-node

是的,只有這么一行。當(dāng)在各個(gè)項(xiàng)目目錄中,用這個(gè)只有一行的 Dockerfile 構(gòu)建鏡像時(shí),之前基礎(chǔ)鏡像的那三行 ONBUILD 就會(huì)開(kāi)始執(zhí)行,成功的將當(dāng)前項(xiàng)目的代碼復(fù)制進(jìn)鏡像、并且針對(duì)本項(xiàng)目執(zhí)行 npm install,生成應(yīng)用鏡像。

Maven

類似Java,Go等編譯型項(xiàng)目,可以使用ONBUILD指令進(jìn)行優(yōu)化Dockerfile。

編寫(xiě)onbuild Dockerfile如下:

FROM maven:3-jdk-8

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

ONBUILD ADD . /usr/src/app

ONBUILD RUN mvn install

然后所有依賴maven編譯的項(xiàng)目Dockerfile可以簡(jiǎn)化成如下形式:

FROM maven:3.3-jdk-8-onbuild
CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

關(guān)于怎么看待Dockerfile最佳實(shí)踐問(wèn)題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒(méi)有解開(kāi),可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI