Docker Dockerfile

鏡像的生成途徑:html

  • Dockerfile
  • 基於容器製做

本篇介紹Dockerfile。node

文件說明

Dockerfile是一個包含用於組合映像的命令的文本文檔。Docker經過讀取Dockerfile中的指令自動生成鏡像。nginx

基本結構

基本格式:web

# Comment
INSTRUCTION arguments

主要就2類一句,第一行是註釋,第二行是指令。
指令(INSTRUCTION)大小寫不敏感,爲了將指令和參數或其餘內容區分,一般指令使用全大寫。
Docker以從上到下的順序運行Dockerfile的指令。爲了指定基本映像,第一條指令必須是FROM。一個聲明以#字符開頭則被視爲註釋。能夠在Docker文件中使用RUN,CMD,FROM,EXPOSE,ENV等指令。docker

工做目錄

製做鏡像,首先要有一個文件目錄,即工做目錄。
製做鏡像所引用的文件,都必須在工做目錄下。
Dockerfile文件,文件名就是Dockerfile(首字母大寫),寫docker指令。
.dockeringore文件,寫在該文件中的路徑在打包時都不會打包。shell

docker build命令用於從Dockerfile構建鏡像。能夠在docker build命令中使用-f標誌指向文件系統中任何位置的Dockerfile。vim

環境變量

製做鏡像的過程當中,可使用環境變量。
直接爲一個變量名賦值,這種只會在當前shell中有效。可是對在當前shell(父shell)中啓動的其餘shell(子shell)無效。好比賦值以後,調用了一個.sh腳本,在這個腳本中沒有以前賦值的變量。
通常就用export命令來建立環境變量,這樣是全局都有效的。
不管哪一種狀況,只要會話關閉,就所有失效了。要想永久有效須要編輯文件。centos

引用變量
可使用$KEY${KEY}來使用環境變量。不帶大括號的格式以後必須有空格,若是以後沒有空格而是要接着其餘內容,就須要用帶大括號的格式。
另外,${KEY}這種形式還支持變量替換的特殊格式:數組

  • \${KEY:-VALUE}: VALUE是默認值,若是變量不存在就使用默認值
  • \${KEY:+VALUE}: 若是VALUE有值,則返回VALUE,不然返回空

Dockerfile 指令

在這裏列出了一些經常使用的指令。緩存

FROM

FROM指定是最重要的一個指定,而且必須爲Dockerfile文件開篇的第一個非註釋行,用於爲映像文件構建過程指定基本鏡像,後續的指令運行於此基準鏡像所提供的運行環境中。

實踐中,基準鏡像能夠是任何可用鏡像文件。默認狀況下,dockerfile會在docker主機上查找指定的鏡像文件,若是不存在,會自動從Registry上拉取。

語法格式:

FROM <repository>[:<tag>]
FROM <repository>@<digest>

上面兩種格式均可以。第一行和拉取鏡像或者運行鏡像同樣,一個鏡像能夠有多個版本,能夠經過tag指定。第二行的格式是經過鏡像的ID來指定,由於ID是惟一的,因此這種方式能夠對鏡像進行校驗。基於名字引用鏡像可能會有安全問題,防止引用被修改了名字的含有惡意代碼的鏡像。

MAINTAINER

維護者信息。該指令在舊版本中使用,建議用下面的LABEL來指定。

語法格式:

MAINTAINER <name>

能夠是任何形式的文本信息,但約定俗成地使用做者名及郵箱地址:

MAINTAINER Steed Xu <x749b@163.com>

LABEL

用戶能夠爲鏡像指定各類元數據。

語法格式:

LABEL <key1>=<value1> <key2>=<value2> <key3>=<value3> ...

使用LABEL指定元數據時,一條LABEL指定能夠指定一或多條元數據,指定多條元數據時不一樣元數據之間經過空格分隔。推薦將全部的元數據經過一條LABEL指令指定,以避免生成過多的中間鏡像。

