[Docker系列]Dockerfile介绍

为了快速对Dockerfile有一个初步的认识我们先来看一个简单的样例

首先我们从镜像仓库拉下来一个mysql:5.7的镜像,我们启动容器并且进入容器当中,但是我们想在里面ping一下某个ip是否能ping通的时候,发现并没有ping这个命令
在这里插入图片描述

如果我们刚好有这个需求,那就非常尴尬了,那可以通过什么办法解决呢?
我们可以基于mysql:5.7重新构建一个新的镜像,并且加入我们想要的东西;我们来看下下面这个Dockerfile文件内容

FROM mysql:5.7

RUN apt-get update \
&& apt install iputils-ping -y

RUN apt-get install vim -y

这里我们安装了ping和vim工具(注意要通过cat /etc/issue 查看发行版信息,不同的版本其安装工具的命令也不一样)

我们先通过此Dockerfile开始构建docker build -t mysql:mumu .
在这里插入图片描述

我们可以看到构建过程分为三步,刚好对应我们Dockerfile的三条命令
构建完成后我们可以看到比mysql:5.7大了53MB
在这里插入图片描述

镜像打完我们就可以开始启动容器:
docker run -p 3306:3306 --name mysql -v /data/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:mumu
启动过后我们ping一下 www.baidu.com,可以看到我们的工具是正常可用的
在这里插入图片描述

通过上面的案例我们是不是已经对Dockerfile有了一个初步的认识了:
Dockerfile是用来构建镜像的
Dockerfile一条指令对应一个step
我们通过Dockerfile以及基础镜像可以构建一个更符合我们要求的镜像

我们通过另外一个Dockerfile来了解镜像分层的概念:

FROM debian
RUN apt-get update && apt-get -y -f install emacs
RUN apt-get update && apt-get -y -f install apache2

Dockerfile中每条指令都是新建一层,一层一层叠加,最终组成我们想要的镜像
在这里插入图片描述

例如上面这个指令就会创建三层,每层只记录本层所做的更改,且这些成都是只读成。当启动一个容器,Docker会在最顶部添加读写层,一般都称该层为容器层。
在这里插入图片描述

每个容器都有自己独立的容器层,所以修改只会存在自己的容器层,容器之间并无影响,这种方式就可以实现不同容器共享一个镜像层。
在这里插入图片描述

为什么要采用分层结构?
最大的好处就是共享资源,比如很多镜像都是从base镜像构建而来的,我们保存一份到磁盘后,就可以被所有镜像共享了

但是比如有人有疑问,如果这个镜像衍生的容器把里面的文件给改了,比如修改了/opt下的某个文件,其他容器的/opt是否也会被修改?
上面我们说过容器层,其是每个容器独立的,修改的数据会直接存放到容器层,镜像层会保持不变。

Dockerfile指令介绍

FROM: 指定基础镜像,必须为第一个命令
FROM :
FROM @
示例:
FROM elasticsearch:7.6.0
注意:tag和digest是可选的,如果为空的时候会自动使用latest版本的基础镜像

MAINTAINER: 维护者信息
MAINTAINER
示例:
MAINTAINER mumu
MAINTAINER [email protected]
MAINTAINER [email protected]

RUN: 构建镜像时执行的命令
RUN
RUN [“executable”, “param1”, “param2”]
示例:
RUN apk update
RUN ["./test", “dev”, “offline”]
注意:RUN指令创建的中间镜像会被缓存,并且在下次构建中使用。如果不想使用这些缓存镜像,可以在构建构建的时候使用–no-cache参数,如:docker build --no-cache

ADD: 将本地文件添加到容器中,tar类型文件会自动解压(网络压缩资源不会被解压),可以访问网络资源,类似wget
ADD
示例:
ADD mumu.txt /mydir/
注意:COPY:功能类似于ADD,但是不会自动解压文件,也不能访问网络资源

CMD: 构建容器后调用,也就是在容器启动时才进行调用。
CMD [“executable”, “param1”, “param2”]
CMD [“param1”, “param2”]
CMD command param1 param2 (shell内部命令)
示例:
CMD [“java”, “-jar” ,"/opt/server/product.jar"]
注意:CMD和RUN不同,CMD用于指定在容器启动时所要执行的命令,而RUN是用于指定镜像构建时所要执行的命令;每个Dockerfile只能有一条CMD命令,如果指定了多条,只有最后一条会执行,如果用户启动容器时指定了运行命令,则会覆盖CMD指定的命令。

ENTRYPOINT: 配置容器,使其可执行化,配合CMD可省去application,只使用参数。
ENTRYPOINT [“executable”, “param1”, “param2”]
ENTRYPOINT command param1 param2 (shell内部命令)
示例:
ENTRYPOINT ["/bin/echo", “hello”]
注意:ENTRYPOINT于CMD非常类似,但是docker run执行的命令不会覆盖ENTRYPOINT,但是如果docker run时使用了–entrypoint选项,此选项的参数可当作要运行的程序覆盖ENTYPPOINT指令指定的程序,而docker run命令中指定的任何参数,都会被当做参数再次传递给ENTRYPOINT; DockerFile中只允许有一个ENTRYPOINT命令,多指定时会覆盖前面的,只执行最后一个。
当指定了ENTRYPOINT后,CMD的含义就发生变化了,不再是直接的运行命令,而是将CMD的内容作为参数传递给ENTRYPOINT指令,换句话说实际执行时将要变成 “”。

到这里是不是有一点迷糊了,CMD和ENTRYPOINT到底有什么联系?
在Dockerfile中,应该至少指定一个CMD和ENTRYPOINT;
将Docker当作可执行程序时,应该使用ENTRYPOINT进行配置;
CMD可以用作ENTRYPOINT默认参数,或者用作Docker的默认命令;
CMD可以被docker run传入的参数覆盖;
docker run传入的参数会附加到ENTRYPOINT后,前提是使用了exec格式

例如Dockerfile如下
FROM nginx
ENTRYPOINT [“nginx”, “-c”]
CMD ["/etc/nginx/nginx.conf"]
当我们启用nginx的时候:docker run nginx:mumu
相当于会执行如下的命令:nginx -c /etc/nginx/nginx.conf
当我们把启动指令改为:docker run nginx:mumu -c /etc/nginx/new.conf
相当于执行如下的命令:nginx -c /etc/nginx/new.conf

LABEL: 用于为镜像添加元数据
LABEL = = =
示例:
LABEL version=“1.0” descriptioin=“这是木木的服务”
注意:使用LABEL指定元数据时,一条LABEL可以指定一条或者多条元数据,通过空格分隔。推荐将所有的元数据通过一条LABEL指令指定,以免生成过多的中间镜像。

ENV: 设置环境变量
ENV 一次只能设置一个变量,key后面的内容都将被视为value
ENV = … 可以设置多个变量,每个变量为一个"="的键值对,如果中包含空格的特殊字符,可以使用\来进行转义
示例:
ENV username admin
ENV password 123456
ENV port=3306

EXPOSE: 指定外界交互的端口
EXPOSE […]
示例:
EXPOSE 80 443
EXPOSE 11221/tcp 11221/udp
注意:EXPOSE命令只是声明了容器应该打开的端口并没有实际打开,如果不用-p指定要映射的端口的话,容器是不会映射端口出去的;要使其可访问的话需要在docker run的时候通过-p来指定这些端口

VOLUME: 用于指定持久化目录
VOLUME ["/path/to/dir"]
示例:
VOLUME ["/ect/nginx/con.d"]
VOLUME["/etc/nginx/con.d", “/etc/nginx/logs” ]
说明:容器运行时应该尽量保持容器存储层不发生写操作,为了防止用户运行时忘记将动态文件所保存目录挂载为卷,我们可以在Dokcerfile中事先指定某些目录挂载为匿名卷,这样即使用户不指定挂载,其也不会向容器存储层写入大量数据;其无法指定主机上对应的目录,是自动生成的。
注意:卷可以和其它容器共享,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理任何容器引用的数据卷,如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v命令
这里重点说一下什么是挂载:

WORKDIR: 工作目录,类似于cd命令
WORKDIR /path/to/dir
示例:
WORKDIR /etc (工作目录为etc)
WORKDIR nginx (工作目录变为/etc/nginx)
注意:通过WORKDIR设置目录后,Dockerfile中后面的命令都是在其指定的目录下执行。在运行容器的时候可以通过-w覆盖Dockerfile的工作目录。

USER: 指定运行容器时的用户名或者UID,后续的一系列命令都将使用该用户。
USER user
USER uid
USER uid:gid
USER user:gid
USER uid:group
USEr user:group
示例:
USER mumu
注意:使用USER指定用户后,Dockerfile后面的命令都将使用该用户,但是在运行容器时可以通过-u参数来覆盖所指定的用户。

ARG: 用于指定传递给构建运行时的变量,跟EVN很像
ARG [=<default value]
示例:
ARG site
ARG user=mumu
注意:如果ARG指定一个默认值并且在构建期间没有传递值过去,那么就使用默认值。

ONBUILD: 用于设置镜像触发器
ONBUILD [INSTRUCTION]
示例:
ONBUILD ADD . /etc/nginx
ONBUILD RUN
注意:其参数可以是任意一个Dockerfile指令,该指令不会对当前的镜像产生实质的影响(把当前镜像名为A),但是如果镜像B基于镜像A,那么在构建镜像B的过程中才会执行其指令,但是当C基于B的时候也不会执行该指令(可以理解为不能隔代遗传,哈哈)。

下面我们再通过一个案例来复习一下下:
我们来看下nginx:latest版本的Dokcerfile,注释部分已经删掉

FROM debian:buster-slim

LABEL maintainer="NGINX Docker Maintainers <[email protected]>"

ENV NGINX_VERSION   1.19.6

ENV NJS_VERSION     0.5.0

ENV PKG_RELEASE     1~buster

RUN set -x \

COPY docker-entrypoint.sh /

COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d

COPY 20-envsubst-on-templates.sh /docker-entrypoint.d

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 80

STOPSIGNAL SIGQUIT

CMD ["nginx", "-g", "daemon off;"]

引用基础镜像:debian:buster-slim
添加源数据:maintainer=“NGINX Docker Maintainers [email protected]
引入三个环境变量:NGINX_VERSION 1.19.6 NJS_VERSION 0.5.0
PKG_RELEASE 1~buster
执行命令 set -x,这个是shell的中把命令打印到屏幕的指令。
复制三个文件到指定的地方
ENTRYPOINT和CMD的组合在一起就是:/docker-entrypoin.sh nginx -g daemon off
指定80端口
STOPSIGNAL:这个是docker1.9后引入的,这个指令发送到容器推出的系统调用信号,例如退出系统前要执行什么操作。

猜你喜欢

转载自blog.csdn.net/qq_41979344/article/details/113405668