k8s日志集中收集解决方案

简介:

在正常生产环境中使用k8s部署业务后能正常运行还不够,我们需要很多附加的东西来满足日常的需求。比如日志、监控、告警等。这一篇给大家分享一下我们生产环境中的日志集中解决方案。当然不敢说是最好的,分享出来供大家参考。
在正常环境中有几类日志我们比较关心:
1、k8s中的ingress日志。比如traefik,里面记录的从公网域名访问进来的访问记录,类似nginx的access.log
2、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集群。具体实现方式我主要写目前的我们生产的集中方式。

实施细节

请注意这不是可以照抄的部署文档,只是一个方案的呈现。请在使用的过程中自行根据自己的环境调整适配。
k8s日志集中收集解决方案
部署rsyslog-server
编辑/etc/rsyslog.conf
$ModLoad imrelp
$InputRELPServerRun 2514

$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. ?ztk8s
local4.
?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 = "http" #健康检查
    [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
}

这样就是按小时轮替文件了。详细日志轮替配置推荐这篇文章 https://www.cnblogs.com/yanwei-wang/p/5241436.html
使用rsyslog把所有主机的traefik日志收集到一台主机上。
修改/etc/rsyslog.conf添加
$ModLoad 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 https://github.com.cnpmjs.org/heptiolabs/eventrouter.git
修改Makefile中相关参数,以适应中国网络
修改REGISTRY变量为自己私服的地址
修改BUILD_IMAGE为golang:1.14.2以支持通过env设置go-proxy
修改编译步骤中$(DOCKER_BUILD)后边的参数为 "go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct && CGO_ENABLED=0 go build"
修改Dockerfile
RUN 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和TEXT
accessLogFormat代表日志内容格式。
可以通过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格式,相对来说格式化起来更简单一些。其它的日志格式需要自己根据实际情况进行修改。到此日志集中分享就到这里,适合自己的才是最好的。希望大家看了以后有所启发,再次申明这不是产品部署文档不可以照抄,对此产生的一切问题由使用者自己负责。

猜你喜欢

转载自blog.51cto.com/denwork/2531964