本文来聊聊 Docker 双栈日志,看看这个方案解决了我们实际应用中的哪些痛点,以及如何落地使用。
1
容器日志收集
在生产环境中使用 Docker 或 Kubernetes ,甚至是非容器环境,日志管理都是个非常重要的部分。
对于日志的管理,主要涉及以下几个方面:
最终目标都是我们可以通过日志来及时的发现异常,或排查故障,同时也可以通过日志来分析当前的应用程序或者基础架构是否运行正常,是否有需要改进的地方等。
本篇的重点在于日志收集,目前主要有以下几种解决方案,简单聊聊。
1.1
应用直接写日志中心
最简单,也是应用容器化改造中最省心省力的一种。应用程序直接将日志写入远端的日志中心。
这种方案的好处在于只要基础设施跟得上,应用容器化改造或者服务迁移之类的基本不涉及日志改造之类的问题。
但使用这种方式的话,有几个显著的缺点:
1.2
应用将日志写入固定文件
日志写入固定文件,通过日志采集器收集并解析后发送到日志中心。
这种方案最常见,大家常见的 ELK 日志栈,其中的 L 通常是指 Logstash ,当然也可以换成其他的类似组件,这里不赘述了。如果这种方案是应用在 Kubernetes 中的话,通常会使用一个 sidecar 来配合进行日志收集的工作。
这种方案好处就是业务方不需要有太多调整,维持原本的写日志方式。
缺点则是需要单独启动 sidecar 做日志收集,相应的这会造成一些资源浪费。以及需要注意日志清理等。
1.3
应用将日志直接写 stdout/stderr
应用层稍做改造,将日志写 stdout/stderr 。
这种方案在业务进行容器化改造,或者部署到 Kubernetes 时,是比较推荐的,这也是相对标准/通用的实践方式。
这种方式的要说缺点的话主要是业务层需要对日志记录做些改造,改造成直接写 stdout/stderr,同时也需要注意对日志做区分/标记。
但好处也很明显,不需要有什么额外的 sidecar 做收集,直接利用 Docker 原生的日志机制收集便可以了。很多人会选择通过 DaemonSet 的方式在每个节点部署一个日志采集器做日志收集。
2
Docker 默认日志驱动
介绍完上述几种常规的解决方案后,很明显,这里最推荐的方式是直接写 stdout/stderr 了。我们将重点拉回到 Docker 上,当日志直接写 stdout/stderr 时,Docker 为我们做了些什么?
我们启动一个容器做个测试:
# 启动一个容器,并输出 Happy Birthday Docker
(MoeLove) ➜ ~ docker run alpine echo "Happy Birthday Docker"
Happy Birthday Docker
Docker 提供了一个 docker logs 的命令可供我们查看容器的日志输出:
(MoeLove) ➜ ~ docker logs $(docker ps -ql)
Happy Birthday Docker
你可能会好奇,这个日志记录是从哪里来的呢?这就要归功于 Docker 的日志驱动了。当前默认的日志驱动名叫 json-file ,其功能是将日志以 JSON 的格式写入到本地的文件中,可通过以下命令进行验证:
(MoeLove) ➜ ~ sudo cat `docker info --format '{{ .DockerRootDir }}'`/containers/$(docker ps -ql --no-trunc)/$(docker ps -ql --no-trunc)-json.log
{"log":"Happy Birthday Docker\n","stream":"stdout","time":"2020-03-20T12:28:02.804391064Z"}
可以看到这个文件中保存着刚才通过 echo 写入 stdout 的内容。
PS:很多人在 Kubernetes 中采集容器日志的方式,就是通过 DaemonSet 在每个 Node 上部署一个日志收集器进行日志收集。关于这种方案的优劣不是本篇的重点,暂且跳过。
3
Docker 其他日志驱动
除了这种默认的 json-file 的日志驱动外,Docker 还提供了很多其他的驱动,可通过以下命令进行查看:
(MoeLove) ➜ ~ docker info --format '{{ .Plugins.Log }}'
[awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog]
这些日志驱动有它们不同的使用场景,简单聊几种:
这里有个值得注意的内容,fluentd 这种日志驱动其实不一定非要和 Fluentd 绑定,你也可以使用 Fluent Bit ( https://fluentbit.io/ )。
4
Docker 使用 fluentd 日志驱动
这里我来做个示例,使用 fluentd 这个日志驱动,但使用 Fluent Bit 进行接收。
4.1
启动 Fluent Bit
首先,创建一个配置文件,这里为了演示,我将接收到的日志直接进行输出:
[SERVICE]
Flush 5
Daemon Off
Log_Level debug
[INPUT]
Name forward
Listen 0.0.0.0
Port 24224
[OUTPUT]
Name stdout
这里需要注意,INPUT 块中,使用 forward 输入。
这里我将此配置文件保存为 docker_to_stdout.conf,并使用此配置启动一个 Fluent Bit 的容器。
(MoeLove) ➜ fluentbit-docker docker run -p 24224:24224 -v $PWD/docker_to_stdout.conf:/docker_to_stdout.conf --rm fluent/fluent-bit:1.3
/fluent-bit/bin/fluent-bit -c /docker_to_stdout.conf
Fluent Bit v1.3.11
Copyright (C) Treasure Data
[2020/03/20 16:09:13] [ info] Configuration:
[2020/03/20 16:09:13] [ info] flush time | 5.000000 seconds
[2020/03/20 16:09:13] [ info] grace | 5 seconds
[2020/03/20 16:09:13] [ info] daemon | 0
[2020/03/20 16:09:13] [ info] ___________
[2020/03/20 16:09:13] [ info] inputs:
[2020/03/20 16:09:13] [ info] forward
[2020/03/20 16:09:13] [ info] ___________
[2020/03/20 16:09:13] [ info] filters:
[2020/03/20 16:09:13] [ info] ___________
[2020/03/20 16:09:13] [ info] outputs:
[2020/03/20 16:09:13] [ info] stdout.0
[2020/03/20 16:09:13] [ info] ___________
[2020/03/20 16:09:13] [ info] collectors:
[2020/03/20 16:09:13] [debug] [storage] [cio stream] new stream registered: forward.0
[2020/03/20 16:09:13] [ info] [storage] version=1.0.3, initializing...
[2020/03/20 16:09:13] [ info] [storage] in-memory
[2020/03/20 16:09:13] [ info] [storage] normal synchronization mode, checksum disabled, max_chunks_up=128
[2020/03/20 16:09:13] [ info] [engine] started (pid=1)
[2020/03/20 16:09:13] [debug] [engine] coroutine stack size: 24576 bytes (24.0K)
[2020/03/20 16:09:13] [debug] [in_fw] Listen='0.0.0.0' TCP_Port=24224
[2020/03/20 16:09:13] [ info] [in_fw] binding 0.0.0.0:24224
[2020/03/20 16:09:13] [debug] [router] default match rule forward.0:stdout.0
[2020/03/20 16:09:13] [ info] [sp] stream processor started
这里我将 Fluent Bit 监听的端口映射到了本地的 24224 端口。
4.2
将日志转发至 Fluent Bit
启动容器,并使用 --log-driver=fluentd 参数指定容器使用此日志驱动。你也可以直接修改 /etc/docker/daemon.json 的配置文件,添加 "log-driver": "fluentd" 令所有容器都默认使用此配置。
(MoeLove) ➜ ~ docker run --log-driver=fluentd --log-opt fluentd-address=tcp://localhost:24224 alpine echo "Happy Birthday Docker"
Happy Birthday Docker
查看刚才 Fluent Bit 容器的输出,可以看到多了以下内容:
[2020/03/20 16:17:22] [debug] [task] created task=0x7fdc6288e0a0 id=0 OK
[2020/03/20 16:17:23] [debug] [task] destroy task=0x7fdc6288e0a0 (task_id=0)
[0] 678aa7a18729: [1584721039.000000000, {"container_id"=>"678aa7a18729b25159b65f5221968f825447c6f6b71b31ee2853e0dcdd992cb2", "container_name"=>"/distracted_faraday", "source"=>"stdout", "log"=>"Happy Birthday Docker"}]
说明 日志已经传递到了 Fluent Bit 。
同时,我们继续使用 docker logs 命令查看刚才容器的日志:
(MoeLove) ➜ ~ docker logs $(docker ps -ql)
Error response from daemon: configured logging driver does not support reading
可以看到当使用了 fluentd 的日志驱动后,无法再通过 docker logs 直接查看日志了! 实际上,不只是对于 fluentd 这个日志驱动,包括 syslog,awslogs,gcplogs,splunk 等除了 jsonfile 和 journald 这两个日志驱动时,都不能通过 docker logs 直接查看日志!这就非常的不方便了。
你无法在本地直接查看容器日志了,当发生一些紧急情况时,这就会比较影响效率了。
这也就进行到了本节的重点内容了,用 Docker 双栈日志解决此问题!
5
Docker 双栈日志