在上一篇文章中,有幸和各位
分享了一点人生的经验,正如我在文章末所说,这些都只是沧海一粟,学会 docker 的基本概念和操作还不足以让你快速部署代码,今天我们就来聊一聊 Dockerfile
在了解什么是 Dockerfile之前,我们先来做一个小实验
还记得我们在上一节中 pull 下来的 python 镜像吗,首先通过 docker run -it 进入容器内部,回忆一下, -it 这个参数的作用是什么,没错,使用这个参数,docker会为容器分配一个伪输入终端,通过这个终端,用户就可以和容器进行相应的交互了。
进入容器后,先列出当前路径下的所有文件
[root@FUCC ~]# docker run -it python:3.7 /bin/bash
root@692f87774bf7:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@692f87774bf7:/#
OK,接着,我们将创建一个hello.py 的文件,创建完成后再次列出所有文件
root@692f87774bf7:/# touch hello.py
root@692f87774bf7:/# ls
bin boot dev etc hello.py home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
可以看到,hello.py 已经创建完成了,这时我们退出容器,使用之前的 docker run -it python:3.7 /bin/bash 命令再次进入容器的交互模式,并列出所有文件
root@692f87774bf7:/# exit
exit
[root@FUCC ~]# docker run -it python:3.7 /bin/bash
root@65c767655e8a:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@65c767655e8a:/#
不用我多说,相信你已经发现了问题的所在,奇怪,为什么我们之前创建的文件凭空消失了呢?
这是因为,在容器内部执行的操作并不会修改镜像,容器其实只是在镜像上面添加了一个可写层,就像字帖里的临摹纸一样,无论你在临摹纸上怎么写,对下面一层都不会有任何影响,这个特性其实和VM的快照有些类似。
但是问题来了,既然在容器内部的操作都不会修改镜像,那如何做到快速部署,每次部署代码前都去重复执行一些操作,岂不是太low了?
假设你是一家社交网站的技术负责人,某一天,奥特虾突然宣布恋情上了热搜,哦不是,八对明星突然同时出轨,你需要在短时间内创建上百台云服务器以支撑吃瓜群众海量的请求,团队选用了 Docker 去部署应用,如果通过上面这种方法去部署,吃瓜群众想杀了你的心都有了,为什么?等你的服务恢复,吃瓜群众早就散了,动不动就崩,怎么愉快吃瓜?
这时,就轮到 Dockerfile 闪亮登场了,老规矩,先看一下 Docker 官方对于 Dockerfile 的介绍
Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession
简单来说,Dockerfile是一个文本文档,其中包含用户可以在命令行上调用以构建镜像的所有命令,举个例子,你可以理解为构建镜像是一个搭积木的过程,Dockerfile 就像是说明书一样,Docker 会根据这份说明书去构建你想生成的镜像。
接下来我们会从一个实际的场景出发,讲一讲如何构建 Dockerfile
依旧是以一个 python 镜像为例,在我的本地(容器外部),有一个 Optimal_Hotel_Matching.py 的代码文件,这是一个爬虫程序,先尝试在容器中运行它
果不其然,出现了报错,这是因为在python镜像中,并没有安装requests这个第三方的http库,这其实就是一个典型的安装依赖环境的过程,我们试着用 Dockerfile 去解决这个问题
[root@FUCC dockerfileTest]# ls
Optimal_Hotel_Matching.py
[root@FUCC dockerfileTest]# docker run -v $PWD:/usr/src/code -w /usr/src/code python:3.7 python Optimal_Hotel_Matching.py
Traceback (most recent call last):
File "Optimal_Hotel_Matching.py", line 1, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
[root@FUCC dockerfileTest]#
在编写第一个 Dockerfile 之前,我们先来熟悉一下 Dockerfile 中几个常用的指令
FROM
语法: FROM <image>
说明:这是 Dockerfile 中的第一条指令,它用于指定一个构建镜像的基础源镜像,如果本地没有的话, Docker 会从仓库中自动拉取
MAINTAINER
语法:MAINTAINER <name> <email>
说明:用于描述镜像创建者的名称和邮箱
RUN
语法: RUN <command> <param1> <param2>
说明:RUN 绝对是 Dockerfile 中最重要的命令之一了,当RUN执行完成后,会在原先的基础镜像上创建一个新的镜像层,每执行一次RUN就会产生一个新的层。通常,安装软件或者配置环境都是通过RUN来实现的
CMD
语法: CMD <command> <param1> <param2>
说明:CMD相对而言比较简单,相信大家看到名字就能理解,这个指令用于指定容器启动时执行的命令
Tips: 在我刚接触Docker的时候,RUN 和 CMD 一度傻傻分不清楚,其实这两者的作用完全不同,RUN 用于构建镜像,是对镜像进行操作,而 RUN 则是指定了容器启动时执行的命令,是对容器进行操作
COPY
语法:COPY <src> <dest>
说明:COPY 用于复制本地文件,并将其放置在指定的容器目录,但是需要注意的是 COPY 并不能将复制的压缩文件自动解压,也不能复制网络文件,如果有这两点需求可以使用 ADD 命令,这两个命令的用法是相同的, ADD 可以将复制的压缩文件自动解压
WORKDIR
语法: WORKDIR <path>
说明:为RUN、CMD、ENTRYPOINT指令配置工作目录,在一个 Dockerfile 中可以使用多个WORKDIR指令
编写第一个Dockerfile
掌握了以上指令后,我们就已经能写一个较为基础的 Dockerfile 了,回到我们之前的需求,一句话总结下来就是,安装requests库后运行代码文件
思考一下,应该怎么做?
如果你还是没有眉目,不妨来看看我为你准备的参考答案
FROM python:3.7 #指定基础镜像为python:3.7
MAINTAINER ultraxia "ultraxia@foxmail.com" #维护者名称和邮箱
RUN pip install requests #安装requests库,并构建一个新的镜像层
WORKDIR /dockerfileTest #指定工作目录为/dockerfileTest
COPY . . #将当前目录下的文件复制到容器中,这里为工作目录
CMD [ "python", "Optimal_Hotel_Matching.py" ] #运行代码
将上面的内容保存为名称为Dockerfile的文件,并放置在项目所在的目录
[root@FUCC dockerfileTest]# ls
Dockerfile Optimal_Hotel_Matching.py
开始构建
这时,我们就可以使用docker build去构建了,通常为了区分不同的镜像,需要给镜像打上tag(标签)
[root@FUCC dockerfileTest]# docker build --tag python:requests .
Sending build context to Docker daemon 5.12 kB
Step 1/6 : FROM python:3.7
---> 42d620af35be
Step 2/6 : MAINTAINER ultraxia "ultraxia@foxmail.com"
---> Running in b9ca2049ef19
---> c6fb2a731a82
Removing intermediate container b9ca2049ef19
Step 3/6 : RUN pip install requests
---> Running in 4edba5ca3ead
Collecting requests
Downloading https://files.pythonhosted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/requests-2.22.0-py2.py3-none-any.whl (57kB)
Collecting urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 (from requests)
Downloading https://files.pythonhosted.org/packages/e6/60/247f23a7121ae632d62811ba7f273d0e58972d75e58a94d329d51550a47d/urllib3-1.25.3-py2.py3-none-any.whl (150kB)
解释一下这条命令
docker build 命令用于使用 Dockerfile 创建镜像
--tag 参数用于指定镜像tag,这里我们命名为python:requests
. docker 会从当前目录寻找Dockerfile文件,并开始构建镜像
构建完成!使用 docker images命令验证一下吧
[root@FUCC dockerfileTest]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
python requests 04f20acdc288 2 hours ago 926 MB
docker.io/python 3.7 42d620af35be 3 weeks ago 918 MB
docker.io/rabbitmq 3-management 7aae48fa6ef6 3 weeks ago 179 MB
docker.io/golang latest f50db16df5da 4 weeks ago 774 MB
docker.io/mariadb latest 3a2ef06682ac 4 weeks ago 356 MB
docker.io/centos latest 9f38484d220f 4 months ago 202 MB
docker.io/hello-world latest fce289e99eb9 7 months ago 1.84 kB
docker.io/django latest eb40dcf64078 2 years ago 436 MB
[root@FUCC dockerfileTest]#
可以看到,此时本地的仓库中已经生成了一个tag为requests的python镜像。试着运行一下这个镜像,看看是否还会出现No module named 'requests'的报错,以及,程序是否会如我们期待的一样开始运行。
[root@FUCC dockerfileTest]# docker run python:requests
Computing distance between 116.368816,39.866464 and 116.438946,39.921624
Computing distance between 116.370910,39.869603 and 116.438946,39.921624
Computing distance between 116.409583,39.983356 and 116.438946,39.921624
Computing distance between 116.302621,39.966272 and 116.438946,39.921624
Computing distance between 116.322551,39.886995 and 116.438946,39.921624
可以看到,运行成功,容器中的代码运行完成后,容器退出。
完结了吗?
在这一篇里,我通过一个简单的案例和各位分享了 Dockerfile 的简单应用,事实上在企业的实际生产中,Dockerfile 的内容会比这个更复杂一些。
最后,正好赶上七夕,祝各位七夕快乐,下期再见。
今天过节的人会有时间看你的文章吗?不会