1. 程式人生 > >Docker--DockerFile與映象

Docker--DockerFile與映象

一:Dockerfile介紹

    Dockerfile是一個用於引導docker映象生成過程的檔案,遵循其特定的語法,我們便可以建立一個自己的映象。

    Docker在預設情況下,如果不額外指定 Dockerfile 的話,會將上下文目錄下的名為 Dockerfile 的檔案作為 Dockerfile。這只是預設行為,實際上 Dockerfile 的檔名並不要求必須為 Dockerfile ,而且並不要求必須位於上下文目錄中,比如可以用 -f ../Dockerfile.php 引數指定某個檔案作為 Dockerfile 。並依據該檔案的內容來建立定製映象。映象的定製實際上就是定製每一層所新增的配置、檔案,在Dockerfile 中每一個指令都會建立一層映象,最後成為一個總的映象。

    Dockerfile 是一個文字檔案,用來配置 image,Docker 根據 該檔案生成二進位制的 image 檔案。


二:Dockerfile語法

2.1:FROM

  • 用法: FROM <image>

  • 說明:定製映象,那一定是以一個映象為基礎,在其上進行定製。而 FROM 就是指定基礎映象,因此一個 Dockerfile 中 FROM 是必備的指令,並且必須是第一條指令。(在 Docker Hub 上有非常多的高質量的官方映象,有可以直接拿來使用的服務類的映象,如nginx 、 redis 、 mongo 、mysql 等;也有一些方便開發、構建、執行各種語言應用的映象,如 node 、 openjdk 、 python 等。可以在其中尋找一個最符合我們最終目標的映象為基礎映象進行定製。除了選擇現有映象為基礎映象外,Docker 還存在一個特殊的映象,名為 scratch 。這個映象是虛擬的概念,並不實際存在,它表示一個空白的映象。 如果你以 scratch 為基礎映象的話,意味著你不以任何映象為基礎,接下來所寫的指令將作為映象第一層開始存在。)

  • 例如

FROM ubuntu15:10

2.2:MAINTAINER

  • 用法: MAINTAINER name

  • 說明:指定映象建立者的姓名

  • 例如:

MAINTAINER liyangyang

2.3:RUN

  • 用法:RUN  command param1 param2 ...

  • 說明:用來執行命令列命令的指令, 執行完成之後會成為一個新的映象。一句RUN就是一層,也相當於一個版本。這就是之前說的快取的原理。我們知道docker是映象層是隻讀的,所以你如果第一句安裝了軟體,用完在後面一句刪除是不可能的。所以這種情況要在一句RUN命令中完成,可以通過&符號連線多個RUN語句。

  • 注意:

    • Dockerfile 中每一個指令都會建立一層,就比如每一個 RUN 命令執行後,就和剛才我們手工建立映象的過程一樣:新建立一層,在其上執行這些命令,執行結束後, commit 這一層的修改,構成新的映象,這樣一來很多執行時不需要的東西,都被裝進了映象裡,比如編譯環境、更新的軟體包等等。結果就是產生非常臃腫、非常多層的映象,不僅僅增加了構建部署的時間,也很容易出錯。

    • 我們最好在一組命令的最後新增清理工作的命令,刪除了為了編譯構建所需要的軟體,清理了所有下載、展開的檔案,並且還清理了 apt 快取檔案。這是很重要的一步,映象是多層儲存,每一層的東西並不會在下一層被刪除,會一直跟隨著映象。因此映象構建時,一定要確保每一層只新增真正需要新增的東西,任何無關的東西都應該清理掉

    • command是不會呼叫shell的,所以也不會繼承相應變數,要檢視變數輸入RUN bash -c echo $HOME,而不是RUN echo $HOME

  • 例如:

    • 安裝tomcat初始版本

RUN mkdir /var/tmp/tomcat  

RUN wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz

RUN tar xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz
  • 優化:上述這幾部完全可以在一層中,沒有必要分層,則優化後

RUN mkdir /var/tmp/tomcat \  

    && wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz \

    && tar -xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz

