Docker 教程

Docker 日志与性能监控

容器日志和性能监控是了解应用行为、诊断故障以及确保 Docker 容器健康高效运行的必备利器。

正如你在前面章节中学到的,容器为应用程序提供了隔离的环境。虽然这种隔离带来了许多好处,但也意味着直接在宿主机操作系统上查看应用输出或系统指标的传统方法可能不再适用。

日志(Logs)提供了容器化应用程序内部发生所有事情的历史记录,从启动信息到错误报告以及应用特定的输出。另一方面,性能监控(Performance Monitoring)为你提供容器资源消耗的实时洞察,帮助你识别性能瓶颈、优化配置并防止服务中断。在任何环境中,精通这两个方面对于有效管理和排查容器化应用都至关重要。

1. 访问容器日志

容器日志是观察容器内部应用程序运行状态的首要途径。Docker 会自动捕获在容器内运行的进程的标准输出(stdout)和标准错误(stderr)流。

默认情况下,这些数据流由 Docker 的日志驱动(通常是 json-file)收集,并存储在运行 Docker 守护进程的宿主机系统上。这使你能够轻松检索和查看历史及实时日志,用于调试、审计以及全面了解应用的运行情况。

1.1 玩转 docker logs 命令

docker logs 命令是你从运行中或已停止的容器检索日志的主要工具。它提供了几个强大的选项,可以根据你的需求过滤和显示输出。

让我们用一个常见的 Web 服务器 Nginx 来演示。

基础日志检索:
要获取容器累积的所有日志,你可以使用它的名称或 ID。首先,让我们运行一个 Nginx 容器:

docker run -d --name my-nginx -p 8080:80 nginx

这条命令在后台(-d)启动了一个 Nginx 容器,命名为 my-nginx,并将宿主机的 8080 端口映射到容器的 80 端口。

现在,查看它的日志:

docker logs my-nginx

你将看到 Nginx 的启动日志,包括配置详情和服务器准备好接受连接的消息。如果你在浏览器中访问 http://localhost:8080,然后再运行一次 docker logs my-nginx,你就会看到 HTTP 访问请求的新记录。

实时追踪日志 (--follow-f):
为了主动监控一个应用,特别是在开发或调试期间,你通常需要看日志是如何实时生成的。--follow 选项可以将新的日志条目实时推送到你的终端。

docker logs -f my-nginx

这条命令会持续显示出现的新日志。当这个命令运行时,尝试多次访问 http://localhost:8080,你会看到访问日志立刻弹出来。要停止追踪,按下 Ctrl+C

显示最近的日志 (--tail):
有时你只需要最新的几条日志,可能是为了快速检查最近的错误,而不想翻阅庞大的历史日志。--tail 选项允许你指定从日志末尾开始显示的行数。

docker logs --tail 10 my-nginx

这将显示 my-nginx 容器生成的最后 10 行日志。

按时间过滤日志 (--since):
在排查特定时间发生的故障时,你可能想查看某个时间戳之后生成的日志。--since 选项允许你指定一个具体的时间戳或相对的时间段。

# 示例 1:查看特定时间之后的日志
docker logs --since "2023-01-01T10:00:00Z" my-nginx

# 示例 2:查看过去 5 分钟的日志
docker logs --since "5m" my-nginx

# 示例 3:查看过去 1 小时的日志
docker logs --since "1h" my-nginx

这在定位与特定事件或部署窗口相关的日志时非常有用。

为日志添加时间戳 (--timestamps-t):
默认情况下,某些应用程序的标准输出可能不包含时间戳。Docker 本身可以为它存储的日志条目加上时间戳。-t 选项会显示每条日志的时间戳,表明 Docker 接收到该日志行的确切时间。

docker logs -t my-nginx

这对于关联不同日志中的事件,或理解单个容器内各项操作的耗时非常有帮助。

1.2 日志驱动 (Log Drivers)

Docker 使用日志驱动来收集和存储容器日志。当你运行 docker logs 时,你实际上是在与默认的 json-file 日志驱动交互。这个驱动将日志写入宿主机上的一个 JSON 文件中。

