ps:接上篇

利用 Dockerfile 定制镜像

1、Dockerfile概述

  镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是Dockerfile    Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建.    Dockfile是由一行行命令语句组成,并且迟滞以#开头的注释行;一般而言,Dockerfiel分为四部分:1.基础镜像信息 ;2.维护者信息;3.镜像操作指令;4.容器启动时指令,例如

配置第一个dockerfile文件
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
内容:
# this is dockerfile uses the nginx images
# VERSION 1 - EDITON 1
# Author: docker

#base image the use 
FROM nginx

# maintainer: docker_user
MAINTAINER docker_user docker_user@docker.com

RUN echo ' hello,docker! ' > /usr/share/nginx/html/index.html

1.1、FROM 指定基础镜像

  所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令   除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像 FROM scratch   如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。   不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如swarm 、 coreos/etcd 。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go是特别适合容器微服务架构的语言的原因之一

1.2、RUN 执行命令

  RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:   shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。   例如:RUN echo "123" > /tmp/1.txt

  exec 格式: RUN ["可执行文件", "参数1", "参数2"] ,这更像是函数调用中的格式

  Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像.   Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过127 层

  因此,这里没有使用很多个 RUN对应一一不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来, 在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建

比如 RUN yum -y install nginx \
				&& echo "hello docker" > /usr/share/nginx/html/index.html\
				&& RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf \
				      CMD /usr/bin/nginx

   Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯

1.3、构建镜像

  语法: docker build [选项] <上下文路径/URL/->

[root@docker mynginx]# docker build -t nginx:v4 .
	Sending build context to Docker daemon  2.048kB
	Step 1/2 : FROM nginx
	 ---> 7f70b30f2cc6
	Step 2/2 : RUN echo ' hello,docker! ' > /usr/share/nginx/html/index.html
	 ---> Running in 065c3e52b540
	Removing intermediate container 065c3e52b540
	 ---> 3d80dfa73097
	Successfully built 3d80dfa73097
	Successfully tagged nginx:v4

[root@docker mynginx]# docker images
	REPOSITORY          TAG                 IMAGE ID            CREATED              SIZE
	nginx               v4                  3d80dfa73097        About a minute ago   109MB
			 
