ch04 管理容器
前边一直在介绍如何构建镜像, 下面看看如何管理容器, 以及使用命令行与之交互.
本章会涉及到与第一章相同的命令, 但是会更加深入. 一旦熟悉了这些命令, 就可以去看看 Docker networks 以及 Docker Volumes.
本章主要内容:
- 理解容器命令
- network 和 volume
- 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
, 表示不限制.
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
)
执行命令后, 并再查看 inspect
再次查看状态:
退出状态查看界面, 按下两次 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
docker container pause nginx1
docker container unpause nginx1
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
.
删除容器
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. 按照作者给出的内容是:
最后一行是一个真实的 IP 地址, 表示本地的一个容器. 对应的 e7335ca1830d
是一个容器的 ID.
这里没有 redis 的签名入口, 然后查看 /etc/resolv.conf
:
docker container exec moby-counter cat /etc/resolv.conf
按照作者的显示为:
这里使用了本地 nameserver
. 执行下面命令查看 DNS 解析:
docker container exec moby-counter nslookup redis 127.0.0.11
输出 redis 容器的 IP 地址 (作者的返回)
然后创建另一个网络, 并运行另一个应用
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
. 作者返回的内容是:
由于容器 moby-counter2
运行在与 redis
容器不同的网络中, 因此无法解析容器中的主机名.
docker container exec moby-counter2 nslookup redis 127.0.0.11
因此返回 bad address
的错误:
下面在第二个网络中运行第二个 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
可以看到返回结果为 (作者的截图)
可以看到 redis
实际上是 redis2.moby-counter2
, 它被解析到 172.19.0.3
.
至此, 我们在本地 Docker 主机上独立运行了两套应用程序, 它们是互相隔离的. 可以分别使用 http://localhost:8080
和 http://localhost:9090
来访问它们.
运行 docker network ls
会显示出本地所有的网络配置, 包括默认网络 (作者的截图):
执行下面命令, 可以查看更多网络信息:
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