K8S 运维管理

Helm 包管理器

创建目录「以下操作均在该目录进行」

mkdir -p /opt/k8s/helm/

Helm 简介

Artifact Hub
Helm 是一种 Kubernetes 的包管理器,它允许您打包、配置和部署 Kubernetes 应用程序。Helm 使用称为 Charts 的打包格式,可以将复杂的 Kubernetes 应用程序定义为一个易于管理的单元。

Helm 是查找、分享和使用软件构件 Kubernetes 的最优方式。
Helm 管理名为 chart 的 Kubernetes 包的工具。Helm 可以做以下的事情:

Helm 架构

主要概念

Helm 的优势

组件

Helm 客户端

Helm 客户端 是终端用户的命令行客户端。负责以下内容:

Helm 库

Helm 库 提供执行所有 Helm 操作的逻辑。与 Kubernetes API 服务交互并提供以下功能:

独立的 Helm 库封装了 Helm 逻辑以便不同的客户端可以使用它。

安装 Helm

# 创建文件夹
mkdir -p /opt/k8s/helm && cd /opt/k8s/helm
# 下载二进制文件
wget https://get.helm.sh/helm-v3.2.3-linux-amd64.tar.gz -O linux-amd64.tar.gz
# 解压
tar -zxvf linux-amd64.tar.gz
# 将目录下的 helm 程序拷贝到 usr/local/bin/helm
cp -a linux-amd64/helm /usr/local/bin/helm
# 添加阿里云 helm 仓库

# 验证安装的helm
helm version

Helm 的常用命令

## 添加 Helm 仓库
helm repo add stable https://charts.helm.sh/stable  # 添加名为 stable 的仓库,URL 为 https://charts.helm.sh/stable
helm repo add bitnami https://charts.bitnami.com/bitnami #添加bitnami仓库

## 列出已添加的 Helm 仓库
helm repo list  # 列出所有已添加的 Helm 仓库
# 删除名为 stable 的仓库
helm repo remove stable
## 更新 Helm 仓库
helm repo update  # 更新所有已添加的 Helm 仓库
## 在仓库中搜索 chart
helm search repo nginx  # 在仓库中搜索包含 "nginx" 的 chart
## 下载 chart 到本地
helm pull stable/nginx  # 从 stable 仓库下载 nginx chart 到本地
## 安装 chart
helm install my-nginx stable/nginx  # 安装 stable 仓库中的 nginx chart,并命名为 my-nginx
#helm install my-nginx ./nginx #安装当前目录的nginx chart
## 列出已安装的 chart
helm list  # 列出所有已安装的 chart
## 升级 chart
helm upgrade my-nginx stable/nginx  # 升级名为 my-nginx 的 chart 到最新版本
## 卸载 chart
helm uninstall my-nginx  # 卸载名为 my-nginx 的 chart
## 查看 chart 的 values.yaml 文件
helm show values stable/nginx  # 显示 stable 仓库中 nginx chart 的 values.yaml 文件内容
## 创建一个新的 chart
helm create my-chart  # 创建名为 my-chart 的新 chart 项目
## 打包 chart
helm package my-chart  # 将 my-chart 打包成 .tgz 文件
## 本地渲染 chart 模板
helm template my-nginx stable/nginx  # 渲染 stable 仓库中 nginx chart 的模板,并命名为 my-nginx
## 获取release的values
helm get values my-nginx #获取my-nginx这个release的values
## 获取release的历史记录
helm history my-nginx #获取my-nginx这个release的历史记录
## 管理 chart 依赖
helm dependency update my-chart # 更新 my-chart 的依赖
helm dependency build my-chart # 构建 my-chart 的依赖
## 检查 chart 配置是否有误
helm lint my-chart # 检查 my-chart 的配置是否有误
## 回滚 release 到历史版本
helm rollback my-nginx 1 # 回滚名为 my-nginx 的 release 到第一个历史版本

chart 详解

目录结构

mychart
├── Chart.yaml
├── charts # 该目录保存其他依赖的 chart(子 chart)
├── templates # chart 配置模板,用于渲染最终的 Kubernetes YAML 文件
│   ├── NOTES.txt # 用户运行 helm install 时候的提示信息
│   ├── _helpers.tpl # 用于创建模板时的帮助类
│   ├── deployment.yaml # Kubernetes deployment 配置
│   ├── ingress.yaml # Kubernetes ingress 配置
│   ├── service.yaml # Kubernetes service 配置
│   ├── serviceaccount.yaml # Kubernetes serviceaccount 配置
│   └── tests
│       └── test-connection.yaml
└── values.yaml # 定义 chart 模板中的自定义配置的默认值,可以在执行 helm install 或 helm update 的时候覆盖

Redis chart 实践

修改 helm 源

# 查看默认仓库
helm repo list

# 添加仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
# 阿里云
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo add azure http://mirror.azure.cn/kubernetes/charts

搜索 redis chart

# 搜索 redis chart
helm search repo redis

# 查看安装说明
helm show readme azure/redis

修改配置安装

# 创建redis目录,并切换
mkdir -p /opt/k8s/helm/redis && cd /opt/k8s/helm/redis
# 先将 chart 拉到本地
helm pull azure/redis

# 解压后,修改 values.yaml 中的参数
tar -xvf redis-10.5.7.tgz

# 修改values.yaml文件,内容如下
...
storageClass: nfs-storage # 修改
redis:  
  password: redis # 修改
cluster:  
  enabled: false # 修改
...
修改实例存储大小 persistence.size 为需要的大小
修改 service.nodePorts.redis 向外暴露端口,范围 <30000-32767>

# 安装
helm install redis ./redis
# 卸载
helm uninstall redis

查看安装情况

# 查看 helm 安装列表
helm list

# 查看 redis 命名空间下所有对象信息
kubectl get all

升级与回滚

# 要想升级 chart 可以修改本地的 chart 配置并执行:
helm upgrade [RELEASE] [CHART] [flags]
helm upgrade redis ./redis
# 使用 helm ls 的命令查看当前运行的 chart 的 release 版本,并使用下面的命令回滚到历史版
helm ls
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
redis   default         2               2025-02-17 17:39:56.253158943 +0800 CST deployed        redis-10.5.7    5.0.7

# 回滚到历史版本
helm rollback <RELEASE> [REVISION] [flags]
# 查看历史
helm history redis
# 回退到上一版本
helm rollback redis
# 回退到指定版本
helm rollback redis 3

helm 卸载 redis

helm delete redis

k8s 集群监控

监控方案

Heapster

Heapster 是容器集群监控和性能分析工具,天然的支持Kubernetes 和 CoreOS。
Kubernetes 有个出名的监控 agent---cAdvisor。在每个kubernetes Node 上都会运行 cAdvisor,它会收集本机以及容器的监控数据(cpu,memory,filesystem,network,uptime)。
在较新的版本中,K8S 已经将 cAdvisor 功能集成到 kubelet 组件中。每个 Node 节点可以直接进行 web 访问。

Weave Scope

Weave Scope 可以监控 kubernetes 集群中的一系列资源的状态、资源使用情况、应用拓扑、scale、还可以直接通过浏览器进入容器内部调试等,其提供的功能包括:

Prometheus

Prometheus 是一套开源的监控系统、报警、时间序列的集合,最初由 SoundCloud 开发,后来随着越来越多公司的使用,于是便独立成开源项目。自此以后,许多公司和组织都采用了 Prometheus 作为监控告警工具。

Prometheus 监控 k8s

创建目录「以下操作均在该目录进行」

mkdir -p /opt/k8s/kube-prometheus/

kube-prometheus

替换国内镜像

sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' prometheusOperator-deployment.yaml
sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' prometheus-prometheus.yaml
sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' alertmanager-alertmanager.yaml
sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' kubeStateMetrics-deployment.yaml
sed -i 's/k8s.gcr.io/lank8s.cn/g' kubeStateMetrics-deployment.yaml
sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' nodeExporter-daemonset.yaml
sed -i 's/quay.io/quay.mirrors.ustc.edu.cn/g' prometheusAdapter-deployment.yaml
sed -i 's/k8s.gcr.io/lank8s.cn/g' prometheusAdapter-deployment.yaml

# 查看是否还有国外镜像
grep -r "image: " *

修改访问入口

分别修改 prometheus、alertmanager、grafana 中的 spec.type 为 NodePort 方便测试访问

安装

# 使用服务器端应用(SSA)(生产环境推荐添加参数 --server-side)将 `manifests/setup` 目录下的所有 Kubernetes 资源清单应用到 Kubernetes 集群。
kubectl apply --server-side -f manifests/setup
# 等待 `monitoring` 命名空间中的所有 `CustomResourceDefinition` 资源达到 `Established` 状态。
kubectl wait \
--for condition=Established \
--all CustomResourceDefinition \
--namespace=monitoring
kubectl apply -f manifests/

配置 Ingress

# 通过域名访问(没有域名可以在主机配置 hosts)
10.0.0.101 grafana.wolfcode.cn
10.0.0.101 prometheus.wolfcode.cn
10.0.0.101 alertmanager.wolfcode.cn

# 创建 prometheus-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: monitoring
  name: prometheus-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: grafana.wolfcode.cn  # 访问 Grafana 域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: grafana
            port:
              number: 3000
  - host: prometheus.wolfcode.cn  # 访问 Prometheus 域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prometheus-k8s 
            port:
              number: 9090
  - host: alertmanager.wolfcode.cn  # 访问 alertmanager 域名
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: alertmanager-main
            port:
              number: 9093

# 创建 ingress
kubectl apply -f prometheus-ingress.yaml

卸载

kubectl delete --ignore-not-found=true -f /opt/k8s/kube-prometheus/manifests -f /opt/k8s/kube-prometheus/manifests/setup

ELK 日志管理

image 20250218113627|1300

ELK 组成

Elasticsearch

ES 作为一个搜索型文档数据库,拥有优秀的搜索能力,以及提供了丰富的 REST API 让我们可以轻松的调用接口。

Filebeat

Filebeat 是一款轻量的数据收集工具

Logstash

通过 Logstash 同样可以进行日志收集,但是若每一个节点都需要收集时,部署 Logstash 有点过重,因此这里主要用到 Logstash 的数据清洗能力,收集交给 Filebeat 去实现

Kibana

Kibana 是一款基于 ES 的可视化操作界面工具,利用 Kibana 可以实现非常方便的 ES 可视化操作

集成 ELK

创建目录「以下操作均在该目录进行」

mkdir -p /opt/k8s/elk/

部署 es 搜索服务

创建es.yaml

---
apiVersion: v1
kind: Service # 定义资源类型为 Service(服务)
metadata:
  name: elasticsearch-logging # Service 的名称
  namespace: kube-logging # Service 所在的命名空间
  labels:
    k8s-app: elasticsearch-logging # 标签,用于选择 Pod
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
    kubernetes.io/name: "Elasticsearch" # Kubernetes 名称标签
spec:
  ports:
    - port: 9200 # Service 暴露的端口
      protocol: TCP # 协议类型
      targetPort: db # Pod 上的目标端口,对应容器端口的名称,或者端口号
  selector:
    k8s-app: elasticsearch-logging # 选择具有此标签的 Pod
---
# RBAC authn and authz (RBAC 认证和授权)
apiVersion: v1
kind: ServiceAccount # 定义资源类型为 ServiceAccount(服务账号)
metadata:
  name: elasticsearch-logging # ServiceAccount 的名称
  namespace: kube-logging # ServiceAccount 所在的命名空间
  labels:
    k8s-app: elasticsearch-logging # 标签
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
---
kind: ClusterRole # 定义资源类型为 ClusterRole(集群角色)
apiVersion: rbac.authorization.k8s.io/v1 # API 版本
metadata:
  name: elasticsearch-logging # ClusterRole 的名称
  labels:
    k8s-app: elasticsearch-logging # 标签
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
rules:
  - apiGroups:
      - "" # 核心 API 组
    resources:
      - "services" # 授权的资源类型:Services
      - "namespaces" # 授权的资源类型:Namespaces
      - "endpoints" # 授权的资源类型:Endpoints
    verbs:
      - "get" # 允许的操作:get(获取)
---
kind: ClusterRoleBinding # 定义资源类型为 ClusterRoleBinding(集群角色绑定)
apiVersion: rbac.authorization.k8s.io/v1 # API 版本
metadata:
  namespace: kube-logging # ClusterRoleBinding 所在的命名空间
  name: elasticsearch-logging # ClusterRoleBinding 的名称
  labels:
    k8s-app: elasticsearch-logging # 标签
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
subjects:
  - kind: ServiceAccount # 授权对象类型:ServiceAccount
    name: elasticsearch-logging # ServiceAccount 的名称
    namespace: kube-logging # ServiceAccount 所在的命名空间
    apiGroup: "" # API 组
roleRef:
  kind: ClusterRole # 引用的 ClusterRole 类型
  name: elasticsearch-logging # 引用的 ClusterRole 名称
  apiGroup: rbac.authorization.k8s.io # API 组
---
# Elasticsearch deployment itself (Elasticsearch 部署本身)
apiVersion: apps/v1 # API 版本
kind: StatefulSet # 使用 StatefulSet 创建 Pod(有状态副本集)
metadata:
  name: elasticsearch-logging # Pod 名称,StatefulSet 创建的 Pod 有序号和顺序
  namespace: kube-logging # 命名空间
  labels:
    k8s-app: elasticsearch-logging # 标签
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
    srv: srv-elasticsearch # 自定义标签
spec:
  serviceName: elasticsearch-logging # 与 Service 相关联,确保使用以下 DNS 地址访问 StatefulSet 中的每个 Pod (es-cluster-[0,1,2].elasticsearch.elk.svc.cluster.local)
  replicas: 1 # 副本数量,单节点
  selector:
    matchLabels:
      k8s-app: elasticsearch-logging # 和 Pod 模板配置的 labels 相匹配
  template:
    metadata:
      labels:
        k8s-app: elasticsearch-logging # 标签
        kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    spec:
      serviceAccountName: elasticsearch-logging # 使用 ServiceAccount
      containers:
        - image: docker.io/library/elasticsearch:7.9.3 # 使用的 Elasticsearch 镜像
          name: elasticsearch-logging # 容器名称
          resources:
            limits:
              cpu: 1000m # CPU 限制
              memory: 2Gi # 内存限制
            requests:
              cpu: 100m # CPU 请求
              memory: 500Mi # 内存请求
          ports:
            - containerPort: 9200 # 容器端口
              name: db # 端口名称
              protocol: TCP # 协议
            - containerPort: 9300 # 容器端口
              name: transport # 端口名称
              protocol: TCP # 协议
          volumeMounts:
            - name: elasticsearch-logging # 卷挂载名称
              mountPath: /usr/share/elasticsearch/data/ # 挂载路径
          env:
            - name: "NAMESPACE" # 环境变量:命名空间
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace # 从 Pod 元数据中获取命名空间
            - name: "discovery.type" # 环境变量:定义单节点类型
              value: "single-node" # 单节点
            - name: ES_JAVA_OPTS # 环境变量:设置 Java 内存参数
              value: "-Xms512m -Xmx2g" # 内存参数
      volumes:
        - name: elasticsearch-logging # 卷名称
          hostPath:
            path: /data/es/ # 主机路径,将数据存储在主机节点上
      nodeSelector: # 节点选择器,如果需要匹配落盘节点可以添加 nodeSelect
        es: data # 选择具有 es: data 标签的节点
      tolerations:
        - effect: NoSchedule # 污点容忍
          operator: Exists # 存在即可
      initContainers: # 初始化容器,在主容器启动前运行
        - name: elasticsearch-logging-init # 初始化容器名称
          image: alpine:3.6 # 使用的镜像
          command: [ "/sbin/sysctl", "-w", "vm.max_map_count=262144" ] # 设置内核参数
          securityContext:
            privileged: true # 运行特权容器
        - name: increase-fd-ulimit # 初始化容器名称
          image: busybox # 使用的镜像
          imagePullPolicy: IfNotPresent # 如果本地不存在镜像,则拉取
          command: [ "sh", "-c", "ulimit -n 65536" ] # 修改文件描述符最大数量
          securityContext:
            privileged: true # 运行特权容器
        - name: elasticsearch-volume-init # 初始化容器名称,es 数据落盘初始化,加上 777 权限
          image: alpine:3.6 # 使用的镜像
          command:
            - chmod
            - -R
            - "777"
            - /usr/share/elasticsearch/data/ # 修改权限
          volumeMounts:
            - name: elasticsearch-logging # 卷挂载名称
              mountPath: /usr/share/elasticsearch/data/ # 挂载路径

创建namespace.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: kube-logging

执行命令

# 需要提前给 es 落盘节点打上标签 
kubectl label node k8s-node1 es=data
# 创建命名空间
kubectl create -f namespace.yaml
# 创建服务
kubectl create -f es.yaml
# 查看 pod 启用情况
kubectl get pod -n kube-logging

部署 logstash 数据清洗

创建 logstash.yaml

---
apiVersion: v1
kind: Service # 定义 Kubernetes Service 资源
metadata:
  name: logstash # Service 名称
  namespace: kube-logging # Service 所在的命名空间
spec:
  ports:
    - port: 5044 # Service 暴露的端口
      targetPort: beats # Pod 上的目标端口名称,对应容器端口名称
  selector:
    type: logstash # 通过标签选择 Pod
  clusterIP: None # 使用 Headless Service,不分配 ClusterIP
---
apiVersion: apps/v1
kind: Deployment # 定义 Kubernetes Deployment 资源
metadata:
  name: logstash # Deployment 名称
  namespace: kube-logging # Deployment 所在的命名空间
spec:
  selector:
    matchLabels:
      type: logstash # 通过标签选择 Pod
  template:
    metadata:
      labels:
        type: logstash # Pod 标签
        srv: srv-logstash # Pod 标签
    spec:
      containers:
        - image: docker.io/kubeimages/logstash:7.9.3 # 使用的 Logstash 镜像,支持 arm64 和 amd64 架构
          name: logstash # 容器名称
          ports:
            - containerPort: 5044 # 容器端口
              name: beats # 端口名称
          command:
            - logstash # 容器启动命令
            - '-f' # 指定配置文件
            - '/etc/logstash_c/logstash.conf' # 配置文件路径
          env:
            - name: "XPACK_MONITORING_ELASTICSEARCH_HOSTS" # 环境变量,指定 Elasticsearch 主机
              value: "http://elasticsearch-logging:9200" # Elasticsearch Service 地址
          volumeMounts:
            - name: config-volume # 挂载 ConfigMap 卷,用于 logstash.conf 配置文件
              mountPath: /etc/logstash_c/ # 挂载路径
            - name: config-yml-volume # 挂载 ConfigMap 卷,用于 logstash.yml 配置文件
              mountPath: /usr/share/logstash/config/ # 挂载路径
            - name: timezone # 挂载主机时区文件,保证 Logstash 使用正确时区
              mountPath: /etc/localtime # 挂载路径
          resources: # 资源限制和请求,避免资源抢占
            limits:
              cpu: 1000m # CPU 限制
              memory: 2048Mi # 内存限制
            requests:
              cpu: 512m # CPU 请求
              memory: 512Mi # 内存请求
      volumes:
        - name: config-volume # ConfigMap 卷,用于 logstash.conf 配置文件
          configMap:
            name: logstash-conf # ConfigMap 名称
            items:
              - key: logstash.conf # ConfigMap 中的键
                path: logstash.conf # 挂载到容器内的路径
        - name: timezone # 主机时区卷
          hostPath:
            path: /etc/localtime # 主机路径
        - name: config-yml-volume # ConfigMap 卷,用于 logstash.yml 配置文件
          configMap:
            name: logstash-yml # ConfigMap 名称
            items:
              - key: logstash.yml # ConfigMap 中的键
                path: logstash.yml # 挂载到容器内的路径
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-conf
  namespace: kube-logging
  labels:
    type: logstash
data:
  logstash.conf: |-
    input {
      beats { 
        port => 5044 
      } 
    } 
    filter {
      # 处理 ingress 日志 
      if [kubernetes][container][name] == "nginx-ingress-controller" {
        json {
          source => "message" 
          target => "ingress_log" 
        }
        if [ingress_log][requesttime] { 
          mutate { 
            convert => ["[ingress_log][requesttime]", "float"] 
          }
        }
        if [ingress_log][upstremtime] { 
          mutate { 
            convert => ["[ingress_log][upstremtime]", "float"] 
          }
        } 
        if [ingress_log][status] { 
          mutate { 
            convert => ["[ingress_log][status]", "float"] 
          }
        }
        if  [ingress_log][httphost] and [ingress_log][uri] {
          mutate { 
            add_field => {"[ingress_log][entry]" => "%{[ingress_log][httphost]}%{[ingress_log][uri]}"} 
          } 
          mutate { 
            split => ["[ingress_log][entry]","/"] 
          } 
          if [ingress_log][entry][1] { 
            mutate { 
              add_field => {"[ingress_log][entrypoint]" => "%{[ingress_log][entry][0]}/%{[ingress_log][entry][1]}"} 
              remove_field => "[ingress_log][entry]" 
            }
          } else { 
            mutate { 
              add_field => {"[ingress_log][entrypoint]" => "%{[ingress_log][entry][0]}/"} 
              remove_field => "[ingress_log][entry]" 
            }
          }
        }
      }
      # 处理以srv进行开头的业务服务日志 
      if [kubernetes][container][name] =~ /^srv*/ { 
        json { 
          source => "message" 
          target => "tmp" 
        } 
        if [kubernetes][namespace] == "kube-logging" { 
          drop{} 
        } 
        if [tmp][level] { 
          mutate{ 
            add_field => {"[applog][level]" => "%{[tmp][level]}"} 
          } 
          if [applog][level] == "debug"{ 
            drop{} 
          } 
        } 
        if [tmp][msg] { 
          mutate { 
            add_field => {"[applog][msg]" => "%{[tmp][msg]}"} 
          } 
        } 
        if [tmp][func] { 
          mutate { 
            add_field => {"[applog][func]" => "%{[tmp][func]}"} 
          } 
        } 
        if [tmp][cost]{ 
          if "ms" in [tmp][cost] { 
            mutate { 
              split => ["[tmp][cost]","m"] 
              add_field => {"[applog][cost]" => "%{[tmp][cost][0]}"} 
              convert => ["[applog][cost]", "float"] 
            } 
          } else { 
            mutate { 
              add_field => {"[applog][cost]" => "%{[tmp][cost]}"} 
            }
          }
        }
        if [tmp][method] { 
          mutate { 
            add_field => {"[applog][method]" => "%{[tmp][method]}"} 
          }
        }
        if [tmp][request_url] { 
          mutate { 
            add_field => {"[applog][request_url]" => "%{[tmp][request_url]}"} 
          } 
        }
        if [tmp][meta._id] { 
          mutate { 
            add_field => {"[applog][traceId]" => "%{[tmp][meta._id]}"} 
          } 
        } 
        if [tmp][project] { 
          mutate { 
            add_field => {"[applog][project]" => "%{[tmp][project]}"} 
          }
        }
        if [tmp][time] { 
          mutate { 
            add_field => {"[applog][time]" => "%{[tmp][time]}"} 
          }
        }
        if [tmp][status] { 
          mutate { 
            add_field => {"[applog][status]" => "%{[tmp][status]}"} 
            convert => ["[applog][status]", "float"] 
          }
        }
      }
      mutate { 
        rename => ["kubernetes", "k8s"] 
        remove_field => "beat" 
        remove_field => "tmp" 
        remove_field => "[k8s][labels][app]" 
      }
    }
    output { 
      elasticsearch { 
        hosts => ["http://elasticsearch-logging:9200"] 
        codec => json 
        index => "logstash-%{+YYYY.MM.dd}" #索引名称以logstash+日志进行每日新建 
      }
    }
---
apiVersion: v1
kind: ConfigMap # 定义 Kubernetes ConfigMap 资源
metadata:
  name: logstash-yml # ConfigMap 名称
  namespace: kube-logging # ConfigMap 所在的命名空间
  labels:
    type: logstash # ConfigMap 标签
data:
  logstash.yml: |- # Logstash 配置文件内容
    http.host: "0.0.0.0" # 设置 Logstash HTTP API 监听的 IP 地址,0.0.0.0 表示监听所有网络接口
    xpack.monitoring.elasticsearch.hosts: http://elasticsearch-logging:9200 # 设置 Logstash 监控数据发送到的 Elasticsearch 主机地址,指向 Elasticsearch Service

运行命令

# 创建
kubectl apply -f logstash.yaml

部署 filebeat 数据采集

创建 filebeat.yaml

---
apiVersion: v1
kind: ConfigMap # 定义 ConfigMap 资源,用于存储 Filebeat 配置文件
metadata:
  name: filebeat-config # ConfigMap 名称:filebeat-config
  namespace: kube-logging # 命名空间:kube-logging
  labels:
    k8s-app: filebeat # 标签:k8s-app=filebeat
data:
  filebeat.yml: |- # Filebeat 配置文件内容
    filebeat.inputs:
      - type: container # 输入类型:container(容器日志)
        enable: true # 启用该输入
        paths:
          - /var/log/containers/*.log # Filebeat 采集挂载到 Pod 中的日志目录
        processors:
          - add_kubernetes_metadata: # 添加 Kubernetes 元数据,用于后续数据清洗
              host: ${NODE_NAME} # 主机名,从环境变量 NODE_NAME 获取
              matchers:
                - logs_path:
                    logs_path: "/var/log/containers/" # 匹配日志路径
    #output.kafka:  # 如果日志量较大,ES 中的日志有延迟,可以选择在 Filebeat 和 Logstash 中间加入 Kafka
    #  hosts: ["kafka-log-01:9092", "kafka-log-02:9092", "kafka-log-03:9092"] 
    # topic: 'topic-test-log' 
    #  version: 2.0.0 
    output.logstash: # 因为还需要部署 Logstash 进行数据清洗,因此 Filebeat 将数据推送到 Logstash
      hosts: [ "logstash:5044" ] # Logstash 主机地址和端口
      enabled: true # 启用 Logstash 输出
---
apiVersion: v1
kind: ServiceAccount # 定义 ServiceAccount 资源,用于 Filebeat Pod 的身份验证
metadata:
  name: filebeat # ServiceAccount 名称:filebeat
  namespace: kube-logging # 命名空间:kube-logging
  labels:
    k8s-app: filebeat # 标签:k8s-app=filebeat
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # 定义 ClusterRole 资源,授予 Filebeat Pod 必要权限
metadata:
  name: filebeat # ClusterRole 名称:filebeat
  labels:
    k8s-app: filebeat # 标签:k8s-app=filebeat
rules:
  - apiGroups: [ "" ] # 核心 API 组
    resources:
      - namespaces # 资源类型:namespaces
      - pods # 资源类型:pods
    verbs: [ "get", "watch", "list" ] # 允许的操作:get, watch, list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding # 定义 ClusterRoleBinding 资源,将 ClusterRole 绑定到 ServiceAccount
metadata:
  name: filebeat # ClusterRoleBinding 名称:filebeat
subjects:
  - kind: ServiceAccount # 授权对象类型:ServiceAccount
    name: filebeat # ServiceAccount 名称:filebeat
    namespace: kube-logging # 命名空间:kube-logging
roleRef:
  kind: ClusterRole # 引用的 ClusterRole 类型
  name: filebeat # 引用的 ClusterRole 名称:filebeat
  apiGroup: rbac.authorization.k8s.io # API 组:rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: DaemonSet # 定义 DaemonSet 资源,确保每个节点运行一个 Filebeat Pod
metadata:
  name: filebeat # DaemonSet 名称:filebeat
  namespace: kube-logging # 命名空间:kube-logging
  labels:
    k8s-app: filebeat # 标签:k8s-app=filebeat
spec:
  selector:
    matchLabels:
      k8s-app: filebeat # 选择具有 k8s-app=filebeat 标签的 Pod
  template:
    metadata:
      labels:
        k8s-app: filebeat # Pod 标签:k8s-app=filebeat
    spec:
      serviceAccountName: filebeat # 使用 ServiceAccount:filebeat
      terminationGracePeriodSeconds: 30 # Pod 终止宽限期:30 秒
      containers:
        - name: filebeat # 容器名称:filebeat
          image: docker.io/kubeimages/filebeat:7.9.3 # 使用的 Filebeat 镜像,支持 arm64 和 amd64 架构
          args: [
            "-c", "/etc/filebeat.yml", # 指定 Filebeat 配置文件路径
            "-e","-httpprof","0.0.0.0:6060" # 启用 Filebeat HTTP profiler
          ]
          #ports:
          #  - containerPort: 6060
          #    hostPort: 6068
          env:
            - name: NODE_NAME # 环境变量:节点名称
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName # 从 Pod 规范中获取节点名称
            - name: ELASTICSEARCH_HOST # 环境变量:Elasticsearch 主机
              value: elasticsearch-logging # Elasticsearch Service 名称
            - name: ELASTICSEARCH_PORT # 环境变量:Elasticsearch 端口
              value: "9200" # Elasticsearch 端口
          securityContext:
            runAsUser: 0 # 以 root 用户运行 Filebeat
            # 如果使用 Red Hat OpenShift,取消注释以下行:
            #privileged: true
          resources:
            limits:
              memory: 1000Mi # 内存限制:1000Mi
              cpu: 1000m # CPU 限制:1000m
            requests:
              memory: 100Mi # 内存请求:100Mi
              cpu: 100m # CPU 请求:100m
          volumeMounts:
            - name: config # 挂载 Filebeat 配置文件
              mountPath: /etc/filebeat.yml # 挂载路径
              readOnly: true # 只读挂载
              subPath: filebeat.yml # 子路径:filebeat.yml
            - name: data # 持久化 Filebeat 数据到宿主机
              mountPath: /usr/share/filebeat/data # 挂载路径
            - name: varlibdockercontainers # 挂载宿主机上的源日志目录到 Filebeat 容器中,如果未修改 Docker 或 Containerd 的运行时标准日志落盘路径,可以将 mountPath 改为 /var/lib
              mountPath: /var/lib # 挂载路径
              readOnly: true # 只读挂载
            - name: varlog # 挂载宿主机上的 /var/log/pods 和 /var/log/containers 的软链接到 Filebeat 容器中
              mountPath: /var/log/ # 挂载路径
              readOnly: true # 只读挂载
            - name: timezone #挂载时区文件
              mountPath: /etc/localtime #挂载路径
      volumes:
        - name: config # Filebeat 配置文件卷
          configMap:
            defaultMode: 0600 # 默认权限:0600
            name: filebeat-config # ConfigMap 名称:filebeat-config
        - name: varlibdockercontainers # 宿主机上的源日志目录卷,如果未修改 Docker 或 Containerd 的运行时标准日志落盘路径,可以将 path 改为 /var/lib
          hostPath:
            path: /var/lib # 宿主机路径
        - name: varlog # 宿主机上的 /var/log/ 目录卷
          hostPath:
            path: /var/log/ # 宿主机路径
        # data folder stores a registry of read status for all files, so we don't send everything again on a Filebeat pod restart
        - name: inputs #输入文件
          configMap:
            defaultMode: 0600
            name: filebeat-inputs
        - name: data # Filebeat 数据卷,用于存储已读取文件的注册信息,避免 Filebeat Pod 重启时重复发送日志
          hostPath:
            path: /data/filebeat-data # 宿主机路径
            type: DirectoryOrCreate # 如果目录不存在,则创建
        - name: timezone #时区文件
          hostPath:
            path: /etc/localtime
      tolerations: # 加入容忍,能够调度到每一个节点
        - effect: NoExecute # 污点效果:NoExecute
          key: dedicated # 污点键:dedicated
          operator: Equal # 操作符:Equal
          value: gpu # 污点值:gpu
        - effect: NoSchedule # 污点效果:NoSchedule
          operator: Exists # 操作符:Exists

运行命令

# 创建
kubectl apply -f filebeat.yaml

部署 kibana 可视化界面

创建kibana.yaml
此处有配置 kibana 访问域名,如果没有域名则需要在本机配置 hosts
10.0.0.102 kibana.wolfcode.cn

---
apiVersion: v1
kind: ConfigMap # 定义 ConfigMap 资源,用于存储 Kibana 配置文件
metadata:
  namespace: kube-logging # 命名空间:kube-logging
  name: kibana-config # ConfigMap 名称:kibana-config
  labels:
    k8s-app: kibana # 标签:k8s-app=kibana
data:
  kibana.yml: |- # Kibana 配置文件内容
    server.name: kibana # Kibana 服务器名称:kibana
    server.host: "0" # Kibana 服务器监听地址:0(监听所有接口)
    i18n.locale: zh-CN # 设置默认语言为中文
    elasticsearch:
      hosts: ${ELASTICSEARCH_HOSTS} # Elasticsearch 集群连接地址,由于都在 Kubernetes 部署且在同一命名空间下,可以直接使用 Service 名称连接
---
apiVersion: v1
kind: Service # 定义 Service 资源,用于暴露 Kibana 服务
metadata:
  name: kibana # Service 名称:kibana
  namespace: kube-logging # 命名空间:kube-logging
  labels:
    k8s-app: kibana # 标签:k8s-app=kibana
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
    kubernetes.io/name: "Kibana" # Kubernetes 名称标签
    srv: srv-kibana # 自定义标签:srv-kibana
spec:
  type: NodePort # Service 类型:NodePort,通过节点端口暴露服务
  ports:
    - port: 5601 # Service 端口:5601
      protocol: TCP # 协议:TCP
      targetPort: ui # Pod 目标端口:ui(对应容器端口名称)
  selector:
    k8s-app: kibana # 选择具有 k8s-app=kibana 标签的 Pod
---
apiVersion: apps/v1
kind: Deployment # 定义 Deployment 资源,用于部署 Kibana Pod
metadata:
  name: kibana # Deployment 名称:kibana
  namespace: kube-logging # 命名空间:kube-logging
  labels:
    k8s-app: kibana # 标签:k8s-app=kibana
    kubernetes.io/cluster-service: "true" # Kubernetes 集群服务标签
    addonmanager.kubernetes.io/mode: Reconcile # Addon 管理器模式
    srv: srv-kibana # 自定义标签:srv-kibana
spec:
  replicas: 1 # 副本数量:1
  selector:
    matchLabels:
      k8s-app: kibana # 选择具有 k8s-app=kibana 标签的 Pod
  template:
    metadata:
      labels:
        k8s-app: kibana # Pod 标签:k8s-app=kibana
    spec:
      containers:
        - name: kibana # 容器名称:kibana
          image: docker.io/kubeimages/kibana:7.9.3 # 使用的 Kibana 镜像,支持 arm64 和 amd64 架构
          resources:
            # 初始化时需要更多 CPU,因此使用 burstable 类
            limits:
              cpu: 1000m # CPU 限制:1000m
            requests:
              cpu: 100m # CPU 请求:100m
          env:
            - name: ELASTICSEARCH_HOSTS # 环境变量:Elasticsearch 主机地址
              value: http://elasticsearch-logging:9200 # Elasticsearch Service 地址
          ports:
            - containerPort: 5601 # 容器端口:5601
              name: ui # 端口名称:ui
              protocol: TCP # 协议:TCP
          volumeMounts:
            - name: config # 挂载 Kibana 配置文件
              mountPath: /usr/share/kibana/config/kibana.yml # 挂载路径
              readOnly: true # 只读挂载
              subPath: kibana.yml # 子路径:kibana.yml
      volumes:
        - name: config # Kibana 配置文件卷
          configMap:
            name: kibana-config # ConfigMap 名称:kibana-config
---
apiVersion: networking.k8s.io/v1
kind: Ingress # 定义 Ingress 资源,用于通过域名访问 Kibana
metadata:
  name: kibana # Ingress 名称:kibana
  namespace: kube-logging # 命名空间:kube-logging
spec:
  ingressClassName: nginx # 使用的 Ingress 控制器:nginx
  rules:
    - host: kibana.wolfcode.cn # 域名:kibana.wolfcode.cn
      http:
        paths:
          - path: / # 路径:/
            pathType: Prefix # 路径类型:Prefix(前缀匹配)
            backend:
              service:
                name: kibana # 后端 Service 名称:kibana
                port:
                  number: 5601 # 后端 Service 端口:5601

运行命令

# 创建
kubectl apply -f kibana.yaml
# 查看svc
kubectl get svc -n kube-logging -o wide
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE   SELECTOR
elasticsearch-logging   ClusterIP   10.106.218.50   <none>        9200/TCP         71m   k8s-app=elasticsearch-logging
kibana                  NodePort    10.110.253.35   <none>        5601:31068/TCP   25m   k8s-app=kibana
logstash                ClusterIP   None            <none>        5044/TCP         36m   type=logstash

Kibana 配置

避免日志无限增长并占用过多磁盘空间

在 Kibana 中设置索引生命周期管理(ILM)策略
1. 导航到 Stack Management(堆栈管理)

方便在 discover 中查看日志

  1. 打开Kibana 界面->Stack Management 可以看到采集到的日志
  2. 打开 索引模式->创建索引模式 按钮
  3. 索引模式名称 里面配置 logstash-* ,点击下一步
  4. 时间字段 选择 @timestamp,点击 创建索引模式 按钮

由于部署的单节点,产生副本后索引状态会变成 yellow,打开 dev tools,取消所有索引的副本数

PUT _all/_settings
{
    "number_of_replicas": 0
}

为了标准化日志中的 map 类型,以及解决链接索引生命周期策略,我们需要修改默认模板

PUT _template/logstash 
{ 
    "order": 1, 
    "index_patterns": [ 
      "logstash-*" 
    ], 
    "settings": { 
      "index": { 
      "lifecycle" : { 
          "name" : "logstash-history-ilm-policy" 
        }, 
        "number_of_shards": "2", 
        "refresh_interval": "5s", 
        "number_of_replicas" : "0" 
      } 
    }, 
    "mappings": { 
        "properties": { 
          "@timestamp": { 
            "type": "date" 
          }, 
          "applog": { 
            "dynamic": true, 
            "properties": { 
              "cost": { 
                "type": "float" 
              }, 
              "func": { 
                "type": "keyword" 
              }, 
              "method": { 
                "type": "keyword" 
              } 
            } 
          }, 
          "k8s": { 
            "dynamic": true, 
            "properties": { 
              "namespace": { 
                "type": "keyword" 
              }, 
              "container": { 
                "dynamic": true, 
                "properties": { 
                  "name": { 
                    "type": "keyword" 
                  } 
                } 
              }, 
              "labels": { 
                "dynamic": true, 
                "properties": { 
                  "srv": { 
                    "type": "keyword" 
                  } 
                } 
              } 
            } 
          }, 
          "geoip": { 
            "dynamic": true, 
            "properties": { 
              "ip": { 
                "type": "ip" 
              }, 
              "latitude": { 
                "type": "float" 
              }, 
              "location": { 
                "type": "geo_point" 
              }, 
              "longitude": { 
                "type": "float" 
              } 
            } 
          } 
      } 
    }, 
    "aliases": {} 
  } 

最后即可通过 discover 进行搜索了