Kubernetes-基于Dockerfile構建docker鏡像實踐

d0dwreplica 6年前發布 | 24K 次閱讀 Kubernetes

1、Dockerfile文件和核心指令

在Kubernetes中運行容器的前提是已存在構建好的鏡像文件,而通過Dockerfile文件構建鏡像是最好方式。Dockerfile是一個文本文件,在此文件中的可以設置各種指令,以通過docker build命令自動構建出需要的鏡像。Dockerfile文件必需以FROM命令開始,然后按照文件中的命令順序逐條進行執行。在文件以#開始的內容會被看做是對相關命令的注釋。

# Comment 

INSTRUCTION arguments

下面是一個典型的Dockerfile文件,此Dockerfile用于構建一個docker鏡像倉庫的鏡像。Dockerfile文件的格式如下,在文件中對于大小寫是不敏感的。但是為了方便的區分命令和參數,一般以大寫的方式編寫命令。此鏡像的基礎鏡像為alpine:3.4,構建一個docker鏡像倉庫的鏡像:

# Build a minimal distribution container

FROM alpine:3.4

RUN set -ex \

&& apk add --no-cache ca-certificates apache2-utils

COPY ./registry/registry /bin/registry

COPY ./registry/config-example.yml /etc/docker/registry/config.yml

VOLUME ["/var/lib/registry"]

EXPOSE 5000

COPY docker-entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]

CMD ["/etc/docker/registry/config.yml"]

1.1 FROM:設置基礎鏡像

FROM命令為后續的命令設置基礎鏡像,它是Dockerfile文件的第一條命令,FROM命令的格式如下:

FROM <image>[:<tag>] [AS <name>]

1.2 RUN:設置構建鏡像時執行的命令

RUN命令有兩種格式,下面是shell格式的RUN命令,在Linux中RUN的默認命令是/bin/sh;在Windows中默認命令為cmd /S /C:

RUN <command>

下面是exec格式的RUN命令:

RUN ["executable", "param1", "param2"]

RUN指令將會在當前鏡像頂部的新層中執行任何命令,并提交結果。提交的結果鏡像將用于Dockerfile文件的下一步。分層RUN指令和生成提交符合Docker的核心概念,容器可以從鏡像歷史中的任何點鏡像創建,非常類似于源代碼管理。

1.3 CMD:設置容器的默認執行命令

CMD指令的主要目的是為容器提供一個默認的執行命令,在一個Dockerfile只能有一條CMD指令,如果設置多條CMD指令,只有最后一條CMD指令會生效。The CMD指令有如下三種格式:

exec格式,這是推薦的格式:

CMD ["executable","param1","param2"]

為ENTRYPOINT提供參數:

CMD ["param1","param2"]

shell格式:

CMD command param1 param2

如果在Dockerfile中,CMD被用來為ENTRYPOINT指令提供參數,則CMD和ENTRYPOINT指令都應該使用exec格式。當基于鏡像的容器運行時,將會自動執行CMD指令。如果在docker run命令中指定了參數,這些參數將會覆蓋在CMD指令中設置的參數。

1.4 ENTRYPOINT:設置容器為可執行文件

通過ENTRYPOINT指令可以將容器設置作為可執行的文件,ENTRYPOINT 有兩種格式:

exec格式,這是推薦的格式:

ENTRYPOINT ["executable", "param1", "param2"]

shell格式:

ENTRYPOINT command param1 param2

下面是是啟動一個nginx的例子,端口為80:

docker run -i -t --rm -p 80:80 nginx

docker run <image>命令行參數將會被追加到exec格式的ENTRYPOINT所有元素之后,并將會覆蓋使用CMD指定的所有元素。這就允許江參數傳遞到入口點,例如,docker run <Image> -d 將通過-d 參數傳遞到入口點。可以使用docker run --entrypoint 字段覆蓋“ENTRYPOINT ”指令。如果在Dockerfile文件設置了多條ENTRYPOINT指令,則只會生效最后的一條指令。

1.4.1 ENTRYPOINT指令exec格式示例:

可以使用ENTRYPOINT 的exec形式來設置相對穩定的默認命令和參數,然后使用任何形式的CMD指令來設置可能發生變化的參數。