COPY

用於從Docker主機複製文件至建立的新映像文件。

語法格式:

COPY <src> ... <dest>
COPY ["<src>", ... "<dest>"]

src是要複製的源文件或目錄,支持使用通配符問號(?)和星號(*)。通常使用相對路徑,起始路徑就是工做目錄。
dest爲目標路徑,通常爲絕對路徑。若是是相對路徑,起始路徑以WORKDIR(後面會講)指定。

上面的兩種格式均可以,第二行的列表格式能夠支持包含空格的路徑。

文件複製準則:

  • src必須在工做目錄中
  • 若是src是目錄,其內部文件和子目錄會遞歸複製,但src自己不會被複制
  • 若是指定多個src,或src中使用了通配符,則dest必須是一個目錄,必須以/結尾
  • 若是dest不存在,它將會被自動建立,包括其父目錄的路徑

ADD

ADD與COPY相似,ADD支持使用tar文件和url路徑。tar文件會自動解壓,url會下載相似wget。

語法格式:

ADD <src> ... <dest>
ADD ["<src>", ... "<dest>"]

操做準則:

  • 若是src爲url,且dest不以/結尾,則下載文件並建立爲dest。若是dest以/結果,則將文件下載到dest的目錄下。
  • 只有本地的tar文件,會自動展開。若是是url,下載下來的文件是tar文件,這個不會再自動展開。
  • 若是src有多個,或者使用了通配符,dest以/結尾,則全部文件下載到dest目錄下。若是dest不以/結尾,則被視爲文件,src的內容將直接寫入到dest。

WORKDIR

用於爲Dockerfile中全部的RUN、CMD、ENTRYPOINT、COPY和ADD指令設定工做目錄。
工做目錄不只影響以後命令的起始路徑,也會影響容器啓動後的工做目錄,默認是根目錄。

語法格式:

WORKDIR <dirpath>

WORKDIR能夠出現屢次,路徑也能夠是相對路徑。相對路徑就是相對前一個WORKDIR指定指定的路徑。
WORKDIR還能夠調用由ENV(後面會講)指令定義的變量。

示例:

# 工做目錄爲/a
WORKDIR /a
# 工做目錄爲/a/b
WORKDIR b
# 工做目錄爲/a/b/c
WORKDIR c

VOLUME

用於在image中建立一個掛載點目錄。
在dockerfle中只能指定掛載點,沒法指定宿主機上的路徑。因此這裏的掛載的只能是docker管理的卷。

語法格式:

VOLUME <mounpoint>
VOLUME ["<mounpoint1>", "<mounpoint2>", "<mounpoint3>"]

若是掛載點目錄路徑下以前存在文件,docker run 命令會在卷掛載完成後將此前的全部文件複製到新掛載的卷中。

EXPOSE

用於爲容器打開指定要監聽的端口以實現對外部通訊。

語法格式:

EXPOSE <port> [<port> ...]

默認是TCP協議,可使用/tcp或/udp來指定。指令還能夠一次指定多個端口,示例:

EXPOSE 11211/tcp 11211/udp

ENV

用於爲鏡像定義所需的環境變量,並可被Dockerfile文件中位於其後的其它指令所調用。調用環境變量在開頭已經講過了。

語法格式:

ENV <key> <value>
ENV <key1>==<value1> <key2>==<value2> ...

第一種格式,key以後的全部內容都會被視爲value,這樣一次只能設置一個變量。
第二種格式,能夠一次設置多個變量,若是value中包含空格,就用反斜槓(\)轉義,也能夠經過對value加引號進行標識。另外反斜槓也可用於續行。
建議使用第二種格式時,用上反斜槓續行,一行設置一對鍵值對:

ENV key1=value1 \
    key2=value2

容器的環境變量
除了製做鏡像時能夠設置環境變量,在啓動容器時也能夠設置環境變量。docker container run啓動容器是使用-e參數:

