容器与云|Docker 容器初体验
在本文中,我们将迈出使用 Docker 的第一步,学习第一个 Docker 容器。本章还会介绍如何与 Docker 进行交互的基本知识。
1 确保 Docker 已经就绪
首先,我们会查看 Docker 是否能正常工作,然后学习基本的 Docker 的工作流:创建并管理容器。我们将浏览容器的典型生命周期:从创建、管理到停止,直到最终删除。
第一步,查看 docker 程序是否存在,功能是否正常,如代码清单 3-1 所示。
代码清单 3-1 查看 docker 程序是否正常工作
$ sudo docker info Containers: 0 Images: 0 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Dirs: 144 Execution Driver: native-0.1 Kernel Version: 3.8.0-29-generic Registry: [https://index.docker.io/v1/]
在这里我们调用了 docker 可执行程序的 info 命令,该命令会返回所有容器和镜像(镜像即是 Docker 用来构建容器的“构建块”)的数量、Docker 使用的执行驱动和存储驱动(execution and storage driver),以及 Docker 的基本配置。
在前面几章我们已经介绍过,Docker 是基于客户端-服务器构架的。它有一个 docker 程序,既能作为客户端,也可以作为服务器端。作为客户端时,docker 程序向 Docker 守护进程发送请求(如请求返回守护进程自身的信息),然后再对返回来的请求结果进行处理。
2 运行我们的第一个容器
现在,让我们尝试启动第一个 Docker 容器。我们可以使用 docker run 命令创建容器,如代码清单 3-2 所示。docker run 命令提供了 Docker 容器的创建到启动的功能,在本书中我们也会使用该命令来创建新容器。
代码清单 3-2 创建第一个容器
$ sudo docker run -i -t ubuntu /bin/bash Pulling repository ubuntu from https://index.docker.io/v1 Pulling image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c (precise) from ubuntu Pulling 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c metadata Pulling 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c fs layer Downloading 58337280/? (n/a) Pulling image b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc (quantal) from ubuntu Pulling image 27cf784147099545 () from ubuntu root@fcd78e1a3569:/#
提示:官方文档列出了完整的 Docker 命令列表,你也可以使用 docker help 获取这些命令。此外,你还可以使用 Docker 的 man 页(即执行 man docker-run)。
代码清单 3-3 所示命令的输出结果非常丰富,下面我们来逐条解析。
代码清单 3-3 docker run 命令
$ sudo docker run -i -t ubuntu /bin/bash
首先,我们告诉 Docker 执行 docker run 命令,并指定了-i 和-t 两个命令行参数。-i 标志保证容器中 STDIN 是开启的,尽管我们并没有附着到容器中。持久的标准输入是交互式 shell 的“半边天”,-t 标志则是另外“半边天”,它告诉 Docker 为要创建的容器分配一个伪 tty 终端。这样,新创建的容器才能提供一个交互式 shell。若要在命令行下创建一个我们能与之进行交互的容器,而不是一个运行后台服务的容器,则这两个参数已经是最基本的参数了。
提示:官方文档上列出了 docker run 命令的所有标志,此外还可以用命令 docker help run 查看这些标志。或者,也可以用 Docker 的 man 页(也就是执行 man docker-run 命令)。
接下来,我们告诉 Docker 基于什么镜像来创建容器,示例中使用的是 ubuntu 镜像。ubuntu 镜像是一个常备镜像,也可以称为“基础”(base)镜像,它由 Docker 公司提供,保存在 Docker HubRegistry 上。
你可以用 ubuntu 基础镜像(以及类似的 fedora、debian、centos 等镜像)为基础,在你选择的操作系统上构建自己的镜像。这里,我们基于此基础镜像启动了一个容器,并且没有对容器进行任何改动。
那么,在这一切的背后又都发生了什么呢?首先 Docker 会检查本地是否存在 ubuntu 镜像,如果本地还没有该镜像的话,那么 Docker 就会连接官方维护的 Docker Hub Registry,查看 Docker Hub 中是否有该镜像。Docker 一旦找到该镜像,就会下载该镜像并将其保存到本地宿主机中。
随后,Docker 在文件系统内部用这个镜像创建了一个新容器。该容器拥有自己的网络、IP 地址,以及一个用来和宿主机进行通信的桥接网络接口。最后,我们告诉 Docker 在新容器中要运行什么命令,在本例中我们在容器中运行/bin/bash 命令启动了一个 Bash shell。
当容器创建完毕之后,Docker 就会执行容器中的/bin/bash 命令,这时我们就可以看到容器内的 shell 了,就像代码清单 3-4 所示。
代码清单 3-4 第一个容器的 shell
root@f7cbdac22a02:/#
注意:在第 4 章中,我们将会看到如何构建自己的镜像并基于该镜像创建容器的基础知识。
3 使用第一个容器
现在,我们已经以 root 用户登录到了新容器中,容器的 IDf7cbdac22a02``,乍``看起来有些令人迷惑的字符串。这是一个完整的 Ubuntu 系统,你可以用它来做任何事情。下面我们就来研究一下这个容器。首先,我们可以获取该容器的主机名,如代码清单 3-5 所示。
代码清单 3-5 检查容器的主机名
root@f7cbdac22a02:/# hostname f7cbdac22a02
可以看到,容器的主机名就是该容器的 ID。我们再来看看/etc/hosts 文件,如代码清单 3-6 所示。
代码清单 3-6 检查容器的/etc/hosts 文件
root@f7cbdac22a02:/# cat /etc/hosts 172.17.0.4 f7cbdac22a02 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters
Docker 以在 hosts 文件中为该容器的 IP 地址添加了一条主机配置项。我们再来看看容器的网络配置情况,如代码清单 3-7 所示。
代码清单 3-7 检查容器的接口
root@f7cbdac22a02:/# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo inet6 ::1/128 scope host valid_lft forever preferred_lft forever 899: eth0: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 link/ether 16:50:3a:b6:f2:cc brd ff:ff:ff:ff:ff:ff inet 172.17.0.4/16 scope global eth0 inet6 fe80::1450:3aff:feb6:f2cc/64 scope link valid_lft forever preferred_lft forever
我们可以看到,这里有 lo 的环回接口,还有 IP 为 172.17.0.4 的标准 eth0 网络接口,和普通宿主机是完全一样的。我们还可以查看容器中运行的进程,如代码清单 3-8 所示。
代码清单 3-8 检查容器的进程
root@f7cbdac22a02:/# ps -aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 18156 1936 ? Ss May30 0:00 /bin/bash root 21 0.0 0.0 15568 1100 ? R+ 02:38 0:00 ps -aux
接下来我们要干些什么呢?安装一个软件包怎么样?如代码清单 3-9 所示。
代码清单 3-9 在第一个容器中安装软件包
root@f7cbdac22a02:/# apt-get update && apt-get install vim
通过上述命令,我们就在容器中安装了 Vim 软件。
你可以继续在容器中做任何自己想做的事情。当所有工作都结束时,输入 exit,就可以返回到 Ubuntu 宿主机的命令行提示符了。
这个容器现在怎样了?容器现在已经停止运行了!只有在指定的/bin/bash 命令处于运行状态的时候,我们容器也才会相应地处于运行状态。一旦退出容器,/bin/bash 命令也就结束了,这时容器也随之停止了运行。
但容器仍然是存在的,我们可以用 docker ps -a 命令查看当前系统中容器的列表
默认情况下,当执行 docker ps 命令时,只能看到正在运行的容器。如果指定-a 标志,选项的话,那么 docker ps 命令会列出所有容器,包括正在运行的和已经停止的。
提示:你也可以为 docker ps 命令指定-l 标志,该选项会列出最后一次运行的容器,包括正在运行和已经停止的。
从该命令的输出结果中我们可以看到关于这个容器的很多有用信息:ID、用于创建该容器的镜像、容器最后执行的命令、创建时间以及容器的退出状态(在上面的例子中,退出状态是 0,因为容器是通过正常的 exit 命令退出的)。我们还可以看到,每个容器都有一个名称。
注意:有三种方式可以指代唯一容器:短 UUID(如 f7cbdac22a02)、长 UUID(如 f7cbdac22a02e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778)或者名称(如 gray_cat)。
4 容器命名
Docker 会为我们创建的每一个容器自动生成一个随机的名称。例如,上面我们刚刚创建的容器就被命名为 gray_cat。如果想为容器指定一个名称,而不是使用自动生成的名称,则可以用--name 标志来实现,如代码清单 3-10 所示。
代码清单 3-10 给容器命名
$ sudo docker run --name bob_the_container -i -t ubuntu /bin/bash root@aa3f365f0f4e:/# exit
上述命令将会创建一个名为 bob_the_container 的容器。一个合法的容器名称只能包含以下字符:小写字母 a~z、大写字母 A~Z、数字 0~9、下划线、圆点、横线(如果用正则表达式来表示这些符号,就是[a-zA-Z0-9_.-])。
在很多 Docker 命令中,我们都可以用容器的名称来替代容器 ID,后面我们将会看到。容器名称有助于分辨容器,当构建容器和应用程序之间的逻辑连接时,容器的名称也有助于从逻辑上理解连接关系。具体的名称(如 web、db)比容器 ID 和随机容器名好记多了。我推荐大家都使用容器名称,以更加方便地管理容器。
容器的命名必须是唯一的。如果我们试图创建两个名称相同的容器,则命令将会失败。如果要使用的容器名称已经存在,可以先用 docker rm 命令删除已有的同名容器后,再来创建新的容器。
5 重新启动已经停止的容器
bob_the_container 容器已经停止了,接下来我们能对它做些什么呢?如果愿意,我们可以用下面的命令重新启动一个已经停止的容器,如代码清单 3-11 所示。
代码清单 3-11 启动已经停止运行的容器
$ sudo docker start bob_the_container
除了容器名称,我们也可以用容器 ID 来指定容器,如代码清单 3-12 所示。
代码清单 3-12 通过 ID 启动已经停止运行的容器
$ sudo docker start aa3f365f0f4e
提示:我们也可以使用 docker restart 命令来重新启动一个容器。
这时运行不带-a 标志的 docker ps 命令,就应该看到我们的容器已经开始运行了。
6 附着到容器上
Docker 容器重新启动的时候,会沿用 docker run 命令时指定的参数来运行,因此我们容器重新启动后会运行一个交互式会话 shell。此外,我们也可以用 docker attach 命令,重新附着到该容器的会话上,如代码清单 3-13 所示。
代码清单 3-13 附着到正在运行的容器
$ sudo docker attach bob_the_container
我们也可以使用容器 ID,重新附着到容器的会话上,如代码清单 3-14 所示。
代码清单 3-14 通过 ID 附着到正在运行的容器
$ sudo docker attach aa3f365f0f4e
现在,我们又重新回到了容器的 Bash 提示符,如代码清单 3-15 所示。
代码清单 3-15 重新附着到容器的会话
root@aa3f365f0f4e:/#
提示:你可能需要按下回车键才能进入该会话。
如果退出容器的 shell,容器也会随之停止运行。
7 创建守护式容器
除了这些交互式运行的容器(interactive container),我们也可以创建长期运行的容器。守护式容器(daemonized container)没有交互式会话,非常适合运行应用程序和服务。大多数时候我们都需要以守护式来运行我们的容器。下面我们就来启动一个守护式容器,如代码清单 3-16 所示。
代码清单 3-16 创建长期运行的容器
$ sudo docker run --name daemon_dave -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" 1333bb1a66af402138485fe44a335b382c09a887aa9f95cb9725e309ce5b7db3
我们在上面的 docker run 命令使用了-d 参数,因此 Docker 会将容器放到后台运行。
我们还在容器要运行的命令里使用了一个 while 循环,该循环会一直打印 hello world,直到容器或其进程停止运行。
通过组合使用上面的这些参数,你可以发现 docker run 命令并没有像上一个容器一样将主机的控制台附着到新的 shell 会话上,而是仅仅返回了一个容器 ID 而已,我们还是在主机的命令行之中。如果我们执行 docker ps 命令,可以看到一个正在运行的容器,如代码清单 3-17 所示。
代码清单 3-17 查看正在运行的 daemon_dave 容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1333bb1a66af ubuntu:14.04 /bin/sh -c 'while tr 32 secs ago Up 27 daemon_dave
8 容器内部都在干些什么
现在我们已经有了一个在后台运行 while 循环的守护型容器。为了探究该容器内部都在干些什么,我们可以用 docker logs 命令来获取容器的日志,如代码清单 3-18 所示。
代码清单 3-18 获取守护式容器的日志
$ sudo docker logs daemon_dave hello world hello world hello world hello world hello world hello world hello world . . .
这里,我们可以看到 while 循环正在向日志里打印 hello world。Docker 会输出最后几条日志项并返回。我们也可以在命令后使用-f 参数来监控 Docker 的日志,这与 tail -f 命令非常相似,如代码清单 3-19 所示。
代码清单 3-19 跟踪守护式容器的日志
$ sudo docker logs -f daemon_dave hello world hello world hello world hello world hello world hello world hello world . . .
提示:可以通过 Ctrl+C 退出日志跟踪。
我们也可以跟踪容器日志的某一片段,和之前类似,只需要在 tail 命令后加入-f --lines 标志即可。例如,可以用 docker logs --tail 10 daemon_dave 获取日志的最后 10 行内容。另外,也可以用 docker logs --tail 0 -f daemon_dave 命令来跟踪某个容器的最新日志而不必读取整个日志文件。
为了让调试更简单,我们还可以使用-t 标志为每条日志项加上时间戳,如代码清单 3-20 所示。
代码清单 3-20 跟踪守护式容器的最新日志
$ sudo docker logs -ft daemon_dave [May 10 13:06:17.934] hello world [May 10 13:06:18.935] hello world [May 10 13:06:19.937] hello world [May 10 13:06:20.939] hello world [May 10 13:06:21.942] hello world . . .
提示:同样,可以通过 Ctr+C 退出日志跟踪。
9 查看容器内的进程
除了容器的日志,我们也可以查看容器内部运行的进程。要做到这一点,要使用 docker top 命令,如代码清单 3-21 所示。
代码清单 3-21 查看守护式容器的进程
$ sudo docker top daemon_dave
该命令执行后,我们可以看到容器内的所有进程(主要还是我们的 while 循环)、运行进程的用户及进程 ID,如代码清单 3-22 所示。
代码清单 3-22 docker``top 命令的输出结果
PID USER COMMAND 977 root /bin/sh -c while true; do echo hello world; sleep 1; done 1123 root sleep 1
10 在容器内部运行进程
在 Docker 1.3 之后,我们也可以通过 docker exec 命令在容器内部额外启动新进程。可以在容器内运行的进程有两种类型:后台任务和交互式任务。后台任务在容器内运行且没有交互需求,而交互式任务则保持在前台运行。对于需要在容器内部打开 shell 的任务,交互式任务是很实用的。下面我们先来看一个后台任务的例子,如代码清单 3-23 所示。
代码清单 3-23 在容器中运行后台任务
$ sudo docker exec -d daemon_dave touch /etc/new_config_file
这里的-d 标志表明需要运行一个后台进程,-d 标志之后,指定的是要在内部执行这个命令的容器的名字以及要执行的命令。上面例子中的命令会在 daemon_dave 容器内创建了一个空文件,文件名为/etc/new_config_file。通过 docker exec 后台命令,我们可以在正在运行的容器中进行维护、监控及管理任务。
我们也可以在 daemon_dave 容器中启动一个诸如打开 shell 的交互式任务,如代码清单 3-24 所示。
代码清单 3-24 在容器内运行交互命令
$ sudo docker exec -t -i daemon_dave /bin/bashVersion:
和运行交互容器时一样,这里的-t 和-i 标志为我们执行的进程创建了 TTY 并捕捉 STDIN。接着我们指定了要在内部执行这个命令的容器的名字以及要执行的命令。在上面的例子中,这条命令会在 daemon_dave 容器内创建一个新的 bash 会话,有了这个会话,我们就可以在该容器中运行其他命令了。
注意:docker exec 命令是 Docker 1.3 引入的,早期版本并不支持该命令。对于早期 Docker 版本,请参考第 6 章中介绍的 nsenter 命令。
11 停止守护式容器
要停止守护式容器,只需要执行 docker stop 命令,如代码清单 3-25 所示。
代码清单 3-25 停止正在运行的 Docker 容器
$ sudo docker stop daemon_dave
当然,也可以用容器 ID 来指代容器名称,如代码清单 3-26 所示。
代码清单 3-26 通过容器 ID 停止正在运行的容器
$ sudo docker stop c2c4e57c12c4
注意:docker stop 命令会向 Docker 容器进程发送 SIGTERM 信号。如果你想快速停止某个容器,也可以使用 docker kill 命令来向容器进程发送 SIGKILL 信号。
要想查看已经停止的容器的状态,则可以使用 docker ps 命令。还有一个很实用的命令 docker ps -n x,该命令会显示最后 x 个容器,不论这些容器正在运行还是已经停止。
12 自动重启容器
如果由于某种错误而导致容器停止运行,我们还可以通过--restart 标志,让 Docker 自动重新启动该容器。--restart 标志会检查容器的退出代码,并据此来决定是否要重启容器。默认的行为是 Docker 不会重启容器。
代码清单 3-27 是一个在 docker run 命令中使用—restart 标志的例子。
代码清单 3-27 自动重启容器
$ sudo docker run --restart=always --name daemon_dave -d ubuntu / bin/sh -c "while true; do echo hello world; sleep 1; done"
在本例中,--restart 标志被设置为 always。无论容器的退出代码是什么,Docker 都会自动重启该容器。除了 always,我们还可以将这个标志设为 on-failure,这样,只有当容器的退出代码为非 0 值的时候,才会自动重启。另外,on-failure``还接受``一个可选的重启次数参数,如代码清单 3-28 所示。
代码清单 3-28 为 on-failure 指定 count 参数
--restart=on-failure:5
这样,当容器退出代码为非 0 时,Docker 会尝试自动重启该容器,最多重启 5 次。
注意:--restart 标志是 Docker1.2.0 引入的选项。
13 深入容器
除了通过 docker ps 命令获取容器的信息,我们还可以使用 docker inspect``来获得更多的容器信息,如代码清单 3-29 所示。
代码清单 3-29 查看容器
$ 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", . . .
docker inspect 命令会对容器进行详细的检查,然后返回其配置信息,包括名称、命令、网络配置以及很多有用的数据。
我们也可以用-f 或者--format 标志来选定查看结果,如代码清单 3-30 所示。
代码清单 3-30 有选择地获取容器信息
$ sudo docker inspect --format='{{ .State.Running }}' daemon_dave false
上面这条命令会返回容器的运行状态,示例中该状态为 false。我们还能获取其他有用的信息,如容器 IP 地址,如代码清单 3-31 所示。
代码清单 3-31 查看容器的 IP 地址
$ sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' daemon_dave 172.17.0.2
提示:--format 或者-f 标志远非表面看上去那么简单。该标志实际上支持完整的 Go 语言模板。用它进行查询时,可以充分利用 Go 语言模板的优势。
我们也可以同时指定多个容器,并显示每个容器的输出结果,如代码清单 3-32 所示。
代码清单 3-32 查看多个容器
$ sudo docker inspect --format '{{.Name}} {{.State.Running}}' daemon_dave bob_the_container /daemon_dave false /bob_the_container false
我们可以为该参数指定要查询和返回的查看散列(inspect hash)中的任意部分。
注意:除了查看容器,你还可以通过浏览/var/lib/docker 目录来深入了解 Docker 的工作原理。该目录存放着 Docker 镜像、容器以及容器的配置。所有的容器都保存在/var/lib/docker/containers 目录下。
14 删除容器
如果容器已经不再使用,可以使用 docker rm 命令来删除它们,如代码清单 3-33 所示。
代码清单 3-33 删除容器
$ sudo docker rm 80430f8d0921 80430f8d0921
注意:需要注意的是,运行中的 Docker 容器是无法删除的!你必须先通过 docker stop 或 docker kill 命令停止容器,才能将其删除。
目前,还没有办法一次删除所有容器,不过可以通过代码清单 3-34 所示的小技巧来删除全部容器。
代码清单 3-34 删除所有容器
docker rm `docker ps -a -q`
上面的 docker ps 命令会列出现有的全部容器,-a 标志代表列出所有(all)容器,而-q 标志则表示只需要返回容器的 ID 而不会返回容器的其他信息。这样我们就得到了容器 ID 的列表,并传给了 docker rm 命令,从而达到删除所有容器的目的。
小结
在本章中我们介绍了 Docker 容器的基本工作原理。这里学到的内容也是本书剩余章节中学习如何使用 Docker 的基础。
本文摘自《 第一本 Docker 书 》
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论