FROM ubuntu

ENTRYPOINT ["top", "-b"]

CMD ["-c"]

當運行容器是,可以看到只有一個top進程在運行:

$ docker run -it --rm --name test top -H

top - 08:25:00 up 7:27, 0 users, load average: 0.00, 0.01, 0.05

Threads: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie

%Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

KiB Mem: 2056668 total, 1616832 used, 439836 free, 99352 buffers

KiB Swap: 1441840 total, 0 used, 1441840 free. 1324440 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND

1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top

通過docker exec命令,能夠參考容器的更多信息。

$ docker exec -it test ps aux

USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND

root 1 2.6 0.1 19752 2352 ? Ss+ 08:24 0:00 top -b -H

root 7 0.0 0.1 15572 2164 ? R+ 08:25 0:00 ps aux

下面的Dockerfile顯示使用ENTRYPOINT在前臺運行Apache:

FROM debian:stable

RUN apt-get update && apt-get install -y --force-yes apache2

EXPOSE 80 443

VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]

ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

1.4.2 ENTRYPOINT指令的shell格式

通過為ENTRYPOINT指定文本格式的參數,此參數將在/bin /sh -c 中進行執行。這個形式將使用shell處理,而不是shell環境變量,并且將忽略任何的CMD或docker run運行命令行參數。

FROM ubuntu

ENTRYPOINT exec top -b

1.4.3 CMD和ENTRYPOINT交互

CMD和ENTRYPOINT指令都可以定義容器運行時所執行的命令,下面是它們之間協調的一些規則:

1)在Dockerfile至少需要設置一條CMD或者ENTRYPOINT指令;

2)當將容器作為可執行文件使用時,建議定義ENTRYPOINT指令;

3)CMD作為為ENTRYPOINT命令定義默認參數的一種方式;

4)當使用帶有參數的命令運行容器時,CMD將會被覆蓋。

下表是顯示了不同的ENTRYPOINT / CMD指令組合的命令執行情況:

No ENTRYPOINT ENTRYPOINT exec_entry p1_entry ENTRYPOINT [“exec_entry”, “p1_entry”]

No CMD 報錯,這種情況不運行出現 /bin/sh -c exec_entry p1_entry exec_entry p1_entry

CMD [“exec_cmd”, “p1_cmd”] exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry exec_cmd p1_cmd

CMD [“p1_cmd”, “p2_cmd”] p1_cmd p2_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry p1_cmd p2_cmd

CMD exec_cmd p1_cmd /bin/sh -c exec_cmd p1_cmd /bin/sh -c exec_entry p1_entry exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

1.5 ENV:設置環境變量

Env指令通過<鍵>和<值>對設置環境變量。此值將在環境中用于生成階段中的所有后續指令,并且也可以在許多情況下被替換為內聯。

“Env”指令有兩種形式。第一種形式,即ENV <Key>  < value >,將一個變量設置為一個值。第一個空間之后的整個字符串將被處理為“<值>”,包括空白字符。

ENV <key> <value>

第二種形式,即ENV <Key>=Value>…,允許一次設置多個變量。注意,第二個表單在語法中使用等號(=),而第一個表單則不使用。與命令行解析一樣,引用和反斜杠可用于在值內包含空格。

ENV <key>=<value> ...

例如:

ENV myName="John Doe" myDog=Rex\ The\ Dog \

myCat=fluffy

和:

ENV myName John Doe

ENV myDog Rex The Dog

ENV myCat fluffy

1.6 ADD:添加內容到容器中

ADD指令用于從當前機器或遠程URL中的<src>中拷貝文件、目錄,并將它們添加到鏡像文件系統的<dest>中。在指令中能夠設置多個<src>,--chown僅僅在構建Linux容器鏡像時起作用,ADD指令有兩種格式:

ADD [--chown=<user>:<group>] <src>... <dest>

下面的ADD指令格式可以運行源和目標路徑包含空格。

ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

<src>可以包含通配符,例如:

ADD hom* /mydir/ # 添加所有以"hom"開頭的文件到鏡像中的/mydir目錄下。

ADD hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt"

<dest>是容器一個絕對路徑,或者是一個相對于WORKDIR的相對路徑,

