容器
容器是Docker的另一个核心概念。
简单的说,容器是镜像的一个运行实例,所不同的是,它带有额外的可写入层。
如果认为虚拟机是模拟运行的一套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。那么Docker容器就是独立运行的一个或一组应用。以及它们的必须运行环境。
本篇文章具体介绍围绕容器的重要操作,包括创建一个容器,启动容器,停止一个容器,进入容器进行具体的操作,删除容器和通过导入容器来实现容器容器的迁移
4.1 创建容器
Docker的容器十分轻量级,用户可以随时创建或删除容器。
新建容器
可以使用docker create 命令创建一个新的容器,例如:
[root@private_vpn ~]# docker pull centos
Using default tag: latest
Trying to pull repository docker.io/library/centos ...
latest: Pulling from docker.io/library/centos
d9aaf4d82f24: Pull complete
Digest: sha256:4565fe2dd7f4770e825d4bd9c761a81b26e49cc9e3c9631c58cfc3188be9505a
[root@private_vpn ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/centos latest d123f4e55e12 3 weeks ago 196.6 MB
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@private_vpn ~]# docker create -it d123
480b9fb36e0dc83bf2072d96c909f33a9a6ae5dedc18d30d994228ab50880e4e
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
480b9fb36e0d d123 "/bin/bash" 7 seconds ago Created goofy_raman
4.2 容器的启动
启动容器(方式一)
此时创建的容器处于停止状态,可以使用docker start 容器ID
命令来启动它。
[root@private_vpn ~]# docker start 480
480
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
480b9fb36e0d d123 "/bin/bash" 2 minutes ago Up 4 seconds goofy_raman
对比容器启动前后的STATUS
状态则可以看出容器现在已经在运行了。
启动容器(方式二)
容器的启动有2种方式,一种是基于镜像创建容器并启动,如上面所讲。另一种是将终止(stopped)状态的容器重启启动。所需要的主要命令是docker run
,等价于先执行docker create 镜像ID
再执行docker start 容器ID
。
例如,下面的命令输出一个"hello,world!"后自动终止运行。
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
480b9fb36e0d d123 "/bin/bash" 15 minutes ago Exited (0) 8 minutes ago goofy_raman
[root@private_vpn ~]# docker run centos /bin/echo "hello,world"
hello,world
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ef6cbb4b7f9 centos "/bin/echo hello,worl" 4 seconds ago Exited (0) 3 seconds ago gloomy_bardeen
480b9fb36e0d d123 "/bin/bash" 15 minutes ago Exited (0) 8 minutes ago goofy_raman
可以看到输出了"hello,world"的容器的STATUS是Exited (0) 3 seconds ago
,此处也可以看到我之前用第一种方式启动的容器也是处于Exited状态了,这是因为我用docker attach 480
进入容器后又exit退出了,所以显示这个容器也是exit状态,此处可以先不看这个。后面会细讲。
现在回到我们说的第二种启动容器的上来。
这跟在本地直接执行/bin/echo "hello world" 几乎感觉不出有任何区别。
当利用docker run来创建并启动容器时,Docker在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载。
- 利用镜像创建并启动一个容器。
- 分配一个文件系统,并在只读的镜像层外面挂在一层可读写层。
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去。
- 从地址池配置一个IP地址给容器。
- 执行用户指定的应用程序。
- 执行完毕后容器被终止。
当然我们也可以加些参数允许我们可以和容器进行交互。
[root@private_vpn ~]# docker run -t -i d123f4e55e12 /bin/bash
[root@a2328599c69d /]# ls -l /tmp/
total 4
-rwx------ 1 root root 836 Sep 11 15:53 ks-script-q6TWGF
-rw------- 1 root root 0 Sep 11 15:51 yum.log
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a2328599c69d d123f4e55e12 "/bin/bash" 16 seconds ago Up 15 seconds determined_cray
其中参数-t
选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,参数-i
则让容器的标准输入保持打开。
在交互式模式下,用户可以通过所创建的伪终端来输入命令,如上所示的ls -l /tmp
。
在容器内执行ps -ef
可以看到如下结果
[root@a2328599c69d /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 16:40 ? 00:00:00 /bin/bash
root 14 1 0 16:44 ? 00:00:00 ps -ef
容器只运行了bash应用,并没有运行其他不需要的进程。
[root@a2328599c69d /]# exit
exit
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a2328599c69d d123f4e55e12 "/bin/bash" 7 minutes ago Exited (0) 4 seconds ago determined_cray
对于所创建的bash容器,当使用exit命令退出之后,该容器就自动处于终止状态了。这是因为对于Docker容器来说,当运行的应用(此处例子是bash)退出后,容器也就没有运行下去的必要了。
这也是上面说到的创建容器然后启动容器,再进入容器,最后exit退出后,容器的STATUS显示Exited
守护态运行
更多的时候,需要让Docker容器在后台以守护态(Daemonized)形式运行。
使用 -d
参数
[root@private_vpn ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/nginx latest 9e7424e5dbae 7 days ago 108.5 MB
docker.io/nuagebec/ubuntu latest 7c2ea61fd57e 12 days ago 329.4 MB
docker.io/centos latest d123f4e55e12 3 weeks ago 196.6 MB
[root@private_vpn ~]#
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d1311a650a77 nginx "nginx -g 'daemon off" About an hour ago Up About an hour 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 24 hours grave_pasteur
[root@private_vpn ~]#
[root@private_vpn ~]#
[root@private_vpn ~]# docker run -tid 7c2ea61fd57e /bin/bash
be037923b36fc1731a4a3abf36973c772efe1289d47f612fc2c083b5b017c647
[root@private_vpn ~]#
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be037923b36f 7c2ea61fd57e "/bin/bash" 7 seconds ago Up 6 seconds 22/tcp cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" About an hour ago Up About an hour 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 24 hours grave_pasteur
新创建的容器(ID:be037923b36f )此时已经放入后台运行,STATUS可以看出。
4.3 容器的停止
可以使用docker stop 来终止一个运行中的容器。此命令格式:
docker stop [-t | --time [=10] ] [container...]
首先向容器发送sigterm信号,等待一端超时时间(默认是10s)后,再发送sigkill信号来终止容器:
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be037923b36f 7c2ea61fd57e "/bin/bash" 14 minutes ago Up 14 minutes 22/tcp cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" About an hour ago Up About an hour 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 24 hours grave_pasteur
[root@private_vpn ~]#
[root@private_vpn ~]# docker stop be
be
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be037923b36f 7c2ea61fd57e "/bin/bash" 14 minutes ago Exited (0) 2 seconds ago cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" About an hour ago Up About an hour 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 24 hours grave_pasteur
容器的重启
docker restart container
此命令会将一个运行态的容器先停止,然后再重新启动它。
4.4 进入容器
使用-d参数时,容器启动后会进入后台,用户无法看到容器中的信息,也无法进行操作。这个时候如果需要进入容器进行操作,有多种方法,包括使用官方的attach或exec命令,以及第三方的nsenter工具等,下面分别介绍一下。
1. attach命令
attach是docker自带的命令,命令格式为:
docker attach [--detach-keys[=[]]] [--no-stdin] [--sig-proxy [=true]] container
示例:
[root@private_vpn ~]# docker run -itd ubuntu
e41d5b8cdc4705622fc05b334717911a0e3afa1287aabf0462f795ec3332ef3d
[root@private_vpn ~]#
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e41d5b8cdc47 ubuntu "/bin/bash" 5 seconds ago Up 4 seconds grave_heisenberg
7d265677e942 ubuntu "/bin/bash" 4 minutes ago Exited (0) About a minute ago dreamy_chandrasekhar
9fe70e55acad ubuntu "/bin/bash" 4 minutes ago Exited (127) 4 minutes ago prickly_sinoussi
be037923b36f 7c2ea61fd57e "/bin/bash" About an hour ago Up 20 minutes 22/tcp cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 25 hours grave_pasteur
[root@private_vpn ~]#
[root@private_vpn ~]# docker attach grave_heisenberg
root@e41d5b8cdc47:/#
root@e41d5b8cdc47:/# ls -l
total 12
drwxr-xr-x 2 root root 4096 Nov 14 13:49 bin
...
root@e41d5b8cdc47:/# exit
exit
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e41d5b8cdc47 ubuntu "/bin/bash" 50 seconds ago Exited (0) 1 seconds ago grave_heisenberg
7d265677e942 ubuntu "/bin/bash" 4 minutes ago Exited (0) About a minute ago dreamy_chandrasekhar
9fe70e55acad ubuntu "/bin/bash" 5 minutes ago Exited (127) 4 minutes ago prickly_sinoussi
be037923b36f 7c2ea61fd57e "/bin/bash" About an hour ago Up 21 minutes 22/tcp cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 25 hours grave_pasteur
但是使用attach命令有时候不方便,当多个窗口同时使用attach命令连接到同一个容器的时候,所有窗口都会同步显示,当某个窗口命令阻塞时,其他窗口也无法执行操作。根据上面的示例我们也可以看到当使用attach进入到容器后,再执行exit退出容器后可以看到容器已处于停止运行状态。
2. exec命令
docker从1.3.0版本开始就提供了一个更加方便的exec命令,可以在容器内直接执行任意命令。该命令的基本格式为:
docker exec [-d|--detach] [--detach-keys[=[]]] [-i|--interactive] [--priveleged] [-t|--tty] [-u|--user[=USER]] container command [arg...]
比较重要的参数有:
-i,--interactive=true|false:
打开标准输入接受用户输入命令,默认为false;
--privileged=true|false:
是否给执行命令以高权限,默认为false;
-t,--tty=true|false:
分配伪终端,默认为false
-u,--user="":
执行命令的用户名或ID
例如进入到刚创建的容器中,并启动一个bash
[root@private_vpn ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be037923b36f 7c2ea61fd57e "/bin/bash" About an hour ago Up About an hour 22/tcp cocky_blackwell
d1311a650a77 nginx "nginx -g 'daemon off" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp webserver
6935f8fff19c d123f4e55e12 "/bin/bash" 4 days ago Up 26 hours grave_pasteur
[root@private_vpn ~]# docker exec -it d1 /bin/bash
root@d1311a650a77:/# ls -l
total 12
drwxr-xr-x 2 root root 4096 Oct 9 00:00 bin
...
- nsenter工具
在util-linux软件包版本2.23+中包含nsenter工具。如果系统中的util-linux包没有该命令,可以按照下面的方法从源码安装:
注:我的测试机已经有了此工具,所以此处不再做实验,只贴上方法,自己验证。
cd /tmp; curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf -; cd util-linux-2.24;
./configure --without-nucrses
make nsenter && cp nsenter /usr/local/bin
为了使nsenter连接到容器,还需要找到容器进程的PID,可以通过下面的命令获取:
PID=$(docker inspect --format "{{ .State.Pid }}" <container>)
通过这个PID,就可以连接到这个容器:
nsenter --target $PID --mount --uts --ipc --net --pid
eg:
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf5de6a10e7c centos6:0.2 "/bin/bash" 18 minutes ago Up 18 minutes 0.0.0.0:80->80/tcp webserver02
78b0969bc01a centos6:0.2 "/bin/bash" About an hour ago Up About an hour 0.0.0.0:81->80/tcp webserver03
6935f8fff19c d123f4e55e12 "/bin/bash" 10 days ago Up 7 days grave_pasteur
[root@private_vpn ~]#
[root@private_vpn ~]# PID=$(docker inspect --format "{{ .State.Pid }}" 6935 ) && echo $PID
11751
[root@private_vpn ~]# nsenter --target 11751 --mount --uts --ipc --net --pid
[root@6935f8fff19c /]# ls -l
total 24
-rw-r--r-- 1 root root 15836 Sep 11 15:53 anaconda-post.log
......
此方式exit退出容器时也不会停止容器。
4.5 删除容器
可以使用docker rm
命令来删除处于中止或退出状态的容器,命令格式为:
docker rm [-f|--force] [-l|--link] [-v|--volumes] container [container ...]
主要支持的选项包括:
- -f,--force=false: 是否强行中止并删除一个运行中的容器
- -l,--link=false: 删除容器的连接,但保留容器
- -v,--volumes: 删除容器挂载的数据卷
eg:
删除处于停止状态的容器,并删除:
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
13c6d062dd37 20 "/bin/bash" 11 seconds ago Exited (0) 6 seconds ago drunk_hawking
cf5de6a10e7c centos6:0.2 "/bin/bash" 31 minutes ago Up 31 minutes 0.0.0.0:80->80/tcp webserver02
78b0969bc01a centos6:0.2 "/bin/bash" About an hour ago Up About an hour 0.0.0.0:81->80/tcp webserver03
6935f8fff19c d123f4e55e12 "/bin/bash" 10 days ago Up 7 days grave_pasteur
[root@private_vpn ~]#
[root@private_vpn ~]# docker rm 13
13
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf5de6a10e7c centos6:0.2 "/bin/bash" 32 minutes ago Up 32 minutes 0.0.0.0:80->80/tcp webserver02
78b0969bc01a centos6:0.2 "/bin/bash" About an hour ago Up About an hour 0.0.0.0:81->80/tcp webserver03
6935f8fff19c d123f4e55e12 "/bin/bash" 10 days ago Up 7 days grave_pasteur
默认情况下,docker rm 命令只能删除处于停止状态的容器,并不能删除还处于运行状态的容器。
如果一定要直接删除一个正在运行中的容器,可以加-f 参数。docker 会先发送sigkill信号给容器,终止其中的应用,之后强行删除,这个在前面的文章中已经讲过,此处不再赘述。
4.6 导入和导出容器
某些时候,需要将容器从一个系统迁移到另外一个系统,此时可以使用docker的导入和导出功能。这也是docker自身提供的一个重要特性。
1. 导出容器
导出容器是指导出一个已经创建的容器到一个文件,不管此时这个容器是否处于运行状态,可以使用docker export
命令,该命令的格式为docker export [-o|--output [=""]] container
。其中,可以通过-o
选项来指定导出的tar文件名,也可以直接通过重定向来实现。
首先查看所有的容器,如下所示:
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cf5de6a10e7c centos6:0.2 "/bin/bash" 49 minutes ago Up 49 minutes 0.0.0.0:80->80/tcp webserver02
78b0969bc01a centos6:0.2 "/bin/bash" About an hour ago Up About an hour 0.0.0.0:81->80/tcp webserver03
6935f8fff19c d123f4e55e12 "/bin/bash" 10 days ago Up 7 days grave_pasteur
现在导出容器"cf5de6a10e7c"到文件webserver02.tar:
先进入容器并创建一个文件写入内容,以便测试。
[root@private_vpn ~]# docker exec -ti cf5 /bin/bash
[root@cf5de6a10e7c /]# cd /tmp/
[root@cf5de6a10e7c tmp]# echo "test" >> test.txt
然后导出容器:
[root@private_vpn ~]# docker export -o webserver02.tar cf5
[root@private_vpn ~]# ls -l
-rw------- 1 root root 990253056 Dec 9 17:55 webserver02.tar
之后就可以将导出的tar文件传输到其他服务器上,然后再通过导入命令导入到系统中,从而实现容器的迁移。
2.容器的迁移
导出的文件又可以使用docker import
命令导入变成镜像,该命令格式为:
docker import [-c|--change [=[]]] [-m|--message [=MESSAGE]] file|URL| -[repository[:tag]]
用户可以通过-c,--change=[]
选项在导入的同时执行对容器进行修改的Dockerfile指令(此处后面会讲到)。
下面将导出的webserver02.tar文件导入到系统中:
[root@private_vpn ~]# cat webserver02.tar | docker import - webserver:v0.21
sha256:e230d26d7671212bdd89dcd4373f32fe266c9dd7ae7d3419ef38300a3c9213ed
[root@private_vpn ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
webserver v0.21 e230d26d7671 6 seconds ago 967.9 MB
centos6.8 0.21 7e48bfa0489e 28 hours ago 1.052 GB
centos6 0.2 9ceeba2e3b82 3 days ago 990.2 MB
centos6 0.1 d8af0a913398 3 days ago 824.6 MB
现在基于e230d镜像创建一个容器并进入查看/tmp/test.txt文件
[root@private_vpn ~]# docker run -tid e23 /bin/bash
2717b6b2c5ae80038872c5e23ed4901a899783aa77f2af205d1337a470a70413
[root@private_vpn ~]#
[root@private_vpn ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2717b6b2c5ae e23 "/bin/bash" 7 seconds ago Up 7 seconds focused_ritchie
cf5de6a10e7c centos6:0.2 "/bin/bash" 2 days ago Up 2 days 0.0.0.0:80->80/tcp webserver02
78b0969bc01a centos6:0.2 "/bin/bash" 2 days ago Up 2 days 0.0.0.0:81->80/tcp webserver03
[root@private_vpn ~]#
[root@private_vpn ~]# docker exec -ti 2717 /bin/bash
[root@2717b6b2c5ae /]# cat /tmp/test.txt
test
可以看到test.txt文件是存在且内容相同,测试成功。
之前镜像章节中曾介绍过使用docker load
命令来导入一个镜像文件,与docker import
命令十分相似。
实际上,既可以使用docker load
命令来导入镜像存储文件到本地镜像库,也尅一使用docker import
命令来导入一个容器快照到本地镜像库
这两者的区别在于容器快照将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也更大。此外,从容器快照文件导入时可以重新指定标签等元数据信息
小结:
容器是直接提供应用服务的组件,也是docker实现快速启停和高效服务性能的基础。
在生产环境中,因为容器自身的轻量级特性,推荐在使用容器时在一组容器前引入HA(High Availability)机制。例如使用HAProxy工具来代理容器访问,这样在容器出现故障时,可以快速切换到功能正常的容器(前提是,如果这些容器跑在同一宿主机时,宿主机不出现故障)。此外,建议通过合适的容器重启策略,来自动重启退出的容器。