13、Prometheus

13、Prometheus

Prometheus特性

  • 一个多维的数据模型,具有由指标名称和键/值对标识的时间序列数据;
  • 使用 PromOL 查询和聚合数据,可以非常灵活的对数据进行检索;
  • 不依赖额外的数据存储,Prometheus 本身就是一个时序数据库,提供本地存储和分布式存储,并且每个Prometheus都是自治的;
  • 应用程序暴露Metrics接口,Prometheus 通过基于 HTTP 的 Pull 模型采集数据,同时可以使用 PushGateway 进行Push数据;
  • Prometheus 同时支持动态服务发现和静态配置发现目标机器;
  • 支持多种图形和仪表盘,和Grafana堪称“绝配”。

1. Prometheus架构

img

  • Prometheus Server:Prometheus 生态最重要的组件,主要用于抓取和存储时间序列数据,同时提供数据的查询和告警策略的配置管理;
  • Alertmanager:Prometheus 生态用于告警的组件,Prometheus Server 会将告警发送给 Alertmanager,Alertmanager 根据路由配置,将告警信息发送给指定的人或组。Alertmanager 支持邮件、Webhook、微信、钉钉、短信等媒介进行告警通知;
  • Grafana:用于展示数据,便于数据的查询和观测;
  • Push Gateway:Prometheus 本身是通过 Pull 的方式拉取数据,但是有些监控数据可能是短期的,如果没有采集数据可能会出现丢失。Push Gateway 可以用来解决此类问题,它可以用来接收数据,也就是客户端可以通过 Push 的方式将数据推送到 Push Gateway,之后 Prometheus 可以通过 Pull 拉取该数据;
  • Exporter:主要用来采集监控数据,比如主机的监控数据可以通过 node_exporter采集,MySQL 的监控数据可以通过 mysql_exporter 采集,之后 Exporter 暴露一个接口,比如/metrics,Prometheus 可以通过该接口采集到数据;
  • PromQL:PromQL 其实不算 Prometheus 的组件,它是用来查询数据的一种语法,比如查询数据库的数据,可以通过 SQL 语句,查询 Loki 的数据,可以通过 LogQL,查询 Prometheus 数据的叫做 PromQL;
  • Service Discovery:用来发现监控目标的自动发现,常用的有基于 Kubernetes、Consul、Eureka、文件的自动发现等。

2. 部署Prometheus

2.1 下载项目、拉取镜像

本次部署使用 Kube-Prometheus Stack 进行高可用安装

Kube-Prometheus 项目地址:https://github.com/prometheus-operator/kube-prometheus/

首先需要通过该项目地址,找到和自己 Kubernetes 版本对应的Kube Prometheus Stack的版本:

image-20240507120351197

我的k8s版本是1.23.7,所以我进行clone release-0.11版本。

git clone -b release-0.11 https://github.com/prometheus-operator/kube-prometheus.git

#如果报错↓↓↓
Git默认的POST缓冲区大小可能不足以处理大的文件传输。你可以通过以下命令临时增加这个值:
git config --global http.postBuffer 524288000
这个命令将POST缓冲区大小设置为500MB。之后再尝试进行git clone

进入到/root/kube-prometheus/manifests,grep出所需要用到的镜像,提前拉下来推送到本地Harbor仓库中。

[root@k8s-master01 manifests]# pwd
/root/kube-prometheus/manifests
[root@k8s-master01 manifests]# grep -n "image:" -r . --exclude-dir=setup/
./alertmanager-alertmanager.yaml:13:  image: quay.io/prometheus/alertmanager:v0.24.0
./blackboxExporter-deployment.yaml:33:        image: quay.io/prometheus/blackbox-exporter:v0.21.0
./blackboxExporter-deployment.yaml:60:        image: jimmidyson/configmap-reload:v0.5.0
./blackboxExporter-deployment.yaml:88:        image: quay.io/brancz/kube-rbac-proxy:v0.12.0
./grafana-deployment.yaml:33:        image: grafana/grafana:8.5.5
./kubeStateMetrics-deployment.yaml:35:        image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.5.0
./kubeStateMetrics-deployment.yaml:56:        image: quay.io/brancz/kube-rbac-proxy:v0.12.0
./kubeStateMetrics-deployment.yaml:82:        image: quay.io/brancz/kube-rbac-proxy:v0.12.0
./nodeExporter-daemonset.yaml:38:        image: quay.io/prometheus/node-exporter:v1.3.1
./nodeExporter-daemonset.yaml:74:        image: quay.io/brancz/kube-rbac-proxy:v0.12.0
./prometheus-prometheus.yaml:21:  image: quay.io/prometheus/prometheus:v2.36.1
./prometheusAdapter-deployment.yaml:40:        image: k8s.gcr.io/prometheus-adapter/prometheus-adapter:v0.9.1
./prometheusOperator-deployment.yaml:33:        image: quay.io/prometheus-operator/prometheus-operator:v0.57.0
./prometheusOperator-deployment.yaml:56:        image: quay.io/brancz/kube-rbac-proxy:v0.12.0

release-0.11版本项目文件中的镜像地址,以上镜像地址信息过滤重复后的结果如下(并更换了k8s.io的镜像源为docker hub的)。

docker pull quay.io/prometheus/alertmanager:v0.24.0
docker pull quay.io/prometheus/blackbox-exporter:v0.21.0
docker pull jimmidyson/configmap-reload:v0.5.0
docker pull quay.io/brancz/kube-rbac-proxy:v0.12.0
docker pull grafana/grafana:8.5.5
docker pull landv1001/kube-state-metrics:v2.5.0
docker pull quay.io/prometheus/node-exporter:v1.3.1
docker pull quay.io/prometheus/prometheus:v2.36.1
docker pull v5cn/prometheus-adapter:v0.9.1
docker pull quay.io/prometheus-operator/prometheus-operator:v0.57.0
#推送至镜像仓库
docker tag quay.io/prometheus/alertmanager:v0.24.0 10.0.0.138:5000/prometheus/alertmanager:v0.24.0
docker push 10.0.0.138:5000/prometheus/alertmanager:v0.24.0

docker tag quay.io/prometheus/blackbox-exporter:v0.21.0 10.0.0.138:5000/prometheus/blackbox-exporter:v0.21.0
docker push 10.0.0.138:5000/prometheus/blackbox-exporter:v0.21.0

docker tag jimmidyson/configmap-reload:v0.5.0 10.0.0.138:5000/prometheus/configmap-reload:v0.5.0
docker push 10.0.0.138:5000/prometheus/configmap-reload:v0.5.0

docker tag quay.io/brancz/kube-rbac-proxy:v0.12.0 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0
docker push 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0

docker tag grafana/grafana:8.5.5 10.0.0.138:5000/prometheus/grafana:8.5.5
docker push 10.0.0.138:5000/prometheus/grafana:8.5.5

docker tag landv1001/kube-state-metrics:v2.5.0 10.0.0.138:5000/prometheus/kube-state-metrics:v2.5.0
docker push 10.0.0.138:5000/prometheus/kube-state-metrics:v2.5.0

docker tag quay.io/prometheus/node-exporter:v1.3.1 10.0.0.138:5000/prometheus/node-exporter:v1.3.1
docker push 10.0.0.138:5000/prometheus/node-exporter:v1.3.1

docker tag quay.io/prometheus/prometheus:v2.36.1 10.0.0.138:5000/prometheus/prometheus:v2.36.1
docker push 10.0.0.138:5000/prometheus/prometheus:v2.36.1

docker tag v5cn/prometheus-adapter:v0.9.1 10.0.0.138:5000/prometheus/prometheus-adapter:v0.9.1 
docker push 10.0.0.138:5000/prometheus/prometheus-adapter:v0.9.1

docker tag quay.io/prometheus-operator/prometheus-operator:v0.57.0 10.0.0.138:5000/prometheus/prometheus-operator:v0.57.0 
docker push 10.0.0.138:5000/prometheus/prometheus-operator:v0.57.0

推送到本地仓库后记得修改各yaml种的镜像地址为本地仓库镜像地址