虽然 json-file 非常适合本地开发和基础调试,但它并不适合需要集中式日志管理的大规模生产环境。对于这些场景,Docker 支持多种其他日志驱动,可以将日志发送到外部服务,例如:

  • syslog: 将容器日志发送到 Syslog 服务器。
  • journald: 将容器日志发送到 systemd journal。
  • fluentd: 将容器日志发送到 Fluentd 收集器。
  • awslogs: 将容器日志发送到 Amazon CloudWatch Logs。
  • gcp-json: 将容器日志发送到 Google Cloud Logging。

配置这些驱动通常需要在运行容器时指定,或者将它们设置为 Docker 守护进程的默认驱动。例如,使用 syslog 驱动运行容器:

docker run -d --log-driver syslog --name my-app-with-syslog your-image

重要提示: docker logs 命令适用于 json-filejournald 驱动。如果容器配置了其他日志驱动,docker logs 将无法直接检索其日志,因为日志已经被发送到外部了。在这种情况下,你需要使用所选日志服务提供的工具(例如 Fluentd 仪表板,CloudWatch 控制台)来查看日志。在本章中,我们将继续专注于 docker logs 直接交互的默认 json-file 驱动。

2. 监控容器性能

除了日志,了解你的容器消耗了多少 CPU、内存、网络和磁盘 I/O 对于性能优化和容量规划至关重要。Docker 提供了一个内置命令 docker stats,它能提供容器资源使用情况的实时数据流。

2.1 读懂 docker stats

docker stats 命令显示一个或多个运行中容器的实时资源使用统计信息流。它类似于 Linux 中的 top 命令或 Windows 中的任务管理器,但专门针对 Docker 容器。

让我们先运行一个执行 CPU 密集型工作的简单容器(例如计算素数),以便我们观察其资源使用情况。

docker run -d --name prime-calculator alpine/git:latest sh -c "i=0; while true; do echo \"Prime $(factor \$i | wc -w)\"; i=\$((i+1)); done"

这条命令在一个 alpine/git 容器中运行一个简单的 Shell 脚本,不断计算因数,从而让 CPU 保持繁忙。

现在,打开一个新终端并运行:

docker stats

你将看到一个默认每秒更新一次的表格,显示所有运行中容器的统计信息。对于我们的 prime-calculator 容器,你会观察到类似如下的输出:

CONTAINER IDNAMECPU %MEM USAGE / LIMITMEM %NET I/OBLOCK I/OPIDS
23b4c5d6e7f8prime-calculator98.00%10.2MiB / 2GiB0.50%726B / 0B0B / 0B1
a1b2c3d4e5f6my-nginx0.05%5.1MiB / 2GiB0.25%2.34kB / 2.34kB0B / 0B7

让我们拆解一下关键列:

  • CONTAINER ID: 容器的简短 ID。
  • NAME: 你分配给容器的名称或其自动生成的名称。
  • CPU %: 容器当前使用的宿主机 CPU 百分比。高 CPU 百分比表示计算繁重。对于单核应用,100% 意味着它完全利用了一个核心。对于多核,它可以超过 100%。
  • MEM USAGE / LIMIT: 容器当前使用的内存量,以及容器可用的总内存限制。如果没有显式设置限制,它会显示宿主机系统上的可用总内存。
  • MEM %: 容器当前使用的已分配限制的百分比。如果没有设置限制,则是总宿主机内存的百分比。
  • NET I/O: 容器通过其网络接口接收 (Rx) 和发送 (Tx) 的数据量。用于监控网络流量。
  • BLOCK I/O: 容器从块设备读取和写入块设备的数据量。这反映了磁盘活动。
  • PIDS: 容器内当前运行的进程或线程数。

你可以通过提供容器名称或 ID 来指定要监控哪些容器:

docker stats prime-calculator my-nginx

要停止 docker stats,按下 Ctrl+C

2.2 设置资源限制 (Resource Limits)

使用 docker stats 监控资源使用情况可以帮助你观察性能,但 Docker 也允许你通过对容器设置资源限制来管理性能。这对于防止单个容器垄断宿主机资源并影响其他服务至关重要。

你可以使用 docker run 选项设置 CPU 和内存限制:

  • --cpus <value>: 指定容器可以使用的最大 CPU 量。这是一个浮点数,代表 CPU 核心数。例如,--cpus 0.5 意味着容器最多可以使用单核 CPU 的 50%。
  • --memory <value>: 指定容器可以使用的最大 RAM 量。你可以使用后缀如 b, k, m, g(例如,512m, 1g)。

