您好,登錄后才能下訂單哦!
本篇文章展示了Docker容器中Dockerfile配置文件的介紹,文章主要介紹Docker執行Dockerfile的流程、Dockerfile的結構、Dockerfile常用指令以及鏡像緩存,有需要的小伙伴可以參考。
Dockerfile 是Docker中用于定義鏡像自動化構建流程的配置文件。在Dockerfile中,包含了構建鏡像過程中需要執行的命令和其他操作。通過Dockerfile可以更加清晰,明確的給定Docker鏡像的制作過程,由于僅是簡單,小體積的文件,在網絡等介質中傳遞的速度快,能夠更快的實現容器遷移和集群部署。
Dockerfile是一個文本文件,其內包含了一條條的指令,每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。
相對于提交容器修改在進行鏡像遷移的方式相比,使用Dockerfile有很多優勢:
實際開發使用中很少會選擇容器提交這種方法來構建鏡像,而是幾乎采用Dockerfile來制作鏡像。
docker會刪除中間層創建的容器,但不會刪除中間層鏡像,所以可以使用docker run運行一個中間層容器,從而查看每一步構建后的鏡像狀態,,這樣就可以進行調試。
Dockerfile 又一行行命令語句組成,并且支持以#開頭的注釋行。
Dockerfile 分為四部分:基礎鏡像信息,維護者信息,鏡像操作指令,容器啟動時執行指令。
例如,以下為一個完整的Dockerfile:
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
1,第一行必須指定,基礎鏡像信息
FROM ubuntu
# Maintainer: docker_user <docker_user at email.com> (@docker_user)
2,維護者信息
MAINTAINER docker_user docker_user@email.com
# Commands to update the image
3,鏡像操作指令
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# Commands when creating a new container
4,容器啟動執行指令
CMD /usr/sbin/nginx
其中,已開始必須要指明所基于的鏡像名稱,接下來一般會說明維護者信息;后面則是鏡像操作指令,例如RUN指令。每執行一條RUN指令,鏡像添加新的一層,并提交;最后是CMD指令,來指明運行容器時的操作命令。
以下常見的dockerfile指令,基本包含常用的90%功能。
1,FROM--指定基礎鏡像
2,MAINTAINER--指定維護者信息
3,RUN--運行指定的命令
4,CMD--容器啟動時執行的命令
5, EXPOSE--聲明容器的服務端口
6,ENV--設置環境變量
7,ARG--構建參數
8,COPY--復制文件或目錄
9,ADD--更高級的復制文件/目錄
10,ENTRYPOINT--入口點
11,ENTRYPOINT與CMD指令結合使用
12,VOLUME--定義匿名卷
13,WORKDIR--指定工作目錄
14,USER--指定當前用戶
15,ONBUILD--為鏡像添加觸發器
1,FROM--指定基礎鏡像
第一條指令必須為FROM指令。
如果不以任何鏡像為基礎,那么寫法如下,同時意味著接下來所寫的指令將作為鏡像的第一層開始:FROM nginx
FROM 指令支持三種格式:
FROM <image>
FROM <image>:<tag>
FROM <image>:<digest>
三種寫法,第二種和第三種是可選項,如果沒有選擇,那么默認為latest。
在Dockerfile中可以多次出現FROM指令,當FROM第二次或者之后出現時,表示在此刻構建時,要將當前指出鏡像的內容合并到此刻構建鏡像的內容。
ARG是唯一可以在FROM之前執行的參數:
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
2,MAINTAINER--指定維護者信息
格式為:MAINTAINER <name>,指定維護者信息(鏡像維護者姓名或郵箱),可選項。
例如:MAINTAINER docker_user docker_user@email.com
3,RUN--運行指定的命令
RUN有兩種格式:
RUN <command>
RUN ["executable", "param1", "param2"]
前者將在shell終端運行命令,即/bin/sh -c; 后者則使用exec執行。指定使用其它終端可以通過第二種方式實現,例如使用bash終端: RUN ["/bin/bash", "-c", "echo hello"]。
注意:多行命令不要寫多個RUN,原因是Dockerfile中每一個指令都會建立一層,多少個RUN就構建了多少層鏡像,會造成鏡像的臃腫、多層,不僅僅增加了構件部署的時間,還容易出錯。一般一個RUN指令后邊可以跟多個要執行的命令(使用&&符即可),當命令較長時可以使用"\"來換行。
例子如下:
RUN yum -y install gcc* pcre-devel openssl-devel zlib-devel unzip make vim net-tools elinks tree \
&& groupadd nginx \
&& useradd nginx -g nginx -s /sbin/nologin
4,CMD--容器啟動時執行的命令
CMD支持三種格式:
CMD ["executable","param1","param2"] 使用 exec 執行,推薦方式;
CMD command param1 param2 在 /bin/sh 中執行,提供給需要交互的應用;
CMD ["param1","param2"] 提供給 ENTRYPOINT 的默認參數;
指定啟動容器時執行的命令,每個dockerfile只能有一條CMD命令。如果指令了多條命令,只有最后一條會被執行。
如果用戶啟動容器時候指定了運行的命令,則會覆蓋掉CMD指定的命令。
補充細節:如果不是使用shell(/bin/sh)這種方式,”[]“中括號這里邊包括參數的一定要用雙引號,千萬不能寫成單引號。原因是參數傳遞后,docker解析的是一個JSON array
例如:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
CMD echo hello world
CMD echo hello china
[root@sqm-docker01 dfs]# docker run --rm test:v1 #只有最后一條CMD命令會被執行
hello china
[root@sqm-docker01 dfs]# docker run --rm test:v1 echo "hello friend" #在啟動容器時指定運行命令,會將其覆蓋
hello friend
采用exec格式,那么上面的例子改為:
FROM centos:latest
CMD ["/bin/bash","-c","echo hello world"]
CMD ["/bin/bash","-c","echo hello china"]
還有一種情況下CMD會結合ENTRYPOINT指令使用,后面會講到。
5, EXPOSE--聲明容器的服務端口
格式為 EXPOSE <端口1> [<端口2>...] 。
EXPOSE 指令是聲明運行時容器提供服務端口,這只是一個聲明,在運行時并不會因為這個聲明應用就會開啟這個端口的服務。
在Dockerfile 中寫入這樣的聲明有兩個好處,一個是幫助鏡像使用者理解這個鏡像服務的守護端口,以方便配置映射;另一個用戶則是運行時使用隨機端口映射時,也就是docker run -P 時,會自動隨機映射EXPOSE的端口。
要將 EXPOSE 和在運行時使用 -p <宿主端口>:<容器端口> 區分開來。-p,是映射宿主端口和容器端口,換句話說,就是將容器的對應端口服務公開給外界訪問,而 EXPOSE 僅僅是聲明容器打算使用什么端口而已,并不會自動在宿主進行端口映射。
例如:EXPOSE 80 443
6,ENV--設置環境變量
格式有兩種:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
兩者的區別是第一種是一次設置一個,第二種是一次設置多個。
ENV這個指令就是指定一個環境變量,會被后續RUN指定使用,并在容器運行時保持。
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
這個例子中演示了如何換行,以及對含有空格的值用雙引號括起來的辦法,這和shell下的行為是一致的。
舉例如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM busybox:latest
ENV var1=haha var2=hehe
RUN echo $var1 > a.txt && echo $var2 > b.txt
執行dockerfile后,進行容器查看文件:
[root@sqm-docker01 dfs]# docker run --rm test:v2 /bin/sh
/ # cat a.txt
haha
/ # cat b.txt
hehe
#并且定義的變量會在容器運行時保持:
/ # echo $var1
haha
/ # echo $var2
hehe
7,ARG--構建參數
格式:
ARG <name>[=<default value>]
構建參數和ENV的效果一樣,都是設置環境變量。所不同的是,ARG所設置的構建環境的環境變量,在將來容器運行時是不會存在的這些環境變量的。但是不要因此就使用ARG保存密碼之類的信息,因為docker history還是可以看到所有值的。
#用法一:在執行docker build構建時傳遞參數
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
ARG user
ARG password
RUN echo $user > a.txt \
&& echo $password >> a.txt
#傳遞兩個參數時,則在每個參數中添加--build-arg。
[root@sqm-docker01 dfs]# docker build --build-arg user=sqm --build-arg password=123.com -t arg:v1 .
#運行容器,參數傳遞成功
[root@sqm-docker01 dfs]# docker run -it --rm arg:v1 /bin/bash
root@4809b0c54f8d:/# cat a.txt
sqm
123.com
#與ENV不同,在容器中是不會存在這些變量的
root@9383b7d3d21e:/# echo $user
root@9383b7d3d21e:/# echo $password
root@9383b7d3d21e:/#
注意:如果指定了該參數,但Dockerfile中未使用,構建過程中會輸出警告。
#用法二:在dockerfile中設定一個默認值,如果ARG指令具有默認值,并且在構建時未傳遞任何參數,那么構建其將使用該默認值
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
ARG user=zhangsan
ARG password=123456
RUN echo $user $password > a.txt
#構建鏡像且不傳遞任何參數:
[root@sqm-docker01 dfs]# docker build -t arg:v2 .
#運行容器,參數傳遞成功
[root@sqm-docker01 dfs]# docker run -it --rm arg:v2 /bin/bash
root@b52fa70086de:/# cat a.txt
zhangsan 123456
#多階段構建中每個階段都必須包含arg指令:
FROM busybox
ARG user
RUN ./run/setup $user
FROM busybox
ARG user
RUN ./run/other $user
#ENV變量會覆蓋同名的ARG變量:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
ARG user=zhangsan
ENV user lisi
RUN echo $user > a.txt
[root@sqm-docker01 dfs]# docker build -t arg:v3 .
[root@sqm-docker01 dfs]# docker run -it --rm arg:v3 /bin/bash
root@a2aefd05efee:/# cat a.txt
lisi
#ARG和ENV指令結合使用:(一般這種用法,不常用)
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
ARG var
ENV user=${var}
RUN echo $user > a.txt
[root@sqm-docker01 dfs]# docker build --build-arg var=zhangsan -t arg:v4 .
[root@sqm-docker01 dfs]# docker run -it --rm arg:v4 /bin/bash
[root@26bf8c139a3f /]# cat a.txt
zhangsan
8,COPY--復制文件或目錄‘’
格式:
COPY [--chown=<user>:<group>] <源路徑>... <目標路徑>
COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目標路徑>"]
和RUN指令一樣,也有兩種格式,前者在shell終端運行; 后者則使用exec執行。
<源路徑>為宿主機的上的路徑,可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目標路徑>是容器中的路徑,可以是絕對路徑,也可以是相對于工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。
此外,還需要注意一點,使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。可以使用如下命令指定:
COPY [--chown=:] ...
COPY [--chown=:] ["",... ""] (包含空格的路徑需要這種形式)
–chown功能僅在用于構建Linux容器的Dockerfiles上受支持
例如:COPY --chown=nginx:nginx files* /somedir/
舉例:
[root@sqm-docker01 dfs]# echo "hello world" > index.html
[root@sqm-docker01 dfs]# echo aaaaaa > a.txt
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
COPY ./index.html /usr/share/nginx/html/
COPY ./a.txt /test/
[root@sqm-docker01 dfs]# docker run --rm nginx:v1 /bin/bash
root@8a1ee4925b43:/# cat /usr/share/nginx/html/index.html
hello world
#即使目標目錄不存在,會自己事先創建
root@8a1ee4925b43:/# cat /test/a.txt
aaaaaa
##拷貝整個目錄:
格式:COPY src WORKDIR/src
例如:
[root@sqm-docker01 dfs]# cat test/a.txt
addddddddddddaaaaa
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
COPY ./test /usr/share/nginx/test/
#必須要在目標路徑下指定要拷貝的目錄,不然只拷貝源目錄中的文件,而不拷貝目錄
#進入容器查看整個目錄及其目錄下的文件
[root@sqm-docker01 dfs]# docker run --rm nginx:v2 /bin/bash
root@4eae96cbe364:/# cd /usr/share/nginx/
root@4eae96cbe364:/usr/share/nginx# ls
html test
root@4eae96cbe364:/usr/share/nginx# cat test/a.txt
addddddddddddaaaaa
9,ADD--更高級的復制文件/目錄
格式為:
ADD [--chown=<user>:<group>] <源路徑>... <目標路徑>
ADD [--chown=<user>:<group>] ["<源路徑1>",... "<目標路徑>"]
ADD指令和COPY 的格式和性質基本一致。但是在 COPY`基礎上增加了一些功能:可以是一個URL(通過URL下載下來自動設置權限600);還可以是一個tar文件(自動解壓為目錄)。
舉例:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
ADD nginx-1.8.0.tar.gz /usr/src
[root@sqm-docker01 dfs]# docker run --rm nginx:v3 /bin/bash
[root@0c8d6789aa4c /]# cd /usr/src/
[root@0c8d6789aa4c src]# ls
debug kernels nginx-1.8.0 #自動解壓tar包
[root@0c8d6789aa4c src]# ls nginx-1.8.0/
CHANGES CHANGES.ru LICENSE README auto conf configure contrib html man src
#通過url下載鏈接文件放到目標路徑下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
ADD http://nginx.org/download/nginx-1.9.0.tar.gz /
[root@sqm-docker01 dfs]# docker run --rm nginx:v4 /bin/bash
[root@b9d978e3a333 /]# ls -lh nginx-1.9.0.tar.gz
-rw------- 1 root root 835K Apr 28 2015 nginx-1.9.0.tar.gz
#默認權限600
#與COPY的區別:
用于與COPY類似,不同的是COPY的只能是本地文件/目錄,如果src是歸檔文件(tar,zip,tgz,xz等),使用ADD命令文件會被自動解壓到dest。
因此在COPY和ADD指令中選擇的時候,可以遵循這樣的原則,所有的文件復制均使用COPY,僅在需要自動解壓縮的場合使用 ADD。
10,ENTRYPOINT--入口點
兩種格式:
ENTRYPOINT ["executable", "param1", "param2"](exec格式)
ENTRYPOINT command param1 param2(shell中執行)。
ENTRYPOINT 的 目的和CMD一樣,都是在指定容器啟動程序及參數。ENTRYPOINT 在運行時也可以替代,不過比CMD要略顯繁瑣,需要通過docekr run的參數 --entrypoint 來指定。
#與CMD比較說明:
1)相同點:只能寫一條,如果寫多條,那么只有最后一條生效。
容器啟動時才運行,運行時機相同。
2)不同點: ENTRYPOINT不會被運行的命令覆蓋,而CMD則會被覆蓋。
例子:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
ENTRYPOINT echo hello world
ENTRYPOINT echo hello china
#與CMD一樣,只有最后一條指令生效
[root@sqm-docker01 dfs]# docker run --rm en:v1
hello china
#不同的是不會被運行時的命令覆蓋
[root@sqm-docker01 dfs]# docker run ---rm en:v1 echo "test"
hello china
#除非使用--entrypoint參數
[root@sqm-docker01 dfs]# docker run --rm --entrypoint hostname en:v1
fa667d019ce5
#這里使用hostname命令將hello china覆蓋了
11,ENTRYPOINT與CMD指令結合使用
#如果我們在Dockerfile中同時寫了ENTRYPOINT和CMD,那么CMD指定的內容就會作為ENTRYPOINT的參數。
舉例如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
ENTRYPOINT ["/usr/bin/ping","baidu.com","-c"]
CMD ["4"]
#運行一個容器:
[root@sqm-docker01 dfs]# docker run --rm en:v2
PING baidu.com (39.156.69.79): 56 data bytes
64 bytes from 39.156.69.79: seq=0 ttl=127 time=72.931 ms
64 bytes from 39.156.69.79: seq=1 ttl=127 time=62.366 ms
64 bytes from 39.156.69.79: seq=2 ttl=127 time=58.875 ms
64 bytes from 39.156.69.79: seq=3 ttl=127 time=50.662 ms
--- baidu.com ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 50.662/61.208/72.931 ms
此時容器中運行的命令為:ping baidu.com -c 4。
#ENTRYPOINT中的參數始終會被使用,而CMD的額外參數可以在容器啟動時動態替換掉,例子如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
ENTRYPOINT ["/usr/bin/ping","baidu.com","-c"]
CMD ["4"]
[root@sqm-docker01 dfs]# docker run --rm en:v2 2
PING baidu.com (39.156.69.79) 56(84) bytes of data.
64 bytes from 39.156.69.79 (39.156.69.79): icmp_seq=1 ttl=127 time=80.4 ms
64 bytes from 39.156.69.79 (39.156.69.79): icmp_seq=2 ttl=127 time=61.5 ms
--- baidu.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 3ms
rtt min/avg/max/mdev = 61.532/70.945/80.358/9.413 ms
此時容器運行的命為:ping baidu.com -c 2。
#這兩種指令結合起來使用,有什么好處呢?我們通過以場景來理解:(例子和上邊例子大同小異)
//假設我們需要一個得知自己當前公網IP的鏡像,可以那么先用CMD來實現:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
RUN yum -y install curl
CMD ["/usr/bin/curl","-s","https://ip.cn"]
#構建鏡像并運行容器:
[root@sqm-docker01 dfs]# docker build -t myip:v1 .
[root@sqm-docker01 dfs]# docker run --rm myip:v1
{"ip": "117.136.60.216", "country": "江西省", "city": "移動"}
看起來我們是可以把它當作命令使用了,不過命令總有參數,如果我們希望顯示HTTP頭信息,就需要加上-i參數。那么我們可以直接加-i參數給docker run myip么?
[root@sqm-docker01 dfs]# docker run --rm myip:v1 -i
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"-i\": executable file not found in $PATH": unknown.
我們可以看到替換文件找不到的報錯,executable file not found,之前我們說過,跟在上面名字后面的是command,運行時會替換為CMD的值。因此這里的-i替換了原來的CMD,而不是添加在原來的curl -s https://ip.cn 后面。-i根本不是命令,所以自然找不到。
//為了有很好的解決這個問題,我們將CMD和ENTRYPOINT結合使用:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos:latest
RUN yum -y install curl
ENTRYPOINT ["/usr/bin/curl","-s","https://ip.cn"]
CMD ["-i"]
#構建并運行容器:
[root@sqm-docker01 dfs]# docker build -t myip:v2 .
[root@sqm-docker01 dfs]# docker run --rm myip:v2
HTTP/2 200
date: Wed, 19 Feb 2020 07:10:48 GMT
content-type: application/json; charset=UTF-8
set-cookie: __cfduid=d1903fe7e93a885c6ae4890572cda42161582096248; expires=Fri, 20-Mar-20 07:10:48 GMT; path=/; domain=.ip.cn; HttpOnly; SameSite=Lax
cf-cache-status: DYNAMIC
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
alt-svc: h4-25=":443"; ma=86400, h4-24=":443"; ma=86400, h4-23=":443"; ma=86400
server: cloudflare
cf-ray: 56766c505d6fb22e-HKG
{"ip": "117.136.60.216", "country": "江西省", "city": "移動"}
可以看到成功了,這是因為當存在ENTRYPOINT后,CMD的內容將會傳遞給ENTRYPOINT使用,從而達到了我們預期的效果。
##總結幾條規律:
12,VOLUME--定義匿名卷
格式為:
VOLUME ["<路徑1>", "<路徑2>"...]
VOLUME <路徑>
在Dockerfile中,我們可以預先指定某些目錄掛載為匿名卷,這樣在運行時如果用戶不指定掛載,其應用也可以正常運行,不會向容器存儲層寫入大量數據。
VOLUME /data
這里的/data 目錄就會在運行時自動掛載為匿名卷,任何向/data 中寫入的信息都不會記錄進容器存儲層,從而保證了容器存儲層的無狀態化。當然,運行容器時可以覆蓋這個掛載設置。
docker run -d -v mydata:/data xxxx
在上邊命令中,就使用了mydata 這個命名卷掛載到了/data 這個位置,替代了Dockerfile 中定義的匿名卷的掛載配置。
#注意:從該指令的支持的格式就可以知道,VOLUME只支持docker manager volume的掛載方式,而不支持bind mount的方式。
docker manager volume:不需要指定源文件,只需要指定mount point。把容器里面的目錄映射到了本地(宿主機)。
舉例如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
VOLUME /usr/share/nginx/html/
#構建并運行容器:
[root@sqm-docker01 dfs]# docker build -t volume:v1 .
[root@sqm-docker01 dfs]# docker run -itd --name volume volume:v1
5d963fb3b51ae9ddcc3b55382e289e0447235676f8893900a440f5f9500f035e
我們通過docker inspect查看通過該dockerfile創建的大量生成的容器,可以看到掛載點的信息:
[root@sqm-docker01 dfs]# docker inspect volume
"Mounts": [
{
"Type": "volume",
"Name": "190c5a22df09462a9f5fd54209b8bc5ad06fc9382f9c8b9c665c734e4bcf95e0",
"Source": "/var/lib/docker/volumes/190c5a22df09462a9f5fd54209b8bc5ad06fc9382f9c8b9c665c734e4bcf95e0/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
從上面的信息可以看出默認掛載到本地的源路徑為“/var/lib/docker/volumes/190c5a22df09462a9f5fd54209b8bc5ad06fc9382f9c8b9c665c734e4bcf95e0/_data” ,而目標路徑(容器內的路徑)為自定義的"/usr/share/nginx/html"。
13,WORKDIR--指定工作目錄
格式為:
WORKDIR <工作目錄路徑>
設置工作目錄,對RUN,CMD,ENTRYPOINT,COPY,ADD生效。如果目錄不存在,則會幫你創建,也可以設置多次,如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
#則最終的路徑為/a/b/c。
##WORKDIR也可以解析環境變量,如:
ENV DIRPATH /usr/src
WORKDIR $DIRPATH
RUN pwd
#pwd的執行結果是/usr/src。
注意:千萬不要把dockerfile等同于shell腳本來書寫,例如:
RUN cd /app
RUN echo "hello" > world.txt
將這個dockerfile 進行構建鏡像運行后,會發現找不到/app/world.txt 文件,或者其內容不是 hello。原因其實很簡單,在dockerfile中這兩行RUN命令的執行環境不同,是兩個完全不同的容器。這就是對 Dockerfile 構建分層存儲的概念不了解所導致的錯誤。
如果需要改變以后各層的工作目錄的位置,那么應該使用 WORKDIR 指令。
14,USER--指定當前用戶
格式: USER <用戶名>[:<用戶組>]
USER指令和WORKDIR相似,都是改變環境狀態并影響以后的層。
WORKDIR是改變工作目錄,USER是改變之后層的執行RUN,CMD,以及ENTRYPOINT 這類命令的身份。
當然,和WORKDIR一樣,USER只是幫助你切換到指定用戶而已,這個用戶必須是事先建立好的,否則無法切換:
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
如果以root執行腳本,在執行期間希望改變身份,比如希望以某個已經建立好的用戶來運行某個服務進程,不要使用su或者sudo,這些都需要比較麻煩的配置,而且在TTY缺失的環境下經常出錯。一般使用 gosu。
舉例如下:
# 建立 redis 用戶,并使用 gosu 換另一個用戶執行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下載 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 設置 CMD,并以另外的用戶(redis)執行
CMD [ "exec", "gosu", "redis", "redis-server" ]
15,ONBUILD--為鏡像添加觸發器
格式:ONBUILD <其它指令>
ONBUILD 是一個特殊的指令,它后面跟的是其他指令,比如RUN,COPY等,而這些指令,在當前鏡像構建時并不會被執行。
只有當以當前鏡像為基礎鏡像,去構建下一級鏡像的時候才會被執行。
Dockerfile中的其他指令都是為了定制當前鏡像而準備的,唯有ONBUILD 是為了幫助別人定制自己而準備的。
舉例如下:
//編寫一個Dockerfile文件,內容如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM nginx:latest
ONBUILD COPY ./index.html / #拷貝文件到容器內的 / 下
//利用上面的dockerfile文件構建鏡像:[root@sqm-docker01 dfs]# docker build -t image1 .
//利用image1鏡像創建容器:
[root@sqm-docker01 dfs]# docker run --rm -it image1 /bin/bash
root@db8a068d9f30:/# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
我們發現以image1鏡像運行的容器/目錄下并沒有index.html文件,這說明ONBUILD指定的指令并不會在自己的鏡像構建中執行。
//再編寫一個新的Dockerfile文件,內容如下:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM image1 #使用的基礎鏡像是上面構建的鏡像image1
CMD echo hello world
//利用上面的dockerfile文件構建鏡像:
[root@sqm-docker01 dfs]# docker build -t image2 .
Sending build context to Docker daemon 102.6MB
Step 1/2 : FROM image1
# Executing 1 build trigger
---> 796e32308d29
Step 2/2 : CMD echo hello world
---> Running in 5c16f913f5e9
Removing intermediate container 5c16f913f5e9
---> 4c0a45374727
Successfully built 4c0a45374727
Successfully tagged image2:latest
//利用image2鏡像創建容器:
[root@sqm-docker01 dfs]# docker run -it --rm image2 /bin/bash
root@3e3c1d0fe3f6:/# ls
bin dev home lib media opt root sbin sys usr
boot etc index.html lib64 mnt proc run srv tmp var
root@3e3c1d0fe3f6:/# cat index.html
hello world
我們發現以image2鏡像運行的容器根目錄下有index.html文件,說明觸發器執行了 COPY ./index.html / 指令。
1)Docker 會緩存已有鏡像的鏡像層,構建新鏡像時,如果某鏡像層已經存在,就直接使用,無需重新創建,如下所示:
//創建一個新的dockerfile
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
RUN yum -y install vim
//構建一個鏡像
[root@sqm-docker01 dfs]# docker build -t a:v1 .
#往剛剛創建的dockerfile中添加一點新內容(在鏡像中添加一條COPY指令):
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
RUN yum -y install vim
COPY testfile /
#以同一個dockefile重新構建鏡像:
[root@sqm-docker01 dfs]# touch testfile
[root@sqm-docker01 dfs]# docker build -t a:v2 .
Sending build context to Docker daemon 102.6MB
Step 1/3 : FROM centos
---> 0f3e07c0138f
Step 2/3 : RUN yum -y install vim
---> Using cache
---> 6467d4675159
Step 3/3 : COPY testfile /
---> 6b92fe05882f
Successfully built 6b92fe05882f
Successfully tagged a:v2
(1)確保testfile文件已存在
(2)Using cache,重點在這里:之前已經運行過相同的RUN指令,這次直接使用緩存中的鏡像層6467d4675159。
(3)執行COPY指令。
其過程是啟動臨時容器,復制testfile,提交新的鏡像層6b92fe05882f,刪除臨時容器。
如果我們希望在構建鏡像時不使用緩存,可以在docker build命令中加上--no-cache參數。
2)Dockerfile中每一個指令都會創建一個鏡像層,上層是依賴于下層的。無論什么時候,只要某一層發生變化,其上面所有層的緩存都會失效。
也就是說,如果我們改變 Dockerfile 指令的執行順序,或者修改或添加指令,都會使緩存失效。
舉例說明,比如交換上面RUN和COPY指令的順序:
[root@sqm-docker01 dfs]# cat Dockerfile
FROM centos
COPY testfile /
RUN yum -y install vim
雖然在邏輯上這種改動對鏡像的內容沒有影響,但由于分層的結構特性,Docker 必須重建受影響的鏡像層。
//重新構建鏡像:
[root@sqm-docker01 dfs]# docker build -t a:v3 .
Sending build context to Docker daemon 102.6MB
Step 1/3 : FROM centos
---> 0f3e07c0138f
Step 2/3 : COPY testfile /
---> 97e725434a6b
Step 3/3 : RUN yum -y install vim
---> Running in 5f30ff393047
......
Removing intermediate container 5f30ff393047
---> 90ceae8b3638
Successfully built 90ceae8b3638
Successfully tagged a:v3
從上面的輸出可以看到生成了新的鏡像層97e725434a6b,緩存已經失效。
除了構建時使用緩存,Docker 在下載鏡像時也會使用。例如我們下載 httpd 鏡像:
[root@sqm-docker01 dfs]#docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
f17d81b4b692: Already exists
06fe09255c64: Already exists
0baf8127507d: Already exists
07b9730387a3: Already exists
6dbdee9d6fa5: Already exists
Digest: sha256:76954e59f23aa9845ed81146ef3cad4a78f5eb3daab9625874ebca0e416367e2
Status: Image is up to date for httpd:latest
docker pull 命令輸出顯示第一層(base 鏡像)已經存在,不需要下載。
簡單了解鏡像分層概念
Dockerfile中每行代碼都會產生一個新的分層,一個鏡像不能超過127層,每個層都會產生一個單獨的id。
在已經存在image中的層,都是只讀的。當一個image被運行成為一個container的時候,這個層是可讀可寫的,需要操作鏡像層中的文件時,都是通過容器層復制一份,然后在進行操作,這個機制是copy on write(寫時復制)。
以上就是Docker容器的配置文件Dockerfile的詳細介紹了,看完之后是否有所收獲呢?如果想了解更多相關內容,歡迎關注億速云行業資訊!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。