DockerFile语法
DockerFile在我理解就是可以将所需要构建镜像的功能、组件都天前配置好,然后直接生成一个Image,而不是先生成镜像,再通过修改容器的方法来生成最终需要的镜像。
镜像的定值实际上就是定值每一层所需要添加的配置、文件,将每一层修改、安装、构建、操作的命令都写入一个脚本,最后使用脚本来构建镜像。Dockerfile的每一条指令(instruction)构建一层,描述了该层该如何构建。
Dockerfile的构建
docker build [选项] <上下文路径/URL/->
Dockerfile上下文(context)
一般情况下构建镜像docker build 最后都会有一个.,表示当前目录,有些人会认为这是指定Dockerfile的所在路径,但是其实是在指定上下文路径。在构建镜像的时候,docker build得知上下文路径后,会将该路径下所有东西打包,然后上传给Docker 引擎,Docker引擎就回获得构建镜像所需要的一切文件。
需要访问打包的文件,必须以./开头,并不是指当前路径,而是上下文环境。应该讲Dockerfile置于一个空目录下,或者项目根目录下,如果该上下文环境有不希望传给Docker引擎的,可以使用.gitignore一样的语法写一个.dockerignore来剔除不需要的环境。
其他Docker builc的用法
直接使用Git repo构建
docker build https://github.com/twang2218/gitlab-ce-zh.git#:8.14
用给定的tar包构建
docker build build.tar.gz
DockerFile指定
FROM 指定基础镜像
必选,而且是第一条指令,在制定基础镜像。
FROM nginx
有一个特殊的镜像名为scratch,是虚拟的概念,表示一个空白的镜像,意味着不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在,使用Go预研开发的应用很多会使用这种方式来制作镜像。
FROM scratch
RUN 执行命令
用于执行命令,相当于命令行,格式有两种:
shell格式:RUN <命令>,相当于在命令行中写命令。
exec格式:RUN["可执行文件","参数1", "参数2"],相当于函数调用中的格式。
RUN echo 'hello world' > /usr/share/nginx/html/index.html
Dockerfile会将每一个命令视为一层,所以一般使用RUN都是结合&&来将多个RUN视为一层,避免产生臃肿、多层的镜像,增加构建时间而且容易出错。
FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps
末尾使用了\的方式,以及行首#进行注释的格式,可以让维护、更为容易。另外,可以在这一组命令最后添加清理工作的命令,删除为了编译固件所需要的软件,并且还清理了qpt缓存文件,可以有效避免镜像臃肿
COPY 复制文件
格式:
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
与RUN命令类似,有shell以及函数式调用,将从上下文目录中的源路径复制到一个新的镜像内的目标路径。源路径可以是多个,可以使用通配符,需要满足Go语言的filepath Match规则。
目标路径可以使容器内的绝对路径,也可以是相对路径,目标路径不需要手动创建,Docker会自动创建确实的目录。
需要注意的是拷贝过去的文件会保留源文件的特性,比如读写权限、文件变更时间等。
ADD 高级的复制文件
ADD可以理解为一个升级版本的COPY,源路径可以是一个URL(下载之后的文件权限为600);如果源路径是一个tar压缩文件、格式为gzip、bzip2以及xz的情况下,ADD指令可以自动解压该文件到目标路径中。所以这个命令一般用于解压文件更多。
Docker官方建议尽可能使用COPY,因为ADD语音不明确,并且ADD会另镜像构建缓存失效,从而可能另镜像构建的比较缓慢。因此仅在需要自动解压的情况下使用ADD。
CMD 容器启动命令
和RUN相似,但是CMD、ENTERPOINT、HEALTHCHECK只能出现一次,写了多个只会使用最后一个。两种格式:
shell 格式: CMD <命令>
exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
参数列表格式: CMD ["参数1", "参数2"...] 。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
我们知道Docker不是虚拟机,而容器就是一个进程而已,那么在启动容器的时候就需要指定容器运行的程序以及参数,CMD指令就是用于指定默认的容器主进程的启动命令的。
在ubuntu默认CDM是/bin/bash,直接docker run -it会直接进入bash,也可以通过手动docker run -it imageid cat /etc/os-release的方式替换默认CMD,打印系统版本信息。
一般推荐exec的指令格式,会被解析为JSON数组,一定使用双引号而不是单引号。如果直接使用shell格式的话会被包装为sh -c的参数形式执行
CMD echo $HOME
CMD ["sh","-c","echo $HOME"]
这也就是我们为什么可以使用环境变量的原因,因为这些环境变量会被shell进行解析处理。
另外Docker不是虚拟机,容器中没有后台执行服务的概念,不能使用upstart/systemd去执行后台服务。比如nginx服务
CMD service nginx start
其实会启动之后就立刻推出,甚至systemctl命令根本无法执行。容器中启动进程就是容器应用进程,容器就是为了主进程而存在的,主进程退出容器也就失去了意义。nginx启动应该为:
CMD ["nginx","-g","daemon off;"]
因为service nginx start会被理解为CMD["sh","-c""service nginxstart"],因此主进程是sh,当service nginx start执行完,sh也就结束了,sh作为主进程推出就会导致容器推出。
ENTERPOINT 入口点
作用和CMD类似,都是在容器启动程序及参数,ENTERPOINT在运行时候也可以替代,但是比CMD要繁琐,需要通过docker run的参数 -entrypoint来指定。当指定了ENTERPOINT之后,CMD就不再是直接的运行命令,而是将CMD的内容作为参数传递给ENTERPOINT指令,相当于:
<ENTERPOINT> "<CMD>"
那么有什么用呢?
让镜像变成命令一样使用
有些情况下构建的镜像我们可以当做一个应用直接使用命令行调用,但是使用CMD的构建是写死的。比如获取公网IP:
FROM ubuntu:16:04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD ["curl","-s","http://ip.cn"]
docker build -t myip .构建镜像之后,只需要执行docker run myip就可以查看当前的公网ip。但是如果我们现在想修改一下使用别的参数,比如希望显示http头信息,需要加上-i参数,那么使用docker run myip -i是一定不可行的。因为跟在镜像名后边的command运行时候会替换CMD默认值,相当于直接执行-i,所以绝对不会成功,这个时候就可以使用ENTERPOINT。
FROM ubuntu:16:04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
然后直接调用docker run myip -i就可以正常执行,相当于curl -s http://ip.cn -i,CMD的内容会作为参数传递给ENTERPOINT,而这里个-i就是新的CMD,和ENTERPOINT组合成新的CMD。
ENV 环境变量
只是设置环境变量,无论是后边其他的任何命令RUN等都可以直接使用这里的环境变量,使用{ENV_NAME}来引用,格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>
比如:
ENV NODE_VERSION =7.2.0 DEBUG=on \
NAME="lzy"
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"
以下的指令支持环境变啦ing的展开:
ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD
通过环境变量,我们可以让一份Dockerfile制作更多的镜像,只需要使用不同的环境变量即可。
ARG 构建参数
与ENV想类似,设置环境变量,但是ARG的环境变量在最终声称的容器中不会存在,但是不存在不意味着可以保存密码之类的东西,因为在docker history中也会看到。该值所设置的环境变量可以在docker build时候shiyong --build-arg <参数名>=<值>来覆盖。格式:
ARG <参数名>[=<参数值>]
VOLUME定义匿名卷
如果在容器中进行数据输出,那么最终数据不会被保存,对于需要动态保存数据的应用,应该保存与卷volume中,为了避免用户忘记动态文件所保存目录挂载为卷,可以实现指定默写目录挂载为匿名卷,如果用户不指定挂载,应用也可以正常运行,不会向容器存储层写入大量数据。格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
#比如
COLUME /data
上述的/data就会在运行时候自动挂载为匿名卷,运行时也可以覆盖这个挂载设置
docker run -d -v mydata:/data xxxx
上述就是使用了mydata目录来挂载到了/data位置,替代了Dockerfile中匿名卷的挂载配置。
EXPOSE 声明端口
生命运行时候容器提供服务端口,只是一个生命,在运行时候不会因为这个生命就会开启这个端口。主要作用是帮助镜像使用者理解这个镜像服务的守护端口,方便配置映射。另外就是使用随机端口映射时候docker run -P可以自动随机映射expose的端口。格式为:
EXPOSE <端口1> [<端口2>...]
expose和运行时候指定-p 宿主端口:容器端口不同,-p用户映射宿主端口和容器端口,将容器对应端口公开给外界访问。expose仅仅是生命容器打算使用什么端口而已,并不会自动在宿主机进行端口映射。
WORKDIR 指定工作目录
首先,Dockerfile中不同于执行shell命令,每一层都是不同的运行环境如下的命令一定会报/app/world.txt不存在的错误。
RUN cd /app
RUN echo "hello" > world.txt
第一层的cd /app仅仅是当前进程的工作目录变更,一个内存上的变化而已,结果不会造成任何文件变更。第二层时候是一个全新的容器,自然不可能集成前一层的内存变化,所以如果想是想cd /app的效果,必须使用WORKDIR指令来更改工作空间,并且如果目录不存在,WORKDIR会帮我们建立目录。
USER 指定当前用户
USER <用户名>
USER和WORKDIR类似,都会改变环境状态并影响到以后的层。WORKDIR是改变工作目录,USER是改变之后层的执行RUN、CMD、ENTERPONIT等命令的身份,用户必须提前建立好,否则无法切换
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN ["redis-server"]
HEALTHCHECK 健康检查
HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
告知Docker如何判断容器的状态是否正常,于1.12版本引入。当镜像制定了healthcheck指令,指定一行命令之后,勇气启动容器,初始状态为starting,在HEALTHCHECK指令检查陈宫之后变为healthy,如果连续一定次数失败,则会变成unhealthy。HEALTHCHECK支持的选项有:
interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3 次。