k8s日志集中收集解决方案

网友投稿 364 2022-10-27

k8s日志集中收集解决方案

简介:

在正常生产环境中使用k8s部署业务后能正常运行还不够,我们需要很多附加的东西来满足日常的需求。比如日志、监控、告警等。这一篇给大家分享一下我们生产环境中的日志集中解决方案。当然不敢说是最好的,分享出来供大家参考。在正常环境中有几类日志我们比较关心:1、k8s中的ingress日志。比如traefik,里面记录的从公网域名访问进来的访问记录,类似nginx的access.log2、istio中envoy边车日志。如果启用了istio那么这个日志也是需要的,每次程序被调用时都会有一个记录。3、k8s中的事件日志。里面就有容器健康检查失败重新部署,或者pod新建、删除等事件。kubectl describe pods中的Events:内容4、业务程序运行时产生的业务日志。比如java中常用的log4j套件输出的日志和kubectl logs命令查看的日志。这个和输出方式有关。

实现方式

这里日志收集的方式是采用elk模式,如果大家感兴趣还有loki的模式。当然这次分享是基于elk的。首先要部署的还是es集群,这里我们使用的是虚拟机部署方式非容器化。理由es是公共的组件可以对接所有的产品业务线,单独拿出来部署可以给别的产品线输出集中化日志存储方案。平时维护量还是比较少的,当然我们的es集群每15分钟大概800万条记录规模并不是特别大, 不过以后出现瓶颈横向扩容也不是问题。基于es集群作为日志集中存储,又有集中收集方式。我会按照我自己的理解说说各个的特点。1、采用边车模式使用filebeat直接对接es集群。这个方式优点是可以随着业务部署一起,pod运行起来以后就能自动把日志输出到es中。但是会有单独的开销,因为每个pod实例都需要额外的硬件资源来运行filebeat。网络可靠性要求比较高,需要实时把日志传过去。2、业务程序的log4j对接logstash或者kafka。这个方式优点是全部集成了,缺点是依赖第三方。如果处理不好logstash或者kafka宕机可能会影响到业务程序本身的运行,导致业务中断。3、业务日志写本地文件,通过本地磁盘挂载到pod中。优点是对现有程序没改动,缺点是需要单独去做日志集中处理。我们目前的做法就是第三种,通过pod挂载本地磁盘目录。单独使用日志收集把日志集中到一台日志服务器上,在通过logstash存入es集群。具体实现方式我主要写目前的我们生产的集中方式。

实施细节

$template myformat,"%msg%\n"$template istio, "/data/logs/rancher/%syslogtag%/%$year%-%$month%-%$day%-%$hour%.log"$template ztk8s, "/data/logs/ztk8s/%syslogtag%/%$year%-%$month%-%$day%-%$hour%.log"local3. ?ztk8slocal4. ?istio;myformat这里使用的是relp模块,所有服务端和客户端都需要安装rsyslog-relp的包才能正常使用。配置文件定义了两个日志类型,local3作为业务日志收集,local4作为k8s的相关系统组件日志收集。这里目录为rancher是因为我们用的rancher2管理页面。1、traefik日志集中:日志的定义需要修改configmap

apiVersion: v1 data: traefik.toml: |- # traefik.toml logLevel = "info" defaultEntryPoints = ["http","https"] [entryPoints] [entryPoints.http] address = ":80" compress = true [entryPoints.https] address = ":443" compress = true [entryPoints.https.tls] [[entryPoints.https.tls.certificates]] CertFile = "/ssl/tls.crt" KeyFile = "/ssl/tls.key" [entryPoints.traefik] #dashboard端口 address = ":8080" [entryPoints.prometheus] #metrics端口 address = ":9100" [ping] entryPoint = "#健康检查 [kubernetes] [kubernetes.ingressEndpoint] publishedService = "kube-system/traefik" [traefikLog] format = "json" [accessLog] #访问日志 filePath = "/data/node.log" format = "json" [api] #dashboard功能 entryPoint = "traefik" dashboard = true [metrics] [metrics.prometheus] entryPoint = "prometheus" kind: ConfigMap metadata: name: traefik namespace: kube-system

