前面我們使用docker commit只做了一個自己的鏡像,但是這個主要適用於學習以及一些特殊的用途,實際應用一般不會通過這種方式製作鏡像.因爲docker commit只是把變動的文件中紙做成了鏡像,比如之前文章中創建test鏡像的時候,記錄的是創建的/test這個文件夾,但是變動單而過程無法記錄,沒有辦法追蹤到我是通過mkdir /test這個命令創建的/test目錄.這樣的話就非常不利於追蹤歷史和維護,如果要是涉及到編譯安裝軟件等等過程,那就更加讓人崩潰了.其實對我們來說更重要的是想記錄mkdir /test這條命令,因爲只要有這條命令我們就是肯定可以得到/test目錄這個結果.
FROM和RUN
因此 Docker 推出了 Dockerfile 來解救猿類;
Dockerfile 就是一個名叫 Dockerfile 的普通的文本文件;
用來記錄製作鏡像的命令;
我們找個空白文件夾建一個 Dockerfile 文件;
我們之前製作鏡像的時候分爲 3 步;
1.先是拉取鏡像;
docker pull ubuntu
那在 Dockerfile 文件中則是通過 FROM 關鍵字;
FROM ubuntu
2.接着是執行了 mkdir /test 命令;
那在 Dockerfile 文件中則是通過 RUN 關鍵字;
FROM ubuntu
RUN mkdir /test
3.執行 docker commit 製作鏡像
這一步不體現在 Dockerfile 中了;
需要使用 docker build 命令;
docker build -t baijunyao/test:v2
-t baijunyao/test:v2 : 定義鏡像的名稱和版本
. : build 的上下文環境目錄
build 完成後 push 鏡像就跟之前一樣;
這裏就略過不講了;
敲黑板劃重點注意我標註的箭頭了;
接着我要拿它來講解上面說的讓人不太理解的上下文環境目錄;
Docker 跟 MySQL 之類的一樣也分爲客戶端和服務端;
幹活的是服務端;
客戶端主要起調用的作用;
在執行 docker build 的時候是要把文件都發給服務端引擎來 dockerd 處理;
所以這個 . 就是指定上下文目錄的;
意思是把當前目錄的所有文件都發送給 dockerd ;
爲了驗證上面所說;
我們把 /bin/bash 文件複製到當前目錄再運行看看;
cp /bin/bash
docker build -t baijunyao//test:v2
可以看到發送的數據明顯變多了;
但是其實 build 鏡像的時候並不需要在這個 bash 文件;
這種發送全部文件的行爲明顯是不合理的;
要解決這個問題可以藉助 .dockerignore ;
看名字就明顯一股濃濃的山寨 .gitignore 的氣息;
這正是 Docker 的高明之處;
什麼 pull 、push、commit 等等通通使用我們已經熟知的 Git 概念;
極大的減少了我們的學習阻礙;
接着創建 .dockerignore 文件並加入 bash ;
/bash
再次執行 build 命令;
另外針對上面的 RUN 順便提一嘴;
如果要 RUN 多個命令可以用 && 連接;
比如說常見的 apt 安裝;
apt update && apt install busybox
CMD
CMD關鍵字用來指定容器啓動後執行的默認命令,修改下 Dockerfile 定義一個 CMD 輸出 “Dockerfile CMD Commmand” ;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD echo "Dockerfile CMD Commmand"
build 鏡像;
docker build -t baijunyao/test:v3 .
啓動容器:
docker run baijunyao/test:v3
可以看到在run完後會出輸出這句話:
但是注意上面我說過是默認命令;
既然叫默認值那一般是可以替換的;
如果我們在啓動容器的時候執行了其他命令;
那就不會再執行 CMD 默認命令了;
docker run baijunyao/test:v3 /bin/echo "test"
可以看到 CMD 的默認命令沒有執行;
不同於 RUN 可以定義多個;
CMD 只可以定義一個;
如果定義多個那麼後面的會覆蓋前面的 CMD;
ENTRYPOINT
那如果想定義一個不會被覆蓋的命令就可以使用 ENTRYPOINT ;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT echo "Dockerfile ENTRYPOINT Commmand"
我們來重新 build ;
docker build -t baijunyao/test:v4 .
運行容器:
docker run baijunyao/test:v4
截圖會出乎你的意料;
“Dockerfile CMD Commmand” 或者說 “test” 都沒有輸出;
“test” 會覆蓋掉 “Dockerfile CMD Commmand” 是可以想到的;
但是連 “test” 都不輸出這就有點說不過去了;
這裏其實是模式的不同;
RUN 、 CMD、ENTRYPOINT 三種關鍵字都有兩種模式;
1.shell 模式 這就是上面我們一直用的模式;
跟在命令行輸入命令一樣;
2.exec 模式
把上面的 shell 改成 exec 模式的話就如下所示;
['/bin/eho','test']
結論來了在 shell 模式下 ENTRYPOINT 會直接覆蓋掉 CMD 的命令;
那我們用 exec 模式改寫下;
Dockerfile
FROM ubuntu
RUN mkdir /test
CMD ["/bin/echo", "Dockerfile CMD Commmand"]
ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand"]
再次build運行
docker build -t baijunyao/test:v5 .
docker run baijunyao/test:v5
結果再次出乎意料;
CMD 的命令並沒有正常輸出;
原因是在 exec 模式下;
CMD 命令會被作爲 ENTERPOINT 的參數;
因此上面的命令等於下面這樣;
ENTRYPOINT ["/bin/echo", "Dockerfile ENTRYPOINT Commmand", "/bin/echo test"]
這真是爲難人了;
那有木有一種方案可以讓我正常的符合直覺的同時使用 CMD 和 ENTERPOINT ;
有;但是需要我先把下個關鍵字講了;
COPY
COPY關鍵字用來將宿主機的文件 copy 到鏡像中;
我們上面的 ENTERPOINT 代碼轉移到文件中;
enterpoint.sh
#!/bin/bash
echo "Dockerfile ENTRYPOINT Commmand"
exec "$@"
注意這裏多加了一行 exec “$@” ;
它 的作用是執行接到的參數;
給予寫權限;
chmod +x enterpoint.sh
接着改寫下 Dockerfile 文件;
Dockerfile
FROM ubuntu
RUN mkdir /test
COPY enterpoint.sh /root/docker/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
上面這幾條命令也很容易理解了;
先把宿主機上的 enterpoint.sh 文件複製到容器的 /root/docker/ 目錄下;
再次 build 運行
docker build -t baijunyao/test:v6 .
docker run baijunyao/test:v6
終於如願以償;
這裏需要再次強調上下文環境;
我們 build 命令使用的是 . ;
也就是說我們不能獲取當前目錄外面的內容;
我們來試着把 /bin/bash 文件複製到鏡像中;
Dockerfile
FROM ubuntu
RUN mkdir /test
COPY enterpoint.sh /root/docker/
COPY /bin/bash /root/docker/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
果然是報錯的.
ADD
ADD 和 COPY 差不多;
比較常見的場景是可以用來解壓縮文件;
除了需要解壓文件;
這個關鍵字就記住一句話就行了;
官方推薦用 COPY ;
並不建議用 ADD;
ENV
ENV 用來定義變量;
用空格或者 = 定義;
如果值中有空格就加引號;
比如:
ENV PATH /root/baijunyao
ENV PATH=/root/baijunyao
ENV NAME "Junyao Bai"
ENV NAME="Junyao Bai"
使用變量的時候加上 $ 即可;
FROM ubuntu
ENV DOCKER_PATH=/root/docker
RUN mkdir $DOCKER_PATH
COPY enterpoint.sh $DOCKER_PATH/
CMD echo "Dockerfile CMD Commmand"
ENTRYPOINT ["/root/docker/enterpoint.sh"]
另外需要注意的是有幾個系統的變量不能作爲 key;
比如說 PATH 、 HOME ;
這類變量可以直接在 Dockerfile 中使用;
EXPOSE
EXPOSE 的主要作用就是告訴使用者鏡像的守護端口;
比如說 MySQL 的 Dockerfile 就會定義 EXPOSE 爲 3306 來告訴用戶端口號;