当前位置: 首页 > 科技观察

ES只适合检查日志吗?那是因为你不会这样使用Clickhouse..._0

时间:2023-03-11 23:24:04 科技观察

1.后台GraphiteDocumentation所有应用都部署在Kubernetes上,每时每刻都会有大量的日志输出。之前我们主要使用SLS和ES作为日志存储。但是当我们使用这些组件时,我们发现了一些问题。1.成本问题我个人认为SLS是一个非常好的产品,速度快,交互方便,但是SLS索引的成本比较贵。当我们想降低SLS索引的开销时,发现云厂商不支持对单个索引的开销进行分析,这让我们无法知道哪些索引构建不当ES使用了大量的存储,消耗了a大量内存2.通用问题如果业务是混合云架构,或者业务形态有SAAS和私有化两种方式,那么一般日志和链路都不能使用SLS,需要使用两套云产品,即不是很方便。3、精度SLS存储的精度只能做到秒级,而我们实际的日志精度是做到毫秒级的。如果日志中有traceid,SLS是无法通过的。根据traceid信息,Log是按照毫秒时间排序的,不利于排查问题。经过一番研究,我们发现使用Clickhouse可以很好的解决上述问题,而且Clickhouse节省了存储空间,也节省了资金,所以我们选择了Clickhouse来存储日志。但是经过我们的深入研究,Clickhouse作为日志存储有很多实现细节,但是业界并没有很好的解释Clickhouse相关的收集日志的整个过程,也没有优秀的Clickhouse日志查询工具来帮助分析日志。为此,我们编写向开源社区贡献了一套Clickhouse日志系统,总结了Clickhouse日志采集架构的经验。先上Clickhouse日志查询界面,让大家感受陌陌是最懂前端的后端程序员。2.架构示意图我们将日志系统分为四个部分:日志收集、日志传输、日志存储和日志管理。日志采集:LogCollector以Daemonset方式部署,宿主机日志目录挂载到LogCollector容器中。LogCollector可以通过挂载目录收集应用日志、系统日志、K8S审计日志。日志传输:通过不同的Logstore映射到Kafka不同topic中的不同topic将不同数据结构的日志分开。日志存储:使用Clickhouse中引擎数据表和物化视图两种。日志管理:开源的Mogo系统可以查询日志、设置日志索引、设置LogCollector配置、设置Clickhouse表、设置告警等,我们将按照这四个部分来讲解架构原理。三、日志采集1、采集方式Kubernetes容器中的日志采集通常有三种选择。DaemonSet方法采集:在各个节点部署LogCollector,挂载宿主机目录作为容器的日志目录。LogCollector读取日志内容,收集到日志中心。网络采集:通过应用日志SDK直接采集日志内容到日志中心。Sidecar采集:LogCollector部署在每个pod中,LogCollector只读取pod中的日志内容并采集到日志中心。以下是三种收集方式的优缺点:我们主要使用DaemonSet方式和网络方式来收集日志。DaemonSet方式用于收集ingress和应用日志,network方式用于收集大数据日志。下面我们主要介绍DeamonSet的集合方法。2、日志输出从上面的介绍可以看出,我们的DaemonSet会通过两种方式收集日志类型,一种是标准输出,一种是文件。引用袁一的描述:虽然使用Stdout打印日志是Docker官方推荐的方式,但是需要注意:这个推荐是基于容器只作为简单应用的场景。在实际业务场景中,我们还是建议大家尽量使用文件,主要原因如下:stdout性能问题,从应用程序输出stdout到服务器,中间会有几个进程(比如常用的JSONLogDriver):应用程序stdout->DockerEngine->LogDriver->serializeintoJSON->保存到文件->Agent收集文件->解析JSON->上传到服务器。整个过程的开销比文件大得多。压测时,每秒10万行日志输出,会额外占用DockerEngine一个CPU核心;Stdout不支持分类,即所有的输出都混合在一个流中,不能像文件一样对输出进行分类。一个应用程序中通常会有AccessLog、ErrorLog、InterfaceLog(调用外部接口的日志)、TraceLog等,这些日志的格式和用途各不相同。如果它们混在同一个流中,将难以收集和分析;Stdout只支持容器主程序的输出。如果是运行在daemon/fork模式下的程序,将无法使用stdout;文件的Dump模式支持多种策略,比如同步/异步写入、缓存大小、文件轮转策略、压缩策略、清除策略等,相对来说更加灵活。从这个描述可以看出,在docker中输出文件,然后收集到日志中心是比较好的做法。所有的日志采集工具都支持文件日志采集,但是我们在配置日志采集规则的时候,发现一些开源的日志采集工具,比如fluentbit、filebeat,在采集文件日志时,不支持追加pod、namespace、container_name等守护进程部署。,container_id等标签信息,也不可能通过这些标签做一些自定义的日志采集。由于无法添加标签信息,我们暂时放弃了DeamonSet部署下的文件日志采集方式,采用了基于DeamonSet部署的标准输出采集方式。3.日志目录日志目录的基本情况如下。因为我们使用标准输出方式收集日志,根据上表,我们的LogCollector只需要挂载/var/log和/var/lib/docker/containers这两个目录即可。1)标准输出日志目录应用的标准输出日志存放在/var/log/containers目录下,文件名按照K8S日志规范生成。这里我们以nginx-ingress日志为例。我们可以通过ls/var/log/containers/|查看nginx-ingress的文件名grepnginx-ingress命令。nginx-ingress-controller-mt2wx_kube-system_nginx-ingress-controller-be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a.log我们参照K8S日志的规范:/var/log/containers/%{DATA:pod_name}_%{DATA:namespace}_%{GREEDYDATA:container_name}-%{DATA:container_id}.log.可以将nginx-ingress日志解析为:pod_name:nginx-ingress-controller-mt2wnamespace:kube-systemcontainer_name:nginx-ingress-controllercontainer_id:be3741043eca1621ec4415fd87546b1beb29480ac74ab1cdd9f52003cf4abf0a通过以上的日志解析信息,我们的LogCollector就可以很方便地追加pod、namespace、Informationaboutcontainer_nameandcontainer_id.2)ContainerinformationdirectoryThecontainerinformationoftheapplicationisstoredinthe/var/lib/docker/containersdirectory,andeachfolderinthedirectoryisthecontainerID.Wecanobtainthebasicinformationoftheapplicationdockerthroughcatconfig.v2.json.4.LogCollectorcollectslogs1)ConfigureourLogCollectortousefluent-bit,whichisownedbycncfandcanbetterintegratewithcloudnative.TheKubernetesclustercanbeselectedthroughtheMogosystem,andtheconfigurationrulesofthefluent-bitconfigmapcanbeeasilyset.2)Datastructurefluent-bit’sdefaultcollectiondatastructure@timestampfield:stringorfloat,usedtorecordthetimeofcollectinglogs.logfield:string,usedtorecordthecompletecontentofthelog.IfClickhouseuses@timestamp,becausethereare@specialcharacters,willbehandledproblematic.所以我们在处理fluent-bit的集合数据结构的时候,会做一些映射关系,规定双下划线是mogo系统日志索引,避免和业务日志的索引冲突。_time_字段:string或float,用于记录日志采集的时间_log_字段:string,用于记录日志的完整内容比如你的日志记录是{"id":1},那么实际fluent-bitcollection日志会以{"_time_":"2022-01-15...","_log_":"{\"id\":1}"的日志结构直接写入Kafka,并且Mogo系统会根据_time_,_log_这两个字段在clickhouse中设置数据表。3)采集如果我们要采集ingress日志,需要在input配置中设置ingress日志目录,fluent-bit会将ingress日志采集到内存中,然后我们在filter配置中将日志重写到_log_中,然后我们在ouput配置中,设置附加日志采集时间为_time_,设置日志写入的kafkaborkers和kafkatopics,则fluent-bit内存中的日志会写入kafka,日志会写入kafka_log_needs成为json。如果你的应用写的日志不是json,那么你需要根据fluent-bit的解析器文档调整你写的日志的数据结构:https://docs.fluentbit.io/manual/pipeline/filters/parser4、日志传输Kafka主要用于日志传输。上面说了我们使用fluent-bit来采集日志的默认数据结构。在下图中的kafka工具中,我们可以看到日志采集的内容。在日志采集过程中,由于不同业务日志字段不一致,导致解析方式不同。因此,在日志传输阶段,我们需要为不同数据结构的日志创建不同的Clickhouse表,映射到Kafka的不同主题。这里以ingress为例,我们需要在Clickhouse中创建一个ingress_stdout_stream的Kafka引擎表,然后将其映射到Kafka的ingress-stdoutTopic。5.日志存储我们将使用三张表来存储一类业务的日志。1、Kafka引擎表从Kafka采集数据到Clickhouse的ingress_stdout_stream数据表中。创建表logger.ingress_stdout_stream(_source_String,_pod_name_String,_namespace_String,_node_name_String,_container_name_String,_cluster_String,_log_agent_String,_node_ip_String,_time_Float64,_log_String)engine=KafkaSETTINGSkafka_broker_list='kafka:9092',kafka_list='ingress-stdout',kafka_group_name='logger_ingress_stdout',kafka_format='JSONEachRow',kafka_num_consumers=1;2、物化视图从ingress_stdout_stream数据表中读取数据,_log_根据Mogo配置的索引提取字段写入ingress_stdout结果表。CREATEMATERIALIZEDVIEWlogger.ingress_stdout_viewTOlogger.ingress_stdoutASSELECTtoDateTime(toInt64(_time_))AS_time_second_,fromUnixTimestamp64Nano(toInt64(_time_*1000000000),'Asia/Shanghai')AS_time_nanosecond_,_pod_name_,_namespace_,_node_name_,_container_name_,_cluster_,_log_agent_,_node_ip_,_source_,_log_AS_raw_log_,JSONExtractInt(_log_,'status')ASstatus,JSONExtractString(_log_,'url')ASurlFROMlogger.ingress_stdout_streamwhere1=1;3、结果表存储最终的数据createtablelogger.ingress_stdout(_time_second_DateTime,_time_nanosecond_DateTime64(9,'亚洲/上海'),_source_String,_cluster_String,_log_agent_String,_namespace_String,_node_name_String,_node_ip_String,_container_name_String,_pod_name_String,_raw_log_String,statusNullable(Int64),urlNullable(String),)engine=MergeTreePARTITIONBYtoYYYYMMDD(_time_second_)ORDERBY_time_second_TTLtoDateTime(_time_second_)+INTERVAL7天设置index_granularity=8192;六、总结流程1、日志会被Kafka通过fluent-bit的规则进行采集。这里我们将日志收集到两个字段中。_time_字段用于存储fluent-bit_log采集的时间_字段用于存储原始日志2.通过mogo,在clickhouse中设置了三张表。app_stdout_stream:从Kafka采集数据到Clickhouse的Kafka引擎表app_stdout_view:视图表,用于存储mogo设置的索引规则3.最后是mogoUI界面,根据app_stdout数据,查询日志信息7.Mogo界面展示1.查询日志界面2.设置日志采集配置界面以上文档说明针对GraphiteKubernetes的日志采集。