[root@k8s-master01 manifests]# cat 1.sh
#!/bin/bash
# Replace images in blackboxExporter-deployment.yaml
sed -i 's#image: quay.io/prometheus/alertmanager:v0.24.0#image: 10.0.0.138:5000/prometheus/alertmanager:v0.24.0#g' blackboxExporter-deployment.yaml
sed -i 's#image: quay.io/prometheus/blackbox-exporter:v0.21.0#image: 10.0.0.138:5000/prometheus/blackbox-exporter:v0.21.0#g' blackboxExporter-deployment.yaml
sed -i 's#image: jimmidyson/configmap-reload:v0.5.0#image: 10.0.0.138:5000/prometheus/configmap-reload:v0.5.0#g' blackboxExporter-deployment.yaml
sed -i 's#image: quay.io/brancz/kube-rbac-proxy:v0.12.0#image: 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0#g' blackboxExporter-deployment.yaml

# Replace images in grafana-deployment.yaml
sed -i 's#image: grafana/grafana:8.5.5#image: 10.0.0.138:5000/prometheus/grafana:8.5.5#g' grafana-deployment.yaml

# Replace images in kubeStateMetrics-deployment.yaml
sed -i 's#image: k8s.gcr.io/kube-state-metrics/kube-state-metrics:v2.5.0#image: 10.0.0.138:5000/prometheus/kube-state-metrics:v2.5.0#g' kubeStateMetrics-deployment.yaml
sed -i 's#image: quay.io/brancz/kube-rbac-proxy:v0.12.0#image: 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0#g' kubeStateMetrics-deployment.yaml

# Replace images in nodeExporter-daemonset.yaml
sed -i 's#image: quay.io/prometheus/node-exporter:v1.3.1#image: 10.0.0.138:5000/prometheus/node-exporter:v1.3.1#g' nodeExporter-daemonset.yaml
sed -i 's#image: quay.io/brancz/kube-rbac-proxy:v0.12.0#image: 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0#g' nodeExporter-daemonset.yaml

# Replace images in prometheus-prometheus.yaml
sed -i 's#image: quay.io/prometheus/prometheus:v2.36.1#image: 10.0.0.138:5000/prometheus/prometheus:v2.36.1#g' prometheus-prometheus.yaml

# Replace images in prometheusAdapter-deployment.yaml
sed -i 's#image: k8s.gcr.io/prometheus-adapter/prometheus-adapter:v0.9.1#image: 10.0.0.138:5000/prometheus/prometheus-adapter:v0.9.1#g' prometheusAdapter-deployment.yaml

# Replace images in prometheusOperator-deployment.yaml
sed -i 's#image: quay.io/brancz/kube-rbac-proxy:v0.12.0#image: 10.0.0.138:5000/prometheus/kube-rbac-proxy:v0.12.0#g' prometheusOperator-deployment.yaml

2.2 部署Kube Prometheus Stack

安装Prometheus Operator

[root@k8s-master01 manifests]# pwd
/root/kube-prometheus/manifests
[root@k8s-master01 manifests]# kubectl create -f setup/

Tips:注意新版安装setup后,可能不会有 Pod,不影响继续安装即可

安装 Prometheus Stack

[root@k8s-master01 manifests]# pwd
/root/kube-prometheus/manifests
[root@k8s-master01 manifests]# kubectl create -f .

2.3 访问Grafana、Prometheus

查看 Prometheus 容器状态

image-20240507134421194

新版添加了 networkpolicy,可能会导致 Grafana 无法访问,执行一下操作即可。

kubectl delete networkpolicy --all -n monitoring

将 Grafana 的 Service 改成 NodePort 类型,并查看暴露出的端口进行访问,通过 30535 端口即可访问到 Grafana 的 Web UI,默认账号密码admin/admin。

image-20240507134748477

image-20240507134821120

选择监控仪表盘

image-20240507135145333

image-20240507135119760

选择node节点查看

image-20240507135313497

将 Prometheus 的 Service 改成 NodePort 类型,并查看暴露出的端口进行访问,通过 31974 端口即可访问到 Prometheus 的 Web UI。

kubectl edit svc prometheus-k8s -n monitoring

image-20240507135543140

image-20240507135622511

3. 云原生与非云原生监控流程

3.1 监控数据来源

Prometheus 通常采用 Pull 的形式来拉取数据,也就意味着被监控应用只要有一个能获取到监控数据的接口,就可以采集到监控数据。

云原生应用监控

基于云原生理念开发的程序自己会暴露 Metrics 接口,就像 Kubernetes 本身的组件、Etcd等,都有一个/metrics接口,Prometheus 只需要请求这个接口即可获取到相关数据。

img

非云原生应用监控

Kubernetes 节点(也就是服务器本身)肯定不是云原生应用,或者说它不算一个应用,而是一个系统,那么系统本身肯定没有类似的Metrics 接口。

那么就需要有一个 Exporter 的 Pod 采集宿主机信息,然后暴露给 Prometheus,此时可以使用专用于主机信息采集的 node-exporter 应用来采集数据,并暴露 Metrics 接口给 Prometheus。

安装Prometheus Stack 时,默认用 DaemonSet 安装了 node-exporter,它会在每个 Kubernetes 节点部署一个 node-exporter。

image-20240507142239455

此时可以在每个宿主机看到node-exporter监听的9100端口,访问9100即可访问到主机的监控信息。

image-20240507142326856

目前比较常用的Exporter工具

img

3.2 ServiceMonitor 自定义资源

3.2.1 了解 ServiceMonitor

Prometheus 有一个配置文件,用于配置需要监控哪些数据,或者配置一些告警策略。这个配置文件的维护非常麻烦,特别是监控项非常多的情况下,很容易出现配置错误,而在 Kubernetes上 部署 Prometheus,可以不用去维护这个配置文件,而是通过一个叫ServiceMonitor 的资源来自动发现监控目标并动态生成配置。

image-20240507143248878

Kubelet 和 NodeExporter 的监控,都会有一个 ServiceMonitor

下图可以看到它的 kind 配置为 ServiceMonitor,并非标准的 Kubernetes 提供的资源类型,Prometheus Stack 也是一种 CRD 和 Operator 的实现。我们定义了一个类型为 ServiceMonitor 的自定义资源,Prometheus 的 Operator 会解析该资源类型的配置,然后动态生成 Prometheus 的配置,这就是 ServiceMonitor 的作用,它可以让我们用资源定义的方式去配置 Prometheus 需要监控谁,不用再处理成百上千行的 Prometheus 配置,也不需要每次更改 Prometheus 配置后重启 Prometheus 实例。

image-20240507143352370

通过上图定义的 selector 来匹配符合该 Label 的 Service,从而获取 endpoint 中的信息。

image-20240507143517599

3.2.2 自动发现机制

ServiceMonitor 使用以下机制来实现自动发现和配置:

  1. 标签选择器(Label Selectors): 在 ServiceMonitor 的规范中,您可以定义一个或多个标签选择器。这些选择器用于匹配具有特定标签的 Service 和 Endpoint 资源。当与这些选择器匹配的资源发生变化时,Prometheus Operator 将自动更新相关的监控配置。
  2. 目标生成(Target Generation): Prometheus Operator 根据 ServiceMonitor 和匹配的 Service、Endpoint 资源生成监控目标的配置。这包括目标的URL、标签和其他指标收集配置的详细信息。
  3. 动态配置更新: Prometheus Operator 会持续监控 ServiceMonitor 及其关联的资源。当 Service 或 Endpoint 的标签、地址、端口等信息发生变化时,Prometheus Operator 会自动检测到这些更改并更新相关的监控配置。
  4. 自动注销: 如果 Service 或 Endpoint 资源被删除,Prometheus Operator 会自动停止相关的监控目标和指标收集任务。这确保了无效的监控目标不会继续占用资源或导致错误。

3.2.3 监控流程图

img

对于不同的应用,可以采用不同的管控流程,具体说明如下:

  • 云原生应用:找到本身提供的 Metrics 接口,然后配置一个 Service 指向该应用的Pod,最后创建一个 ServiceMonitor 指定该应用的 Service 即可完成监控数据的采集。
  • 非云原生应用:需要找到合适该应用的 Exporter 的 Pod,之后 Exporter 链接至该应用并采集相关数据,然后配置一个 Service 指向该 Exporter 的 Pod,最后创建一个 ServiceMonitor 指定该 Exporter 的 Service 即可完成监控数据的采集。

