jk's notes
  • ch04 管理容器

ch04 管理容器

前边一直在介绍如何构建镜像, 下面看看如何管理容器, 以及使用命令行与之交互.

本章会涉及到与第一章相同的命令, 但是会更加深入. 一旦熟悉了这些命令, 就可以去看看 Docker networks 以及 Docker Volumes.

本章主要内容:

  1. 理解容器命令
  2. network 和 volume
  3. Docker Desktop Dashboard

4.1 基本要求 (略)

4.2 理解 Docker 容器命令

understand docker container command

在深入复杂命令之前, 先回顾一下之前章节中使用过的命令.

4.2.1 基础 (仅仅是复习)

复习了使用 docker container run ... 运行 hello-world 镜像, 以及在 Dockerfile 中运行命令的指令. 并描述容器内程序运行结束, 容器也会结束.

并使用 docker container ls -a 来查看所有容器, 并说明了 -a 作用. 无该选项只会显示出正在运行的容器. -a 表示 --all, 显示所有的容器.

然后说明了容器的名字, Docker 会自动生成名字. 然后介绍了利用名字来删除镜像: docker conrainer rm ....

然后利用第一章结束时, 运行 nginx 的案例复习了 -d (--detach), --name, -p 参数的含义: 后台运行, 自定义名字, 以及端口映射 (当前主机端口:容器内端口).

作者还对比了, 带有 -d 和不带 -d 运行 nginx 时终端的不同表现.

然后简要说明了 stdin, stdout, 以及 stderr 的含义.

然后说明了可以在主机上运行多个容器, 映射主机上的不同的端口, 但是如果端口已被使用, 是会报错的.

然后补充, 运行的容器都是独立的, 只有在映射端口的时候, 才有可能出现冲突.

作者表示, 可以将 nginx 容器保持运行, 以便在后面小结好演示.

4.2.2 与容器交互

保证运行 nginx 容器, 下面介绍如何与这个运行着的容器进行交互.

attach

docker container attach <容器名>

将终端附加到运行的容器中, 如此相当于进入了容器所在上下文环境.

输入 Ctrl + C 退出同时容器也会停止. 使用下面命令可以重启容器:

docker container start <容器名>

注意这个运行会沿用创建容器时的参数. 包括 -d, -p 等.

如果在退出时, 依旧需要保持 nginx 处于运行状态, 可以带上 --sig-proxy=false 参数.

docker container attach --sig-proxy=false <容器名>

exec

attach 很方便, 但需要更强的交互性, 就需要使用 exec 了.

相对于 nginx, 使用 attach 相当于进入 nginx 的进程. 可以查看 nginx 的实时日志, 但是无法处理其他命令. 逻辑上 attach 到一个进程相当于, 不使用 -d 运行进程的状态.

docker container exec <容器名> 命令 参数

表示进入正在运行的容器, 并在容器中执行另一个命令. 常用的是:

docker container exec -i -t <容器名> /bin/bash

其中 -i 是 --interactive 的简写. 该指令是 docker 提供的, 目的是保持 stdin 开启状态, 从而保证我们可以将命令传递给进程. 而 -t 是 --tty 的缩写, 表示在会话中申请伪 tty.

早期使用电传打印机 (teletypewriters) 来连接计算机, 今天已经没有该设备, 但该简称被保留下来, 一般用来表示文本控制台.

作者不建议对容器做过多修改, 因为使用完容器会被删掉.

现在可以连接到容器内了, 下面介绍一些其他工具.

4.2.3 日志与进程信息

主要目的是, 不连接到容器内, 怎么去查看相关的信息.

logs

基于标准输出流与 Docker 中的日志进行查看.

docker container logs --tail 数字 <容器名>

显示最后 几行 日志信息.

要实时查看日志, 可以使用:

docker container logs -f <容器名>

其中 -f 是 --follow 的简写. 如果要查看从某个时间开始的日志, 可以使用

docker container logs --since 时间的UTC格式 <容器名>

时间格式例如: 2020-03-28T15:52:00

默认显示的日志与 --since 中的时间格式不一样, 可以在命令上带上 -s, 即 --timestamp 来显示时间戳

docker container logs --since 2020-03-28T15:52:00 -t <容器名>

top

top 与 linux 的 top 类似.

docker container top <容器名>

stats

用于显示容器的实时信息, 如果没有提供 NAME 或 ID, 则显示所有的.

它会提供 CPU, RAM, NETWORK, DISK, IO, PIDS 等信息.

它显示的是容器使用了本地资源的情况, 如果容器没有运行, 是没有值的.

4.2.4 资源限制

默认情况下, 容器可以使用所有可以使用的资源.

通常在使用 run 命令的时候就可以限制资源, 例如要限制 CPU 优先级减半, 并限制内存为 128M, 可以使用:

docker container run -d --name nginx-test --cpu-share 512 --memory 128M -p 8080:80 nginx

