docker——6、Dockerfile
Dockerfile -->docker build(RUN) --> images --> docker run(CMD) --> 运行容器
Docker中有个非常重要的概念叫做——镜像(Image)。Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
- 编写.dockerignore文件
- 容器只运行单个应用
- 将多个RUN指令合并为一个
- 基础镜像的标签不要用latest
- 每个RUN指令后删除多余文件
- 选择合适的基础镜像(alpine版本最好)
- 设置WORKDIR和CMD
- 使用ENTRYPOINT (可选)
- 在entrypoint脚本中使用exec
- COPY与ADD优先使用前者
- 合理调整COPY与RUN的顺序
- 设置默认的环境变量,映射端口和数据卷
- 使用LABEL设置镜像元数据
- 添加HEALTHCHECK
Dockerfile语法说明
1、FROM: 指定基础镜像
FROM <repository>[:<tag>]
FROM <repository>@<digest>
<repository>
指定作为base image的名称<tag>
:可省, base image的标签,为可选项,省略时默认为latest
定制镜像的时候都是以一个镜像为基础,在这个基础上面进行定制。FROM在Dockerfile中是必须的指令,而且必须是第一条指令。
1)在Docker Hub上有非常多的官方镜像,比如服务类(nginx/redis)、语言类(node/openjdk/python)、操作系统类(ubuntu/debian/centos)等,我们可以直接拿来使用。
2)除了选择现有的镜像作为基础镜像外,Docker还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
2、MAINTAINER
MAINTAINER <author's detail>
<author's detail>
可以是任何文本信息,但约定使用作者名称及邮件地址
如: MAINTAINER "can<[email protected]>"
声明作者信息,可以放在文件任何位置,建议放在FROM后面
3、LABEL = = = …
标签,将后面的元数据添加到镜像中,可以用docker inspect查看
4、COPY:复制文件
用于从Docker主机复制文件至创建的新映射文件。COPY 指令将从构建上下文目录中<源路径>的文件复制到新的一层的镜像内的<目标路径>位置。
和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用:
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]
<src>
:要复制的源文件或目录,支持使用通配符<dest>
:目标路径,即正在创建image的文件系统路径;建议为使用绝对路径,否则COPY指定则以WORKDIR为其其实路径;
注意:在路径中有空白字符时,通常使用第2种格式
文件复制准则:
(1)必须是build上下文中的路径,不能是其父目录中的文件
(2)如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制
(3)如果指定了多个,或在中使用了通配符,则必须是一个目录,且必须以/结尾
(4)如果事先不存在,它将会被自动创建,这包括其父目录路径
示例:
[[email protected] img1]# vim /img1/Dockerfile
# Description: test image
FROM busybox:latest
MAINTAINER "Can <[email protected]>"
#LABEL maintainer="Can <[email protected]>"
COPY index.html /data/web/html/
[[email protected] img1]# vim /img1/index.html
Busybox httpd server.
[[email protected] img1]# docker build -t tinyhttpd:v0.1-1 ./
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM busybox:latest
latest: Pulling from library/busybox
57c14dd66db0: Pull complete
Digest: sha256:7964ad52e396a6e045c39b5a44438424ac52e12e4d5a25d94895f2058cb863a0
Status: Downloaded newer image for busybox:latest
---> 3a093384ac30
Step 2/3 : MAINTAINER "Can <[email protected]>"
---> Running in 9915777d4c1f
Removing intermediate container 9915777d4c1f
---> 65f3bb45add2
Step 3/3 : COPY index.html /data/web/html/
---> d91b191fe38f
Successfully built d91b191fe38f
Successfully tagged tinyhttpd:v0.1-1
[[email protected] img1]# docker image ls #查看镜像创建成功
REPOSITORY TAG IMAGE ID CREATED SIZE
tinyhttpd v0.1-1 d91b191fe38f 2 minutes ago 1.2MB
[[email protected] img1]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-1 cat /data/web/html/index.html
Busybox httpd server.
5、ADD
ADD <src>... <dest>
ADD ["<src>",... "<dest>"]
ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径
这个命令将会把src(源)目录、文件、远程文件URL复制到镜像的文件系统中,存放目录为dest(目标)目录。如果src是压缩文件会帮你解压出来。
操作准则:
(1)如果<src>
为URL且<dest>
不以/结尾,到指定的文件将被下载并直接被创建为<dest>
;如果<dest>
以/结尾,则文件名URL指定的文件将被直接下载并保存为<dest>/<filename>
(2)如果<src>
是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”
,命令;然而,通过URL获取到的tar文件将不会自动展开
(3)如果<src>
有多个,或其间接或直接使用了通配符,则<dest>
必须是一个以/结尾的目录路径;如果<dest>
不以/结尾,则其被视作一个普通文件,<src>
的内容将被直接写入到<dest>
;
例如:
将URL的tar包下载至/use/local/src/
#ADD http://nginx.org/download/nginx-1.15.8.tar.gz /use/local/src/
将本地tar包解压至/use/local/src/
ADD nginx-1.15.8.tar.gz /usr/local/src/
6、WORKDIR: 指定工作目录
WORKDIR <dirpath>
用于为Dockerfile中所有的RUN CMD ENTRYPOINT COPY ADD
指定设定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR
会帮你建立目录。
说明:
在Dockerfile文件中,WORKDIR
指令可以出现多次,其路径也可以为相对路径,不过,其实相对比前一个WORKDIR
指令指定的路径。另外,WORKDIR
也可调用由ENV指定定义的变量
例如:
WORKDIR /var/log
WORKDIR $STATEPATH
7、VOLUME
用于在image中创建一个挂载点目录,以挂载Docker host上的卷或其他容器上的卷
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]
如果挂载点目录路径下此前在文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中
如:VOLUME /data/mysql/ 将容器挂载到/data/mysql/
8、EXPOSE: 声明端口
用于为容器打开指定要监听的端口以实现与外部通信
EXPOSE <port1>[/<protocol>][<port1>[/<protocol>]...]
<protocol>
用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议
EXPOSE指令可一次指定多个端口,例如:EXPOSE 11211/udp 11211/tcp
EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在Dockerfile中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是docker run -P
时,会自动随机映射EXPOSE的端口。
要将EXPOSE和在运行时使用-p <宿主端口>:<容器端口>区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而EXPOSE仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
[[email protected] img1]# vim Dockerfile
EXPOSE 80/tcp
[[email protected] img1]# docker build -t tinyhttpd:v0.1-6 ./
[[email protected] img1]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html
[[email protected] ~]# docker inspect tinyweb1
[[email protected] ~]# curl 10.0.0.2
Busybox httpd server.
[[email protected] img1]# docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html
[[email protected] img1]# docker port tinyweb1
80/tcp -> 0.0.0.0:32768
9、ENV: 设置环境变量
用于为奖项定义所需的环境变量,并可被Dockerfile文件中位于其后的其他指令(如ENV、 ADD、COPY
等所调用),后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
调用格式为$variable_name
或${variable_name}
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
第一种格式中,<key>
之后的所有内容均会被视作其<value>
的组成部分,因此,一次只能设置一个变量;
第二种格式可用一次设置多个变量,每个变量为一个“<key>=<value>
”的键值对,如果<value>
中包含空格,可以以反斜线()进行转义,也可通过对<value>
加引号进行标识;另外,反斜线也可用于续行;
定义多个变量时,建议使用第二种格式,以便在同一层中完成所有功能
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。
[[email protected] img1]# docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=0ba2f53395ea
DOC_ROOT=/data/web/html/
WEB_SERVER_PACKAGE=nginx-1.15.8
HOME=/root
[[email protected] img1]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15.2" tinyhttpd:v0.1-7 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=b77090575ca9
WEB_SERVER_PACKAGE=nginx-1.15.2
DOC_ROOT=/data/web/html/
HOME=/root
10、RUN: 执行命令
用于指定docker build
过程中运行的程序,其可以是任何命令。run指令在定制镜像时是最常用的指令之一。
shell 格式:CMD <command>
exec 格式:CMD ["executable", "param1", "param2"...]
(1)第一个格式中,<command>
通常是一个shell命令,且以"/bin/sh -c
“来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用docker stop <container>
命令停止容器时,此进程接收不到SIGTERM信号;就像直接在命令行中输入的命令一样,如RUN echo 'hello, world!' >
(2)第二种语法格式中的参数是一个JSON参数的数组,其中<executable>
为要运行的命令,后面的<paramN>
为传递给命令的选项或参数;然而,此种格式指定的命令不会以”/bin/sh -c
"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式。
RUN ["/bin/bash","-c","<executable>","<param1>"]
类似于函数调用,将可执行文件和参数分开,如RUN [ "sh", "-c", "echo $HOME" ]
Dockerfile中每一个指令都会建立一层,RUN也不例外。每一个RUN的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit这一层的修改,构成新的镜像。所以我们在使用的时候尽可能将指令进行整合(可以使用&&将各个所需命令串联起来)。
注意:json数组中,要使用双引号
11、CMD:容器启动命令
和 RUN 相似,可用于运行任何命令或应用程序,不过,二者的运行时间点不同
(1)RUN指令运行与映像文件构建过程中,而CMD指令运行于基于Dockerfile构建出的新映像文件启动一个容器时。
(2)CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令行选项所覆盖。
(3)在Dockerfile中可以存在多个CMD命令,但仅最后一个会生效
shell 格式:CMD <命令>
exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
参数列表格式:CMD ["参数1", "参数2"...]
#用于为ENTRYPOINT指令提供默认参数
12、USER
用于指定运行image
时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT
指令指定的程序时的用户名或UID
默认情况下,container
的运行身份为root用户
USER <UID> | <UserName>
需要注意的是,可以为任意数字,但实践中其必须为/etc/passwd中某用户的有效UID,否则,docker run 命令将运行失败
示例一:
[[email protected] ~]# cd img1
[[email protected] img1]# vim Dockerfile
# Description: test image
FROM busybox:latest
MAINTAINER "Can <[email protected]>" #作者说明
#LABEL maintainer="Can <[email protected]>" #同上,写法不一样
ENV DOC_ROOT /data/web/html/ #环境变量
ENV WEB_SERVER_PACKAGE="nginx-1.15.8.tar.gz"
COPY index.html ${DOC_ROOT:-/data/web/html/} #复制index.html到/data/web/html/
#COPY index.html /data/web/html/
COPY yum.repos.d /etc/yum.repos.d/
ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/
#下载URL上的tar包并解压至/usr/local/src
#ADD nginx-1.15.8.tar.gz /usr/local/src/ #将本地的tar包放至/usr/local/src
WORKDIR /usr/local/ #指定工作目录
#ADD ${WEB_SERVER_PACKAGE} ./src/ #将tar包解压至/usr/local/src
VOLUME /data/mysql/ #将容器挂载到/data/mysql/
EXPOSE 80/tcp #为容器打开指定要监听的端口80
#执行命令
RUN cd /usr/local/src && \
tar xf ${WEB_SERVER_PACKAGE}
[[email protected] img1]# docker build -t tinyhttpd:v0.1-9 ./
[[email protected] img1]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.15.8" -it tinyhttpd:v0.1-9
/usr/local # cd src
/usr/local/src # ls
nginx-1.15.8 nginx-1.15.8.tar.gz
示例二:
[[email protected] ~]# cd img2
[[email protected] img2]# vim Dockerfile
FROM busybox
LABEL maintainer="Can <[email protected]>" app="httpd"
ENV WEB_DOC_ROOT="/data/web/html"
RUN mkdir -p $WEB_DOC_ROOT && \
echo '<h1>Busybox httpd server.</h1>' > ${WEB_DOC_ROOT}/index.html
CMD /bin/httpd -f -h ${WEB_DOC_ROOT}
#CMD ["/bin/sh","-c","/bin/httpd","-f","-h /data/web/html"]
[[email protected] img1]# docker image inspect tinyhttpd:v0.2-1
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"WEB_DOC_ROOT=/data/web/html"
],
"Cmd": [
"/bin/sh",
"-c",
"/bin/httpd -f -h ${WEB_DOC_ROOT}"
],
[[email protected] img1]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-1
[[email protected] img2]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fcc58e45aa6f tinyhttpd:v0.2-1 "/bin/sh -c '/bin/ht…" About a minute ago Up About a minute tinyweb2
[[email protected] img2]# docker exec -it tinyweb2 /bin/sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/httpd -f -h /data/web/html
6 root 0:00 /bin/sh
10 root 0:00 ps
/ # printenv
WEB_DOC_ROOT=/data/web/html
HOSTNAME=fcc58e45aa6f
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
示例三:
[[email protected] img3]# vim entrypoint.sh
#!/bin/sh
#
cat > /etc/nginx/conf.d/www.conf << EOF
server {
server_name $HOSTNAME;
listen ${IP:-0.0.0.0}:${PORT:-80};
root ${NGX_DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "[email protected]"
[[email protected] img3]# chmod +x entrypoint.sh
[[email protected] img3]# vim Dockerfile
FROM nginx:1.14-alpine
LABEL maintainer="Can <[email protected]>"
ENV NGX_DOC_ROOT='/data/web/html/'
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[[email protected] img3]# vim index.html
New Doc Root for Nginx
[[email protected] img3]# docker build -t myweb:v0.3-6 ./
[[email protected] ~]# docker run --name myweb1 --rm -P myweb:v0.3-6
Sending build context to Docker daemon 4.096kB
Step 1/7 : FROM nginx:1.14-alpine
---> c5b6f731fbc0
Step 2/7 : LABEL maintainer="Can <[email protected]>"
---> Using cache
---> 84f5b7412dea
Step 3/7 : ENV NGX_DOC_ROOT='/data/web/html/'
---> Using cache
---> b27e4dc2fdd6
Step 4/7 : ADD index.html ${NGX_DOC_ROOT}
---> Using cache
---> 10730b8eabb1
Step 5/7 : ADD entrypoint.sh /bin/
---> Using cache
---> 738dbbfd7980
Step 6/7 : CMD ["/usr/sbin/nginx","-g","daemon off;"]
---> Running in 45863d9cb43a
Removing intermediate container 45863d9cb43a
---> a46abb57ac5f
Step 7/7 : ENTRYPOINT ["/bin/entrypoint.sh"]
---> Running in 1d484ccac2c8
Removing intermediate container 1d484ccac2c8
---> 1900fce136a2
Successfully built 1900fce136a2
Successfully tagged myweb:v0.3-6
[[email protected] img3]# docker exec -it myweb1 /bin/sh
/ # cat /etc/nginx/conf.d/www.conf
server {
server_name b97459d32aaa;
listen 0.0.0.0:80;
root /data/web/html/;
}
/ # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
/ # wget -O - -q localhost #访问测试
/ # wget -O - -q b97459d32aaa
New Doc Root for Nginx
#测试植入参数
[[email protected] ~]# docker kill myweb1
[[email protected] ~]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-6
[[email protected] img3]# docker exec -it myweb1 /bin/sh
/ # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
典型案例参考:https://github.com/docker-library/mysql/
官方文档:https://docs.docker.com/engine/reference/builder/
最佳实践文档:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/
13、Dockerfile HEALTHCHECK
Dockerfile中使用HEALTHCHECK
的形式有两种:
1、HEALTHCHECK [options] CMD command
(本次详细解释)
2、HEALTHCHECK NODE
意思是禁止从父镜像继承的HEALTHCHECK
生效
下面我们主要介绍第一种形式的应用:
options有三个参数可设定:interval
:间隔(s秒、m分钟、h小时),从容器运行起来开始计时interval
秒(或者分钟小时)进行第一次健康检查,随后每间隔interval
秒进行一次健康检查;还有一种特例请看timeout
解析。timeout
:执行command
需要时间,比如curl 一个地址,如果超过timeout
秒则认为超时是错误的状态,此时每次健康检查的时间是timeout+interval
秒。retries
:连续检查retries次,如果结果都是失败状态,则认为这个容器是unhealth
的
CMD关键字后面可以跟执行shell脚本的命令或者exec数组。CMD后面的命令执行完的返回值代表容器的运行状况,可能的值:0 health状态,1 unhealth状态,2 reserved状态,
这个没细研究,用的也很少。
注意:在Dockerfile中只能有一个HEALTHCHECK
指令。如果您列出多个,则只有最后一个HEALTHCHECK
将生效。
[[email protected] img3]# vim Dockerfile
FROM nginx:1.14-alpine
LABEL maintainer="Can <[email protected]>"
ENV NGX_DOC_ROOT='/data/web/html/'
ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/
EXPOSE 80/tcp
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/
CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]
[[email protected] ~]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-8
127.0.0.1 - - [27/Jan/2019:06:36:24 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:36:55 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:37:25 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:37:56 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:38:26 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:38:57 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
127.0.0.1 - - [27/Jan/2019:06:39:27 +0000] "GET / HTTP/1.1" 200 23 "-" "Wget" "-"
14、SHELL
SHELL指令可以覆盖命令的shell模式所使用的默认shell。Linux的默认shell是[“/bin/sh”, “-c”]
,Windows的是[“cmd”, “/S”, “/C”]
。SHELL指令必须以JSON格式编写。
SHELL指令在有两个常用的且不太相同的本地shell:cmd
和powershell
,以及可选的sh的windows上特别有用。
SHELL指令可以出现多次。每个SHELL指令覆盖之前的SHELL指令设置的shell,并影响随便的指令。
15、ARG
只在build中使用。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值>
来覆盖。
在 1.13 之前的版本,要求 --build-arg
中的参数名,必须在 Dockerfile 中用 ARG 定义过了,换句话说,就是 --build-arg
指定的参数,必须在 Dockerfile 中使用了。如果对应参数没有被使用,则会报错退出构建。从 1.13 开始,这种严格的限制被放开,不再报错退出,而是显示警告信息,并继续构建。这对于使用 CI 系统,用同样的构建流程构建不同的 Dockerfile 的时候比较有帮助,避免构建命令必须根据每个 Dockerfile 的内容修改。