Kubernetes集群日志持久化实战

场景

现有场景

在整个Kubernetes集群中有多套环境多套服务如果需要查询日志较为繁琐,并且考虑到开发查询日志并不太方便,所以我们通过hostpath方式将日志文件持久化存储到服务器本地,然后通过ELK将日志收集并进行展示,这样就解决了日志查看繁琐问题。

痛点:
因为使用hostpath持久化到本地,每个pod都会将日志存储在运行节点的固定目录上,所有的pod都是同样的操作,一个node节点上好几十个pod,就会导致同一个文件被多个pod写入内容导致日志覆盖的问题,往往最后一个pod才可以真正的将日志写入到该文件中,就会导致日志丢失的问题。

解决方案

痛点分析

  • 日志覆盖的根源
    1、共享路径冲突:所有的pod日志文件路径相同(历史遗留问题,但是如果我们根据每个服务的名称去划分路径的话,如果启动多个pod副本,几个服务同时向同一个文件写入,也会有概率导致丢失)
    2、文件写入模式:pod以覆盖模式(非追加模式)打开日志文件(hostpath是直接挂载上去的)

解决思路

从上述的覆盖根源来看,我们需要将每个pod隔离出来就可以解决覆盖问题,但是怎么将每个pod都单独出来呢?

  1. 通过kubernetes的pv、pvc的方式,动态的给每个pod都生成一个存储路径
  2. 通过subPathExpr 的方式自动为每个pod创建一个独立的目录进行存储
  3. 使用daemonset方式启动filebeat收集日志
  4. 部署 ELK,接收filebeat收集的日志并进行展示

注意:subPathExpr 这种方式需要Kubernetes 1.15+版本支持
pv、pvc的这种方式需要为每个deploy都新增pvc,加大了运维维护成本,所以暂时不考虑该方案

实施指南

一、Kubernetes pod单独存储操作示例

1. 应用deploy配置

  1. deploy注入pod名称环境变量
env:
  - name: POD_NAME  # 变量名
    valueFrom:
      fieldRef:
        fieldPath: metadata.name  # 自动获取 Pod 名称
  1. 使用subPathExpr 动态生成子目录
volumeMounts:
  - mountPath: /opt/logs
    name: logs-dir
    subPathExpr: $(POD_NAME)  # env变量引用
  1. 配置houstpath卷
volumes:
  - name: logs-dir
    hostPath:
      path: /data/logs/uat
      type: DirectoryOrCreate  # 自动创建目录
  1. 完整配置示例
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ioscar-commonsystem-callcenter-server
  name: ioscar-commonsystem-callcenter-server
  namespace: uat
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ioscar-commonsystem-callcenter-server
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
        kubectl.kubernetes.io/restartedAt: "2025-01-10T14:27:52+08:00"
      creationTimestamp: null
      labels:
        app: ioscar-commonsystem-callcenter-server
    spec:
      containers:
      - env:
        - name: pod_name
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: ns
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: status.podIP
        image: harbor-ioscar.cbf.com/ioscar-uat/commonsystem-callcenter-server:116
        imagePullPolicy: Always
        name: ioscar-commonsystem-callcenter-server
        ports:
        - containerPort: 9103
          protocol: TCP
        readinessProbe:
          failureThreshold: 15
          httpGet:
            path: /actuator/health
            port: 9103
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 20
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: "1"
            memory: 1Gi
          requests:
            cpu: 200m
            memory: 200Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /opt/logs
          name: logs-dir
          subPathExpr: $(pod_name)
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - hostPath:
          path: /data/logs/uat
          type: DirectoryOrCreate
        name: logs-dir

2. 效果验证

最终实现的效果:将pod中的/opt/logs挂载到宿主机的/data/logs/uat/pod具体名称

# 1. 查看服务部署的具体node节点
kubectl get pod -n uat -o wide  |grep ioscar-commonsystem-callcenter-server 
 
# 2. 进入到node节点上
ssh node-1

# 3. 查看日志目录是否生成
ll /data/logs/uat/ioscar-commonsystem-callcenter-server-65b7f54f7b-6mmpr

至此日志覆盖的问题已经解决不回再出现日志丢失的问题,此时接入ELK进行日志收集即可

