构建一个自定义的Docker镜像必定离不开Dockerfile,有了这个文件我们就可以通过docker image build命令来构建我们自己的镜像,所以我们从先从制作一个简单的镜像开始。
简单的Dockerfile:
首先我们要先建立一个目录,用于存放Dockerfile,这个目录就是构建镜像的环境,该环境称作构建上下文,在构建镜像的时候会将该上下文和该上下文中的文件和目录上传到Docker守护进程,这样守护进程就可以访问这里的内容,只有这样才能把用户想封装到镜像的东西添加到镜像中,包括文件、代码或者其他数据。
我这里在/work下建立一个叫做buildContext的目录,这个目录随意任何位置任何名称都可以。
编辑Dockerfile文件
创建镜像
1
|
docker image build -t 仓库/标签:版本号 .
|
查看刚刚构建的镜像
1
|
docker image
history
ID
|
现在我们说一下docker build命令中的参数
参数 | 说明 |
--build-art=[] | 设置镜像创建时的变量 |
--compress | 是否在构建过程使用gzip进行压缩,默认为否 |
--cpu-shares | 设置CPU使用权重,默认为0 |
--cpu-period | 设置CPU的CFS(完全公平调度算法)周期,默认为0,这个可以调整是否可以给该容器分配更多或更少的CPU时间。 |
--cpu-quota | 设置CPU的CFS配额,默认为0 |
--cpuset-cpus | 指定使用哪个CPU运行该容器,从0开始表示第一个CPU或者核心 |
--cpuset-mems | 设置指定使用的内存ID,一根物理内存在系统上是有编号的,就跟CPU一样,这个参数设置该容器使用哪根物理内存条。使用下面的命令查看: dmidecode -t 17 | less 其中Handle后面的表示内存ID,插上内存的插槽在Size中有值,没插的显示“No Module Installed”。 |
--disable-content-truse | 忽略校验,默认开启 |
-f | 设置使用哪个dockerfile,如果使用这个参数指定文件位置那么该Dockerfile的名称可以是任意的,但是必须在构建上下文之中。之前演示构建的命令中最后是一个“.”,这个表示到本地当前目录(也叫做构建上下文路径)去找dockerfile,这时候你的Dockerfile文件名称就必须是“dockerfile”,而且它会读取该目录以及其子目录的Dockerfile,并将所有的内容发送给docker守护进程来创建镜像,所以一般建议构建上下文或者也叫做内容路径为空目录。如果你不使用构建上下文路径下的Dockerfile,那么就要使用-f参数来指定文件。 |
--force-rm | 是否允许强制删除容器,默认为false |
--isolation | 容器隔离技术 |
--lable=[] |
为镜像设置一个标签 |
-m,--memory | 设置内存限制,如果仅指定这一个参数,那么实际限制会是这个值的2倍,因为单纯的设置这个值其实是1倍给内存1倍给SWAP。如果要想固定就要和下面的--memory-swap一起使用,不但指定内存限制也要指定SWAP限制。如果使用-m参数遇到下面的错误: "Your kernel does not support swap limit capabilities.memory limit without swap" 那就需要修改/etc/default/grub文件 GRUB_COMMLINE_LINUX="cgroup_enable=memory swapaccount=1" 然后grub并且重启计算机。 |
--memory-swap |
设置容器的SWAP限制 |
--network | 设置网络方式 |
--no-cache |
构建镜像时不适用缓存,默认为false。因为构件时,每一步都会提交为一个镜像,所以Docker会把之前的看做缓存,假设构建分成5步,前三步都不需要修改,那么Docker就会将前3步作为缓存适用,尤其是在构建镜像调试的时候会用到,这样可以节省时间。 |
--pull |
总会尝试拉取一个最新版本的镜像进行使用,默认为false |
--tag,-t | 镜像名称 名称:标签 |
-q |
不输出构建镜像过程,结束后输出结果。默认是false,也就是输出构建过程。 |
--rm |
镜像构建成功后,删除构建过程中的容器。 |
--shm-size |
默认的/dev/shm大小,默认64M |
--ulimit |
设置容器的ulimit设置,就是我们使用ulimit -a看到的那些内容。 |
如果构建失败怎么办:
找到失败的那一步上面的一步,因为失败的上一步才是成功的,这样我们就可以使用docker run命令进入容器中进行调试。找到问题后再退出,然后修改Dockerfile,最后再次构建。从上图看出是我的Dockerfile里的一个命令写错了,进入容器我运行正确的命令进行调试
再次运行构建
从构建的镜像启动容器
1
|
docker run -d -p 80 --name httpSrv rex
/1stimage
:0.1
|
我们这里用了一个新的参数-p,这个是用来控制容器在运行时公开哪个端口给宿主机。从上图可以看到容器中的80(也就是在Dockerfile中EXPOSE定义的端口)映射到了本地宿主机的32769端口上。我们可以通过下面的命令来查看映射情况:
1
2
|
docker port ID [PORT]
#不加后面的端口表示查看该容器的所有端口映射情况
|
说一下这个-p|P参数:
-p | [IP]:[PORT]:PORT -p PORT 表示使用宿主机的0.0.0.0地址的任意高位端口映射到容器的PORT上 -p [PORT]:PORT 表示使用宿主机0.0.0.0地址指定的端口映射到容器的PORT上 -p [IP]:[PORT]:PORT 表示使用宿主机指定的IP和端口映射到容器的PORT上 -p [IP]::PORT 表示使用宿主机指定的IP和随机高位端口映射到容器的PORT上 |
-P | 大写P,表示使用Dockerfile中定义的EXPOSE端口映射到宿主机的随机端口上。 |
测试访问:
Dockerfile的格式和字段:
FROM
指定创建的镜像的基础镜像,如果本地不存在就会去你所使用的仓库去拉取。任何Dockerfile中该命令必须是第一条。如果在同一个Dockerfile中创建多个镜像,可以使用多个FROM命令。
1
|
FROM 镜像:[标签]
#如果不指定标签,则默认为leatest,最新镜像
|
MAINTAINER
设置维护者信息,也就是写该Dockerfile的作者是谁,联系方式等。该信息会写入镜像的Author属性中。如下图:
RUN
构建镜像过程中指定要运行的命令
1
2
3
4
|
RUN <COMMAND>
#或者
RUN [
"COMMAND"
,
"ARG1"
...]
#后面这张形式会被解析为JSON数组,所以必须加引号
|
前者默认会在shell终端中运行,也就是/bin/bash -c;后者会使用exec执行,不会启动shell环境。每一条RUN命令会在当前镜像基础上执行,并提交为新的镜像。如果命令较长可以使用“\”来换行。
CMD
用于指定一个在容器启动时需要执行的命令,这个命令和RUN命令很像,只不过这个命令是在容器启动时默认执行的,而RUN命令是构建镜像时执行的。书写格式如下:
1
2
3
|
CMD <COMMAND>
#或者
CMD [
"COMMAND"
,
"ARG1"
,
"ARG2"
...]
|
和RUN命令一样前者默认会在shell终端中运行,也就是/bin/bash -c;后者会使用exec执行,不会启动shell环境。每一条RUN命令会在当前镜像基础上执行,并提交为新的镜像。如果命令较长可以使用“\”来换行。
注意:每个Dockerfile只能有一条CMD命令。如果写了多条,那么只有最后一条会被执行,另外docker run命令后面如果加上了要执行的命令,将会覆盖CMD中的命令。
LABEL
该指令用于生成镜像的元数据标签信息,格式为:
1
|
LABEL <KEY>=<VALUE> <KEY>=<VALUE> ....
|
EXPOSE
声明镜像内部服务监听的端口,格式如下:
1
|
EXPOSE <PORT> ....
|
可以同时指定多个,每个用空格分割。这里设置监听的端口只是声明并不会自动完成端口映射,也就是完成宿主机上的端口到容器监听端口的映射。如果要进行端口映射,那么需要在docker run命令中使用-p|P参数来设置。
ENV
用来定义环境变量,在镜像构建时会被后续的RUN命令使用,同时在容器启动时也会生效。
1
2
3
|
ENV <NAME> <VALUE>
#或者
ENV <NAME>=<VALUE>
|
比如:
1
2
|
ENV JAVA_HOME
/opt/java1
.8.0_111
ENV PATH $JAVA_HOME:$PATH
|
另外使用docker run命令加上-e "JAVA_HOME=/usr/local/java1.6"参数可以修改ENV设定的环境变量值,不过该值只会在容器运行时有效。
ADD
该指令用于复制指定目录下的内容到容器中的指定内容,格式如下:
1
|
ADD <SRC> <DES>
|
注意:源路径可以是一个URL,或者是构建上下文中的文件或者目录,不能是构建上下文目录之外的文件或者目录,源文件也可以是一个tar、tar.gz或者.gz的文件,它将会被自动解压缩到目标路径下,目标路径可以是一个镜像内的绝对路径,也可以是一个相对于WORKDIR的相对路径。如果目标目录不存在那么Docker会自动创建。新创建的文件和目录的权限为755,UID和GID都为0.
1
2
|
ADD /构建上下文
/apache/httpd
.conf
/etc/httpd/conf/
ADD /构建上下文
/apache/httpd
.d/*.conf
/etc/httpd/conf
.d/
|
Docker会自动判断你要拷贝的文件还是目录,如果以“/”结尾表示目录下的所有内容,如果不是则认为是文件。
COPY
这个命令和ADD命令类似,区别是COPY只复杂把文件拷贝到目标,不会提取和解压,如果你的源文件不涉及到解压缩工作,那么你用COPY和ADD都一样。COPY命令的使用方式和原则和ADD一样。
1
|
COPY <SRC> <DES>
|
ENTRYPOINT
这个命令和CMD命令很像,也很容易弄混。docker run命令可以覆盖CMD命令,它的主要作用是在容器启动时作为根命令执行,所有传入的值作为该命令的参数执行。其格式如下:
1
2
3
|
ENTRYPOINT <COMMAND ARG1 ARG2...>
#或者
ENTRYPOINT [
"COMMAND"
,
"ARG1"
,
"ARG2"
...]
|
每个Dockerfile也只能有一个ENTRYPOINT,如果有多个最后一个有效。看一个例子:
1
|
ENTRYPOINT [
"/usr/sbin/nginx"
]
|
在Dockerfile中包括上面的一行,然后在启动容器时:
1
|
docker run -d 镜像 -g
"daemon off;"
|
这样启动容器时,-g和后面的参数将会传递给ENTRYPOINT,最终的命令将会是:
1
|
/usr/sbin/nginx
-g
"daemon off;"
|
如果需要可以在容器启动时使用--entrypoint这个标志来覆盖ENTRYPOINT的设置,这样就可以实现自己指定运行的命令。
VOLUME
在容器中创建一个数据挂载点,它不是把你宿主机的目录挂载到容器中,而是在容器中建立一个卷,这个目录不需要提前建立。
1
|
VOLUME [
"/目录1"
,
"/目录2"
....]
|
这个目录可以绕过联合文件系统,通常情况下如果容器程序产生的数据需要保存那么会使用这个命令来挂载或者多个容器需要共享一些数据也可而已通过这个来完成。
-
该卷可以在容器间共享或者重用
-
对该卷的修改立即生效
-
对卷的修改不会对镜像产生影响
如果单纯的是使用VOLUME,而不在docker run命令中使用-v参数的话,那么就是把VOLUME指定的目录挂载到宿主机中的某一个位置,这个位置在哪里呢?你通过 docker inspect 容器ID 中的Mounts里面来查看,Destination是VOLUME指定的目录,Source是宿主机本地路径,如果你再docker run中使用了-v参数,那么Source就是你指定的路径。
这个VOLUME的特点是绕过文件系统,容器中的程序产生的数据就可以放在这个VOLUME指定的目录中,这样它不占容器空间,而是消耗宿主机磁盘空间,默认本地的路径可以通过docker inspect查看,这个路径里面的数据不会因为容器的删除而删除。
USER
指定用哪个用户来运行容器,默认是用root来运行容器的。
1
2
3
4
|
USER <USERNAME>
USER <UID>
USER <USERNAME>:<GROUPNAMD>
USER <UID>:<GID>
|
可以使用docker run命令中的-u参数来覆盖这个设置。
WORKDIR
指定容器内的工作目录,就是容器内的目录切换,在不同目录下需要做不同的事情,就可以用这个指令。这个命令主要是配合后续要执行的RUN、CMD和ENTRYPOINT的。
1
|
WORKDIR <PATH>
|
在启动容器时如果在docker run 命令中加入-w参数可以覆盖WORKDIR目录,来指定一个新的工作目录。
ARG
该参数用来定义在build构建镜像是可以传递到构建过程中的变量,这样可以使用--build-arg来传递变量进去。
1
2
|
ARG A1
ARG A2=HELLO
|
A1变量没有值,A2变量有一个默认值。
1
|
docker build --build-arg A1=world -tNAME .
|
上面的命就会给A1赋值,而A2则会用默认值。
ONBUILD
这个指令可以为镜像添加一个触发器,一旦具有ONBUILD参数的镜像当做其他镜像的基础镜像时,ONBUILD定义的命令就会被执行。格式如下:
1
|
ONBUILD RUN [
"echo"
,
"hello world"
]
|
如果上面的的镜像是1,那么基于1去构建镜像2
1
2
3
|
FROM 1
.....
|
这就相当于在新构建镜像时,自动运行1镜像中的ONBUILD
说明:这种继承只会一次,不会再次传递,也就是说如果镜像3是基于镜像2制作的,那么将不会执行ONBUILD,因为也不会继承过来。
STOPSIGNAL
用来设置停止容器时发送什么系统信号调用信号给容器,这个信号必须是内核可以识别的。通过kill -l就可以查看系统支持哪些信号。
1
|
STOPSIGNAL <SIGNAL>
|
HEALTHCHECK
配置容器启动时如何进行健康检查
1
2
3
4
|
#根据执行命令是否返回0来判断
HEALTHCHECK [OPTION] CMD <COMMAND>
#禁止继承镜像中的健康检查
HEALTHCHECK NONE
|
OPTION有:
-
--interval=DURATION ,多久检查一次,默认30秒
-
--timeout=DURATION ,每次检查等待结果的时间,默认30秒
-
--retries=N ,如果失败了,重试几次才最终确定失败
SHELL
指定使用SHELL的默认SHELL类型,默认值为:["/bin/sh","-c"]
为什么不能用构建上下文之外的路径?
因为构建上下文环境会上传到Docker守护进程中,而COPY或者ADD是在Docker守护进程中进行的,任何构建环境之外的都找不到,所以就不能用。
举例制作TOMCAT镜像
上面讲过了Dockerfile的格式以及可以使用的命令,我们现在制作一个TOMCAT的镜像。环境如下:
编写dockerfile,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
# Version: 0.0.1
# 指定使用哪个基础镜像
FROM centos:6.6
# 说明该镜像的制作者和联系方式
MAINTAINER Rex.chen rex.chen@aaa.com
#设置标签,不是必须的只是为了说明里面的版本查看起来方便
LABEL Tomcat=
"8.5.13"
JDK=
"1.8.0_111"
# 清理一下YUM源,这个不是必须的只是为了临时通过YUM安装软件方便
RUN yum clean all
# 复制安装程序到指定目录,我这里使用COPY命令只是因为我要自行解压缩
ENV SOFT_DEST
/usr/local/src
ENV TOMCAT_APP_NAME apache-tomcat-8.5.13.
tar
.gz
ENV JDK_APP_NAME jdk-8u111-linux-x64.
tar
.gz
ADD tomcat/$TOMCAT_APP_NAME $SOFT_DEST
ADD jdk/$JDK_APP_NAME $SOFT_DEST
# 拷贝程序到指定目录并设置环境变量
WORKDIR $SOFT_DEST
RUN [
"mv"
,
"./jdk1.8.0_111"
,
"/usr"
]
RUN [
"mkdir"
,
"/work/apps"
,
"-p"
]
RUN [
"mv"
,
"./apache-tomcat-8.5.13"
,
"/work/apps"
]
ENV JAVA_HOME
"/usr/jdk1.8.0_111"
ENV PATH $JAVA_HOME
/bin
:$PATH
ENV CATALINA_HOME
"/work/apps/apache-tomcat-8.5.13"
ENV PATH $CATALINA_HOME
/bin
:$PATH
# ENTRYPOINT命令是指定在容器启动时执行的命令,CMD中指定使用run,因为catalina.sh start是后台运行,run是前台运行
ENTRYPOINT [
"catalina.sh"
]
CMD [
"run"
]
# 设置容器内的应用程序使用容器的哪个端口
EXPOSE 8080
|
构建镜像
1
|
docker build -t tomcat8:0.1 .
|
过程如下
查看一下我们之前定义的标签
1
2
|
docker inspect tomcat8:0.1 --
format
=
'``.`ContainerConfig`.`Labels`.`Tomcat`'
docker inspect tomcat8:0.1 --
format
=
'``.`ContainerConfig`.`Labels`.`JDK`'
|
启动容器
1
|
docker run -d -p 8080 --name jspSrv01 tomcat8:0.1
|
测试访问