-e, --env list                       Set environment variables

製做鏡像時的設置的環境變量會影響ENV語句後面的指令。而且會一直保留這個環境變量,在容器運行時依然有效。在啓動容器時能夠設置新的環境變量或者把以前的環境變量的值替換掉。這不會影響到以前鏡像製做的過程當中使用環境變量的值。鏡像製做時遇到環境變量,會直接獲取到當前該變量額值並進行操做。
小結:

  • 製做鏡像時設置的環境變量不但在製做鏡像過程當中有效,也能夠把容器運行時須要的環境變量提早設置好。
  • 容器運行時設置的環境變量,只會對容器運行的過程有影響,不會影響到以前鏡像製做過程當中的操做。
  • 上面的狀況也有例外,好比容器默認執行的命令中帶有環境變量,這個命令的內容就是環境變量而不是環境變量的值,因此直接把環境變量的引用寫到默認命令中。等到容器啓動的時候再運行這個命令,此時纔會去獲取當前環境變量的值,也就是被run命令的-e參數改變以後的值。

printenv 查看環境變量
使用printenv命令能夠查看環境變量,env命令不帶參數也同樣:

$ docker container run --rm -e k1=v1 -e k2=v2 tinyweb1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=ee6793a43c8c
k1=v1
k2=v2
HOME=/root
$

這裏就是啓動容器時,指定運行printenv命令便可。

RUN

用於指定docker build過程當中運行的程序,能夠是任何命令。

語法格式:

RUN <command>
RUN ["executable", "param1", "param2"]

RUN指令可使用屢次。不過對於先後有關聯的一系列指令,建議多條指令之間使用&&鏈接成爲一條指令,再用續行符讓一條指令寫一行增長可讀性。

經過shell來啓動指令
第一種格式中,command是一條完整的shell命令,而且是以/bin/sh -c來運行的。
第二種格式是一個數組,executable是要運行的命令,後面都是要傳遞的參數。此種格式的命令不會以/bin/sh -c來發起,而是直接之內核建立進程。所以,常見的shell特性如變量替換以及通配符都無效。不過,若是須要依賴shell特性的話,能夠用下面的格式:

RUN ["/bin/bash", "-c", "executable param1 param2"]

這裏注意,最終要執行的命令包括這個命令的參數須要寫成一個完整的字符串做爲列表的一個元素,不能拆開。後面的製做鏡像的示例中會有分析。

安裝應用程序
大多數鏡像在基於基礎鏡像安裝應用程序時,都是編譯安裝的。這個編譯安裝的過程就須要RUN指令。
固然也可使用yum安裝。不過須要注意,yum安裝過程當中是會生成緩存的。這些緩存是不會自動刪除,應該清除掉以節約鏡像空間。下面的命令能夠清除yum緩存:

yum clean all

另外,緩存的內容是保存在/var/cache/yum/目錄下的。因此也能夠把目錄下的文件刪除。

清除緩存
這部分的內容沒有驗證過,可能和上面是同樣的做用。
RUN指令建立的中間鏡像會被緩存,並會在下次構建中使用(這裏的使用不是真的要用,而是由於這些緩存沒有用了,卻要佔用鏡像空間,應該要清除掉)。若是不想使用這些緩存鏡像,能夠在構建時指定--no-cache參數:

docker build --no-cache

CMD

爲啓動容器指定默認要運行的程序。這個就是容器中PID爲1的進程,其運行結束後,容器也將終止。CMD指定相似於RUN指令,也是運行任何命令或應用程序,不過兩者的運行時間點不一樣。RUN是在製做鏡像時執行的。CMD是在容器啓動後執行的,而且只有最後一條CMD指令有效,就是指定容器的主進程。

語法格式:

CMD <command>
CMD ["executable", "param1", "param2"]
CMD ["param1", "param2"]

