路漫漫其修远兮
吾将上下而求索

k8s学习:容器化落地中日志处理解决方案实践

1、背景

        公司推进容器化落地从去年到现在接近一年的时间,投入了两个部门七个研发人员,从刚开始方案选择,讨论,开发,一步一步迭代,到现在差不多功能完成80%,剩下的工作为各业务线接入过程中反馈的问题的解决,持续功能性迭代优化。到目前为止接入的项目有600个左右,各个环境pod总数为1200个左右,运行平稳。证明整套解决方案在设计和实践过程中是可行的。

        容器化是一个很大的话题,在整个设计和实践过程中涉及了很多的功能模块的开发,也遇到了很多的问题。每个人分工不同,研发过程中遇到的问题也不同,我这边负责的日志处理和网络控制两个模块的实践方案。今天主要讨论日志处理,下面给出整个设计过程中的思路,以及少量的关键示例,有一定容器化基础的同学会更容易理解。

2、日常需求:

1)、研发同学需要登录到对应的项目机器上面,通过常用的shell命令来查看或者过滤相关的日志,尽可能保持以前使用jumpserver的习惯,降低各个业务线接入的门槛。

2)、每次研发cicd完成后,原有的pod会被销毁,包含新代码的pod会启动,而每次用户请求进来,程序会写日志到pod的容器中,研发需要能查看历史pod中的日志文件内容。

3)、公司使用的语言大体有java,php,go等语言,当java程序处理发生异常,触发oom或者full gc的时候,需要将堆栈或者内存信息打印下来,然后下载到本地通过工具进行分析。

4)、生产上每个项目可能有多个pod,每个pod都会产生大量日志,研发需要通过统一的日志平台来过滤、查看、分析整体服务运行状况,快速定位问题。

3、遇到的问题

        因为pod在node上面的调度是不固定的,因此需要思考如何在当前已有的情况下满足研发的需求。当前其他公司容器落地有些成熟的方案,大体为以下几种:

1)、每个pod中运行一个sidecar容器,里面包含了filebeat客户端,可以将程序日志收集并发送给kafka队列,这种方式的一个缺点为cpu成本比较高,有多少个pod就有多少个sidecar,占用资源大。

2)、每个pod挂载共享存储,程序打印日志直接打印到共享存在中,可以解决日志收集问题,便于统一处理,这种方式的缺点为网络io会成为瓶颈,共享存储出问题会导致所有pod不正常,并且共享存储会有网络延迟,用户请求处理视图中有日志打印方法的话,会导致用户请求响应速度慢。

3)、pod挂载宿主机目录,将日志文件落到宿主机上面,然后每台node上面运行一个filebeat服务,来处理收集日志,优点为pod运行不依赖外部存储等组件,可靠性提高。并且挂载宿主机目录,写磁盘速度最快,占用资源最少。并且每台node上只运行一个filebeat服务,cpu利用率比较高。缺点为开发成本相对比较高,需要规划目录结构,通过程序来增删filebeat配置文件内容,需要记录每个pod在node的漂移记录等。

4、设计思路

        经过团队商议,决定采用第三种方式来进行设计,进行更多的研发投入,来保证服务性能。

        首先要解决的问题是程序写文件路径和宿主机路径映射关系整理。pod启动起来后,程序写日志的路径固定为/app/logs目录,日志写入习惯和常规虚机一样。为了把日志持久化,需要将此路径映射到宿主机。而宿主机运行着各个部门的不同项目,同一个项目在同一个宿主机上可能有多个pod在运行,因此目录结构需要划分好。我们采用的映射关系为一个部门一个namespace,一个应用对应一个deployment,因此目录结构在宿主机为:/data/applogs/:namespace/:deployment/:pod/access.log 这样可以将每个pod中的日志都完整的保存起来,并且不会发生目录冲突情况,查找起来也有规律。

       这里就有问题了,怎么保证程序写日志目录固定,并且宿主机目录规律呢?因为pod创建之前pod的名字是不知道的,也就是说生成deployment的yaml文件的时候,pod名字获取不到,容器启动的时候,路径就无法精确到pod目录级别,只能到deployment级别,映射目录为:/data/applogs/:namespace/:deployment/ = /app/logs/ 进一步修补措施为程序初始化的时候在 /app/logs/ 目录中创建主机名目录,可以实现上面的设计。但是多个主机名目录,而且目录名字一直变,对研发来说不友好,并且需要一直沟通为什么这里会多一层目录,增加沟通成本,出错成本。 在这过程中我们想了很多方法,最后找到一个好的方式,用一个软链来解决这个问题,具体措施为:创建容器的时候,挂载的目录关系为:/data/applogs/:namespace/:deployment/ = /home/xxx/logs/ 然后程序启动的时候,执行shell命令:ln -s /home/xxx/logs/$HOSTNAME /app/logs ,这样一来,做到了一举三得,研发写日志目录固定,宿主机日志目录规律,并且在yaml中不用获取pod名字等额外操作。最终日志目录映射关系示例如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: test-deployment
  namespace: tech-daily
