Docker镜像简介:
对于每个软件,除了它自身的代码以外,它的运行还需要有一个运行环境和依赖。不管这个软件是象往常一样运行在物理机或者虚机之中,还是运行在现在的容器之中,这些都是不变的。在传统环境中,软件在运行之前也需要经过 代码开发->运行环境准备 -> 安装软件 -> 运行软件 等环节,在容器环境中,中间的两个环节被镜像制作过程替代了。也就是说,镜像的制作也包括运行环境准备和安装软件等两个主要环节,以及一些其他环节。因此,Docker 容器镜像其实并没有什么新的理论,只是这过程有了新的方式而已。
镜像(image)是动态的容器的静态表示(specification),包括容器所要运行的应用代码以及运行时的配置。Docker 镜像包括一个或者多个只读层( read-only layers ),因此,镜像一旦被创建就再也不能被修改了。一个运行着的Docker 容器是一个镜像的实例( instantiation )。从同一个镜像中运行的容器包含有相同的应用代码和运行时依赖。但是不像镜像是静态的,每个运行着的容器都有一个可写层( writable layer ,也成为容器层 container layer),它位于底下的若干只读层之上。运行时的所有变化,包括对数据和文件的写和更新,都会保存在这个层中。因此,从同一个镜像运行的多个容器包含了不同的容器层。
Docker容器镜像创建方式:
- 创建一个容器,运行若干命令,再使用 docker commit 来生成一个新的镜像。不建议使用这种方案。
- 创建一个 Dockerfile 然后再使用 docker build 来创建一个镜像。大多人会使用 Dockerfile 来创建镜像。
1. 镜像相关的几个概念:
1.1 Host OS、Guest OS和Base image
例如:一台主机安装的是 Centos 操作系统,现在在上面跑一个 Ubuntu 容器。此时,Host OS 是 Centos,Guest OS 是 Ubuntu。Guest OS 也被称为容器的 Base Image。
Linux内核和版本:所有 Linux 发行版都采用相同的 Linux 内核(kernel),然后所有发行版对内核都有轻微改动。这些改动都会上传回 linux 社区,并被合并。
Linux容器环境:因为所有Linux发行版都包含同一个linux 内核(有轻微修改),以及不同的自己的软件,因此,会很容易地将某个 userland 软件安装在linux 内核上,来模拟不同的发行版环境。比如说,在 Ubuntu 上运行 Centos 容器,这意味着从 Centos 获取 userland 软件,运行在 Ubuntu 内核上。因此,这就像在同一个操作系统(linux 内核)上运行不同的 userland 软件(发行版的)。这就是为什么Docker 不支持在 Linux 主机上运行 FreeBSD 或者windows 容器。
可见,容器的 base image 并不真的是 base OS。Base image 会远远比 base OS 更轻量。它只安装发行版特殊的部分(userland 软件)。
那为什么还需要 base image 呢?这是因为,docker 容器文件系统与 host OS 是隔离的。容器镜像中的应用软件无法看到主机文件系统,除非将主机文件系统挂载为容器的卷。因此,可以想像一下,你容器中的应用依赖于各种操作系统库,因此我们不得不将这些库打包到镜像之中。另外,base image 会让我们使用到各个发行版的包管理系统,比如 yum 和 apt-get。而且,各个linux 发行版的 base image 也不是普通的发行版,而是一个简化了的版本。而且,base image 并不带有 linux 内核,因为容器会使用主机的内核。
因此,需要注重理解 image 和 OS 这两个概念。之所以成为 base image,而不是 base OS,是因为 base image 中并不包括完整的 OS。而这一点,是容器与虚拟机之前的本质区别之一。那就是,容器并没有虚拟化,而是共享主机上的linux 内核。
1.2 Container Base image
从上面内容可以看出,容器把 linux 镜像从内核空间和用户空间进行了分开管理。对 host OS 来说,它更侧重于内核,再加上少量的用户空间内容;对 Guest OS 来说,它侧重于(只有)用户空间,只包括库文件、编译器、配置文件,以及用户代码。
常见的容器基础镜像:
因此,用户需要仔细选择容器的 base image,不仅从上表中的几个方面,还包括性能、安全性等一些因素。
2. docker build生成镜像:
2.1 生成过程实例
在使用 Dockerfile 创建容器之前,需要先准备一个 Dockerfile 文件,然后运行 docker build 命令来创建镜像。我们通过下面的例子来看看Docker 创建容器的过程。
这是一个非常简单的Dockerfile,它的目的是基于 Ubuntu 14.04 基础镜像安装 ntp 从而生成一个新的镜像
FROM ubuntu:14.04
MAINTAINER tony "tony@tony.com"
RUN apt-get update
RUN apt-get -y install ntp
EXPOSE 5555
CMD ["/usr/sbin/ntpd"]
过程如下:
Step 1/6 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
2e6e20c8e2e6: Downloading
30bb187ac3fc: Downloading
b7a5bcc4a58a: Downloading
14.04: Pulling from library/ubuntu
2e6e20c8e2e6: Pull complete
30bb187ac3fc: Pull complete
b7a5bcc4a58a: Pull complete
Digest: sha256:ffc76f71dd8be8c9e222d420dc96901a07b61616689a44c7b3ef6a10b7213de4
Status: Downloaded newer image for ubuntu:14.04
---> 6e4f1fe62ff1
Step 2/6 : MAINTAINER tony "tony@tony.com"
---> Running in 4a90e3e00a32
Removing intermediate container 4a90e3e00a32
---> 254539e6455e
Step 3/6 : RUN apt-get update
---> Running in 37a69a28ac43
Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Ign http://archive.ubuntu.com trusty InRelease
Get:2 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:3 http://security.ubuntu.com trusty-security/main amd64 Packages [1032 kB]
Get:4 https://esm.ubuntu.com trusty-infra-security InRelease
Get:5 https://esm.ubuntu.com trusty-infra-updates InRelease
Get:6 http://archive.ubuntu.com trusty-backports InRelease [65.9 kB]
Get:7 https://esm.ubuntu.com trusty-infra-security/main amd64 Packages
Hit http://archive.ubuntu.com trusty Release.gpg
Get:8 http://security.ubuntu.com trusty-security/restricted amd64 Packages [18.1 kB]
Get:9 http://archive.ubuntu.com trusty-updates/main amd64 Packages [1460 kB]
Get:10 http://security.ubuntu.com trusty-security/universe amd64 Packages [378 kB]
Get:11 http://security.ubuntu.com trusty-security/multiverse amd64 Packages [4730 B]
Get:12 https://esm.ubuntu.com trusty-infra-updates/main amd64 Packages
Get:13 http://archive.ubuntu.com trusty-updates/restricted amd64 Packages [21.4 kB]
Get:14 http://archive.ubuntu.com trusty-updates/universe amd64 Packages [671 kB]
Get:15 http://archive.ubuntu.com trusty-updates/multiverse amd64 Packages [16.1 kB]
Get:16 http://archive.ubuntu.com trusty-backports/main amd64 Packages [14.7 kB]
Get:17 http://archive.ubuntu.com trusty-backports/restricted amd64 Packages [40 B]
Get:18 http://archive.ubuntu.com trusty-backports/universe amd64 Packages [52.5 kB]
Get:19 http://archive.ubuntu.com trusty-backports/multiverse amd64 Packages [1392 B]
Hit http://archive.ubuntu.com trusty Release
Get:20 http://archive.ubuntu.com trusty/main amd64 Packages [1743 kB]
Get:21 http://archive.ubuntu.com trusty/restricted amd64 Packages [16.0 kB]
Get:22 http://archive.ubuntu.com trusty/universe amd64 Packages [7589 kB]
Get:23 http://archive.ubuntu.com trusty/multiverse amd64 Packages [169 kB]
Fetched 13.8 MB in 9s (1377 kB/s)
Reading package lists...
Removing intermediate container 37a69a28ac43
---> a7d60c03a894
Step 4/6 : RUN apt-get -y install ntp
---> Running in cea7f659c0bd
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
libedit2 libopts25
Suggested packages:
ntp-doc apparmor
The following NEW packages will be installed:
libedit2 libopts25 ntp
0 upgraded, 3 newly installed, 0 to remove and 1 not upgraded.
Need to get 564 kB of archives.
After this operation, 1925 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu/ trusty/main libedit2 amd64 3.1-20130712-2 [86.7 kB]
Get:2 http://archive.ubuntu.com/ubuntu/ trusty/main libopts25 amd64 1:5.18-2ubuntu2 [55.3 kB]
Get:3 http://archive.ubuntu.com/ubuntu/ trusty-updates/main ntp amd64 1:4.2.6.p5+dfsg-3ubuntu2.14.04.13 [422 kB]
debconf: unable to initialize frontend: Dialog
debconf: (TERM is not set, so the dialog frontend is not usable.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
dpkg-preconfigure: unable to re-open stdin:
Fetched 564 kB in 2s (219 kB/s)
Selecting previously unselected package libedit2:amd64.
(Reading database ... 12097 files and directories currently installed.)
Preparing to unpack .../libedit2_3.1-20130712-2_amd64.deb ...
Unpacking libedit2:amd64 (3.1-20130712-2) ...
Selecting previously unselected package libopts25:amd64.
Preparing to unpack .../libopts25_1%3a5.18-2ubuntu2_amd64.deb ...
Unpacking libopts25:amd64 (1:5.18-2ubuntu2) ...
Selecting previously unselected package ntp.
Preparing to unpack .../ntp_1%3a4.2.6.p5+dfsg-3ubuntu2.14.04.13_amd64.deb ...
Unpacking ntp (1:4.2.6.p5+dfsg-3ubuntu2.14.04.13) ...
Processing triggers for ureadahead (0.100.0-16) ...
Setting up libedit2:amd64 (3.1-20130712-2) ...
Setting up libopts25:amd64 (1:5.18-2ubuntu2) ...
Setting up ntp (1:4.2.6.p5+dfsg-3ubuntu2.14.04.13) ...
invoke-rc.d: policy-rc.d denied execution of start.
Processing triggers for libc-bin (2.19-0ubuntu6.15) ...
Processing triggers for ureadahead (0.100.0-16) ...
Removing intermediate container cea7f659c0bd
---> f3b5d9bd0a91
Step 5/6 : EXPOSE 5555
---> Running in 61e532f23757
Removing intermediate container 61e532f23757
---> fcabc9901274
Step 6/6 : CMD ["/usr/sbin/ntpd"]
---> Running in 402fffe3af65
Removing intermediate container 402fffe3af65
---> 8844d2cf711d
Successfully built 8844d2cf711d
Dockerfile 中的每个步骤都会对应每一个 docker build 输出中的 step。
Step 1:FROM ubuntu:14.04
获取基础镜像 ubuntu:14.04. Docker 首先会在本地查找,如果找到了,则直接利用;否则从 Docker registry 中下载。在第一次使用这个基础镜像的时候,Docker 会从 Docker Hub 中下载这个镜像,并保存在本地:
Step 1/6 : FROM ubuntu:14.04
14.04: Pulling from library/ubuntu
2e6e20c8e2e6: Downloading
30bb187ac3fc: Downloading
b7a5bcc4a58a: Downloading
14.04: Pulling from library/ubuntu
2e6e20c8e2e6: Pull complete
30bb187ac3fc: Pull complete
b7a5bcc4a58a: Pull complete
Digest: sha256:ffc76f71dd8be8c9e222d420dc96901a07b61616689a44c7b3ef6a10b7213de4
Status: Downloaded newer image for ubuntu:14.04
---> 6e4f1fe62ff1
以后再使用的时候就直接使用这个镜像而不再需要下载了。
Step 2/6 : MAINTAINER tony "tony@tony.com"
本例中依然是从 Cache 中获取新的镜像。在第一次的时候,Docker 会创建一个临时的容器4a90e3e00a32,然后运行 MAINTAINER 命令,再使用 docker commit 生成新的镜像
Step 2/6 : MAINTAINER tony "tony@tony.com"
---> Running in 4a90e3e00a32
Removing intermediate container 4a90e3e00a32
---> 254539e6455e
Step 3: RUN apt-get update
本例中Docker 仍然从缓存中获取了镜像。在第一次的时候,Docker 仍然是通过创建临时容器在执行 docker commit 的方式来创建新的镜像:
---> Running in 37a69a28ac43
Get:1 http://security.ubuntu.com trusty-security InRelease [65.9 kB]
Ign http://archive.ubuntu.com trusty InRelease
Get:2 http://archive.ubuntu.com trusty-updates InRelease [65.9 kB]
Get:3 http://security.ubuntu.com trusty-security/main amd64 Packages [1032 kB]
Get:4 https://esm.ubuntu.com trusty-infra-security InRelease
Get:5 https://esm.ubuntu.com trusty-infra-updates InRelease
Get:6 http://archive.ubuntu.com trusty-backports InRelease [65.9 kB]
Get:7 https://esm.ubuntu.com trusty-infra-security/main amd64 Packages
Hit http://archive.ubuntu.com trusty Release.gpg
Get:8 http://security.ubuntu.com trusty-security/restricted amd64 Packages [18.1 kB]
Get:9 http://archive.ubuntu.com trusty-updates/main amd64 Packages [1460 kB]
Get:10 http://security.ubuntu.com trusty-security/universe amd64 Packages [378 kB]
Get:11 http://security.ubuntu.com trusty-security/multiverse amd64 Packages [4730 B]
Get:12 https://esm.ubuntu.com trusty-infra-updates/main amd64 Packages
Get:13 http://archive.ubuntu.com trusty-updates/restricted amd64 Packages [21.4 kB]
Get:14 http://archive.ubuntu.com trusty-updates/universe amd64 Packages [671 kB]
Get:15 http://archive.ubuntu.com trusty-updates/multiverse amd64 Packages [16.1 kB]
Get:16 http://archive.ubuntu.com trusty-backports/main amd64 Packages [14.7 kB]
Get:17 http://archive.ubuntu.com trusty-backports/restricted amd64 Packages [40 B]
Get:18 http://archive.ubuntu.com trusty-backports/universe amd64 Packages [52.5 kB]
Get:19 http://archive.ubuntu.com trusty-backports/multiverse amd64 Packages [1392 B]
Hit http://archive.ubuntu.com trusty Release
Get:20 http://archive.ubuntu.com trusty/main amd64 Packages [1743 kB]
Get:21 http://archive.ubuntu.com trusty/restricted amd64 Packages [16.0 kB]
Get:22 http://archive.ubuntu.com trusty/universe amd64 Packages [7589 kB]
Get:23 http://archive.ubuntu.com trusty/multiverse amd64 Packages [169 kB]
Fetched 13.8 MB in 9s (1377 kB/s)
Reading package lists...
Removing intermediate container 37a69a28ac43
---> a7d60c03a894
通过以上步骤,生成了新的中间镜像a7d60c03a894,它也会被保存在缓存中。你可以使用 docker inspect a7d60c03a894 命令查看该中间镜像,但是无法在docker images 列表中找到它,这是因为 docker images 默认隐藏了中间状态的镜像,因此你需要使用 docker images -a 来获取它
root@MyAliserver:~/dockerfile/ubuntu# docker images -a | grep a7d60c03a894
<none> <none> 678f7c547a03 20 minutes ago 210MB
该镜像和原始镜像相比,多了一个 layer,它保存的是 apt-get update 命令所带来的变化:
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:f2fa9f4cf8fd0a521d40e34492b522cee3f35004047e617c75fadeb8bfd1e6b7",
"sha256:48dc77435ad5c63ea60d91e6ad4828c70e7e61755f99982b0505abb8aaa00872",
"sha256:3da511183950aa462f667f43fcda0bb5484c5c73eaa94fcd0a94bbd4db396e1c",
"sha256:6cd5fc48cf77c2f6b7b288aa95a7f25c6902cbbf9ca0c81ff54e247dbd3aaa3f" #这层是新加的
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
Step 4: RUN apt-get -y install ntp
和上面 Step 3 过程一样,这个步骤也会通过创建临时容器,执行该命令,再使用 docker commit 命令生成一个中间镜像 。和上面步骤生成的镜像相比,它又多了一层:
Step 5: EXPOSE 5555
这一步和上面的 Step 2 一样,Docker 生成了一个临时容器,执行 EXPOSE 55 命令,再通过 docker commit 创建了中间镜像。该镜像的 layers 没有变化,但是元数据发生了一些变化,包括:
"ExposedPorts": {
"5555/tcp": {}
}
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"EXPOSE 5555/tcp"
]
Step 6: CMD ["/usr/sbin/ntpd"]
这一步和上面的步骤相同,最终它创建了镜像8844d2cf711d,该镜像只是修改了 CMD 元数据:
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/usr/sbin/ntpd\"]"
]
该镜像也是Docker 根据本 Dockerfile 生成的最终镜像。它也出现在了 docker images 结果中:
docker images | grep 8844d2cf711d
ntponubuntu latest 8844d2cf711d 1 hours ago 212.8 MB
我们可以使用 docker history 命令查看该镜像中每一层的信息:
以上过程说明:
- 容器镜像包括元数据和文件系统,其中文件系统是指对基础镜像的文件系统的修改,元数据不影响文件系统,只是会影响容器的配置
- 每个步骤都会生成一个新的镜像,新的镜像与上一次的镜像相比,要么元数据有了变化,要么文件系统有了变化而多加了一层
- Docker 在需要执行指令时通过创建临时镜像,运行指定的命令,再通过 docker commit 来生成新的镜像
- Docker 会将中间镜像都保存在缓存中,这样将来如果能直接使用的话就不需要再从头创建了。关于镜像缓存,请搜索相关文档。
2.2 Docker 镜像分层,COW 和镜像大小
2.2.1 镜像层和容器层
从上面例子可以看出,一个 Docker 镜像是基于基础镜像的多层叠加,最终构成和容器的 rootfs (根文件系统)。当 Docker 创建一个容器时,它会在基础镜像的容器层之上添加一层新的薄薄的可写容器层。接下来,所有对容器的变化,比如写新的文件,修改已有文件和删除文件,都只会作用在这个容器层之中。因此,通过不拷贝完整的 rootfs,Docker 减少了容器所占用的空间,以及减少了容器启动所需时间。
2.2.2 COW和镜像大小
COW,copy-on-write 技术,一方面带来了容器启动的快捷,另一方也造成了容器镜像大小的增加。每一次 RUN 命令都会在镜像上增加一层,每一层都会占用磁盘空间。举个例子,在 Ubuntu 14.04 基础镜像中运行 RUN apt-get upgrade 会在保留基础层的同时再创建一个新层来放所有新的文件,而不是修改老的文件,因此,新的镜像大小会超过直接在老的文件系统上做更新时的文件大小。因此,为了减少镜像大小起见,所有文件相关的操作,比如删除,释放和移动等,都需要尽可能地放在一个 RUN 指令中进行。
比如说,通过将上面的示例 Dockerfile 修改为:
# 产生的镜像不仅层数少了一层,而且大小也会减少。
FROM ubuntu:14.04
MAINTAINER tony "tony@tony.com"
RUN apt-get update && apt-get -y install ntp
EXPOSE 5555 CMD ["/usr/sbin/ntpd"]
2.2.2 使用容器需要避免的一些做法
- 不要在容器中保存数据(Don’t store data in containers)
- 将应用打包到镜像再部署而不是更新到已有容器(Don’t ship your application in two pieces)
- 不要产生过大的镜像 (Don’t create large images)
- 不要使用单层镜像 (Don’t use a single layer image)
- 不要从运行着的容器上产生镜像 (Don’t create images from running containers )
- 不要只是使用 “latest”标签 (Don’t use only the “latest” tag)
- 不要在容器内运行超过一个的进程 (Don’t run more than one process in a single container )
- 不要在容器内保存 credentials,而是要从外面通过环境变量传入 ( Don’t store credentials in the image. Use environment variables)
- 不要使用 root 用户跑容器进程(Don’t run processes as a root user )
- 不要依赖于IP地址,而是要从外面通过环境变量传入 (Don’t rely on IP addresses )
3. Dockerfile 语法
上面的步骤说明了 Docker 可以通过读取 Dockerfile 的内容来生成容器镜像。Dockerfile 的每一行都是 INSTRUCTION arguments 格式,即 “指令 参数”。关于 Dockerfile 的格式,请参考 https://docs.docker.com/engine/reference/builder/。下面只是就一些主要的指令做一些说明。
3.1 几个主要指令
3.1.1 ADD和COPY
Add:将 host 上的文件拷贝到或者将网络上的文件下载到容器中的指定目录
# Usage: ADD [source directory or URL] [destination directory]
ADD /my_app_folder /my_app_folder
#例子:
FROM ubuntu:14.04
MAINTAINER tony <tony@tony.com>
ADD temp dockfile
ENTRYPOINT top
ADD 指令会将本地 temp 目录中的文件拷贝到容器的 dockfile 目录下面,从而在镜像中增加一个 layer。在未指定绝对路径的时候,会放到 WORKDIR 目录下面。
那两者有什么区别呢?
- ADD 多了2个功能, 下载URL和对支持的压缩格式的包进行解压. 其他都一样。比如 ADD http://foo.com/bar.go /tmp/main.go 会将文件从因特网上方下载下来,ADD /foo.tar.gz /tmp/ 会将压缩文件解压再COPY过去
- 如果你不希望压缩文件拷贝到container后会被解压的话, 那么使用COPY。
- 如果需要自动下载URL并拷贝到container的话, 请使用ADD
3.1.2 CMD
CMD:在容器被创建后执行的命令,和 RUN 不同,它是在构造容器时候所执行的命令。
# Usage 1: CMD application "argument", "argument", ..
CMD "echo" "Hello docker!"
CMD 有三种格式:
-
CMD ["executable","param1","param2"]
(like an exec, preferred form) -
CMD ["param1","param2"]
(作为 ENTRYPOINT 的参数) -
CMD command param1 param2
(作为 shell 运行)
一个Dockerfile里只能有一个CMD
,如果有多个,只有最后一个生效。
3.1.3 ENTRYPOINT
ENTRYPOINT :设置默认应用,会保证每次容器被创建后该应用都会被执行。CMD 和 ENTRYPOINT 的关系会在下面详细解释。
3.1.4 ENV:设置环境变量,可以使用多次
# Usage: ENV key value
ENV SERVER_WORKS 4
#设置了后,后续的RUN命令都可以使用,并且会作为容器的环境变量。举个例子,下面是 dockfile:
FROM ubuntu:14.04
ENV abc=1
ENV def=2
ENTRYPOINT top
#生成镜像,docker build -t envimg4 -f dockerfile-env . 其元数据包括了这两个环境变量:
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"abc=1",
"def=2"
],
#启动容器:docker run -it --name envc41 envimg4。也能看到:
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"abc=1",
"def=2"
]
# 进入容器:能看到定义的abc和def变量:
root@aliserver:/home/ntponubuntu# docker exec -it envc41 bash
root@ba460e0e9dc4:/# echo $abc
1
root@ba460e0e9dc4:/# echo $def
2
3.1.5 EXPOSE:像容器外暴露 一个端口
# Usage: EXPOSE [port]
EXPOSE 8080
3.1.6 FROM:指定基础镜像,必须是第一条指令
# Usage: FROM [image name]
FROM ubuntu
3.1.7 MAINTAINER:可以在任意地方使用,设置镜像的作者
# Usage: MAINTAINER [name]
MAINTAINER authors_name
3.1.8 RUN:运行命令,结果会生成镜像中的一个新层
# Usage: RUN [command]
RUN aptitude install -y ntp
3.1.9 USER: 设置该镜像的容器的主进程 所使用的用户,以及后续的RUN、CMD和ENTRYPOINT指令运行所使用的用户
# Usage: USER [UID]
USER 751
3.1.10 VOLUME:允许容器访问host上某个目录
# Usage: VOLUME ["/dir_1", "/dir_2" ..]
VOLUME ["/my_files"]
3.1.11 WORKDIR:设置CMD所指定命令的执行目录
# Usage: WORKDIR /path
WORKDIR ~/
3.1.12 HEALTHCHECK
这是 Docker 1.12 版本中新引入的指令,其语法为 HEALTHCHECK [OPTIONS] CMD command。 来看一个例子:
FROM ubuntu:14.04
MAINTAINER tony <tony@tony.com>
RUN apt-get update
RUN apt-get -y install curl
EXPOSE 8888
CMD while true; do echo 'hello world' | nc -l -p 8888; done
HEALTHCHECK --interval=10s --timeout=2s CMD curl -f http://localhost:8888/ || exit 1
在启动容器后,其health 状态首先是 starting,然后在过了10秒做了第一次健康检查成功后,变为 healthy 状态。需要注意的是 CMD 是在容器之内运行的,因此,你需要确保其命令或者脚本存在于容器之内并且可以被运行。
3.2 几个比较绕的地方
3.2.1 EXPOSE 和 docker run -p -P 之间的关系
容器的端口必须被发出(publish)出来后才能被外界使用。Dockerfile 中的 EXPOSE 只是“标记”某个端口会被暴露出来,只有在使用了 docker run -p 或者 -P 后,端口才会被“发出”出来,此时端口才能被使用。
举例:
(1)Dockerfile
FROM ubuntu:14.04
MAINTAINER tony <tony@tony.com>
CMD while true; do echo 'hello world' | nc -l -p 8888; done
(2)创建镜像
docker build -t no-exposed-ports -f dockerfile-ports .
(3)启动容器1
docker run -d --name no-exposed-ports1 no-exposed-ports。
#此容器没有 exposed 和 published 任何端口。
(4)启动容器2
docker run -d --name no-exposed-ports2 -p 8888:8888 no-exposed-ports
#此时容器的 8888 端口被发布为主机上的 8888 端口:
"Ports": {
"8888/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8888"
}
]
}
#该端口会正常返回
root@MyAliserver:~# telnet 0.0.0.0 8888
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
hello world
Connection closed by foreign host.
(5)使用 -P 参数:docker run -d --name no-exposed-ports3 -P no-exposed-ports
此时没有任何端口被 published,说明 Docker 在使用了 “-P” 情形下只是自动将 exposed 的端口 published。
(6)使用 -p 加上一个不存在的端口:docker run -d --name no-exposed-ports4 -p 8889:8889 no-exposed-ports
此时,8889 端口会被暴露,但是没法使用。说明 -p 会将没有 exposed 的端口自动 exposed 出来。
可见:
EXPOSE
或者--expose
只是为其他命令提供所需信息的元数据,或者只是告诉容器操作人员有哪些已知选择。它只是作为记录机制,也就是告诉用户哪些端口会提供服务。它保存在容器的元数据中。- 使用 -p 发布特定端口。如果该端口已经被 exposed,则发布它;如果它还没有被 exposed,则它会被 exposed 和 published。Docker 不会检查容器端口的正确性。
- 使用 -P 时 Docker 会自动将所有已经被 exposed 的端口发出出来。
3.2.2 CMD 和 ENTRYPOINT
这两个指令都指定了运行容器时所运行的命令。以下是它们共存的一些规则:
- Dockerfile 至少需要指定一个 CMD 或者 ENTRYPOINT 指令
- CMD 可以用来指定 ENTRYPOINT 指令的参数
例子:
(1)同时有CMD和ENTRYPOINT
FROM ubuntu:14.04MAINTAINER tony <tony@tony.com>CMD top <br><br>ENTRYPOINT ps
此时会运行得指令为/bin/sh -c ps /bin/sh -c top
但是实际上只是运行了ps:
root@MyAliserver:~# /bin/sh -c ps /bin/sh -c top
PID TTY TIME CMD
27267 pts/0 00:00:00 bash
27324 pts/0 00:00:00 sh
27325 pts/0 00:00:00 ps
root@MyAliserver:~# /bin/sh -c ps
PID TTY TIME CMD
27267 pts/0 00:00:00 bash
27327 pts/0 00:00:00 sh
27328 pts/0 00:00:00 ps
(2)CMD作为ENTRYPOINT得参数
FROM ubuntu:14.04MAINTAINER tony <tony@tony.com>CMD ["-n", "10"] <br><br>ENTRYPOINT top
启动容器后运行得命令为/bin/sh -c top -n 10