ADD test relativeDir/ # 添加"test"到容器中 WORKDIR /relativeDir/

ADD test /absoluteDir/ # 添加"test"到容器中的/absoluteDir/

ADD指令遵循下面的規則:

<src>路徑必需在構建的上下文中;不能使用 ADD ../someting /someting,這是因為docker build的第一步就是發送上下文目錄給docker daemon。

如果<src>是一個URL,并且<dest>不是以斜線結束的情況,則會從URL中下載一個文件,并將其拷貝到<dest>;

如果<src>是一個URL,并且<dest>以斜線結束,則會然后從URL中導出文件名,并將文件下載到<dest>/<filename>中。例如:ADD http://example.com/foobar /,則會在容器的/目錄下創建foobar文件,并將URL中foobar文件中的內容復制到容器中/foobar文件中。

如果<src>是一個目錄,那么將會拷貝整個目錄下的內容,并包括文件系統的元數據。需要注意的時,拷貝時,并不會拷貝目錄本身,而只是拷貝目錄下內容。

如果<src>是本地的一個壓縮(例如:gzip、bzip2、xz等格式)文件,則會對其進行解壓縮。對于來自于遠程的URL,則不會進行解壓縮。

如果<src>是一個普通文件,將會直接將文件和它的元數據拷貝到鏡像的<dest>目錄下。

如果指定了多個<src>,如果這些<src>中存在目錄或使用了通配符,則<Dest>必須是一個目錄,并且必須以斜杠/結尾。

如果<dest>不是以斜杠/結尾,它將被認為是一個文件,那么<src>的內容將被寫到<dest>中。

1.7 COPY:拷貝內容到鏡像中

COPY指令用于從<src>中拷貝文件或目錄,并將其添加到鏡像文件系統的<path>目錄下。在指令中可以指定多個< src>資源,但是文件和目錄的路徑將被解釋為相對于當前構建上下文的資源。COPY指令與ADD指令的功能基本上相似,但ADD能夠從遠程拷貝,以及解壓縮文件。COPY指令有兩種格式:

COPY [--chown=<user>:<group>] <src>... <dest>

當目錄中存在空格時,請使用下面的格式:

COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

1.8 WORKDIR:設置當前工作目錄

WORKDIR指令用于為RUN、CMD、ENTRYPOINT、COPY和ADD指令設置當前的工作目錄。如果WORKDIR不存在,則會自動創建一個,即使后續不使用。

WORKDIR /path/to/workdir

在Dockerfile文件中,可以設置多個WORKDIR指令。如果給定了一個相對路徑,則后續WORKDIR設置的路徑是相對于上一個相對路徑的路徑:

WORKDIR /a

WORKDIR b

WORKDIR c

RUN pwd

在Dockerfile中,最后的pwd命令輸出的為:/a/b/c

1.9 EXPOSE:設置暴露的端口

EXPOSE指令告知docker,容器在運行時將監聽指定哪個指定的網絡端口。并可以指定端口的協議是TCP或UDP,如果沒有指定協議,則默認為TCP協議。EXPOSE指令的格式如下:

EXPOSE <port> [<port>/<protocol>...]

“EXPOSE”指令實際上并不發布端口,它在構建鏡像的人員和運行容器的人員之間起著文檔告知的作用。要在運行容器時實際發布端口,則需要通過在docker run命令使用-p和-P來發布和映射一個或者多個端口。

1.10 LABEL:設置鏡像的元數據信息

LABEL指令擁有為鏡像添加一些描述的元數據。LABEL是一系列的鍵值對,它的格式如下:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

下面是LABEL指令的示例:

LABEL "com.example.vendor"="ACME Incorporated"

LABEL com.example.label-with-value="foo"

LABEL version="1.0"

LABEL description="This text illustrates \

that label-values can span multiple lines."

通過docker inspect命令,可以查看鏡像中的標簽信息:

"Labels": {

"com.example.vendor": "ACME Incorporated"

"com.example.label-with-value": "foo",

"version": "1.0",

"description": "This text illustrates that label-values can span multiple lines.",

"multi.label1": "value1",

"multi.label2": "value2",

"other": "value3"

},

1.12 VOLUME:設置存儲卷