3.2.4 ServiceMonitor配置解析

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor #定义了资源的类型为ServiceMonitor
metadata:
  labels: #为ServiceMonitor定义了标签
    k8s-app: elasticsearch-exporter
    release: es-exporter
  name: es-elasticsearch-exporter #定义了ServiceMonitor的名称
  namespace: monitoring #指定了资源所属的命名空间
spec:
  endpoints: #定义了监控的终端信息
  - honorLabels: true #则通过保留已抓取数据的标签值,并忽略冲突的服务器端标签来解决标签冲突。
    interval: 10s   #指定了获取指标的间隔时间为10秒
    path: /metrics  #指定了用于获取指标的路径
    port: https     #指定了指标收集的端口,可以名称或者端口号
    scheme: https   #指定了使用HTTPS协议进行指标收集
    tlsConfig:      #定义了TLS连接的配置信息
      insecureSkipVerify: true  #允许在连接时跳过证书验证
  jobLabel: app.kubernetes.io/name #指定了用于标识工作的标签
  namespaceSelector: #监控目标Service所在的命名空间
    matchNames: #匹配具有下列标签的Service资源用于监控
    - monitoring
  selector: #指定了要监控的Service资源的标签选择器
    matchLabels: #匹配具有下列标签的Service资源用于监控
      k8s-app: elasticsearch-service

4. 云原生应用监控etcd实战

4.1 测试Metrics

测试访问 Etcd Metrics 接口

curl -s --cert /etc/kubernetes/pki/etcd/server.crt --key /etc/kubernetes/pki/etcd/server.key https://10.0.0.104:2379/metrics -k | tail -2

image-20240507144710565

证书的位置查询如下

#kubeadm安装方式
grep -E "key-file|cert-file" /etc/kubernetes/manifests/etcd.yaml
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    
#二进制安装方式,找不到就通过systemctl status etcd查看指定的配置文件
grep -E "key-file|cert-file" /etc/etcd/etcd.config.yml

4.2 创建Service

首先需要配置 Etcd 的 Service 和 Endpoint,需要注意将下列IP改成自己的 Etcd 主机 IP,另外需要注意 port 的名称为 https-metrics,需要和后面的 ServiceMonitor 保持一致。

# vim etcd-svc.yaml
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: etcd-prom
  name: etcd-prom
  namespace: kube-system
subsets:
- addresses:
  - ip: 10.0.0.104
  - ip: 10.0.0.105
  - ip: 10.0.0.106
  ports:
  - name: https-metrics
    port: 2379 # etcd 端口
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: etcd-prom
  name: etcd-prom
  namespace: kube-system
spec:
  ports:
  - name: https-metrics
    port: 2379
    protocol: TCP
    targetPort: 2379
  type: ClusterIP

创建该资源并查看 Service 的 ClusterIP

[root@k8s-master01 ~]# kubectl create -f etcd-svc.yaml
endpoints/etcd-prom created

[root@k8s-master01 ~]# kubectl get svc -n kube-system etcd-prom 或 get -f etcd-svc.yaml
NAME        TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)    AGE
etcd-prom   ClusterIP   192.168.154.148   <none>        2379/TCP   14m

通过 ClusterIP 访问测试

[root@k8s-master01 kube-prometheus]# curl -s --cert /etc/kubernetes/pki/etcd/server.crt --key 

image-20240507145452258

4.3 创建Secret

创建 Etcd 证书(Prometheus需要etcd的证书)的 Secret(证书路径根据实际环境进行更改)

kubectl create secret generic etcd-ssl --from-file=/etc/kubernetes/pki/etcd/ca.crt --from-file=/etc/kubernetes/pki/etcd/server.crt --from-file=/etc/kubernetes/pki/etcd/server.key -n monitoring

image-20240507145750532

4.4 挂载 Secret 到 Prometheus 容器

将证书挂载至Prometheus容器(由于Prometheus是Operator部署的,所以只需要修改Prometheus资源即可)

kubectl edit -n monitoring prometheus

image-20240507150401887

保存退出后,Prometheus 的 Pod 会自动重启,重启完成后,查看证书是否挂载(任意一个 Prometheus 的 Pod 均可)

[root@k8s-master01 kube-prometheus]# kubectl exec -n monitoring prometheus-k8s-0 -c prometheus -- ls /etc/prometheus/secrets/etcd-ssl/
ca.crt
server.crt
server.key

4.5 创建监控 Etcd 的 ServiceMonitor

监控Etcd的ServiceMonitor配置如下:

#cat servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: etcd
  namespace: monitoring
  labels:
    app: etcd
spec:
  endpoints:
    - interval: 30s
      port: https-metrics # 这个 port 对应 Service.spec.ports.name
      scheme: https
      tlsConfig:
        caFile: /etc/prometheus/secrets/etcd-ssl/ca.crt #证书路径
        certFile: /etc/prometheus/secrets/etcd-ssl/server.crt
        keyFile: /etc/prometheus/secrets/etcd-ssl/server.key
        insecureSkipVerify: true # 关闭证书校验
  selector:
    matchLabels:
      app: etcd-prom #跟Service的labels保持一致
  namespaceSelector:
    matchNames:
    - kube-system

创建ServiceMonitor

kubectl create -f servicemonitor.yaml

image-20240507151429869

4.6 查看Prometheus并Grafana展示

访问 10.0.0.236:31648 进入 Prometheus 查看监控目标,状态已经up!

image-20240507152222642

尝试查询数据

image-20240507152336664

配置Grafana模板 也可以使用官方链接下载再导入

img

img

image-20240507152622008

Tips:数据流 etcd(云原生应用)--> Service --> ServiceMonitor --> Prometheus

5. 非云原生应用监控MySQL实战

5.1 部署MySQL

首先部署 MySQL 至 Kubernetes 集群中

kubectl create deploy mysql --image=10.0.0.138:5000/library/mysql:5.7.23 -n kube-public

设置密码

kubectl -n kube-public set env deploy/mysql MYSQL_ROOT_PASSWORD=mysql

查看 Pod 是否正常

image-20240507154440130

5.2 创建Service

创建 Service 暴露 MySQL

kubectl -n kube-public expose deploy mysql --port 3306

image-20240507155103778

查询该 Service 的 Labels,后续可以给 ServiceMonitor 进行匹配

kubectl get svc -n kube-public --show-labels

image-20240507154829367

检查 Service 是否可用,没问题!

image-20240507155411548

5.3 配置MySQL权限

语句如下:

CREATE USER 'exporter'@'%' IDENTIFIED BY 'exporter' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';

image-20240507155855843

5.4 配置MySQL Exporter采集MySQL监控数据

yaml 配置如下,创建MySQL Exporter的 Deployment 和 Service,这两者都需要在 Prometheus 所在的 Namespace 中。

注意 DATA_SOURCE_NAME 的配置,需要将 exporter:exporter@(mysql.kube-public:3306)/改成自己的实际配置。

格式如下:

USERNAME:PASSWORD@MYSQL_HOST_ADDRESS:MYSQL_PORT
USERNAME:PASSWORD@Service名称.NameSpace命名空间:MYSQL_PORT

# vim mysql-exporter.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-exporter
  namespace: monitoring
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: mysql-exporter
  template:
    metadata:
      labels:
        k8s-app: mysql-exporter
    spec:
      containers:
      - name: mysql-exporter
        image: registry.cn-beijing.aliyuncs.com/dotbalo/mysqld-exporter
        env:
        - name: DATA_SOURCE_NAME
          value: "exporter:exporter@(mysql.kube-public:3306)/"
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9104
---
apiVersion: v1
kind: Service
metadata:
  name: mysql-exporter
  namespace: monitoring
  labels:
    k8s-app: mysql-exporter
spec:
  type: ClusterIP
  selector:
    k8s-app: mysql-exporter  #选择deployment标签为mysql-exporter
  ports:
  - name: api
    port: 9104
    protocol: TCP

mysql-exporter镜像拉取地址:registry.cn-beijing.aliyuncs.com/dotbalo/mysqld-exporter

创建MySQL Exporter的Deployment和Service

kubectl create -f mysql-exporter.yaml

image-20240507162057290

通过该 Service 地址,检查是否能正常获取 Metrics 数据,没问题!

