第一章:云原生日志体系概述
大家好,我是你们的老朋友。今天咱们聊聊云原生日志体系。
说实话,我刚开始接触Kubernetes时,第一反应就是:日志怎么这么难搞?以前在虚拟机里tail -f跑得飞起,到了K8s里全乱套了。嗯,这背后其实有深层原因。
为什么传统日志收集在K8s中失效?
先说说传统方式。以前我们部署应用,日志通常写在本地磁盘上。比如Java应用写个/var/log/app.log,然后Logstash或者Filebeat去采集。这套模式在物理机或虚拟机时代跑得好好的。
但到了K8s里,问题来了——
- Pod是临时性的:Pod随时可能被调度到其他节点。你刚把日志写到本地,Pod挂了,日志也跟着没了。我在项目中遇到过,一个关键Pod被OOM Kill,日志全丢,排查问题花了整整两天。
- 容器文件系统不持久:容器重启后,文件系统重置。你想想看,如果日志写在容器内部,重启一次就清空一次,这谁受得了?
- 多副本日志分散:一个Deployment有10个副本,日志分布在10个节点上。你手动去每个节点找?不现实。
- 日志格式不统一:有的应用输出JSON,有的输出纯文本,有的带时间戳有的不带。采集端解析起来非常痛苦。
说白了,K8s的核心理念是「不可变基础设施」。容器应该被视为一次性消费品,而不是长期运行的服务器。日志收集方式必须跟着这个理念走。
云原生日志三大标准
那怎么办?社区总结出了三种主流方案。我按推荐程度排个序:
| 方案 | 适用场景 | 复杂度 | 资源消耗 |
|---|---|---|---|
| 标准输出 | 12-Factor App、无状态服务 | 低 | 低 |
| Sidecar | 需要日志预处理、格式转换 | 中 | 中 |
| DaemonSet | 节点级日志采集、遗留应用 | 高 | 低(共享) |
标准输出(Stdout/Stderr)
这是最推荐的方式。应用直接把日志写到标准输出和标准错误流,K8s会自动捕获并存储到节点上。
为什么推荐?因为简单。你不需要关心日志文件放哪,不需要配置采集路径,K8s帮你搞定一切。
# 一个简单的Go应用示例
package main
import (
"log"
"os"
)
func main() {
// 直接输出到标准输出
log.SetOutput(os.Stdout)
log.Println("应用启动成功")
// 错误信息输出到标准错误
log.SetOutput(os.Stderr)
log.Println("数据库连接超时")
}
我个人习惯,新项目一律用标准输出。配合kubectl logs命令,排查问题非常方便。
kubectl logs --tail=100 -f可以实时查看最近100行日志。加上--timestamps参数还能显示时间戳。
但标准输出也有局限。比如日志格式不统一,或者你需要对日志做预处理(比如加字段、过滤敏感信息)。这时候就需要Sidecar了。
Sidecar模式
Sidecar模式,说白了就是在同一个Pod里跑两个容器:一个是你自己的应用容器,另一个是日志处理容器。两个容器共享同一个Volume,日志通过Volume传递。
我举个例子。假设你的应用输出的是纯文本日志,但你的日志系统只接受JSON格式。怎么办?加个Sidecar容器,专门做格式转换。
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-processor
image: fluent-bit:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
command: ["fluent-bit", "-c", "/etc/fluent-bit.conf"]
volumes:
- name: logs
emptyDir: {}
你看,应用把日志写到/var/log/app,Sidecar容器读取并处理。两个容器共享同一个emptyDir卷。
Sidecar的好处是灵活。你可以做日志过滤、格式转换、压缩、加密,甚至发送到多个后端。但代价是资源消耗翻倍——每个Pod多跑一个容器。
DaemonSet模式
最后说说DaemonSet。这种模式在每个节点上部署一个日志采集代理,比如Filebeat、Fluentd、Logstash。代理采集节点上所有容器的日志,然后发送到中央存储。
DaemonSet适合什么场景?我遇到过一种情况:团队有大量遗留应用,代码改不动,没法输出到标准输出。这时候DaemonSet就派上用场了——它直接从节点文件系统采集日志。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat
spec:
selector:
matchLabels:
app: filebeat
template:
metadata:
labels:
app: filebeat
spec:
containers:
- name: filebeat
image: docker.elastic.co/beats/filebeat:8.0.0
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
DaemonSet的优点是资源利用率高——一个节点只跑一个采集代理,不管上面有多少Pod。但缺点也很明显:你需要管理采集代理的配置,而且采集路径可能因容器运行时不同而不同。
如何选择?
我个人的建议是这样的:
- 新项目:优先用标准输出。这是最云原生的方式,也是社区推荐的做法。
- 需要日志预处理:用Sidecar。比如日志格式转换、敏感信息脱敏、添加自定义字段。
- 遗留应用迁移:用DaemonSet。不改代码,直接采集文件日志。
- 混合场景:标准输出 + DaemonSet组合。标准输出用于新应用,DaemonSet兜底采集所有日志。
嗯,这里要注意一点:不管用哪种方式,日志格式一定要统一。我建议所有日志都输出JSON格式,包含时间戳、级别、模块、消息等字段。这样后续分析会轻松很多。
好了,第一章就聊到这。下一章我们深入讲讲标准输出日志的采集和配置,包括如何用kubectl logs高效排查问题。
公众号:蓝海资料掘金营,微信deep3321