容器系列之Dockerfile
1.Dockerfile
场景:定义一个nginx不会运行在默认配置下,要自定义配置文件,由于各个生产场景环境的不一样,即使从nginx仓库中拉下来的镜像也不一样符合需求。
有了容器配置后,要启动docker后,再进入容器来使用vi修改配置,这样变得更加不方便使用,不过可以把配置文件做成存储卷的形式来操作容器,把配置文件存放路径与宿主机的做关联,启动后做reload,但是有个问题,就是再次修改完后,配置是要重载才能生效.
解决方式:自行定义镜像
- 基于容器:基于一个基础的容器启动,再进行交互式来修改配置,修改完成后是放在可写层中的,这时可以把它保存为一个新的镜像,再创建容器时,根据自己所创建的镜像来启动,缺陷也是配置写死在镜像中,还有一个特大的问题是如开发,测试,生产有三个环境三个镜像,如果某个服务如nginx要升级,又加三个镜像,如果nginx的服务作用也不一样更多,如做proxy,web等。
制作镜像的原因:其他人制作的环境,配置都是比较普遍的,未必适合自身的场景
docker在配置文件上的解决方案
分析:如nginx服务启动为容器后,只做一件事,提供一个虚拟主机,提供一个服务,监听的端口,文档的目录路径可能是不一样的,但是配置文件的格式是一样的,可以做成模板,把server.conf,/etc/nginx/conf.d下,里面要配置的选项通过一个变量来获得如server_name $NGX_SERVER_NAME等,文件内部可以接收变量,并且可以实现变量替换的方式来操作,当用户使用这个镜像启动容器时,先在容器内部的主进程中,启动之前先启动一个别的程序A,程序根据镜像中的文件server.conf,以及用户启动容器时,向容器环境传递环境变量,没有默认值的必需转参数,程序就把用户把变量传递进的值传入到配置文件server.conf的变量中,保存完了,再由这个程序A启动主进程,它就退出,同一个进程启动并替换当前进程使用exec,启动一个子进程,并且子进程是直接把当前进程替换掉了。进程A作用主要是给主进程预设环境的,这是为什么通过环境变量可以配置服务的原因。
所以基于一个镜像启动多个容器时,让容器拥有不同配置的作法是向它传变量,而对应的容器要能够处理变量,而且把它替换
成主机内的配置信息才可以,默认情况下nginx是不支持这种功能,所以自陪定义镜像就是实现这些问题。
云源生:通常是设置配置文件就是使用变量来替换,甚至可以做到程序启动时,完全不读配置文件,直接去加载系统之上的环境变量,就可以获得这个配置,直接通过获取环境变量来获取配置,容器化时代把直接修改配置文件的操作就直接改变了。
关于dockerfile
Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 快速创建自定义的镜像。文件中使用是docker所要使用规定的指令
2.Dockerfile语法格式
主要是两类组成
- 注释信息#开头
- 指定与参数,一般一行一个指令,通常是使用大写,是顺序自上而下执行,但是第一个非注释行必须是FROM指令,
要基于那个镜像来基础做的
docckerfile工作逻辑
分析:使用操作系统时每时都应该处在某个目录下,称为工作目录,也叫做当前目录,制作docker镜像时必须在一个特定的目录下进行,在这个目录中放进dockerfile,文件名的首字母要大写的docker文件,如果dockerfile说要打包镜像进去很多文件,这些文件必须做好后放在当前的工作目录下,也就是基于dockerfile去制作镜像时,它所引用的文件的文件路径不能是这个工作目录的父目录,只能是基于这个目录往下走的路径,所以要使用的所有文件都要放进去,如一个rpm包,可以是子目录,但是不能是在父目录,dockerfile支持制作一个隐藏文件.dockeringore,在这个文件中可以写进文件路径,一行一个,可以使用通配符来表示多个,所有在打包时的文件中,写在.dockeringore中的文件中的都不包含进去,如在打包中有100个要把包的,可以写在一个目录中,其中有不想把打包写在.dockeringore文件中来排除,然后就可以通过docker build命令来读取文件,制作好镜像push到仓库中就可以使用。
docker build中只是帮助使用者制作,在制作镜像中有种基于容器来制作方式,容器是通过联合挂载,上面再通过一个可写层,所有写操作都放在可写层中,制作镜像时直接把它保存就可以了,显然可写层上执行的操作,要交互式的连接到容器中才能完成,执行的命令都是容器中支持的可执行程序的命令。
基于dockerfile制作镜像时,是使用docker build来启动容器的,隐藏式启,dockfile中可以执行很多shell的命令,这些命令是底层镜像中所包含的命令,如果底层镜像没有就不支持。
环境变量替换
描述:是使用docker build时所能使用的环境变量,制作镜像就要使用使用镜像先启容器来实现,启容器就可以向它传环境变量
$variable_name ${variable_name} ${variable:-word} 如果变量未设置或者引用的值为空,就使用word字串 ${variable:+word} 变量有值就显示word,没值就没
3.Dockerfile指令
注意:在dockerfile里面,每条指令都会生成一层,层越多联全挂载的效率越差
FROM
- FROM指令是最重的一个且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于基准镜像提供的运行环境
- 实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需要的镜像文件
- 如果找不到指定的镜像文件,docker build会返回一个错误信息
语法: - FROM <repository>[:<tag>]或 - FROM <repository>@<digest> <repository>:指定作为base image的名称 <tag>: base image的标签,为可选项,省略时默认为latest; [root@node1 ~]# mkdir img1 [root@node1 ~]# cd img1 [root@node1 img1]# vim Dockerfile # Description: test image FROM busybox:latest
LABEL
描述:让用户为镜像指定各种源数据 语法:LABEL <key>=<value> <key>=<value> <key>=<value> ... 键值数据 LABEL maintainer="Reid <reid@kk.com>"
COPY
用于从Docker主机复制文件至创建的新映像文件(从宿主机把文件复制到目标镜像系统中) 语法: - COPY <src> ..<des> 或 - COPY ["<src>".."<dest>"] <src>: 要复制的源文件或目录,支持使用通配符(一般是当前工作目录) <dest>: 目标路径,即正在创建的image的文件系统路径;建议为<dest>使用绝对路径,否则,COPY指定刚则以WORKDIR为其起路径;(生成目标镜像的路径,当前是找不到的) 注:在路径中有空白字符时,通常使用第二种格式 文件复制准则 - <src>必须是build上下文中的路径,不能是其父目录的文件 - 如果<src>是目录,则其内部文件或子目录会被递归复制,但<src>目录自身不会被复制(目录自身不会被复制,如cp -r a/* /tmp) - 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以/结尾 - 如果<dest>事先不存在,它将会自动创建,这包括其父目录路径 COPY index.html /data/web/html/ #index.html一定要在当前目录下,或在子目录下 COPY yum.repos.d /etc/yum.repos.d/ #复制yum.repos.d只是复制它下面的内容 [root@node1 img1]# ls Dockerfile index.html #制作完成后的镜像只有标签号 [root@node1 img1]# docker build -t tinyhttpd:v0.1-1 ./ Sending build context to Docker daemon 3.072kB Step 1/3 : FROM busybox:latest ---> e1ddd7948a1c Step 2/3 : LABEL maintainer="Reid <reid@kk.com>" ---> [Warning] IPv4 forwarding is disabled. Networking will not work. ---> Running in 1b1655c055c9 Removing intermediate container 1b1655c055c9 ---> 7a1bae308ce9 Step 3/3 : COPY index.html /data/web/html/ ---> 89011c191ecc Successfully built 89011c191ecc Successfully tagged tinyhttpd:v0.1-1 [root@node1 img1]# docker image ls|grep tinyhttp tinyhttpd v0.1-1 89011c191ecc About a minute ago 1.16MB [root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-1 cat /data/web/html/index.html 可以直接执行命令 <h1> busybox ,hi </h1>
ADD
ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径 语法: ADD <src> ...<dest> 或 ADD ["<src>".."<dest>"] - 同COPY指令 - 如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以/结尾,则文件名URL指定 的文件将被直接下载并保存为<dest>/<filename> - 如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于"tar -x"命令;然而,通过URL获取到 tar文件将不会自动展开 - 如果<src>有多个,或期间接或直接使用了通配符,则<dest>必须是一个以/结尾的目录路径,如果<dest>不以/结尾,则其被视作 一个普通文件,<src>的内容被直接写入到<dest>; 注意:本地的才会展开 ADD http://nginx.org/download/nginx-1.14.0.tar.gz /usr/local/src/ ADD nginx-1.14.0.tar.gz /usr/local/src/ #展开 [root@node1 img1]# docker build -t tinyhttpd:v0.1-4 ./ Sending build context to Docker daemon 1.02MB Step 1/4 : FROM busybox:latest ---> e1ddd7948a1c Step 2/4 : LABEL maintainer="Reid <reid@kk.com>" ---> Using cache ---> 7a1bae308ce9 Step 3/4 : COPY index.html /data/web/html/ ---> Using cache ---> 89011c191ecc Step 4/4 : ADD nginx-1.14.0.tar.gz /usr/local/src/ ---> 0f793eed90dd Successfully built 0f793eed90dd Successfully tagged tinyhttpd:v0.1-4 [root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-4 ls /usr/local/src nginx-1.14.0 目录
WORKDIR
用于Dockerfile中所有的RUN,CMD,ENTRYPOINT,COPY和ADD指定设定工作目录 语法: WORKDIR <dirpath> 在Dockerfile文件中,WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径 另外,WORKDIR也可调用由ENV指定定义的变量 例如: WORKDIR /var/log WORKDIR $STATEPATH 或者: WORKDIR /usr/local/src/ ADD nginx-1.14.0.tar.gz ./
VOLUME
用于在image中创建一个挂载点目录,以挂载Docker host上的卷或其它容器上的卷 语法 VOLUME <mountpoint> 或 VOLUME |”<mountpoint>" 如果挂载点目录路径下此前在文件存在,docker run命令会在卷挂载完成后将此前所有文件复制到新挂载的卷中 卷有两种格式:绑定挂载卷和docker管理卷,绑定挂载可以指定容器和宿主机的路径,dockerfile中只能指定容器中的路径 VOLUME /data/mysql/ [root@node1 img1]# docker build -t tinyhttpd:v0.1-5 ./ [root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-5 mount|grep data /dev/mapper/centos-root on /data/mysql type xfs (rw,relatime,attr2,inode64,noquota) 查看 [root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-5 sleep 100 #让它启动时sleep一段时间 [root@node1 img1]# docker inspect tinyweb1 |grep -A 5 Mounts "Mounts": [ { "Type": "volume", "Name": "c0b68f9bc011494912ef6f0978cc0c9e10a4c4a4e5a7f2ee22bdfe87584a50ed", "Source": "/var/lib/docker/volumes/c0b68f9bc011494912ef6f0978cc0c9e10a4c4a4e5a7f2ee22bdfe87584a50ed/_data", "Destination": "/data/mysql", ##############
EXPOSE
用于为容器打开指定要监听的端口以实现与外部通信(只能指定容器端口,再随机绑定宿主机的端口,因为在那个宿主机上启动是不确定的)
注意:容器中会提供端口暴露的功能,如运行http的80,但是宿主机启动docker进程时不一定需要暴露到外面去,一个容器的服务可以被当前宿主机上工作在同一个桥上的容器所访问,甚至可以被宿主机docker host所访问,所以需要暴露时才暴露,如果基于镜像启动的容器不需要向外,但是一启动就暴露出来是很危险的,所以写在dockerfile中的端口暴露并不会直接暴露,使用docker run中的-P选项时,不用指定,会自动读取镜像中要暴露那个端口
语法 EXPOSE <port>[/<protocol>][<port>[/<protocol>]...] <protocol>用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议 EXPOSE指令可一次指定多个端口,例如EXPOSE 112/udp 112/tcp 例子: EXPOSE 80/tcp [root@node1 img1]# docker build -t tinyhttpd:v0.1-6 ./ [root@node1 ~]# docker run --name tinyweb1 --rm tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html #-f前台启动 [root@node1 img1]# docker inspect tinyweb1|grep IPAddress "SecondaryIPAddresses": null, "IPAddress": "10.0.0.6", "IPAddress": "10.0.0.6", [root@node1 img1]# curl 10.0.0.6 <h1> busybox ,hi </h1> [root@node1 img1]# docker port tinyweb1 #实际查询是没有暴露端口的 [root@node1 img1]# docker kill tinyweb1 tinyweb1 另一种启动方式: [root@node1 ~]# docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-6 /bin/httpd -f -h /data/web/html [root@node1 ~]# docker port tinyweb1 80/tcp -> 0.0.0.0:32768 可以在宿主机上测试:http://192.168.56.129:32768/
ENV
用于为镜像定义所需要的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用调用格式为$variable_name或$(variable_name)
语法: ENV <key> <value>或 ENV <key>=<value>... 第一种格式中,<key>之后所有内容均会被视作其<value>的组成部分,因此,一次中能设置一个变量 第二种格式可用一次设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<value>中包含空格,可以以反斜线(\)进行转义, 也可通过<value>引号进行标识;另外,反斜线也可用于续行 定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能 ENV DOC_ROOT /data/web/html/ COPY index.html $DOC_ROOT 或者: ENV DOC_ROOT /data/web/html/ COPY index.html ${DOC_ROOT:-/data/web/html} 或者定义多个 ENV DOC_ROOT /data/web/html/ ENV WEB_SERVER_PACKAGE="nginx-1.14.0" ADD ${WEB_SERVER_PACKAGE}.tar.gz ./src/ 检查: [root@node1 img1]# docker build -t tinyhttpd:v0.1-7 ./ [root@node1 ~]# docker run --name tinyweb1 --rm -P tinyhttpd:v0.1-7 ls /usr/local/src/ nginx-1.14.0 系统启动与镜像构建是两个不同的过程 分析:有目录执行build可以制作成镜像,run是把一个镜像启动为容器,所以是两个不同的步骤和阶段,dockerfile中定义的变量 是在build阶段中执行,但是可以在启动容器后,直接在容器中使用的变量,变量是可以被注入到容器中的 [root@node1 ~]# 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=680790f408e5 DOC_ROOT=/data/web/html/ WEB_SERVER_PACKAGE=nginx-1.14.0 HOME=/root [root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.14.1" tinyhttpd:v0.1-7 printenv #-e直接指定环境变量 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOSTNAME=2e091b34f7bf WEB_SERVER_PACKAGE=nginx-1.14.1 ##### DOC_ROOT=/data/web/html/ HOME=/root
CMD与RUN
分析: 在docker build与run两个阶段都可以运行shell命令,把一个镜像启动为容器时,默认是使用CMD,当基于dockerfile构建镜像时,RUN是在docker build中进行
启动时运行:printenv命令 [root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.14.1" tinyhttpd:v0.1-7 printenv RUN操作:命令是可以运行多次的 RUN cd /usr/local/src && \ tar -xf ${WEB_SERVER_PACKAGE} 完整: [root@node1 img1]# cat Dockerfile # Description: test image FROM busybox:latest LABEL maintainer="Reid <reid@kk.com>" ENV DOC_ROOT /data/web/html/ ENV WEB_SERVER_PACKAGE="nginx-1.14.0.tar.gz" COPY index.html ${DOC_ROOT:-/data/web/html/} WORKDIR /usr/local/ ADD http://nginx.org/download/${WEB_SERVER_PACKAGE} /usr/local/src/ VOLUME /data/mysql/ EXPOSE 80/tcp RUN cd /usr/local/src && \ tar xf ${WEB_SERVER_PACKAGE} [root@node1 img1]# docker build -t tinyhttpd:v0.1-9 ./ [root@node1 ~]# docker run --name tinyweb1 --rm -P -e WEB_SERVER_PACKAGE="nginx-1.14.1" tinyhttpd:v0.1-9 ls /usr/local/src/ nginx-1.14.0 展开了 nginx-1.14.0.tar.gz
CMD命令使用场景引入
场景分析:docker的一个容器只是为了运行单个程序,一般能有生产能力的应用是一般在宿主机上按传统惯例,让其运行为守护进程的后台程序,如nginx,redis,mysql,但是此前去启动并运行这些服务时,每个进程都是某个进程的子进程,除了init,它是使用内核来启动的,假设手动启动nginx,在命令行中,如果不使用systemctl的命令下启动,也可以直接使用自行命令进行启动,这时nginx是shell的子进程,用户创建并启动进程的接口是shell,是与操作系统可以交互的接口,所以打开一个命令行提示符就相当于运行一个shell,在shell窗口下创建的进程都是shell的子进程,而且有些进程可能直接占据当前终端shell的设备,放在后台使用&,但是它并不能脱离关系,启动它的父进程是当前shell,如果现在启动一个命令后,直接exit退出了shell,任何进程终止时,会顺便把它的所有子进程销毁,所以任何一个进程即使使用&符号,退出shell窗口后还是不能继续运行,要实现这种功能要使用nohup,使用它可以将子进程送到后台,并且脱离与当前shell的关系,也就是相当于把启动进程的父进程,把它递交给init。
相当于自家生一个娃,帮它找一个父进程,一旦家族有问题,不会让它成为孤儿,在进程运行领域中,都是白发人送黑发人的,万一白发人先走了,没有给它的后代安排好的话,它的后代就会成为孤儿进程,就会浏览于整个系统之上,也不会释放内存。
shell启动:在容器中,要启动并只运行一个进程是main process,它就是这个用户空间id号为1的进程,本应该是由内核来启动的,它就是这个用户空间id号为1的进程,所以应该是由内核来启动的,但是在此处不能使用ls /var/*这种类似的命令,*号是shell所提供的功能,所以在shell命令行下所启动的话,这个命令是可以正常执行的,如果由内核启动,则不能下正常执行,如果在用户空间中,不是作为shell的子进程去启动一个程序,这些命令行的形式是都变得不再可用的,如果基于shell来启动,这个主进程却又不是这个用户空间的用户id为1进程。
假设在容器中,希望基于shell的方式来启动一个主进程,应该先在用户空间中启动shell,shell的id号为1,再启动它的主进程,这时shell也不能退出, 一退出,它的主进程就没服,所以还要基于shell以剥离终端的方式来启动它的,但是剥离终端后发现主进程的id号不为1
exec启动: 有一个方法:如果使用shell来启动,shell的id为1也可以,只能使用exec,在shell的命令行下启动主进程时,在shell启动时要使用exec command方式,结果是exec顶替shell的id为1,来取代shell进程,shell退出它就成为id为1的进程。很多时候,在容器中启动一个进程时可以不基于shell直接启动进程,也可以基于shell为启动,但是要基于shell启动,并且不违背这个主进程一定是id为1的条件和关系,可以通过exec command方式来实现。
RUN&CMD命令使用:有两个运行命令的dockerfile指令可用,RUN 与 CMD, RUN是在docker build过程中执行的,CMD是定义一个镜像文件启动为容器时默认要运行的程序,而docker程序默认只运行一个程序,所以不能在一个dockerfile中使用多个CMD,CMD可以使用多个,但是只有最后一个生效.但是RUN在build的过程中,有多个RUN指令,运行一个就可以运行第二个,一个个按顺序运行下去。RUN与CMD最大两个不同在于运行时间点不同和运行的行为不同。
CMD
类似于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>"] 前两种语法格式的意义同RUN,第三种则用于ENTRYPOINT指令提供默认参数
RUN
用于指定docker build过程中运行的程序,其可以是任何命令 语法: 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>"] [root@node1 ~]# mkdir img2 [root@node1 ~]# cd img2 [root@node1 img2]# vim Dockerfile [root@node1 img2]# cat Dockerfile FROM busybox LABEL maintainer="Reid <reid@kk.com>" ENV WEB_DOC_ROOT="/data/web/html/" RUN mkdir -p $WEB_DOC_ROOT && \ echo '<h1> http server </h1>' ${WEB_DOC_ROOT}/index.html CMD /bin/httpd -f -h ${WEB_DOC_ROOT} #注意要有空行 [root@node1 img2]# docker build -t tinyhttpd:v0.2-1 ./ Sending build context to Docker daemon 2.048kB Step 1/5 : FROM busybox ---> e1ddd7948a1c Step 2/5 : LABEL maintainer="Reid <reid@kk.com>" ---> Using cache ---> 7a1bae308ce9 Step 3/5 : ENV WEB_DOC_ROOT="/data/web/html/" ---> Using cache ---> ba1d69b6b755 Step 4/5 : RUN mkdir -p $WEB_DOC_ROOT && echo '<h1> http server </h1>' ${WEB_DOC_ROOT}/index.html ---> Running in 9ac61fd65132 <h1> http server </h1> /data/web/html//index.html Removing intermediate container 9ac61fd65132 ---> 48f92d906a46 Step 5/5 : CMD /bin/httpd -f -h ${WEB_DOC_ROOT} ---> Running in 7b6f813a301c Removing intermediate container 7b6f813a301c ---> f014eb4921c5 Successfully built f014eb4921c5 Successfully tagged tinyhttpd:v0.2-1 # 镜像中定义默认运行的程序不再是shell,之前使用-it可以自动进入交互接口,现在不行,默认应用程序是httpd [root@node1 ~]# docker image inspect tinyhttpd:v0.2-1|grep -A 5 Cmd "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/sh\" \"-c\" \"/bin/httpd -f -h ${WEB_DOC_ROOT}\"]" [root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-1 #启动时,卡住了 [root@node1 ~]# docker ps 默认是httpd,是shell的子进程,不是shell,没有交付式接口 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4032da8ae682 tinyhttpd:v0.2-1 "/bin/sh -c '/bin/ht…" 9 seconds ago Up 7 seconds tinyweb2 [root@node1 ~]# docker exec -it tinyweb2 /bin/sh #使用exec进行交付 / # ps PID USER TIME COMMAND 1 root 0:00 /bin/httpd -f -h /data/web/html/ #httpd的进程为1,默认就使用exec的替换,避免不为1,为了确保容器能自动接收unix的信号,执行docker stop时能停止,执行docker kill时能杀掉, 已经确定是启动为bin shell的子进程的,而且镜像启动后默认运行命令已经修改了,而且/data/web/html/中shell的解释也是成功的。 6 root 0:00 /bin/sh 12 root 0:00 ps / # printenv WEB_DOC_ROOT=/data/web/html/ #变量是在dockerfile中定义的,会自动注入,docker run时也可以调用 HOSTNAME=4032da8ae682 SHLVL=1 HOME=/root TERM=xterm PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PWD=/ 修改CMD [root@node1 img2]# cat Dockerfile FROM busybox LABEL maintainer="Reid <reid@kk.com>" ENV WEB_DOC_ROOT="/data/web/html/" RUN mkdir -p $WEB_DOC_ROOT && \ echo '<h1> http server </h1>' ${WEB_DOC_ROOT}/index.html #CMD /bin/httpd -f -h ${WEB_DOC_ROOT} CMD ["/bin/httpd","-f","-h ${WEB_DOC_ROOT}"] #会报错,默认不会运行为shell的子进程,WEB_DOC_ROOT是shell的变量 [root@node1 img2]# docker build -t tinyhttpd:v0.2-2 ./ [root@node1 ~]# docker image inspect tinyhttpd:v0.2-2|grep -A 5 Cmd "Cmd": [ "/bin/httpd", 没有bin shell "-f", "-h ${WEB_DOC_ROOT}" ], "ArgsEscaped": true, [root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-2 httpd: can't change directory to ' ${WEB_DOC_ROOT}': No such file or directory #无法解析
ENTRYPOINT
场景:docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-4 ls /data/web/html/ ,在容器中默认的程序是httpd,但是可以自定义输出命令,对于自制的镜像,在容器中默认要运行命令是可以被覆盖的,有时候不允许覆盖,CMD不能完成,ENTRYPOINT可以实现类似于CMD指令的功能,用于为容器指定默认运行程序,从而使用容器像是一个单独的可执行程序与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当参数传递ENTRYPOINT指定的程序不过docker run命令是的--entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序
语法: - ENTRYPOINT <command> - ENTRYPOINT ["<executable","<param1>","<param2>"] docker run 命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用 **** Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会和生效 特别注意:JSON格式中要使用双引号 [root@node1 img2]# cat Dockerfile FROM busybox LABEL maintainer="Reid <reid@kk.com>" ENV WEB_DOC_ROOT="/data/web/html/" RUN mkdir -p $WEB_DOC_ROOT && \ echo '<h1> http server </h1>' ${WEB_DOC_ROOT}/index.html ENTRYPOINT /bin/httpd -f -h ${WEB_DOC_ROOT} [root@node1 img2]# docker build -t tinyhttpd:v0.2-5 ./ [root@node1 ~]# docker run --name tinyweb2 -it --rm -P tinyhttpd:v0.2-5 ls /data/web/html/ #一直卡住 #它真正运行的命令是/bin/httpd -f -h /data/web/html,使用ENTRYPOINT,把ls /data/web/html/当作参数放在httpd命令后面, 所以ENTRYPOINT定义的命令是不会被覆盖的。
实现nginx灵活修改配置文件
需求:配置文件能够接收变量,生成配置文件,变量能够在启动容器时进行传递,nginx配置文件一般放在/etc/
[root@node1 ~]# docker image ls|grep nginx 基础镜像 nginx 1.14-alpine 14d4a58e0d2e 2 weeks ago 17.4MB 注释:把entrypoint.sh添加到bin目录下 -g 设定一个全局选项,daemon off在前台运行(daemon是运行守护进程),这是放在main段中的使用-g(global),没有指定配置文件,会自动加载/etc下nginx.conf,它会包含con.d下所有配置文件. 通过脚本entrypoint.sh读取环境变量生成配置文件,CMD是当作参数传递给ENTRYTPOINT,把CMD当作顶替自己的变量,nginx启动entrypoint.sh进程就退出 exec "$@" $@脚本所有参数,传递的参数是什么,就运行什么,exec替换当前变量 [root@node1 img3]# cat Dockerfile FROM nginx:1.14-alpine LABEL maintainer="reid <reid@kk.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"] [root@node1 img3]# cat entrypoint.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 "$@" [root@node1 img3]# chmod +x entrypoint.sh [root@node1 img3]# cat index.html <h1> reid and nginx </h1> [root@node1 img3]# docker build -t myweb:v0.3-6 ./ [root@node1 ~]# docker run --name myweb1 --rm -P myweb:v0.3-6 [root@node1 ~]# docker exec -it myweb1 /bin/sh / # cat /etc/nginx/conf.d/www.conf server { server_name 406cca3b761c; 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 406cca3b761c #如果使用localhost访问,它对应是默认主页 <h1> reid and nginx </h1>
传递环境变量
[root@node1 ~]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-6 [root@node1 ~]# 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 / # ps PID USER TIME COMMAND 1 root 0:00 nginx: master process /usr/sbin/nginx -g daemon off; #主进程id号是1,主要在entrypoint.sh脚本使用exec 8 nginx 0:00 nginx: worker process 9 root 0:00 /bin/sh 15 root 0:00 ps
问题:多环境问题,开发,测试,生产,要制作多个镜像文件,可以只做几个镜像
解决:每个镜像上可以通过entrypoint的脚本把关键需要修改的部分,如IP,PORT等,作为参数放置要修改的配置文件中,让脚本可以通过读取或者处理这些环境变量并生成配置文件,在docker run时把环境变量给它传递值
USER
用于指定运行image的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指令的程序的用户名或UID默认情况下,container的运行身份为root用户
语法: USER <UID>|<UserName> 需要注意的是,<UID>可以为任意数字,但是实践中其必须为/etc/passwd中某用户的有效UID,否则,docker run命令将运行失败
HEALTHCHECK
解释:当一个基于一个镜像启动一个容器时,这个容器的主进程没停止,容器就不会停止,假设启动一个nginx进程,一直处于运行状态,但是可能有这种情况doc_root指定错误,而它的目录也是确实存在的,nginx指定错误doc_root后,它也不会报错,可以正常运行,但是客户端访问容器是无法访问的。
docker引擎去判断docker健康与否,并不是看主进程正常提供服务,也只是看它是否正常运行的,它这种判断机制,并不是真正判断主进程是健康的,需要使用其他工具来监控,可以在本地使用wget或curl是否能加载页面,如果在外部可以提供这种工具来测试就会更加精准。
用于定义CMD command来检查容器主进程是否健康
选项: --interval=DURATION(default: 30s) --timeout=DURATION(default: 30s) --start-period=DURATION(default: 0s) #容器启动时,可能进程服务还没初始化完成,这时去检查会是报错失败的,检测可以等待一段时间,像tomcat这种部署一个jsp站点时可能需要10s --retries=N(default: 3) #重试 响应结果 0:success 1: unhealthy 2: reserved 预留,自定义 例子: HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost/ || exit 1 [root@node1 img3]# cat Dockerfile FROM nginx:1.14-alpine LABEL maintainer="reid <reid@kk.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.}:${PORT:-80}/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] [root@node1 img3]# docker build -t myweb:v0.3-7 ./ [root@node1 ~]# docker run --name myweb1 --rm -P -e "PORT=8080" myweb:v0.3-7 127.0.0.1 - - [03/Oct/2018:00:39:30 +0000] "GET / HTTP/1.1" 200 26 "-" "Wget" "-" 127.0.0.1 - - [03/Oct/2018:00:40:01 +0000] "GET / HTTP/1.1" 200 26 "-" "Wget" "-"
SHELL
默认linux是["/bin/sh","-c"],windows是["cmd","/S","/C"]
ARG
用于定义变量时使用,在docker build时使用--build-arg <varname>=<value>,适用于多种不同场景,特别是应用程序版本发生变化时。
[root@node1 img3]# cat Dockerfile FROM nginx:1.14-alpine ARG author="Jerry <jerry@kk.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"] [root@node1 img3]# docker build -t myweb:v0.3-8 ./ [root@node1 ~]# docker image inspect myweb:v0.3-8|grep maintainer "maintainer": "Jerry <jerry@kk.com>" [root@node1 img3]# docker build --build-arg author="Tom <tom@kk.com>" -t myweb:v0.3-8 ./ [root@node1 ~]# docker image inspect myweb:v0.3-8|grep maintainer "maintainer": "Tom <tom@kk.com>"
ONBUILD
用于在Dockerfile中定义一个触发器
注:自定义镜像,在dockerfile的指令有ONBUILD, 第一次成image时不会被执行,当其他用户基于这个image再做image时会被触发Dockerfile用于build映像文件,此映像文件亦可作为base image被另一个Dockerfile用作FROM指令的参数,并以之构建新的映像文件在后面的这个Dockerfile中FROM指令在build过程中被执行时,将会"触发"创建其base image的Dockerfile文件中的ONBUILD指令文件的触发器
语法: OUBUILD <INSTRUCTION> 尽管任何指令都可注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令 使用包含OUBUILD指令Dockerfile的构建的镜像应该使用特殊的标签,例如:ruby:2.0-onbuild 在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建的上下文件在缺少指定的源文件时会失败 [root@node1 img3]# cat Dockerfile FROM nginx:1.14-alpine ARG author="Jerry <jerry@kk.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}/ ONBUILD ADD https://mirrors.aliyun.com/centos/7.5.1804/os/x86_64/Packages/PackageKit-cron-1.1.5-1.el7.centos.x86_64.rpm /usr/local/src/ CMD ["/usr/sbin/nginx","-g","daemon off;"] ENTRYPOINT ["/bin/entrypoint.sh"] [root@node1 img3]# docker build -t myweb:v0.3-11 ./ Sending build context to Docker daemon 4.096kB Step 1/11 : FROM nginx:1.14-alpine ---> 14d4a58e0d2e Step 2/11 : ARG author="Jerry <jerry@kk.com>" ---> Using cache ---> 777b787523b8 Step 3/11 : LABEL maintainer="${author}" ---> Using cache ---> 62981933d4ec Step 4/11 : ENV NGX_DOC_ROOT='/data/web/html/' ---> Using cache ---> c46e3b205600 Step 5/11 : ADD index.html ${NGX_DOC_ROOT} ---> Using cache ---> b5f4ad0f4a2e Step 6/11 : ADD entrypoint.sh /bin/ ---> Using cache ---> 22f064d70dba Step 7/11 : EXPOSE 80/tcp ---> Using cache ---> a6aeaddc1674 Step 8/11 : HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/ ---> Using cache ---> be229e9ac481 Step 9/11 : ONBUILD ADD https://mirrors.aliyun.com/centos/7.5.1804/os/x86_64/Packages/PackageKit-cron-1.1.5-1.el7.centos.x86_64.rpm /usr/local/src/ ##################ONBUILD时没下载 ---> Running in e0753ce68ef0 Removing intermediate container e0753ce68ef0 ---> 23e3905b152e Step 10/11 : CMD ["/usr/sbin/nginx","-g","daemon off;"] ---> Running in 226a0a4dd5cb Removing intermediate container 226a0a4dd5cb ---> 8749100783ba Step 11/11 : ENTRYPOINT ["/bin/entrypoint.sh"] ---> Running in 8a891287e6b0 Removing intermediate container 8a891287e6b0 ---> 006c64e4b95b Successfully built 006c64e4b95b Successfully tagged myweb:v0.3-11 基于myweb:0.3-11做一个测试的镜像 [root@node1 ~]# mkdir img4 [root@node1 ~]# cd img4 [root@node1 img4]# cat Dockerfile FROM myweb:v0.3-11 RUN mkdir /tmp/test [root@node1 img4]# docker build -t test:v0.1-1 ./ Sending build context to Docker daemon 2.048kB Step 1/2 : FROM myweb:v0.3-11 # Executing 1 build trigger Downloading 5.328kB/5.328kB #有下载操作 ---> a0148b4b2e48 Step 2/2 : RUN mkdir /tmp/test ---> Running in 070410cd7f07 Removing intermediate container 070410cd7f07 ---> e9c9c945076a Successfully built e9c9c945076a Successfully tagged test:v0.1-1 [root@node1 ~]# docker run --name test1 --rm test:v0.1-1 ls /usr/local/src/ PackageKit-cron-1.1.5-1.el7.centos.x86_64.rpm