上一篇Blog我们学习了容器数据卷,了解了容器的数据持久化机制,本篇Blog延续之前的镜像原理【Docker学习笔记 四】深入理解Docker镜像原理,继续进行学习,之前了解过:使用docker commit 命令提交容器成为一个新的版本,本篇Blog我们来继续看看image究竟是怎么构建出来的,依据什么文件构建的。
Dockerfile基本概念
通俗的说Dockerfile就是绘制镜像image的蓝图。我们可以从dockerhub进去搜索centos我们可以看到centos的镜像,然后跳转到github主页查看镜像构建的文件:
在github主页我们可以看到centos的构建过程,也就是今天的主角:dockerfile。
dockerfile常用指令
我们就是依据如下指令来进行dockerfile文件编写的:
Dockerfile 一般分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令,’#’ 为 Dockerfile 中的注释。
- 每个保留关键字(指令)都必须是大写字母
- 文件中的指令从上到下顺序执行,第一个指令必须是FROM
- 每一个指令都会创建提交一个新的镜像层,并提交!
关于Dockerfile指令的详细语法解释:Dockerfile文件详解,Dockerfile指令介绍的官方文档,接下来我们亲手构建一个dockerfile文件:
概念结构辨析
现在我们梳理下docker学习以来的各个概念
- dockerfile文件:镜像文件,制作镜像的蓝图,通过一行行指令标识镜像的实现过程
- dockerimage镜像:依据镜像文件build出来的镜像
- 容器:依据镜像运行的容器实例,镜像只读顶端的读写层
三个概念是自底向上的,联动前面学到的知识,其实dockerfile就是layer镜像层的编排设计图,通过这个设计图编排出一个个镜像,镜像是一个只读的使用联合文件系统的概念,顶端的容器就是读写的一个文件路径。所以镜像里的layer并不是image,这也就解释了我之前的困惑:难道镜像之间有依赖关系,删除一个镜像会影响另一个?其实镜像是基于layer构建的,而不是别的镜像,而layer可以理解为一行指令,删除镜像理解为只是删除了一个联合文件系统构建出来的一个目录包,并不会真的删除指令构建的文件,所以也不会影响别的镜像的使用。
依据dockerfile制作centos镜像
官方的centos功能不全,例如没有vim指令,这样执行vim指令的时候会报错:
[root@192 ~]# docker run -it centos [root@7a23a43233d6 /]# ls bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr [root@7a23a43233d6 /]# pwd / [root@7a23a43233d6 /]# vim test.java bash: vim: command not found [root@7a23a43233d6 /]#
下面通过编写Dockerfile文件来制作Centos镜像,并在官方镜像的基础上添加vim和net-tools工具。首先在/home/dockfile 目录下新建文件mydockerfile-centos。然后使用上述指令编写该文件
1 编写dockerfile文件
首先我们找到一个目录地址存放自己的镜像文件:
[root@192 ~]# cd /home [root@192 home]# ls dockerfiles mysql test.java tml-1 tml-not-add.java volume_centos [root@192 dockerfiles]# vim tml-dockerfile-centos
然后编写镜像文件tml-dockerfile-centos
FROM centos:7 MAINTAINER tml<1233@qq.com> ENV MYPATH /user/local WORKDIR $MYPATH RUN yum -y install vim RUN yum -y install net-tools EXPOSE 80 CMD echo $MYPATH CMD echo "---end---" CMD /bin/bash
逐行解释该Dockerfile文件的指令:
FROM centos
:该image文件继承官方的centos,后面加冒号如centos:7,用于指定镜像的版本ENV MYPATH /usr/local
:设置环境变量MYPATH ,后面有用到WORKDIR $MYPATH
:直接使用上面设置的环境变量,指定/usr/local为工作目录RUN yum -y install vim
和RUN yum -y install net-tools
:在/usr/local目录下,运行yum -y install vim和yum -y install net-tools命令安装工具,注意安装后的所有依赖和工具都会打包到image文件中EXPOSE 80
:将容器80端口暴露出来,允许外部连接这个端口CMD
:指定容器启动的时候运行命令
之所以使用7的版本是因为在2022年1月31日,CentOS团队从官方镜像中移除了CentOS 8的所有包。
2 依据dockerfile构建dockerimage镜像
通过这个dockerfile构建镜像,构建镜像命令:docker build -f dockerfile文件路径 -t 镜像名[:版本号] .(这里有个小点.)
上面命令中,-t
参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。
[root@192 dockerfiles]# docker build -f tml-dockerfile-centos -t my-centos-image:0.1 . Sending build context to Docker daemon 2.048kB Step 1/10 : FROM centos:7 7: Pulling from library/centos 2d473b07cdd5: Pull complete Digest: sha256:9d4bcbbb213dfd745b58be38b13b996ebb5ac315fe75711bd618426a630e0987 Status: Downloaded newer image for centos:7 ---> eeb6ee3f44bd Step 2/10 : MAINTAINER tml<1233@qq.com> ---> Running in 7247002afc76 Removing intermediate container 7247002afc76 ---> c1ea220d6008 Step 3/10 : ENV MYPATH /user/local ---> Running in ce0625db8456 Removing intermediate container ce0625db8456 ---> f8d16a4d561d Step 4/10 : WORKDIR $MYPATH ---> Running in 8d2b2d15d9d7 Removing intermediate container 8d2b2d15d9d7 ---> e3680c03925c Step 5/10 : RUN yum -y install vim ----------------------------------中间过程省略-------------------------------------------------- Step 6/10 : RUN yum -y install net-tools ----------------------------------中间过程省略-------------------------------------------------- Step 7/10 : EXPOSE 80 ---> Running in b9d4d8524126 Removing intermediate container b9d4d8524126 ---> 2bb9def10fb1 Step 8/10 : CMD echo $MYPATH ---> Running in f33f8e49a6d9 Removing intermediate container f33f8e49a6d9 ---> e3e6e352b142 Step 9/10 : CMD echo "---end---" ---> Running in 8ffece1e8795 Removing intermediate container 8ffece1e8795 ---> e11211a46b5e Step 10/10 : CMD /bin/bash ---> Running in 0e880df2dec8 Removing intermediate container 0e880df2dec8 ---> e4e087ce52a3 Successfully built e4e087ce52a3 Successfully tagged my-centos-image:0.1 [root@192 dockerfiles]#
3 依据dockerimage镜像启动容器
查看自己构造的镜像并依据自己编写的dockerfile启动一个容器:
[root@192 dockerfiles]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE my-centos-image 0.1 e4e087ce52a3 2 minutes ago 580MB <none> <none> 55ac5aa9c06f 15 minutes ago 231MB mytomcat 1.0 251e3ac9aff1 6 days ago 684MB nginx latest 605c77e624dd 8 weeks ago 141MB tomcat latest fb5657adc892 2 months ago 680MB mysql 5.7 c20987f18b13 2 months ago 448MB mysql latest 3218b38490ce 2 months ago 516MB hello-world latest feb5d9fea6a5 5 months ago 13.3kB centos 7 eeb6ee3f44bd 5 months ago 204MB centos latest 5d0da3dc9764 5 months ago 231MB portainer/portainer latest 580c0e4e98b0 11 months ago 79.1MB elasticsearch 7.6.2 f29a1ee41030 23 months ago 791MB elasticsearch latest 5acf0e8da90b 3 years ago 486MB [root@192 dockerfiles]# docker run -it --name my-container my-centos-image:0.1 [root@2c3ef338f21d local]# pwd /user/local [root@2c3ef338f21d local]# ifconfig eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 172.17.0.8 netmask 255.255.0.0 broadcast 172.17.255.255 ether 02:42:ac:11:00:08 txqueuelen 0 (Ethernet) RX packets 7 bytes 586 (586.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 loop txqueuelen 1000 (Local Loopback) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 [root@2c3ef338f21d local]# vim test.java [root@2c3ef338f21d local]#
可以看到现在该centos具备了vim命令能力并应用了我们配置的目录
4 查看镜像构建过程
实际上就是查看构造该镜像的dockerfile文件执行过程,我们可以看看刚刚自己编写的dockerfile:
[root@192 dockerfiles]# docker history my-centos-image:0.1 IMAGE CREATED CREATED BY SIZE COMMENT e4e087ce52a3 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B e11211a46b5e 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B e3e6e352b142 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B 2bb9def10fb1 6 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B fae10d8a4ee7 6 minutes ago /bin/sh -c yum -y install net-tools 161MB b55b15585af4 6 minutes ago /bin/sh -c yum -y install vim 216MB e3680c03925c 8 minutes ago /bin/sh -c #(nop) WORKDIR /user/local 0B f8d16a4d561d 8 minutes ago /bin/sh -c #(nop) ENV MYPATH=/user/local 0B c1ea220d6008 8 minutes ago /bin/sh -c #(nop) MAINTAINER tml<1233@qq.co… 0B eeb6ee3f44bd 5 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 5 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B <missing> 5 months ago /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4… 204MB [root@192 dockerfiles]#
还可以从我们的docker可视化面板看的更详细些,包括镜像的基本信息
以及镜像的镜像文件细节和镜像层信息:
RUN,CMD和ENTRYPOINT的区别
RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令(或者可以有多个CMD,但是只有最后一个CMD生效)。注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。CMD和ENTRYPOINT的区别
- CMD :指定容器启动的时候要运行的命令,只有最后一个会生效,如果启动时run附加了命令,则CMD的自动无效
- ENTRYPOINT :指定容器启动的时候要运行的命令,命令可以追加,如果启动时run附加了命令,则会追加到ENTRYPOINT 后执行
分别看下两个的示例,注意-l :详细信息显示
,是ls命令的子选项
1 CMD命令测试实践
创建dockerfile文件tml-dockerfile-cmd
[root@192 dockerfiles]# ls tml-dockerfile-centos [root@192 dockerfiles]# vim tml-dockerfile-cmd
编辑dockerfile文件tml-dockerfile-cmd
FROM centos:7 ENTRYPOINT ["ls","-a"]
依据tml-dockerfile-cmd
文件构造镜像tml-cmd-test:0.1
[root@192 dockerfiles]# docker build -f tml-dockerfile-cmd -t tml-cmd-test:0.1 . Sending build context to Docker daemon 3.072kB Step 1/2 : FROM centos:7 ---> eeb6ee3f44bd Step 2/2 : CMD ["ls","-a"] ---> Running in 59b9d2b0bd4e Removing intermediate container 59b9d2b0bd4e ---> 2b54bee0bae6 Successfully built 2b54bee0bae6 Successfully tagged tml-cmd-test:0.1
依据tml-cmd-test:0.1
镜像运行容器,可以看到CMD的命令已被追加执行
[root@192 dockerfiles]# docker run tml-cmd-test:0.1 . .. .dockerenv anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
run的时候尾部追加执行却抛异常,因为-l
会覆盖ls -a
命令
[root@192 dockerfiles]# docker run tml-cmd-test:0.1 -l docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "-l": executable file not found in $PATH: unknown. [root@192 dockerfiles]#
再测试下末尾加ls
:
[root@192 dockerfiles]# docker run tml-cmd-test:0.1 ls anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var [root@192 dockerfiles]#
2 ENTRYPOINT 命令测试实践
创建dockerfile文件tml-dockerfile-entrypoint-test
[root@192 dockerfiles]# ls tml-dockerfile-centos tml-dockerfile-cmd [root@192 dockerfiles]# vim tml-dockerfile-entrypoint-test
编辑dockerfile文件tml-dockerfile-entrypoint-test
FROM centos:7 ENTRYPOINT ["ls","-a"]
依据tml-dockerfile-cmd
文件构造镜像tml-cmd-test:0.1
[root@192 dockerfiles]# docker build -f tml-dockerfile-entrypoint-test -t tml-entrypoint-test:0.1 . Sending build context to Docker daemon 4.096kB Step 1/2 : FROM centos:7 ---> eeb6ee3f44bd Step 2/2 : ENTRYPOINT ["ls","-a"] ---> Running in 191de7d8b95e Removing intermediate container 191de7d8b95e ---> 79cf0d9a1240 Successfully built 79cf0d9a1240 Successfully tagged tml-entrypoint-test:0.1 [root@192 dockerfiles]#
依据tml-entrypoint-test:0.1
镜像运行容器,可以看到ENTRYPOINT 的命令已被追加执行
[root@192 dockerfiles]# docker run tml-entrypoint-test:0.1 . .. .dockerenv anaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
然后我们在命令后边继续追加-l
,可以看到:
[root@192 dockerfiles]# docker run tml-entrypoint-test:0.1 -l total 12 drwxr-xr-x 1 root root 6 Feb 27 06:47 . drwxr-xr-x 1 root root 6 Feb 27 06:47 .. -rwxr-xr-x 1 root root 0 Feb 27 06:47 .dockerenv -rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.log lrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bin drwxr-xr-x 5 root root 340 Feb 27 06:47 dev drwxr-xr-x 1 root root 66 Feb 27 06:47 etc drwxr-xr-x 2 root root 6 Apr 11 2018 home lrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/lib lrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64 drwxr-xr-x 2 root root 6 Apr 11 2018 media drwxr-xr-x 2 root root 6 Apr 11 2018 mnt drwxr-xr-x 2 root root 6 Apr 11 2018 opt dr-xr-xr-x 223 root root 0 Feb 27 06:47 proc dr-xr-x--- 2 root root 114 Nov 13 2020 root drwxr-xr-x 11 root root 148 Nov 13 2020 run lrwxrwxrwx 1 root root 8 Nov 13 2020 sbin -> usr/sbin drwxr-xr-x 2 root root 6 Apr 11 2018 srv dr-xr-xr-x 13 root root 0 Feb 27 06:47 sys drwxrwxrwt 7 root root 132 Nov 13 2020 tmp drwxr-xr-x 13 root root 155 Nov 13 2020 usr drwxr-xr-x 18 root root 238 Nov 13 2020 var [root@192 dockerfiles]#
Dockerfile中设置数据卷
我们可以在Dockerfile中使用VOLUME指令来给镜像添加一个或多个数据卷。下面使用Dockerfile构建一个新的镜像,dockerfile01文件的内容,匿名挂载了volume01和volume02两个目录:
FROM centos:7 VOLUME ["volume01","volume02"] CMD echo "----end----" CMD /bin/bash
执行构建镜像:
[root@192 dockerfiles]# docker build -f tml-volume-dockerfile -t docker-image-volume:1.0 . Sending build context to Docker daemon 5.12kB Step 1/4 : FROM centos:7 ---> eeb6ee3f44bd Step 2/4 : VOLUME ["volume01","volume02"] ---> Running in bd03f08b91ab Removing intermediate container bd03f08b91ab ---> d8fd913ad145 Step 3/4 : CMD echo "----end----" ---> Running in 62a9988664de Removing intermediate container 62a9988664de ---> 0ecb45090e52 Step 4/4 : CMD /bin/bash ---> Running in 7b8ac923feff Removing intermediate container 7b8ac923feff ---> 3d736800dd60 Successfully built 3d736800dd60 Successfully tagged docker-image-volume:1.0
完成镜像的生成后,启动自己生成的容器
[root@192 dockerfiles]# docker run -it docker-image-volume:1.0 /bin/bash [root@f9323fd4567f /]# ls anaconda-post.log dev home lib64 mnt proc run srv tmp var volume02 bin etc lib media opt root sbin sys usr volume01 [root@f9323fd4567f /]# ls -l total 12 -rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.log lrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bin drwxr-xr-x 5 root root 360 Feb 27 09:29 dev drwxr-xr-x 1 root root 66 Feb 27 09:29 etc drwxr-xr-x 2 root root 6 Apr 11 2018 home lrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/lib lrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64 drwxr-xr-x 2 root root 6 Apr 11 2018 media drwxr-xr-x 2 root root 6 Apr 11 2018 mnt drwxr-xr-x 2 root root 6 Apr 11 2018 opt dr-xr-xr-x 232 root root 0 Feb 27 09:29 proc dr-xr-x--- 2 root root 114 Nov 13 2020 root drwxr-xr-x 11 root root 148 Nov 13 2020 run lrwxrwxrwx 1 root root 8 Nov 13 2020 sbin -> usr/sbin drwxr-xr-x 2 root root 6 Apr 11 2018 srv dr-xr-xr-x 13 root root 0 Feb 27 06:47 sys drwxrwxrwt 7 root root 132 Nov 13 2020 tmp drwxr-xr-x 13 root root 155 Nov 13 2020 usr drwxr-xr-x 18 root root 238 Nov 13 2020 var drwxr-xr-x 2 root root 6 Feb 27 09:29 volume01 drwxr-xr-x 2 root root 6 Feb 27 09:29 volume02 [root@f9323fd4567f /]#
可以看到自动挂载的数据卷目录。下面查看对应宿主机的数据卷目录:
"Mounts": [ { "Type": "volume", "Name": "84bf0cdcdf0f1d000a11391ca56a9e9ca70839e99e2187d16893d133cd094065", "Source": "/var/lib/docker/volumes/84bf0cdcdf0f1d000a11391ca56a9e9ca70839e99e2187d16893d133cd094065/_data", "Destination": "volume01", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" }, { "Type": "volume", "Name": "6299205be5706a8604821514ee304a1007b314d03c9350cc4715e1217862c709", "Source": "/var/lib/docker/volumes/6299205be5706a8604821514ee304a1007b314d03c9350cc4715e1217862c709/_data", "Destination": "volume02", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ],
可以看到Mounts下有宿主机的挂载目录。因为dockerfile中没有指定宿主机目录,所以属于匿名挂载,在/var/lib/docker/volumes/目录下生成了随机命名的路径。