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

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/rootfs.git#container:docker

下面是通過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/distribution-library-image.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指令分開進行拷貝。

本文轉自中文社區-Kubernetes-基於Dockerfile構建docker鏡像實踐

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章