一、Dockerfile使用详解
1.1 Dockerfile介绍
DockerFile是一种被Docker程序解释执行的脚本,由一条条的命令组成的,每条命令对应linux下面的一条命令,Docker程序将这些DockerFile指令再翻译成真正的linux命令,其有自己的书写方式和支持的命令,Docker程序读取DockerFile并根据指令生成Docker镜像,相比手动制作镜像的方式,DockerFile更能直观的展示镜像是怎么产生的,有了DockerFile,当后期有额外的需求时,只要在之前的DockerFile添加或者修改响应的命令即可重新生成新的Docker镜像,避免了重复手动制作镜像的麻烦,类似于shell脚本一样,可以方便高效的制作镜像Docker守护程序 Dockerfile 逐一运行指令,如有必要,将每个指令的结果提交到新镜像,然后最终输出新镜像的ID。Docker守护程序将自动清理之前发送的上下文。
请注意,每条指令都是独立运行的,并会导致创建新镜像,比如RUN cd /tmp对下一条指令不会有任何影响。
Docker将尽可能重用中间镜像层(缓存),以显著加速docker build命令的执行过程,这由 Using cache控制台输出中的消息指示。
1.2 Dockerfile镜像制作和使用流程
1.3 Dockerfile文件的制作镜像的分层结构
1.4 Dockerfile文件格式
Dockerfile 是一个有特定语法格式的文本文件
dockerfile 官方说明: https://docs.docker.com/engine/reference/builder/
帮助: man 5 dockerfile
Dockerfile文件说明:
二、 Dockerfile相关指令
相关指令:
这里我们对以下命令进行验证:
2.1 FROM
定制镜像,需要先有一个基础镜像,在这个基础镜像上进行定制。
FROM就是指定基础镜像,此指令通常必须放在Dockerfile文件第一个非注释行。后续的指令都是运行于此基础准镜像所提供的运行环境基础镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找制定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件,如果找不到指定的镜像文件,则会返回一个错误信息。
如何选择合适的镜像呢?
对于不同的软件官方都提供了相关的docker镜像,比如: nginx、redis、mysql、httpd、tomcat等服务类的镜像,也有操作系统类,如: centos、ubuntu、debian等。建议使用官方镜像,比较安全。
格式:
2.2 LABEL
LABEL:说明信息
可以指定镜像元数据,如:镜像作者等。
2.3 RUN
RUN 指令用来在构建镜像阶段需要执行 FROM 指定镜像所支持的Shell命令。
通常各种基础镜像一般都支持丰富的shell命令
注意: RUN 可以写多个,每一个RUN指令都会建立一个镜像层,所以尽可能合并成一条指令,比如将多个shell命令通过 && 连接一起成为在一条指令
每个RUN都是独立运行的,和前一个RUN无关
我们创建一个目录:
[root@Node2 ~]#:mkdir /data/docker/dockerfile/system/ -p
进到这个目录下:创建一个Dockerfile文件。
运行一条命令:在根下创建一个文件test.txt。文件内容为test:
[root@Node2 ~]#:cd /data/docker/dockerfile/system/
[root@Node2 system]#:vim Dockerfile
FROM centos
LABEL author="docker cloud" \
version="1.0"
RUN echo "test" > /test.txt
这里:文件名只能叫Dockerfile。区分大小写字母!并且只能在当前路径下执行。
在当前文件夹下,构建镜像:
docker build -t centos:v1 .
这样就构建了一个镜像:centos:v1。可以进入镜像,有了一个我们自己创建的文件:/test.txt。
进入镜像,查看验证:
多个 RUN 命令和shell命令不同,多个RUN是独立无关的。比如上一行的RUN是cd /etc/。下一行的RUN是touch 123。则不会在/etc/下创建文件123的。123会创建在根下。两个RUN是独立的。
例子:修改Dockerfile。构建镜像:centos:v2
运行容器,进入查看:123是否在/etc/下还是/下:
可以发现123文件是在/下。并不在/etc/下。
如果想在/etc/下创建文件(夹)
可以写在一行上,RUN cd /etc/ && touch 123
这样就在/etc/下创建了123文件夹
[root@Node2 system]#:cat Dockerfile
FROM centos
LABEL author="docker cloud" \
version="1.0"
RUN cd /etc/ && touch 123
[root@Node2 system]#:docker build -t centos:v3 .
......
#查看镜像
[root@Node2 system]#:docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos v3 5005bf4b61a4 5 seconds ago 231MB
centos v2 afc36b7672c2 16 minutes ago 231MB
......
#运行容器,查看根下不存在123文件,是在/etc/下了。
[root@Node2 system]#:docker run -it 500 bash
[root@00f6cf384624 /]# ls
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@00f6cf384624 /]# cd /etc/
[root@00f6cf384624 etc]# ls 123
123
2.4 ENV
ENV 可以定义环境变量和值,会被后续指令(如:ENV,ADD,COPY,RUN等)通过$KEY或${KEY}进行引用,并在容器运行时保持。
Dockerfile文件:
[root@Node2 system]#:vim Dockerfile
[root@Node2 system]#:cat Dockerfile
FROM centos
LABEL author="docker cloud" \
version="1.0"
ENV name=cxk class=yun
RUN touch ${name}.txt
构建镜像,并运行该镜像:进入查看是否创建了cxk.txt文件:
[root@Node2 system]#:docker build -t centos:v3 .
[+] Building 0.5s (6/6) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 206B 0.0s
=> [internal] load metadata for docker.io/library/centos:latest 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [1/2] FROM docker.io/library/centos:latest 0.0s
=> [2/2] RUN touch cxk.txt 0.4s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:28925936639bf4d10e552a082d5bdea285faaf7b97644e4ef89ab5d850a69958 0.0s
=> => naming to docker.io/library/centos:v3 0.0s
[root@Node2 system]#:docker run -it centos:v3 bash
[root@c231d5ca5d0c /]# ls
bin dev home lib64 media opt root sbin sys usr
cxk.txt etc lib lost+found mnt proc run srv tmp var
[root@c231d5ca5d0c /]# echo $name
cxk
[root@c231d5ca5d0c /]# echo $class
yun
2.5 COPY
复制本地宿主机的,到容器中:
说明:
1.可以是多个,可以使用通配符,通配符规则满足GO的filepath.Match规则和filepath.Match。
参考链接: https://golang.org/pkg/path/filepath/#Match
2.必须是build上下文中的路径,就是Dockerfile所在的目录的相对路径,不能是其父目录中的文件
3.如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
4.如果指定了多个,或在中使用了通配符,则必须是一个目录,且必须以/结尾。虽然不报错,效果不一样了,不加/类似于改名
5.可以是绝对路径或者WORKDIR指定的相对路径
6.使用COPY指令,源文件的各种元数据都会报错。比如读、写、执行权限、文件变更时间等。
7.如果事先不存在,它将会被自动创建,这包括其父目录路径,即递归创建目录。
例子:
演示:
[root@Node2 system]#:touch index.html #在宿主机当前目录下,创建文件index.html
[root@Node2 system]#:ls
Dockerfile index.html
[root@Node2 system]#:vim Dockerfile #修改Dockerfile文件
[root@Node2 system]#:cat Dockerfile
FROM centos
LABEL author="docker cloud" \
version="1.0"
RUN useradd lisi #创建一个用户
COPY --chown=lisi:lisi index.html /data/ #使用copy,将宿主机路径下该文件复制到容器中的/data/目录下。
容器中这个/data/目录如果没有,会自动创建的。
构建镜像,并运行容器,进入查看是否有/data/目录,并且宿主机的index.html文件是否复制在了/data/下,并且权限为lisi。
[root@Node2 system]#:docker build -t centos:v4 .
[+] Building 0.1s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 209B 0.0s
=> [internal] load metadata for docker.io/library/centos:latest 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 86B 0.0s
=> [1/3] FROM docker.io/library/centos:latest 0.0s
=> CACHED [2/3] RUN useradd lisi 0.0s
=> [3/3] COPY --chown=lisi:lisi index.html /data/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:bd9fb326b5234baa6067c0ee7c81558c545f93ad72dc22d1f31d7d64dfc7a0c3 0.0s
=> => naming to docker.io/library/centos:v4 0.0s
[root@Node2 system]#:docker run --rm -it centos:v4 bash
[root@df300b39c30e /]# ls -l /data/
total 0
-rw-r--r--. 1 lisi lisi 0 Aug 2 16:33 index.html
[root@df300b39c30e /]#
关于复制到容器中,后面加不加/的问题:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
COPY index.html /data/ #复制到容器中,这里加/也就是:/data/
#构建镜像
[root@Node2 system]#:docker build -t c7:v1 .
[+] Building 0.1s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 193B 0.0s
=> [internal] load metadata for docker.io/library/centos:centos7.9.2009 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 112B 0.0s
=> CACHED [1/2] FROM docker.io/library/centos:centos7.9.2009 0.0s
=> [2/2] COPY index.html /data/ 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:0b4fc12ae8f837c5b6bfc9de5867f53c1b77664b8c218bb7d7f18f6d7f645905 0.0s
=> => naming to docker.io/library/c7:v1 0.0s
#进入容器:
[root@Node2 system]#:docker run -it c7:v1 bash
[root@d37bb6e78069 /]# ls
anaconda-post.log data etc lib media opt root sbin sys usr
bin dev home lib64 mnt proc run srv tmp var
[root@d37bb6e78069 /]# ls data #那么就把index.html复制到了/data/文件夹下。
index.html
[root@d37bb6e78069 /]# cat data/index.html
nginx port EXPOSE
[root@d37bb6e78069 /]#
[root@d37bb6e78069 /]#
[root@d37bb6e78069 /]# exit
exit
[root@Node2 system]#:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
COPY index.html /data #复制到容器中,这里不加/也就是:/data
#构建镜像:
[root@Node2 system]#:docker build -t c7:v2 .
[+] Building 0.1s (7/7) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 192B 0.0s
=> [internal] load metadata for docker.io/library/centos:centos7.9.2009 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 88B 0.0s
=> CACHED [1/2] FROM docker.io/library/centos:centos7.9.2009 0.0s
=> [2/2] COPY index.html /data 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:ee778f72054089fb5ffca94ed0ce9f289e3809667a85bbde4d34e9c5d49bb6b1 0.0s
=> => naming to docker.io/library/c7:v2 0.0s
#运行容器:进入
[root@Node2 system]#:docker run -it c7:v2 bash
[root@4ea5ed32c30c /]# ls
anaconda-post.log data etc lib media opt root sbin sys usr
bin dev home lib64 mnt proc run srv tmp var
[root@4ea5ed32c30c /]# cat data #这里的data是文件,相当于把index.html复制进来改名为data了
nginx port EXPOSE
2.6 ADD
该命令可认为是增强版的COPY,不仅支持COPY,还支持自动解缩。可以将复制指定的 到容器中的。
说明:
示例:
我们准备一个清华源的文件qh.repo
将容器中的/etc/yum.repos.d/下的repo文件删除,将该qh.repo文件通过ADD的方式添加到指定路径下:
Dockerfile文件如下:
构建镜像:
运行容器:查看是否有qh.repo
2.7 CMD
一个容器中需要持续运行的进程一般只有一个,CMD 用来指定启动容器时默认执行的一个命令,且其运行结束后,容器也会停止,所以一般CMD 指定的命令为持续运行且为前台命令。
如果docker run没有指定任何的执行命令或者dockerfile里面也没有entrypoint,那么开启容器后就会使执行CMD指定的默认的命令
前面介绍过的RUN命令是在构建镜像执行的命令,注意二者的不同之处
每个dockerfile只能有一条CMD命令,如指定了多条,只有最后一个被执行
如果用户启动容器时用docker run xxx指定运行的命令,则会覆盖CMD指定的命令
范例:
repo源下载在本地:
wget http://mirrors.aliyun.com/repo/Centos-8.repo
wget http://mirrors.aliyun.com/repo/Centos-7.repo
构建镜像:
由于构建的镜像中CMD命令是前台运行的命令。所以运行系统类容器时,依旧是启动状态:
另一种用法:
现拉取一个centos:centos7.9.2009
[root@Node2 system]#:vim Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
RUN rm -rf /etc/yum.repos.d/* #删除所有yum源
COPY epel-7.repo /etc/yum.repos.d/ #将本地的epel源复制进容器的yum中
ADD qh.repo /etc/yum.repos.d/ #清华源
RUN yum install -y epel-release.noarch #下载epel源
RUN yum install -y nginx #yum安装nginx
ADD nginx-1.18.0.tar.gz /usr/local/src #将本地的nginx源码包复制进去
COPY index.html /usr/share/nginx/html #首页
CMD ["nginx","-g","daemon off;"]
写一个主页,并且构建镜像:
后台启动:
[root@Node2 system]#:docker run -d --name cn -p 80:80 c7:v1
2991e1f182a645bf3e2090254dcfc373c38a0a521a708ac8c606b8eb58416c3b
验证:可以看到,我们指定了宿主机与容器的端口。访问宿主机的IP端口默认80。
2.8 ENTRYPOINT
功能类似于CMD,配置容器启动后执行的命令及参数
ENTRYPOINT不能被docker run提供的参数覆盖,而是追加,即如果docker run命令有参数,那么参数全部都会作为ENTRYPOINT的参数。
如果docker run后面没有额外参数,但是dockerfile中的CMD里有(即上面CMD的第三种用法),即Dockerfile中既有CMD也有ENTRYPOINT,那么CMD的全部内容会作为ENTRYPOINT的参数。
如果docker run 后面有额外参数,同时Dockerfile中即有CMD也有ENTRYPOINT,那么docker run后面的参数覆盖掉CMD参数内容,最终作为ENTRYPOINT的参数。
可以通过docker run --entrypoint string 参数在运行时替换,注意string不要加空格
使用CMD要在运行时重新写命令本身,然后在后面才能追加运行参数,ENTRYPOINT则可以运行时无需重写命令就可以直接接受新参数
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效。
例子:编译安装nginx:
[root@Node2 system]#:ls
Centos-7.repo Centos-8.repo Dockerfile epel-7.repo index.html nginx-1.18.0.tar.gz qh.repo
Dockerfile文件:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
RUN rm -rf /etc/yum.repos.d/
COPY epel-7.repo /etc/yum.repos.d/ #通过下载到本地:wget https://mirrors.aliyun.com/repo/epel-7.repo
ADD qh.repo /etc/yum.repos.d/
RUN yum -y install gcc gcc-c++ make automake pcre pcre-devel zlib zlib-devel openssl openssl-devel wget
ADD nginx-1.18.0.tar.gz /usr/local/src #准备nginx源码包到本地,ADD可以支持解压包
RUN cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install
COPY index.html /apps/nginx/html #我们在本地准备的index.html文件
CMD ["-g","daemon off;"]
ENTRYPOINT ["/apps/nginx/sbin/nginx"] #CMD会不在
构建镜像:docker build -t c7:v3 .
启动容器:
docker run -d -p 80:80 --name centnginx c7:v3
这里c7:v3是镜像,后面不跟参数,那么ENTRYPOING会使用CMD中的命令,作为参数。放在ENTRYPOINT的后面执行。也就是:/apps/nginx/sbin/nginx -g daemon off
2.9 VOLUME
在容器中创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等,一般会将宿主机上的目录挂载至VOLUME 指令指定的容器目录。即使容器后期被删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存。
宿主机目录为:/var/lib/docker/volumes/<volume_id>/_data
语法:
注意:
示例:在容器创建两个/data1/和/data2/的挂载点
Dockerfile文件:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
VOLUME ["/data1/","/data2/"]
构建镜像:
运行容器:发现在根下自动创建了两个文件夹/data1/和/data2/
在/data1/下创建一个文件123321
开一个终端查看宿主机:那么在宿主机也会有一个123321的文件:
同理我们也可以在宿主机创建一个文件夹root_Node2。看是否在容器中/data1/下也创建了该文件夹。
[root@Node2 ~]#:mkdir /var/lib/docker/volumes/efb1be01ff17e52521e07bc175a9c444e5bbed1e42af49f7797e5e30cd940b39/_data/root_Node2
那么在容器中也有了该文件夹。
总结:VOLUME会在宿主机中挂载上容器中的指定目录,但宿主机中的挂载点不是我们决定的。一般默认在/var/lib/docker/volumes/<volume_id>/_data/下。
2.10 EXPOSE
指定服务端的容器需要对外暴露(监听)的端口号,以实现容器与外部通信。EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会真正暴露端口,即不会自动在宿主进行端口映射因此,在启动容器时需要通过 -P 或-p ,Docker 主机才会真正分配一个端口转发到指定暴露的端口才可使用。
注意:即使Dockerfile没有EXPOSE 端口指令,也可以通过docker run -p临时暴露容器内程序真正监听的端口,所以EXPOSE 相当于指定默认的暴露端口,可以通过docker run -P 进行真正暴露。
示例:
编译安装nginx,暴露端口:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
RUN rm -rf /etc/yum.repos.d/
COPY epel-7.repo /etc/yum.repos.d/
ADD qh.repo /etc/yum.repos.d/
RUN yum -y install gcc gcc-c++ make automake pcre pcre-devel zlib zlib-devel openssl openssl-devel wget
ADD nginx-1.18.0.tar.gz /usr/local/src
RUN cd /usr/local/src/nginx-1.18.0 && ./configure --prefix=/apps/nginx && make && make install
VOLUME ["/apps/nginx/html"]
COPY index.html /apps/nginx/html
CMD ["-g","daemon off;"]
EXPOSE 80 443
ENTRYPOINT ["/apps/nginx/sbin/nginx"]
[root@Node2 system]#:echo "nginx port EXPOSE" > index.html
构建镜像:
那么在本机的路径下的文件:通过查看容器详细信息:docker inspect web1查看容器中的/apps/nginx/html/对应在宿主机的哪个路径。过滤出一个叫Source的一行:
可以看到这个index.html就是容器中nginx的主页
这样就通过暴露端口的方式,找到本机的哪个端口。也可以在宿主机修改主页文件,而不必进入容器中了。
在删除容器时如果指定-v选项。那么宿主机的这个文件夹也一并删除了。
2.11 WORKDIR
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,当容器运行后,进入容器内WORKDIR指定的默认目录。
WORKDIR 指定工作目录(或称当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会自行创建。
范例:
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
则最终路径为 /a/b/c
验证:
Dockerfile文件:
[root@Node2 system]#:cat Dockerfile
FROM centos:centos7.9.2009
LABEL author="dockerfile cloud" \
version="1.0"
WORKDIR /data/
RUN touch a.txt
CMD ["tail","-f","/etc/hosts"]
构建镜像:
运行容器:进入查看:
这样我们touch a.txt创建的文件就在指定的/data/下了。WORKDIR指定文件目录。
---end---