[root@k8s-master01 ~]# curl -s 192.168.88.216:9104/metrics | tail -1
promhttp_metric_handler_requests_total{code="503"} 0

5.5 创建监控MySQL的ServiceMonitor

监控MySQL的ServiceMonitor配置如下:

#cat mysql-sm.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: mysql-exporter
  namespace: monitoring
  labels:
    k8s-app: mysql-exporter
    namespace: monitoring
spec:
  endpoints:
  - port: api  #使用名称连接,以后这里改了也不用去修改ServiceMonitor的配置
    interval: 30s
    scheme: http
  selector:
    matchLabels:
      k8s-app: mysql-exporter  #跟mysql-exporter的Service的labels保持一致
  namespaceSelector:
    matchNames:
    - monitoring

创建ServiceMonitor

kubectl create -f mysql-sm.yaml

image-20240507162955798

image-20240507163225508

尝试查询数据

image-20240507163654221

配置Grafana模板

img

img

image-20240507163848253

Tips:数据流 MySQL(非云原生应用)--> 应用Service --> Exporter --> Export Service(做引流) --> ServiceMonitor --> Prometheus

6. Service Monitor监控问题排查

6.1 发现问题

安装Prometheus后,kube-controller-manager和kube-scheduler可能处于无法监控的状态,也就是找不到目标主机。在Alerts页面也能看到Controller Manager和Scheduler的告警。

img

6.2 排查问题流程

通过ServiceMonitor监控应用时,如果监控没有找到目标主机的排查步骤,排查步骤大致如下:

  1. 确认ServiceMonitor是否成功创建。
  2. 确认Prometheus是否生成了相关配置。
  3. 确认存在ServiceMonitor匹配的Service。
  4. 确认通过Service能够访问程序的Metrics接口。
  5. 确认Service的端口和Scheme、ServiceMonitor一致。

6.3 排查kube-controller-manager案例

Prometheus是有相关监控配置的,说明ServiceMonitor已经创建成功,可以通过以下命令查看:

kubectl get servicemonitor -n monitoring

image-20240507164754430

Prometheus中也有监控配置信息

fadf7295-c2e7-4744-a37b-51c6bd94ae0c

查看 kube-controller-manager 的 ServiceMonitor 配置,看到该 ServiceMonitor 匹配的是 kube-system 命名空间下具有 app.kubernetes.io/name=kubecontroller-manager 的标签。

kubectl get servicemonitor -n monitoring kube-controller-manager -oyaml

image-20240507165622822

接下来通过该标签查看是否有该Service,可以看到并没有此标签的Service,所以导致找不到需要监控的目标。

kubectl get svc -n kube-system -l app.kubernetes.io/name=kube-controller-manager

image-20240507165700013

手动创建该Service和Endpoint,配置中IP填写自己的Controller Manager节点IP

#vim kube-controller-manager-svc.yaml
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app.kubernetes.io/name: kube-controller-manager
  name: kube-controller-manager-prom
  namespace: kube-system
subsets:
- addresses:
  - ip: 10.0.0.104
  - ip: 10.0.0.105
  - ip: 10.0.0.106
  ports:
  - name: https-metrics
    port: 10257
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/name: kube-controller-manager
  name: kube-controller-manager-prom
  namespace: kube-system
spec:
  ports:
  - name: https-metrics
    port: 10257
    protocol: TCP
    targetPort: 10257
  sessionAffinity: None
  type: ClusterIP

创建Service

kubectl create -f controller-manager-service.yaml

image-20240507165746006

此时该Service可能是不通的,因为在集群搭建时,Controller Manager和Scheduler监听的可能是127.0.0.1,导致无法被外部访问,此时需要更改它的监听地址为0.0.0.0。

#二进制部署方式
sed -i "s#address=127.0.0.1#address=0.0.0.0#g" /usr/lib/systemd/system/kube-controller-manager.service
systemctl daemon-reload
systemctl restart kube-controller-manager

#kubeadm部署方式
#kubeadm安装方式配置文件在/etc/kubernetes/manifests目录

#三个Master节点都需要进行
sed -i "s#address=127.0.0.1#address=0.0.0.0#g" /etc/kubernetes/manifests/kube-controller-manager.yaml

Tips:如果集群原本监听的就是0.0.0.0,则无须更改!

由于 manifests 目录下是以静态 Pod 运行在集群中的,所以只要修改静态 Pod 目录下对应的 yaml 文件即可。等待一会后,对应服务会自动重启,所以不需要我们手动重启。(最后一列能看到是刚重新启动的)

image-20240507170416475

#如果需要重启
#kubeadm部署方式
systemctl restart kubelet

#二进制部署方法
systemctl daemon-reload
systemctl restart kube-controller-manager

查看Prometheus,监控正常,告警消除。

image-20240507170838098

image-20240507171300397

Grafana显示正常

image-20240508011941299

7. 黑盒监控与静态配置

7.1 白盒与黑盒监控概念

白盒监控:MySQL或者Etcd的监控都是监控应用本身,也就是程序内部的一些指标,这类监控关注的是原因,一般为出现问题的根本。

黑盒监控:关注的是现象,也就是正在发生的告警,比如某个网站突然慢了,或者打不开了。此类告警是站在用户的角度看到的东西,比较关注现象,表示正在发生的问题。

白盒监控可以通过Exporter采集数据,黑盒监控也可以通过Exporter采集数据,新版本的Prometheus Stack已经默认安装了Blackbox Exporter,可以用其采集某个域名、接口或者TCP连接的状态、是否可用等。

7.2 黑盒监控使用

新版 Prometheus Stack 已经默认安装了 Blackbox Exporter,可以通过以下命令查看:

kubectl get po -n monitoring -l app.kubernetes.io/name=blackbox-exporter

image-20240507175238057

如果集群中没有配置Blackbox Exporter,可以参考https://github.com/prometheus/blackbox_exporter进行安装。

同时也会创建一个 Service,可以通过该 Service 访问 Blackbox Exporter 。

[root@k8s-master01 kube-prometheus]# kubectl get svc -n monitoring -l app.kubernetes.io/name=blackbox-exporter
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)              AGE
blackbox-exporter   ClusterIP   192.168.229.51   <none>        9115/TCP,19115/TCP   4h12m

检测下 www.baidu.com(使用任何一个公网域名或者公司内的域名探测即可)网站的状态,可以通过如下命令进行检查

[root@k8s-master01 kube-prometheus]# curl -s "http://192.168.229.51:19115/probe?target=www.baidu.com&module=http_2xx" | tail -1
probe_success 1

Tips:probe 是接口地址,target 是检测的目标,module 是使用哪个模块进行探测。

7.3 Prometheus加载静态配置

7.3.1 为什么使用静态配置

之前配置监控目标时,用的都是ServiceMonitor,但是ServiceMonitor可能会有一些限制。比如,如果没有安装Prometheus Operator,可能就无法使用 ServiceMonitor,另外并不是所有的监控都能使用 ServiceMonitor 进行配置,或者使用 ServiceMonitor 配置显得过于烦琐。

黑盒监控使用 ServiceMonitor 就显得过于复杂,使用传统的配置方式,直接将 target 传递给 Blackbox Exporter 即可。虽然本篇使用的是 Operator 安装 Prometheus,但是它也是支持静态配置的,可以通过以下步骤开启 Prometheus 的静态配置。

7.3.2 创建Secret

首先创建一个空文件,然后通过该文件创建一个Secret,这个Secret即可作为Prometheus的静态配置:

[root@k8s-master01 blackbox]# touch prometheus-blackbox.yaml

[root@k8s-master01 blackbox]# kubectl create secret generic blackbox-configs --from-file=prometheus-blackbox.yaml -n monitoring

secret/blackbox-configs created
[root@k8s-master01 blackbox]# kubectl describe secrets -n monitoring blackbox-configs
Name:         blackbox-configs
Namespace:    monitoring
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
prometheus-blackbox.yaml:  0 bytes

7.3.3 Prometheus加载Secret

编辑下 Prometheus 配置,添加下面配置后保存退出,无需重启 Prometheus 的 Pod 即可生效。

kubectl edit prometheus -n monitoring k8s
#内容如下:
additionalScrapeConfigs: #是Prometheus的一个配置选项,用于指定额外的抓取配置。
  name: blackbox-configs #引用secret
  key: prometheus-blackbox.yaml #指定secret中的自定义的抓取配置文件
  optional: true  #即使额外配置文件不存在,Prometheus 也可以成功启动。false则是必须得加载成功才能启动Prometheus。