2.4:CMD

  • 用法: 

    • shell 格式: CMD <命令>

    • exec 格式: CMD ["可執行檔案", "引數1", "引數2"...]

    • 引數列表格式: CMD ["引數1", "引數2"...] 。在指定了 ENTRYPOINT 指令後,用 CMD 指定具體的引數。

  • 說明:CMD 指令用於指定預設的容器主程序的啟動命令的,其作用是在啟動容器的時候提供一個預設的命令項。

  • 注意:

    • 在指令格式上,一般推薦使用 exec 格式,這類格式在解析時會被解析為 JSON 陣列,因此一定要使用雙引號 " ,而不要使用單引號。

    • 如果使用 shell 格式的話,實際的命令會被包裝為 sh -c 的引數的形式進行執行。比如:CMD echo $HOME,在實際執行中,會將其變更為:CMD [ "sh", "-c", "echo $HOME" ]。這就是為什麼我們可以使用環境變數的原因,因為這些環境變數會被 shell 進行解析處理。

    • CMD在Dockerfile中只能出現一次,如果有多個,只有最後一個會有效。通常放在dockerfile的最後面。

  • 例如:

    • 設定tomcat隨容器的啟動而啟動

CMD [ "/home/apache-tomcat-7.0.91/bin/catalina.sh", "run" ]

2.5:EXPOSE

  • 用法:EXPOSE <port> [<port>...]

  • 說明:宣告執行時容器提供服務埠(容器暴露的埠), 在docker run -p的時候生效。

  • 注意:

    • 在 Dockerfile 中寫入這樣的宣告有兩個好處,一個是幫助映象使用者理解這個映象服務的守護埠,以方便配置對映;另一個用處則是在執行時使用隨機埠對映時,也就是 docker run -P 時,會自動隨機對映 EXPOSE 的埠。

    • 要將 EXPOSE 和在執行時使用 -p <宿主埠>:<容器埠> 區分開來。 -p ,是對映宿主埠和容器埠,換句話說,就是將容器的對應埠服務公開給外界訪問,而 EXPOSE 僅僅是宣告容器打算使用什麼埠而已,並不會自動在宿主進行埠對映。

  • 例如:

    • 暴露容器的8080埠給宿主機

EXPOSE 8080

2.6:ENV

  • 用法:

    • ENV <key> <value>

    • ENV <key1>=<value1> <key2>=<value2>...

  • 說明: 設定映象的環境變數

  • 例如:

    • 定義jdk環境變數

ENV JAVA_HOME /home/jdk1.8.0_181

ENV JRE_HOME $JAVA_HOME/jre

ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib

ENV PATH $PATH:$JAVA_HOME/bin
  • 定義變數,在後面的語句中使用

ENV JDK_VERSION  8u-91   //定義了JDK_VERSION變數

RUN echo JDK_VERSION  //使用

2.7:COPY

  • 用法: COPY <源路徑> <目標路徑>

  • 說明: 複製本機檔案或目錄或遠端檔案,新增到指定的容器目錄,支援GO的正則模糊匹配。路徑是絕對路徑,不存在會自動建立。如果源是一個目錄,只會複製目錄下的內容,目錄本身不會複製。

  • 注意:

    • <源路徑> 可以是多個,甚至可以是萬用字元,其萬用字元規則要滿足 Go 的 filepath.Match 規則。 <目標路徑> 可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先建立,如果目錄不存在會在複製檔案前先行建立缺失目錄。

    • 使用 COPY 指令,原始檔的各種元資料都會保留。比如讀、寫、執行許可權、檔案變更時間等。這個特性對於映象定製很有用。特別是構建相關檔案都在使用 Git進行管理的時候。

  • 例如:

COPY    hom*        /mydir/

COPY    hom?.txt  /mydir/

COPY    hom*       ./mydir/

2.8:ADD

  • 用法:ADD <源路徑> <目標路徑>

  • 說明:ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。比如 <源路徑> 可以是一個 URL ,這種情況下,Docker 引擎會試圖去下載這個連結的檔案放到 <目標路徑> 去。下載後的檔案許可權自動設定為 600 ,如果這並不是想要的許可權,那麼還需要增加額外的一層 RUN 進行許可權調整

  • 注意:

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

    • 在 Docker 官方的 Dockerfile 最佳實踐文件 中要求,儘可能的使用 COPY ,因為 COPY 的語義很明確,就是複製檔案而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

  • 例如:

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

2.9:ENTRYPOINT

  • 用法: ENTRYPOINT command param1 param2 ...

  • 說明:ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程式及引數。ENTRYPOINT 在執行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過 docker run 的引數 –entrypoint 來指定。

  • 注意:

    • 當指定了 ENTRYPOINT 後, CMD 的含義就發生了改變,不再是直接的執行其命令,而是將CMD 的內容作為引數傳給 ENTRYPOINT 指令。

  • 例如:

    • 啟動容器就是啟動主程序,但有些時候,啟動主程序前,需要一些準備工作。比如 mysql 類的資料庫,可能需要一些資料庫配置、初始化的工作,這些工作要在最終的 mysql 伺服器執行之前解決。這些準備工作是和容器 CMD 無關的,無論 CMD 為什麼,都需要事先進行一個預處理的工作。這種情況下,可以寫一個指令碼,然後放入 ENTRYPOINT 中去執行,而這個指令碼會將接到的引數(也就是 )作為命令,在指令碼最後執行。比如官方映象 redis 中就是這麼做的:

FROM alpine:3.4 
... 
RUN addgroup -S redis && adduser -S -G redis redis 
... 
ENTRYPOINT ["docker-entrypoint.sh"] 
EXPOSE 6379 
CMD [ "redis-server" ]   #此處的CMD的作為引數傳入ENTRYPOINT所執行的指令碼中

2.10:VOLUME

  • 用法:

    • VOLUME ["<路徑1>", "<路徑2>"...]

    • VOLUME <路徑>

  • 說明:定義匿名卷,在主機上建立一個掛載,掛載到容器的指定路徑。容器執行時應該儘量保持容器儲存層不發生寫操作,對於資料庫類需要儲存動態資料的應用,其資料庫檔案應該保存於卷(volume)中。為了防止執行時使用者忘記將動態檔案所儲存目錄掛載為卷,在 Dockerfile 中,我們可以事先指定某些目錄掛載為匿名卷,這樣在執行時如果使用者不指定掛載,其應用也可以正常執行,不會向容器儲存層寫入大量資料。

  • 案例:

    • 將/data 目錄就會在執行時自動掛載為匿名卷,任何向 /data 中寫入的資訊都不會記錄進容器儲存層,從而保證了容器儲存層的無狀態化。

VOLUME /data
  • 當然,執行時可以覆蓋這個掛載設定。比如:

docker run -d -v mydata:/data xxxx
  • 在這行命令中,就使用了 mydata 這個命名卷掛載到了 /data 這個位置,替代了 Dockerfile 中定義的匿名卷的掛載配置。

2.11:USER

  • 用法:USER <使用者名稱>

  • 說明:USER 指令和 WORKDIR 相似,都是改變環境狀態並影響以後的層。 WORKDIR 是改變工作目錄, USER 則是改變之後層的執行 RUN , CMD 以及 ENTRYPOINT 這類命令的身份。

  • 注意:

    • USER 只是幫助你切換到指定使用者而已,這個使用者必須是事先建立好的,否則無法切換。

  • 案例:

    • 切換使用者為lyy

USER lyy

2.12:WORKDIR

  • 用法:WORKDIR <工作目錄路徑> 

  • 說明:用 WORKDIR 指令可以來指定工作目錄(或者稱為當前目錄),以後各層的當前目錄就被改為指定的目錄,如該目錄不存在, WORKDIR 會幫你建立目錄。

  • 注意:

    • RUN cd /app 並不會將當前目錄切換到app目錄下,原因其實很簡單,在 Shell 中,連續兩行是同一個程序執行環境,因此前一個命令修改的記憶體狀態,會直接影響後一個命令;而在 Dockerfile 中,這兩行 RUN 命令的執行環境根本不同,是兩個完全不同的容器。

    • 如果需要改變以後各層的工作目錄的位置,那麼應該使用 WORKDIR 指令。

  • 案例:

    • 切換當前目錄到/home/lyy下,使後面的命令都在該目錄下執行

WORKDIR /home/lyy

2.13:ONBUILD 

  • 用法:ONBUILD <其它指令>

  • 說明:ONBUILD 是一個特殊的指令,它後面跟的是其它指令,比如 RUN , COPY 等,而這些指令,在當前映象構建時並不會被執行。只有當以當前映象為基礎映象,去構建下一級映象的時候才會被執行。

  • 注意:

    • 當基礎映象變化後,使用ONBUILD的指令在各個專案都用這個 Dockerfile 重新構建映象,會繼承基礎映象的更新,就不必去更改所有的子映象了。

  • 案例:

    • dockerfile部分:

FROM node:slim 

RUN mkdir /app 

WORKDIR /app 

ONBUILD COPY ./package.json /app 
ONBUILD RUN [ "npm", "install" ] 
ONBUILD COPY . /app/ 

CMD [ "npm", "start" ]
  • 在構建基礎映象的時候,ONBUILD這三行並不會被執行,只有以該映象為基礎映象建立映象時才會執行。


三:構建映象

  • 命令:docker build [OPTIONS] PATH | URL

  • 常用引數:

-f :指定要使用的Dockerfile路徑;
-m :設定記憶體最大值;
--no-cache :建立映象的過程不使用快取;
--pull :嘗試去更新映象的新版本;
--quiet, -q :安靜模式,成功後只輸出鏡像 ID,不會輸出其log在顯示屏;
--rm :設定映象成功後刪除中間容器;
-tag, -t: 映象的名字及標籤,通常 name:tag 或者 name 格式;可以在一次構建中為一個映象設定多個標籤。
--network: 預設 default。在構建期間設定RUN指令的網路模式
  • 說明:根據PATH | URL 中獲取Dockerfile來建立映象,當我們進行映象構建的時候,並非所有定製都會通過 RUN 指令完成,經常會需要將一些本地檔案複製進映象,比如通過 COPY 指令、 ADD 指令等。而 docker build 命令構建映象,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。

  • 注意:

    • docker build 的工作原理:Docker 在執行時分為 Docker 引擎(也就是服務端守護程序)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱為 DockerRemote API,而如 docker 命令這樣的客戶端工具,則是通過這組 API 與 Docker 引擎互動,從而完成各種功能。因此,雖然表面上我們好像是在本機執行各種 docker 功能,但實際上,一切都是使用的遠端呼叫形式在服務端(Docker 引擎)完成。也因為這種 C/S 設計,讓我們操作遠端伺服器的 Docker 引擎變得輕而易舉。

    • docker build 命令構建映象,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。

    • 當構建的時候,使用者會指定構建映象上下文的路徑也就是PATH | URL  部分, docker build 命令得知這個路徑後,會將路徑下的所有內容打包,然後上傳給 Docker 引擎。這樣Docker 引擎收到這個上下文包後,展開就會獲得構建映象所需的一切檔案。所以, 一般來說,應該會將 Dockerfile 置於一個空目錄下,或者專案根目錄下。如果該目錄下沒有所需檔案,那麼應該把所需檔案複製一份過來。如果目錄下有些東西確實不希望構建時傳給 Docker 引擎,那麼可以用 .gitignore 一樣的語法寫一個 .dockerignore ,該檔案是用於剔除不需要作為上下文傳遞給 Docker 引擎的。

  • 映象建立過程:

    • 容器映象包括元資料和檔案系統,其中檔案系統是指對基礎映象的檔案系統的修改,元資料不影響檔案系統,只是會影響容器的配置

    • 每個步驟都會生成一個新的映象,新的映象與上一次的映象相比,要麼元資料有了變化,要麼檔案系統有了變化而多加了一層

    • Docker 在需要執行指令時通過建立臨時映象,執行指定的命令,再通過 docker commit 來生成新的映象

    • Docker 會將中間映象都儲存在快取中,這樣將來如果能直接使用的話就不需要再從頭建立了。

  • 映象分層與容器層:

    • 一個 Docker 映象是基於基礎映象的多層疊加,最終構成和容器的 rootfs (根檔案系統)。

    • 當 Docker 建立一個容器時,它會在基礎映象的容器層之上新增一層新的薄薄的可寫容器層。接下來,所有對容器的變化,比如寫新的檔案,修改已有檔案和刪除檔案,都只會作用在這個容器層之中。因此,通過不拷貝完整的 rootfs,Docker 減少了容器所佔用的空間,以及減少了容器啟動所需時間。

    • 映象與容器圖如下:

  • COW 和映象大小

    • COW,copy-on-write 技術,一方面帶來了容器啟動的快捷,另一方也造成了容器映象大小的增加。

    • 每一次 RUN 命令都會在映象上增加一層,每一層都會佔用磁碟空間。舉個例子,在 Ubuntu 14.04 基礎映象中執行 RUN apt-get upgrade 會在保留基礎層的同時再建立一個新層來放所有新的檔案,而不是修改老的檔案,因此,新的映象大小會超過直接在老的檔案系統上做更新時的檔案大小。

    • 因此,為了減少映象大小起見,所有檔案相關的操作,比如刪除,釋放和移動等,都需要儘可能地放在一個 RUN 指令中進行。

  • 例如:

    • 使用當前目錄下的 Dockerfile 建立映象,標籤為 xcardata/ubuntu:v1.0

docker build -txcardata/ubuntu:v1.0  .     #注意後面的空格和點,代表當前目錄
#後臺方式執行
nohup  docker build -t newxcardata/centos:v1.0 .  >  ./build.log  2>&1  &
docker build github.com/creack/docker-firefox
  • 通過 -f 指定Dockerfile 檔案的位置建立映象