spec:
  template:
    spec:
      containers:
      - image: harbor.xxx.com/test:01
        imagePullPolicy: IfNotPresent
        name: omega-image
        volumeMounts:
        - mountPath: /home/xxx/logs
          name: applogs
      volumes:
      - hostPath:
          path: /data/k8s-applogs/tech-daily/test-deployment
          type: ""
        name: applogs

        如何能让研发获取到飘来飘去的pod的日志目录和列表呢?首先要知道pod漂移的历史记录,即什么时间,某个pod在某个node上面创建或删除,然后将这些历史记录保存到mysql中,通过自己写的程实时的watch所有pod的增删事件。当研发人员想看某个项目历史都有哪些pod记录的时候,程序从mysql中找到对应的pod在哪个node上,读取列表信息返回并展示给研发。如果研发想要直观的在浏览器中查看某个pod都有哪些日志文件,程序可以接着从这个node上面对应的pod路径中爬取对应的日志文件列表展示给用户(node上需要安装nginx,并且开启目录列表功能),用户点击对应的文件就可以下载到本地的电脑进行分析。

        要能让研发人员使用shell分析历史日志内容,并且功能和jumpserver类似,首先需要临时启动一个pod,并将项目的某个历史pod的日志目录挂载到当前的pod目录下面,这样研发人员才可以看到历史日志列表,然后进行后续的操作,并且因为只挂载了当前项目的目录,其他项目的目录是看不到的,也起到了隔离作用。研发人员可以在web界面操作shell命令,需要前端同学开发前端界面,并使用websocket协议与后端程序进行交互,后端程序收到请求后,从mysql中查询到该到哪个node上面临时创建pod,然后根据已有的yaml模板启动pod,启动成功并正常运行后,程序调取kubernetes的api接口,返回终端给用户进行操作。为了防止临时pod大量创建,导致资源浪费,pod的终端在断开半小时后会被清理掉。

        对于同一日志平台来说,已有的elk方案基本可以满足常见的需求。我们使用的graylog日志平台,相比较的优点有权限控制,角色控制,支持多节点并行处理,数据流向为:filebeat->kafka->graylog->elasticsearch,filebeat在每台宿主机上面部署一个服务,当有新项目创建的时候,会知道项目名以及名称空间,然后根据已有的模板,协商好的路径规则,添加到filebeat配置文件中,因为pod在每个node都有可能,所以需要将filebeat配置分发到每个node上面,采集对应的日志内容,然后经过kafka最后存储到es上面,使用graylog进行排错分析。

5、效果展示

1)、pod列表展示图

20200403-7201540196.png

2)、pod对应日志列表展示图

20200403-6720397650.png

3)、研发人员进入历史日志pod展示图

20200403-3964268482.png

4)、统一日志查询平台展示图

20200403-6582799843.png

6、注意事项

1)、刚开始的时候后台程序是用python进行开发的,后来发现当watch所有pod的更新事件的时候会丢事件,程序使用长连接和kubernetes进行通信,但是当连接断掉后,python对应的库不会抛异常处理,导致很多事件丢失,为了解决这个问题,后来使用go进行了重写,使用go-client官方库进行处理,解决了这个问题。并且使用go以后,和kubernetes进行交互处理更加稳定,一些常见的增删改查变得更容易一些。

2)、查看历史日志的pod要尽可能小些,启动比较快,但常见的shell命令需要内置,并且一些常见的问题,比如汉字乱码,shell窗口大小等问题需要解决。

3)、日志级别, 日志格式,日志大小都需要规范。哪个没有规范制定,哪里就有坑等着你。

7、总结

1)、 对于研发人员来说,其实他并不用关心背后实现的技术细节,比如有几个node,日志怎么持久化,历史pod怎么进去。只需要和以往的操作一样,就可以适应新的平台。对他们来说,整个底层架构和集群都是黑盒。

2)、其他公司已有的解决方案,需要根据自己公司的实际情况,分析其优点和缺点,看看是否满足当前业务需求,技术需求。有时候需要团队去思考设计新的模式和解决方案。

3)、所有软件都是从0到1,从1到100的过程,中间会经过很多新需求迭代,或者问题解决

如果有机会再给大家说说其他模块的设计思想

未经允许不得转载:江哥架构师笔记 » k8s学习:容器化落地中日志处理解决方案实践

分享到:更多 ()

评论 抢沙发

评论前必须登录!