VOLUME指令用于創建一個帶有指定名稱的掛載點,并將其標記為來自于本地主機或其他容器的存儲卷。該值可以是JSON數組、VOLUME ["/var/log/“],或者是具有多個參數的普通字符串,例如VOLUME /var/log 或 VOLUME /var/log /var/db。

VOLUME ["/data"]

2、構建鏡像

在定義后Dockerfile文件,并準備好相關的內容后,就可以通過docker build命令從Dockerfile和上下文構建docker鏡像。構建的上下文是位于指定路徑或URL中的文件集合。構建過程可以引用上下文中的任何文件。例如,您的構建可以使用復制指令來引用上下文中的文件。

docker build [OPTIONS] PATH | URL | -

2.1 命令選項

名稱 默認值 描述

--add-host   添加定制 host-to-IP映射(host:ip)

--build-arg   設置構建時的變量

--cache-from   考慮被作為緩存源的鏡像

--cgroup-parent   容器的可選父cgroup

--compress   使用gzip壓縮構建上下文

--cpu-period   限制CPU CFS(完全公平調度程序)周期

--cpu-quota   限制CPU CFS(完全公平調度程序)配額

--cpu-shares , -c   CPU份額(相對權重)

--cpuset-cpus   允許執行的CPU(0-3,0,1)

--cpuset-mems   允許執行的內存(0-3,0,1)

--disable-content-trust true 忽略鏡像驗證

--file , -f   Dockerfile文件的名稱(默認值為”PATH/Dockerfile“)

--force-rm   總是移除中間容器

--iidfile   將鏡像ID寫入文件

--isolation   容器隔離技術

--label   為鏡像設置元數據

--memory , -m   內存限制

--memory-swap   Swap限制等于內存加swap:“-1”允許無限swap

--network   在構建期間,為RUN指令設置聯網模式

--no-cache   在構建鏡像時不使用緩存

--platform   如果服務器是多平臺能力的,設置平臺

--pull   一直嘗試拉取鏡像的最新版本

--quiet , -q   抑制構建輸出和打印鏡像ID

--rm true 成功構建后,移除中間容器

--security-opt   安全選項

--shm-size   /dev/shm的大小

--squash   將新建的層擠壓成一個新的層

--stream   流連接到服務器,以協商構建的上下文

--tag , -t   為構建的鏡像以”name:tab“格式打上標簽

--target   設置目標構建階段進行構建

--ulimit   Ulimit選項

1.2 URL參數

URL參數可以引用三種資源:Git存儲庫、預打包的tabball上下文和純文本文件,本文主要描述如何使用Git倉庫構建鏡像。當 URL 參數指向一個Git倉庫的位置,倉庫將作為構建的上下文。系統的遞歸獲取庫及其子模塊,提交歷史不保存。倉庫是首先被拉取到本地主機的臨時目錄。成功后,此臨時目錄被發送給Docker daemon作為構建上下文。

Git URL接受的上下文配置,由冒號分隔:進行分割。第一部分表示Git將簽出的引用,可以是分支、標簽或遠程引用。第二部分表示存儲庫內的子目錄,該目錄將用作構建上下文。

例如:使用container分支的docker目錄構建鏡像:

$ docker build https://github.com/docker/root ... ocker

下面是通過git構建鏡像的合法表達:

建立語法后綴 提交使用 構建上下文使用

myrepo.git refs/heads/master /

myrepo.git#mytag refs/tags/mytag /

myrepo.git#mybranch refs/heads/mybranch /

myrepo.git#pull/42/head refs/pull/42/head /

myrepo.git#:myfolder refs/heads/master /myfolder

myrepo.git#master:myfolder refs/heads/master /myfolder

myrepo.git#mytag:myfolder refs/tags/mytag /myfolder

myrepo.git#mybranch:myfolder refs/heads/mybranch /myfolder

1.3 構建示例

下面是通過本地路徑構建一個私有鏡像倉庫鏡像的示例,在此示例中,通過-t設置了鏡像的標簽為registry:latest;構建上下文為當前執行命令所在的目錄,Dockerfile為當前上下文中的文件。

$ docker build -t registry:latest .

下面是通過Git倉庫構建鏡像的示例:

$ docker build -t regiestry:latest https://github.com/docker/dist ... e.git

3、最佳實踐

1)不安裝不必要的包