docker build -f  /path/Dockerfile  . 

四:其他

4.1:容器中應用在前臺執行和後臺執行的問題?

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

    初學者一般將 CMD 寫為:  

CMD service nginx start

    然後發現容器執行後就立即退出了。甚至在容器內去使用 systemctl 命令結果卻發現根本執行不了。這就是因為沒有搞明白前臺、後臺的概念,沒有區分容器和虛擬機器的差異,依舊在以傳統虛擬機器的角度去理解容器。

 對於容器而言,其啟動程式就是容器應用程序,容器就是為了主程序而存在的,主程序退出,容器就失去了存在的意義,從而退出,其它輔助程序不是它需要關心的東西。

 而使用 service nginx start 命令,則是希望 systemd 來以後臺守護程序形式啟動 nginx 服務。而剛才說了 CMD service nginx start 會被理解為 CMD [ “sh”, “-c”, “service nginxstart”] ,因此主程序實際上是 sh 。那麼當 service nginx start 命令結束後, sh 也就結束了, sh 作為主程序退出了,自然就會令容器退出。

 正確的做法是直接執行 nginx 可執行檔案,並且要求以前臺形式執行。比如:

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

4.2:Dockerfile案例(基於centos構建jdk+tomcat映象)

#使用的基礎映象
FROM centos  
#不指定版本tag,則預設pull最新的

#建立者資訊
MAINTAINER <liyangyang> <[email protected]>

#安裝wget
RUN yum install -y wget

#安裝JDK
RUN mkdir /var/tmp/jdk  \
    && wget --no-cookies --no-check-certificate --header "Cookie: oraclelicense=accept-securebackup-cookie" -P /var/tmp/jdk  http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz   \
    && tar -zxf /var/tmp/jdk/jdk-8u181-linux-x64.tar.gz -C /var/tmp/jdk \
    && rm -rf /var/tmp/jdk/jdk-8u111-linux-x64.tar.gz   

#配置JDK環境變數
ENV JAVA_HOME /var/tmp/jdk/jdk1.8.0_181
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH .:$JAVA_HOME/lib:$JRE_HOME/lib
ENV PATH $PATH:$JAVA_HOME/bin

#安裝Tomcat
RUN mkdir /var/tmp/tomcat \  
     && wget -P  /var/tmp/tomcat http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.91/bin/apache-tomcat-7.0.91.tar.gz \
     && tar -xzf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz -C /var/tmp/tomcat  \
     && rm -rf /var/tmp/tomcat/apache-tomcat-7.0.91.tar.gz

#配置Tomcat環境變數
ENV CATALINA_HOME /var/tmp/tomcat/apache-tomcat-7.0.91

#指定容器暴露埠
EXPOSE 8080

#新增專案
ADD xcar-index-web.war /var/tmp/tomcat/apache-tomcat-7.0.91/webapps/

#建立容器時預設啟動tomcat
CMD ["/var/tmp/tomcat/apache-tomcat-7.0.91/bin/catalina.sh", "run"]

4.3:使用容器需要避免的一些做法

  • * 不要在容器中儲存資料(Don’t store data in containers)

  • * 將應用打包到映象再部署而不是更新到已有容器(Don’t ship your application in two pieces)

  • * 不要產生過大的映象 (Don’t create large images)

  • * 不要使用單層映象 (Don’t use a single layer image)

  • * 不要從執行著的容器上產生映象 (Don’t create images from running containers )

  • * 不要只是使用 “latest”標籤 (Don’t use only the “latest” tag)

  • * 不要在容器內執行超過一個的程序 (Don’t run more than one process in a single container )

  • * 不要在容器內儲存 credentials,而是要從外面通過環境變數傳入 ( Don’t store credentials in the image. Use environment variables)

  • * 不要使用 root 使用者跑容器程序(Don’t run processes as a root user )

  • * 不要依賴於IP地址,而是要從外面通過環境變數傳入 (Don’t rely on IP addresses )

4.4:docker映象的組成

  • * docker 映象中主要就是 tar 檔案包和元資料 json 檔案

  • * docker 映象的打包過程,其實就是將每一層對應的檔案打包過程,最後組成一個單一的 tar 檔案

  • * docker 映象的使用過程,其實就是將一層層的 tar 檔案接包到檔案系統的過程

 

參考:https://www.cnblogs.com/lighten/p/6900556.html

http://www.runoob.com/docker/docker-build-command.html

https://blog.csdn.net/wo18237095579/article/details/80540571

https://www.cnblogs.com/sammyliu/p/5877964.html