語法格式也和RUN是同樣的,去對比查看RUN的指令格式說明和兩種格式的差異便可。
這裏還有第三種格式,內容形式上和第二種是沒有差異的(形式上都是字符串,體現不出是命令仍是參數)。在設置了ENTRYPOINT(後面會講)時,CMD列表裏的元素就都做爲ENTRYPOINT指令的參數了,因此就沒有命令了。

下面兩小段是引伸出來的內容,和使用容器無關。

exec命令
這一小段的內容只是要了解一下容器中啓動的命令是如何成爲PID爲1的主進程的。
同RUN命令同樣,第一種格式默認是用過/bin/sh -c啓動的。這意味着啓動的進程在容器中的PID不爲1,不能接收Unix信號。所以,當使用docker stop命令中止容器時,此進程接收不到SIGTERM信號。而且shell纔是PID爲1的那個進程,那麼程序啓動完以後,容器就中止了。默認應該是使用了exec命令的機制,調用要啓動的進程而且新的進程會替代父進程,也就是成爲PID爲1的進程。

在後臺運行程序
這一小段的內容和容器無關,是講一下傳統的作法是如何直接在宿主機上啓動後臺程序的。
傳統的方式在宿主機上運行應用,要啓動這些服務,可使用systemctl start命令。
還有一種方式,能夠手動啓動,就是在命令行中執行命令。每個進程都應該是某一個進程的子進程。手動啓動的程序默認是做爲shell的子進程存在的。有些程序啓動後會佔據當前shell的終端,能夠在命令後加&符號,讓程序在後臺運行。不過這並無改變這個應用程序是shell的子進程,一旦退出當前shell,任何一個進程終止時,都會先把它的子進程終止。要避免這種狀況,還須要nobub命令,讓以後的命令啓動的進程剝離於當前shell進程的關係,使得程序能夠在退出shell時繼續運行。
因此可使用下面的方式啓動程序而且保持運行:

nohup COMMAND &
nohup COMMAND > /dev/null 2>&1 &

ENTRYPOINT

相似CMD指令的功能,用於爲容器指定默認運行程序。可是有ENTRYPOINT啓動的程序不會被docker run命令指定的參數所覆蓋。並且命令行參數會被當作參數傳遞給ENTRYPOINT指令指定的程序。
在docker run的時候,仍是能夠經過--entrypoint參數來覆蓋ENTRYPOINT指令指定的程序。

語法格式:

ENTRYPOINT <command>
ENTRYPOINT ["executable", "param1", "param2"]

docker run命令傳入的命令參數會覆蓋CMD指令的內容而且附加到ENTRYPOINT命令最後做爲其參數使用。

關於ENTRYPOINT的應用,仍是看下以後的動態生成配置文件的示例。

USER

用於指定運行image時,或運行Dockerfile中任何RUN、CMD、ENTRYPOINT指令指定的程序時的用戶名或UID。默認狀況下,container運行身份爲root用戶。

語法格式:

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

這裏的uid、gid能夠是任意數字,可是必須爲有效的,不然docker run命令將會失敗。

HEALTHCHECK

HEALTHCHECK指令告訴Docker如何測試容器以檢查它是否仍在工做。即便服務器進程仍在運行,這也能夠檢測到陷入無限循環且沒法處理新鏈接的Web服務器等狀況。

語法格式:

HEALTHCHECK [OPTIONS] CMD <command>
HEALTHCHECK NONE

第一行,經過在容器內運行命令來檢查容器運行情況。
第二行,禁用從基礎映像繼承的任何運行情況檢查。
容器默認就能夠有健康狀態檢測的方法。只是默認的方式有的場景不適用,這就須要自定義。CMD是關鍵字,後面跟用於健康監測的命令。

參數說明:

  • --interval: 輪詢時間,默認30秒
  • --timeout: 超時時間,默認30秒
  • --start-priod: 容器啓動多久後開始檢測,默認0秒。若是主進程啓動比較慢,須要設置一下一個參數
  • retries: 確認健康檢查失敗,須要檢查失敗的次數,默認3次。