如果是使用 run 命令, 会自动配合 --memory 参数设置 --memory-swap 参数, 该参数需要是 --memory 的两倍.

如果 run 没有携带 --memory, 需要使用 update 命令更新参数, 但如果直接使用会报错, 因为默认的 --memory-swap 是 0, 表示不限制.

image-20240926153940070

docker update 的语法是:

docker update --argName argValue ... <containerName>

所以需要带上 --memory-swap 的参数:

docker update --cpu-shares 512 --memory 128M --memory-swap 256M nginx-test

执行命令之前查看容器状态 (docker stats nginx-test)

image-20240926154332033

执行命令后, 并再查看 inspect

image-20240926154447050

再次查看状态:

image-20240926154511015

退出状态查看界面, 按下两次 Ctrl+ C, 中间需要间隔一会儿, 等待程序退出.

需要注意的是, 设置资源限制可以使用 兆 (M) 作为单位, 但是 inspect 显示的时候使用的是字节作为单位.

4.2.5 容器状态与其他命令

pause 和 unpause

首先介绍了将容器悬挂 (暂停), 然后恢复运行的命令:

docker pause <容器名>
docker unpause <容器名>

悬挂容器 不会让容器进入停止分类中, 使用 docker container ls 可以直接查看, 不需要携带 -a 选项. 也方便恢复其运行.

作者描述, 一般在容器运行出现问题时, 只是想先暂时挂起, 后续再处理时常用.

毕竟 stop 后, 需要在 -a 中查找, 比较繁琐.

# 作者给了一个示例
for i in {1..5}; do docker container run -d --name nginx$(printf "$i") nginx; done

image-20240926163843175

docker container pause nginx1

image-20240926164350117

docker container unpause nginx1

image-20240926170357315

stop, start, restart, 和 kill

挂起与恢复, 会保存容器当时运行的状态.

停止 (stop), 与启动 (start), 则是完全关闭容器, 然后从头开始运行. 而重启 (restart) 是这两个命令的复合.

停止命令默认会立即停止, 涉及到需要延迟执行的行为可能造成数据丢失, 因此可以使用 -t 参数, --time 的简写, 来延迟停止的逻辑, 单位是秒.

docker 也提供了 kill 命令, 来立即杀死进程.

停止也是使用 发送信号量的方式进行的.

涉及命令:

docker container stop -t 10 <容器名>
docker container start <容器名>
docker container restart -t 10 <容器名>
docker container kill <容器名>

注意状态:

  • pause 后的状态依旧是 Up.
  • stop 后的状态是 Exited, 需要带上 -a 才可以查看到.
  • 而 kill 后的状态虽然也是 Exited, 但是返回码不是 0.

image-20240926174810333

删除容器

docker rm -f <容器名>

默认只能删除 Exited 的容器, 除非添加 -f 选项 (--force).

即使是 Paused 也不允许删除

批量删除可以使用

docker rm -f $(docker ps -aq)

-q 是 --quiet 的简写, 仅显示 ID.

其他命令

这部分命令比较散碎, 也不一定会常用.

create
docker create --argName argValue ... <镜像名>

作用与 run 类似, 但它不会运行容器. 一般用于预先配置.

创建好的容器状态为 Created, 使用 start 命令启动.

port
docker port <容器名>

用于查看端口映射.

diff
docker diff <容器名>

查看容器中文件的变化. 似乎使用的比较少.

cp
docker cp <容器名>:路径 文件
docker cp 文件 <容器名>:路径

用于将容器中的文件拷贝到本地, 或将本地文件拷贝进容器.

一般使用卷, 不会直接进行文件的拷贝.

4.3 网络与卷

Docker networking

jk: 这一块的难点依旧是 Linux 网络的内容.

现如今都是单一扁平网络环境中上运行容器. 也就是说没有划分网络, 该网络中的容器可以互相通信.

作者为了方便描述, 设计了两个容器, 与运行环境.

  • 作者准备了两个容器, 一个用于运行 Redis.
  • 另一个运行我们的应用程序, 该程序会使用 Redis 容器来存储系统状态.

首先下载镜像, 并创建网络

docker image pull redis:alpine
docker image pull russmckendrick/moby-counter
docker network create moby-counter

其中 russmckendrick/moby-counter 是作者准备的应用程序, 它会使用 redis 容器.

然后运行 redis 容器

docker container run -d --name redis --network moby-counter redis:alpine

注意这里指定了网络. redis 容器会运行在其中. 然后使用下面命令运行我们的应用程序.

docker container run -d --name moby-counter --network moby-counter -p 8080:80 runssmckendrick/moby-counter

该容器也指定了 --network, 同时将本地 8080 与容器内 80 端口连接起来.

这里不需要担心 redis 暴露的端口号. 这是因为 redis 默认使用 6379 的端口.

在外部 (主机中), 使用 http://localhost:8080/ 来访问我们的应用程序.