让我们带上 CPU 限制重新启动我们的 prime-calculator,并观察变化。首先,停止并删除现有的:

docker stop prime-calculator
docker rm prime-calculator

现在,以 0.5 核(单核的 50%)的 CPU 限制运行它:

docker run -d --name prime-calculator-limited --cpus 0.5 alpine/git:latest sh -c "i=0; while true; do echo \"Prime $(factor \$i | wc -w)\"; i=\$((i+1)); done"

然后,再次使用 docker stats 监控:

docker stats

你会注意到 prime-calculator-limitedCPU % 现在徘徊在 50% 左右,即使里面的应用程序试图使用更多。Docker 在主动限制其 CPU 使用率。这演示了你如何防止极其消耗资源的应用影响同一宿主机上的其他服务。

同样,你可以设置内存限制:

docker run -d --name memory-hog --memory 128m alpine/git:latest sh -c "dd if=/dev/zero of=/dev/null bs=1M count=10000; sleep 10000"

这个容器试图分配远超 128MB 的内存。不久之后,Docker 很可能会以 OOMKilled (Out Of Memory Killed,内存溢出被杀) 状态终止该容器,因为它超出了允许的内存限制。你可以通过检查 docker ps -a 并观察 memory-hogSTATUS 列来验证这一点。

设置合适的资源限制是容器管理的关键环节,可确保共享同一 Docker 宿主机的应用之间的稳定性和公平性。docker stats 命令对于验证你的限制是否有效,或是否需要根据实际使用模式进行调整来说非常宝贵。

3. 综合实战演示

让我们把这些概念应用到一个更真实的场景中:一个记录请求并可能消耗一些资源的简单 Web 应用程序。我们将使用 Python Flask 应用来完成此操作。

1. 创建一个 Python Flask 应用程序:
首先,创建一个目录和一个简单的 Flask 应用。

mkdir flask-app
cd flask-app

创建 app.py:

# app.py
from flask import Flask, request, jsonify
import time
import os

app = Flask(__name__)

@app.route('/')
def hello():
    app.logger.info(f"收到来自 {request.remote_addr} 对 / 的请求")
    return "Hello, Docker! 这是主页。"

@app.route('/slow')
def slow_endpoint():
    app.logger.info(f"收到来自 {request.remote_addr} 对 /slow 的请求 - 模拟工作中...")
    # 模拟 CPU 密集型工作
    result = 0
    for i in range(1, 10000000):
        result += i * i
    time.sleep(0.1) # 小延迟使其更明显
    app.logger.info(f"完成了来自 {request.remote_addr} 的 /slow 请求。结果: {result}")
    return jsonify({"message": "这是一个缓慢的响应!", "result": result})

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(debug=True, host='0.0.0.0', port=port)

创建 requirements.txt:

Flask

2. 创建 Dockerfile:

# Dockerfile
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

3. 构建 Docker 镜像:

docker build -t my-flask-app .

4. 运行容器并访问日志:
现在,运行 Flask 应用程序容器。

docker run -d --name flask-web --memory 256m --cpus 0.5 -p 5000:5000 my-flask-app

我们运行它时设置了 256MB 的内存限制和 0.5 核的 CPU 限制。

打开一个新终端来追踪日志:

docker logs -f flask-web

在你的浏览器或使用 curl,制造一些请求:

# 在另一个独立的终端或浏览器中:
curl http://localhost:5000
curl http://localhost:5000/slow
curl http://localhost:5000

观察运行着 docker logs -f flask-web 的终端里的日志。你会看到来自 Flask 应用的 INFO 消息实时出现。

5. 使用 docker stats 监控性能:
打开另一个新终端并运行:

docker stats flask-web

现在,不断地请求 /slow 接口:

# 在一个独立的终端中,循环请求慢速接口
while true; do curl http://localhost:5000/slow; sleep 0.1; done

在你的 docker stats 输出中观察 flask-web 容器的 CPU %。由于我们施加了 --cpus 0.5 限制,你应该看到它持续徘徊在 50% 左右,即使应用正试图做更多的工作。MEM USAGE 会显示一些活动,但应远低于 256MiB 限制。如果 /slow 接口极其耗费内存并超过 256MB,Docker 将会终止该容器。

这个演示清晰地展示了日志如何帮助你理解应用流程和调试,而 docker stats 和资源限制使你能够控制和监控应用的资源占用。