一、为什么容器日志管理这么麻烦?
当我们在使用Docker跑应用的时候,经常会遇到这样的困扰:日志去哪了?怎么查看?为什么突然就占满磁盘了?这就像把东西都装进集装箱后,突然发现找不到装箱单一样让人头疼。
传统虚拟机时代,日志通常老老实实地躺在/var/log目录下。但容器是临时性的,它的文件系统也是临时的,这就带来了三个典型问题:
- 容器重启后日志就消失了
- 多个容器的日志分散在各处
- 日志量快速增长可能撑爆磁盘
举个例子,我们有个简单的Node.js应用:
// 技术栈:Node.js + Docker
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log('有人访问了首页'); // 这条日志会去哪?
res.send('欢迎光临');
});
app.listen(3000, () => {
console.log('服务已启动'); // 这个又去哪了?
});
如果直接docker run启动这个容器,这些console.log输出的日志默认会存在容器内部。一旦容器停止,日志就跟着消失了。
二、Docker日志的四种基础处理方式
1. 最偷懒的做法 - 什么都不做
直接使用Docker默认的json-file日志驱动,这是新手最容易掉进的坑。日志会堆积在/var/lib/docker/containers/目录下,直到有一天磁盘报警。
# 查看容器日志(当容器还在运行时)
docker logs <容器ID>
# 查看日志文件位置(需要root权限)
sudo ls -lh /var/lib/docker/containers/<容器ID>/<容器ID>-json.log
2. 稍微好点 - 限制日志大小
在docker run时加上日志大小限制:
docker run --log-opt max-size=10m --log-opt max-file=3 my-app
这样单个日志文件最大10MB,最多保留3个文件。虽然治标不治本,但至少不会让日志把磁盘撑爆。
3. 推荐做法 - 把日志输出到标准输出
Docker最佳实践是让应用把日志直接打到stdout和stderr,然后由Docker接管:
// 修改后的Node.js应用
const express = require('express');
const morgan = require('morgan'); // 专门用来记录HTTP访问日志的中间件
const app = express();
app.use(morgan('combined')); // 使用Apache标准格式输出到stdout
app.get('/', (req, res) => {
console.log('有人访问了首页'); // 现在会输出到stdout
res.send('欢迎光临');
});
4. 专业方案 - 使用日志收集系统
当你有多个容器时,就需要像ELK、Fluentd这样的专业工具了。这里以Fluentd为例:
# docker-compose.yml示例
version: '3'
services:
app:
image: my-node-app
logging:
driver: "fluentd"
options:
fluentd-address: localhost:24224
tag: docker.my-app
fluentd:
image: fluent/fluentd
ports:
- "24224:24224"
volumes:
- ./fluentd.conf:/fluentd/etc/fluent.conf
三、生产环境日志收集实战
场景1:单机多容器日志收集
对于中小规模部署,可以使用Docker的syslog驱动:
# 启动一个测试容器
docker run --log-driver=syslog --log-opt syslog-address=udp://localhost:514 busybox echo "这条日志会发往syslog"
# 常见的syslog服务器配置(以rsyslog为例)
# 在/etc/rsyslog.conf中添加:
module(load="imudp")
input(type="imudp" port="514")
场景2:分布式集群日志方案
Kubernetes环境下更推荐使用Fluentd+Elasticsearch的组合:
# Fluentd配置示例(fluentd.conf)
<source>
@type forward
port 24224
</source>
<match docker.**>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix docker
</match>
场景3:日志预处理妙招
有时候我们需要在日志进入存储系统前先做些处理:
# 使用Fluentd的grep过滤器
<filter docker.my-app>
@type grep
<exclude>
key log
pattern /healthcheck/ # 过滤掉健康检查日志
</exclude>
</filter>
四、避坑指南与高级技巧
1. 时区问题
很多开发者发现日志时间不对,这是因为容器默认使用UTC时区:
# 解决方案:在Dockerfile中设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
2. 敏感信息过滤
日志中经常不小心包含密码、token等敏感信息:
// 不好的写法
console.log(`用户登录使用了密码:${password}`);
// 应该这样
console.log('用户登录成功', { username: user.name });
3. 结构化日志
相比普通文本,结构化日志更利于后续分析:
// 使用JSON格式日志
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.json(),
transports: [new winston.transports.Console()]
});
logger.info('用户操作', {
action: 'login',
user: '张三',
ip: '192.168.1.100'
});
4. 日志分级策略
建议采用以下分级标准:
- ERROR:需要立即处理的错误
- WARN:需要注意但不是错误的情况
- INFO:重要的运行时事件
- DEBUG:调试信息
# 启动容器时设置日志级别
docker run -e "LOG_LEVEL=debug" my-app
五、该选择哪种方案?
根据不同的场景,我总结了这个决策表:
| 场景 | 推荐方案 | 优点 | 缺点 |
|---|---|---|---|
| 开发环境 | 控制台输出 | 简单直接 | 无法持久化 |
| 单机生产 | syslog驱动 | 系统原生支持 | 功能较基础 |
| 微服务集群 | Fluentd+ES | 功能强大 | 部署复杂 |
| 云环境 | 云厂商日志服务 | 开箱即用 | 可能有费用 |
最后给个忠告:不要等到线上出问题了才想起来看日志。好的日志系统应该像汽车仪表盘,随时告诉你系统运行的健康状况。
六、实战:从零搭建日志系统
让我们用一个完整的例子结束本文。假设我们有个电商系统,包含:
- 前端Node.js服务
- 后端Java服务
- MySQL数据库
# docker-compose.yml
version: '3'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.9.2
environment:
- discovery.type=single-node
ports:
- "9200:9200"
kibana:
image: docker.elastic.co/kibana/kibana:7.9.2
ports:
- "5601:5601"
depends_on:
- elasticsearch
fluentd:
build: ./fluentd
ports:
- "24224:24224"
volumes:
- ./fluentd/conf:/fluentd/etc
depends_on:
- elasticsearch
frontend:
build: ./frontend
logging:
driver: "fluentd"
options:
fluentd-address: fluentd:24224
tag: docker.frontend
backend:
build: ./backend
logging:
driver: "fluentd"
options:
fluentd-address: fluentd:24224
tag: docker.backend
对应的Fluentd配置:
# fluentd/conf/fluent.conf
<source>
@type forward
port 24224
</source>
<filter docker.**>
@type record_transformer
<record>
host "#{Socket.gethostname}"
service ${tag}
</record>
</filter>
<match docker.**>
@type elasticsearch
hosts elasticsearch
logstash_format true
logstash_prefix docker
include_tag_key true
</match>
这样我们就搭建了一个完整的日志收集系统,所有服务的日志都会:
- 通过Fluentd收集
- 存储到Elasticsearch
- 可以在Kibana中可视化查看
记住,好的日志系统不是一天建成的。建议从小规模开始,逐步完善。最重要的是要让日志真正用起来,而不是收集完就束之高阁。
评论