Docker容器技術(四)——Dockerfile詳解
1. 編寫一個簡單的Dockerfile
創建一個Dockerfile
創建Dockerfile儘量不要在根目錄,因爲默認在構建的時候會把當前目錄所有數據發送到docker引擎,如果在根目錄,會把跟目錄所有數據發送給docker引擎進行構建。
mkdir docker
cd docker/
echo hello > testfile
vim Dockerfile
FROM busybox
COPY testfile /
COPY參數要求要拷貝的文件必須在當前目錄,不能寫絕對路徑,只能是相對路徑。拷貝的目的地可以是目錄或文件
構建鏡像
docker build -t demo:v1 .
查看鏡像的分層結構
docker history demo:v1
通過Dockerfile來構建鏡像,可以清除的看到每一層都幹了什麼,有安全審計的功能。而通過docker commit 構建新鏡像的方式則無法知曉具體在鏡像內做了什麼操作,無法對鏡像進行審計,存在安全隱患。
鏡像的緩存特性
構建鏡像中有相同的鏡像層時,會使用緩存來加速構建。
vim Dockerfile
FROM busybox
COPY testfile /
RUN echo helloword > file1
docker build -t demo:v2 .
docker history demo:v1
docker history demo:v2
vim Dockerfile
FROM busybox
COPY testfile /
RUN echo helloword > file1
RUN echo haha > file2
docker build -t demo:v2 .
docker history demo:v1
docker history demo:v2
docker run -it --rm demo:v1
docker run -it --rm demo:v2
docker run -it --rm demo:v3
Dockerfile最佳實踐
通過Dockerfile構建鏡像,有很好的審計功能,比較安全,推薦使用,不推薦使用docker commit 來直接構建
2. Dockerfile詳解
2.1 dockerfile常用指令
FROM
:指定base鏡像,如果本地不存在會從遠程倉庫下載。MAINTAINER
:設置鏡像的作者,比如用戶郵箱等。COPY
:把文件從build context複製到鏡像
支持兩種形式:COPY src dest 和 COPY [“src”, “dest”]
src必須指定build context中的文件或目錄ADD
:用法與COPY類似,不同的是src可以是歸檔壓縮文件,文件會被自動解壓到dest,也可以自動下載URL並拷貝到鏡像:
ADD html.tar /var/www
ADD http://ip/html.tar /var/wwwENV
:設置環境變量,變量可以被後續的指令使用:
ENV HOSTNAME server1.example.comEXPOSE
:如果容器中運行應用服務,可以把服務端口暴露出去:
EXPOSE 80VOLUME
:申明數據卷,通常指定的是應用的數據掛載點:
VOLUME ["/var/www/html"]WORKDIR
:爲RUN、CMD、ENTRYPOINT、ADD和COPY指令設置鏡像中的當前工作目錄,如果目錄不存在會自動創建。RUN
:在容器中運行命令並創建新的鏡像層,常用於安裝軟件包:
RUN yum install -y vimCMD 與 ENTRYPOINT
這兩個指令都是用於設置容器啓動後執行的命令,但CMD會被docker run後面的命令行覆蓋,而ENTRYPOINT不會被忽略,一定會被執行。
docker run後面的參數可以傳遞給ENTRYPOINT指令當作參數。
Dockerfile中只能指定一個ENTRYPOINT,如果指定了很多,只有最後一個有效。
2.2 dockerfile使用案例
ADD自動解壓文件
ADD:用法與COPY類似,不同的是src可以是歸檔壓縮文件,文件會被自動解壓到dest,也可以自動下載URL並拷貝到鏡像:
ADD html.tar /var/www
ADD http://ip/html.tar /var/www
step1 放一個nginx的壓縮包在/root/docker下
step2 修改Dockerfile:
vim Dockerfile
FROM busybox
ADD nginx-1.16.1.tar.gz /
step3 構建鏡像:
docker build -t demo:v4 .
ADD的用法與COPY類似,但不同的是它可以將文件自動解壓到dest
step4 測試:
docker run -it -rm demo:v4
ENV定義環境變量
ENV:設置環境變量,變量可以被後續的指令使用:
ENV HOSTNAME sevrer1.example.com
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1 #定義環境變量hostname爲server1
ADD nginx-1.16.1.tar.gz /
step2 構建鏡像:
docker build -t demo:v5 .
step3 測試:
docker run -it --rm demo:v5
env查看到環境變量信息:
VOLUME聲明數據卷,在封裝應用容器時常用
VOLUME:申明數據卷,通常指定的是應用的數據掛載點:
VOLUME ["/var/www/html"]
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
ADD nginx-1.16.1.tar.gz /
VOLUME ["/data"]
step2 構建鏡像:
docker build -t demo:v6 .
step3 查看鏡像的創建歷史:
docker history demo:v6
step4 進入容器,創建文件:
docker run -it demo:v6
cd /data/
touch file1
按ctrl+p+q退出
step5 查看demov6的掛載信息:
docker ps #找出對應的容器ID
docker inspect 5231e1ce76fe
docker引擎在啓動容器時發現定義了卷,會自動生成一個卷。而docker引擎在啓動容器時,自動在本地爲它創建了這個目錄,並且掛載在容器內,可以讓容器讀取到本地的數據目錄。
step6 進入目錄,查看到剛剛創建的文件file:
cd /var/lib/docker/volumes/ebe2016c773e0ae350be1f6dc9c6463bb71009bcb6a8b8355e58509c8741f2fe/_data
ls
step7 在此目錄下修改文件,容器中的文件同樣會被修改:
echo hello > file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
echo hello >> file1
cat file1
docker ps
docker attach 5231e1ce76fe
/data # ls
/data # cat file1
/data # rm -f file1
按ctrl+p+q退出
step8 釋放數據卷:
docker ps
docker rm -f 52 #刪除容器(此處使用容器ID的簡寫,因爲只有一個52開頭的ID)
docker volume ls #顯示所有數據卷
docker volume prune #釋放數據卷(刪除沒有被容器使用的卷)
WORKDIR設置鏡像中的當前工作目錄
WORKDIR:爲RUN、CMD、ENTRYPOINT、ADD和COPY指令設置鏡像中的當前工作目錄,如果目錄不存在會自動創建。
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
step2 構建鏡像:
docker build -t demo:v7 .
step3 測試:
docker run -it --rm demo:v7
我們可以發現,進入容器時就默認在/nginx這個目錄下,而將nginx的包解壓在了這個目錄中。
這個目錄之前並不存在,是WORKDIR自動創建的
CMD與ENTRYPOINT
這兩個指令都是用於設置容器啓動後執行的命令,但CMD會被docker run後面的命令行覆蓋,而ENTRYPOINT不會被忽略,一定會被執行。
docker run後面的參數可以傳遞給ENTRYPOINT指令當作參數。
Dockerfile中只能指定一個ENTRYPOINT,如果指定了很多,只有最後一個有效。
CMD:
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
CMD echo helloword
step2 構建鏡像:
docker build -t demo:v8 .
step3 測試:
docker run -it --rm demo:v8
docker run -it --rm demo:v8 sh #命令被覆蓋
ENTRYPOINT:
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV hostname server1
WORKDIR /nginx
ADD nginx-1.16.1.tar.gz /nginx
VOLUME ["/data"]
ENTRYPOINT echo helloword
step2 構建鏡像:
docker build -t demo:v9 .
step3 測試:
docker run -it --rm demo:v9
docker run -it --rm demo:v9 sh #命令沒有被覆蓋
2.3 shell和exec格式的區別
區別一
shell格式底層會調用/bin/sh -c來執行命令,可以解析變量,而exec格式不會
shell格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT echo $hostname
step2 構建鏡像:
docker build -t demo:v10 .
step3 測試:
docker run -it --rm demo:v10
可以看到變量$hostname
的值被輸出了
exec格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT ["/bin/echo","$hostname"]
step2 構建鏡像:
docker build -t demo:v11 .
step3 測試:
docker run -it --rm demo:v11
可以看到變量$hostname
沒有被解析,而是直接輸出了$hostname
所以exec格式需要改寫:
vim Dockerfile
FROM busybox
ENV name redhat
ENTRYPOINT ["/bin/sh","-c","echo $hostname"]
再次測試,變量就被解析了
區別二
exec格式時,ENTRYPOINT可以通過CMD提供額外參數,CMD的額外參數可以在容器啓動時動態替換。在shell格式時,ENTRYPOINT會忽略任何CMD或者docker run提供的參數
exec格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENTRYPOINT ["/bin/echo","hello"]
CMD ["world"]
step2 構建鏡像:
docker build -t demo:v12 .
step3 測試:
docker run -it --rm demo:v12
docker run -it --rm demo:v12 linux #輸出的world變爲linux
docker run -it --rm demo:v12 redhat #輸出的world變爲redhat
shell格式
step1 修改Dockerfile:
vim Dockerfile
FROM busybox
ENTRYPOINT echo hello
CMD echo world
step2 構建鏡像:
docker build -t demo:v13 .
step3 測試:
docker run -it --rm demo:v13
docker run -it --rm demo:v13 linux #輸出的world變爲linux
docker run -it --rm demo:v13 redhat #輸出的world變爲redhat
可以看到,在shell格式時,ENTRYPOINT會忽略任何CMD提供的參數