应用程序的交互, 这里略去. 简单的说, 应用程序记录用户点击浏览器中的坐标, 然后通过访问 redis, 记录坐标.

那么应用程序怎么连接 redis 呢? 在应用程序的 server.js 文件中:

var port = opts.redis_port || process.env.USE_REDIS_PORT || 6379
var host = opts.redis_host || process.env.USE_REDIS_HOST || 'redis'

居然是用 js 写的服务端.

因为在运行容器时没有做指定, 所以我们的应用程序使用 redis:6379 来连接 redis 容器. 要探究其细节

  • 首先使用 exec 进入应用程序容器
  • 然后在容器中 ping redis
docker container exec moby-counter ping -c 3 redis

ping 3 次, 并会显示出目标 IP

然后查看容器中的 host 文件:

docker container exec moby-counter cat /etc/hosts

可以发现其中并没有 redis. 按照作者给出的内容是:

image-20240929204349128

最后一行是一个真实的 IP 地址, 表示本地的一个容器. 对应的 e7335ca1830d 是一个容器的 ID.

这里没有 redis 的签名入口, 然后查看 /etc/resolv.conf:

docker container exec moby-counter cat /etc/resolv.conf

按照作者的显示为:

image-20241008161827546

这里使用了本地 nameserver. 执行下面命令查看 DNS 解析:

docker container exec moby-counter nslookup redis 127.0.0.11

输出 redis 容器的 IP 地址 (作者的返回)

image-20241008161843706

然后创建另一个网络, 并运行另一个应用

docker network create moby-counter2
docker run -itd --name moby-counter2 --network moby-counter2 -p 9090:80 russmckendrick/moby-counter

此时, 已经有了第二个运行在容器中的应用程序. 下面通过该容器来 ping redis.

docker container exec moby-counter2 ping -c 3 redis

然后报错了. 按照作者的返回是 bad address 'redis'.

然后查看 resolv.conf 文件:

docker container exec moby-counter2 cat /etc/resolv.conf

从输出的结果来看, 确实使用了 nameserver. 作者返回的内容是:

image-20241008162430378

由于容器 moby-counter2 运行在与 redis 容器不同的网络中, 因此无法解析容器中的主机名.

docker container exec moby-counter2 nslookup redis 127.0.0.11

因此返回 bad address 的错误:

image-20241008163416005

下面在第二个网络中运行第二个 Redis 服务. 由于 redis 容器已存在, 这里使用 redis2. 那么问题来了, 我们的程序容器会去解析 redis, 那么我们是否需要修改容器? 答案是不用, Docker 可以处理.

虽然容器不允许重名, 但是我们可以发现, 第二个网络与第一个网络环境是完全隔离的. 也就是说, 在第二个网络环境中依旧可以使用 DNS 名 redis. 我们只需要添加一个选项 --network-alias.

docker container run -d --name redis2 --network moby-counter2 --network-alias redis redis:alpine

上面的命令给容器命名为 redis2. 但是使用了 --network-alias redis.

docker container exec moby-counter2 nslookup redis 127.0.0.1

可以看到返回结果为 (作者的截图)

image-20241008165139233

可以看到 redis 实际上是 redis2.moby-counter2, 它被解析到 172.19.0.3.

至此, 我们在本地 Docker 主机上独立运行了两套应用程序, 它们是互相隔离的. 可以分别使用 http://localhost:8080 和 http://localhost:9090 来访问它们.

运行 docker network ls 会显示出本地所有的网络配置, 包括默认网络 (作者的截图):

image-20241008165613396

执行下面命令, 可以查看更多网络信息:

docker network inspect moby-counter

该命令会返回网络基本信息, 以及 IP 地址, 子网等 IP 管理系统信息, 然后是已连接的容器等信息.

jk: 这里就是计算机网络的知识了. 确实挺综合的.

注意: IP 地址管理 (IP address management, IPAM) 就是在网络中分配, 跟踪, 以及管理 IP.

IPAM 包含 DNS 和 DHCP 服务. 一个服务发生变化会通知另一个服务更新.

然后作者为了继续后续的内容, 删除应用与对应网络:

docker container stop moby-count2 redis2
docker container prune
docker network prune

至此还在单个 Docker 主机上访问, 后续内容会看到多个主机的情况.

Docker Volumes

接上面案例, 应该有两个运行的容器: 一个应用, 一个 redis. 在浏览器中访问时, 可以看到之前的记录.

然后作者计划删除 redis, 再查看会发生什么.

docker container stop redis
docker container rm redis

如果此时浏览器处于打开状态, 会出现一个等待的交互交过. 这是在等待 Redis 容器 (这都是作者代码的兼容处理, 自己写估计就报错了).

然后重新运行一个 redis, 使用下面命令 (注意之前已被删除)

docker container run -d --name redis --network moby-counter redis:alpine
Last Updated:
Contributors: jk