Dockerfile指令詳解上


COPY復制文件指令

和RUN命令一樣,COPY命令也有兩種格式,一種類似與命令行,一種類似與函數調用,命令格式如下:

  • COPY <源路徑>...<目標路徑>
  • COPY ["<源路徑1>",...<目標路徑>]

COPY將構建上下文中目錄中的文件或則目錄復制到復制到鏡像內的目錄位置中。比如:

COPY data.json /opt/data/

原路徑可以有多個,甚至是通配符,但是通配符要符合GO語言的通配符規范,比如:

COPY data* /mydir/
COPY dat?.txt /mydir/

<目標路徑> 可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行
創建缺失目錄。還需要注意一點,使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。這個特性對於鏡像定制很有用。特別是構建相關文件都在使用 Git進行管理的時候。

ADD更高級的復制文件指令

ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。比如 <源路徑> 可以是一個 URL ,這種情況下,Docker 引擎會試圖去下載這個鏈接的文件放到 <目標路徑> 去。下載后的文件權限自動設置為 600 ,如果這並不是想要的權限,那么還需要增加額外的一層 RUN 進行權限調整,另外,如果下載的是個壓縮包,需要解壓縮,也一樣還需要額外的一層 RUN 指令進行解壓縮。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下載,處理權限、解壓縮、然后清理無用文件更合理。因此,這個功能其實並不實用,而且不推薦使用。

如果 <源路徑> 為一個 tar 壓縮文件的話,壓縮格式為 gzip , bzip2 以及 xz 的情況下, ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑> 。

在某些情況下,這個自動解壓縮的功能非常有用,比如官方鏡像 ubuntu 中:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...

但在某些情況下,如果我們真的是希望復制個壓縮文件進去,而不解壓縮,這時就不可以使
用 ADD 命令了。適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

另外需要注意的是, ADD 指令會令鏡像構建緩存失效,從而可能會令鏡像構建變得比較緩慢。因此在 COPY 和 ADD 指令中選擇的時候,可以遵循這樣的原則,所有的文件復制均使用COPY 指令,僅在需要自動解壓縮的場合使用 ADD 。

CMD容器啟動命令

CMD 指令的格式和 RUN 相似,也是兩種格式:

  • shell 格式: CMD <命令>
  • exec 格式: CMD ["可執行文件", "參數1", "參數2"...]
  • 參數列表格式: CMD ["參數1", "參數2"...] 。在指定了 ENTRYPOINT 指令后,用 CMD 指定具體的參數。

之前介紹容器的時候曾經說過,Docker 不是虛擬機,容器就是進程。既然是進程,那么在啟動容器的時候,需要指定所運行的程序及參數。 CMD 指令就是用於指定默認的容器主進程的啟動命令的。

在運行時可以指定新的命令來替代鏡像設置中的這個默認命令,比如, ubuntu 鏡像默認的CMD 是 /bin/bash ,如果我們直接 docker run -it ubuntu 的話,會直接進入 bash 。我們也可以在運行時指定運行別的命令,如 docker run -it ubuntu cat /etc/os-release 。這就是用 cat /etc/os-release 命令替換了默認的 /bin/bash 命令了,輸出了系統版本信息。

在指令格式上,一般推薦使用 exec 格式,這類格式在解析時會被解析為 JSON 數組,因此一定要使用雙引號 " ,而不要使用單引號。如果使用 shell 格式的話,實際的命令會被包裝為 sh -c 的參數的形式進行執行。比如:

CMD echo $HOME

在實際執行中將會變成

CMD ["sh","-c","echo $HOME"]

Docker 不是虛擬機,容器中的應用都應該以前台執行,而不是像虛擬機、物理機里面那樣,用 upstart/systemd 去啟動后台服務,容器內沒有后台服務的概念。

比如執行下面的命令:

CMD service httpd start

