docker系列11:Dockerfile入门

 传送门

docker系列1:docker安装

docker系列2:阿里云镜像加速器

 docker系列3:docker镜像基本命令

docker系列4:docker容器基本命令

docker系列5:docker安装nginx

docker系列6:docker安装redis

docker系列7:docker安装ES

docker系列8:容器卷挂载(上)

docker系列9:容器卷挂载(下)

docker系列10:Dockerfile挂载容器卷

通过Dockerfile文件挂载容器卷回顾

在上一节中介绍了Dockerfile挂载容器卷,其中的Dockerfile文件如下:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

该命令的大概意思就是:

  • FROM表示,从标准的ubuntu镜像为准
  • RUN表示执行shell命令,创建一个文件目录/myvol
  • 执行命令,打印"hello world"字符串到greeting文件
  • 最后VOLUME,就是这节要讲到卷挂载!

然后通过Dockerfile生成了一个镜像:

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指令开头,命令格式为:

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

表示选择构建使用的基础镜像,比如ubuntu镜像。其中--platform用在多平台镜像中,一般不用指定:

The optional --platform flag can be used to specify the platform of the image in case FROM references a multi-platform image. For example, linux/amd64linux/arm64, or windows/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命令:

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 to ENTRYPOINT)
  • CMD command param1 param2 (shell form)

比如,在容器运行之后,打印出当前目录,修改Dockerfile文件,ls -al

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 one CMD, only the last one takes effect. 

而且官网上也对RUN与CMD的区别做了说明:

Don't confuse RUN with CMDRUN 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命名,格式如下:

COPY [OPTIONS] <src> ... <dest>
COPY [OPTIONS] ["<src>", ... "<dest>"]

其中src表示源文件,dest表示目标文件。比如上面的例子中,在当前目录下建一个test_cp.txt文件,并打包进镜像中:

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

docker build -f Dockerfile_copy -t greetting_stared_cp3 /root/test-docker

Dockerfile里面COPY改为相对路径是可以的。由此得出,COPY只能基于相对路径来操作!

 这个原理呢,其实就要从docker build的说起了,跟Dockerfile没有多大关系,后面再讨论。 

WORKDIR

WORKDIR指令是设置工作目录,格式为:

WORKDIR /path/to/workdir

WORKDIR指令为Dockerfile中跟随它的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录,默认为"/"目录下。 比如创建一个Dockerfile文件内容如下:

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,格式为:

EXPOSE <port> [<port>/<protocol>...]

比如在docker系列7:docker安装ES 里面运行容器的命令里面通过-op指定了端口映射:

$ docker run -d --name elasticsearch --net somenetwork -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch

这个命令里面,-p表示容器对外暴露9200端口与主机的9200端口映射。但是如果Dockerfile里面也可以达到这种效果:

EXPOSE 9200

 对外暴露9200端口,默认是TCP协议。如果要支持UDP协议,要指定协议类型:

EXPOSE 9200/udp

或者一块暴露tcp、udp,要写2条指令:

EXPOSE 9200/tcp
EXPOSE 9200/udp

无论EXPOSE设置如何,都可以在运行时使用-p标志覆盖。这是因为EXPOSE在Dockerfile中,一般运行时要结合真实宿主机的情况来映射端口,所以一般来说EXPOSE更多的是一种文档化的指引,告诉用户哪个服务暴露哪个端口。比如nginx的Dockerfile中,EXPOSE了80,但是真实使用的时候还是要通过-p来指定:

ENV

ENV指令用于设置环境变量,格式为:

ENV <key>=<value> ...

可以一次性设置多个变量:

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中引用这个变量了:

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 variable is 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指令设置生成镜像的作者字段,格式为:

MAINTAINER <name>

比如在上面的Dockerfile增加作者信息:

总结

图片来自: 百度安全验证

命令式与声明式

上面介绍了Dockerfile文件与对应的指令,通过编写Dockerfile文件一步步的指定docker构建要执行的命令,“告诉”计算机每步该做什么,所有的步骤都列清楚,这样程序才能够一步步走下去,最后完成任务。这种就是命令式的文件,与之相反Kubernetes编写的YAML文件,就是"声明式"的:

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程序!

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值