Michael.W谈Docker 第五期-Dockerfile的编写
1 什么是Dockerfile?
从宏观的角度开,你可以认为Dockerfile就是一个shell脚本文件。在Dockerfile中可以批量执行docker的操作命令,以此来制作docker的镜像文件。
所以Dockerfile中要写的就是制作镜像的步骤。
2 Dockerfile使用中要注意的事项
- Dockerfile对应的文件名首字母一定要大写。
- 做到将Dockerfile文件放到一个空的目录中。目的是为了排除其他文件在视觉上对你产生干扰,让你在制作的过程中视觉上产生舒适。
- 你制作的镜像,最后都要运行成容器。制作出来的镜像在功能上要保持单一性。每一个镜像就单独实现一个功能。比如redis的镜像只实现redis,mysql的镜像只实现mysql,不要把redis和mysql安装到一个镜像内。(如果有特殊需求除外)
- Dockerfile中执行的命令越少越好,这样可以节省空间。(如果你是不在乎存储空间的土豪就当我没说)
3 Dockerfile的工作流程
- 编写Dockerfile。
- 执行一个docker命令,启动Dockerfile中的指令,依次执行,开始制作镜像。
从第3步开始后面的动作都是计算机自动执行的,跟我们人类已经没有关系了!
- 由于我们制作镜像都是以基础镜像为基础,在其上进行加工。所以计算机会检查你的本地仓库中是否拥有对应的基础镜像。如果没有,会从网上自动pull。
- 打开镜像,得到容器并进入其内部,执行Dockerfile中的指令。
- 执行完所有的指令后,自动将容器export成镜像,即我们最终需要的镜像。
这样我想将我使用的镜像发送给别人的时候,不再需要拷贝整个大的镜像文件,只要将这个Dockerfile发送给他让他运行一下就可以了
4 Dockerfile的基础指令
4.1 FROM
FROM 基础镜像名:tag
FROM必须在Dockerfile的第一行(除注释)。 可以连续写多个FROM。
4.2 MAINTAINER
MAINTAINER 制作人的信息
MAINTAINER后面跟一个字符串。
4.3 RUN
RUN shell命令
# 写法1
RUN mkdir /home/michael.w/blockchain -p
# 写法2
RUN ["mkdir","/home/michael.w/blockchain","-p"]
就是执行shell命令,两种写法。注意第一种写法如果shell命令过长需要换行,行末需要加反斜杠\
4.4 EXPOSE
EXPOSE 容器要开放的端口
由于容器是对外封闭的,如果外界客户端要访问宿主机的容器,那么容器必须开放自己的端口与宿主机形成端口映射。EXPOSE指令的功能就是在Dockerfile制作成的镜像在被建立成容器时,自动开放自己的某一端口。
4.5 如何启动Dockerfile?
$ docker build -t 新创建出的镜像的名字:tag dockerfile所在路径
我简单写一个小型的Dockerfile:
# 基于基础ubuntu镜像
FROM ubuntu
# 制作人信息
MAINTAINER Michael.W
# 创建文件夹
RUN mkdir michael
# 更新apt源
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
# 更新软件源
RUN apt-get update
# 安装vim,-y为所有的弹出的人机交互都默认输入yes
RUN apt-get install vim -y
# 开放80端口
EXPOSE 80
执行docker build -t myimage:V2.0
中间在更新源的时候会有很多打印信息,当最后出现Successfully的字样的时候表名镜像已制作成功。
执行docker images
可见刚刚创建的myimage镜像
打开启动容器docker run -it --name test myimage bash
,然后执行ls
可见有michael文件夹,表名镜像已经按照Dockerfile镜像已经制作成功!
5 Dockerfile中作用于容器运行后的指令
5.1 CMD
一个容器在运行起来之后,会默认执行一条指令。以uginx为例:
在指令中我没有在指令的最后写bash,从docker ps
可见nginx在启动后自动了一条指令nginx -g daemon off
。这条指令就是在Dockerfile中利用CMD指令实现的。
# 写法1
CMD shell命令
# 写法2
CMD ["shell命令", "命令参数1", "命令参数2"...]
CMD后是根据制作的镜像启动容器后默认执行的命令。但是该CMD会被docker run
指令中最后指定的shell命令覆盖掉:
同时,CMD可以写多个,但是只有最后一个才会生效。
5.2 ENTRYPOINT
ENTRYPOINT的作用和用法跟CMD是一样的,但是ENTRYPOINT后的指令不会被docker run
指令中最后指定的shell命令覆盖。
# 写法1
ENTRYPOINT shell命令
# 写法2
ENTRYPOINT ["shell命令", "命令参数1", "命令参数2"]
如果想硬要在docker run
中用指令覆盖,可以借助–entrypoint参数。
如果CMD和ENTRYPOINT一起使用时遵循以下原则:
任何docker run最后的命令或者CMD后面的命令,都将作为ENTRYPOINT 指令的命令参数,追加到ENTRYPOINT指令之后。例如:
ENTRYPOINT mkdir /home/michael.w/hello
CMD -p
等价于容器启动后执行mkdir /home/michael.w/hello -p
个人感觉这种结合使用毫无卵用!
6 Dockerfile编辑指令
6.1 ADD
比如我想在容器中通过某个源码包安装文件,那么需要将这个源码包拷贝到容器中。这时就用到ADD命令:
# 写法1
ADD 宿主机文件 容器目录/文件
# 写法2
ADD ["宿主机文件", "容器目录/文件"]
如果宿主机中的文件是tar.gz
或tar.bz2
类型的压缩文件,ADD指令会自动解压缩该文件到指定的容器目录。
注:
- 一般这个需要拿来拷贝的宿主机文件要放在Dockerfile的同级目录下。
- 容器目录如果不存在,会自动创建路径。
6.2 COPY
COPY指令和ADD指令类似。是直接将宿主机中的文件复制到容器的某个路径下。只是COPY指令不会做解压缩的工作。
# 写法1
COPY 宿主机文件 容器目录/文件
# 写法2
COPY ["宿主机文件", "容器目录/文件"]
6.3 VOLUME
VOLUME后面加容器的路径,表示该容器被创建后可以成为一个数据卷容器(天生具有挂载点),其数据卷挂载路径为VOLUME后面的路径。
VOLUME 容器挂载点路径
# 例子
VOLUME ["/backup"]
#上面等价于
$ docker run ... -v /backup ...
所以利用上面制作成的镜像生成容器时,不用再输入-v参数就会成为一个数据卷容器。
7 Dockerfile环境指令
7.1 ENV
在终端中输入env
可以看见当前终端的所有环境变量。都是以键值对的形式存在。
在Dockerfile中有时候会有很多非常长的变量,我们可以将这些值存到环境变量中。
# 写法1
ENV 环境变量名 环境变量的值
# 写法2
ENV 环境变量名=环境变量的值
# 写法2可以在一行中设置多个环境变量
ENV YEAR=2019 NAME=Michael
在RUN 之前设置环境变量,然后在执行RUN 命令时使用。容器启动后这些环境变量都会被使用!
7.2 WORKDIR
WORKDIR功能为切换目录,为后续的RUN、CMD、ENTRYPOINT 服务。相当于linux中的cd
指令。
WORKDIR 要去的进程目录
# 例子
WORKDIR /root
例子表示在容器启动后进入到/root路径下。
7.3 USER
指在容器中以哪个用户的身份来执行Dockerfile文件中的指令。
不使用USER指令,默认为root用户。
USER 容器内的用户名
7.4 ARG
ARG与ENV有些类似,可以被后面的其它指令直接使用,但是它并不是环境变量,这意味着将来容器运行时是不会存在ARG变量的。
# Dockerfile中
FROM ubuntu
ARG ARG1
RUN echo $ARG1
# docker指令
$ docker build --build-arg ARG1=michael.w .
镜像制作过程中打印出了”michael.w“,可见参数ARG1从docker build
的指令行传到了Dockerfile中。
8 Dockerfile的触发器指令
8.1 ONBUILD
Dockerfile触发器指令就像有些隔代遗传的遗传疾病。父亲有这种病,但是在儿子身上是不会显现出来,但是到了孙子就会显现出来。
所以,如果你在Dockerfile中使用了ONBUILD指令制作出了镜像A。利用镜像A生成容器时将不会有任何效果,但是当你再用镜像A制作成了镜像B时,这个操作就会显现出来。
ONBUILD [command]
# 例子
ONBUILD RUN echo "Michael.W"
在利用busybox作为基础镜像生成镜像son_image时候,并没有执行Dockerfile中的触发器指令RUN echo "Michael.W"
。
修改Dockerfile,利用刚刚创建的镜像son_image来制作镜像grandson_image,此时触发器指令被触发,执行了RUN echo "Michael.W"
。
ps:
本人热爱图灵,热爱中本聪,热爱V神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
公众号名称:后现代泼痞浪漫主义奠基人