运行   以后台方式运行 名称为test 端口映射80:80
$ docker run -dit --name test -p 80:80 nginx:v4
	```		

![](https://s1.51cto.com/images/blog/201804/02/33ce31a47ca15764f1c0b4eb5a134da8.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
	

第一个例子,使用centos基础镜像安装nginx环境,从而创建一个新的nginx

	
	
## 1.4、镜像构建上下文(Context)
>   build 构建时最后带的那个. 并不是指定Dockerfile所在的路径,这样理解是不正确的,如果对应上面的命令格式,这其实是在指定上下文路径,那么什么是上下文呢?
		 
  首先我们要理解**docker build 工作原理**: Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 DockerRemote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
	
  而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
	
  这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件
	![](https://s1.51cto.com/images/blog/201804/02/fb74fce5146a65779b5664a5d6f9b778.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
	
![](https://s1.51cto.com/images/blog/201804/02/0455595d9ffc42be3b2d01dc33862030.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
  一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个.dockerignore ,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的,如果在根目录下直接build -t name .的话 会将整个系统都打包给docker容器.

  直接指定**  . ** 构建的时候默认行为就是直接指定的Dockerfile文件,当然也可以指定别的文件做为构建参数, 比如 -f test.txt, 当然大家最好还是习惯性使用默认文件名Dockerfile。
	
## 1.5、Docker build的其它用法
1. 直接使用Git repo进行构建 

使用该方法老报错,我们可以给它先下载到本地,然后再build $ docker build https://github.com/twang2218/gitlab-ce-zh.git#:10.6

$ git clone https://github.com/twang2218/gitlab-ce-zh.git
$ docker build -t name .
  这行命令指定了构建所需的 Git repo,并且指定默认的 master 分支,构建目录为 /8.14/ ,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。
	
2、用给定的tar压缩包构建

[root@docker ~]# mkdir mytar [root@docker ~]# cd mytar/ #内容如下 FROM nginx RUN echo "test tar images" > /usr/share/nginx/html/index.html
&& echo "test" > /tmp/1.txt

注意这里得直接打包 $ tar mytar.tar.gz Dockerfile docker build -t test/nginx:v4.1 - < mytar.tar.gz Sending build context to Docker daemon 208B Step 1/2 : FROM nginx ---> 7f70b30f2cc6 Step 2/2 : RUN echo "test tar images" > /usr/share/nginx/html/index.html && echo "test" > /tmp/1.txt ---> Running in a8f2b14e8576 Removing intermediate container a8f2b14e8576 ---> d5398275b4d7 Successfully built d5398275b4d7 Successfully tagged test/nginx:v4.1

$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE test/nginx v4.1 d5398275b4d7 4 seconds ago 109MB

   如果发现标准输入的文件格式是 gzip 、 bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。

 # 2、COPY 复制文件
>    和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用

语法:
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]

比如:
   COPY package.json /usr/src/app/
也可以是多个,甚至是通配符
	 COPY home* /usr/src/app/
	 COPY hom?.txt /usr/src/app/
   <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(**工作目录可以用 WORKDIR 指令来指定**)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录

例如: Dockerfile内容 FROM centos WORKDIR /usr/local/app/ COPY a* ./

$ docker build -t filetest/test . Sending build context to Docker daemon 3.584kB Step 1/3 : FROM centos ---> 2d194b392dd1 Step 2/3 : WORKDIR /usr/local/app/ Removing intermediate container 7fc63b9b6c51 ---> f5bd6a0a5ba8 Step 3/3 : COPY a* ./ ---> 738d1c5a877a Successfully built 738d1c5a877a Successfully tagged filetest/test:latest

[root@docker mytar]# docker run -it --rm filetest/test /bin/bash [root@b043e7420357 app]# ls a ab ac

   此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候。

# 3、ADD 更高级的复制文件
> ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

   比如 <源路径> 可以是一个URL,docker会试图下载这个链接的文件放到<目标文件中去>,权限默认为600,如果想调整需要在增加一层RUN,另外如果是压缩文件还需要额外增加一层RUN进行解压缩, 不如直接使用wget 处理权限来的直接,因此这个功能其实不实用,而且不推荐使用。
   如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip , bzip2 以及 xz 的情况下, ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令
   因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY 指令,仅在需要自动解压缩的场合使用 ADD

 # 4、CMD
>    启动容器时默认执行的命令,每个Dockerfile只能有一条CMD命令,如果指定多条,只有最后一条会被执行。

CMD 指令的格式和 RUN 相似,也是两种格式:
shell 格式: CMD <命令>
exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
参数列表格式: CMD ["参数1", "参数2"...] 。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
   之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。 CMD 指令就是用于指定默认的容器主进程的启动命令的。
   在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 " ,而不要使用单引
   如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:
 CMD echo $HOME
在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

**注意:**Docker 不是虚拟机,容器中的应用都应该以前台执行,对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西

例如: 使用from run cmd copy 制造一个nginx镜像 先下载一个nginx yum源到构造的目录下 http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

[root@dockers nginx]# cat Dockerfile

this Docker file use centos images

Version 1

Author: xiong

Base image to used

FROM centos

COPY nginx.repo /etc/yum.repos.d/

configure nginx start to Reception

RUN yum install -y nginx
&& echo "hello my frist dockerfile" > /usr/share/nginx/html/index.html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

构建一个镜像 名称为centos/nginx:v1 [root@dockers nginx]# docker build -t centos/nginx:v1 .

查看镜像是否构建完成 [root@dockers nginx]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE centos/nginx v1 68c6fc13188a 23 seconds ago 302MB

测试并启动镜像 [root@dockers nginx]# docker run -dit -p 80:80 --name web centos/nginx:v1

![](https://s1.51cto.com/images/blog/201804/04/33fcd667d04e3a8708e440c7159611e9.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

 # 5、ENTRYPOINT
>    指定镜像的默认入口命令,该入口会在启动容器时做为根命令执行,所有传入值作为该命令的参数。
	 
ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。

exec语法 ENTRYPOINT ["executable","param1","param2"] (exec调用执行); shell语法ENTRYPOINTcommand param1 param2 (shell执行)


   ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数,当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变,不再是直接的运行其命令,而是将CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

例如:使用CMD查询自己本地的IP [root@dockers curl]# cat Dockerfile FROM centos COPY local.repo /etc/yum.repos.d/ RUN yum -y install curl
&& rm -rf /etc/yum.repos.d/local.repo CMD [ "curl","-s", "https://ip.cn" ]

构建它 [root@dockers curl]# docker run centos/curl:v1

运行 [root@dockers curl]# docker run centos/curl:v1 当前 IP:1.1.1.1 来自:北京市 xx

   看起来好像可以直接当成命令使用,但是命令总归有参数,直接运行上面命令,它在构建完成最后一步的时候已经处理完成了,所以增加参数也没有任何效果

如果直接这样运行,那么镜像中指定的CMD命令就被覆盖了 docker run centos/curl curl -i https://ip.cn

而使用ENTRYPOINT就可以解决这个问题 [root@dockers entry]# cat Dockerfile FROM centos COPY local.repo /etc/yum.repos.d/ RUN yum -y install curl
&& rm -rf /etc/yum.repos.d/local.repo ENTRYPOINT [ "curl","-s", "https://ip.cn" ]

执行也是同样的效果了 docker run enrty 当前 IP:1.1.1.1 来自:北京市 xx

这里我们来查询Http头部信息 [root@dockers curl]# docker run enrty -i HTTP/1.1 200 OK Date: Wed, 04 Apr 2018 06:38:35 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: __cfduid=db95eb1f0c8b7f59fe0bba7e8b8979ae31522823915; expires=Thu, 04-Apr-19 06:38:35 GMT; path=/; domain=.ip.cn; HttpOnly Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Server: cloudflare CF-RAY: 4061c5e2ecb52882-SJC


   这是因为当存在 ENTRYPOINT 后, CMD 的内容将会作为参数传给ENTRYPOINT ,而这里 -i 就是新的 CMD ,因此会作为参数传给 curl
   **这里就相当于ENTRYPOINT exec语法  ENTRYPOINT ['exectable','param1','param2']
	  executable 命令不动,将param1 param2的值 做为可传递参数进行修改。**

docker run enrty -i http://www.baidu.com HTTP/1.1 200 OK Date: Wed, 04 Apr 2018 06:39:40 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Connection: keep-alive Set-Cookie: __cfduid=d1ba6914d20dc0bXXf785572c21994a551522823980; expires=Thu, 04-Apr-19 06:39:40 GMT; path=/; domain=.ip.cn; HttpOnly Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" Server: cloudflare CF-RAY: 4061c7732e8392ee-SJC

 #  6、ENV环境变量
>       指定环境变量,在镜像生成过程中会被后续RUN指令使用 
> 			格式:ENV<KEY> <VALUE> 或 ENV <KEY>=<value>....

例如安装一个JDK [root@dockers jdk]# cat Dockerfile FROM centos COPY jdk-8u77-linux-x64.rpm /usr/src/app/ RUN jdk_dir=/usr/src/app/jdk-8u77-linux-x64.rpm
&& rpm -ivh $jdk_dir
&& rm -rf $jdk_dir ENV JDK_HOME /usr/java/default/ ENV PATH $JDK_HOME/bin:$PATH CMD ["java","-version"]

ENV 如果加了变量那么生效就一定是 ENV PATH $key:$PATH

最后测试 [root@dockers jdk]# docker run jdk java version "1.8.0_77" Java(TM) SE Runtime Environment (build 1.8.0_77-b03) Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)


 # 7、ARG
> 		指定镜像内使用的参数(例如版本号信息等)

   构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是, ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
   Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build 中用 --build-arg <参数名>=<值> 来覆盖。
	 

来个例子说明: [root@dockers nginx]# cat Dockerfile FROM centos ARG nginx=1.11 COPY nginx.repo /etc/yum.repos.d/ RUN yum install -y nginx
&& echo "hello my frist dockerfile" > /usr/share/nginx/html/index.html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

构建一个镜像 [root@dockers nginx]# docker build -t testnginx .

查看历史信息 [root@dockers nginx]# docker history testnginx IMAGE CREATED CREATED BY SIZE COMMENT 3f54a75772f3 About a minute ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
598c5676eeed About a minute ago /bin/sh -c #(nop) EXPOSE 80 0B
17ec99c29593 About a minute ago |1 nginx=1.11 /bin/sh -c yum install -y ngin… 107MB
1e03021d4d43 About a minute ago /bin/sh -c #(nop) COPY file:c33f426ae65e9d11… 113B
88a51545ba05 About a minute ago /bin/sh -c #(nop) ARG nginx=1.11 0B
2d194b392dd1 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL name=CentOS Base Im… 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:8d83f3e2c14f39e7f… 195MB

   ARG感觉没有实际作用,只能指定构建时的版本信息,一次只能指定一个key=value,并且还占用一层资源
	 
 # 8、定义匿名卷
>   创建数据卷挂载点,可以从本地主机或其它容器挂载数据卷,一般用来存放数据库和需要保存的数据等,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。   格式为: VOLUME <路径>

$ cat Dockerfile FROM centos VOLUME /data

$ docker build -t store .

$ docker run -it store container# df -Th | grep /data /dev/mapper/centos-root xfs 37G 2.9G 34G 8% /data container# touch containessss

查找容器与本地关连的卷 $find / -name containessss /var/lib/docker/volumes/3a17321d0a8a78f2b3c7709b9302bf43bb036c42da2cfc87a5dd1c71e835132b/_data/containessss


# 9、EXPOSE
>    EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。
>    在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

   **注意**:EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

此处还是使用nginx做为例子声明80端口 [root@dockers nginx]# vim Dockerfile FROM centos COPY nginx.repo /etc/yum.repos.d/ RUN yum install -y nginx
&& echo "EXPOSE 80 , dockerfile from nginx " > /usr/share/nginx/html/index.html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]

构建镜像 [root@dockers nginx]# docker build -t test/nginx:v2.0 .

查看镜像 [root@dockers nginx]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE test/nginx v2.0 120eaeae17d4 About a minute ago 302MB

指定一个随机端口 [root@dockers nginx]# docker run -dit -p :80 --name tnginx test/nginx:v2.0

使用 docker container ls查看容器,最后访问

![](https://s1.51cto.com/images/blog/201804/06/aefa8a43eff9d45f95e221af9130911c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)

# 10、WORKDIR
>    使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录,因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。
>    格式为 WORKDIR <工作目录路径>

创建前一个要先创建一个空目录,工作目录/tmp/ 复制当前上下文中的testfile.txt文件到容器的/tmp目录下 [root@dockers work]# cat Dockerfile FROM centos WORKDIR /tmp COPY testfile.txt ./ CMD ls /tmp

启动一个容器,因为有CMD执行最后一个命令,显示/tmp目录下的文件 [root@dockers work]# docker run -it --name works work ks-script-IAlIsB testfile.txt yum.log

 # 11、USER指定当前用户
>    USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及 ENTRYPOINT 这类命令的身份
>    格式: USER <用户名>
	 
**注意:**USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换

这里用文档中的例子来说明 创建用户并切换redis用户来启动redis-server服务 RUN groupadd -r redis && useradd -r -g redis redis USER redis RUN [ "redis-server" ]

第二个例子. RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/ gosu-amd64"
&& chmod +x /usr/local/bin/gosu
&& gosu nobody true

设置 CMD,并以另外的用户执行

CMD [ "exec", "gosu", "redis", "redis-server" ]

# 12、HEALTHCHECK 健康检查
>     HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12引入的新指令。
> 格式:
>     HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
>     HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

     当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting ,在HEALTHCHECK 指令检查成功后变为 healthy ,如果连续一定次数失败,则会变为unhealthy。
    HEALTHCHECK 支持下列选项:
     --interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
     --timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
     --retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3次。

**注意: **HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效

空目录下创建一个Dockerfile FROM centos

COPY nginx.repo /etc/yum.repos.d/ RUN groupadd -g 1000 nginx
&& useradd -g 1000 -u 1001 nginx
&& yum -y install nginx

每10秒检查一次,超时时长5秒就退出

HEALTHCHECK --interval=10s --timeout=5s
CMD curl -fs http://127.0.0.1 || exit 1 EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

构建并运行并查看状态,一开始是healthy状态,然后理性成starting说明就启动了,失效了就会改变成 unhealthy状态 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e7982e3ec394 nginx:v3 "nginx -g 'daemon of…" 2 seconds ago Up 2 seconds (health: starting) 0.0.0.0:80->80/tcp Tnginx

最后可以使用 docker inspect 查看容器的状态 使用python json模块打印 docker inspect -f '{{json .State.Health}}' Tnginx | python -m json.tool { "FailingStreak": 0, "Log": [ { "End": "2018-04-07T04:39:29.265404529+08:00", "ExitCode": 0, "Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\nWelcome to nginx!\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a rel="nofollow" href="http://nginx.org/">nginx.org</a>.<br/>\nCommercial support is available at\n<a rel="nofollow" href="http://nginx.com/">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n", "Start": "2018-04-07T04:39:29.177126247+08:00" }, ], "Status": "healthy" }


 # 13、ONBUILD 为他人做嫁衣裳
>      ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
>      Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的
>      格式: ONBUILD <其它指令> 

正常镜像写法: [root@dockers onbuild]# cat Dockerfile FROM centos COPY nginx.repo /etc/yum.repos.d/ RUN yum -y install nginx CMD ["nginx", "-g", "daemon off;"]

构建,运行,查看 [root@dockers onbuild]# docker run -dit -p 80:80 --name TestWeb1 web1 96c3a74da2d9ea63664d7a47c298747a5bb2679f1812a74a2d6d235f4989f624 [root@dockers onbuild]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 96c3a74da2d9 web1 "nginx -g 'daemon of…" 2 seconds ago Up 1 second 0.0.0.0:80->80/tcp TestWeb1

     但如果很多项目都需要相同的项目只是Html文件不一样哩,那么每次都需要再生成一次Dockerfile文件挺麻烦,这里就可以使用 ONBUILD了。

[root@dockers onbuild]# cat Dockerfile FROM centos ONBUILD COPY nginx.repo /etc/yum.repos.d/ ONBUILD RUN yum -y install nginx ONBUILD CMD ["nginx", "-g", "daemon off;"]

这时构建一下这个Dockerfile生成一个镜像 onbuilds [root@dockers onbuild]# docker build -t onbuilds .

我们在创建一个目录生成一个Dockerfile [root@dockers ontwo]# cat Dockerfile FROM onbuilds

构建这个文件提示nginx.repo不存在,需要将文件都复制在这个目录下 [root@dockers ontwo]# docker build -t onbuild2 . Sending build context to Docker daemon 2.048kB Step 1/1 : FROM onbuilds

Executing 3 build triggers

COPY failed: stat /var/lib/docker/tmp/docker-builder748734679/nginx.repo: no such file or directory

正确的构建方式: [root@dockers ontwo]# docker build -t onbuild2 . Sending build context to Docker daemon 3.072kB Step 1/1 : FROM onbuilds

Executing 3 build triggers

---> Running in fafe6903070c Loaded plugins: fastestmirror, ovl ..................... Successfully built 7f1a2af8e80d Successfully tagged onbuild2:latest

     当在各个项目目录中,用这个只有一行的 Dockerfile 构建镜像时,之前基础镜像的那三行 ONBUILD 就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 yum以及copy ,生成应用镜像。

[dockerfile 最佳实验手册](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#env)
[dockerfile 官方文档](https://docs.docker.com/engine/reference/builder/#usage)