镜像的定制实际上就是定制每一层所添加的配置、文件。咱们能够把每一层修改、安装、构建、操做的命令都写入一个脚本,这个脚本就是Dockerfile。html
Dockerfile是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,所以每一条指令的内容,就是描述该层应当如何构建。mysql
接下来咱们以官方nginx镜像为例,使用Dockerfile来定制。nginx
在一个空白目录中,创建一个文本文件,并命名为Dockerfile:redis
mkdir mynginx cd mynginx touch Dockerfile
其内容为:sql
FROM nginx RUN echo '<h1> Hello,Docker!</h1>' >/usr/share/nginx/html/index.html
这个Dockerfile很简单,一共就两行。涉及到了两条指令,FROM和RUN。docker
所谓定制镜像,必定是以一个镜像为基础,在其上进行定制。基础镜像是必须指定的,而FROM就是指定基础镜像,所以一个Dockerfile中FROM是必备的指令,而且必须是第一条指令。在Docker Hub上有很是多的高质量的官方镜像,有能够直接拿来使用的服务类的镜像,如nginx、redis、mysql、tomcat等;能够在其中寻找一个最符合咱们最终目标的镜像为基础镜像进行定制。shell
若是没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操做系统镜像,如ubuntu、debian、centos、alpine等,这些操做系统的软件库为咱们提供了更广阔的扩展空间。数据库
除了选择现有镜像为基础镜像外,Docker还存在一个特殊的镜像,名为scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。json
FROM scratch ...
若是你以scratch为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将做为镜像第一层开始存在。ubuntu
对于Linux下静态编译的程序来讲,并不须要有操做系统提供运行时支持,所需的一切库都已经在可执行文件里了,所以直接FROM scratch会让镜像体积更加小巧。使用Go语言开发的应用不少会使用这种方式来制做镜像,这也是为何有人认为Go是特别适应容器微服务架构的语言的缘由之一。
RUN指令是用来执行命令行命令的。因为命令行的强大能力,RUN指令在定制镜像时是最经常使用的指令之一。其格式有两种:
shell格式:RUN <命令>
RUN echo '<h1>Hello,Docker~</h1>' > /usr/share/nginx/html/index.html
exec格式: RUN ["可执行文件",“参数1”,“参数2”]
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 RUN make -C /usr/src/redis RUN make -C /usr/src/redis install
上面咱们利用Dockerfile定制了nginx镜像,如今咱们明白了这个Dockerfile的内容,接下来咱们来构建这个镜像。
在Dockerfile文件所在目录执行:
docker build -t nginx:v3 .
从命令的输出结果中,咱们能够清晰的看到镜像的构建过程。在Step2中,RUN指令启动了一个容器782d25b7c611,执行了所要示的命令,并最后提交了这一层ba38ff665f57,随后删除了所用到的这个容器782d25b7c611。
启动构建的Nginx
docker run --name nginx-test -p 8081:80 -d nginx:v3
如图所示
格式:
COPY指令将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置。好比:
COPY package.json /usr/src/app/
<源路径>能够是多个,甚至能够是通配符,如:
COPY hom* /mydir/ COPY hom?.txt /mydir/
ADD指令和COPY的格式和性质基本一致。可是在COPY基础上增长了一些功能。好比<源路径>能够是一个URL,这种状况下,Docker引擎会试图去下载这个连接的文件放到<目标路径>去。
在Docker官方的Dockerfile最佳实践文档中要求,尽量的使用COPY,所以COPY的语义很明确,就是复制文件而已,而ADD则包含了更复杂的功能,其行为也不必定很清晰。最适合使用ADD的场合,就是所说起的须要自动解压缩的场合。
所以在COPY和ADD指令中选择的时候,能够遵循这样的原则,全部的文件复制均使用COPY指令,仅在须要自动解压缩的场合使用ADD。
CMD指令的格式和RUN类似,也是两种格式:
Docker不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,须要指定所运行的程序及参数。CMD指令就是用于指定默认的容器主进程启动命令的。
ENTRYPOINT的目的和CMD同样,都是在指定容器启动程序及参数。ENTRYPOINT在运行也能够替代,不过比CMD要略显繁琐,须要经过docker run的参数 --entrypoint来指定。
当指定了ENTRYPOINT后,CMD的含义就发生了改变,再也不是直接的运行其命令,而是将CMD的内容做为参数传给ENTRYPOINT指令,换句话说实际执行时,将变为:
<ENTRYPOINT>"<CMD>"
格式有两种:
这个指令很简单,就是设置环境变量而已,不管是后面的其它指令,如RUN,仍是运行时的应用,均可以直接使用这里定义的环境变量。
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet" $VERSION #使用环境变量
下列指令能够支持环境变量展开:ADD、COPY、ENV、EXPOSE、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD。
格式:
构建参数和ENV的效果同样,都是设置环境变量。所不一样的是,ARG所设置的构建环境的环境变量,在未来容器运行时是不会存在这些环境变量的。可是不要所以就使用ARG保存密码之类的信息,所以docker history仍是能够看到全部值的。
Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值能够在构建命令docker build中用 --build-arg <参数名>=<值>来覆盖。
格式为:
容器运行时应该尽可能保持容器存储层不发生写操做,对于数据库须要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile中,咱们能够事先指定某些目录挂载为匿名卷,这样在运行时若是用户不指定挂载,其应用也能够正常运行,不会向容器存储层写入大量数据
VOLUME /data
这里的/data目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。固然,运行时能够覆盖这个挂载设置。
好比:
docker run -d -v mydata:/data xxxx
在这行命令中,就使用了mydata这个命名卷挂载到了/data这个位置,替代了Dockerfile中定义的匿名卷的挂载配置。
格式为EXPOSE <端口1>[<端口2>...]。
EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会由于这个声明应该就会开启这个端口的服务。
在Dockerfile中写入这样的声明有两个好处:
格式为WORKDIR <工做目录路径>
使用WORKDIR指令能够来指定工做目录(或者称为当前目录),之后各层的当前目录就被改成指定的目录,如该目录不存在,WORKDIR会帮你创建目录。
以前提到一些初学者常犯的错误是把Dockerfile等同于Shell脚原本书写,这种错误的理解还可能会致使出现下面这样的错误:
RUN cd /app RUN echo "hello">world.txt
若是将这个Dockerfile进行构建镜像运行后,会发现找不到 /app/world.txt文件。
缘由
在Shell中,连续两行是同一个进程执行环境,所以前一个命令修改的内存状态,会直接影响后一个命令。
而在Dockerfile中,这两行RUN命令的执行环境根本不一样,是两个彻底不一样的容器。这就是对Dockerfile构建分层存储的概念不了解致使的错误。
每个RUN都是启动一个容器、执行命令、而后提交存储层文件变量。第一层RUN cd /app的执行仅仅是当前进程的工做目录变量,一个内存上的变化而已,其结果不会形成任何文件变动。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更彻底不要紧,天然不可能继承前一层构建过程当中的内存变化。
所以若是须要改变之后各层的工做目录的位置,那么应该使用WORKIDR指令。
格式:USER <用户名>
USER指令和WORKDIR类似,都是改变环境状态并影响之后的层。WORKDIR是改变工做目录,USER则是改变以后层的执行RUN,CMD以及ENTRYPOINT这类命令的身份。
当前,和WORKDIR同样,USER只是帮助你切换到指定用户而已,这个用户必须是事先创建好的,不然没法切换。
RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN ["redis-server"]
格式:
HEALTHCHECK指令是告诉Docker应该如何进行判断容器的状态是否正常,这是Docker1.12引入的新指令。经过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。
一个镜像指令HEALTHCHECK指令后,用其启动容器,初始状态会为starting,在执行健康检查成功后变为healthy,若是连续必定次数失败,则会变为unhealthy。
HEALTHCHECK支持下列选项:
为了帮助排障,健康检查命令的输出(包括stdout以及stderr)都会被存储于健康状态里,能够用docker inspect来查看。
格式: ONBUILD <其它指令>
ONBUILD是一个特殊的指令,它后面跟的是其它指令,好比RUN,COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当之前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile中的其它指令都是为了定制当前镜像而准备的,惟有ONBUILD是为了帮助别人定制本身而准备的。
Docker还提供了docker load和docker save命令,用以将镜像保存为一个tar文件,而后传输到另外一个位置上,再加载进来。这是在没有Docker Registry时的作法,如今已经不推荐,镜像迁移应该直接使用Docker Registry,不管是直接使用Docker Hub仍是使用内网私有Registry均可以。
例如:保存nginx镜像
docker save nginx|gzip > nginx-latest.tar.gz
而后咱们将nginx-latest.tar.gz文件复制到了另外一个机器上,再次加载镜像:
docker load -i nginx-latest.tar.gz