image-20240508010059303

7.3.4 黑盒监控配置热更新

prometheus-blackbox.yaml文件内编辑一些静态配置,此处用黑盒监控的配置进行演示:

- job_name: 'blackbox'
  metrics_path: /probe
  params: #使用哪个模块进行探测
    module: [http_2xx] # Look for a HTTP 200 response.
  static_configs:
    - targets: #探测的目标,根据实际情况进行更改
      - http://www.cnblogs.com # Target to probe with http.
      - https://www.baidu.com  # Target to probe with https.
      - https://www.qq.com     # Target to probe with https.
  relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - source_labels: [__param_target]
      target_label: instance
    - target_label: __address__
      replacement: blackbox-exporter:19115 # Blackbox Exporter的地址

之后通过热更新该Secret将黑盒监控配置加载到Prometheus

kubectl create secret generic blackbox-configs --from-file=prometheus-blackbox.yaml --dry-run=client -oyaml | kubectl replace -f - -n monitoring

Tips:“ - ” 表示从标准输入中读取配置。

7.3.5 查看Prometheus、Grafana

执行下列热更新完成后,稍等一分钟即可在Prometheus Web UI看到该配置,监控状态为UP。

image-20240508012329102

导入Grafana模板

928e5014-d751-4571-9e71-b0ddc3d189ee

5a91334b-e3b2-4ba0-b658-e2ea6c62b8ac

image-20240508012613330

8.Alertmanager告警实战

8.1 Alertmanager配置文件解析

Alertmanager 的配置示例

# global块配置下的配置选项在本配置文件内的所有配置项下可见
global:
  # 在Alertmanager内管理的每一条告警均有两种状态: "resolved"或者"firing"。在altermanager首次发送告警通知后, 该告警会一直处于firing状态,设置resolve_timeout可以指定处于firing状态的告警间隔多长时间会被设置为resolved状态, 在设置为resolved状态的告警后,altermanager不会再发送firing的告警通知。
  resolve_timeout: 1h

  # 邮件告警配置
  smtp_smarthost: 'smtp.exmail.qq.com:25'
  smtp_from: 'zzb@xxx.com'
  smtp_auth_username: 'zzb@xxx.com'
  smtp_auth_password: 'DKxxx'
  wechat_api_url: 'https://qyapi.weixin.qq.com/cgi-bin/'
  wechat_api_secret: 'JJ'
  wechat_api_corp_id: 'ww'

  # 告警通知模板
templates:
- '/etc/alertmanager/config/*.tmpl'

# route: 
#1. 根路由,该模块用于该根路由下的节点及子路由routes的定义. 子树节点如果不对相关配置进行配置,则默认会从父路由树继承该配置选项。
#2. 每一条告警都要进入route,即要求配置选项group_by的值能够匹配到每一条告警的至少一个labelkey(即通过POST请求向altermanager服务接口所发送告警的labels项所携带的<labelname>)
#3. 告警进入到route后,将会根据子路由routes节点中的配置项match_re或者match来确定能进入该子路由节点的告警(由在match_re或者match下配置的labelkey: labelvalue是否为告警labels的子集决定,是的话则会进入该子路由节点,否则不能接收进入该子路由节点)。
route:
  # 例如所有labelkey:labelvalue含cluster=A及altertname=LatencyHigh labelkey的告警都会被归入单一组中
  group_by: ['job', 'altername', 'cluster', 'service','severity']
  # 若一组新的告警产生,则会等group_wait后再发送通知,该功能主要用于当告警在很短时间内接连产生时,在group_wait内合并为单一的告警后再发送
  group_wait: 30s
  # 再次告警时间间隔
  group_interval: 5m
  # 如果一条告警通知已成功发送,且在间隔repeat_interval后,该告警仍然未被设置为resolved,则会再次发送该告警通知
  repeat_interval: 12h
  # 默认告警通知接收者,凡未被匹配进入各子路由节点的告警均被发送到此接收者
  receiver: 'wechat'
  # 上述route的配置会被传递给子路由节点,子路由节点进行重新配置才会被覆盖

  # 子路由树
  routes:
  # 该配置选项使用正则表达式来匹配告警的labels,以确定能否进入该子路由树
  # match_re和match均用于匹配labelkey为service,labelvalue分别为指定值的告警,被匹配到的告警会将通知发送到对应的receiver
  - match_re:
      service: ^(foo1|foo2|baz)$
    receiver: 'team-ops-mails'
    # 在带有service标签的告警同时有severity标签时,他可以有自己的子路由,同时具有severity != critical的告警则被发送给接收者team-ops-mails,对severity == critical的告警则被发送到对应的接收者即team-ops-pager
    routes:
    - match:
        severity: critical
      receiver: 'team-ops-pager'

  # 比如关于数据库服务的告警,如果子路由没有匹配到相应的owner标签,则都默认由team-DB接收
  - match:
      service: database
    receiver: 'team-DB'
    
# 抑制规则,当出现critical告警时,忽略warning
inhibit_rules:
- source_match:
    severity: 'critical'
  target_match:
    severity: 'warning'


# 收件人配置
receivers:
- name: 'team-ops-mails'
  email_configs:
  - to: 'zzb@xxx.com'
  
- name: 'wechat'
  wechat_configs:
  - send_resolved: true
    corp_id: 'ww'
    api_secret: 'JJ'
    to_tag: '1'
    agent_id: '1000002'
    api_url: 'https://qyapi.weixin.qq.com/cgi-bin/'
    message: '{{ template "wechat.default.message" . }}'
    
- name: 'team-ops-pager'
  email_configs:
  - to: 'team-ops-pager@example.org'
  pagerduty_configs:
  - service_key: <team-X-key>

- name: 'team-DB'
  email_configs:
  - to: 'team-DB@example.org'
#
#- name: 'team-Y-pager'
#  pagerduty_configs:
#  - service_key: <team-Y-key>
#
#- name: 'team-X-pager'
#  pagerduty_configs:
#  - service_key: <team-X-key>

8.2 Alertmanager告警路由规则

route配置

route:
  receiver: Default
  group_by:
  - namespace
  - job
  - alertname
  routes:
  - receiver: Watchdog
    match:
      alertname: Watchdog
  - receiver: Critical
    match:
      severity: critical
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h

receiver:告警的通知目标,需要和 receivers 配置中 name 进行匹配。需要注意的是route.routes 下也可以有 receiver 配置,优先级高于 route.receiver 配置的默认接收人,当告警没有匹配到子路由时,会使用 route.receiver 进行通知,比如上述配置中的Default。

group_by:分组配置,值类型为列表。比如配置成['job', 'severity'],代表告警信息包含job 和 severity 标签的会进行分组,且标签的 key 和 value 都相同才会被分到一组。

continue:决定匹配到第一个路由后,是否继续后续匹配。默认为 false,即匹配到第一个子节点后停止继续匹配

match:一对一匹配规则,比如 match 配置的为 job: mysql,那么具有 job=mysql 的告警会进入该路由。

match_re:和 match 类似,只不过是 match_re 是正则匹配。

group_wait:告警通知等待,值类型为字符串。若一组新的告警产生,则会等 group_wait后再发送通知,该功能主要用于当告警在很短时间内接连产生时,在 group_wait 内合并为单一的告警后再发送,防止告警过多,默认值 30s。

group_interval:同一组告警通知后,如果有新的告警添加到该组中,再次发送告警通知的时间,默认值为 5m。

repeat_interval:如果一条告警通知已成功发送,且在间隔 repeat_interval 后,该告警仍然未被设置为 resolved,则会再次发送该告警通知,默认值 4h。

8.3 Alertmanager邮件通知

修改/root/kube-prometheus/manifests/alertmanager-secret.yaml

[root@k8s-master01 manifests]# cat alertmanager-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  labels:
    app.kubernetes.io/component: alert-router
    app.kubernetes.io/instance: main
    app.kubernetes.io/name: alertmanager
    app.kubernetes.io/part-of: kube-prometheus
    app.kubernetes.io/version: 0.24.0
  name: alertmanager-main
  namespace: monitoring
