docker(四)--Dockerfile
镜像制作
1,基于容器制作
2,dockerfile
docker 语法格式
- # comment 注释信息
- INSTRUCTION arguments 指令与参数,通常指令都是大写
docker是自上而下顺序执行的,第一个非注释行必须是'FROM'指令,基于哪个基础镜像来做的
Dockerfile
1,需要有一个专用工作目录
2,dockerfile放在这个工作目录下,且文件名首字母大写
3,dockerfile如果要打包其他文件到镜像,这些文件的路径必须是当前工作目录或下级子目录,不能是父目录
4,dockerfile支持制作一个隐藏文件.dockeringore,这个文件中可以写入文件路径,在打包时,所有写在.dockeringore中的文件都不包含进去。 例如要打包一个目录下的文件,但不包含其中某3个文件,可以将这3个文件的路径写入.dockeringore。那么.dockeringore本身以及里面包含的文件,都不会被打包进去。
变量
变量引用:$variable_name ${variable_name}
${variable:-word} 如果变量没设置或值为空,就使用word字串,否则就返回变量值。用得较多
[root@abao ~]# echo ${NAME:-lily}
lily
[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:-lily}
jerry
${variable:+word} 变量有值就显示word, 没值就没
[root@abao ~]# NAME=jerry
[root@abao ~]# echo ${NAME:+tom}
tom
[root@abao ~]# unset NAME
[root@abao ~]# echo ${NAME:+tom}
Dockerfile常用指令
FROM 最重要的一个指令,且必须为Dockerfile文件开篇的第一个非注释行,用于指定基准镜像,后续的指令运行于基准镜像提供的运行环境。
基准镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,当其不存在时,则从Docker Hub上拉取
语法:
- FROM <repository>[:<tag>]或
- FROM <repository>@<digest> # <repository>:指定作为base image的名称,digest指定hash码
MAINTAINER (可选,已废弃被LABLE代替) 用于让Dockerfile制作者提供本人的信息
LABLE 为镜像指定各种元数据。 语法:LABEL <key>=<value> <key>=<value> <key>=<value> ... 键值数据
COPY 从宿主机把文件复制到目标镜像中
语法:
- COPY <src> ..<des> 或
- COPY ["<src>",..."<dest>"]
<src>: 要复制的源文件或目录,支持使用通配符(一般是当前工作目录)
<dest>: 目标路径,即正在创建的image的文件系统路径,建议为<dest>使用绝对路径,否则以WORKDIR为其起始路径
注:在路径中有空白字符时,通常使用第二种格式
文件复制准则
- <src> 必须是build上下文中的路径,不能是其父目录的文件
- <src> 如果是目录,<src>目录自身不会被复制,其内部文件或子目录都会被复制。 (目录自身不会被复制,如cp -r tmp/* /tmp)
- 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以/结尾,不加会报错
- 如果<dest>事先不存在,它将会自动创建
COPY index.html /data/web/html/ # index.html一定要在当前目录下,或在子目录下
COPY yum.repos.d /etc/yum.repos.d/ #复制yum.repos.d目录只是复制它下面的内容,所以要写明目标目录名
Dockerfile:
# Description: test image FROM busybox:latest #MAINTAINER "abao <abao@163.com>" LABEL maintainer="abao <abao@163.com>" COPY index.html /data/web/html/
# docker build -t testhttpd:v1.0 ./
# ls
Dockerfile index.html
# docker run --name tinyweb1 --rm tinyhttpd:v0.1 cat /data/web/html/index.html
<h1>hello, Busybox httpd server image.</h1>
ADD 类似于COPY指令,ADD支持使用tar文件和URL路径
语法:
ADD <src> ...<dest> 或
ADD ["<src>",.."<dest>"]
- 同COPY指令
- <src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并被创建为<dest>;如果<dest>以/结尾,则URL指定的文件将被下载并保存到<dest>/目录下
- <src>是一个本地压缩格式tar文件,它将被展开为一个目录,类似于"tar -x"命令; 但是,通过URL获取的tar文件不会自动展开
- <src>有多个,或使用了通配符,则<dest>必须是一个以/结尾的目录路径,如果<dest>不以/结尾,则其被视作一个普通文件,<src>的内容被直接写入到<dest>;
WORKDIR 用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录
语法:
WORKDIR <dirpath>
volume 用于在image中创建一个挂载点目录,用来挂载Docker host上的卷或其它容器上的卷
语法: VOLUME <mountpoint> 或 VOLUME |”<mountpoint>"
如果挂载点目录路径下此前在文件存在,docker run命令会在卷挂载完成后将此前所有文件复制到新挂载的卷中
卷有两种格式:绑定挂载卷和docker管理卷,dockerfile中只能用docker管理的卷,即指定容器中的路径,而不能指定宿主机的目录
VOLUME /data/mysql/ # docker build -t tinyhttpd:v0.5 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.5 sleep 100 # docker inspect tinyweb1 "Mounts": [ { "Type": "volume", "Name": "a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29", "Source": "/var/lib/docker/volumes/a08c5c5c1c4779e77a97682cd6309fbc67e61ece448965354eea28556b7dae29/_data", "Destination": "/data/mysql",
EXPOSE 待暴露端口,用于为容器打开指定要监听的端口以实现与外部通信
(只能指定容器暴露的端口,再随机绑定宿主机的端口,不能指定宿主机绑定的端口)
语法:
EXPOSE <port>[/<protocol>][<port>[/<protocol>]...]
<protocol> 用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议
EXPOSE指令可一次指定多个端口,例如EXPOSE 112/udp 112/tcp
注意:写在dockerfile中的端口暴露并不会直接暴露,使用docker run中的-P选项时,不用指定,会自动读取镜像中要暴露那个端口
EXPOSE 80/tcp # docker build -t tinyhttpd:v0.6 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html # docker inspect tinyweb1 查看容器的ip # curl 172.17.0.3 <h1>hello, Busybox httpd server image.</h1> # docker port tinyweb1 #实际查询没有暴露端口 # docker kill tinyweb1 tinyweb1 # docker run --name tinyweb1 --rm -P tinyhttpd:v0.6 /bin/httpd -f -h /data/web/html # docker port tinyweb1 80/tcp -> 0.0.0.0:32769 可以在外部访问宿主机:32769
ENV 用于为镜像定义所需要的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用,调用格式为$variable_name}或$variable_name
语法:
ENV <key> <value> #一次只能设置一个变量,<key>之后所有内容均会被视作其<value>的组成部分
ENV <key>=<value>... # 一次可设置多个变量,每个变量为一个"<k>=<v>"的键值对,如果<value>中有空格,可以用反斜线\转义,也可用<value>引号进行标识;另外,反斜线也可用于续行。当定义多个变量时,建议使用这种方式,以便在同一层中完成所有功能
例如:
ENV DOC_ROOT=/data/web/html/ \ WEB_SERVER_PACKAGE="nginx-1.17.8" COPY index.html ${DOC_ROOT:-/data/web/html/} WORKDIR /usr/local/src/ ADD ${WEB_SERVER_PACKAGE}.tar.gz ./ # docker build -t tinyhttpd:v0.7 ./ # docker run --name tinyweb1 --rm tinyhttpd:v0.7 ls /usr/local/src/ nginx-1.17.8 # docker run --name tinyweb1 --rm tinyhttpd:v0.7 cat /data/web/html/index.html <h1>hello, Busybox httpd server image.</h1> # docker run --name tinyweb1 --rm tinyhttpd:v0.7 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=1c99b2e89674 DOC_ROOT=/data/web/html/ WEB_SERVER_PACKAGE=nginx-1.17.8 HOME=/root
在docker run时 通过-e 选项 也可指定变量
# docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 printenv PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=e13da61e21b2 WEB_SERVER_PACKAGE=nginx-1.14.0 DOC_ROOT=/data/web/html/ HOME=/root # docker run --name tinyweb1 --rm -e WEB_SERVER_PACKAGE=nginx-1.14.0 tinyhttpd:v0.7 ls /usr/local/src/ nginx-1.17.8 #可以看到docker run时可以修改环境变量的值,但是并不会改变docker build过程
RUN 用于 docker build 过程中运行的命令
ENV DOC_ROOT=/data/web/html/ \ WEB_SERVER_PACKAGE="nginx-1.17.8.tar.gz" ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/ WORKDIR /usr/local/src/ RUN cd /usr/local/src && \ tar -xf ${WEB_SERVER_PACKAGE} && \ mv nginx-1.17.8 webserver # docker build -t tinyhttpd:v0.8 ./ # docker run --name tinyweb1 --rm -it tinyhttpd:v0.8 /usr/local/src # ls nginx-1.17.8.tar.gz webserver
Syntax:
- RUN <command> 或
- RUN ["<executable>","<param1>","<param2>"] #注意要使用双引号,单引号可能会出错
- 第一种格式,<command>通常是一个shell命令,且以"/bin/sh -c"来运行它,当作shell子进程来运行,这意味此进程在容器的PID不为1,不能接收Unix信号,因此,当使用docker stop <container>命令停止容器时,此进程接收不到SIGTERM信号。
- 第二种中的参数是一个JSON格式的数组,其中<executable>为要运行命令,后面<paramN>为传递给命令的选项或参数;然而,命令不会以"/bin/sh -c"来发起,也就是直接由内核来创建,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行; 不过,如果想使用shell来操作,可以使用以下格式:RUN ["/bin/bash","-c","<exectuable>","<param1>"]
CMD 用于定义把镜像启动为容器时(docker run) 默认运行的命令
- 类似于RUN指令,CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同,RUN指令运行于映像文件构建过程中,而CMD指令运行基于Dockerfile构建出新映像文件 启动一个容器时
- CMD指令的首要目的在于为启动容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令选项所覆盖
- 在Dockerfile中可以存在多个CMD指令,但仅最后一个会生效
语法:
- CMD <command> # 自动运行为shell子进程,最大的坏处是id不为1,无法使用docker stop去停止,因为它接收不到信号,接信号都是进程号为1的进程,因为它是一个超管进程,可以让它为1
- CMD ["<executable>","<param1>","<param2>"] # 直接启动为用户id为1的进程,可以接收处理shell的信号
- CMD ["<param1>","<param2>"] # 用于 ENTRYPOINT 指令提供默认参数
测试第一种方法:
FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html CMD /bin/httpd -f -h ${WEB_DOCROOT} #启动容器时,应该是作为shell的子进程来运行,pid不为1 #创建镜像 docker build -t tinyhttpd:v1.0 ./ # 查看镜像 docker image inspect tinyhttpd:v1.0 "Cmd": [ "/bin/sh", #可以看到默认会以"/bin/sh -c"来运行它 "-c", "/bin/httpd -f -h ${WEB_DOCROOT}" ], # 启动容器 docker run --name tinyweb1 -it --rm -P tinyhttpd:v1.0 没有进入交互式,# 镜像中定义默认运行的程序是httpd,是shell的子进程,不是shell,没有交互式接口 # 使用exec进入交互式 ]# docker exec -it tinyweb1 /bin/sh / # ps PID USER TIME COMMAND 1 root 0:00 /bin/httpd -f -h /data/web/html/ # 这里id为1是因为默认执行了exec的替换,为了确保容器能自动接收unix的信号,执行docker stop时能停止,执行docker kill时能杀掉 6 root 0:00 /bin/sh 11 root 0:00 ps / # printenv HOSTNAME=5b27cc8a100f SHLVL=1 HOME=/root TERM=xterm PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin WEB_DOCROOT=/data/web/html/ PWD=/ / #
第二种方式:
CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"] #制作镜像 docker build -t tinyhttpd:v1.1 ./ # 查看镜像 docker image inspect tinyhttpd:v1.1 "Cmd": [ "/bin/httpd", "-f", "-h ${WEB_DOCROOT}" ], # 启动容器 docker run --name tinyweb1 -it --rm -P tinyhttpd:v1.1 httpd: can't change directory to ' ${WEB_DOCROOT}': No such file or directory #会报错,因为程序不会运行为shell子进程,而变量是为shell变量,所以无法识别
ENTRYPOINT
# docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 ls /data/web/html index.html # docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.2 httpd -f -h /data/web/html/ # 在dockerfile中指定的默认要运行的程序是httpd,但是docker run时可以自己指定命令(ls /data/web/html和httpd ...等,覆盖镜像中默认要运行的命令
- ENTRYPOINT可以实现类似于CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序
- 与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当参数传递给ENTRYPOINT指定的程序
- 不过docker run命令中的 --entrypoint选项的参数 可覆盖ENTRYPOINT指令指定的程序
Syntax:
- ENTRYPOINT <command>
- ENTRYPOINT ["<executable","<param1>","<param2>"]
docker run 命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用
Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效
FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html ENTRYPOINT /bin/httpd -f -h ${WEB_DOCROOT} 构建镜像并启动容器,测试: docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.3 ls /data/web/html #并没有执行ls,而是执行的/bin/httpd -f -h /data/web/html ls /data/web/html。 把ls...当作参数附加在命令后面
CMD的第三种方式:
同时有 CMD 和 ENTRYPOINT 时,CMD指定的选项会用于为ENTRYPOINT提供默认参数
FROM busybox LABEL maintainer="abao <abao@163.com>" app="httpd" ENV WEB_DOCROOT="/data/web/html/" RUN mkdir -p ${WEB_DOCROOT} && \ echo "<h1> Busybox httpd server.</h1>" > ${WEB_DOCROOT}/index.html CMD ["/bin/httpd","-f","-h ${WEB_DOCROOT}"] ENTRYPOINT ["/bin/sh","-c"] #默认会执行 /bin/sh -c /bin/httpd -f -h /data/web/html
docker build -t tinyhttpd:v1.3 ./ docker run --name tinyweb2 -it --rm -P tinyhttpd:v1.4 "ls /data/web/html" index.html #发现执行了 ls 命令,因为CMD的参数会被当成ENTRYPOINT的默认参数,而命令行参数会被当参数传给ENTRYPOINT,所以CMD的参数被覆盖了。执行的命令是 /bin/sh -c ls /data/web/html
配置nginx示例:
# cat Dockerfile FROM nginx:1.14-alpine LABEL maintainer="abao <abao@abao.163.com>" 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"] # cat 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 "$@" chmod +x entrypoint.sh echo " <h1> New doc root for nginx.</h1>" > index.html docker build -t myweb:v0.5 ./ docker run --name myweb1 --rm -P myweb:v0.5 docker exec -it myweb1 /bin/sh / # ps -ef PID USER TIME COMMAND 1 root 0:00 nginx: master process /usr/sbin/nginx -g daemon off; 8 nginx 0:00 nginx: worker process 9 root 0:00 /bin/sh 14 root 0:00 ps -ef / # cat /etc/nginx/conf.d/www.conf server { server_name 790db97efd5a; listen 0.0.0.0:80; root /data/web/html/; } / # wget -O - -q 790db97efd5a #访问nginx <h1> New doc root for nginx.</h1>
docker run时传递环境变量 docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.5 docker exec -it myweb1 /bin/sh / # ps PID USER TIME COMMAND 1 root 0:00 nginx: master process /usr/sbin/nginx -g daemon off; #pid为1,因为entrypoint.sh中执行命令是exec 8 nginx 0:00 nginx: worker process 9 root 0:00 /bin/sh 14 root 0:00 ps / # cat /etc/nginx/conf.d/www.conf server { server_name 0a9b161911b8; listen 0.0.0.0:8080; root /data/web/html/; } / # wget -O - -q 0a9b161911b8:8080 <h1> New doc root for nginx.</h1>
通常情况下,可以使用 entrypoint脚本把需要修改的部分,如IP,PORT,作为变量放在配置文件中,让脚本可以通过使用这些环境变量生成配置文件,在docker run时用环境变量给它传值。
USER
用于指定运行image时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指定的程序的用户名或UID
默认情况下,container的运行身份为root用户
语法:
USER <UID>|<UserName>
需要注意的是,<UID>可以为任意数字,但是实践中其必须为/etc/passwd中某用户的有效UID,否则docker run命令将运行失败
HEALTHCHECK
不依据主进程运行与否来判断健康与否,而是检测是否真正能提供服务,使用wget或curl查看是否能加载页面。
语法:
HEALTHCHECK [OPTIONS] CMD command (通过运行一个command来检查容器主进程是否健康)
HEALTHCHECK NONE (拒绝任何健康状态检测,包括默认的检测机制)
OPTIONS:
--interval=DURATION(default: 30s) #检测间隔时间
--timeout=DURATION(default: 30s) #超时时间
--start-period=DURATION(default: 0s) #容器启动时,可能进程服务还没初始化完成,这时去检查会是报错失败的,检测可以等待一段时间,像tomcat程序启动时可能需要10s
--retries=N(default: 3) #重试次数
响应结果:
0:success
1: unhealthy
2: reserved 预留,自定义
示例:
HEALTHCHECK --interval=5m --timeout=3s \
CMD curl -f http://localhost || exit 1
FROM nginx:1.14-alpine LABEL maintainer="abao <abao@abao.163.com>" 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"] # docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.6 127.0.0.1 - - [25/Feb/2020:03:16:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-" 127.0.0.1 - - [25/Feb/2020:03:17:23 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-" 127.0.0.1 - - [25/Feb/2020:03:17:53 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-" 127.0.0.1 - - [25/Feb/2020:03:18:24 +0000] "GET / HTTP/1.1" 200 34 "-" "Wget" "-"
SHELL 定义运行程序默认使用的shell
语法: SHELL ["executable","parameters"]
默认linux是["/bin/sh","-c"],windows是["cmd","/S","/C"]
STOPSIGNAL
进程号id 为 1 的,可以接收 docker stop 命令,从而能够停止主进程,从而停止容器。如 stop 发送的是 15,signal 是无符号整型数字(必须匹配 kernal 的 syscall table),如 9 ,也可以是 SIGNAME,如 SIGKILL
Syntax: STOPSIGNAL signal
ARG
在 docker build 过程中传参数,使用 --build-arg <varname>=<value>
FROM nginx:1.14-alpine ARG author="abao <abao@163.com>" LABEL maintainer=${author} 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"] # docker build --build-arg author="xiao <xiao@qq.com>" -t myweb:v0.9 ./ # docker image inspect myweb:v0.9 |grep maintainer "maintainer": "xiao <xiao@qq.com>" "maintainer": "xiao <xiao@qq.com>"
ONBUILD 用于在 Dockerfile 中定义一个触发器
Dockerfile用于 build 镜像文件,此镜像文件可以作为 base image 被另一个 Dockerfile 用做 FROM 指令的参数,并以只构建新的镜像文件
在后面的这个Dockerfile中的 FROM 指令在 build 过程中被执行时,将会“触发”创建其 base image 的 Dockerfile 文件中 ONBUILD 指令定义的触发器
base image->build -> image1 -> build(这个过程中执行ONBUILD)->image2
Syntax: ONBUILD <INSTRUCTION>
- 尽管任何指令都可以注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER 指令
- 使用包含 ONBUILD 指令的 Dockerfile 构建的镜像应该使用特殊的标签,例如 ruby:2.0-onbuild
- 在 ONBUILD 指令中使用 ADD 或 COPY指令应该格外小心,因为新构建过程的上下文在缺少指定原文件时会失败