CMD關鍵字後面跟的就是健康狀態檢查的命令,docker根據這個命令的返回值來判斷健康狀態。運行結果返回值的含義:

  • 0: 成功
  • 1: 不健康
  • 2: 預留值,無心義

指令示例
這是個簡單的例子:

HEALTHCHECK --interval=5m --timeout=3s \
    CMD curl -f http://localhost/ || exit 1

可能沒有curl命令,使用wget命令也是同樣的:

CMD wget -O - -q http://localhost/

這個命令可能還須要修改,每次wget成功都會輸出一條消息。雖然使用了-q靜默了日誌輸出,可是wget下載的內容仍是要輸出的。這裏用了-O參數指定到標準輸出了。但願沒有任何輸出的話仍是經過重定向到/dev/null來解決。

若是檢查的邏輯不是那麼簡單,那麼可能須要寫一個腳原本調用:

HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
    CMD /bin/sh /opt/health_test.sh

健康檢查腳本的內容:

#!/bin/sh
ss -antl | grep 80
if [ $? ==0 ]; then
    exit 0
else
    exit 1
fi

檢查容器健康狀態
當容器指定了運行情況檢查時,除了正常狀態外,它還具備運行情況。這個狀態最初是starting。每當健康檢查經過時,它就會變成healthy(之前的狀態)。通過必定數量的連續失敗後,它就變成了unhealthy。

使用docker ps命令查看容器狀態,能夠看到健康檢查的狀態:

