日志对于业务分析和系统分析而言是非常重要的数据。在一个 Kubernetes 集群中,大量容器应用运行在众多 Node 上,各容器和 Node 的系统组件都会生成许多日志文件。但是容器具有不稳定性,在发生故障时可能会被 Kubernetes 重新调度,Node 也可能会由于故障无法使用,造成日志丢失,这就要求管理员对容器和系统组件生成的日志进行统一规划和管理。
1. 应用日志的输出形式
容器应用可以选择将日志输出到下列不同的目标位置:
- 输出到标准输出(stdout)和标准错误输出(stderr)。
- 输出到容器内的某个日志文件。
- 输出到某个外部系统。
1.1 输出到标准输出和标准错误输出
输出到标准输出和标准错误输出的日志通常由容器引擎接管,并保存在容器运行的 Node 上,例如 containerd 容器引擎会将日志保存到 /var/log/pods 目录下,并将日志文件软连接到 /var/log/containers 目录下。
输出到标准输出和标准错误输出的日志可以通过 kubectl logs 命令查看。
1.2 输出到容器内的某个日志文件
日志的保存位置依赖于容器应用使用的存储类型。如果未指定特别的存储,则容器内的应用程序生成的日志文件在容器的文件系统内,在容器退出时会被删除。需要将日志持久化存储时,容器可以选择使用 Kubernetes 提供的某种存储卷(Volume),例如 hostpath (保存在Node上)、nfs(保存在 NFS 服务器上)、PVC(保存在某种网络共享存储上)。
该方式输出的日志路径由应用程序决定,并且不能通过 kubectl logs 命令查看日志。
1.3 输出到某个外部系统
应用程序代码层面实现,直接在应用程序中就可以将日志推送到一个后端日志存储中心,这种方式需要改动源代码,也超出了 Kubernetes 本身的范围,一般不容易实现。
2. k8s 环境的日志采集方案
2.1 在节点上运行一个 agent 来采集日志
如果 pod 内的应用会将日志输出到标准输出和标准错误输出,这时我们可以在每个节点上运行一个采集日志的 agent,将 /var/log/containers 目录下的日志采集到日志系统后端程序。
优缺点:
- 优点:部署简单,侵入性小
- 在节点上运行一个日志收集的 agent 这种方式是常见的一种方法,因为它只需要在每个节点上运行一个代理程序,并不需要对节点上运行的应用程序进行更改,对应用程序没有任何侵入性。
- 缺点:适用场景有局限
- 仅仅适用于将日志输出到 stdout 和 stderr 的应用程序。
如果 pod 内的应用并不是把日志输出到标准输出和标准错误输出,而是输出到容器内的某个日志文件中,那么上述方案就无法采集到日志,可以使用以下优化方案:
在应用程序 pod 内嵌入一个 sidecar 容器,与产生日志的业务容器共享日志存储卷,然后 sidecar 容器读取容器内日志文件的内容输出到 stdout 和 stderr ,其后的步骤就与上述方案一致。
优缺点:
- 优点:优化了上个方案的适用场景有限的问题
- 缺点:业务容器将日志输出到容器内的日志文件中,sidecar 又将该日志输出到 stdout 和 stderr,而输出到 stdout 和 stderr 的日志会被存到宿主机的文件中,于是同一份日志存了两次,造成存储空间的浪费。
2.2 使用日志采集 sidecar 采集日志
为了解决存储空间浪费的问题,直接将 agent 以 sidecar 形式集成到 pod 内,即使用 sidecar 运行日志采集 agent。
优缺点:
- 优点:不会造成存储空间的浪费,并且配置灵活。
- 缺点:会增加额外的管理成本。
方案示例:agent 采用 fluentd。
apiVersion: v1
kind: Pod
metadata:
name: counter
spec:
containers:
- name: count
image: busybox
args:
- /bin/sh
- -c
- >
i=0;
while true;
do
echo "$i: $(date)" >> /var/log/1.log;
echo "$(date) INFO $i" >> /var/log/2.log;
i=$((i+1));
sleep 1;
done
volumeMounts:
- name: varlog
mountPath: /var/log
- name: fluentd
image: k8s.gcr.io/fluentd-gcp:1.30
env:
- name: FLUENTD_ARGS
value: -c /etc/fluentd-config/fluentd.conf
volumeMounts:
- name: varlog
mountPath: /var/log
- name: config-volume
mountPath: /etc/fluentd-config
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluentd-config
YAML3. 日志平台的架构方案
3.1 ELK 与 EFK 介绍
主流的 ELK (Elasticsearch,Logstash,Kibana)目前已经转变为 EFK (Elasticsearch,Fluentd ,Kiban)对于容器云的日志方案业内也普遍推荐采用 Fluentd,EFK 也是官方现在比较推荐的一种方案。
ELK
- Elasticsearch:Elasticsearch 是一个开源的分布式搜索和分析引擎,用于存储、搜索和分析大规模的数据。它提供了强大的全文搜索功能和实时分析能力。
- Logstash:Logstash 是一个用于日志收集、处理和传输的工具,可以从多个来源收集日志数据,对数据进行处理和转换,然后将数据发送到 Elasticsearch 或其他存储后端。
- Kibana:Kibana 是一个用于数据可视化和分析的工具,可以与 Elasticsearch 集成,帮助用户创建实时的仪表板、图表和可视化报告,以便更好地理解和分析数据。
EFK
- Elasticsearch:同样是用于存储、搜索和分析数据的分布式搜索引擎,提供了强大的数据存储和检索功能。
- Fluentd:Fluentd 是一个开源的数据收集器,用于收集、转换和传输日志数据。它支持多种数据源和目标,可以将数据发送到 Elasticsearch 进行存储和分析。
- 与 ELK 中的 Kibana 相同,用于数据可视化和分析,与 Elasticsearch 集成,帮助用户创建仪表板和图表。
主要区别:Logstash 和 Fluentd 都是用于日志收集和处理的工具,但在一些方面有所不同。Logstash 更加灵活和功能丰富,但相对消耗更多资源;Fluentd 更轻量级,性能较好,适合大规模部署。
Filebeats、Logstash、Elasticsearch 和 Kibana 是属于同一家公司的开源项目,官方文档如下:https://www.elastic.co/guide/index.html
Fluentd 则是另一家公司的开源项目,官方文档:https://docs.fluentd.org
Filebeat 是轻量级的收集本地 log 数据的方案,适合简单的日志收集任务。Filebeats 功能比较单一,它仅仅只能收集本地的 log,但并不对收集到的 log 做处理,所以通常 Filebeats 通常需要将收集到的 log 发送到 Logstash 做进一步的处理。
3.2 EFK+kafka+logstash 方案
在大规模集群中,日志量庞大,Elasticsearch 的写入压力过大,可以引入 kafka 进行削峰填谷,并将多个组件结合使用,流程如下:
Log Sources -> Fluentd -> Kafka -> Logstash -> Elasticsearch -> Kibana
详细工作流程:
- 日志来源 (Log Sources):
- 各种日志来源,包括应用程序、服务器、容器(如 Kubernetes 集群中的 Pod 日志)、网络设备等。
- Fluentd:
- 采集:Fluentd 从各种来源收集日志数据。
- 初步处理:Fluentd 可以对数据进行简单的处理、过滤、格式化等。
- 转发到 Kafka:Fluentd 将处理后的日志数据推送到 Kafka 主题中。Kafka 在这里起到日志缓冲和传输的作用,确保日志数据能够可靠地传递到下游系统。
- Kafka:
- 消息缓冲:Kafka 作为中间缓冲层,可以高效地处理和存储大量的日志数据流。
- 消息传输:Kafka 将日志数据持久化到主题中,并提供高效的数据分发机制,允许多个消费者订阅和消费这些数据。
- Logstash:
- 从 Kafka 接收数据:Logstash 配置 Kafka 输入插件,从指定的 Kafka 主题中消费日志数据。
- 复杂处理:Logstash 对数据进行更复杂的处理任务,如字段提取、格式转换、数据增强等。
- 输出到 Elasticsearch:处理后的数据被发送到 Elasticsearch 进行存储和索引。
- Elasticsearch:
- 存储和索引:Elasticsearch 接收来自 Logstash 的处理后的日志数据,并对其进行存储和索引,提供高效的搜索和分析能力。
- 集群扩展:支持分布式和高可用性,可以处理大规模的数据存储需求。
- Kibana:
- 查询与搜索:Kibana 允许用户基于 Elasticsearch 中的数据进行复杂的查询和搜索。
- 数据可视化:Kibana 提供丰富的可视化工具,帮助用户从不同角度分析日志数据。
- 仪表板:用户可以创建和定制仪表板,实时监控关键日志指标和事件。
4. EFK 构架示例
4.1. 安装 Elasticsearch 集群
4.1.1. ElasticSearch 安装最低配置要求
ElasticSearch 节点 | CPU 最小要求 | 内存最小要求 |
elasticsearch master | 核心数 > 2 | 内存 > 2 G |
elasticsearch data | 核心数 > 1 | 内存 > 2 G |
elasticsearch client | 核心数 > 1 | 内存 > 2 G |
建议配置:每台机器 cpu 为 4 c,内存 >= 4 G。
4.1.2. 部署规划
节点类型 | 副本数目 | 存储大小 | 网络模式 | 描述 |
Master | 3 | 5 G | ClusterIP | 主节点,用于控制 ES 集群 |
Data | 3 | 10 G | ClusterIP | 数据节点,用于存储 ES 数据 |
Client | 2 | 无 | NodePort | 负责处理用户请求,实现请求转发、负载均衡 |
4.1.3. 为 ES 准备持久化存储
为了能够持久化 Elasticsearch 的数据,需要准备一个存储,此处我们使用 NFS 类型的StorageClass ,线上环境建议使用 Local PV 或者 Ceph RBD。
找一台服务器安装 nfs 服务端:
# 内网环境可以直接关闭防火墙
systemctl stop firewalld.service
systemctl disable firewalld.service
# 安装
yum install -y nfs-utils rpcbind
# 创建数据共享目录
mkdir -p /data/nfs
chmod 755 /data/nfs
# 配置共享目录
cat > /etc/exports <<EOF
/data/nfs *(rw,sync,no_root_squash)
EOF
# *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
# rw:读写的权限
# sync:表示文件同时写入硬盘和内存
# no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份
# 启动nfs服务
systemctl start rpcbind.service
systemctl enable rpcbind
systemctl status rpcbind
systemctl start nfs
systemctl enable nfs
systemctl status nfs
ShellScriptk8s 集群节点上安装客户端:
sudo yum install -y nfs-utils
sudo apt install -y nfs-common
ShellScript搭建 StorageClass + NFS:
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
# 镜像地址可以更换为自己的
helm upgrade --install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner --set nfs.server=192.168.2.50 --set nfs.path=/data/nfs --set storageClass.defaultClass=true --set image.repository=crpi-sozjkv641zbs4m9x.cn-shenzhen.personal.cr.aliyuncs.com/pingk-k8s-test/nfs-subdir-external-provisioner --set image.tag=v4.0.2 -n kube-system
ShellScript4.1.4. 为 ES 准备证书文件
生成证书文件:
# 运行容器生成证书
mkdir -p /logging/elastic-certs
nerdctl run --name elastic-certs -v /logging/elastic-certs:/app -it -w /app elasticsearch:7.17.3 /bin/sh -c "elasticsearch-certutil ca --out /app/elastic-stack-ca.p12 --pass '' && elasticsearch-certutil cert --name security-master --dns security-master --ca /app/elastic-stack-ca.p12 --pass '' --ca-pass '' --out /app/elastic-certificates.p12"
# 删除容器
nerdctl rm -f elastic-certs
# 将 pcks12(Public Key Cryptography Standards #12)文件中的证书和私钥提取出来,并保存为 PEM 格式的文件
# cd /logging/elastic-certs
# cd elastic-certs && openssl pkcs12 -nodes -passin pass:'' -in elastic-certificates.p12 -out elastic-certificate.pem
ShellScript添加证书到 Kubernetes:
# 添加证书
cd /logging/elastic-certs
kubectl create ns logging
kubectl create secret -n logging generic elastic-certs --from-file=elastic-certificates.p12
# 设置集群用户名密码,用户名为 elastic,密码为 pingk123456
# 密码不能为纯数字,否则后面安装 kibana 会报错
kubectl create secret generic elastic-auth -n logging --from-literal=username=elastic --from-literal=password=pingk123456
ShellScript4.1.5. 安装 ES 集群
添加 ELastic 的 Helm 仓库:
helm repo add elastic https://helm.elastic.co
helm repo update
ShellScriptElaticSearch 安装需要安装三次,分别安装 Master、Data、Client 节点,Master 节点负责集群间的管理工作;Data 节点负责存储数据;Client 节点负责代理 ElasticSearch Cluster 集群,负载均衡。
首先使用 helm pull 拉取 Chart 并解压:
helm pull elastic/elasticsearch --untar --version 7.17.3
cd elasticsearch
ShellScript在 Chart 目录下面创建用于 Master 节点安装配置的 values 文件:
# 创建一个新文件 values-master.yaml,内容如下
## 设置集群名称
clusterName: 'elasticsearch'
## 设置节点名称
nodeGroup: 'master'
## 设置角色
roles:
master: 'true'
ingest: 'false'
data: 'false'
# ============镜像配置============
## 指定镜像与镜像版本
image: 'elasticsearch' # 此处会去官网下载,对应的访问地址为:docker.io/library/elasticsearch:7.17.3
#image: 'registry.cn-hangzhou.aliyuncs.com/egon-k8s-test/elasticsearch' # 可以用自己的镜像,你的镜像仓库必须是公开的
imageTag: '7.17.3'
imagePullPolicy: 'IfNotPresent'
## 副本数:
replicas: 3
# ============资源配置============
## JVM 配置参数
esJavaOpts: '-Xmx1g -Xms1g'
## 部署资源配置(生产环境要设置大些)
resources:
requests:
cpu: '2000m'
memory: '2Gi'
limits:
cpu: '2000m'
memory: '2Gi'
## 数据持久卷配置
persistence:
enabled: true
## 存储数据大小配置
volumeClaimTemplate:
storageClassName: nfs-client
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 5Gi
# ============安全配置============
## 设置协议,可配置为 http、https
protocol: http
## 证书挂载配置,这里我们挂入上面创建的证书
secretMounts:
- name: elastic-certs
secretName: elastic-certs
path: /usr/share/elasticsearch/config/certs
defaultMode: 0755
## 允许您在/usr/share/elasticsearch/config/中添加任何自定义配置文件,例如 elasticsearch.yml、log4j2.properties
## ElasticSearch 7.x 默认安装了 x-pack 插件,部分功能免费,这里我们配置下
## 下面注掉的部分为配置 https 证书,配置此部分还需要配置 helm 参数 protocol 值改为 https
esConfig:
elasticsearch.yml: |
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.enabled: true
# xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
## 环境变量配置,这里引入上面设置的用户名、密码 secret 文件
extraEnvs:
- name: ELASTIC_USERNAME
valueFrom:
secretKeyRef:
name: elastic-auth
key: username
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elastic-auth
key: password
# ============调度配置============
## 设置调度策略
## - hard:只有当有足够的节点时 Pod 才会被调度,并且它们永远不会出现在同一个节点上
## - soft:尽最大努力调度
antiAffinity: 'soft'
# tolerations:
# - operator: "Exists" ##容忍全部污点
YAML然后创建用于 Data 节点安装的 values 文件:
# 创建文件:values-data.yaml
# ============设置集群名称============
## 设置集群名称
clusterName: 'elasticsearch'
## 设置节点名称
nodeGroup: 'data'
## 设置角色
roles:
master: 'false'
ingest: 'true'
data: 'true'
# ============镜像配置============
## 指定镜像与镜像版本
image: 'elasticsearch'
#image: 'registry.cn-hangzhou.aliyuncs.com/egon-k8s-test/elasticsearch'
imageTag: '7.17.3'
## 副本数(建议设置为3,资源不足可以只设置1个副本)
replicas: 3
# ============资源配置============
## JVM 配置参数
esJavaOpts: '-Xmx1g -Xms1g'
## 部署资源配置(生产环境一定要设置大些)
resources:
requests:
cpu: '1000m'
memory: '2Gi'
limits:
cpu: '1000m'
memory: '2Gi'
## 数据持久卷配置
persistence:
enabled: true
## 存储数据大小配置
volumeClaimTemplate:
storageClassName: nfs-client
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 10Gi
# ============安全配置============
## 设置协议,可配置为 http、https
protocol: http
## 证书挂载配置,这里我们挂入上面创建的证书
secretMounts:
- name: elastic-certs
secretName: elastic-certs
path: /usr/share/elasticsearch/config/certs
## 允许您在/usr/share/elasticsearch/config/中添加任何自定义配置文件,例如 elasticsearch.yml
## ElasticSearch 7.x 默认安装了 x-pack 插件,部分功能免费,这里我们配置下
## 下面注掉的部分为配置 https 证书,配置此部分还需要配置 helm 参数 protocol 值改为 https
esConfig:
elasticsearch.yml: |
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.enabled: true
# xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# 禁用了 Elasticsearch 从外部下载 GeoIP 数据库的功能(GeoIP 数据库用于将 IP 地址映射到地理位置)
# 这在以下场景中非常有用:
# 1、日志分析:了解用户访问来源的地理位置。
# 2、安全审计:检测异常的地理位置访问。
# 3、内容个性化:根据用户位置提供个性化内容
# 如果你没有需要使用 GeoIP 数据库的特定需求,禁用这个选项是完全可以的,特别是在内网环境中,这样可以避免连接外网的问题
ingest.geoip.downloader.enabled: 'false'
## 环境变量配置,这里引入上面设置的用户名、密码 secret 文件
extraEnvs:
- name: ELASTIC_USERNAME
valueFrom:
secretKeyRef:
name: elastic-auth
key: username
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elastic-auth
key: password
# ============调度配置============
## 设置调度策略
## - hard:只有当有足够的节点时 Pod 才会被调度,并且它们永远不会出现在同一个节点上
## - soft:尽最大努力调度
antiAffinity: 'soft'
## 容忍配置
# tolerations:
# - operator: "Exists" ##容忍全部污点
YAML用于创建 Client 节点的 values 文件:
# 创建文件:values-client.yaml
# ============设置集群名称============
## 设置集群名称
clusterName: 'elasticsearch'
## 设置节点名称
nodeGroup: 'client'
## 设置角色
roles:
master: 'false'
ingest: 'false'
data: 'false'
# ============镜像配置============
## 指定镜像与镜像版本
image: 'elasticsearch'
# image: 'registry.cn-hangzhou.aliyuncs.com/egon-k8s-test/elasticsearch'
imageTag: '7.17.3'
## 副本数
# 测试环境资源有限,所以设置为1吧
replicas: 2
# ============资源配置============
## JVM 配置参数
esJavaOpts: '-Xmx1g -Xms1g'
## 部署资源配置(生产环境一定要设置大些)
resources:
requests:
cpu: '1000m'
memory: '2Gi'
limits:
cpu: '1000m'
memory: '2Gi'
## 数据持久卷配置
persistence:
enabled: false
# ============安全配置============
## 设置协议,可配置为 http、https
protocol: http
## 证书挂载配置,这里我们挂入上面创建的证书
secretMounts:
- name: elastic-certs
secretName: elastic-certs
path: /usr/share/elasticsearch/config/certs
## 允许您在/usr/share/elasticsearch/config/中添加任何自定义配置文件,例如 elasticsearch.yml
## ElasticSearch 7.x 默认安装了 x-pack 插件,部分功能免费,这里我们配置下
## 下面注掉的部分为配置 https 证书,配置此部分还需要配置 helm 参数 protocol 值改为 https
esConfig:
elasticsearch.yml: |
xpack.security.enabled: true
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
xpack.security.transport.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.enabled: true
# xpack.security.http.ssl.truststore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
# xpack.security.http.ssl.keystore.path: /usr/share/elasticsearch/config/certs/elastic-certificates.p12
## 环境变量配置,这里引入上面设置的用户名、密码 secret 文件
extraEnvs:
- name: ELASTIC_USERNAME
valueFrom:
secretKeyRef:
name: elastic-auth
key: username
- name: ELASTIC_PASSWORD
valueFrom:
secretKeyRef:
name: elastic-auth
key: password
# ============Service 配置============
service:
type: NodePort
nodePort: '30200'
YAML安装:
# --------------->>>>>>>>>>> 注意install指定的release名字不能重复
# helm install 你起的release名 你的chart包的路径 -f values-master.yaml --namespace logging
# 如果是升级安装则用:helm upgrade --install 你起的release名 你的chart包的路径 -f values-master.yaml --namespace logging
cd elasticsearch/
# 安装 master 节点
helm install es-master ./ -f values-master.yaml --namespace logging
# 安装 data 节点
helm install es-data ./ -f values-data.yaml --namespace logging
# 安装 client 节点
helm install es-client ./ -f values-client.yaml --namespace logging
# 升级操作示例
helm upgrade --install es-master ./ -f values-master.yaml --namespace logging
ShellScript4.2. 安装 Kibana
下载并解压 chart 包:
helm pull elastic/kibana --untar --version 7.17.3
cd kibana
ShellScript创建用于安装 Kibana 的 values 文件:
# 创建全新文件:values-prod.yaml
## 指定镜像与镜像版本
image: 'registry.cn-hangzhou.aliyuncs.com/egon-k8s-test/kibana'
#image: 'docker.elastic.co/kibana/kibana'
imageTag: '7.17.3'
imagePullPolicy: "IfNotPresent"
## 配置连接 ElasticSearch 地址,使用的es-client的svc
elasticsearchHosts: 'http://elasticsearch-client:9200'
# ============环境变量配置============
## 环境变量配置,这里引入上面设置的用户名、密码 secret 文件
extraEnvs:
- name: 'ELASTICSEARCH_USERNAME'
valueFrom:
secretKeyRef:
name: elastic-auth
key: username
- name: 'ELASTICSEARCH_PASSWORD'
valueFrom:
secretKeyRef:
name: elastic-auth
key: password
# ============资源配置============
resources:
requests:
cpu: '500m'
memory: '1Gi'
limits:
cpu: '500m'
memory: '1Gi'
# ============配置 Kibana 参数============
## kibana 配置中添加语言配置,设置 kibana 为中文
kibanaConfig:
kibana.yml: |
i18n.locale: "zh-CN"
server.publicBaseUrl: "http://192.168.2.201:30601" #这里地址改为你访问 kibana 的地址,不能以 / 结尾
# ============Service 配置============
service:
type: NodePort
nodePort: '30601'
YAML部署:
helm install kibana ./ -f values-prod.yaml --namespace logging
ShellScript上面我们安装 Kibana 的时候指定了 30601 的 NodePort 端口,所以我们可以使用任意 k8s 集群节点 IP+30601 访问,例如:http://192.168.2.201:30601。用户名和密码为 4.1.4. 小结设置的用户名和密码。
4.3. 安装 Fluentd 采集日志
Fluentd 的配置文件中主要三段:日志源配置(采集日志数据)、路由(日志发往的目标)、过滤(过滤掉一些无用数据)。
4.3.1. 日志源配置
<source>
@id fluentd-containers.log
@type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取最新的日志。
path /var/log/containers/*.log # 挂载的宿主机容器日志地址
pos_file /var/log/es-containers.log.pos # 指定一个位置文件(position file)。位置文件是一个记录文件读取位置的文件,用于在文件轮转(log rotation)或重新启动时,帮助应用程序记住上次读取的位置,以便继续从上次位置读取日志文件。
tag raw.kubernetes.* # 设置日志标签
read_from_head true # 表示 Fluentd 在第一次读取文件时,从文件头开始读取(而不是只读取新增的部分)。适用于启动时需要读取已有日志的场景。
<parse> # 多行格式化成 JSON
@type multi_format # 使用 multi-format-parser 解析器插件
<pattern>
format json # JSON 解析器
time_key time # 指定事件时间的时间字段
time_format %Y-%m-%dT%H:%M:%S.%NZ # 时间格式
</pattern>
<pattern>
format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
time_format %Y-%m-%dT%H:%M:%S.%N%:z
</pattern>
</parse>
</source>
YAML上面配置部分参数说明如下:
- <source>:表示 Fluentd 的输入源配置块的开始。
- @id fluentd-containers.log:为输入源配置块指定一个唯一的标识符,用于在 Fluentd 配置中引用该输入源。
- @type tail:tail 是 Fluentd 内置的输入插件类型,类似于 Linux 命令 tail -f,用于从日志文件中读取新增的日志行,适合处理容器或文件系统中的日志文件。
- path /var/log/containers/*.log:tail 类型下的特定参数,告诉 Fluentd 采集 /var/log/containers 目录下的所有日志,这是 containerd 容器引擎在 Kubernetes 节点上用来存储运行容器 stdout 输出日志数据的目录。
- pos_file:pos_file 是指定一个用于记录读取进度的文件(即日志文件读取到了哪个位置)。如果 Fluentd 重启或遇到故障,它会从上次停止的地方继续读取日志,避免日志丢失或重复处理。
- tag raw.kubernetes.*:这个标签会应用于所有从日志文件中读取的数据,方便后续的过滤、转发和处理操作。raw.kubernetes.* 是一个通配符标签。
- read_from_head true:表示 Fluentd 在第一次读取文件时,从文件头开始读取(而不是只读取新增的部分)。适用于启动时需要读取已有日志的场景。
- <parse>:表示开始配置日志解析的部分,定义如何解析日志内容。
- @type multi_format:指定使用 multi-format-parser 插件进行日志解析,支持多种日志格式。它会按照配置顺序依次尝试每个解析器,直到其中一个解析器成功解析当前日志行为止,如下定义了两个 <pattern> 就是两个解析器,第一个是 json 解析、第二个是正则解析,解析顺序为:
- Fluentd 先会尝试使用 JSON 解析器解析日志。
- 如果日志行是一个有效的 JSON 对象,解析成功,Fluentd 会提取对应字段并完成日志处理。
- 如果日志行不是有效的 JSON(例如是纯文本或其他格式),JSON 解析器会失败,Fluentd 会继续下一步。
- 如果 JSON 解析失败,Fluentd 会尝试使用正则表达式解析。
- Fluentd 会用正则表达式匹配当前日志行,并提取指定的字段(如时间、流类型、日志内容)。
- 如果日志行符合正则表达式的模式,则解析成功,Fluentd 会处理该日志。
- 如果日志行不符合正则表达式,解析也会失败。
- 第一个 <pattern>:定义日志解析的模式为 JSON 格式。
- format json:第一个匹配规则是 JSON 格式,Fluentd 会尝试将日志解析为 JSON 格式。
- time_key time:指定日志中的 time 字段为事件时间。
- time_format %Y-%m-%dT%H:%M:%S.%NZ:设置时间字段的格式,采用 ISO8601 格式,精确到毫秒,后面有 Z 表示 UTC 时间。
- 第二个 <pattern>:定义日志解析的模式为正则表达式格式。
- (?<time>.+) :提取日志中的时间部分。
- (?<stream>stdout|stderr) :提取日志输出的流类型,可能是 stdout 或 stderr 。
- (?<log>.*) :提取日志的实际内容。
- @type multi_format:指定使用 multi-format-parser 插件进行日志解析,支持多种日志格式。它会按照配置顺序依次尝试每个解析器,直到其中一个解析器成功解析当前日志行为止,如下定义了两个 <pattern> 就是两个解析器,第一个是 json 解析、第二个是正则解析,解析顺序为:
4.3.2. 路由配置
上面是日志源的配置,接下来看看如何将日志数据发送到 Elasticsearch。
<match **> # <match **> 中 math 后跟着的是一个匹配日志源的正则,此处的两个星号**代表捕获所有的日志并将它们发送给 Elasticsearch。
@id elasticsearch # 为这个输出插件配置指定了一个唯一的 ID elasticsearch,便于管理和识别。
@type elasticsearch # 使用了 Fluentd 的内置 elasticsearch 插件作为日志输出插件,将日志数据发送到 Elasticsearch。
@log_level info # 日志级别配置成i nfo,表示任何该级别及者该级别以上(INFO、WARNING、ERROR)的日志都将被路由到 ES。
include_tag_key true # 设置为 true,表示在发送到 Elasticsearch 的日志数据中会包含日志的标签(tag)。这是有用的,可以在 Elasticsearch 中使用标签进行筛选和查询
type_name fluentd # 用于指定 Elasticsearch 中索引的类型名称,这会影响数据在 Elasticsearch 中的存储方式。
# 定义 Elasticsearch 的地址,也可以配置认证信息,我们的 Elasticsearch 不需要认证,所以这里直接指定 host 和 port 即可。
host "#{ENV['OUTPUT_HOST']}"
# 表示环境变量 OUTPUT_HOST 的值会被插入到配置中。该环境变量的值是 Elasticsearch 的主机名或 IP 地址
port "#{ENV['OUTPUT_PORT']}"
# 设置为 true,表示 Fluentd 将日志数据以 Logstash 格式发送到 Elasticsearch。Logstash 格式通常包括时间戳、日志级别、消息内容等,有助于结构化日志的处理。
logstash_format true
# Fluentd 允许在目标(对接的下游服务)不可用时进行缓存,比如,如果网络出现故障或者 ES 不可用的时候。缓冲区配置也有助于降低磁盘的 IO。
<buffer>
@type file # 设置缓存类型为 file,表示 Fluentd 将日志数据缓存在本地文件中。这对于处理大量日志或网络故障时非常有用。
path /var/log/fluentd-buffers/kubernetes.system.buffer # 缓存文件存储的路径。日志数据会被写入这个路径下的文件中。
flush_mode interval # 设置为 interval,表示按照时间间隔进行数据刷新(将数据从缓冲区发送到 Elasticsearch)。
retry_type exponential_backoff # 设置重试类型为 exponential_backoff,这意味着在网络错误或 Elasticsearch 不可用时,Fluentd 会进行指数退避重试,逐渐增加重试间隔。
flush_thread_count 2 # 设置用于刷新缓冲区的线程数为 2。提高线程数可以提升数据的处理能力。
flush_interval 5s # 设置刷新间隔为 5 秒。这意味着每隔 5 秒,Fluentd 将尝试将缓冲区中的数据发送到 Elasticsearch。
retry_forever # 默认值为 true,表示在 Elasticsearch 不可用时会无限重试,不会放弃。
retry_max_interval 30 # 设置重试的最大间隔时间为 30 秒。如果 Elasticsearch 长时间不可用,重试间隔会增加,直到达到这个最大值。
chunk_limit_size "#{ENV['OUTPUT_BUFFER_CHUNK_LIMIT']}" # 设置缓冲区中单个数据块的最大尺寸,通过环境变量 OUTPUT_BUFFER_CHUNK_LIMIT 配置。限制数据块的大小有助于管理缓冲区内存使用。
queue_limit_length "#{ENV['OUTPUT_BUFFER_QUEUE_LIMIT']}" # 设置缓冲区队列的最大长度,通过环境变量 OUTPUT_BUFFER_QUEUE_LIMIT 配置。超过这个长度的日志将会被阻塞或丢弃。
overflow_action block # 设置溢出行为为 block,这意味着如果缓冲区队列长度超过 queue_limit_length,新的日志写入将被阻塞,直到队列长度减少到可接受的范围内。
</buffer>
</match>
YAML这段配置通过 Fluentd 将所有日志数据发送到 Elasticsearch。它设置了缓存策略、重试机制、缓冲区参数等,以确保在网络故障或 Elasticsearch 不可用时,日志数据能够被有效地缓冲并在恢复后重新发送。此外,它还配置了日志级别、标签处理和格式设置,以便更好地管理和查询日志数据。
4.3.3. 过滤
由于 Kubernetes 集群中应用太多,也还有很多历史数据,所以我们可以只将某些应用的日志进行收集,其他的都过滤掉,那就需要用配置过滤。
比如我们只想采集具有 logging=true 这个 Label 标签的 Pod 日志,如下所示:
# 删除无用的日志字段/属性,以简化日志数据并减小数据存储的负担。
<filter kubernetes.**> # 这个过滤器应用于所有日志标签以 kubernetes. 开头的日志
@type record_transformer # 使用 record_transformer 插件,它允许对日志记录进行转换,如添加、修改或删除字段
remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
# remove_keys 指定了要从日志记录中删除的字段。这里列出的字段包括:
# $.docker.container_id: Docker 容器 ID
# $.kubernetes.container_image_id: Kubernetes 容器镜像 ID
# $.kubernetes.pod_id: Kubernetes Pod ID
# $.kubernetes.namespace_id: Kubernetes 命名空间 ID
# $.kubernetes.master_url: Kubernetes Master URL
# $.kubernetes.labels.pod-template-hash: Kubernetes Pod 模板哈希标签
</filter>
# 只保留具有 logging=true 标签的 Pod 日志
<filter kubernetes.**> # # 这个过滤器同样应用于所有标签以 kubernetes. 开头的日志。
@id filter_log # 为这个过滤器配置一个唯一的 ID filter_log,便于 管理和调试。
@type grep # 使用 grep 插件,它用于按指定的模式过滤日志记录。 只有匹配模式的日志才会被保留下来。
<regexp>
key $.kubernetes.labels.logging # 这是要检查的字段,表示 Kubernetes Pod 的标签 logging。
pattern ^true$ # 这是正则表达式模式,用于匹配字段值。如果 $.kubernetes.labels.logging 字段的值是 true,则日志记录会被保留。否则,日志记录会被丢弃。
</regexp>
</filter>
YAML4.3.4. 安装 Fluentd
要想在 k8 每个物理节点都能采集到数据,我们可以直接用 DasemonSet 控制器来部署 Fluentd 应用,确保在集群中的每个节点上始终运行一个 Fluentd 容器,可以直接使用 Helm 来进行一键安装,为了能够了解更多实现细节,我们这里还是采用手动方法来进行
安装。
官网部署参考:https://docs.fluentd.org/container-deployment/kubernetes
fluented 配置解析,采集容器日志与处理的流转流程如下:
- 日志采集:从指定的路径读取日志文件并打上标签 raw.kubernetes.*。
- 日志源:从容器日志文件 /var/log/containers/*.log 中读取日志。
- 标签:日志被打上 raw.kubernetes.* 标签,这里的 * 是通配符,表示所有相关的日志都会用这个标签。
- 解析:使用 multi_format 解析器处理日志,先尝试 JSON 解析,如果失败,再用正则表达式解析。
- 异常检测+处理 (处理 raw.kubernetes.** 标签的日志):
- 标签匹配:匹配所有以 raw.kubernetes. 开头的日志。
- 插件:使用 detect_exceptions 插件检测异常信息,并处理这些异常栈。
- 标签处理:去除日志标签中的 raw 前缀,将 raw.kubernetes.some_log 转换为kubernetes.some_log。
- 日志字段:指定 log 字段作为日志消息的主要内容。
- 多行处理:确保多行日志在 5 秒内被合并为一条完整的日志记录。
- 过滤器配置:
- 拼接日志(针对所有日志)
- 匹配:所有日志(**)。
- 插件:使用 concat 插件将多行日志拼接成一条完整的日志记录。
- 拼接规则:根据换行符 \n 拼接日志行,拼接后的日志条目之间没有额外分隔符。
- 添加 Kubernetes 元数据。
- 匹配:所有以 kubernetes. 开头的日志。
- 插件:使用 kubernetes_metadata 插件为日志添加 Kubernetes 相关元数据。
- 解析 JSON 字段。
- 匹配:所有以 kubernetes. 开头的日志。
- 插件:使用 parser 插件来处理 JSON 格式的日志字段,保留原始数据,并移除 log 字段后进行进一步解析。
- 删除多余字段。
- 匹配:所有以 kubernetes. 开头的日志。
- 插件:使用 record_transformer 插件删除指定的字段,清理不必要的日志数据。
- 筛选符合条件的日志。
- 匹配:所有以 kubernetes. 开头的日志。
- 插件:使用 grep 插件根据 $.kubernetes.labels.logging 字段的值过滤日志,仅保留 logging=true 的日志。
- 拼接日志(针对所有日志)
# fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-conf
namespace: logging
data:
# 配置采集与处理容器日志
containers.input.conf: |-
<source>
@id fluentd-containers.log
@type tail
path /var/log/containers/*.log
pos_file /var/log/es-containers.log.pos
tag raw.kubernetes.*
read_from_head true
<parse>
@type multi_format
<pattern>
format json
time_key time
time_format %Y-%m-%dT%H:%M:%S.%NZ
</pattern>
<pattern>
format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
time_format %Y-%m-%dT%H:%M:%S.%N%:z
</pattern>
</parse>
</source>
<match raw.kubernetes.**>
@id raw.kubernetes
@type detect_exceptions
remove_tag_prefix raw
message log
multiline_flush_interval 5
</match>
<filter **>
@id filter_concat
@type concat
key message
multiline_end_regexp /\n$/
separator ""
</filter>
<filter kubernetes.**>
@id filter_kubernetes_metadata
@type kubernetes_metadata
</filter>
<filter kubernetes.**>
@id filter_parser
@type parser
key_name log
reserve_data true # 设置为 true,表示在解析过程中保留原始数据。这有助于保留日志数据的原始形式,便于调试和进一步分析。
remove_key_name_field true # 设置为 true,表示在成功解析日志后,移除 key_name 字段。这样可以在解析后去掉指定的日志字段名称。
<parse> # 依次运行多个解析pattern,运行不成功才会进行下一个
@type multi_format
<pattern>
format json
</pattern>
<pattern>
format none # 对于无法解析为 JSON 的数据,none 表示不进行任何处理。可以用于处理非 JSON 格式的日志数据。
</pattern>
</parse>
</filter>
<filter kubernetes.**>
@type record_transformer
remove_keys $.docker.container_id,$.kubernetes.container_image_id,$.kubernetes.pod_id,$.kubernetes.namespace_id,$.kubernetes.master_url,$.kubernetes.labels.pod-template-hash
</filter>
<filter kubernetes.**>
@id filter_log
@type grep
<regexp>
key $.kubernetes.labels.logging
pattern ^true$
</regexp>
</filter>
# 监听配置,一般用于日志聚合用
forward.input.conf: |-
# 监听通过 TCP 发送的消息
<source>
@id forward
@type forward # forward 插件会监听通过 TCP 协议发送到 Fluentd 的日志消息。其他 Fluentd 实例或应用程序可以将日志数据发送到这个 Fluentd 实例,该实例会接收并处理这些日志消息
# 默认情况下,forward 插件会监听 TCP 端口 24224。您可以根据需要修改端口号和其他设置。要进行自定义配置,如设置端口号、绑定地址等,可以添加相应的配置参数。例如
# port 24224 # 设置监听的端口号
# bind 0.0.0.0 # 绑定所有网络接口
# 这个配置通常用于构建多实例 Fluentd 部署,其中一个实例作为集中式日志接收器,接收来自不同实例或服务的日志数据。这种设置有助于集中管理和处理日志数据,并将日志数据进一步转发到其他系统(如 Elasticsearch、Kafka、文件等)。
</source>
# 配置将日志数据发送到 Elasticsearch
output.conf: |-
<match **>
@id elasticsearch
@type elasticsearch
@log_level info
include_tag_key true
# 配置访问ES的svc与端口,账号密码
host elasticsearch-client # 指定ES的client的svc名
port 9200
user elastic # FLUENT_ELASTICSEARCH_USER | FLUENT_ELASTICSEARCH_PASSWORD
password pingk123456
# 配置采集的日志使用 logstash 格式,并且配置日志前缀为 k8s,后续在es中索引查询要用到该前缀。
logstash_format true
logstash_prefix k8s
request_timeout 30s
<buffer>
@type file
path /var/log/fluentd-buffers/kubernetes.system.buffer
flush_mode interval
retry_type exponential_backoff
flush_thread_count 2
flush_interval 5s
retry_forever
retry_max_interval 30
chunk_limit_size 2M
queue_limit_length 8
overflow_action block
</buffer>
</match>
YAML上述配置会采集物理节点上的 /var/log/containers/*.log 日志,然后进行处理后发送到 elasticsearch-client:9200 服务。
然后新建一个 fluentd-daemonset.yaml 的文件,文件内容如下:
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd-es
namespace: logging
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: 'true'
addonmanager.kubernetes.io/mode: Reconcile
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd-es
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: 'true'
addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:
- ''
resources:
- 'namespaces'
- 'pods'
verbs:
- 'get'
- 'watch'
- 'list'
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd-es
labels:
k8s-app: fluentd-es
kubernetes.io/cluster-service: 'true'
addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
name: fluentd-es
namespace: logging
apiGroup: ''
roleRef:
kind: ClusterRole
name: fluentd-es
apiGroup: ''
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: logging
labels:
app: fluentd
kubernetes.io/cluster-service: 'true'
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
kubernetes.io/cluster-service: 'true'
spec:
# 1、控制只调度到带有下述标签的节点上,即只采集固定的节点
# nodeSelector:
# beta.kubernetes.io/fluentd-ds-ready: 'true'
# 2、我们是kubeadm部署的k8s,默认情况下master节点有污点,如果想采集master,则需要加上容忍
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
serviceAccountName: fluentd-es
containers:
- name: fluentd
image: registry.cn-hangzhou.aliyuncs.com/egon-k8s-test/fluentd:v3.4.0
# image: quay.io/fluentd_elasticsearch/fluentd:v3.4.0
volumeMounts:
- name: fluentconfig
mountPath: /etc/fluent/config.d
- name: varlog
mountPath: /var/log
volumes:
- name: fluentconfig
configMap:
name: fluentd-conf
- name: varlog
hostPath:
path: /var/log
YAML想要只采集某些节点的日志,可以添加一个 nodSelector 属性:
nodeSelector:
beta.kubernetes.io/fluentd-ds-ready: 'true'
YAML对应着,你想采集哪个节点的日志,就要给该节点打上标签:
kubectl label nodes [node名字] beta.kubernetes.io/fiuentd-ds-ready=true
ShellScript部署:
kubectl apply -f fluentd-configmap.yaml
kubectl apply -f fluentd-daemonset.yaml
# 查看部署结果
kubectl -n logging get pods -o wide
ShellScript5. 测试
Fluentd 启动成功后,这个时候就可以发送日志到 ES 了,但是我们这里是过滤了只采集具有 logging=true 标签的 Pod 日志,所以现在还没有任何数据会被采集。
创建测试 Pod:
# counter.yaml
apiVersion: v1
kind: Pod
metadata:
name: counter
labels:
logging: 'true' # 一定要具有该标签才会被采集
spec:
containers:
- name: count
image: centos
args:
[
/bin/sh,
-c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done',
]
ShellScript