stringData:
  alertmanager.yaml: |-
    "global":
      "resolve_timeout": "5m"
      smtp_from: "zzb_chen@163.com"
      smtp_smarthost: "smtp.163.com:465"
      smtp_hello: "163.com"
      smtp_auth_username: "zzb_chen@163.com"
      smtp_auth_password: "授权码"
      smtp_require_tls: false
    "inhibit_rules":
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = critical"
      "target_matchers":
      - "severity =~ warning|info"
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = warning"
      "target_matchers":
      - "severity = info"
    - "equal":
      - "namespace"
      "source_matchers":
      - "alertname = InfoInhibitor"
      "target_matchers":
      - "severity = info"
    "receivers":
    - "name": "Default"
      email_configs:
      - to: "szgetshell@163.com"
        send_resolved: true
    - "name": "Watchdog"
    - "name": "Critical"
    - "name": "null"
    "route":
      "group_by":
      - "namespace"
      - "job"
      - "alertname"
      "group_interval": "5m"
      "group_wait": "30s"
      "receiver": "Default"
      "repeat_interval": "12h"
      "routes":
      - "matchers":
        - "alertname = InfoInhibitor"
        "receiver": "null"
      - "matchers":
        - "severity = critical"
        "receiver": "Critical"
type: Opaque

主要上面文件中,修改了这些部分。

      #在global中配置发件人信息
      smtp_from: "k8s_prome_test@163.com"
      smtp_smarthost: "smtp.163.com:465"
      smtp_hello: "163.com"
      smtp_auth_username: "k8s_prome_test@163.com"
      smtp_auth_password: "授权密码"
      smtp_require_tls: false
      
      #告警分组
      group_by:
      - namespace
      - job
      - alertname
      
      #默认没有告警路由到的都发到这个Default上
    - "name": "Default"
      email_configs:
      - to: "k8s_prome_test@163.com" #收件人,可以配置多个,逗号隔开。
        send_resolved: true      #告警如果被解决是否发送解决通知。

Alertmanager访问

可以通过 Alertmanager 提供的 Web UI 查看分组信息,和 Prometheus 一致,将 Alertmanager 的 Service 更改为 NodePort:

kubectl edit svc -n monitoring alertmanager-main

查看监听的端口号

kubectl get svc -n monitoring alertmanager-main

将更改好的 Alertmanager 配置加载到 Alertmanager

kubectl replace -f alertmanager-secret.yaml

稍等几分钟即可在 Alertmanager 的 Web 界面看到更改的配置(Status)

image-20240508163021354

邮箱也收到了已有的告警信息

image-20240508163450533

8.4 自定义告警模板

8.4.1 简易告警模板

配置一个简易的告警模板,在之前配置邮件通知的基础上继续进行。

[root@k8s-master01 manifests]# cat alertmanager-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  labels:
    app.kubernetes.io/component: alert-router
    app.kubernetes.io/instance: main
    app.kubernetes.io/name: alertmanager
    app.kubernetes.io/part-of: kube-prometheus
    app.kubernetes.io/version: 0.24.0
  name: alertmanager-main
  namespace: monitoring