[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                           PORTS                   NAMES
b6dea4e8a808        ngx1-1              "nginx -g 'daemon of…"   2 seconds ago       Up 1 second (health: starting)   0.0.0.0:32776->80/tcp   ngx1
[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS                   NAMES
b6dea4e8a808        ngx1-1              "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds (healthy)   0.0.0.0:32776->80/tcp   ngx1
[root@Docker img4]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                   NAMES
21c80cbd2898        ngx1-1              "nginx -g 'daemon of…"   19 seconds ago      Up 18 seconds (unhealthy)   0.0.0.0:32777->80/tcp   ngx1
[root@Docker img4]#

這裏有3種狀態,啓動後未作檢查的(health: starting),檢查成功的(healthy),確認失敗的(unhealthy)。

SHELL

SHELL指令容許覆蓋用於命令行的默認shell。Linux上的默認shell是["/bin/sh", 「-c」],而在Windows上[「cmd」, 「/S」, 「/C」]。

語法格式:

SHELL [「executable」, 「parameters」]

這裏只有一種格式,必須使用JSON的格式。
瞭解一下,通常用不上

STOPSIGNAL

STOPSIGNAL指令設置將發送到容器的系統調用信號以退出。此信號能夠是與內核的系統調用表中的位置匹配的有效無符號數,例如9,或SIGNAME格式的信號名,例如SIGKILL。

語法格式:

STOPSIGNAL signal

也是瞭解一下,通常用不上,而且這裏也沒有講清楚。

ARG

設置變量命令,用於指定傳遞給構建運行時的變量。ARG命令定義了一個變量,在docker build建立鏡像的時候,使用以下的build命令的選項來指定參數:

--build-arg <varname>=<value>

有多個變量,就屢次使用這個選項。

語法格式:

ARG <name>[=<default value>]

使用ARG定義變量的時候,能夠加上默認值。

在docker build調用時,是沒有--env選項來傳環境變量的值的。因此要使用環境變量就只能使用環境變量默認值。而使用ARG變量,就能夠向ARG變量傳值了。
這個功能使得一個dockerfile能夠適用於多個不一樣的場景。

ONBUILD

用於在Dockerfile中定義一個觸發器。當所構建的鏡像被用作其它鏡像的基礎鏡像時,該鏡像中的觸發器將會被觸發。

語法格式:

ONBUILD <INSTRUCTION>

關鍵字後面跟的是一條正常的dockerfile的指令。

使用包含ONBUILD指令的Dockerfile構建的鏡像應該使用特殊的標籤,好比:ruby:2.0-onbuild

製做鏡像

這裏來製做幾個鏡像,把上面的指令試一下。

httpd鏡像

建立工做目錄:

[root@Docker ~]# mkdir img1
[root@Docker ~]# cd img1/
[root@Docker img1]# echo "<h1>Dockerfile httpd v1</h1>" >> index.html```

進入到工做目錄中,建立一個index.html文件。

建立Dockerfile文件:

[root@Docker img1]# vim Dockerfile
# Description: First image
FROM busybox
LABEL maintainer="Steed Xu <x749b@163.com>"
COPY index.html /var/www/
VOLUME /var/www/
EXPOSE 80

工做目錄建立鏡像:

[root@Docker img1]# pwd
/root/img1
[root@Docker img1]# docker build -t tinyweb1 .
Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM busybox
 ---> db8ee88ad75f
Step 2/5 : LABEL maintainer="Steed Xu <x749b@163.com>"
 ---> Running in 786069882d16
Removing intermediate container 786069882d16
 ---> 890d4ab97cd1
Step 3/5 : COPY index.html /var/www/
 ---> 5ba063c97abb
Step 4/5 : VOLUME /var/www/
 ---> Running in 18351ed13a43
Removing intermediate container 18351ed13a43
 ---> 50c1ae0d6350
Step 5/5 : EXPOSE 80
 ---> Running in 2e7885323425
Removing intermediate container 2e7885323425
 ---> 0656cb8b15bc
Successfully built 0656cb8b15bc
Successfully tagged tinyweb1:latest
[root@Docker img1]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
tinyweb1            latest              0656cb8b15bc        About a minute ago   1.22MB
busybox             latest              db8ee88ad75f        7 days ago           1.22MB
[root@Docker img1]#

運行鏡像:

[root@Docker img1]# docker container run --name app1 --rm -d -P tinyweb1 httpd -f -h /var/www/
[root@Docker img1]# docker container port app1
80/tcp -> 0.0.0.0:32769
[root@Docker img1]#

這裏使用了-P參數,映射的端口是隨機的,使用命令查看到端口映射的狀況。

進入到鏡像內部。這個容器內執行的命令是httpd,裏面沒有shell,因此要用exec起一個shell而後才能進入到內部進行操做:

[root@Docker img1]# docker container exec -it app1 /bin/sh
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 httpd -f -h /var/www/
    7 root      0:00 /bin/sh
   11 root      0:00 ps
/ #

在容器內部使用mount命令查看掛載的卷:

[root@Docker ~]# docker container exec app1 mount | grep /var/www
/dev/mapper/centos-root on /var/www type xfs (rw,seclabel,relatime,attr2,inode64,noquota)
[root@Docker ~]#

使用環境變量

和上面的例子差很少,此次用到了環境變量,而且指定了CMD的命令:

[root@Docker img2]# pwd
/root/img2
[root@Docker img2]# vi Dockerfile 
# Description: Second image
FROM busybox
LABEL maintainer="Steed Xu <x749b@163.com>" \
      app="httpd"
ENV WEB_DOC_ROOT="/var/www/"
COPY index.html $WEB_DOC_ROOT
RUN echo '<h2>Hello</h2>' >> ${WEB_DOC_ROOT}/index.html
CMD httpd -f -h ${WEB_DOC_ROOT}
VOLUME $WEB_DOC_ROOT
EXPOSE 80

建立鏡像:

[root@Docker img2]# docker build -t tinyweb2 .
Sending build context to Docker daemon  3.072kB
Step 1/8 : FROM busybox
 ---> db8ee88ad75f
Step 2/8 : LABEL maintainer="Steed Xu <x749b@163.com>"       app="httpd"
 ---> Running in e32a21495f34
Removing intermediate container e32a21495f34
 ---> 67f721eaedfa
Step 3/8 : ENV WEB_DOC_ROOT="/var/www/"
 ---> Running in ba2d4b1b2cf1
Removing intermediate container ba2d4b1b2cf1
 ---> 844a0dc0bbcc
Step 4/8 : COPY index.html $WEB_DOC_ROOT
 ---> 1af7b350289f
Step 5/8 : RUN echo '<h2>Hello</h2>' >> ${WEB_DOC_ROOT}/index.html
 ---> Running in f61511fe6020
Removing intermediate container f61511fe6020
 ---> 2305f131626e
Step 6/8 : CMD httpd -f -h ${WEB_DOC_ROOT}
 ---> Running in aff5ee08fb46
Removing intermediate container aff5ee08fb46
 ---> e24a76680fb0
Step 7/8 : VOLUME $WEB_DOC_ROOT
 ---> Running in b59115d147c3
Removing intermediate container b59115d147c3
 ---> a2e176849eaf
Step 8/8 : EXPOSE 80
 ---> Running in c981ab7e0137
Removing intermediate container c981ab7e0137
 ---> b8f7225afb7a
Successfully built b8f7225afb7a
Successfully tagged tinyweb2:latest
[root@Docker img2]#

運行容器:

[root@Docker img2]# docker container run --name app2 --rm -dP tinyweb2
7cbf240f0b323c329deb3dd36fd2ae6f1fe630c394f035f723f6124d5d6c5094
[root@Docker img2]# docker container port app2
80/tcp -> 0.0.0.0:32770
[root@Docker img2]#

這裏沒設麼問題。

使用inspect命令查看容器默認啓動額命令。能夠查看鏡像的inspect:

[root@Docker img2]# docker image inspect tinyweb2

也能夠查看容器的inspect:

[root@Docker img2]# docker container inspect app2

查看Cmd的內容以下:

"Cmd": [
    "/bin/sh",
    "-c",
    "httpd -f -h ${WEB_DOC_ROOT}"
],

這裏回過來看下Dockerfile中的CMD指令:

CMD httpd -f -h ${WEB_DOC_ROOT}

這裏自動加上了/bin/sh -c。由於上面的CMD指令使用了第一種格式。若是使用指令的第二種格式,須要這麼寫:

CMD ["/bin/sh", "-c", "httpd -f -h ${WEB_DOC_ROOT}"]

或者避免使用環境變量,就能夠不使用/bin/sh -c,這樣是直接經過內核啓動:

CMD ["httpd", "-f", "-h", "/var/www/"]

CMD錯誤的指定參數
CMD的第二種格式,列表的第一個元素是要執行的命令,以後的元素都是這個命令的參數。一個參數做爲一個元素。這裏的httpd命令包括它的參數所有加一塊兒纔是一個完整的/bin/sh命令的參數。
下面這條指令是不對的:

CMD ["/bin/sh", "-c", "httpd", "-f", "-h ${WEB_DOC_ROOT}"]

使用docker ps -a --no-trunc查看COMMAND字段,

"/bin/sh -c httpd -f '-h ${WEB_DOC_ROOT}'"
"/bin/sh -c 'httpd -f -h ${WEB_DOC_ROOT}'"

第一行是這裏的錯誤格式解析出來的樣子,這裏的邏輯是執行命令/bin/sh,而且這個命令有4個參數。然而實際的狀況是/bin/sh命令只有2個參數,一個是-c,還有一個是後面全部的內容。
第二行纔是正確解析出來的樣子,一個命令2個參數。

下面這條指令也是錯誤的:

CMD ["httpd", "-f", "-h /var/www/"]

須要把路徑做爲一個單獨的參數。

遇到問題,有一下方式檢查。
使用不帶--rm參數的方式啓動容器,這樣保證容器啓動失敗後,不會自動刪除。
使用docker logs命令查看容器內的日誌,主要是默認啓動命令執行後的錯誤消息。
使用docker ps -a --no-trunc查看完整的啓動命令,這裏能看到命令執行時的樣子。
使用docker run命令的-it參數,而且修改默認啓動命令爲/bin/sh,這樣能夠進入到容器內部,執行命令。調試一個能夠被正確執行的命令。

動態生成配置文件

這裏是實現的是根據環境變量動態生成配置文件。首先用ENTRYPOINT指令運行一個腳本完成配置文件的動態生成,而且最後經過調用exec "$@"命令,使得CMD指令的內容能夠被調用並替換稱爲主進程。

自定義一個腳本,完成動態寫入配置文件的功能:

[root@Docker img3]# pwd
/root/img3
[root@Docker img3]# vi entrypoint.sh 
#!/bin/sh
cat > /etc/nginx/conf.d/www.conf << EOF
server {
    server_name ${HOSTNAME};
    listen ${IP:-0.0.0.0}:${PORT:-80};
    root ${NGX_DOC_ROOT};
}
EOF
exec "$@"

首先用cat配合EOF將內容寫入到文件。cat在調用的寫入內容的時候使用到了環境變量,最終寫入的內容是根據環境變量替換後的內容。
完成腳本寫入後,調用了exec "$@"。這句命令將後面的參數做爲命令調用而且替換掉當前的進程。
還要給這個腳本賦予可執行權限:

[root@Docker img3]# chmod a+x entrypoint.sh

上面的腳本是要放在ENTRYPOINT中的,Dockerfile文件內容以下:

[root@Docker img3]# vi Dockerfile
# Description: Nginx image
FROM nginx:alpine
LABEL maintainer="Steed Xu <x749b@163.com>" \
      app="nginx"
ADD entrypoint.sh /bin/
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
EXPOSE 80

建立鏡像:

[root@Docker img3]# docker build -t ngx1 .
Sending build context to Docker daemon  3.072kB
Step 1/7 : FROM nginx:alpine
 ---> 55ceb2abad47
Step 2/7 : LABEL maintainer="Steed Xu <x749b@163.com>"       app="nginx"
 ---> Running in 41050cf8bb89
Removing intermediate container 41050cf8bb89
 ---> 3bfa564b1ac3
Step 3/7 : ENV NGX_DOC_ROOT="/usr/share/nginx/html/"
 ---> Running in b8c929746310
Removing intermediate container b8c929746310
 ---> 2cb3fc5f5c8d
Step 4/7 : ADD entrypoint.sh /bin/
 ---> ecf314c9c29c
Step 5/7 : CMD ["nginx", "-g", "daemon off;"]
 ---> Running in 93d44caaa473
Removing intermediate container 93d44caaa473
 ---> eaf4da71e3c7
Step 6/7 : ENTRYPOINT ["/bin/entrypoint.sh"]
 ---> Running in ecffdcda0486
Removing intermediate container ecffdcda0486
 ---> 71f4222fc33e
Step 7/7 : EXPOSE 80
 ---> Running in 4582fa6067d2
Removing intermediate container 4582fa6067d2
 ---> 24de242b6dfc
Successfully built 24de242b6dfc
Successfully tagged ngx1:latest
[root@Docker img3]#

看一下配置文件:

[root@Docker img3]# docker container run --name app3 --rm ngx1 cat /etc/nginx/conf.d/www.conf
server {
    server_name 38dd4a661a6f;
    listen 0.0.0.0:80;
    root /usr/share/nginx/html/;
}
[root@Docker img3]# docker container run --name app3 --rm -e HOSTNAME=ngx1 ngx1 cat /etc/nginx/conf.d/www.conf
server {
    server_name ngx1;
    listen 0.0.0.0:80;
    root /usr/share/nginx/html/;
}
[root@Docker img3]#

配置文件會根據環境變量來動態改變。

大多數的dockerfile都是經過ENTRYPOINT腳本接受參數之後,再啓動服務的。也就是這裏的作法。

相關文章
相關標籤/搜索