9 个技巧,解决 K8s 中的日志输出问题

作者 | 元乙??阿里云存储服务技术专家

导读:近年来,越来越多的同学咨询如何为 Kubernetes 构建一个日志系统,或者是来求助在此过程中遇到一系列问题如何解决,授人以鱼不如授人以渔,于是作者想把这些年积累的经验以文章的形式发出来,让看到文章的同学少走弯路。K8s 日志系列文章内容偏向落地实操以及经验分享,且内容会随着技术的迭代而不定期更新,本文为该系列文章的第 3 篇。

第一篇:《6 个 K8s 日志系统建设中的典型问题,你遇到过几个?》

第二篇:《一文看懂 K8s 日志系统设计和实践》

前言

在上一篇文章《一文看懂 K8s 日志系统设计和实践》中,主要和大家介绍从全局维度考虑如何去构建 K8s 中的日志系统,本文我们将从实践角度出发来一步步构建 K8s 中的日志监控体系。

构建日志系统的第一步是如何去产生这些日志,而这也往往是最繁杂最困难的一步。

2009 年阿里云春节上班第一天,在北京一间连暖气都没有的办公室里,一帮工程师一边口呼白气,一边敲出了“飞天”的第一行代码。“飞天”作为阿里云的核心技术平台,其英文名 Apsara——来自吴哥王朝的阿仆萨罗飞天仙女的名字。

阿里云飞天系统的第一行代码就是为了编写一个日志系统,而现在 apsara logging 的日志库应用在飞天所有的系统中,包括盘古、女娲、伏羲、洛神...

Kubernetes 中日志重要性

