镜像的定制,实际上就是定制每一层所添加的配置、文件。我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
一、举例:创建Flask项目环境镜像
比如,我自己有个Python的项目,是个Flask项目,我想把它放到服务器上,就是个Web项目。
那我们怎么在一台服务器上快速、方便的把本地的Flask开发环境部署上去呢?比如本地用的Python版本、以前Flask相关的库,以及其它用到的库。就可以用Docker制作一个自己的镜像。
Flask开发环境的镜像制作好了,其实就是一个Dockerfile 文件,之后就可以随意部署到带Docker的服务器上去了。贼方便。
还有,就是我想制作的是一个Flask项目的环境镜像,所以代码这些无所谓的,只要环境能制作好就行,所以在建这个镜像的时候没有复制项目文件这些,也用不着运行。
因为这样一个通用的Flask镜像,后续项目文件自己上传就行。
Flask项目结构(样例)
1 | flask_proj_demo |
1.生成 requirements.txt(本地)
进入flask_proj_demo项目目录的app目录下,运行如下命令,就可以生成项目环境的 requirements.txt 文件。
1 | pip freeze > requirements.txt |
requirements.txt 举例:
1 | click==8.0.1 |
可以看到除了带Flask中常用的库,也带了mysql和selenium、pandas这些,这种随你自己就行,你自己项目经常用哪些就放哪些。生成的requirements.txt文件中如果有些用不着,也可以删除的。
2.用Dockerfile 文件创建镜像
Dockerfile文件内容如下:
1 | # Use an official Python runtime as an image |
然后,就把这整个项目的文件上传到服务器上。我这直接用Xftp上传到了 /root/flask/flask_proj_demo/app
目录。
最后用SSH连接服务器,进入到 /root/flask/flask_proj_demo/app 目录,运行以下命令生成镜像:
1 | docker build -t flask_demo:v1.0 . |
-t 设置镜像名字和版本号
执行完,就可以用命令 docker images
看到以下镜像了(当然也有python3.9的镜像)。
二、Dockerfile 指令详解
1.常用指令
常用的基本上就是上面用到的那几个指令:
FROM python:3.9:依赖的基础镜像 python3.9版本
WORKDIR /app:设置容器启动后的默认工作运行目录 /app
COPY . /app:复制当前目录下文件到容器的/app目录
RUN:执行命令
EXPOSE 5020:暴露5020端口
CMD:CMD指令只能一个,是容器启动后执行的命令,算是程序的入口
1.COPY 复制文件
格式:COPY [--chown=<user>:<group>] <源路径>... <目标路径>
--chown=<user>:<group>
参数来改变文件的所属用户及所属组。
COPY指令复制,源文件的各种元数据都会保留。
例子:
1 | COPY . /app:复制当前目录下文件到容器的/app目录 |
2.ADD 更高级的复制文件
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
比如 <源路径> 可以是一个 URL,
这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。
所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径> 为一个 tar 压缩文件的话,
压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
不过,按在Docker官方的 Dockerfile最佳实践文档中的要求,尽可能的使用COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
所以除了需要自动解压的场合,一般用COPY就行。
3.RUN 执行命令
格式有两种:
shell 格式:RUN <命令>
,就像直接在命令行中输入的命令一样。
exec 格式:RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。
举例:
1 | RUN pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip |
4.CMD 指令
CMD指令只能一个,是容器启动后执行的命令,算是程序的入口。
1).格式
格式和 RUN 相似,也是两种格式:
shell 格式:CMD <命令>
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
参数列表格式:CMD [“参数1”, “参数2”…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
2).举例
ubuntu 镜像默认的 CMD 是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash。
我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 “,而不要使用单引号。
1 | CMD echo $HOME |
在实际执行中,会将其变更为:CMD [ "sh", "-c", "echo $HOME" ]
3).容器中应用在前台执行(重点)
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。
如果CMD这样写:CMD service nginx start
,就会有问题了。
发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。
这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
而使用 service nginx start
命令,则是希望 upstart 来以后台守护进程形式启动 nginx 服务。而刚才说了 CMD service nginx start
会被理解为 CMD [ "sh", "-c", "service nginx start"]
,因此主进程实际上是 sh。
那么当 service nginx start
命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。
正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:
1 | CMD ["nginx", "-g", "daemon off;"] |
5.EXPOSE 暴露端口
格式为:EXPOSE <端口1> [<端口2>...]
EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。
在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE 的端口。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口>
区分开来。-p
,是映射宿主端口和容器端口,就是将容器的对应端口服务公开给外界访问;
而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
6.ENV 设置环境变量
格式有两种:ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
比如举个简单的例子(Dockerfile中):
1 | ENV NODE_VERSION 7.2.0 |
这里先定义了环境变量 NODE_VERSION,其后的 RUN 这层里,多次使用 $NODE_VERSION 来进行操作定制。
那将来升级镜像构建版本的时候,只需要更新 7.2.0 即可,Dockerfile 构建维护变得更轻松了。
下列指令支持环境变量展开: ADD、COPY、ENV、EXPOSE、FROM、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD、RUN。
7.VOLUME 定义匿名卷
格式为:VOLUME <路径>
VOLUME ["<路径1>", "<路径2>"...]
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。
为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
举个例子(Dockerfile中):
1 | VOLUME /data |
这里的 /data 目录就会在容器运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。
比如,运行以下命令,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
1 | $ docker run -d -v mydata:/data xxxx |
8.其它指令
还剩下一些其它的指令,这里就简单列一下了。
1 | USER:指定当前用户 # 格式:USER <用户名>[:<用户组>] |
需要看具体的,网上一搜都有介绍,有Docker从入门到实践的文档:https://yeasy.gitbook.io/docker_practice/image/dockerfile
官方镜像的Dockerfile 参考典范:https://github.com/docker-library/docs
可以瞅瞅,参考参考。
- 本文标题:Docker Dockerfile文件详解(制作自己的镜像)
- 本文作者:HDUZN
- 创建时间:2022-05-29 18:04:26
- 本文链接:http://hduzn.cn/2022/05/29/Docker-Dockerfile文件详解(制作自己的镜像)/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!