kubernetes日志收集 fluent-operator 动态索引名的实现

文章目录

发自个人博客: https://uublog.com/article/20230510/kubernetes-fluent-operator-dynamic-index-name/

日志收集,考虑到我们没有主动上报日志,格式也不太统一,容器化的时候,对于我们而言采集容器标准输出的日志是更快捷达到目的的方式。

大致对比了filebeat、fluentd、fluentbit、vector、logstash后,锁定了flientbit。

虽然它功能不是最强大的,但是基本功能能满足我们的业务需求,采集输入端多样化,也能做简单的filter,支持output的端也很丰富。其次,它吃资源比较少,性能也不错。

我们只需要它收集标准输出的日志,输出到es即可。

而在k8s中,有kubesphere共献给fluent的fluent-operator。该opeator提供了fluentbit only、fluentd only、fluentbit+fluentd 这几种采集方式。对于我们而言,就fluentbit 就够了。

我选择了helm方式安装,下载了它chart (https://github.com/fluent/fluent-operator/releases/download/v2.2.0/fluent-operator.tgz)包,解压到`/app/fluent-operator/ `,直接修改里面values.yaml,这样好方便未来管理自己的部署配置。

简单按照文档,修改了fluentbit的input和output后,直接安装即可

复制代码
helm upgrade --install fluent-operator --create-namespace -n fluent /app/fluent-operator/ --set containerRuntime=containerd

按照服务名区分索引名

日志是能收集了,但是所有的服务的日志,当天都放到一个索引,类似 "k8s-logs-<yyyy.mm.dd>" 这样的索引中。本地还好,如果生产环境的话,那一天单个索引就十分庞大了。而且对于不同服务日志保留天数做差异化保留的时候也不好处理,也不好直观展示各个服务日志大小。

所以能按照k8s-<service-name>-<yyyy.mm.dd>格式的话,对我们会更为理想点。截止目前的版本,没有现成的配置可以实现这个。经过研究,可以利用fluentbit的 logstash_prefix_key 这个来实现。logstash_prefix是固定的前缀,logstash_prefix_key则可以动态读取 key来作为索引名。

1.修改fluent-operator,让其支持logstash_prefix_key

经过测试,fluent-operator当前版本的模板并没有对该字段进行处理,所以要进行修改让它支持这个字段。

我需要output到es,所以我就修改了es相关的output,其它输出源的可以自己检查下。

修改 ./templates/fluentbit-output-elasticsearch.yaml

在Values.fluentbit.output.es.logstashPrefix之后增加 .Values.fluentbit.output.es.logstashPrefixKey 这段的配置,如下:

复制代码
{{- if .Values.fluentbit.output.es.logstashPrefix }}
    logstashPrefix: {{ .Values.fluentbit.output.es.logstashPrefix | default "ks-logstash-log" | quote }}
{{- end }}
{{- if .Values.fluentbit.output.es.logstashPrefixKey }}
    logstashPrefixKey: {{ .Values.fluentbit.output.es.logstashPrefixKey | default "ks-logstash-log-key" | quote }}
{{- end }}

2.让它能获取app name作为服务名

./values.yaml 修改fluentbit部分,开启kubernetes labels,因为labels的app标签就是我们的服务名。

yaml 复制代码
filter:
  kubernetes:
    enable: true
    labels: true
    

3. 拼接索引名

由于logstash_prefix_key只能接受 key,且不支持嵌套的对象的key。

举个直观的例子,假设你的对象是这样的:

json 复制代码
{
  "file": "systemd.log",
  "kubernetes": {
     "labels": {
        "app": "demo-service"
     }
  }
}

k8s-$kubernets["labels"]["app"] 和 像 $kubernets["labels"]["app"] 这样的值,是取不到任何内容的。像上面的例子,只接受 $file 这个值。

所以这里要实现我们的目标,我们得自己拼接出一个新的key,作为索引名。类似下面:

json 复制代码
{
  "file": "systemd.log",
  "app_name": "k8s-demo-service",
  "kubernetes": {
     "labels": {
        "app": "demo-service"
     }
  }
}

我是这样实现的,利用lua的filter,通过取出kubernetes的labels,拼接新的值。

我修改的是 cat ./templates/fluentbit-containerd-config.yaml 增加了一个 add_k8s_app_name_field 函数

yaml 复制代码
{{- if .Values.Kubernetes -}}
{{- if .Values.fluentbit.enable -}}
{{- if .Values.fluentbit.filter.containerd.enable -}}
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluent-bit-containerd-config
data:
  containerd.lua: |
    function containerd( tag, timestamp, record)
           if(record["logtag"]~=nil)
           then
           timeStr = os.date("!*t",  timestamp["sec"])
            t = string.format("%4d-%02d-%02dT%02d:%02d:%02d.%sZ",
            timeStr["year"], timeStr["month"], timeStr["day"],
            timeStr["hour"], timeStr["min"], timeStr["sec"],
            timestamp["nsec"]);
            record["time"] = t;
            record["log"] = record["message"];
            record["message"] =  nil;
            return 1, timestamp, record
            else
            return 0,timestamp,record
           end
    end

    function add_k8s_app_name_field(tag, timestamp, record)
        retcode = 0
        prefix = 'k8s' 
        app_name = record['kubernetes']['labels']['app']
        if  app_name ~= nil then
            app_name = prefix .. '-' .. app_name
            
            if app_name ~= nil then
                record['app_name'] = app_name
                retcode = 2
            end
        end
        
        return retcode, timestamp, record
    end
{{- end }}
{{- end }}
{{- end }}

修改 templates/fluentbit-clusterfilter-kubernetes.yaml 增加新增的lua filter函数 对kubernetes标签进行处理

cat templates/fluentbit-clusterfilter-kubernetes.yaml

yaml 复制代码
{{- if .Values.Kubernetes -}}
{{- if .Values.fluentbit.enable -}}
{{- if .Values.fluentbit.filter.kubernetes.enable -}}
apiVersion: fluentbit.fluent.io/v1alpha2
kind: ClusterFilter
metadata:
  name: kubernetes
  labels:
    fluentbit.fluent.io/enabled: "true"
    fluentbit.fluent.io/component: logging
spec:
  match: kube.*
  filters:
  - kubernetes:
      kubeURL: https://kubernetes.default.svc:443
      kubeCAFile: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      kubeTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
      {{- $params := omit .Values.fluentbit.filter.kubernetes "enable" }}
      {{- if .Values.fluentbit.output.stdout.enable }}
      {{- $_ := set $params "k8sLoggingExclude" true -}}
      {{- end }}
      {{- with $params }}
      {{- . | toYaml | nindent 6 }}
      {{- end }}
  - lua:
      script:
        key: containerd.lua
        name: fluent-bit-containerd-config
      call: add_k8s_app_name_field
      timeAsTable: true
  - nest:
      operation: lift
      nestedUnder: kubernetes
      addPrefix: kubernetes_
  - modify:
      rules:
      - remove: stream
      - remove: kubernetes_pod_id
      - remove: kubernetes_docker_id
      - remove: kubernetes_container_hash
      - remove: kubernetes_labels
  - nest:
      operation: nest
      wildcard:
      - kubernetes_*
      nestUnder: kubernetes
      removePrefix: kubernetes_
{{- end }}
{{- end }}
{{- end }}

主要增加了

yaml 复制代码
- lua:
    script:
      key: containerd.lua
      name: fluent-bit-containerd-config
    call: add_k8s_app_name_field
    timeAsTable: true

到这里基本就满足所有的条件了,为了不影响systemd的日志收集和归类,也给它的lua filter增加app_name字段.

./templates/fluentbit-lua-config.yaml

yaml 复制代码
new_record["app_name"] = "systemd"

经过前面的修改后,只要在values.yaml 设置 fluentbit.output.es.logstashPrefixKey="$app_name" 即可

4. 应用变更

我修改过的values.yaml,去除了无关部分后主要如下:

yaml 复制代码
Kubernetes: true
fluentbit:
  crdsEnable: true
  enable: true
  image:
    repository: "hub.xxxx.com/library/fluent-bit"
    tag: "v2.0.11"
  input:
    tail:
      enable: true
      refreshIntervalSeconds: 10
      memBufLimit: 50MB
      path: "/var/log/containers/*.log"
      skipLongLines: false
    systemd:
      enable: true
      path: "/var/log/journal"
      includeKubelet: true
  output:
    es:
      enable: true
      # 如果多个host的话,用hosts
      host: "es.xxx.local"
      port: 9200
      logstashFormat: true
      logstashPrefixKey: "$app_name"
  filter:
    kubernetes:
      enable: true
      labels: true
      annotations: false
      k8sLoggingExclude: true
    containerd:
      enable: true
    systemd:
      enable: true

再来helm更新一下,搞定

复制代码
helm upgrade --install fluent-operator --create-namespace -n fluent /app/fluent-operator/ --set containerRuntime=containerd

效果如下:

复制代码
yellow open k8s-kiali-2023.05.10                iWEHK0gKR6GoPKjO3gH6Eg 1 1      30      0 105.9kb 105.9kb
yellow open k8s-nginx-deploy-2023.05.10         OCzPIKWgRneSn27J-o4k9g 1 1      16      0  76.3kb  76.3kb
yellow open k8s-istiod-2023.05.10               GlA1dI7aQDqPFD-5ZMX6iw 1 1     797      0 329.5kb 329.5kb
yellow open k8s-reviews-2023.05.10              41Ifiq3cTQGBKdJ4Fn2MJQ 1 1      18      0  86.6kb  86.6kb
相关推荐
众人皆醒我独醉4 小时前
Pod 一直 Terminating,死活删不掉?三个凶手,每一个你都意想不到
面试·kubernetes
飞翔沫沫情6 小时前
K8s Alloy 采集 Pod 控制台日志
云原生·容器·kubernetes
潮起鲸落入海11 小时前
Kubernetes Metric Server, Quota and Limits
容器·kubernetes
梦想的颜色11 小时前
Docker 容器化本地部署 Claude Code 完整硬核教程|隔离沙盒 + 国产模型直连 + 持久化 Skill
运维·容器·ai 工程化·沙盒隔离·docker 本地部署·claude code硬核实战·国内国产模型接入
摇滚侠12 小时前
云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 19-25
java·云原生·kubernetes
渣渣盟12 小时前
Docker 运维常用命令手册(含扩容与实战)
运维·docker·容器
人道领域13 小时前
【LeetCode刷题日记】贪心算法理论与实战:455.分发饼干最优解
java·开发语言·数据结构·算法·leetcode·贪心算法
咕噜咕噜的猪大侠14 小时前
《K8s 核心知识梳理:Deployment 滚动更新、JobCronJob、DaemonSet 和 Service》
云原生·容器·kubernetes
摇滚侠14 小时前
云原生 Java 架构师的第一课 K8s+Docker+KubeSphere+DevOps 26-40
java·云原生·kubernetes
AOwhisky1 天前
kubernetes(K8s)学习笔记:第十期与第十一期核心知识点自测与详解
运维·笔记·云原生·kubernetes·云计算·k8s·hpa