通常日志最基础的作用是记录程序的运行轨迹,在此之上会衍生出非常多的功能,例如线上监控、告警、运营分析、安全分析等等(详情可以参见第一篇文章《6 个 K8s 日志系统建设中的典型问题,你遇到过几个?》,这些功能反过来也对日志具备一定的要求,我们需要尽可能的将日志规范化,以减少收集、解析、分析的代价。

在 Kubernetes 中,环境的动态性很强,日志基本上都是易失的,因此需要实时将日志采集到中心的存储中,为了配合日志采集,对于日志的输出、采集会有更多的要求。

下述我们列举了 Kubernetes 中,日志输出的常见注意事项(其中标记 (*)的是 Kubernetes 中特有的项目):

  1. 如何选择日志等级
  2. 日志内容规范
  3. 合理控制日志输出量
  4. 选择多种日志输出目标
  5. 控制日志性能消耗
  6. 如何选择日志库
  7. 日志形态选择(*)
  8. 日志是否落盘以及落盘介质(*)
  9. 如何保证日志存储周期(*)

如何选择日志等级

日志等级是用来区分日志对应事件严重程度的说明,这是所有日志中必须具备的一个选项。通常日志会分为 6 个不同的等级:

  • FATAL(致命):用来输出非常严重或预期中不会发生的错误,遇到此种错误应当立即报警并人工介入处理;
  • ERROR (错误):非预期中的错误,此种错误可能导致部分系统异常但不会影响核心业务和系统正常运行;
  • WARN(警告):潜在的危险或值得关注的信息(比较核心的路径);
  • INFO(信息):应用执行过程中的详细信息,一般通过该信息可以看到每个请求的主要执行过程;
  • DEBUG(调试):用于线下调试的日志信息,用于分析应用执行逻辑,线上应用切勿开启;
  • TRACE(跟踪):输出最细致的运行轨迹,可能包含涉及的数据内容。

作为程序员,一定要合理设置日志等级,个人在开发过程中总结了以下几点经验:

  1. FATAL 类型日志一定是非常严重的错误、需要人工处理的场景打印的;
  2. ERROR 和 WARNING 的区别很多程序员难以选择,可以从告警角度考虑:ERROR 为一般需要告警,WARNING 为不需要;
  3. 日志等级一方面是为了能够表示日志的严重程度,另一方面也是为了控制应用程序的日志输出量,通常线上只能打开 INFO 或 WARN 的日志;
  4. DEBUG 日志可以多打,方便分析问题;
  5. 所有用户请求日志,必须记录;
  6. 对于不确定的外部系统调用,日志需尽可能覆盖周全;
  7. 程序中的日志库需要具备运行期间变更日志等级的能力,方便在遇到问题需要分析时临时更改日志等级;
  8. 通常在新功能上线,涉及的日志可适当提升一个等级,方便实时观察和监控,待稳定后再调整到正常(记得加上注释,方便改回来)。

日志内容规范

通常在没有约束的情况下,程序员的发挥天马行空,各种日志内容都会出现,这些只有开发自己才能看懂的日志很难进行分析和告警。因此我们需要一个日志顶向下的规范来约束项目中的开发人员,让所有的日志看起来是一个人打印的而且是易于分析的。

日志的字段

日志中通常必备的字段有:Time、Level、Location。对于特定模块/流程/业务,还需要有一些 Common 的字段,例如:

  1. 如果使用 Trace 系统,可以把 TraceID 附加到日志中;
  2. 固定的流程需要附加对应的字段,例如订单的生命周期中,一定要有订单号、用户 ID 等信息,这些信息可以通过 Context 附加到对应流程的日志实例上;
  3. HTTP 请求需要记录:URL、Method、Status、Latency、Inflow、OutFlow、ClientIP、UserAgent 等,详情可以参考?Nginx日志格式
  4. 如果多个模块的日志都打印到同一个流/文件中,必须有字段标识模块名。

日志的字段规约最好由运维平台/中间件平台自顶向下推动,约束每个模块/流程的程序员按照规定打印日志。

日志表现形式

通常我们建议使用 KeyValue 对形式的日志格式,比如我们阿里的飞天日志库采用的就是这种形式:

[2019-12-30 21:45:30.611992]    [WARNING]       [958] [block_writer.cpp:671]  path:pangu://localcluster/index/3/prom/7/1577711464522767696_0_1577711517     min_time:1577712000000000       max_time:1577715600000000       normal_count:27595      config:prom     start_line:57315569     end_line:57343195       latency(ms):42  type:AddBlock

KeyValue 对的日志可以完全自解析且易于理解,同时便于日志采集时自动解析。

另外推荐的是 JSON 日志格式,支持以 JSON 格式输出的日志库很多,而且大部分的日志采集 Agent 都支持 JSON 格式的日志收集。

{"addr":"tcp://0.0.0.0:10010","caller":"main.go:98","err":"listen tcp: address tcp://0.0.0.0:10010: too many colons in address","level":"error","msg":"Failed to listen","ts":"2019-03-08T10:02:47.469421Z"}

注意:绝大部分场景不建议使用非可读的日志格式(例如 ProtoBuf、Binlog 等)。

单条日志换行问题

非必要情况下,尽量不要一条日志输出成多行,这种对于采集、解析和索引的代价都比较高。

合理控制日志输出量

日志的输出量直接影响到磁盘使用以及对于应用的性能消耗,日志太多不利于查看、采集、分析;日志太少不利于监控,同时在出现问题的时候没办法调查。

一般线上应用需合理控制日志的数据量:

  1. 服务入口的请求和响应日志没有特殊原因都要输出并采集,采集的字段可以根据需求调整;
  2. 错误日志一般都要打印,如果太多,可以使用采样方式打印;
  3. 减少无效日志输出,尤其是循环中打印日志的情况需尽量减少;
  4. 请求型的日志(比如 Ingress、Nginx 访问日志)一般不超过 5MB/s(500 字节每条,不超过 1W/s),应用程序日志不超过 200KB/s(2KB 每条,不超过 100 条/s)。

选择多种日志输出目标

建议一个应用不同类型的日志输出到不同的目标(文件),这样便于分类采集、查看和监控。例如:

  1. 访问日志单独放到一个文件,如果域名不多,可以按照一个域名一个文件的形式;
  2. 错误类的日志单独放一个文件,单独配置监控告警;
  3. 调用外部系统的日志单独放一个文件,便于后续对账、审计;
  4. 中间件通常都由统一的平台提供,日志一般单独打印一个文件。

控制日志性能消耗

日志作为业务系统的辅助模块,一定不能影响到业务正常的工作,因此日志模块的性能消耗需要单独额外注意,一般在选择/开发日志库时,需要对日志库进行性能测试,确保正常情况下日志的性能消耗不超过整体 CPU 占用的 5%。

注意:一定要确保日志打印是异步的,不能阻塞业务系统运行。

如何选择日志库

开源的日志库非常多,基本每个语言都有数十种,选择一个符合公司/业务需求的日志库需要精挑细选,有一个简单的指导原则是尽可能使用比较流行的日志库的稳定版本,入坑的几率要小一点。例如:

  1. Java 使用 Log4J、LogBack;
  2. Golang 使用 go-kit
  3. Python 默认集成的日志库大部分场景都够用,建议阅读一下?CookBook
  4. C++ 推荐使用 spdlog,高性能、跨平台。

日志形态选择

在虚拟机/物理机的场景中,绝大部分应用都以文件的形式输出日志(只有一些系统应用输出到 syslog/journal);而在容器场景中,多了一个标准输出的方式,应用把日志打到 stdout 或 stderr 上,日志会自动进入到 docker 的日志模块,可以通过 docker logs 或 kubectl logs 直接查看。

容器的标准输出只适应于比较单一的应用,例如 K8s 中的一些系统组件,线上的服务类应用通常都会涉及到多个层级(中间件)、和各种服务交互,一般日志都会分为好几类,如果全部打印到容器的标准输出,很难区分处理。
同时容器标准输出对于 DockerEngine 的性能消耗特别大,实测 10W/s 的日志量会额外占用 DockerEngine 1 个核心的 CPU(单核 100%)。

日志是否落盘以及落盘介质

在 Kubernetes 中,还可以将日志库直接对接日志系统,日志打印的时候不落盘而直接传输到日志系统后端。这种使用方式免去了日志落盘、Agent 采集的过程,整体性能会高很多。

这种方式我们一般只建议日志量极大的场景使用,普通情况下还是直接落盘,相比直接发送到后端的方式,落盘增加了一层文件缓存,在网络失败的情况下还能缓存一定的数据,在日志系统不可用的情况下我们的研发运维同学可以直接查看文件的日志,提高整体的可靠性。

Kubernetes 提供了多种存储方式,一般在云上,都会提供本地存储、远程文件存储、对象存储等方式。由于日志写入的 QPS 很高,和应用也直接相关,如果使用远程类型的存储,会额外多 2-3 次网络通信开销。我们一般建议使用本地存储的方式,可以使用 HostVolume 或者 EmptyDir 的方式,这样对于写入和采集的性能影响会尽可能的小。

如何保证日志存储周期

相比传统虚拟机/物理机的场景,Kubernetes 对于节点、应用层提供了强大的调度、容错、缩/扩容能力,我们通过 Kubernetes 很容易就能让应用获得高可靠运行、极致弹性。这些优势带来的一个现象是:节点动态创建/删除、容器动态创建/删除,这样日志也会随时销毁,没办法保证日志的存储周期能够满足 DevOps、审计等相关的需求。

在动态的环境下实现日志的长期存储只能通过中心化的日志存储来实现,通过实时的日志采集方式,将各个节点、各个容器的日志在秒级内采集到日志中心系统上,即使节点/容器挂掉也能够通过日志还原当时的现场。

总结

日志输出是日志系统建设中非常重要的环节,公司/产品线一定要遵循一个统一的日志规范,这样才能保证后续日志采集、分析、监控、可视化能够顺利进行。

后面的章节会介绍如何为 Kubernetes 规划日志采集和存储的最佳实践,敬请期待。

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”

原文地址:https://www.cnblogs.com/alisystemsoftware/p/12408258.html

时间: 2024-11-05 22:07:56

9 个技巧,解决 K8s 中的日志输出问题的相关文章

iOS中 加强日志输出 开发技术总结

对于那些做后端开发的工程师来说,看LOG解Bug应该是理所当然的事,但我接触到的移动应用开发的工程师里面,很多人并没有这个意识,查Bug时总是一遍一遍的试图重现,试图调试,特别是对一些不太容易重现的Bug经常焦头烂额.而且iOS的异常机制比较复杂,Objective-C的语言驾驭也需要一定的功力,做出来的应用有时候挺容易产生崩溃闪退.一遍一遍的用XCode取应用崩溃记录.解析符号,通常不胜其烦,有时还对着解析出来的调用栈发呆,因为程序当时的内部状态常常难以看明白,只能去猜测. 对于真机,日志没法

JAVA中自定义日志输出到指定文件

虽然JAVA日志包提供的功能已经很方便,但是假如我们有新的需求如:将日志文件保存到我们希望的位置并在日志文件名中添加日期且保存指定时间内的日志文件:按照自己希望的格式输出日志内容.对于这些需求我们只要扩展java.util.logging.StreamHandler(Handler的子类),java.util.logging.Formatter创建自定义的处理器及格式化器即可以实现.下面是个例子,它分别创建了Handler及Formatter的子类,以便实现将日志文件保存到我们需要的位置,及在日

完美解决K8s中的Pod无法解析外网域名问题

系统:Ubuntu 18.04.02K8s版本:1.13.4 故障现象:安装KubeDNS后,Pod内无法ping通外网域名,访问外网IP.K8s内部域名或者IP均正常.??原因分析,查看Pod中的resolv.conf: kubectl exec busybox -- cat /etc/resolv.conf nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local option

[Django]Django开发中的日志输出

开发环境:Ubuntu16.04+Django 1.11.9+Python2.7 一:使用自定义函数输出日志到log文件: import time def print_log(log): file_obj = open('/tmp/filename.log', 'a+') log_time = time.strftime( '[%Y-%m-%d %H:%M:%S]', time.localtime( time.time())) # 转化时间格式 file_obj.write("%s\n"

Logback 整合 RabbitMQ 实现统一日志输出

原文地址:Logback 整合 RabbitMQ 实现统一日志输出 博客地址:http://www.extlight.com 一.前言 公司项目做了集群实现请求分流,由于线上或多或少会出现请求失败或系统异常,为了查看失败请求的日志信息,我们得将所有服务的日志文件都打开来进行问题的定位分析,操作起来非常麻烦.因此,我们开发组决定设计一套日志查看系统来解决上述问题. 二.实现思路 默认的,应用服务日志信息会保存在本地服务器的目录中,为了方便查看日志我们应该把多台服务器日志统一输出到一个日志文件中.

理解正确的日志输出级别

原文链接:http://macrochen.iteye.com/blog/1399082 很多程序员都忽略了日志输出级别, 甚至不知道如何指定日志的输出级别. 相对于System.out来说, 日志框架有两个最大的优点就是可以指定输出类别(category)和级别(level). 对于日志输出级别来说, 下面是我们应该记住的一些原则:ERROR:系统发生了严重的错误, 必须马上进行处理, 否则系统将无法继续运行. 比如, NPE, 数据库不可用等. WARN:系统能继续运行, 但是必须引起关注.

Python之向日志输出中添加上下文信息

除了传递给日志记录函数的参数(如msg)外,有时候我们还想在日志输出中包含一些额外的上下文信息.比如,在一个网络应用中,可能希望在日志中记录客户端的特定信息,如:远程客户端的IP地址和用户名.这里我们来介绍以下几种实现方式: 通过向日志记录函数传递一个extra参数引入上下文信息 使用LoggerAdapters引入上下文信息 使用Filters引入上下文信息 一.通过向日志记录函数传递一个extra参数引入上下文信息 前面我们提到过,可以通过向日志记录函数传递一个extra参数来实现向日志输出

mysql的innodb中事务日志ib_logfile

mysql的innodb中事务日志ib_logfile事务日志或称redo日志,在mysql中默认以ib_logfile0,ib_logfile1名称存在,可以手工修改参数,调节开启几组日志来服务于当前mysql数据库,mysql采用顺序,循环写方式,每开启一个事务时,会把一些相关信息记录事务日志中(记录对数据文件数据修改的物理位置或叫做偏移量);作用:在系统崩溃重启时,作事务重做:在系统正常时,每次checkpoint时间点,会将之前写入事务应用到数据文件中.引入一个问题:在m/s环境中,in

解决Linux下Tomcat日志目录下的catalina.log日志文件过大的问题

本文摘自:(http://blog.csdn.net/stevencn76/article/details/6246162) 分类: Java技术专区2011-03-13 12:25 5017人阅读 评论(1) 收藏 举报 tomcatlinux工具任务web 由于Tomcat在默认情况下会将没有经过配置的web应用所产生的日志输出已经其本身的日志内容都输出到这个文件中,那么随着时间的推移,这个文件的尺寸将会越来越大,当需要检查日志内容时间会导致文件难以打开,而且同时tomcat依旧在不断的向文