传送门
通过Dockerfile文件挂载容器卷回顾
在上一节中介绍了Dockerfile挂载容器卷,其中的Dockerfile文件如下:
bash
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
该命令的大概意思就是:
- FROM表示,从标准的ubuntu镜像为准
- RUN表示执行shell命令,创建一个文件目录/myvol
- 执行命令,打印"hello world"字符串到greeting文件
- 最后VOLUME,就是这节要讲到卷挂载!
然后通过Dockerfile生成了一个镜像:
bash
docker build -t getting-started .
并且成功运行该镜像,最后成功实现了容器卷的挂载!
那么**什么是Dockerfile文件?Dockerfile的有哪些指令?Dockerfile的编写有哪些最佳实践?**接下来就带着这些疑问来探索一下
什么是Dockerfile
要知道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.
Docker可以通过读取Dockerfile中的指令自动构建镜像。Dockerfile是一个文本文档,其中包含用户可以在命令行上调用的所有命令来组装镜像。
简单来说,可以通过Dockerfile文件来生成Docker镜像!首先Dockerfile文件是一个纯文本,然后在里面编写了一系列的指令,比如选择基础镜像(FROM)、拷贝文件(COPY)、运行脚本(RUN)等等,Docker 顺序执行这个文件里的所有步骤,最后就会创建出一个新的镜像出来。
https://docs.docker.com/build/guide/layers/
从上面的流程可以看出,左边用户编写的Dockerfile文件通过Docker构建之后,成为了右边的镜像。这个镜像跟官方镜像无本质区别(前面从DockerHub上安装的nginx、redis),称为"民间"、"草根"镜像。
镜像分类
从DockerHub上可以看到对镜像的分类大致有3种:
- Docker官方镜像(Docker Official Image)
- 认证镜像(Verified Publisher)
- 非官方镜像(Sponsored OSS)
官方镜像
像上面提到的nginx、redis都属于官方镜像!
这种镜像都会带上官方构建的标签,属于Docker公司提供的**"极品"**镜像,不仅质量上乘(有专门的团队负责审核、发布和更新),而且安全性也有保障(经过了严格的漏洞扫描和安全检测),并且也是标准的Dockerfile编写范例,是使用的首选!
认证镜像
认证镜像由Docker认证的出版商提供的高质量镜像。这些产品由商业公司直接发布和维护,比如 Bitnami、Rancher、Ubuntu 等。
这种由大公司**"出品"**的镜像,一般来说也具有相当高的质量,是不错选择。不过由于Docker认证需要收钱,店大欺客:
因为成为"Verified publisher"是要给 Docker 公司交钱的,而很多公司不想花这笔"冤枉钱",所以只在 Docker Hub 上开了公司账号,但并不加入认证。
--引自"虚伪"的 Docker 开始清退开源组织,不付费就删除所有镜像!|插件|云原生|存储库|应用程序|docker_网易订阅
所以有些不交钱认证的公司的镜像就被降级为"非官方镜像",比如OpenResty:
非官方镜像
队了上面2种,剩下的就是**非官方镜像,**也戏称为"民间"镜像!
对于这些镜像的选择,下载量是一个重要的参考标准。 这个时候就是韩信点兵,多多益善了!
这里面也包括我们自己的镜像,如果推送的DockerHub,也是归于这一类镜像中,后面会单开一节来讨论如果上传。
Dockfile有哪些指令
Dockerfile文件命名
在上面的例子中,通过命令docker build来构建的镜像。这是因为省略了-f参数,默认在当前目录下查看名字为Dockerfile的文件:
Specifies the filepath of the Dockerfile to use. If unspecified, a file named
Dockerfile
at the root of the build context is used by default.
所以一般对于编写的Dockerfile默认使用Dockerfile这个名字,如果要用其它名字,在build时用-f来指定!
指令详解
Dockerfile支持的指令不少,比如例子里面提到的FROM、RUN、VOLUME,更多的可以查看Dockerfile指令
FROM
有效的Dockerfile必须以FROM指令开头,命令格式为:
bash
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
表示选择构建使用的基础镜像,比如ubuntu镜像。其中--platform用在多平台镜像中,一般不用指定:
The optional
--platform
flag can be used to specify the platform of the image in caseFROM
references a multi-platform image. For example,linux/amd64
,linux/arm64
, orwindows/amd64
. By default, the target platform of the build request is used.可选的--Platform标志可用于指定镜像的平台,以防FROM引用多平台镜像。例如,linux/amd64、linux/arm64或windows/amd64。默认情况下,使用构建请求的目标平台。
也可以指定镜像对应的tag,比如nginx的Dockerfile,选择debian操作系统,版本为bookworm-slim:
RUN
Run指令可以说是Dockerfile 里最重要的一个指令了 ,它可以执行任意的 Shell 命令它也是Dockerfile里面最复杂的一条指令了,这取决于对于shell的编写。比如例子里面的简单shell命令:
bash
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
- 创建一个文件夹
- 打印"hello world"到文件greeting文件中
也可以特别复杂,比如nginx的RUN命令:
这整个就是一个复杂的shell脚本,因为太长了(RUN只能执行一行命令),所以采用续行符 \,命令之间也会用 && 来连接,这样保证在逻辑上是一行,如果写错了,build的时候会报错:
CMD
CMD指令设置从镜像运行容器时要执行的命令,格式为:
CMD ["executable","param1","param2"]
(exec form)CMD ["param1","param2"]
(exec form, as default parameters toENTRYPOINT
)CMD command param1 param2
(shell form)
比如,在容器运行之后,打印出当前目录,修改Dockerfile文件,ls -al:
bash
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
CMD ["ls", "-al"]
VOLUME /myvol
构建并运行容器,之后就会执行 ls -al命令:
Dockerfile中只能有一条CMD指令。如果列出多个CMD,则只有最后一个生效。这一点在官网上有明确说明:
There can only be one
CMD
instruction in a Dockerfile. If you list more than oneCMD
, only the last one takes effect.
而且官网上也对RUN与CMD的区别做了说明:
Don't confuse
RUN
withCMD
.RUN
actually runs a command and commits the result;CMD
doesn't execute anything at build time, but specifies the intended command for the image.不要混淆RUN和CMD。RUN实际上运行一个命令并提交结果;CMD在构建时不执行任何东西,但指定镜像的预期命令。
图片来自: https://docs.docker.com/guides/docker-overview/
VOLUME
这个卷挂载命令在前面已经讨论过了,这里就不赘述了。
COPY
COPY指令顾名思义,就是复制命令,有点类似于cp命名,格式如下:
bash
COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] ["<src>", ... "<dest>"]
其中src表示源文件,dest表示目标文件。比如上面的例子中,在当前目录下建一个test_cp.txt文件,并打包进镜像中:
bash
FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
COPY ./test_cp.txt /tmp/test_cp.txt
VOLUME /myvol
然后重新构建并运行,发现构建成功之后,运行对应的镜像,容器中已经有了对应的文件,证明复制成功!
不过这里要注意的是, 拷贝的源文件必须是"**构建上下文"**路径里的,不能随意指定文件,甚至连绝对路径也不行。
如果要从本机向镜像复制文件,还要把这些文件放到一个专门的目录,然后在 docker build 里指定"构建上下文" 到这个目录才行。在这里例子中,"构建上下文"为当前目录.
所以如果指定当前"构建上下文"为绝对路径:/root/test-docker
bash
docker build -f Dockerfile_copy -t greetting_stared_cp3 /root/test-docker
Dockerfile里面COPY改为相对路径是可以的。由此得出,COPY只能基于相对路径来操作!
这个原理呢,其实就要从docker build的说起了,跟Dockerfile没有多大关系,后面再讨论。
WORKDIR
WORKDIR指令是设置工作目录,格式为:
bash
WORKDIR /path/to/workdir
WORKDIR指令为Dockerfile中跟随它的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录,默认为"/"目录下。 比如创建一个Dockerfile文件内容如下:
bash
FROM ubuntu
WORKDIR /usr
WORKDIR share
WORKDIR bug
RUN pwd > greeting
然后打包运行看下效果:
并且细心的观察就会发现,这个进入容器的运行目录,正是刚才通过WORKDIR设置的/usr/share/bug!所以WORKDIR也代表设置的容器运行的目录,这个通过退出容器再进去也能证实这一点:
在官方推荐指定这个WORKDIR的:
Therefore, to avoid unintended operations in unknown directories, it's best practice to set your
WORKDIR
explicitly.--因此,为了避免未知目录中的意外操作,最好的做法是显式设置WORKDIR。
EXPOSE
EXPOSE指令通知Docker容器在运行时侦听指定的网络端口,您可以指定端口侦听TCP还是UDP,如果不指定协议,则默认为TCP,格式为:
bash
EXPOSE <port> [<port>/<protocol>...]
比如在docker系列7:docker安装ES 里面运行容器的命令里面通过-op指定了端口映射:
bash
$ docker run -d --name elasticsearch --net somenetwork -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch
这个命令里面,-p表示容器对外暴露9200端口与主机的9200端口映射。但是如果Dockerfile里面也可以达到这种效果:
bash
EXPOSE 9200
对外暴露9200端口,默认是TCP协议。如果要支持UDP协议,要指定协议类型:
bash
EXPOSE 9200/udp
或者一块暴露tcp、udp,要写2条指令:
bash
EXPOSE 9200/tcp
EXPOSE 9200/udp
无论EXPOSE设置如何,都可以在运行时使用-p标志覆盖。这是因为EXPOSE在Dockerfile中,一般运行时要结合真实宿主机的情况来映射端口,所以一般来说EXPOSE更多的是一种文档化的指引,告诉用户哪个服务暴露哪个端口。比如nginx的Dockerfile中,EXPOSE了80,但是真实使用的时候还是要通过-p来指定:
ENV
ENV指令用于设置环境变量,格式为:
bash
ENV <key>=<value> ...
可以一次性设置多个变量:
bash
ENV MY_NAME="John Doe"
ENV MY_DOG=Rex\ The\ Dog
ENV MY_CAT=fluffy
ENV MY_NAME="John Doe" MY_DOG=Rex\ The\ Dog \
MY_CAT=fluffy
然后就可以在Dockerfile中引用这个变量了:
bash
FROM ubuntu
ENV MY_NAME=DOCKER_ENV_TEST
RUN mkdir /myvol
RUN echo $MY_NAME > /myvol/greeting
VOLUME /myvol
然后打包运行看下效果:
通过ENV这种方式设置变量,官方并不推荐,就是因为在镜像里面也会生效,而不是仅仅在Docker构建过程中生效,所以一般推荐ARG:
If an environment variableis only needed during build, and not in the final image, consider setting a value for a single command instead.
Or using ARG, which is not persisted in the final image
ARG
对于ARG指令,刚才已经谈到了,用法跟ENV相似,区别就是在于 ARG 创建的变量只在镜像构建过程中可见,容器运行时不可见,而 ENV 创建的变量不仅能够在构建镜像的过程中使用,在容器运行时也能够以环境变量的形式被应用程序使用。所以可通过ARG 自行查看!
安全性问题
Dockerfile里面明确提到了,不建议使用变量来传递敏感数据,这又是另外一个话题了
MAINTAINER
MAINTAINER指令设置生成镜像的作者字段,格式为:
bash
MAINTAINER <name>
比如在上面的Dockerfile增加作者信息:
总结
图片来自: 百度安全验证
命令式与声明式
上面介绍了Dockerfile文件与对应的指令,通过编写Dockerfile文件一步步的指定docker构建要执行的命令,"告诉"计算机每步该做什么,所有的步骤都列清楚,这样程序才能够一步步走下去,最后完成任务。这种就是命令式的文件,与之相反Kubernetes编写的YAML文件,就是**"声明式"**的:
bash
apiVersion: v1
kind: Pod
metadata:
name: ngx-pod
labels:
env: demo
owner: chrono
spec:
containers:
- image: nginx:alpine
name: ngx
ports:
- containerPort: 80
**"声明式"**不关心具体的过程,更注重结果(是不是有点耳熟?)
关于镜像的上传
在下一节,会尝试打包自己的镜像并推送到镜像仓库中,并用Dockerfile文件编写打包自己的java程序!