然后把主机目录挂载到容器的/data目录中。比如/home/logs/traefik修改traefik部署的deployment yaml文件

volumeMounts: - mountPath: /config name: config - mountPath: /ssl name: ssl - mountPath: /data #日志容器目录 name: acclog volumes: - configMap: defaultMode: 420 name: traefik name: config - name: ssl secret: defaultMode: 420 secretName: traefik-default-cert - hostPath: path: /home/logs/traefik #日志主机目录 type: "" name: acclog

/home/logs/traefik 在这个目录中就能看到类似于nginx的access.log日志了。在主机上做一个标签调度比如只允许运行到标签为ingress=traefik的主机上。这样分2-3个主机,部署traefik作为负载均衡和高可用。为防止日志过大占用满磁盘空间,可以配置一个日志轮替。到这些主机上使用rsyslog进行日志收集到固定的日志服务器中。为防止日志过大,使用logrotate进行日志轮替

cat /etc/logrotate.d/traefik /home/logs/traefik/node.log { su root root dateext dateformat -%Y-%m-%d-%H #文件格式 extension .log #扩展名 notifempty hourly #每小时轮替一次 rotate 24 #保留24个副本 missingok nocompress #不压缩旧日志,可以指定压缩。 sharedscripts postrotate /bin/kill -s USR1 `ps aux | grep traefik | grep -v grep | awk '{print $2}'` 2> /dev/null || true #完成日志轮替以后执行的脚本,这里配合traefik官网的介绍是传入 USR1的信号给traefik程序就行了 endscript }

这样就是按小时轮替文件了。详细日志轮替配置推荐这篇文章 imfile$ModLoad omrelp创建文件/etc/rsyslog.d/traefik.conf

$WorkDirectory /var/spool/rsyslog $InputFileName /home/logs/traefik/node.log $InputFileTag traefik $InputFileStateFile traefik $InputFileSeverity debug $InputFileFacility local4 $InputFilePersistStateInterval 20000 $RepeatedMsgReduction off $InputRunFileMonitor $InputFilePollInterval 3 $template BiglogFormatTomcat,"%msg%\n" local4.* :omrelp:logserver.tz.com:2514

然后重新启动systemctl restart rsyslog这样在日志服务器中相关目录就能看到内容了。2、k8s的事件日志收集在生产运行中k8s里面所有相关动作都会用事件的方式来呈现,比如一个pod是否改变状态、某个程序是否可用、容器是否重启、健康状态改变、拉取镜像、启动和创建容器等。这些事件对我们进行事故排查非常有用,这些事件保存在etcd的集群中。但是k8s为了保证etcd的性能默认只保存1小时以内并且是目前存在于集群中相关资源的事件信息,其它信息会被定时清理。这就需要把事件导出到其它的日志系统中保存。这里我们选择先导出到日志文件,再通过filebeat导入到elk中方便查阅。注意filebeat的镜像版本一定要和elk的版本一致否则无法上传成功。克隆源代码git clone "go env -w GO111MODULE=on && go env -w GOPROXY=&& CGO_ENABLED=0 go build"修改DockerfileRUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories && apk update --no-cache && apk add ca-certificates执行make all命令生产镜像然后docker push eventrouter:xxx上传到私有仓库中。部署yaml文件。

--- apiVersion: v1 kind: ServiceAccount metadata: name: eventrouter namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: eventrouter rules: - apiGroups: [""] resources: ["events"] verbs: ["get", "watch", "list"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: eventrouter roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: eventrouter subjects: - kind: ServiceAccount name: eventrouter namespace: kube-system --- apiVersion: v1 kind: ConfigMap metadata: name: eventrouter-cm namespace: kube-system data: config.json: |- { "sink": "glog" } --- apiVersion: v1 kind: ConfigMap metadata: name: filebeat-config namespace: kube-system data: filebeat.yml: |- filebeat.inputs: - type: log enabled: true json.keys_under_root: true json.overwrite_keys: true paths: - "/data/log/eventrouter/*" output.elasticsearch: hosts: ["escluster.tz.com:9200"] index: "filebeat-k8s-pro-event-%{+yyyy.MM.dd}" setup.ilm.enabled: false setup.template.name: "filebeat-k8s-pro-event" setup.template.pattern: "filebeat-k8s-pro-event-*" --- apiVersion: apps/v1 kind: Deployment metadata: name: eventrouter namespace: kube-system labels: app: eventrouter spec: replicas: 1 selector: matchLabels: app: eventrouter template: metadata: labels: app: eventrouter tier: control-plane-addons spec: containers: - name: kube-eventrouter image: docker.tz.com/eventrouter:v0.2 command: - "/bin/sh" args: - "-c" - "/eventrouter -v 3 -log_dir /data/log/eventrouter" volumeMounts: - name: eventrouter-cm mountPath: /etc/eventrouter - name: log-path mountPath: /data/log/eventrouter - name: filebeat image: docker.elastic.co/beats/filebeat:7.7.1 command: - "/bin/sh" args: - "-c" - "filebeat -c /etc/filebeat/filebeat.yml" volumeMounts: - name: filebeat-config mountPath: /etc/filebeat/ - name: log-path mountPath: /data/log/eventrouter serviceAccount: eventrouter volumes: - name: eventrouter-cm configMap: name: eventrouter-cm - name: filebeat-config configMap: name: filebeat-config - name: log-path emptyDir: {}

这样再到kibana中就能配置相关索引查看日志了。3、业务日志我们的业务是spring-boot开发的,采用的log4j日志套件输出。日志输出的统一路径是logs/node.log文件,相对程序运行的路径。容器的工作目录是/home/platfrom/。所以日志在容器中的完整路径是/home/platfrom/logs/node.log。映射的主机目录是/home/logs/xxx/下面,具体的路径比如 platform-user项目,在主机上的完整路径是/home/logs/platform-user/node.log。具体的deployment部署yaml为:

volumeMounts: - mountPath: /home/platform/logs name: logs volumes: - hostPath: path: /home/logs/platform-user type: DirectoryOrCreate name: logs

这里有个很麻烦的事情,我们业务在容器中运行的是非root账户。在自动创建了主机目录以后所有权还是root,导致容器中的程序无法写日志文件。我们的解决方案是,使用inotifywait来自动修改权限和生成rsyslog的日志配置。所以需要安装inotify-tools、screen的包先创建一个配置文件。多留几个空行,后面的脚本需要使用。/etc/rsyslog.d/platform.conf

local3.* :omrelp:logserver.tz.com:2514

编辑监控脚本logsconf.sh。这个脚本可以放在一个nfs的共享目录中,每个k8s的宿主机都挂载nfs目录。这样就避免每个主机都去创建一次脚本了。我们这里的nfs主要是作为工具共享使用的,如果用nfs作为共享存储可能会有很严重的性能问题,这会直接导致业务中断。(血的教训,用实际故障买过单的)

#!/bin/bash inotifywait -m -e create /home/logs/ | while read file do #获取新建的文件目录 # /home/logs/ CREATE,ISDIR test BASEDIR=$(echo $file | awk '{print $1}') EVENT=$(echo $file | awk '{print $2}') DIR=$(echo $file | awk '{print $3}') if [ `echo $EVENT |grep ISDIR` ] ;then echo "$EVENT $BASEDIR$DIR" chown 1000:1000 -R $BASEDIR$DIR chmod 764 -R $BASEDIR$DIR #生成日志文件 sed -i '2i$template BiglogFormatTomcat,"%msg%\\n"' /etc/rsyslog.d/platform.conf sed -i '2i$InputFilePollInterval 3' /etc/rsyslog.d/platform.conf sed -i '2i$InputRunFileMonitor' /etc/rsyslog.d/platform.conf sed -i '2i$RepeatedMsgReduction off' /etc/rsyslog.d/platform.conf sed -i '2i$InputFilePersistStateInterval 20000' /etc/rsyslog.d/platform.conf sed -i '2i$InputFileFacility local3' /etc/rsyslog.d/platform.conf sed -i '2i$InputFileSeverity debug' /etc/rsyslog.d/platform.conf sed -i "2i\$InputFileStateFile $DIR" /etc/rsyslog.d/platform.conf sed -i "2i\$InputFileTag $DIR" /etc/rsyslog.d/platform.conf sed -i "2i\$InputFileName $BASEDIR$DIR/node.log" /etc/rsyslog.d/platform.conf sed -i '2i$WorkDirectory /var/spool/rsyslog' /etc/rsyslog.d/platform.conf systemctl restart rsyslog fi done

脚本做个一个倒叙插入,为啥要倒叙插入的原因忘记了。大家可以试试正常的echo去插入配置文件。执行命令screen -dmS watchlog /opt/share/shell/k8s/logsconf.sh,脚本路径自己修改。把这个命令放到/etc/rc.local文件中每次启动时自动运行。这样到日志服务器中就能看到日志搜集的文件了。为了防止日志文件过大,可以采用日志轮替的方式或者定期清理。4、istio的envoy日志。这个日志的收集只适用于启用了istio的场景,没有使用istio可以不用管。我们所有的数据信息访问都由边车Envoy来转发,整个应用集群部署完以后你会发现所有程序模块之间的链接就是Envoy的链接。而我们程序异常的时候可能需要用到这些链接记录来排查问题,而Envoy默认是没有开启日志输出的。日志的开启可以通过修改istio的相关配置来实现。在部署istio的名称空间istio-system中的istio配置文件。kubectl get configmaps -n istio-system istio -o yaml 可以使用这个命令查看详细内容。在配置名称mesh的内容下有一个accessLogFile的选项 accessLogFile: "/dev/stdout"代表输出到容器的终端上。修改完以后需要重启istio-sidecar-injector、istio-galley、istio-pilot三个服务让配置生效。重启之后配置会自动下发,去现有的应用中选择istio-proxy的容器就能看到日志了。另外两个选项:accessLogEncoding代表日志输出格式默认是json。可选项JSON和TEXTaccessLogFormat代表日志内容格式。可以通过istioctl命令来修改,也可以手动编辑configmap文件来修改。istioctl manifest apply --set values.global.proxy.accessLogFile="/dev/stdout"这时候日志是保存在宿主机/var/log/pods/platform_platform-message-v1-5b4fbbb99d-bbgj9_7c5d07a4-4dd6-4e81-a718-dceb5a5e980f/istio-proxy/0.log这样类似的文件中。我们可以进行一轮日志收集到专门的服务器上保存以备查看。这里可以直接修改上面traefik.conf的配置做个修改就行。

$WorkDirectory /var/spool/rsyslog $InputFileName /var/log/pods/*/istio-proxy/0.log $InputFileTag istio $InputFileStateFile istio $InputFileSeverity debug $InputFileFacility local3 $InputFilePersistStateInterval 20000 $RepeatedMsgReduction off $InputRunFileMonitor $InputFilePollInterval 3 $template BiglogFormatTomcat,"%msg%\n" local3.* :omrelp:logserver.tz.com:2514

这里全部日志就都收集到了rsyslog-server的服务器上了。接下来可以使用logstash对日志进行处理,格式化再写入到es中。logstash配置举例:

input{ file { path => "/data/logs/rancher/istio-envoy/*.log" codec => "json" type => "istio-envoy" } file{ path => "/data/logs/ztk8s/platfrom-user/*.log" codec => "json" type => "platfrom-user" } } filter { if [type] == "istio-envoy" { json { source => "log" } } else if [type] == "platfrom-user"{ json { source=>"inparam" } json { source=>"result" } } } output { if "_grokparsefailure" not in [tags] { elasticsearch { hosts => ["escluster.tz.com:9200"] manage_template => true index => "%{type}-%{+YYYY.MM.dd}" } } }

我们的业务日志输出就是json格式,相对来说格式化起来更简单一些。其它的日志格式需要自己根据实际情况进行修改。到此日志集中分享就到这里,适合自己的才是最好的。希望大家看了以后有所启发,再次申明这不是产品部署文档不可以照抄,对此产生的一切问题由使用者自己负责。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:矩阵键盘控制接口设计实验
下一篇:基于多CF接口系统的总线设计
相关文章

 发表评论

暂时没有评论,来抢沙发吧~