啟動的時候會發現容器執行后就立馬退出了這是因為沒有搞明白前台,后台的概念,沒有區分容器和虛擬機的差異,依舊以虛擬機的角度去理解容器。對於容器而言,其啟動程序就是容器應用進程,容器就是為了主進程而存在的,主進程退出,容器就失去了存在的意義,從而退出,其它輔助進程不是它需要關心的東西。上面的例子中實際上是以upstart來以后台守護進程的方式啟動httpd,而上面的命令在實際的執行中會變為 CMD ["sh","-c","service httpd start"],實際上主進程是sh,當命令運行結束后,主進程就退出了,自然就會令容器退出。

正確的做法是直接執行 httpd 可執行文件,並且要求以前台形式運行。比如:

CMD ["httpd", "-g", "daemon off;"]

ENTRYPOINT 入口點

ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。 ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的參數 --entrypoint 來指定。

當指定了 ENTRYPOINT 后, CMD 的含義就發生了改變,不再是直接的運行其命令,而是將CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,為什么還要有 ENTRYPOINT 呢?這種 " " 有什么好處么?讓我們來看幾個場景。

場景一:讓鏡像變成像命令一樣使用

假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用 CMD 來實現:

FROM ubuntu:16.04
RUN apt-get update \
	&& apt-get install -y curl \
	&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://ip.cn" ]

假如我們使用 docker build -t myip . 來構建鏡像的話,如果我們需要查詢當前公網 IP,只需要執行:

sudo docker run myip

這么看起來好像可以直接把鏡像當做命令使用了,不過命令總有參數,如果我們希望加參數呢?這時候可以使用ENTRYPOINT。使用ENTRYPOINT重新構建鏡像

FROM ubuntu:16.04
RUN apt-get update \
	&& apt-get install -y curl \
	&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]

這時候就可以直接加參數了,例如:

docker run myip -i

這里 -i 就是新的 CMD ,因此會作為參數傳給 curl ,從而達到了我們預期的效果。

場景二:應用運行前的准備工作

啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些准備工作。比如 mysql 類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的mysql 服務器運行之前解決。此外,可能希望避免使用 root 用戶去啟動服務,從而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的准備工作,最后切換到服務用戶身份啟動服務。或者除了服務外,其它命令依舊可以使用 root 身份執行,方便調試等。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 )作為命令,在腳本最后執行。比如官方鏡像 redis 中就是這么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]

可以看到其中為了 redis 服務創建了 redis 用戶,並在最后指定了 ENTRYPOINT 為 dockerentrypoint.sh 腳本。

#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
chown -R redis .
exec su-exec redis "$0" "$@"
fi
exec "$@"

該腳本的內容就是根據 CMD 的內容來判斷,如果是 redis-server 的話,則切換到 redis用戶身份啟動服務器,否則依舊使用 root 身份執行。

ENV 設置環境變量

ENV指令的格式有兩種:

  • ENV
  • ENV = = ...

這個指令很簡單,就是設置環境變量而已,無論是后面的其它指令,如 RUN ,還是運行時的
應用,都可以直接使用這里定義的環境變量。

ENV命令的換行使用'' 如果key或則value有空格的話使用""包括起來

定義了環境變量,那么在后續的指令中,就可以使用這個環境變量。使用環境變量使用$符號即可。

下列指令可以支持環境變量展開:

ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、STOPSIGNAL 、 ONBUILD 。

可以從這個指令列表里感覺到,環境變量可以使用的地方很多,很強大。通過環境變量,我
們可以讓一份 Dockerfile 制作更多的鏡像,只需使用不同的環境變量即可。

ARG 構建參數

格式: ARG <參數名>[=<默認值>]

構建參數和 ENV 的效果一樣,都是設置環境變量。所不同的是, ARG 所設置的構建環境的
環境變量,在將來容器運行時是不會存在這些環境變量的。但是不要因此就使用 ARG 保存密
碼之類的信息,因為 docker history 還是可以看到所有值的。

Dockerfile 中的 ARG 指令是定義參數名稱,以及定義其默認值。該默認值可以在構建命令
docker build 中用 --build-arg <參數名>=<值> 來覆蓋。

未完待續


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM