技术交流28群

服务热线

135-6963-3175

微信服务号

7、Dockerfile语法 更新时间 2019-1-18 浏览2182次

Dockerfile

用于构建镜像的文本文件,包含一条条指令。

主要关键字有:FROM RUN COPY ADD CMD ENTRYPOINT ENV ARG VOLUME EXPOSE WORKDIR USER HEALTHCHECK ONBUILD


1、编写镜像文件

以下定制一个mysql镜像文件名为Dockerfile内容如下:

FROM mysql:5.7.17
MAINTAINER 1json 
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 
ENV LANG=C.UTF-8

2、构建镜像

在Dockerfile文件的目录下执行:

$ docker build -t mysql:test .

注:.表示当前上下文路径,mysql:test为镜像名:镜像标签名称

.上下文路径不要放无用文件,因为会一起打包发送给docker引擎,文件过多会缓慢。

[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
my/mysql            v2                  fc126db17f99        About an hour ago   563MB
my/mysql            v1                  b53c20aab1d8        4 hours ago         544MB
mysql               latest              0d64f46acfd1        2 weeks ago         544MB
hello-world         latest              bf756fb1ae65        7 months ago        13.3kB
[root@localhost ~]# docker build -t mysql:test .
Sending build context to Docker daemon   16.9kB
Step 1/4 : FROM mysql:5.7.17
5.7.17: Pulling from library/mysql
6d827a3ef358: Downloading 
3fe11378d5c0: Download complete 
.......
Status: Downloaded newer image for mysql:5.7.17
 ---> 9546ca122d3a
Step 2/4 : MAINTAINER 1json
 ---> Running in b81402cc770e
Removing intermediate container b81402cc770e
 ---> 184e14e337a8
Step 3/4 : RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
 ---> Running in 8019e7b30f4d
Removing intermediate container 8019e7b30f4d
 ---> 3499d618d686
Step 4/4 : ENV.UTF-8
 ---> Running in 2307c3bf9e34
Removing intermediate container 2307c3bf9e34
 ---> 8cab1fc0d50b
Successfully built 8cab1fc0d50b
Successfully tagged mysql:test
[root@localhost ~]# docker images             
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysql               test                8cab1fc0d50b        10 minutes ago      407MB
my/mysql            v2                  fc126db17f99        About an hour ago   563MB
my/mysql            v1                  b53c20aab1d8        4 hours ago         544MB
mysql               latest              0d64f46acfd1        2 weeks ago         544MB
hello-world         latest              bf756fb1ae65        7 months ago        13.3kB
mysql               5.7.17              9546ca122d3a        3 years ago         407MB


指令说明:

FROM:指明基于FROM的镜像,后续的操作都是基于此镜像,例如上面后续操作都是基于mysql:5.7.17

MAINTAINER:是作者
RUN:用于执行命令,等同于在shell终端操作的命令。

命令过长可用 \ 换行和&&连接:例如:

RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
以上执行会创建 3 层镜像。可简化为以下格式:
FROM centos
RUN yum install wget \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && tar -xvf redis.tar.gz

COPY:复制指令,从上下文目录中复制文件或者目录到容器里指定路径。

COPY [--chown=<user>:<group>] <源路径1>...  <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",...  "<目标路径>"]

[--chown=<user>:<group>]:可选参数,用户改变复制到容器内文件的拥有者和属组。

<源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

ADD:和COPY使用格式一致。推荐COPY

ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。

ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。


CMD:类似与RUN,不同在于:

时间点不同:CMD在docker run启动容器时运行,RUN在docker build构建镜像时

一般其作用于:为启动的容器指定默认要运行的程序,程序结束容器也结束。CMD指定的参数可被docker run命令所指定的参数覆盖。若存在多个CMD指令,则仅最后一个生效。

格式:

CMD <shell命令>

CMD ["<可执行文件或命令>","<param1>","<param2>",...] 

CMD ["<param1>","<param2>",...]  # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数。


ENTRYPOINT

类似于CMD,但不会被docker run命令行参数指定的指令覆盖,且会当作参数传递给ENTRYPOINT 指令指定的程序。

如果运行 docker run 时使用了 --entrypoint 选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。

示例:

FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参CMD ["/etc/nginx/nginx.conf"] # 变参

1、不传参运行

$ docker run  nginx:test

容器内会默认运行以下命令,启动主进程。

nginx -c /etc/nginx/nginx.conf

2、传参运行

$ docker run  nginx:test -c /etc/nginx/new.conf

容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)

nginx -c /etc/nginx/new.conf


ENV

设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。

格式:

ENV <key> <value>

ENV <key1>=<value1> <key2>=<value2>...

以下示例设置 NODE_VERSION = 7.2.0 , 在后续的指令中可以通过 $NODE_VERSION 引用:

ENV NODE_VERSION 7.2.0RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \  && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"


ARGS

构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

格式:

ARG <参数名>[=<默认值>]



EXPOSE:声明开放容器端口,只是一个声明,并不一定是实际开放的端口

注意如下情况:

FROM nginx
COPY ./index.html /usr/share/nginx/html
EXPOSE 3000
构建完成后执行:docker run -p 3000:3000 -d nginx:v0在浏览器中输入localhost:3000,访问不到服务。关于EXPOSE的解释,官方文档给出的是:EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。
nginx运行的时候对外提供的端口默认是80,即便你在这里声明了3000,也不会改变默认的端口80。
因此,在声明EXPOSE的时候,一定要实现查明当前容器默认的服务端口。

VOLUME:创建容器挂载点映射容器目录。

