容器是一个打包了应用和服务的环境。它是一个轻量级的虚拟机,每一个容器都由一组特定的应用和必要的依赖库组成。容器作为一个软件应用的标准集装箱,它必然需要定义一组跟具体应用无关的标准接口。
一、容器的管理操作
对于容器的常见命令(包括查看、创建、启动、终止和删除等),我们按照由浅到深、由必须到可选的顺序介绍。
要查看某条指令的详细帮助信息,可以访问 http://docs.docker.com/reference/,或者通过docker help命令。此外,我们也可以通过man pages查看(例如:man docker-run)。
创建容器
创建容器有两个命令,一个是docker create,另一个是docker run。二者的区别在于前者创建的容器处于停止状态,而后者不仅创建了容器,而且启动了容器。
采用docker create创建一个停止状态的容器,具体如下:
$ docker create ubuntu:14.04
adedeb41185ad9ac096b8f1a7a3c2d3c2f0cf759643685d320ff656bd49b9d48
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
adedeb41185a ubuntu:14.04 "/bin/bash" 27 seconds ago stupefied_brown
创建容器后,Docker会立刻返回容器的ID,例如adedeb…就是我们刚刚所创建容器的ID。ID可以唯一标识一个容器,每一个容器的ID都是独一无二的。docker ps命令用于查看正在运行的容器,我们没有看到任何运行的容器,docker ps –a则是查看所有容器,包含未启动的容器。可以看到,我们创建的容器已经存在。
想要让创建的容器立马进入运行态,可以使用docker run命令,该命令等同于用docker create创建容器后再使用docker start启动容器。使用docker run命令,可以创建两种类型的容器——后台型容器和交互型容器。
① 交互型容器:运行在前台,通常会指定有交互的控制台,可以给容器输入,也可以得到容器的输出。创建该容器的终端被关闭,在容器内部使用exit命令或者调用了docker stop、docker kill命令后,容器会变成停止状态。
② 后台型容器:运行在后台,创建启动之后就与终端无关。即便终端关闭了,该后台容器也依然存在,只有调用docker stop或docker kill命令时才能够使容器变成停止状态。
下面我们创建一个交互型容器,相关代码如下:
$ sudo docker run -i -t --name=inspect_shell ubuntu /bin/bash
Unable to find image 'ubuntu' locally
Pulling repository ubuntu
86ce37374f40: Download complete
511136ea3c5a: Download complete
5bc37dc2dfba: Download complete
61cb619d86bc: Download complete
3f45ca85fedc: Download complete
78e82ee876a2: Download complete
dc07507cef42: Download complete
root@761ef6d4b28f:/#
首先,告诉Docker要运行docker run命令。这个命令后面是命令行标志-i和-t,前者用于打开容器的标准输入(STDIN),后者告诉Docker为容器建立一个命令行终端。这两个标志为我们和容器提供了交互shell,是创建交互型容器的基本设置。后面的--name标志为容器指定了一个名字,这是一个可选项。当没有这个选项时,Docker会为我们取一个随机的名字。接下来,我们告诉Docker使用哪个镜像去创建容器,这里使用的是ubuntu。ubuntu镜像是一个基础镜像,我们可以使用基础镜像(例如ubuntu、fedora、debian、centos等)作为创建自己镜像的基础。这里我们只是用基础镜像来启动容器,没有添加任何东西。最后,告诉Docker要在容器里面执行命令/bin/bash。
命令本身我们理解了,那么在后台会发生些什么呢?创建容器的流程如图2-1所示,当我们运行docker run命令后,Docker在本地搜索我们指定的ubuntu镜像,如果没有找到,就会到公有仓库Docker Hub中继续搜索。如果在服务器上找到了需要的镜像,Docker就会下载这个镜像,并将其保存到本地。然后,Docker使用这个镜像创建一个新的容器并将其启动;容器的文件系统是在只读的镜像文件上增加一层可读写的文件层,这样可以保证镜像不变而只记录改变的数据,这对容器的共享和传输都非常有利。接着会配置容器的网络,Docker会为容器分配一个虚拟网络接口,并通过网桥的方式将该网络接口桥接到宿主主机上去,然后该虚拟网络接口分配一个IP地址。最后,Docker在新容器中运行指定的命令,例如我们的例子中是/bin/bash。容器创建成功后,会出现类似下面的提示符:
root@761ef6d4b28f:/#
图2-1 docker run命令的内部流程
@前面的是我们在容器的登录用户root,后面的761ef6d4b28f是容器的主机名。可以使用ctrl+D或者exit命令退出该容器。容器停止并不代表容器销毁,其实容器还在,只是不再是运行态,可以通过docker ps –a命令查看到已存在的容器。
接下来,我们创建一个后台型容器。在实际的应用中,大多数容器都是后台型容器,因为服务器程序不可能因为创建容器的终端退出而退出。创建后台型容器需要使用-d
参数,其创建命令如下:
$ sudo docker run --name daemon_while -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
f40f1463221f6fdcd5ae26d223df10273b0a4831d8bf1db16d461f583ac0cfb6
你可能注意到了,上面的命令没有像前面的容器那样关联到一个shell,而是返回了一个容器ID后直接返回到了宿主主机的命令提示符。我们可以通过运行docker ps命令,查看新建的容器是否正在运行:
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f40f1463221f ubuntu:latest /bin/sh -c 'while tr 2 minutes ago Up 2 minutes daemon_while
761ef6d4b28f ubuntu:latest /bin/bash 49 minutes ago Up 49 minutes inspect_shell
可以看到,有两个容器在运行,交互型的容器inspect_shell和后台型容器daemon_while。
查看容器
我们经常使用docker ps命令来查看正在运行的容器。
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
f40f1463221f ubuntu:latest /bin/sh -c 'while tr 16 minutes ago Up 16 minutes
daemon_while
761ef6d4b28f ubuntu:latest /bin/bash About an hour ago Up About an hour
inspect_shell
- CONTAINER ID:唯一标识容器的ID。它是一个64位的十六进制数,对某个容器的操作可以通过它来标识操作目标。在不会混淆的前提下,可以采用ID的前几位来标识该容器,而在显示的时候一般会显示12位。
- IMAGE:创建容器时使用的镜像。
- COMMAND:容器最后运行的命令。
- CREATED:创建容器的时间。
- STATUS:容器的状态。如果容器是运行状态,则类似UP 49 minutes的形式,其中49分钟是容器已经运行的时间;如果容器是停止状态,则是类似Exited(0)的形式,其中数字0是容器退出的错误码,0为正常退出,其他数字则表示容器内部出现错误。
- PORTS:对外开放的端口。
- NAMES:容器名。和容器ID一样都可以唯一标识一个容器,所以同一台宿主主机上不允许有同名的容器存在,否则会提示冲突。
使用docker ps命令,只会列出当前正在运行的容器。当要查看所有的容器时,可以使用-a选项,它会告诉Docker列出所有容器,包括运行的和停止的容器。示例代码如下:
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
dbc5bafd5805 ubuntu:latest echo hello 7 seconds ago Exited (0) 6 seconds ago
hopeful_bartik
f40f1463221f ubuntu:latest /bin/sh -c 'while tr 16 minutes ago Up 16 minutes
daemon_while
761ef6d4b28f ubuntu:latest /bin/bash About an hour ago Up About an hour
inspect_shell
当要查看最新创建的容器时,还可以使用-l
选项,该选项告诉Docker只列出最后创建的容器:
$ sudo docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dbc5bafd5805 ubuntu:latest echo hello 7 seconds ago Exited (0) 6 seconds ago
此外,还可以使用-n=x
选项,此时会列出最后创建的x个容器:
$ sudo docker ps –n=2
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
dbc5bafd5805 ubuntu:latest echo hello 7 seconds ago Exited (0) 6 seconds ago
hopeful_bartik
f40f1463221f ubuntu:latest /bin/sh -c 'while tr 16 minutes ago Up 16 minutes
这里列出了最近创建的两个容器的运行情况。
启动容器
通过docker run命令创建的容器会直接进入到运行状态,而通过docker create命令创建的容器则会进入到停止状态,想要运行该容器,则可以通过docker start命令来启动它。当容器运行完自己的任务后,容器会退出,进入到停止状态,如果需要再次启动该容器,可以再次用docker start命令来启动。
例如,可以通过docker start来启动之前已经停止了的inspect_shell容器:
$ sudo docker start inspect_shell
也可以使用该容器的ID来启动:
$ sudo docker start 761ef6d4b28f
容器在运行过程中,总是不可避免地会出现各种问题,严重的会导致容器因为异常而退出。而有时我们需要根据错误码来判断容器是否需要重启。默认情况下,容器是不重启的,为了让容器在退出后能够自动重启,需要用到--restart参数。--restart标志会检查容器的退出码,并据此来决定是否需要重启容器。
我们可以用下面的命令创建容器:
$ sudo docker run --restart=always --name docker_restart -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
在这个例子中,--restart标志被设置成always。不管容器的返回码是什么,Docker都会尝试重启容器。另外,我们也可以将其设置成on-failure,这样的话,当容器的返回值是非0时,Docker才会重启容器。on-failure标志还接受一个可选的重启次数,如下所示:
--restart=on-failure:5
表示当收到一个非0的返回码时,最多尝试重启容器5次。
终止容器
退出容器的方式有很多种。当容器发生严重的错误时,容器会因为异常而退出并带有错误码,这时可以通过错误码来判定容器内部发生的错误。而在正常情况下,交互型容器可以在shell中输入exit,或者是使用ctrl+d组合键来使其退出。另外,交互型容器和后台型容器都可以采用docker stop命令来停止:
$ sudo docker stop daemon_while
上述介绍的都是通过容器名来停止该容器。此外,我们还可以通过容器ID来停止容器:
$ sudo docker stop f40f1463221f
docker stop命令给容器中的进程发送SIGTERM信号,默认行为是会导致容器退出。当然,容器内程序可以捕获该信号并自行处理,例如可以选择忽略。如果要强行停止一个容器,则需要使用docker kill命令,它会给容器的进程发送SIGKILL信号,该信号将会使容器必然退出。
删除容器
当一个容器停止时,容器并没有消失,只是进入了停止状态,必要的话还可以重新运行。如果确定不再需要这个容器时,可以使用docker rm命令删除它:
$ sudo docker rm hopeful_bartik
hopeful_bartik
要注意一点,不可以删除一个运行中的容器,此时必须先用docker stop或docker kill命令停止它才能删除。示例如下:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
dfc72feb607b ubuntu:14.04 "/bin/sh" 13 seconds ago Up 12 seconds modest_mestorf
micall@micall-ThinkPad:~$ docker rm modest_mestorf
Error response from daemon: You cannot remove a running container. Stop the container before attempting removal or use –f
FATA[0000] Error: failed to remove one or more containers
此时会输出错误,提示你不能够删除一个正在运行的容器,必须先停止才能够删除。当然,你也可以使用-f选项强制删除它:
$ docker rm -f modest_mestorf
modest_mestorf
micall@micall-ThinkPad:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Docker并没有提供一次性删除所有容器的命令,但是可以用下面的命令来实现这个目的:
docker rm `docker ps -a -q`
这个命令会用docker ps列出当前的所有容器,-a标志列出所有容器,-q标志只列出容器的ID,不包括容器的其他信息。然后将这个列表传给docker rm命令,依次删除容器。
二、容器内信息获取和命令执行
依附容器
依附操作attach通常用在由docker start或者docker restart启动的交互型容器中。由于docker start启动的交互型容器并没有具体终端可以依附,而容器本身是可以接收用户交互的,这时就需要通过attach命令来将终端依附到容器上。具体示例如下:
$ docker run -i -t ubuntu:14.04 /bin/sh
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker run -i -t --name ubuntu ubuntu:14.04 /bin/sh
# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# ^C
#
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker start ubuntu
ubuntu
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d5521fff6cdb ubuntu:14.04 "/bin/sh" 45 seconds ago Up 5 seconds ubuntu
$ docker attach ubuntu
# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
#
需要注意的是,当使用attach命令依附容器后,需要多按一次回车才会出现容器的shell交互界面。
在上面,我们特别强调的是将终端依附到交互型容器中,那么对于后台型容器,使用attach命令会发生什么呢?我们通过实例来探索这个问题的答案。我们发现,后台型容器是无法依附终端的,因为它本身就不接受用户交互输入。
查看容器日志
对于交互型容器,由于本身就存在交互终端或者可以通过attach依附终端,所以查看容器的配置信息或者调试程序比较方便。但对于后台型容器,它不存在交互终端,要获取其信息,势必需要其他的方法,Docker给我们提供了logs、inspect等方法。docker logs命令用于查看容器的日志,它将输出到标准输出的数据作为日志输出到运行docker logs命令的终端上。
首先,创建一个不断输出一些内容的后台型容器:
$ sudo docker run -d --name deamon_logs ubuntu /bin/bash -c 'for((i=0;1;i++));do echo $i;sleep 1;done;'
956c4bb8db65aa24c2e7b865f829c1c19d2b201c9c7fc86a77d199b2589bdc54
deamon_logs是一个包含循环输出自然数的应用程序的容器,我们可以使用logs
来查看其输出:
$ sudo docker logs -f deamon_logs
0
1
2
3
...
可以看到,该容器的程序循环向标准输出数字0、1、2 ...。默认情况下,logs输出的是从容器启动到调用执行logs命令时的所有输出,之后的日志不再输出,并立即返回主机的控制台。如果要实时查看日志,可以使用-f标志。由于这里使用了-f标志,可以看到,日志从开始一直输出到当前时刻,并且还在不断更新,此时可以使用ctrl+C快捷键退出监视日志。
如果前面已经有很多日志,但是我们不想关心,只想要查看日志的最后部分,可以通过--tail参数。
使用--tail标志可以精确控制logs输出的日志行数。例如,查看最后5行日志:
$ sudo docker logs -f --tail=5 daemon_logs
723
724
725
726
727
...
可以看到,首先输出日志的最后5行。由于使用了-f标志,之后的日志也会不断更新出来。
为了方便调试程序,我们还可以通过-t标志查看日志产生的时刻,相关代码如下:
$ sudo docker logs -f --tail=5 -t daemon_logs
2014-12-27T08:16:00.873690621Z 755
2014-12-27T08:16:01.875306639Z 756
2014-12-27T08:16:02.880058459Z 757
2014-12-27T08:16:03.884377275Z 758
2014-12-27T08:16:04.885695676Z 759
...
查看容器进程
使用docker top命令,可以查看容器中正在运行的进程。
首先,创建一个后台型容器(交互型容器也行,但是要到其他控制台运行docker top):
$ sudo docker run -d --name="deamon_top" ubuntu /bin/bash -c 'while true;do sleep 1; done'
d9a46de654821f15269d7427d93b3126dd3217492e7c2eb3c7ae09a18fdf8bc2
运行docker top命令,查看容器中的进程:
$ sudo docker top deamon_top
UID PID PPID C STIME
TTY TIME CMD
root 11699 1158 0
16:38 ? 00:00:00 /bin/bash -c while true;do sleep
1; done
root 11778 11699 0
16:39 ? 00:00:00 sleep 1
可以看到,这里有两个进程,一个是while循环的进程,另一个是while循环内部运行的sleep进程。
查看容器信息
docker inspect用于查看容器的配置信息,包含容器名、环境变量、运行命令、主机配置、网络配置和数据卷配置等:
$ sudo docker inspect daemon_dave
[{
"ID": " c2c4e57c12c4c142271c031333823af95d64b20b5d607970c334784430bcbd0f ",
"Created": "2014-05-10T11:49:01.902029966Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true; do echo hello world; sleep 1; done"
],
"Config": {
"Hostname": "c2c4e57c12c4",
...
使用-f或者--format格式化标志,可以查看指定部分的信息。
查询容器的运行状态:
$ sudo docker inspect --format='{{ .State.Running }}' daemon_dave
false
查询容器的IP地址:
$ sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}'
daemon_dave
同时还可查看多个信息,例如查看容器名和运行状态:
$ sudo docker inspect --format '{{.Name}} {{.State.Running}}' \
daemon_dave bob_the_container
/daemon_dave false
/bob_the_container false
容器内执行命令
在容器启动的时候,通常需要指定其需要执行的程序,然而有时候我们需要在容器运行之后中途启动另一个程序。从Docker 1.3开始,我们可以用docker exec命令在容器中运行新的任务,它可以创建两种任务:后台型任务和交互型任务。后台型任务没有用户交互终端,交互型任务具有和用户交互的输入输出终端。
让我们看一个后台型任务的例子:
$ sudo docker exec -d daemon_dave touch /etc/new_config_file
这里-d标志表示要运行一个后台型任务。接着需要指定要运行命令的容器名和要运行的命令。在这个例子里,touch命令会在daemon_dave容器中创建一个new_config_file文件。通过docker exec创建的后台任务,我们可以执行维护、监视、管理等复杂而有意义的任务。
使用docker exec命令创建交互型任务时,需要加上-t -i标志,示例如下:
$ sudo docker exec -t -i daemon_dave /bin/bash
-t和-i标志的用法与我们创建交互型容器时一样,会创建一个交互终端,并捕捉进程的标准输入和输出。通过该交互终端,我们可以在容器内运行命令和查看信息等。
三、容器的导入和导出
Docker的流行与它对容器的易分享和易移植密不可分。用户不仅可以把容器提交到公共服务器上,还可以将容器导出到本地文件系统中。同样,我们也可以将导出的容器重新导入到Docker运行环境中。Docker的导入和导出分别由import命令和export命令完成。
下面我们说明一下容器的导出。首先,创建一个容器:
$ sudo docker run -i -t --name=inspect_import ubuntu /bin/bash
root@3d2371934e2d:/#
然后按需要修改容器,安装需要的软件,配置系统环境。当我们完成这一切后,就可以把容器保存到本地,使用docker export命令导出容器:
$ sudo docker export inspect_import > my_container.tar
$ ls
my_container.tar
docker export 命令会把容器的文件系统以tar包的格式导出到标准输出,我们将其重定位到目标文件name.tar。将容器保存到本地文件也算是其持久化方式的一种。将容器保存到本地之后,我们就可以通过网络等方法将tar包分享给他人。
反过来,我们可以使用docker import命令导入一个本地的tar包作为镜像:
$ cat my_container.tar | sudo docker import - imported:container
6c16d0ec9f8ef1a1f6ef47ce92b77692dd8c5d12e669b045366bbaf71d7182b1
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
imported container 6c16d0ec9f8e 35 seconds ago 192.5 MB
ubuntu latest 9bd07e480c5b 3 weeks ago 192.7 MB
ocker import会把打包的容器导入为一个镜像。
import表示从标准输入读取容器内容,我们把name.tar的内容传给了标准输入,res和tag分别代表生成的镜像和标记。
除了导入本地文件系统的tar包成为一个镜像外,我们还可以使用一个url来导入网络上的容器:
docker import url res:tag
接着就可以通过docker run命令使用导入的镜像创建我们需要的容器了。