3. 可能遇到的问题

  1. 环境变量未定义

subPathExpr: $(pod_name) 引用了未声明的 pod_name,实际路径会变成空值,导致所有 Pod 共享 /data/logs/uat 根目录。
所以必须配置env环境变量,并且变量名称的大小写必须注意,env是什么subPathExpr就配置成什么

env:
  - name: POD_NAME  # 变量名
    valueFrom:
      fieldRef:
        fieldPath: metadata.name  # 自动获取 Pod 名称

二、日志收集操作示例

1. 日志收集核心组件角色

filebeat(日志收集) --> kafka(削峰) --> logstach(日志解析) --> es(存储与检索(必选)) --> kibana(展示(必选))

filebeat:go语言编写,轻量级,支持断点续传适合部署在当量生产服务器上
kafka:缓冲层,解耦生产者和消费者,削峰。可以临时存储突增的日志数据,不会使下游服务因激增导致过载
logstach:日志脱敏或者日志打标签深度解析等功能(虽然也支持日志收集和输出,但是体量太大jvm运行,不如filebeat强大)
es:数据库对日志进行存储,并支持检索
kibana:日志展示,方便了开发人员直接查询到服务的日志

2. filebeat 安装与部署

filebeat是日志收集器需要在每个节点进行部署安装配置,比较麻烦,可以使用kubernetes集群的daemonset(会默认在每个节点启动一个实例,避免重复的安装部署操作)和configmap(类似于配置文件,将配置文件持久化出来,方便修改配置)来部署