因为容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

这里的 /data 目录就会在运行时自动挂载为匿名卷。

任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。

例子:

VOLUME /tmp /usr/tmp

"Mounts": [
            {
                "Type": "volume",
                "Name": "3c18486ccfe419156ef62d346d29f6668ec34a236497c47f7e7907e95c310d0e",
                "Source": "/var/lib/docker/volumes/3c18486ccfe419156ef62d346d29f6668ec34a236497c47f7e7907e95c310d0e/_data",
                "Destination": "/tmp",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "c5b6e1e0f6db6e70f15d836caa9663173e4bd8e23db1817146c31348d931a43b",
                "Source": "/var/lib/docker/volumes/c5b6e1e0f6db6e70f15d836caa9663173e4bd8e23db1817146c31348d931a43b/_data",
                "Destination": "/usr/tmp",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ]

将容器中的/tmp和/usr/tmp目录映射到宿主机的目录


volume 在生成的时候如果不指定名称,便会随机生成。

volume 在容器停止或删除的时候会继续存在,如需删除需要显示声明。

$ docker rm -v <container_id>

$ docker volume rm <volume_name>


命令行挂载法:

例子:

docker run -it -v /home/dock/Downloads:/usr/Downloads ubuntu64 /bin/bash

冒号前为宿主机目录,必须为绝对路径,冒号后为镜像内挂载的路径。

现在镜像内就可以共享宿主机里的文件了。

默认挂载的路径权限为读写。如果指定为只读可以用:ro

docker run -it -v /home/dock/Downloads:/usr/Downloads:ro ubuntu64 /bin/bash


指定命名卷挂载:

docker run -d -v mydata:/data xxxx

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

使用 dokcer inspect mycontainer 可以查看到具体的挂载情况:

"Mounts": [
            {
                "Type": "volume",
                "Name": "65df74449969fff0764c32e20f0862fdd65c3d509e9f012a26e17e6244cbece8",
                "Source": "/var/lib/docker/volumes/mydata/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
]

表示容器中的"/var/lib/mysql" 被自动匿名挂载到了本地机器上的 /var/lib/docker/volumes/***


创建管理 volume

# 创建一个卷
$ docker volume create my-vol
# 卷列表
$ docker volume ls
local               my-vol
# 卷信息
$ docker volume inspect my-vol

[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }]
# 删除卷
$ docker volume rm my-vol

用卷启动容器
下例,将卷 myvol2 挂载到容器 /app/。-v 和 --mount 产生的效果相同,但下面命令不能同时执行,会冲突:

# --mount
# 使用  --mount source=myvol2,target/app,readonly 创建只读的
$ docker run -d \
  -it \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest
  
# -v 
# 使用 -v myvol2:/app:ro 创建只读的
$ docker run -d \
  -it \
  --name devtest \
  -v myvol2:/app \
  nginx:latest

你可以执行 docker inspect devtest 验证卷是否创建并且挂载正确:

"Mounts": [
    {
        "Type": "volume",
        "Name": "myvol2",
        "Source": "/var/lib/docker/volumes/myvol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

停止容器和清理卷:

$ docker container stop devtest

$ docker container rm devtest

$ docker volume rm myvol2


WORKDIR

用于指定容器的一个目录为工作目录, 容器启动时执行的命令会在该目录下执行,相当于设置容器的工作目录了。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准,相当与cd 命令,但不同的是指定了WORKDIR后,容器启动时执行的命令会在该目录下执行。

格式:

WORKDIR <工作目录路径>

可以在 docker run命令中用 -w参数覆盖掉WORKDIR指令的设置,如:

执行 docker run -w / myimage


USER

用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。

格式:

USER <用户名>[:<用户组>]


HEALTHCHECK

用于指定某个程序或者指令来监控 docker 容器服务的运行状态。

格式:

HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令

HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令,意思是禁止从父镜像继承的HEALTHCHECK生效。

HEALTHCHECK [选项] CMD <命令> : 这边 CMD 后面跟随的命令使用,可以参考 CMD 的用法。

注:

在Dockerfile中只能有一个HEALTHCHECK指令。如果您列出多个,则只有最后一个HEALTHCHECK将生效。

options的可设定参数:

  interval:间隔(s秒、m分钟、h小时),从容器运行起来开始计时interval秒(或者分钟小时)进行第一次健康检查,随后每间隔interval秒进行一次健康检查;还有一种特例请看timeout解析。

  --start-period=DURATION 启动时间, 默认 0s, 如果指定这个参数, 则必须大于 0s ;--start-period 为需要启动的容器提供了初始化的时间段, 在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。

  timeout:执行command需要时间,比如curl 一个地址,如果超过timeout秒则认为超时是错误的状态,此时每次健康检查的时间是timeout+interval秒。
  retries:连续检查retries次,如果结果都是失败状态,则认为这个容器是unhealth的

例子1:

FROM nginx:latest

HEALTHCHECK –interval=10s –timeout=3s –retries=3 CMD /bin/bash /opt/test.sh


#!/bin/bash
ss -ant | grep 80
if [ $?==0 ]
then
     echo 0
     exlt 0
else
     echo 1 
     exit 1
fi


通过Dockerfile可以预计,容器启动10s内HEALTCHECK的状态为starting,10s后为healthy状态。脚本是监听容器的80端口,存在返回0,不存在返回1。


例子2:

HEALTHCHECK –interval=10s –timeout=3s –retries=3 CMD curl http://192.168.30.5:5000/v2


ONBUILD

用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这是执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。

格式:

ONBUILD <其它指令>