stringData:
  demo.tmpl: |-
    {{ define "test.html" }}
    {{- if gt (len .Alerts.Firing) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    ========= 告警通知 ==========<br>
    告警名称:{{ .Labels.alertname }}<br>
    告警级别:{{ .Labels.severity }}<br>
    告警机器:{{ .Labels.instance }} {{ .Labels.device }}<br>
    告警情况:{{ .Annotations.summary }}<br>
    详细信息:{{ .Annotations.description }}<br>
    告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    ========= END ==========<br>
    {{- end }}
    {{- end }}
    {{- if gt (len .Alerts.Resolved) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    ========= 告警恢复 ==========<br>
    告警名称:{{ .Labels.alertname }}<br>
    告警级别:{{ .Labels.severity }}<br>
    告警机器:{{ .Labels.instance }}<br>
    告警情况:{{ .Annotations.summary }}<br>
    详细信息:{{ .Annotations.description }}<br>
    告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    恢复时间:{{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    ========= END ==========<br>
    {{- end }}
    {{- end }}
    {{- end }}
  alertmanager.yaml: |-
    "global":
      "resolve_timeout": "5m"
      smtp_from: "k8s_prome_test@163.com"
      smtp_smarthost: "smtp.163.com:465"
      smtp_hello: "163.com"
      smtp_auth_username: "k8s_prome_test@163.com"
      smtp_auth_password: "授权码"
      smtp_require_tls: false
    templates:
    - '/etc/alertmanager/config/*.tmpl'
    "inhibit_rules":
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = critical"
      "target_matchers":
      - "severity =~ warning|info"
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = warning"
      "target_matchers":
      - "severity = info"
    - "equal":
      - "namespace"
      "source_matchers":
      - "alertname = InfoInhibitor"
      "target_matchers":
      - "severity = info"
    "receivers":
    - "name": "Default"
      email_configs:
      - to: "k8s_prome_test@163.com"
        html: '{{ template "test.html" . }}'
        send_resolved: true
    - "name": "Watchdog"
    - "name": "Critical"
    - "name": "null"
    "route":
      "group_by":
      - "namespace"
      - "job"
      - "alertname"
      "group_interval": "5m"
      "group_wait": "30s"
      "receiver": "Default"
      "repeat_interval": "1m"
      "routes":
      - "matchers":
        - "alertname = InfoInhibitor"
        "receiver": "null"
      - "matchers":
        - "severity = critical"
        "receiver": "Critical"
type: Opaque

主要上面文件中,修改了这些部分。

#添加告警模板,定义为test.html
demo.tmpl: |-
    {{ define "test.html" }}
    {{- if gt (len .Alerts.Firing) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    ========= 告警通知 ==========<br>
    告警名称:{{ .Labels.alertname }}<br>
    告警级别:{{ .Labels.severity }}<br>
    告警机器:{{ .Labels.instance }} {{ .Labels.device }}<br>
    告警情况:{{ .Annotations.summary }}<br>
    详细信息:{{ .Annotations.description }}<br>
    告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    ========= END ==========<br>
    {{- end }}
    {{- end }}
    {{- if gt (len .Alerts.Resolved) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    ========= 告警恢复 ==========<br>
    告警名称:{{ .Labels.alertname }}<br>
    告警级别:{{ .Labels.severity }}<br>
    告警机器:{{ .Labels.instance }}<br>
    告警情况:{{ .Annotations.summary }}<br>
    详细信息:{{ .Annotations.description }}<br>
    告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    恢复时间:{{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}<br>
    ========= END ==========<br>
    {{- end }}
    {{- end }}
    {{- end }}
    
    #加载模板
    templates:
    - '/etc/alertmanager/config/*.tmpl'
    
    #使用email告警模板,test.html与模板中定义的一样。
    html: '{{ template "test.html" . }}'

将更改好的 Alertmanager 配置加载到 Alertmanager

kubectl replace -f alertmanager-secret.yaml

效果展示

image-20240508173103004

8.4.2 美化告警模板

与上面一样,添加告警模板,定义为123.html,同时email_configs中也需要修改对应模板名称。

[root@k8s-master01 manifests]# cat alertmanager-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  labels:
    app.kubernetes.io/component: alert-router
    app.kubernetes.io/instance: main
    app.kubernetes.io/name: alertmanager
    app.kubernetes.io/part-of: kube-prometheus
    app.kubernetes.io/version: 0.24.0
  name: alertmanager-main
  namespace: monitoring
stringData:
  123.tmpl: |-
    {{ define "123.html" }}
    {{- if gt (len .Alerts.Firing) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    <!doctype html>
    <html lang="zh-CN">

      <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="shortcut icon" href="">
        <style>
          @font-face {
            font-family: Unicons;
            src: url(fonts/Unicons.woff2) format("woff2"), url(fonts/Unicons.woff) format("woff");
            font-weight: 400;
            font-style: normal;
            font-display: block
          }

          @font-face {
            font-family: Custom;
            src: url(fonts/Custom.woff2) format("woff2"), url(fonts/Custom.woff) format("woff");
          }

          .img-mask.mask-1 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob.svg) !important;
          }

          .img-mask.mask-2 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/hex.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/hex.svg) !important;
          }

          .img-mask.mask-3 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob2.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob2.svg) !important;
          }
        </style>
        <style type="text/css">
            * {
        box-sizing: border-box;
        }

            body {
                    margin: 0;
            }

            * {
                    box-sizing: border-box;
            }

            body {
                    margin-top: 0px;
                    margin-right: 0px;
                    margin-bottom: 0px;
                    margin-left: 0px;
                    font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Roboto, Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft Yahei", "Microsoft Jhenghei", sans-serif;
            }

            .logo path {
                    pointer-events: none;
                    fill: none;
                    stroke-linecap: round;
                    stroke-width: 7;
                    stroke: rgb(255, 255, 255);
            }

            .wechat-group img {
                    max-width: 220px;
                    height: auto;
                    border-top-left-radius: 8px;
                    border-top-right-radius: 8px;
                    border-bottom-right-radius: 8px;
                    border-bottom-left-radius: 8px;
                    margin-top: 0px;
                    margin-right: auto;
                    margin-bottom: 0px;
                    margin-left: auto;
                    width: 100%;
            }

            .welcome-img img {
                    width: 100%;
            }

            .hover-underline:hover {
                    text-decoration-line: underline !important;
                    text-decoration-thickness: initial !important;
                    text-decoration-style: initial !important;
                    text-decoration-color: initial !important;
            }

            .c3402 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    display: block;
                    font-size: 16px;
                    font-weight: 600;
                    line-height: 100%;
                    color: #ffffff;
                    text-decoration: none;
                    background-color: #7367f0;
                    text-align: center;
                    padding: 12px 16px 12px 16px;
                    float: none;
            }

            .c3394 {
                    mso-line-height-rule: exactly;
                    mso-padding-alt: 16px 24px;
                    border-radius: 4px;
                    background-color: #7367f0;
                    font-family: Montserrat, -apple-system, 'Segoe UI', sans-serif;
            }

            .c3476 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    height: 1px;
                    background-color: #eceff1;
                    line-height: 1px;
            }

            .c3468 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    padding-top: 32px;
                    padding-bottom: 32px;
            }

            .c3533 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    color: #7367f0;
                    text-decoration: none;
            }

            .c3217 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
            }

            .c3244 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    margin: 0;
                    margin-top: 24px;
                    margin-bottom: 24px;
            }

            .c3289 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    margin-bottom: 0;
                    font-size: 18px;
                    font-weight: 500;
            }

            .c3307 {
                    margin-bottom: 24px;
            }

            .c3444 {
                    width: 100%;
            }

            .c3516 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    margin: 0;
                    margin-bottom: 16px;
            }

            .c3572 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    margin: 0;
                    margin-bottom: 16px;
            }

            .c3164 {
                    mso-line-height-rule: exactly;
                    border-radius: 4px;
                    background-color: #ffffff;
                    padding: 48px;
                    text-align: left;
                    font-family: Montserrat, -apple-system, 'Segoe UI', sans-serif;
                    font-size: 16px;
                    line-height: 24px;
                    color: #626262;
            }

            .c3708 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    height: 20px;
            }

            .c4009 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    height: 16px;
            }

            .c3140 {
                    width: 100%;
            }

            .c3132 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
            }

            .c3045 {
                    width: 600px;
            }

            .c3037 {
                    mso-line-height-rule: exactly;
                    background-color: #eceff1;
                    font-family: Montserrat, -apple-system, 'Segoe UI', sans-serif;
            }

            .c3013 {
                    width: 100%;
                    font-family: Montserrat, -apple-system, 'Segoe UI', sans-serif;
            }

            .c2987 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
                    display: none;
            }

            .c3005 {
                    font-family: 'Montserrat', sans-serif;
                    mso-line-height-rule: exactly;
            }

            .row {
                    display: table;
                    padding-top: 10px;
                    padding-right: 10px;
                    padding-bottom: 10px;
                    padding-left: 10px;
                    width: 100%;
            }

            .cell {
                    width: 8%;
                    display: table-cell;
                    height: 75px;
            }

            @media (max-width: 768px) {
                    .cell {
                            width: 100%;
                            display: block;
                    }
            }

            @media (max-width: 600px) {
                    .sm-w-full {
                            width: 100% !important;
                    }

                    .sm-px-24 {
                            padding-left: 24px !important;
                            padding-right: 24px !important;
                    }
            }
            </style>
      </head>

      <body>

        <body>
          <meta charset="utf-8" />
          <meta name="x-apple-disable-message-reformatting" />
          <meta http-equiv="x-ua-compatible" content="ie=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <meta name="format-detection" content="telephone=no, date=no, address=no, email=no" />
          <title>欢迎使用HTMLPAGE模板 👋</title>
          <div role="article" aria-roledescription="email" aria-label="欢迎使用HTMLPAGE模板 👋" lang="en" id="i7m0o" class="c3005">
            <table cellpadding="0" cellspacing="0" role="presentation" id="ivm4l" class="c3013">
              <tbody>
                <tr>
                  <td align="center" id="iateu" class="c3037">
                    <table cellpadding="0" cellspacing="0" role="presentation" id="i4cwv" class="sm-w-full c3045">
                      <tbody>
                        <tr></tr>
                        <tr>
                          <td align="center" id="itbcd" class="sm-px-24 c3132">
                            <table cellpadding="0" cellspacing="0" role="presentation" id="i53eh" class="c3140">
                              <tbody>
                                <tr>
                                  <td id="i2lri" class="sm-px-24 c3164">
                                    <!-- <p style="font-family: 'Montserrat', sans-serif; mso-line-height-rule: exactly; margin-top: 12px; font-size: 20px; font-weight: 700; color: #263238;">陈先生!</p> -->
                                    <div class="row">
                                      <div class="cell"><a href="" class="c3402">告警通知</a>
                                        <div class="row"></div>
                                      </div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                    </div><a href="https://htmlpage.cn" class="c3217"></a>
                                    <p id="i19pj" class="c3244"></p>
                                    <p id="ilj5l" class="c3289"></p>
                                    <ul id="if9r7" class="c3307">
                                      <li>告警名称:{{ .Labels.alertname }}</li>
                                      <li>告警级别:{{ .Labels.severity }}</li>
                                      <li>告警机器:{{ .Labels.instance }} {{ .Labels.device }}</li>
                                      <li>告警信息:{{ .Annotations.summary }}</li>
                                      <li>告警详情:{{ .Annotations.description }}</li>
                                      <li>告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}</li>
                                    </ul>
                                    <table cellpadding="0" cellspacing="0" role="presentation">
                                      <tbody>
                                        <tr>
                                          <td id="iz67p" class="c3394"></td>
                                        </tr>
                                      </tbody>
                                    </table>
                                    <table cellpadding="0" cellspacing="0" role="presentation" id="i7dvy" class="c3444">
                                      <tbody>
                                        <tr>
                                          <td id="i0ol4" class="c3468">
                                            <div id="ifrkk" class="c3476">‌</div>
                                          </td>
                                        </tr>
                                      </tbody>
                                    </table>
                                    <p id="i9omt" class="c3516">处理过程中如遇到问题,请与我们联系
                                      <a href="mailto:k8s_prome_test@163.com.com" class="hover-underline c3533">回复邮件</a>。
                                    </p>
                                    <p id="inqma" class="c3572">                                                                               运维团队</p>
                                  </td>
                                </tr>
                                <tr>
                                  <td id="ir37w" class="c3708"></td>
                                </tr>
                                <tr></tr>
                                <tr>
                                  <td id="ixrj3" class="c4009"></td>
                                </tr>
                              </tbody>
                            </table>
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </body>
      </body>
      <html>
    {{- end }}
    {{- end }}
    {{- if gt (len .Alerts.Resolved) 0 -}}
    {{- range $index, $alert := .Alerts -}}
    <!doctype html>
    <html lang="zh-CN">

      <head>
        <meta charset="utf-8">
        <title></title>
        <link rel="shortcut icon" href="">
        <style>
          @font-face {
            font-family: Unicons;
            src: url(fonts/Unicons.woff2) format("woff2"), url(fonts/Unicons.woff) format("woff");
            font-weight: 400;
            font-style: normal;
            font-display: block
          }

          @font-face {
            font-family: Custom;
            src: url(fonts/Custom.woff2) format("woff2"), url(fonts/Custom.woff) format("woff");
          }

          .img-mask.mask-1 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob.svg) !important;
          }

          .img-mask.mask-2 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/hex.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/hex.svg) !important;
          }

          .img-mask.mask-3 img {
            -webkit-mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob2.svg) !important;
            mask-image: url(https://gitee.com/ggbaoo/k8s-img/raw/master/blob2.svg) !important;
          }
        </style>
        <link rel="stylesheet" href="./css/user-style.css">
      </head>

      <body>

        <body>
          <meta charset="utf-8" />
          <meta name="x-apple-disable-message-reformatting" />
          <meta http-equiv="x-ua-compatible" content="ie=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <meta name="format-detection" content="telephone=no, date=no, address=no, email=no" />
          <title>欢迎使用HTMLPAGE模板 👋</title>
          <div role="article" aria-roledescription="email" aria-label="欢迎使用HTMLPAGE模板 👋" lang="en" id="i7m0o" class="c3005">
            <table cellpadding="0" cellspacing="0" role="presentation" id="ivm4l" class="c3013">
              <tbody>
                <tr>
                  <td align="center" id="iateu" class="c3037">
                    <table cellpadding="0" cellspacing="0" role="presentation" id="i4cwv" class="sm-w-full c3045">
                      <tbody>
                        <tr></tr>
                        <tr>
                          <td align="center" id="itbcd" class="sm-px-24 c3132">
                            <table cellpadding="0" cellspacing="0" role="presentation" id="i53eh" class="c3140">
                              <tbody>
                                <tr>
                                  <td id="i2lri" class="sm-px-24 c3164">
                                    <!-- <p style="font-family: 'Montserrat', sans-serif; mso-line-height-rule: exactly; margin-top: 12px; font-size: 20px; font-weight: 700; color: #263238;">陈先生!</p> -->
                                    <div class="row">
                                      <div class="cell"><a href="" class="c3402">告警恢复</a>
                                        <div class="row"></div>
                                      </div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                      <div class="cell"></div>
                                    </div><a href="https://htmlpage.cn" class="c3217"></a>
                                    <p id="i19pj" class="c3244"></p>
                                    <p id="ilj5l" class="c3289"></p>
                                    <ul id="if9r7" class="c3307">
                                      <li>告警名称:{{ .Labels.alertname }}</li>
                                      <li>告警级别:{{ .Labels.severity }}</li>
                                      <li>告警机器:{{ .Labels.instance }}</li>
                                      <li>告警信息:{{ .Annotations.summary }}</li>
                                      <li>告警详情:{{ .Annotations.description }}</li>
                                      <li>告警时间:{{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}</li>
                                      <li>恢复时间:{{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}</li>
                                    </ul>
                                    <table cellpadding="0" cellspacing="0" role="presentation">
                                      <tbody>
                                        <tr>
                                          <td id="iz67p" class="c3394"></td>
                                        </tr>
                                      </tbody>
                                    </table>
                                    <table cellpadding="0" cellspacing="0" role="presentation" id="i7dvy" class="c3444">
                                      <tbody>
                                        <tr>
                                          <td id="i0ol4" class="c3468">
                                            <div id="ifrkk" class="c3476">‌</div>
                                          </td>
                                        </tr>
                                      </tbody>
                                    </table>
                                    <p id="i9omt" class="c3516">处理过程中如遇到问题,请与我们联系
                                      <a href="mailto:k8s_prome_test@163.com" class="hover-underline c3533">回复邮件</a>。
                                    </p>
                                    <p id="inqma" class="c3572">                                                                               运维团队</p>
                                  </td>
                                </tr>
                                <tr>
                                  <td id="ir37w" class="c3708"></td>
                                </tr>
                                <tr></tr>
                                <tr>
                                  <td id="ixrj3" class="c4009"></td>
                                </tr>
                              </tbody>
                            </table>
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </body>
      </body>
      <html>
    {{- end }}
    {{- end }}
    {{- end }}
  alertmanager.yaml: |-
    "global":
      "resolve_timeout": "5m"
      smtp_from: "k8s_prome_test@163.com"
      smtp_smarthost: "smtp.163.com:465"
      smtp_hello: "163.com"
      smtp_auth_username: "k8s_prome_test@163.com"
      smtp_auth_password: "授权码"
      smtp_require_tls: false
    templates:
    - '/etc/alertmanager/config/*.tmpl'
    "inhibit_rules":
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = critical"
      "target_matchers":
      - "severity =~ warning|info"
    - "equal":
      - "namespace"
      - "alertname"
      "source_matchers":
      - "severity = warning"
      "target_matchers":
      - "severity = info"
    - "equal":
      - "namespace"
      "source_matchers":
      - "alertname = InfoInhibitor"
      "target_matchers":
      - "severity = info"
    "receivers":
    - "name": "Default"
      email_configs:
      - to: "szgetshell@163.com"
        html: '{{ template "123.html" . }}'
        send_resolved: true
    - "name": "Watchdog"
    - "name": "Critical"
    - "name": "null"
    "route":
      "group_by":
      - "namespace"
      - "job"
      - "alertname"
      "group_interval": "5m"
      "group_wait": "30s"
      "receiver": "Default"
      "repeat_interval": "1m"
      "routes":
      - "matchers":
        - "alertname = InfoInhibitor"
        "receiver": "null"
      - "matchers":
        - "severity = critical"
        "receiver": "Critical"
type: Opaque

将更改好的 Alertmanager 配置加载到 Alertmanager

kubectl replace -f alertmanager-secret.yaml

8.5 域名访问延迟告警

假设需要对域名访问延迟进行监控,访问延迟大于 1 秒进行告警,此时可以创建一个PrometheusRule 如下:

[root@k8s-master01 manifests]# cat blackbox-rule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    app.kubernetes.io/component: exporter
    app.kubernetes.io/name: blackbox-exporter
  name: blackbox-exporter-rules
  namespace: monitoring
spec:
  groups:
  - name: blackbox.rules
    rules:
    - alert: DomainAccessDelayExceeds1s
      annotations:
        description: 域名:{{ $labels.instance }} 探测延迟大于 0.5 秒,当前延迟为:{{ $value }}
        summary: 域名探测,访问延迟超过 0.5 
      expr: sum(probe_http_duration_seconds{job=~"blackbox"}) by (instance) > 0.5
      for: 1m
      labels:
        type: blackbox
        level: high

配置项详解:

  • name:rule名称
  • alert:告警策略的名称
  • annotations:告警注释信息,一般写为告警信息
  • expr:告警表达式
  • for:评估等待时间,告警持续多久才会发送告警数据
  • labels:告警的标签,用于告警的路由

创建并查看该 PrometheusRule

#创建
kubectl create -f blackbox-rule.yaml

#查看
[root@k8s-master01 manifests]# kubectl get prometheusrule -n monitoring
NAME                              AGE
alertmanager-main-rules           12d
blackbox-exporter-rules           31s
grafana-rules                     12d
kube-prometheus-rules             12d
kube-state-metrics-rules          12d
kubernetes-monitoring-rules       12d
node-exporter-rules               12d
prometheus-k8s-prometheus-rules   12d
prometheus-operator-rules         12d

在prometheus上查看规则是否生效,没问题!

image-20240508173814841

修改alertmanager-secret.yaml,配置告警路由,新添加一个组,并且定义好收件人和告警信息模板。

    "receivers":
    - "name": "devops"
      email_configs:
      - to: "2428581996@qq.com"
        html: '{{ template "test.html" . }}'
        send_resolved: true

在route.routes下进行配置匹配路由,该例子是匹配labels为type: blackbox的告警,然后发给devops组,而devops组中已经定义好要发送的人的邮箱。

    "route":
      "routes":
      - "matchers":
        - "type = blackbox"
        "receiver": "devops"

将更改好的 Alertmanager 配置加载到 Alertmanager

kubectl replace -f alertmanager-secret.yaml

稍等片刻就能收到

image-20240508180733184

注:本篇学习笔记内容参考杜宽的《云原生Kubernetes全栈架构师》,视频、资料文档等,大家可以多多支持!还有YinJayChen语雀k8s训练营“我为什么这么菜”知乎博主等资料文档,感谢无私奉献!

posted @   zzbao  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
点击右上角即可分享
微信分享提示