為了減少復雜性、依賴性、文件大小和構建時間,避免安裝額外的或不必要的包。

2)最小化層的數量

在舊版本的Docker中,最小化鏡像中的層數是非常重要,這樣可以確保它們的性能。添加以下特征能夠減少這種限制:

在docker 1.10和更高版本中,只有RUN、COPY和ADD會創建層。其他指令僅會創建臨時的中間鏡像,并且不直接增加構建的大小。

在docker17.05和更高版本中,您可以進行多階段構建,只將需要的工件復制到最終鏡像中。這允許您在中間構建階段中包含工具和調試信息,而不增加最終鏡像的大小。

3)解耦應用

每個容器應該只關注一個業務問題。將應用程序分解到多個容器中,從而可以更容易地進行水平擴容和重用。例如,Web應用程序棧可能由三個單獨的容器組成,每個容器都有自己的鏡像,以解耦的方式管理Web應用程序、數據庫和內存緩存。盡最大的努力使容器盡可能保持清晰和模塊化。如果容器相互依賴,可以使用docker容器的網絡來確保這些容器可以進行通信。

4)排序多行參數

只要有可能,盡量按字母順序排序多行參數,可以減輕以后的變化。這有助于避免重復包,并使列表更容易更新。

下面是buildpack-deps鏡像的一個例子:

RUN apt-get update && apt-get install -y \

bzr \

cvs \

git \

mercurial \

subversion

5)利用構建緩存

在構建鏡像時,Docker會通過Dockerfile文件中的指令,并按指定的順序執行每一個指令。在檢查每個指令時,Docker會在緩存中尋找可重用的現有圖像,而不是創建新的(重復的)圖像。

如果您根本不想使用緩存,可以在docker構建命令上使用--no-cache=true選項。但是,如果讓Docker使用緩存,則需要了解它何時能找到匹配的鏡像。docker遵循的基本規則如下:

從已經存在于緩存中的父鏡像開始,將下一條指令與從該基礎鏡像派生的所有子鏡像進行比較,以查看其中是否使用完全相同的指令構建了其中的一個子鏡像。如果沒有,則緩存無效。

在大多數情況下,簡單地將Dockerfile文件中的指令與其中一個子鏡像中指令進行比較就足夠了。然而,某些指令需要更多的檢查和解釋。

對于ADD和COPY指令,檢查鏡像中文件的內容,并為每個文件計算校驗和。這些校驗和中未考慮文件的最后修改和上次訪問時間。在緩存查找期間,將校驗和與現有鏡像中的校驗和進行比較。如果文件中的任何內容(如內容和元數據)發生變化,則緩存被無效。

除了ADD和COPY命令之外,緩存檢查并不查看容器中的文件來確定緩存匹配情況。例如,在處理RUN apt-get -y update更新命令時,不檢查容器中更新的文件,以確定是否存在緩存命中。在這種情況下,僅使用命令字符串本身來查找匹配項。

一旦緩存失效,所有后續Dockerfile命令都生成新的圖像,并且不使用緩存。

6)盡量使用官方的alphine鏡像作為基礎鏡像

只要有可能,使用當前官方的鏡像基礎。建議使用alpine鏡像,因為它尺寸會被嚴格控制(目前低于5 MB),但仍然是一個完整的Linux發行版。

7)ADD和COPY的使用

雖然ADD和COPY功能類似,一般來說,優先使用COPY,那是因為COPY比ADD更透明。COPY只支持將本地文件拷貝到容器中

如果需要將構建上下文中多個文件拷貝到鏡像中,請使用COPY指令分開進行拷貝。

參考資料

1.《docker build》地址: https://docs.docker.com/engine ... uild/

2.《Dockerfile reference》地址: https://docs.docker.com/engine/reference/builder/

3.《Best practices for writing Dockerfiles》地址: https://docs.docker.com/develo ... ices/

作者簡介:

季向遠,北京神舟航天軟件技術有限公司產品經理。本文版權歸原作者所有。

 

來自:http://dockone.io/article/5998

 

 本文由用戶 d0dwreplica 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!