# configmap配置
apiVersion: v1
data:
  filebeat.yml: |
    logging.level: error
    filebeat.inputs:					# 日志收集字段
    - type: log
      enabled: true
      paths:
        - /data/logs/uat/*/*.log		# 日志采集(日志存放路径,支持通配符)
      scan_frequency: 10s				# 每10s扫描一次
      tail_files: true					# 从文件末尾开始读取
      json.keys_under_root: true		# 自动解析json日志
      json.overwrite_keys: true
      json.add_error_key: false
      spool_size: 1000
      idle_timeout: 10s
      close_removed: true

    output.kafka:						# 日志发送给kafka
      enabled: true
      hosts: ["192.168.1.1:9092"]		# kafka地址端口
      #topic: "cbc"
      topic: "ants-%{[app_name]}"		# 自动创建topic app_name是一个json变量是日志文件中的内容(可以自己定义topic的名字)
      partition.round_robin:
        reachable_only: true
kind: ConfigMap
metadata:
  name: filebeat
  namespace: default



# daemonset
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    name: log-filebeat
  name: log-filebeat
  namespace: default
spec:
  selector:
    matchLabels:
      name: log-filebeat
  template:
    metadata:
      labels:
        name: log-filebeat
    spec:
      containers:											# 下列就是使用的镜像
      - image: harbor-ioscar.cbf.com/cbf/filebeat:7.1.1
        imagePullPolicy: IfNotPresent
        name: log-filebeat
        resources:											# 资源的限制
          limits:
            cpu: 800m
            memory: 1G
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        - mountPath: /usr/share/filebeat/filebeat.yml		# 映射到pod中的路径
          name: filebeat									# 关联volumes的名称实现关联关系
          subPath: filebeat.yml								
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      volumes:
      - configMap:											# configmap座位挂载点
          defaultMode: 420
          items:
          - key: filebeat.yml								# configmap的键值对
            mode: 420
            path: filebeat.yml
          name: filebeat									# 引用的configmap名称
        name: filebeat


3. kafka 安装与部署

kafka是日志收集中用来解耦、削峰的组件,主要的作用就是应对日志激增的问题,保护下游组件不会因为激增的日志崩溃。

依赖环境
1、jdk(不详细赘述了)
2、zookeeper(可以使用自己环境中的zookeeper如果没有kafka的安装包中也包含一个简易的)

kafka下载地址

  • 解压安装包
# 解压安装包
tar -zxvf kafka_2.11-1.1.0.tgz	
# 进入到kafka配置文件中创建zookeeper文件夹	
mkdir zookeeper
# 创建下列文件夹用于存储日志和数据(后续在zookeeper配置文件中写入)
cd zookeeper
mkdir {logs,data}
  • 修改kafka配置文件
# 修改kafka的配置文件
vim  /kafka/config/server.properties
broker.id=1												#这个id是唯一标识(必须唯一)
listeners=PLAINTEXT://ip:9092							#是kafka真正的bind的地址
advertised.listeners=PLAINTEXT://ip:9092				#将listener信息发布到zookeeper中去
port=9092                                				#端口号
host.name=ip      										#服务器IP地址,修改为自己的服务器IP
log.dirs=/mnt/soft/kafka_data/log/kafka					#日志存放路径,上面创建的目录,需要手动创建,否则会启动报错
zookeeper.connect=localhost:2181         				#zookeeper地址和端口,单机配置部署,localhost:2181

示例:
在这里插入图片描述

  • 修改zookeeper配置文件
vim  /kafka/config/zookeeper.properties

dataDir=/kafka/zookeeper/data			# zookeeper的data目录(就是之前创建的zookeeper的data文件夹)

dataLogDir=/kafka/zookeeper/logs 		# zookeeper日志目录

clientPort=2181							# zookeeper端口号

maxClientCnxns=0

示例:
在这里插入图片描述

  • 启动kafka服务

因为kafka依赖于zookeeper服务所以我们需要先将zookeeper启动起来,在启动kafka

# 先启动zookeeper 
/kafka/bin/zookeeper-server-start.sh -daemon ../config/zookeeper.properties

# 确定zookeeper启动成功
netstat -lntp |grep 2181   

# 启动kafka
/kafka/bin/kafka-server-start.sh ../config/server.properties

# 确定kafka启动成功
netstat -lntp |grep 9092

启动之后等待接收日志数据即可。

4. logstach安装部署

logstach在日志收集过程中承担日志的转发和日志深度解析的角色(比如日志打标签,日志脱敏等)

依赖环境
1、jdk1.8

logstach下载地址

# 解压logstach压缩包(包名称自己指定)
tar -zxvf logstash.tar.gz	

# 修改logstach的配置文件
cd logstash/config
vim logstash-sample.conf
# 日志导入
input {
  kafka {
    bootstrap_servers => "192.168.1.1:9092"		# 读取kafka的地址以及端口
    topics_pattern  => "cbc-.*"					# 读取kafka的topic名称,如果多个以,分割
    group_id => "logstash"
    consumer_threads => 40
    codec => "json"
    decorate_events => true
    auto_offset_reset => "latest"
    codec => "json"
  }
}
# 日志匹配输出
output {
  elasticsearch {								# 这里表示将日志输出给es
    hosts => ["http://192.168.1.1:9200"]		# es的地址以及端口
    index => "%{[@metadata][kafka][topic]}-%{+YYYYMMdd}"
  }
}


# 修改启动参数
vi /logstash/config/jvm.options
-Xms4g		# 指定启动占用内存为4g
-Xmx4g


# 启动logstach
cd logstash/bin
./logstash -f ../config/logstash-sample.conf	


# 检查是否启动成功
netstat -lntp |grep 9600	

还可以做一些日志的高级设置,因为我这里没需求,如果感兴趣可以自己查阅资料

5. elasticsearch安装部署

elasticsearch下载地址

Elasticsearch是日志收集过程中用于存储和检索日志的一个组件,其实就是一个日志数据库

依赖环境
1、jdk1.8以上
2、需要使用es自己的系统账号(自己创建一个账号)
3、配置系统最大线程数为最大,否则无法启动

安装部署

# 解压es安装包
tar -zxvf elasticsearch.tar.gz


# 修改配置文件
vim config/elasticsearch.yml	
cluster.name: ela-cbc							# 设置集群名称,所有节点的这个配置应该都是一样的
node.name: cbc-es02-al							# 设置当前节点名称
node.master: true								# 设置当前节点为主节点
node.data: true									# 设置当前节点为数据节点
path.data: /data/app/elasticsearch/data/data	# 存储数据的路径
path.logs: /data/app/elasticsearch/logs/		# 存储日志的路径
bootstrap.memory_lock: false					# 是否允许es将自己锁定在内存中,以提高性能,false不允许
network.host: 10.60.250.67						# 监听地址
network.tcp.no_delay: true						# 启用tcp无延迟模式,提高数据传输效率
network.tcp.keep_alive: true					# 保持tcp连接活跃
network.tcp.reuse_address: true					# 启动tcp地址重用
network.tcp.send_buffer_size: 128mb				# 设置tcp发送缓冲区大小
network.tcp.receive_buffer_size: 128mb			# 设置tcp接受缓冲区大小
transport.tcp.port: 9300						# 设置es传输层通信端口
transport.tcp.compress: true					# 启用传输层通信的数据压缩
http.max_content_length: 200mb					# 设置http请求的最大内容长度
http.cors.enabled: true							# 是否启用跨域资源共享,允许其他域名的应用访问es
http.cors.allow-origin: "*"						# 设置语序的跨域源,即允许访问的域名
http.port: 9200									# 设置http服务监听端口
# 集群部署es可以写地址集,当然也可以单节点部署,只写一个节点ip加端口即可
discovery.seed_hosts: ["192.168.1.1:9300", "192.168.1.2:9300", "192.168.1.3:9300"]			# 设置其他节点内部通信端口
cluster.initial_master_nodes: ["192.168.1.1:9200", "192.168.1.2:9200", "192.168.1.3:9200"]	# 初始化列表
cluster.fault_detection.leader_check.interval: 15s	# 设置集群故障检测的主节点检查间隔
discovery.cluster_formation_warning_timeout: 30s	# 超时告警时间
cluster.join.timeout: 30s						# 加入集群超时时间
cluster.publish.timeout: 90s					# 集群发布超时时间
cluster.routing.allocation.cluster_concurrent_rebalance: 32			# 设置集群并发重新平衡数量
cluster.routing.allocation.node_concurrent_recoveries: 32			# 设置节点并发恢复数量
cluster.routing.allocation.node_initial_primaries_recoveries: 32	# 设置节点初始化主分片恢复的数量


vim config/jvm.options
- Xms1g	#表示初始化内存大小
- Xmx1g	#表示最大可以使用的内存大小


# 修改操作系统最大线程数
vim /etc/security/limits.conf
* soft nofile 65536		#用户打开文件数最大为65536(软资源限制)
* hard nofile 131072		#操作系统打开文件数不超过131072(硬性资源限制)
* soft nproc 2048		#用户和进程能打开的最大进程数2048(软资源限制)
* hard nproc 4096		#操作系统能打开的最大进程数4096(硬资源限制)


vim  /etc/sysctl.conf
vm.max_map_count=262145	#这个参数设置一个进程可以创建的虚拟内存映射数量,在es中lucene使用大量的虚拟内存映射来管理索引数据,默认太小了,不满足es的允许


sysctl -p	#刷新一下线程配置



# 创建es账号
useradd elsearch
passwd elsearch


# 启动es
chown elsearch.elsearch /elasticsearch		# 属主属组全部给es账号
su elsearch									# 使用es账号启动否则会报错
./bin/elasticsearch


# 检查是否成功
 netstat -lntp |grep 9200
 netstat -lntp |grep 9300

6. kibana安装部署

kibana是日志收集过程中用于日志展示的,格式化日志方便日志查看

依赖环境
1、jdk1.8以上

kibana下载

tar -zxvf kibana.tar.gz


# 修改配置文件
cd kibana/config
vim kibana.yml
# kibana端口
server.port: 5601
server.host: "0.0.0.0"	
elasticsearch.hosts: ["http://192.168.1.1:9200"]		# 这里配置es的地址加端口
i18n.locale: "zh-CN"


# 启动kibana
kibana/bin/kibana 

# 访问
http://localhost:5601

总结

对于大多数 Kubernetes 1.15+ 环境,推荐将 subPathExpr 方案作为日志隔离的 ​标准化配置。该方案在以下场景表现尤为突出:

  • ​快速实施需求:新服务上线需立即实现日志隔离
  • ​混合部署环境:同一集群运行多团队/多项目服务
    ​- 成本敏感场景:无法承担云存储费用的测试/开发环境
    通过本方案,团队可建立起 ​分钟级 的日志隔离能力,同时为后续构建统一的日志观测平台奠定坚实基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值