集群监控(9)

集群监控(9)

1. 在 Kubernets 中手动安装 Prometheus

从今天开始我们就和大家一起来学习 Kubernetes 中监控系统的搭建,我们知道监控是保证系统运行必不可少的功能,特别是对于 Kubernetes 这种比较庞大的系统来说,监控报警更是不可或缺,我们需要时刻了解系统的各种运行指标,也需要时刻了解我们的 Pod 的各种指标,更需要在出现问题的时候有报警信息通知到我们。

在早期的版本中 Kubernetes 提供了 heapster、influxDB、grafana 的组合来监控系统,所以我们可以在 Dashboard 中看到 heapster 提供的一些图表信息,在后续的版本中会陆续移除掉 heapster,现在更加流行的监控工具是 prometheus,prometheus 是 Google 内部监控报警系统的开源版本,是 Google SRE 思想在其内部不断完善的产物,它的存在是为了更快和高效的发现问题,快速的接入速度,简单灵活的配置都很好的解决了这一切,而且是已经毕业的 CNCF 项目。

这里推荐一本书了解 Goolge 运维的秘密:《SRE: Google运维解密》

1.1 简介

Prometheus 最初是 SoundCloud 构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目。

1.2 特征

Prometheus 相比于其他传统监控工具主要有以下几个特点:

  • 具有由 metric 名称和键/值对标识的时间序列数据的多维数据模型
  • 有一个灵活的查询语言
  • 不依赖分布式存储,只和本地磁盘有关
  • 通过 HTTP 的服务拉取时间序列数据
  • 也支持推送的方式来添加时间序列数据
  • 还支持通过服务发现或静态配置发现目标
  • 多种图形和仪表板支持

1.3 组件

Prometheus 由多个组件组成,但是其中许多组件是可选的:

  • Prometheus Server:用于抓取指标、存储时间序列数据
  • exporter:暴露指标让任务来抓
  • pushgateway:push 的方式将指标数据推送到该网关
  • alertmanager:处理报警的报警组件
  • adhoc:用于数据查询

大多数 Prometheus 组件都是用 Go 编写的,因此很容易构建和部署为静态的二进制文件。

1.4 架构

下图是 Prometheus 官方提供的架构及其一些相关的生态系统组件:

图片.png-342.9kB

整体流程比较简单,Prometheus 直接接收或者通过中间的 Pushgateway 网关被动获取指标数据,在本地存储所有的获取的指标数据,并对这些数据进行一些规则整理,用来生成一些聚合数据或者报警信息,Grafana 或者其他工具用来可视化这些数据。

1.5 安装

由于 Prometheus 是 Golang 编写的程序,所以要安装的话也非常简单,只需要将二进制文件下载下来直接执行即可,前往地址:https://prometheus.io/download 下载我们对应的版本即可。

Prometheus 是通过一个 YAML 配置文件来进行启动的,如果我们使用二进制的方式来启动的话,可以使用下面的命令:

$ ./prometheus --config.file=prometheus.yml

其中 prometheus.yml 文件的基本配置如下:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules"
  # - "second.rules"

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']

上面这个配置文件中包含了3个模块:global、rule_files 和 scrape_configs。

其中 global 模块控制 Prometheus Server 的全局配置:

  • scrape_interval:表示 prometheus 抓取指标数据的频率,默认是15s,我们可以覆盖这个值
  • evaluation_interval:用来控制评估规则的频率,prometheus 使用规则产生新的时间序列数据或者产生警报

rule_files 模块制定了规则所在的位置,prometheus 可以根据这个配置加载规则,用于生成新的时间序列数据或者报警信息,当前我们没有配置任何规则。

scrape_configs 用于控制 prometheus 监控哪些资源。由于 prometheus 通过 HTTP 的方式来暴露的它本身的监控数据,prometheus 也能够监控本身的健康情况。在默认的配置里有一个单独的 job,叫做prometheus,它采集 prometheus 服务本身的时间序列数据。这个 job 包含了一个单独的、静态配置的目标:监听 localhost 上的9090端口。prometheus 默认会通过目标的/metrics路径采集 metrics。所以,默认的 job 通过 URL:http://localhost:9090/metrics采集 metrics。收集到的时间序列包含 prometheus 服务本身的状态和性能。如果我们还有其他的资源需要监控的话,直接配置在该模块下面就可以了。

由于我们这里是要跑在 Kubernetes 系统中,所以我们直接用 Docker 镜像的方式运行即可。

为了方便管理,我们将所有的资源对象都安装在kube-ops的 namespace 下面,没有的话需要提前安装。

[root@node01 ~]# kubectl create ns kube-ops
namespace "kube-ops" created

[root@node01 ~]# kubectl get ns
NAME          STATUS    AGE
default       Active    114d
kube-ops      Active    4s
kube-public   Active    114d
kube-system   Active    114d

为了能够方便的管理配置文件,我们这里将 prometheus.yml 文件用 ConfigMap 的形式进行管理:(prometheus-cm.yaml)

[root@node01 ~]# vim prometheus-cm.yaml
[root@node01 ~]# cat prometheus-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-ops
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
    scrape_configs:
    - job_name: 'prometheus'
      static_configs:
      - targets: ['localhost:9090']

我们这里暂时只配置了对 prometheus 的监控,然后创建该资源对象:

[root@node01 ~]# kubectl create -f prometheus-cm.yaml 
configmap "prometheus-config" created

[root@node01 ~]# kubectl get configmap -n kube-ops
NAME                DATA      AGE
prometheus-config   1         48s

配置文件创建完成了,以后如果我们有新的资源需要被监控,我们只需要将上面的 ConfigMap 对象更新即可。现在我们来创建 prometheus 的 Pod 资源:(prometheus-deploy.yaml)

[root@node01 ~]# vim prometheus-deploy.yaml
[root@node01 ~]# cat prometheus-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: prometheus
  namespace: kube-ops
  labels:
    app: prometheus
spec:
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      serviceAccountName: prometheus
      containers:
      - image: prom/prometheus:v2.4.3
        name: prometheus
        command:
        - "/bin/prometheus"
        args:
        - "--config.file=/etc/prometheus/prometheus.yml"
        - "--storage.tsdb.path=/prometheus"
        - "--storage.tsdb.retention=24h"
        - "--web.enable-admin-api"  # 控制对admin HTTP API的访问,其中包括删除时间序列等功能
        - "--web.enable-lifecycle"  # 支持热更新,直接执行localhost:9090/-/reload立即生效
        ports:
        - containerPort: 9090
          protocol: TCP
          name: http
        volumeMounts:
        - mountPath: "/prometheus"
          subPath: prometheus
          name: data
        - mountPath: "/etc/prometheus"
          name: config-volume
        resources:
          requests:
            cpu: 100m
            memory: 512Mi
          limits:
            cpu: 100m
            memory: 512Mi
      securityContext:
        runAsUser: 0
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: prometheus
      - configMap:
          name: prometheus-config
        name: config-volume

我们在启动程序的时候,除了指定了 prometheus.yml 文件之外,还通过参数storage.tsdb.path指定了 TSDB 数据的存储路径、通过storage.tsdb.retention设置了保留多长时间的数据,还有下面的web.enable-admin-api参数可以用来开启对 admin api 的访问权限,参数web.enable-lifecycle非常重要,用来开启支持热更新的,有了这个参数之后,prometheus.yml 配置文件只要更新了,通过执行localhost:9090/-/reload就会立即生效,所以一定要加上这个参数。

我们这里将 prometheus.yml 文件对应的 ConfigMap 对象通过 volume 的形式挂载进了 Pod,这样 ConfigMap 更新后,对应的 Pod 里面的文件也会热更新的,然后我们再执行上面的 reload 请求,Prometheus 配置就生效了,除此之外,为了将时间序列数据进行持久化,我们将数据目录和一个 pvc 对象进行了绑定,所以我们需要提前创建好这个 pvc 对象:(prometheus-volume.yaml)

[root@node01 ~]# vim prometheus-volume.yaml
[root@node01 ~]# cat prometheus-volume.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: prometheus
spec:
  capacity:
    storage: 10Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: 192.168.200.11
    path: /data/k8s

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: prometheus
  namespace: kube-ops
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

我们这里简单的通过 NFS 作为存储后端创建一个 pv、pvc 对象(在所有非master节点执行):

[root@node01 ~]# yum -y install nfs-utils

[root@node01 ~]# mkdir -p /data/k8s

[root@node01 ~]# vim /etc/exports
[root@node01 ~]# cat /etc/exports
/data/k8s *(rw,sync,no_root_squash)

[root@node01 ~]# systemctl start nfs-server.service
[root@node01 ~]# systemctl enable nfs-server.service
[root@node01 ~]# kubectl create -f prometheus-volume.yaml
persistentvolume "prometheus" created
persistentvolumeclaim "prometheus" created

[root@node01 ~]# kubectl get pv
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                 STORAGECLASS   REASON    AGE
prometheus   10Gi       RWO            Recycle          Bound     kube-ops/prometheus                            14s

除了上面的注意事项外,我们这里还需要配置 rbac 认证,因为我们需要在 prometheus 中去访问 Kubernetes 的相关信息,所以我们这里管理了一个名为 prometheus 的 serviceAccount 对象:(prometheus-rbac.yaml)

[root@node01 ~]# vim prometheus-rbac.yaml
[root@node01 ~]# cat prometheus-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: kube-ops
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - services
  - endpoints
  - pods
  - nodes/proxy
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - configmaps
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: prometheus
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: prometheus
subjects:
- kind: ServiceAccount
  name: prometheus
  namespace: kube-ops

由于我们要获取的资源信息,在每一个 namespace 下面都有可能存在,所以我们这里使用的是 ClusterRole 的资源对象,值得一提的是我们这里的权限规则声明中有一个nonResourceURLs的属性,是用来对非资源型 metrics 进行操作的权限声明,这个在以前我们很少遇到过,然后直接创建上面的资源对象即可:

[root@node01 ~]# kubectl create -f prometheus-rbac.yaml
serviceaccount "prometheus" created
clusterrole.rbac.authorization.k8s.io "prometheus" created
clusterrolebinding.rbac.authorization.k8s.io "prometheus" created

还有一个要注意的地方是我们这里必须要添加一个securityContext的属性,将其中的runAsUser设置为0,这是因为现在的 prometheus 运行过程中使用的用户是 nobody,否则会出现下面的permission denied之类的权限错误:

level=error ts=2018-10-22T14:34:58.632016274Z caller=main.go:617 err="opening storage failed: lock DB directory: open /data/lock: permission denied"

现在我们就可以添加 promethues 的资源对象了:

[root@node01 ~]# kubectl create -f prometheus-deploy.yaml
deployment.apps "prometheus" created
[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS    RESTARTS   AGE
prometheus-7cc7bd5995-2x82n   1/1       Running   0          2m


[root@node01 ~]# kubectl logs -f prometheus-7cc7bd5995-2x82n -n kube-ops
...
level=info ts=2021-11-07T08:38:09.70777218Z caller=main.go:523 msg="Server is ready to receive web requests."

Pod 创建成功后,为了能够在外部访问到 prometheus 的 webui 服务,我们还需要创建一个 Service 对象:(prometheus-svc.yaml)

[root@node01 ~]# vim prometheus-svc.yaml
[root@node01 ~]# cat prometheus-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: prometheus
  namespace: kube-ops
  labels:
    app: prometheus
spec:
  selector:
    app: prometheus
  type: NodePort
  ports:
    - name: web
      port: 9090
      targetPort: http

为了方便测试,我们这里创建一个NodePort类型的服务,当然我们可以创建一个Ingress对象,通过域名来进行访问:

[root@node01 ~]# kubectl create -f prometheus-svc.yaml
service "prometheus" created

[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
prometheus   NodePort   10.107.45.148   <none>        9090:31891/TCP   7s

然后我们就可以通过http://192.168.200.11:31891访问 prometheus 的 webui 服务了。

图片.png-75.7kB

为了数据的一致性,prometheus 所有的数据都是使用的 UTC 时间,所以我们默认打开的 dashboard 中有这样一个警告,我们需要在查询的时候指定我们当前的时间才可以。然后我们可以查看当前监控系统中的一些监控目标:

图片.png-105.8kB

由于我们现在还没有配置任何的报警信息,所以 Alerts 菜单下面现在没有任何数据,隔一会儿,我们可以去 Graph 菜单下面查看我们抓取的 prometheus 本身的一些监控数据了,其中- insert metrics at cursor -下面就是我们搜集到的一些监控数据指标:

图片.png-160.1kB

比如我们这里就选择scrape_duration_seconds这个指标,然后点击Execute,如果这个时候没有查询到任何数据,我们可以切换到Graph这个 tab 下面重新选择下时间,选择到当前的时间点,重新执行,就可以看到类似于下面的图表数据了:

图片.png-165.7kB

除了简单的直接使用采集到的一些监控指标数据之外,这个时候也可以使用强大的 PromQL 工具,PromQL其实就是 prometheus 便于数据聚合展示开发的一套 ad hoc 查询语言的,你想要查什么找对应函数取你的数据好了。

2. 监控 Kubernetes 集群应用

上一节我们和大家介绍了Prometheus的数据指标是通过一个公开的 HTTP(S) 数据接口获取到的,我们不需要单独安装监控的 agent,只需要暴露一个 metrics 接口,Prometheus 就会定期去拉取数据;对于一些普通的 HTTP 服务,我们完全可以直接重用这个服务,添加一个/metrics接口暴露给 Prometheus;而且获取到的指标数据格式是非常易懂的,不需要太高的学习成本。

现在很多服务从一开始就内置了一个/metrics接口,比如 Kubernetes 的各个组件、istio 服务网格都直接提供了数据指标接口。有一些服务即使没有原生集成该接口,也完全可以使用一些 exporter 来获取到指标数据,比如 mysqld_exporter、node_exporter,这些 exporter 就有点类似于传统监控服务中的 agent,作为一直服务存在,用来收集目标服务的指标数据然后直接暴露给 Prometheus。

2.1 普通应用监控

前面我们已经和大家学习了 ingress 的使用,我们采用的是Traefik作为我们的 ingress-controller,是我们 Kubernetes 集群内部服务和外部用户之间的桥梁。Traefik 本身内置了一个/metrics的接口,但是需要我们在参数中配置开启:

[root@node01 ~]# vim traefik.toml 
[root@node01 ~]# tail -4 traefik.toml 
[metrics]
  [metrics.prometheus]
    entryPoint = "traefik"
    buckets = [0.1, 0.3, 1.2, 5.0]

之前的版本中是通过--web--web.metrics.prometheus两个参数进行开启的,要注意查看对应版本的文档。

我们需要在traefik.toml的配置文件中添加上上面的配置信息,然后更新 ConfigMap 和 Pod 资源对象即可,Traefik Pod 运行后,我们可以看到我们的服务 IP:

[root@node01 ~]# kubectl delete configmap traefik-conf -n kube-system
configmap "traefik-conf" deleted

[root@node01 ~]# kubectl create configmap traefik-conf --from-file=traefik.toml -n kube-system
configmap "traefik-conf" created


[root@node01 ~]# kubectl delete -f traefik.yaml
deployment.apps "traefik-ingress-controller" deleted
service "traefik-ingress-service" deleted

[root@node01 ~]# kubectl create -f traefik.yaml 
deployment.apps "traefik-ingress-controller" created
service "traefik-ingress-service" created
[root@node01 ~]# kubectl get svc -n kube-system
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                       AGE
...
traefik-ingress-service   NodePort    10.97.23.241    <none>        80:30407/TCP,8080:32729/TCP   22s

然后我们可以使用curl检查是否开启了 Prometheus 指标数据接口,或者通过 NodePort 访问也可以:

[root@node01 ~]# curl 10.97.23.241:8080/metrics
# HELP go_gc_duration_seconds A summary of the GC invocation durations.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 9.467e-06
go_gc_duration_seconds{quantile="0.25"} 2.2346e-05
go_gc_duration_seconds{quantile="0.5"} 0.000877692
go_gc_duration_seconds{quantile="0.75"} 0.00105826
go_gc_duration_seconds{quantile="1"} 0.004242956
go_gc_duration_seconds_sum 0.007341889
go_gc_duration_seconds_count 7
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 73
......

从这里可以看到 Traefik 的监控数据接口已经开启成功了,然后我们就可以将这个/metrics接口配置到prometheus.yml中去了,直接加到默认的prometheus这个 job 下面:(prometheus-cm.yaml)

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -4 prometheus-cm.yaml 
    - job_name: 'traefik'
      static_configs:
        - targets: ['traefik-ingress-service.kube-system.svc.cluster.local:8080']

当然,我们这里只是一个很简单的配置,scrape_configs 下面可以支持很多参数,例如:

  • basic_auth 和 bearer_token:比如我们提供的/metrics接口需要 basic 认证的时候,通过传统的用户名/密码或者在请求的header中添加对应的 token 都可以支持
  • kubernetes_sd_configs 或 consul_sd_configs:可以用来自动发现一些应用的监控数据

由于我们这里 Traefik 对应的 servicename 是traefik-ingress-service,并且在 kube-system 这个 namespace 下面,所以我们这里的targets的路径配置则需要使用FQDN的形式:traefik-ingress-service.kube-system.svc.cluster.local,当然如果你的 Traefik 和 Prometheus 都部署在同一个命名空间的话,则直接填 servicename:serviceport即可。然后我们重新更新这个 ConfigMap 资源对象:

[root@node01 ~]# kubectl apply -f prometheus-cm.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
configmap "prometheus-config" configured

现在 Prometheus 的配置文件内容已经更改了,隔一会儿被挂载到 Pod 中的 prometheus.yml 文件也会更新,由于我们之前的 Prometheus 启动参数中添加了--web.enable-lifecycle参数,所以现在我们只需要执行一个 reload 命令即可让配置生效:

[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
prometheus   NodePort   10.107.45.148   <none>        9090:31891/TCP   22m

[root@node01 ~]# curl -X POST "http://10.107.45.148:9090/-/reload"

由于 ConfigMap 通过 Volume 的形式挂载到 Pod 中去的热更新需要一定的间隔时间才会生效,所以需要稍微等一小会儿。

reload 这个 url 是一个 POST 请求,所以这里我们通过 service 的 CLUSTER-IP:PORT 就可以访问到这个重载的接口,这个时候我们再去看 Prometheus 的 Dashboard 中查看采集的目标数据:

图片.png-135.3kB

可以看到我们刚刚添加的traefik这个任务已经出现了,然后同样的我们可以切换到 Graph 下面去,我们可以找到一些 Traefik 的指标数据,至于这些指标数据代表什么意义,一般情况下,我们可以去查看对应的/metrics接口,里面一般情况下都会有对应的注释。

到这里我们就在 Prometheus 上配置了第一个 Kubernetes 应用。

2.2 使用 exporter 监控应用

上面我们也说过有一些应用可能没有自带/metrics接口供 Prometheus 使用,在这种情况下,我们就需要利用 exporter 服务来为 Prometheus 提供指标数据了。Prometheus 官方为许多应用就提供了对应的 exporter 应用,也有许多第三方的实现,我们可以前往官方网站进行查看:exporters

比如我们这里通过一个redis-exporter的服务来监控 redis 服务,对于这类应用,我们一般会以 sidecar 的形式和主应用部署在同一个 Pod 中,比如我们这里来部署一个 redis 应用,并用 redis-exporter 的方式来采集监控数据供 Prometheus 使用,如下资源清单文件:(prome-redis.yaml)

[root@node01 ~]# vim prometheus-redis.yaml
[root@node01 ~]# cat prometheus-redis.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: kube-ops
spec:
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9121"
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:4
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 6379
      - name: redis-exporter
        image: oliver006/redis_exporter:latest
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        ports:
        - containerPort: 9121
---
kind: Service
apiVersion: v1
metadata:
  name: redis
  namespace: kube-ops
spec:
  selector:
    app: redis
  ports:
  - name: redis
    port: 6379
    targetPort: 6379
  - name: prom
    port: 9121
    targetPort: 9121

可以看到上面我们在 redis 这个 Pod 中包含了两个容器,一个就是 redis 本身的主应用,另外一个容器就是 redis_exporter。现在直接创建上面的应用:

[root@node01 ~]# kubectl create -f prometheus-redis.yaml 
deployment.apps "redis" created

创建完成后,我们可以看到 redis 的 Pod 里面包含有两个容器:

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS    RESTARTS   AGE
prometheus-7cc7bd5995-2x82n   1/1       Running   0          30m
redis-546f6c4c9c-bp2mf        2/2       Running   0          42s
 
[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
prometheus   NodePort    10.107.45.148   <none>        9090:31891/TCP      25m
redis        ClusterIP   10.102.66.115   <none>        6379/TCP,9121/TCP   51s

我们可以通过 9121 端口来校验是否能够采集到数据:

[root@node01 ~]# curl 10.102.66.115:9121/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 0
go_gc_duration_seconds{quantile="0.25"} 0
go_gc_duration_seconds{quantile="0.5"} 0
go_gc_duration_seconds{quantile="0.75"} 0
go_gc_duration_seconds{quantile="1"} 0
go_gc_duration_seconds_sum 0
go_gc_duration_seconds_count 0
# HELP go_goroutines Number of goroutines that currently exist.
# TYPE go_goroutines gauge
go_goroutines 10
...

同样的,现在我们只需要更新 prometheus-cm.yaml 的配置文件:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -3 prometheus-cm.yaml 
    - job_name: 'redis'
      static_configs:
        - targets: ['redis:9121']

由于我们这里的 redis 服务和 Prometheus 处于同一个 namespace,所以我们直接使用 servicename 即可。

配置文件更新后,重新加载:

[root@node01 ~]# kubectl apply -f prometheus-cm.yaml 
configmap "prometheus-config" configured

# 隔一会儿执行reload操作
[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
prometheus   NodePort    10.107.45.148   <none>        9090:31891/TCP      28m
redis        ClusterIP   10.102.66.115   <none>        6379/TCP,9121/TCP   3m

[root@node01 ~]# curl -X POST "http://10.107.45.148:9090/-/reload"

这个时候我们再去看 Prometheus 的 Dashboard 中查看采集的目标数据:

图片.png-149.7kB

可以看到配置的 redis 这个 job 已经生效了。切换到 Graph 下面可以看到很多关于 redis 的指标数据:

图片.png-136.8kB

我们选择任意一个指标,比如redis_exporter_scrapes_total,然后点击执行就可以看到对应的数据图表了:

图片.png-107kB

注意,如果时间有问题,我们需要手动在 Graph 下面调整下时间

除了监控群集中部署的服务之外,我们下节课再和大家学习怎样监视 Kubernetes 群集本身。

3. 监控 Kubernetes 集群节点

上节课我们和大家学习了怎样用 Promethues 来监控 Kubernetes 集群中的应用,但是对于 Kubernetes 集群本身的监控也是非常重要的,我们需要时时刻刻了解集群的运行状态。

对于集群的监控一般我们需要考虑以下几个方面:

  • Kubernetes 节点的监控:比如节点的 cpu、load、disk、memory 等指标
  • 内部系统组件的状态:比如 kube-scheduler、kube-controller-manager、kubedns/coredns 等组件的详细运行状态
  • 编排级的 metrics:比如 Deployment 的状态、资源请求、调度和 API 延迟等数据指标

3.1 监控方案

Kubernetes 集群的监控方案目前主要有以下几种方案:

  • Heapster:Heapster 是一个集群范围的监控和数据聚合工具,以 Pod 的形式运行在集群中。图片.png-155.1kB
    除了 Kubelet/cAdvisor 之外,我们还可以向 Heapster 添加其他指标源数据,比如 kube-state-metrics。
    需要注意的是 Heapster 已经被废弃了,后续版本中会使用 metrics-server 代替。
  • cAdvisor:cAdvisorGoogle开源的容器资源监控和性能分析工具,它是专门为容器而生,本身也支持 Docker 容器,在 Kubernetes 中,我们不需要单独去安装,cAdvisor 作为 kubelet 内置的一部分程序可以直接使用。
  • Kube-state-metrics:kube-state-metrics通过监听 API Server 生成有关资源对象的状态指标,比如 Deployment、Node、Pod,需要注意的是 kube-state-metrics 只是简单提供一个 metrics 数据,并不会存储这些指标数据,所以我们可以使用 Prometheus 来抓取这些数据然后存储。
  • metrics-server:metrics-server 也是一个集群范围内的资源数据聚合工具,是 Heapster 的替代品,同样的,metrics-server 也只是显示数据,并不提供数据存储服务。

不过 kube-state-metrics 和 metrics-server 之间还是有很大不同的,二者的主要区别如下:

  • kube-state-metrics 主要关注的是业务相关的一些元数据,比如 Deployment、Pod、副本状态等
  • metrics-server 主要关注的是资源度量 API的实现,比如 CPU、文件描述符、内存、请求延时等指标。

3.2 监控集群节点

现在我们就来开始我们集群的监控工作,首先来监控我们集群的节点,要监控节点其实我们已经有很多非常成熟的方案了,比如 Nagios、zabbix,甚至我们自己来收集数据也可以,我们这里通过 Prometheus 来采集节点的监控指标数据,可以通过node_exporter来获取,顾名思义,node_exporter 就是抓取用于采集服务器节点的各种运行指标,目前 node_exporter 支持几乎所有常见的监控点,比如 conntrack,cpu,diskstats,filesystem,loadavg,meminfo,netstat等,详细的监控点列表可以参考其Github repo

我们可以通过 DaemonSet 控制器来部署该服务,这样每一个节点都会自动运行一个这样的 Pod,如果我们从集群中删除或者添加节点后,也会进行自动扩展。

在部署 node-exporter 的时候有一些细节需要注意,如下资源清单文件:(prometheus-node-exporter.yaml)

[root@node01 ~]# vim prometheus-node-exporter.yaml
[root@node01 ~]# cat prometheus-node-exporter.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-exporter
  namespace: kube-ops
  labels:
    name: node-exporter
spec:
  selector:
    matchLabels:
      name: node-exporter
  template:
    metadata:
      labels:
        name: node-exporter
    spec:
      hostPID: true
      hostIPC: true
      hostNetwork: true
      containers:
      - name: node-exporter
        image: prom/node-exporter:v0.16.0
        ports:
        - containerPort: 9100
        resources:
          requests:
            cpu: 0.15
        securityContext:
          privileged: true
        args:
        - --path.procfs
        - /host/proc
        - --path.sysfs
        - /host/sys
        - --collector.filesystem.ignored-mount-points
        - '"^/(sys|proc|dev|host|etc)($|/)"'
        volumeMounts:
        - name: dev
          mountPath: /host/dev
        - name: proc
          mountPath: /host/proc
        - name: sys
          mountPath: /host/sys
        - name: rootfs
          mountPath: /rootfs
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"
      volumes:
        - name: proc
          hostPath:
            path: /proc
        - name: dev
          hostPath:
            path: /dev
        - name: sys
          hostPath:
            path: /sys
        - name: rootfs
          hostPath:
            path: /

由于我们要获取到的数据是主机的监控指标数据,而我们的 node-exporter 是运行在容器中的,所以我们在 Pod 中需要配置一些 Pod 的安全策略,这里我们就添加了hostPID: truehostIPC: truehostNetwork: true3个策略,用来使用主机的 PID namespace、IPC namespace 以及主机网络,这些 namespace 就是用于容器隔离的关键技术,要注意这里的 namespace 和集群中的 namespace 是两个完全不相同的概念。

另外我们还将主机的/dev/proc/sys这些目录挂载到容器中,这些因为我们采集的很多节点数据都是通过这些文件夹下面的文件来获取到的,比如我们在使用top命令可以查看当前cpu使用情况,数据就来源于文件/proc/stat,使用free命令可以查看当前内存使用情况,其数据来源是来自/proc/meminfo文件。

另外由于我们集群使用的是 kubeadm 搭建的,所以如果希望 master 节点也一起被监控,则需要添加相应的容忍,对于污点和容忍还不是很熟悉的同学可以在前面的章节中回顾下。

然后直接创建上面的资源对象即可:

[root@node01 ~]# kubectl create -f prometheus-node-exporter.yaml
daemonset.apps "node-exporter" created

[root@node01 ~]# kubectl get pods -n kube-ops -o wide
NAME                          READY     STATUS    RESTARTS   AGE       IP               NODE
node-exporter-7t86q           1/1       Running   0          12s       192.168.200.11   node01
node-exporter-wddns           1/1       Running   0          12s       192.168.200.12   node02
node-exporter-z5vb8           1/1       Running   0          12s       192.168.200.10   master
...

部署完成后,我们可以看到在3个节点上都运行了一个 Pod,有的同学可能会说我们这里不需要创建一个 Service 吗?我们应该怎样去获取/metrics数据呢?我们上面是不是指定了hostNetwork=true,所以在每个节点上就会绑定一个端口 9100,我们可以通过这个端口去获取到监控指标数据:

[root@node01 ~]# curl 127.0.0.1:9100/metrics
...
node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/rootfs/var/lib/kubelet/pods/e3804f98-3a47-11ec-8fc3-000c2927597c/volumes/kubernetes.io~secret/default-token-n5qwz"} 1
node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/rootfs/var/lib/kubelet/pods/f4fa5265-29cd-11ec-8fc3-000c2927597c/volumes/kubernetes.io~secret/prometheus-token-bb5hd"} 1
node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/sys/fs/cgroup"} 0
node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/var/run/secrets/kubernetes.io/serviceaccount"} 0
...

当然如果你觉得上面的手动安装方式比较麻烦,我们也可以使用 Helm 的方式来安装:

$ helm install --name node-exporter stable/prometheus-node-exporter --namespace kube-ops

3.3 服务发现

由于我们这里3个节点上面都运行了 node-exporter 程序,如果我们通过一个 Service 来将数据收集到一起用静态配置的方式配置到 Prometheus 去中,就只会显示一条数据,我们得自己在指标数据中去过滤每个节点的数据,那么有没有一种方式可以让 Prometheus 去自动发现我们节点的 node-exporter 程序,并且按节点进行分组呢?是有的,就是我们前面和大家提到过的服务发现

在 Kubernetes 下,Promethues 通过与 Kubernetes API 集成,目前主要支持5中服务发现模式,分别是:Node、Service、Pod、Endpoints、Ingress。

我们通过 kubectl 命令可以很方便的获取到当前集群中的所有节点信息:

[root@node01 ~]# kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
master    Ready     master    145d      v1.10.0
node01    Ready     <none>    145d      v1.10.0
node02    Ready     <none>    42d       v1.10.0

但是要让 Prometheus 也能够获取到当前集群中的所有节点信息的话,我们就需要利用 Node 的服务发现模式,同样的,在 prometheus-cm.yaml 文件中配置如下的 job 任务即可:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -3 prometheus-cm.yaml 
    - job_name: 'kubernetes-nodes'
      kubernetes_sd_configs:
    - role: node

通过指定kubernetes_sd_configs的模式为node,Prometheus 就会自动从 Kubernetes 中发现所有的 node 节点并作为当前 job 监控的目标实例,发现的节点/metrics接口是默认的 kubelet 的 HTTP 接口。

prometheus 的 ConfigMap 更新完成后,同样的我们执行 reload 操作,让配置生效:

[root@node01 ~]# kubectl apply -f prometheus-cm.yaml 
configmap "prometheus-config" configured

# 隔一会儿执行reload操作
[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
prometheus   NodePort    10.107.45.148   <none>        9090:31891/TCP      28m
...

[root@node01 ~]# curl -X POST "http://10.107.45.148:9090/-/reload"

配置生效后,我们再去 prometheus 的 dashboard 中查看 Targets 是否能够正常抓取数据,访问任意节点:31891 http://192.168.200.11:31891

图片.png-244.1kB

我们可以看到上面的kubernetes-nodes这个 job 任务已经自动发现了我们3个 node 节点,但是在获取数据的时候失败了,出现了类似于下面的错误信息:

Get http://192.168.200.10:10250/metrics: net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x15\x03\x01\x00\x02\x02"

这个是因为 prometheus 去发现 Node 模式的服务的时候,访问的端口默认是10250,而现在该端口下面已经没有了/metrics指标数据了,现在 kubelet 只读的数据接口统一通过10255端口进行暴露了,所以我们应该去替换掉这里的端口,但是我们是要替换成10255端口吗?不是的,因为我们是要去配置上面通过node-exporter抓取到的节点指标数据,而我们上面是不是指定了hostNetwork=true,所以在每个节点上就会绑定一个端口9100,所以我们应该将这里的10250替换成9100,但是应该怎样替换呢?

这里我们就需要使用到 Prometheus 提供的relabel_configs中的replace能力了,relabel 可以在 Prometheus 采集数据之前,通过Target 实例的 Metadata 信息,动态重新写入 Label 的值。除此之外,我们还能根据 Target 实例的 Metadata 信息选择是否采集或者忽略该 Target 实例。比如我们这里就可以去匹配__address__这个 Label 标签,然后替换掉其中的端口:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -9 prometheus-cm.yaml 
    - job_name: 'kubernetes-nodes'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace

这里就是一个正则表达式,去匹配__address__,然后将 host 部分保留下来,port 替换成了9100,现在我们重新更新配置文件,执行 reload 操作,然后再去看 Prometheus 的 Dashboard 的 Targets 路径下面 kubernetes-nodes 这个 job 任务是否正常了:

图片.png-175.3kB

我们可以看到现在已经正常了,但是还有一个问题就是我们采集的指标数据 Label 标签就只有一个节点的 hostname,这对于我们在进行监控分组分类查询的时候带来了很多不方便的地方,要是我们能够将集群中 Node 节点的 Label 标签也能获取到就很好了。

这里我们可以通过labelmap这个属性来将 Kubernetes 的 Label 标签添加为 Prometheus 的指标标签:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -11 prometheus-cm.yaml 
    - job_name: 'kubernetes-nodes'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

添加了一个 action 为labelmap,正则表达式是__meta_kubernetes_node_label_(.+)的配置,这里的意思就是表达式中匹配都的数据也添加到指标数据的 Label 标签中去。

对于 kubernetes_sd_configs 下面可用的标签如下: 可用元标签:

  • __meta_kubernetes_node_name:节点对象的名称
  • _meta_kubernetes_node_label:节点对象中的每个标签
  • _meta_kubernetes_node_annotation:来自节点对象的每个注释
  • _meta_kubernetes_node_address:每个节点地址类型的第一个地址(如果存在) *

关于 kubernets_sd_configs 更多信息可以查看官方文档:kubernetes_sd_config

另外由于 kubelet 也自带了一些监控指标数据,就上面我们提到的10255端口,所以我们这里也把 kubelet 的监控任务也一并配置上:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -23 prometheus-cm.yaml 
    - job_name: 'kubernetes-nodes'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:9100'
        target_label: __address__
        action: replace
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

    - job_name: 'kubernetes-kubelet'
      kubernetes_sd_configs:
      - role: node
      relabel_configs:
      - source_labels: [__address__]
        regex: '(.*):10250'
        replacement: '${1}:10255'
        target_label: __address__
        action: replace
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)

特别需要注意的是 Kubernetes 1.11+ 版本以后,kubelet 就移除了 10255 端口, metrics 接口又回到了 10250 端口中,所以这里不需要替换端口,但是需要使用 https 的协议。所以如果你使用的是 Kubernetes 1.11+ 版本的化,需要讲上面的 kubernetes-kubelet 任务替换成下面的配置:

- job_name: 'kubernetes-kubelet'
  kubernetes_sd_configs:
  - role: node
  scheme: https
  tls_config:
    ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    insecure_skip_verify: true
  bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
  relabel_configs:
  - action: labelmap
    regex: __meta_kubernetes_node_label_(.+)

现在我们再去更新下配置文件,执行 reload 操作,让配置生效,然后访问 Prometheus 的 Dashboard 查看 Targets 路径:

图片.png-202.9kB

现在可以看到我们上面添加的kubernetes-kubeletkubernetes-nodes这两个 job 任务都已经配置成功了,而且二者的 Labels 标签都和集群的 node 节点标签保持一致了。

现在我们就可以切换到 Graph 路径下面查看采集的一些指标数据了,比如查询 node_load1 指标:

图片.png-278.3kB

我们可以看到将3个 node 节点对应的 node_load1 指标数据都查询出来了,同样的,我们还可以使用 PromQL 语句来进行更复杂的一些聚合查询操作,还可以根据我们的 Labels 标签对指标数据进行聚合,比如我们这里只查询 node02 节点的数据,可以使用表达式node_load1{instance="node02"}来进行查询:

图片.png-168.6kB

到这里我们就把 Kubernetes 集群节点的使用 Prometheus 监控起来了。

4. 监控 Kubernetes 常用资源对象

上节课我们学习了怎样用 Prometheus 来自动发现 Kubernetes 集群的节点,用到了 Prometheus 针对 Kubernetes 的服务发现机制kubernetes_sd_configs的使用,这节课我们来和大家一起了解下怎样在 Prometheus 中来自动监控 Kubernetes 中的一些常用资源对象。

前面我们和大家介绍过了在 Prometheus 中用静态的方式来监控 Kubernetes 集群中的普通应用,但是如果针对集群中众多的资源对象都采用静态的方式来进行配置的话显然是不现实的,所以同样我们需要使用到 Prometheus 提供的其他类型的服务发现机制。

4.1 容器监控

说到容器监控我们自然会想到cAdvisor,我们前面也说过cAdvisor已经内置在了 kubelet 组件之中,所以我们不需要单独去安装,cAdvisor的数据路径为/api/v1/nodes/<node>/proxy/metrics,同样我们这里使用 node 的服务发现模式,因为每一个节点下面都有 kubelet,自然都有cAdvisor采集到的数据指标,配置如下:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -16 prometheus-cm.yaml 
    - job_name: 'kubernetes-cadvisor'
      kubernetes_sd_configs:
      - role: node
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - action: labelmap
        regex: __meta_kubernetes_node_label_(.+)
      - target_label: __address__
        replacement: kubernetes.default.svc:443
      - source_labels: [__meta_kubernetes_node_name]
        regex: (.+)
        target_label: __metrics_path__
        replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor

上面的配置和我们之前配置 node-exporter 的时候几乎是一样的,区别是我们这里使用了 https 的协议,另外需要注意的是配置了 ca.cart 和 token 这两个文件,这两个文件是 Pod 启动后自动注入进来的,通过这两个文件我们可以在 Pod 中访问 apiserver,比如我们这里的__address__不在是 nodeip 了,而是 kubernetes 在集群中的服务地址,然后加上__metrics_path__的访问路径:/api/v1/nodes/${1}/proxy/metrics/cadvisor,现在同样更新下配置,然后查看 Targets 路径:

图片.png-127kB

然后我们可以切换到 Graph 路径下面查询容器相关数据,比如我们这里来查询集群中所有 Pod 的 CPU 使用情况,这里用的数据指标是 container_cpu_usage_seconds_total,然后去除一些无效的数据,查询1分钟内的数据,由于查询到的数据都是容器相关的,最好要安装 Pod 来进行聚合,对应的promQL语句如下:

sum by (pod_name)(rate(container_cpu_usage_seconds_total{image!="", pod_name!=""}[1m] ))

图片.png-302.5kB

我们可以看到上面的结果就是集群中的所有 Pod 在1分钟之内的 CPU 使用情况的曲线图,当然还有很多数据可以获取到,我们后面在需要的时候再和大家介绍。

4.2 apiserver 监控

apiserver 作为 Kubernetes 最核心的组件,当然他的监控也是非常有必要的,对于 apiserver 的监控我们可以直接通过 kubernetes 的 Service 来获取:

[root@node01 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP          145d

上面这个 Service 就是我们集群的 apiserver 在集群内部的 Service 地址,要自动发现 Service 类型的服务,我们就需要用到 role 为 Endpoints 的 kubernetes_sd_configs,我们可以在 ConfigMap 对象中添加上一个 Endpoints 类型的服务的监控任务:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -3 prometheus-cm.yaml 
     - job_name: 'kubernetes-apiservers'
       kubernetes_sd_configs:
       - role: endpoints

上面这个任务是定义的一个类型为endpoints的kubernetes_sd_configs,添加到 Prometheus 的 ConfigMap 的配置文件中,然后更新配置:

[root@node01 ~]# kubectl apply -f prometheus-cm.yaml 
configmap "prometheus-config" configured

# 隔一会儿执行reload操作
[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
prometheus   NodePort    10.107.45.148   <none>        9090:31891/TCP      5h
...

[root@node01 ~]# curl -X POST "http://10.107.45.148:9090/-/reload"

更新完成后,我们再去查看 Prometheus 的 Dashboard 的 target 页面: prometheus

图片.png-385.6kB

我们可以看到 kubernetes-apiservers 下面出现了很多实例,这是因为这里我们使用的是 Endpoints 类型的服务发现,所以 Prometheus 把所有的 Endpoints 服务都抓取过来了,同样的,上面我们需要的服务名为kubernetes这个 apiserver 的服务也在这个列表之中,那么我们应该怎样来过滤出这个服务来呢?还记得上节课的relabel_configs吗?没错,同样我们需要使用这个配置,只是我们这里不是使用replace这个动作了,而是keep,就是只把符合我们要求的给保留下来,哪些才是符合我们要求的呢?我们可以把鼠标放置在任意一个 target 上,可以查看到Before relabeling里面所有的元数据,比如我们要过滤的服务是 default 这个 namespace 下面,服务名为 kubernetes 的元数据,所以这里我们就可以根据对应的__meta_kubernetes_namespace__meta_kubernetes_service_name这两个元数据来 relabel

图片.png-203.3kB

另外由于 kubernetes 这个服务对应的端口是443,需要使用 https 协议,所以这里我们需要使用 https 的协议,对应的就需要将对应的 ca 证书配置上,如下:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -11 prometheus-cm.yaml 
    - job_name: 'kubernetes-apiservers'
      kubernetes_sd_configs:
      - role: endpoints
      scheme: https
      tls_config:
        ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
      relabel_configs:
      - source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
        action: keep
        regex: default;kubernetes;https

现在重新更新配置文件、重新加载 Prometheus,切换到 Prometheus 的 Targets 路径下查看:

图片.png-62.7kB

现在可以看到 kubernetes-apiserver 这个任务下面只有 apiserver 这一个实例了,证明我们的 relabel 是成功的,现在我们切换到 graph 路径下面查看下采集到数据,比如查询 apiserver 的总的请求数:

sum(rate(apiserver_request_count[1m]))

这里我们使用到了 promql 里面的 rate 和 sum函数,表示的意思是 apiserver 在1分钟内总的请求数。

图片.png-118.2kB

这样我们就完成了对 Kubernetes APIServer 的监控。

另外如果我们要来监控其他系统组件,比如 kube-controller-manager、kube-scheduler 的话应该怎么做呢?由于 apiserver 服务 namespace 在 default 使用默认的 Service kubernetes,而其余组件服务在 kube-system 这个 namespace 下面,如果我们想要来监控这些组件的话,需要手动创建单独的 Service,其中 kube-sheduler 的指标数据端口为 10251,kube-controller-manager 对应的端口为 10252,大家可以尝试下自己来配置下这几个系统组件。

4.3 Service 的监控

上面的 apiserver 实际上是一种特殊的 Service,现在我们同样来配置一个任务用来专门发现普通类型的 Service:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -28 prometheus-cm.yaml 
    - job_name: 'kubernetes-service-endpoints'
      kubernetes_sd_configs:
      - role: endpoints
      relabel_configs:
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
        action: replace
        target_label: __scheme__
        regex: (https?)
      - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
      - action: labelmap
        regex: __meta_kubernetes_service_label_(.+)
      - source_labels: [__meta_kubernetes_namespace]
        action: replace
        target_label: kubernetes_namespace
      - source_labels: [__meta_kubernetes_service_name]
        action: replace
        target_label: kubernetes_name

注意我们这里在relabel_configs区域做了大量的配置,特别是第一个保留__meta_kubernetes_service_annotation_prometheus_io_scrapetrue的才保留下来,这就是说要想自动发现集群中的 Service,就需要我们在 Service 的annotation区域添加prometheus.io/scrape=true的声明.

现在我们在之前创建的 redis 这个 Service 中添加上prometheus.io/scrape=true这个 annotation:(prometheus-redis.yaml)

kind: Service
apiVersion: v1
metadata:
  name: redis
  namespace: kube-ops
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "9121"
spec:
  selector:
    app: redis
  ports:
  - name: redis
    port: 6379
    targetPort: 6379
  - name: prom
    port: 9121
    targetPort: 9121

由于 redis 服务的 metrics 接口在9121这个 redis-exporter 服务上面,所以我们还需要添加一个prometheus.io/port=9121这样的annotations,然后更新这个 Service:

[root@node01 ~]# kubectl apply -f prometheus-redis.yaml 
deployment.apps "redis" unchanged
service "redis" configured

更新完成后,去 Prometheus 查看 Targets 路径,可以看到 redis 服务自动出现在了kubernetes-service-endpoints这个任务下面:

图片.png-39.6kB

这样以后我们有了新的服务,服务本身提供了/metrics接口,我们就完全不需要用静态的方式去配置了,到这里我们就可以将之前配置的 redis 的静态配置去掉了。

大家可以尝试去将之前配置的 traefik 服务用动态发现的方式重新配置到上面的 service-endpoints 中。

同样的,大家可以自己去尝试下去配置下自动发现 Pod、ingress 这些资源对象。

4.4 kube-state-metrics

上面我们配置了自动发现 Service(Pod也是一样的)的监控,但是这些监控数据都是应用内部的监控,需要应用本身提供一个/metrics接口,或者对应的 exporter 来暴露对应的指标数据,但是在 Kubernetes 集群上 Pod、DaemonSet、Deployment、Job、CronJob 等各种资源对象的状态也需要监控,这也反映了使用这些资源部署的应用的状态。但通过查看前面从集群中拉取的指标(这些指标主要来自 apiserver 和 kubelet 中集成的 cAdvisor),并没有具体的各种资源对象的状态指标。对于 Prometheus 来说,当然是需要引入新的 exporter 来暴露这些指标,Kubernetes 提供了一个kube-state-metrics就是我们需要的。

kube-state-metrics 已经给出了在 Kubernetes 部署的 manifest 定义文件,我们直接将代码 Clone 到集群中(能用 kubectl 工具操作就行):

$ git clone https://github.com/kubernetes/kube-state-metrics.git
$ cd kube-state-metrics/kubernetes
$ kubectl create -f .
clusterrolebinding.rbac.authorization.k8s.io "kube-state-metrics" created
clusterrole.rbac.authorization.k8s.io "kube-state-metrics" created
deployment.apps "kube-state-metrics" created
rolebinding.rbac.authorization.k8s.io "kube-state-metrics" created
role.rbac.authorization.k8s.io "kube-state-metrics-resizer" created
serviceaccount "kube-state-metrics" created
service "kube-state-metrics" created

将 kube-state-metrics 部署到 Kubernetes 上之后,就会发现 Kubernetes 集群中的 Prometheus 会在kubernetes-service-endpoints 这个 job 下自动服务发现 kube-state-metrics,并开始拉取 metrics,这是因为部署 kube-state-metrics 的 manifest 定义文件 kube-state-metrics-service.yaml 对 Service 的定义包含prometheus.io/scrape: 'true'这样的一个annotation,因此 kube-state-metrics 的 endpoint 可以被 Prometheus 自动服务发现。

关于 kube-state-metrics 暴露的所有监控指标可以参考 kube-state-metrics 的文档kube-state-metrics Documentation

到这里我们就完成了 Kubernetes 集群上部署应用的监控。

5. Grafana 的安装使用

前面的课程中我们使用 Prometheus 采集了 Kubernetes 集群中的一些监控数据指标,我们也尝试使用promQL语句查询出了一些数据,并且在 Prometheus 的 Dashboard 中进行了展示,但是明显可以感觉到 Prometheus 的图表功能相对较弱,所以一般情况下我们会一个第三方的工具来展示这些数据,今天我们要和大家使用到的就是grafana

5.1 安装

grafana 是一个可视化面板,有着非常漂亮的图表和布局展示,功能齐全的度量仪表盘和图形编辑器,支持 Graphite、zabbix、InfluxDB、Prometheus、OpenTSDB、Elasticsearch 等作为数据源,比 Prometheus 自带的图表展示功能强大太多,更加灵活,有丰富的插件,功能更加强大。

接下来我们就来直接安装,同样的,我们将 grafana 安装到 Kubernetes 集群中,第一步同样是去查看 grafana 的 docker 镜像的介绍,我们可以在 dockerhub 上去搜索,也可以在官网去查看相关资料,镜像地址如下:https://hub.docker.com/r/grafana/grafana/,我们可以看到介绍中运行 grafana 容器的命令非常简单:

$ docker run -d --name=grafana -p 3000:3000 grafana/grafana

但是还有一个需要注意的是 Changelog 中v5.1.0版本的更新介绍:

  • Major restructuring of the container
  • Usage of chown removed
  • File permissions incompatibility with previous versions
    • user id changed from 104 to 472
    • group id changed from 107 to 472
  • Runs as the grafana user by default (instead of root)
  • All default volumes removed

特别需要注意第3条,userid 和 groupid 都有所变化,所以我们在运行的容器的时候需要注意这个变化。现在我们将这个容器转化成 Kubernetes 中的 Pod:(grafana-deploy.yaml)

[root@node01 ~]# vim grafana-deploy.yaml
[root@node01 ~]# cat grafana-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
  namespace: kube-ops
  labels:
    app: grafana
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: grafana
  template:
    metadata:
      labels:
        app: grafana
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:5.3.4
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
          name: grafana
        env:
        - name: GF_SECURITY_ADMIN_USER
          value: admin
        - name: GF_SECURITY_ADMIN_PASSWORD
          value: admin321
        readinessProbe:
          failureThreshold: 10
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 30
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /api/health
            port: 3000
            scheme: HTTP
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: 100m
            memory: 256Mi
          requests:
            cpu: 100m
            memory: 256Mi
        volumeMounts:
        - mountPath: /var/lib/grafana
          subPath: grafana
          name: storage
      securityContext:
        fsGroup: 472
        runAsUser: 472
      volumes:
      - name: storage
        persistentVolumeClaim:
          claimName: grafana

我们使用了最新的镜像grafana/grafana:5.3.4,然后添加了监控检查、资源声明,另外两个比较重要的环境变量GF_SECURITY_ADMIN_USERGF_SECURITY_ADMIN_PASSWORD,用来配置 grafana 的管理员用户和密码的,由于 grafana 将 dashboard、插件这些数据保存在/var/lib/grafana这个目录下面的,所以我们这里如果需要做数据持久化的话,就需要针对这个目录进行 volume 挂载声明,其他的和我们之前的 Deployment 没什么区别,由于上面我们刚刚提到的 Changelog 中 grafana 的 userid 和 groupid 有所变化,所以我们这里需要增加一个securityContext的声明来进行声明。

当然如果要使用一个 pvc 对象来持久化数据,我们就需要添加一个可用的 pv 供 pvc 绑定使用:(grafana-volume.yaml)

[root@node01 ~]# vim grafana-volume.yaml
[root@node01 ~]# cat grafana-volume.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: grafana
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    server: 192.168.200.11
    path: /data/k8s
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: grafana
  namespace: kube-ops
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

最后,我们需要对外暴露 grafana 这个服务,所以我们需要一个对应的 Service 对象,当然用 NodePort 或者再建立一个 ingress 对象都是可行的:(grafana-svc.yaml)

[root@node01 ~]# vim grafana-svc.yaml
[root@node01 ~]# cat grafana-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: grafana
  namespace: kube-ops
  labels:
    app: grafana
spec:
  type: NodePort
  ports:
    - port: 3000
  selector:
    app: grafana

现在我们直接创建上面的这些资源对象:

[root@node01 ~]# kubectl create -f grafana-volume.yaml
persistentvolume "grafana" created
persistentvolumeclaim "grafana" created

[root@node01 ~]# kubectl create -f grafana-deploy.yaml
deployment.apps "grafana" created

[root@node01 ~]# kubectl create -f grafana-svc.yaml
service "grafana" created

创建完成后,我们可以查看 grafana 对应的 Pod 是否正常:

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS             RESTARTS   AGE
grafana-79477fbb7c-cqtbb      0/1       CrashLoopBackOff   2          35s
...

我们可以看到这里的状态是CrashLoopBackOff,并没有正常启动,我们查看下这个 Pod 的日志:

[root@node01 ~]# kubectl logs -f grafana-79477fbb7c-cqtbb -n kube-ops
GF_PATHS_DATA='/var/lib/grafana' is not writable.
You may have issues with file permissions, more information here: http://docs.grafana.org/installation/docker/#migration-from-a-previous-version-of-the-docker-container-to-5-1-or-later
mkdir: cannot create directory '/var/lib/grafana/plugins': Permission denied

上面的错误是在5.1版本之后才会出现的,当然你也可以使用之前的版本来规避这个问题。

可以看到是日志中错误很明显就是/var/lib/grafana目录的权限问题,这还是因为5.1版本后 groupid 更改了引起的问题,我们这里增加了securityContext,但是我们将目录/var/lib/grafana挂载到 pvc 这边后目录的拥有者并不是上面的 grafana(472)这个用户了,所以我们需要更改下这个目录的所属用户,这个时候我们可以利用一个 Job 任务去更改下该目录的所属用户:(grafana-chown-job.yaml)

[root@node01 ~]# vim grafana-chown-job.yaml
[root@node01 ~]# cat grafana-chown-job.yaml 
apiVersion: batch/v1
kind: Job
metadata:
  name: grafana-chown
  namespace: kube-ops
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: grafana-chown
        command: ["chown", "-R", "472:472", "/var/lib/grafana"]
        image: busybox
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: storage
          subPath: grafana
          mountPath: /var/lib/grafana
      volumes:
      - name: storage
        persistentVolumeClaim:
          claimName: grafana

上面我们利用一个 busybox 镜像将/var/lib/grafana目录更改成了472这个 user 和 group,不过还需要注意的是下面的 volumeMounts 和 volumes 需要和上面的 Deployment 对应上。

现在我们删除之前创建的 Deployment 对象,重新创建:

[root@node01 ~]# kubectl delete -f grafana-deploy.yaml
deployment.apps "grafana" deleted

[root@node01 ~]# kubectl create -f grafana-deploy.yaml
deployment.apps "grafana" created

[root@node01 ~]# kubectl create -f grafana-chown-job.yaml
job.batch "grafana-chown" created

重新执行完成后,可以查看下上面的创建的资源对象是否正确了:

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS      RESTARTS   AGE
grafana-79477fbb7c-vkzcn      1/1       Running     2          31s
grafana-chown-rlcl8           0/1       Completed   0          27s
...

我们可以看到有一个状态为Completed的 Pod,这就是上面我们用来更改 grafana 目录权限的 Pod,是一个 Job 任务,所以执行成功后就退出了,状态变成了Completed,而上面的 grafana 的 Pod 也已经是Running状态了,可以查看下该 Pod 的日志确认下:

[root@node01 ~]# kubectl logs -f grafana-79477fbb7c-vkzcn -n kube-ops
t=2021-11-14T10:28:01+0000 lvl=info msg="Starting Grafana" logger=server version=5.3.4 commit=69630b9 compiled=2018-11-13T12:19:12+0000
t=2021-11-14T10:28:01+0000 lvl=info msg="Config loaded from" logger=settings file=/usr/share/grafana/conf/defaults.ini
t=2021-11-14T10:28:01+0000 lvl=info msg="Config loaded from" logger=settings file=/etc/grafana/grafana.ini
...
t=2021-11-14T10:28:05+0000 lvl=info msg="Initializing Stream Manager"
t=2021-11-14T10:28:05+0000 lvl=info msg="HTTP Server Listen" logger=http.server address=0.0.0.0:3000 protocol=http subUrl= socket=

看到上面的日志信息就证明我们的 grafana 的 Pod 已经正常启动起来了。这个时候我们可以查看 Service 对象:

[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
grafana      NodePort    10.109.169.63   <none>        3000:30865/TCP      4m
...

现在我们就可以在浏览器中使用http://192.168.200.11:30865来访问 grafana 这个服务了:

图片.png-271.4kB

由于上面我们配置了管理员的,所以第一次打开的时候会跳转到登录界面,然后就可以用上面我们配置的两个环境变量的值来进行登录了,登录完成后就可以进入到下面 Grafana 的首页(admin/admin321):

图片.png-134kB

5.2 配置

在上面的首页中我们可以看到已经安装了 Grafana,接下来点击Add data source进入添加数据源界面。

5.3 数据源

我们这个地方配置的数据源是 Prometheus,所以选择这个 Type 即可,给改数据源添加一个 name:prometheus-ds,最主要的是下面HTTP区域是配置数据源的访问模式。

访问模式是用来控制如何处理对数据源的请求的:

  • 服务器(Server)访问模式(默认):所有请求都将从浏览器发送到 Grafana 后端的服务器,后者又将请求转发到数据源,通过这种方式可以避免一些跨域问题,其实就是在 Grafana 后端做了一次转发,需要从Grafana 后端服务器访问该 URL。
  • 浏览器(Browser)访问模式:所有请求都将从浏览器直接发送到数据源,但是有可能会有一些跨域的限制,使用此访问模式,需要从浏览器直接访问该 URL。

由于我们这个地方 Prometheus 通过 NodePort 的方式的对外暴露的服务,所以我们这个地方是不是可以使用浏览器访问模式直接访问 Prometheus 的外网地址,但是这种方式显然不是最好的,相当于走的是外网,而我们这里 Prometheus 和 Grafana 都处于 kube-ops 这同一个 namespace 下面,是不是在集群内部直接通过 DNS 的形式就可以访问了,而且还都是走的内网流量,所以我们这里用服务器访问模式显然更好,数据源地址:http://prometheus:9090(因为在同一个 namespace 下面所以直接用 Service 名也可以),然后其他的配置信息就根据实际情况了,比如 Auth 认证,我们这里没有,所以跳过即可,点击最下方的Save & Test提示成功证明我们的数据源配置正确:

图片.png-121.6kB

图片

数据源添加完成后,就可以来添加 Dashboard 了。

5.4 Dashboard

同样,切换到主页,我们可以根据自己的需求手动新建一个 Dashboard,除此之外,grafana 的官方网站上还有很多公共的 Dashboard 可以供我们使用,我们这里可以使用Kubernetes cluster monitoring (via Prometheus)(dashboard id 为162)这个 Dashboard 来展示 Kubernetes 集群的监控信息,在左侧侧边栏 Create 中点击import导入:

图片.png-139kB

我们可以将上面编号162的 dashboard 下载到本地,然后这里重新上传即可,也可以在上面的文本框中直接输入162编号回车即可,导入这个 dashboard:

图片.png-212.2kB

图片.png-139.9kB

需要注意的是在执行上面的 import 之前要记得选择我们的prometheus-ds这个名字的数据源,执行import操作,就可以进入到 dashboard 页面:

图片.png-219.5kB
图片.png-188.6kB

我们可以看到 dashboard 页面上出现了很多漂亮的图表,但是看上去数据不正常,这是因为这个 dashboard 里面需要的数据指标名称和我们 Prometheus 里面采集到的数据指标不一致造成的,比如,第一个Cluster memory usage(集群内存使用情况),我们可以点击标题 -> Edit,进入编辑这个图表的编辑页面:

图片.png-151.6kB

进入编辑页面我们就可以看到这个图表的查询语句:

(sum(node_memory_MemTotal) - sum(node_memory_MemFree+node_memory_Buffers+node_memory_Cached) ) / sum(node_memory_MemTotal) * 100

图片.png-205.7kB

这就是我们之前在 Prometheus 里面查询的promQL语句,我们可以将上面的查询语句复制到 Prometheus 的 Graph 页面进行查询,其实可以预想到是没有对应的数据的,因为我们用node_exporter采集到的数据指标不是node_memory_MemTotal关键字,而是node_memory_MemTotal_bytes,将上面的promQL语句做相应的更改:

(sum(node_memory_MemTotal_bytes) - sum(node_memory_MemFree_bytes + node_memory_Buffers_bytes+node_memory_Cached_bytes)) / sum(node_memory_MemTotal_bytes) * 100

这个语句的意思就是`(整个集群的内存-(整个集群剩余的内存以及Buffer和Cached))/整个集群的内存,简单来说就是总的集群内存使用百分比。将上面 grafana 的promQL语句替换掉,就可以看到图表正常了:

图片.png-235.3kB

同样的,我们可以更改后面的 CPU 和 FileSystem 的使用率:

图片.png-92.8kB

同样下面的Pod CPU Usage用来展示 Pod CPU 的使用情况,对应的promQL语句如下,根据 pod_name 来进行统计:

sum by (pod_name)(rate(container_cpu_usage_seconds_total{image!="", pod_name!=""}[1m]))

按照上面的方法替换 grafana 中的 dashboard 图表中的查询语句:

图片.png-622.8kB

其他的也按照我们的实际需求重新编辑下就可以,下图是最终整个 dashboard 的效果图:

最后要记得保存这个 dashboard,下面的链接是我修改后的 dashboard json 文件地址,你可以直接下载下来导入到 grafana 当中,当然你也可以根据实际情况进行相应的更改:k8s-cluster-grafana-dashboard.json。

除此之外,我们也可以前往 grafana dashboard 的页面去搜索其他的关于 Kubernetes 的监控页面,地址:https://grafana.com/dashboards,比如id 为747和741的这两个 dashboard。

5.5 插件

上面是我们最常用的 grafana 当中的 dashboard 的功能的使用,然后我们也可以来进行一些其他的系统管理,比如添加用户,为用户添加权限等等,我们也可以安装一些其他插件,比如 grafana 就有一个专门针对 Kubernetes 集群监控的插件:grafana-kubernetes-app

要安装这个插件,需要到 grafana 的 Pod 里面去执行安装命令:

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS      RESTARTS   AGE
grafana-79477fbb7c-vkzcn      1/1       Running     2          9m
...

[root@node01 ~]# kubectl exec -it grafana-79477fbb7c-vkzcn /bin/bash -n kube-ops
grafana@grafana-79477fbb7c-vkzcn:/usr/share/grafana$ grafana-cli plugins install grafana-kubernetes-app
installing grafana-kubernetes-app @ 1.0.1
from url: https://grafana.com/api/plugins/grafana-kubernetes-app/versions/1.0.1/download
into: /var/lib/grafana/plugins

✔ Installed grafana-kubernetes-app successfully 

Restart grafana after installing plugins . <service grafana-server restart>

grafana@grafana-79477fbb7c-vkzcn:/usr/share/grafana$

安装完成后需要重启 grafana 才会生效,我们这里直接删除 Pod,重建即可

[root@node01 ~]# kubectl delete -f grafana-deploy.yaml
deployment.apps "grafana" deleted

[root@node01 ~]# kubectl create -f grafana-deploy.yaml
deployment.apps "grafana" created

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS      RESTARTS   AGE
grafana-79477fbb7c-9tpnc      1/1       Running     0          29s
...

然后回到 grafana 页面中,切换到 plugins 页面可以发现下面多了一个 Kubernetes 的插件,点击进来启用即可,然后点击Next up旁边的链接配置集群

图片.png-470.7kB

图片.png-108.3kB

这里我们可以添加一个新的 Kubernetes 集群,这里需要填写集群的访问地址:https://kubernetes.default,然后比较重要的是集群访问的证书,勾选上TLS Client AuthWith CA Cert这两项。

图片.png-125.8kB
图片.png-378kB
图片.png-133.6kB

集群访问的证书文件,用我们访问集群的 kubectl 的配置文件中的证书信息(~/.kube/config)即可,其中属性certificate-authority-dataclient-certificate-dataclient-key-data就对应这 CA 证书、Client 证书、Client 私钥,不过 config 文件里面的内容是base64编码过后的,所以我们这里填写的时候要做base64解码。

另外需要将解码过后的换行符换成\n,不然认证会失败。

配置完成后,可以直接点击Deploy(实际上前面的课程中我们都已经部署过相关的资源了),然后点击Save,就可以获取到集群的监控资源信息了。

图片.png-201.5kB
图片.png-128.5kB

可以看到上面展示了整个集群的状态,可以查看上面的一些 Dashboard:

图片.png-230.5kB

5.6 报警

grafana 4 版本以上就支持了报警功能,这使得我们利用 grafana 作为监控面板更为完整,因为报警是监控系统中必不可少的环节,grafana 支持很多种形式的报警功能,比如 email、钉钉、slack、webhook 等等,我们这里来测试下 email 和 钉钉。

5.7email 报警

要启用 email 报警需要在启动配置文件中/etc/grafana/grafan.ini开启 SMTP 服务,我们这里同样利用一个 ConfigMap 资源对象挂载到 grafana Pod 中:(grafana-cm.yaml)

[root@node01 ~]# vim grafana-cm.yaml
[root@node01 ~]# cat grafana-cm.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-config
  namespace: kube-ops
data:
  grafana.ini: |
    [server]
    root_url = http://<你grafana的url地址>
    [smtp]
    enabled = true
    host = smtp.163.com:25
    user = ych_1024@163.com
    password = <邮箱密码>
    skip_verify = true
    from_address = ych_1024@163.com
    [alerting]
    enabled = true
    execute_alerts = true

上面配置了我的 QQ 邮箱,开启报警功能,当然我们还得将这个 ConfigMap 文件挂载到 Pod 中去:

[root@node01 ~]# vim grafana-deploy.yaml 
[root@node01 ~]# tail -7 grafana-deploy.yaml 
        volumeMounts:
        - mountPath: "/etc/grafana"
          name: config
      volumes:
      - name: config
        configMap:
        name: grafana-config

创建 ConfigMap 对象,更新 Deployment:

[root@node01 ~]# kubectl create -f grafana-cm.yaml
configmap "grafana-config" created

[root@node01 ~]# kubectl apply -f grafana-deploy.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps "grafana" configured

更新完成后,在 grafana 的 webui 中Alert页面测试邮件报警:

图片.png-135.1kB

发送测试后,正常情况下就可以收到测试报警邮件:

图片.png-170.1kB

5.8 钉钉报警

上面我们也说了 grafana 也是支持钉钉报警的,在钉钉群里面添加群机器人,选择最后的自定义机器人:

图片.png-94.4kB

添加完成后可以得到一个 webhook 的地址,然后将这个 webhook 地址添加到上面 grafana webui 的报警测试页面进行测试,就可以在钉钉群里面收到报警测试信息了:

图片.png-43.3kB

5.9 配置

目前只有 Graph 支持报警功能,所以我们选择 Graph 相关图表,点击编辑,进入 Graph 编辑页面可以看到有一个 Alert 模块,切换过来创建报警:

图片.png-235.1kB

然后配置相关参数:

  • 1、Alert 名称,可以自定义。
  • 2、执行的频率,这里我选择每60s检测一次。
  • 3、判断标准,默认是 avg,这里是下拉框,自己按需求选择。
  • 4、query(A,5m,now),字母A代表选择的metrics 中设置的 sql,也可以选择其它在 metrics中设置的,但这里是单选。5m代表从现在起往之前的五分钟,即5m之前的那个点为时间的起始点,now为时间的结束点,此外这里可以自己手动输入时间。
  • 5、设置的预警临界点,这里手动输入,和6是同样功能,6可以手动移动,两种操作是等同的。

然后需要设置报警发送信息,点击侧边的Notifications:

图片.png-29kB

其中Send to就是前面我们配置过的发送邮件和钉钉的报警频道的名称。

配置完成后需要保存下这个 graph,否则发送报警可能会失败,然后点击 Alert 区域的Test Rule可以来测试报警规则,然后邮件和钉钉正常来说就可以收到报警信息了。

邮件报警信息:

图片.png-84.6kB

钉钉报警信息:

图片.png-17.8kB

到这里就完成了使用 grafana 来展示 Kubernetes 集群的监控图表信息以及报警配置,但是我们明显可以感觉到 grafana 的优势在于图表的展示,报警功能有点弱,所以一般来说,在生产环境我们不会直接使用 grafana 的报警功能,更多的是使用功能更加强大的 AlertManager

6. AlertManager 的使用

上节课我们和大家一起学习了 Grafana 的使用,也测试了 Grafana 的报警功能,但是 Grafana 的报警功能目前还比较弱,只支持 Graph 的图表的报警。今天来给大家介绍一个功能更加强大的报警工具:AlertManager。

6.1 简介

之前我们学习 Prometheus 的时候就了解到 Prometheus 包含一个报警模块,就是我们的 AlertManager,Alertmanager 主要用于接收 Prometheus 发送的告警信息,它支持丰富的告警通知渠道,而且很容易做到告警信息进行去重,降噪,分组等,是一款前卫的告警通知系统。

图片.png-341.2kB

接下来我们就来学习下 AlertManager 的具体使用方法。

6.2 安装

从官方文档https://prometheus.io/docs/alerting/configuration/中我们可以看到下载AlertManager二进制文件后,可以通过下面的命令运行:

$ ./alertmanager --config.file=simple.yml

其中-config.file参数是用来指定对应的配置文件的,由于我们这里同样要运行到 Kubernetes 集群中来,所以我们使用docker镜像的方式来安装,使用的镜像是:prom/alertmanager:v0.15.3

首先,指定配置文件,同样的,我们这里使用一个 ConfigMap 资源对象:(alertmanager-conf.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: alert-config
  namespace: kube-ops
data:
  config.yml: |-
    global:
      # 在没有报警的情况下声明为已解决的时间
      resolve_timeout: 5m
      # 配置邮件发送信息
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: 'ych_1024@163.com'
      smtp_auth_username: 'ych_1024@163.com'
      smtp_auth_password: '<邮箱密码>'
      smtp_hello: '163.com'
      smtp_require_tls: false
    # 所有报警信息进入后的根路由,用来设置报警的分发策略
    route:
      # 这里的标签列表是接收到报警信息后的重新分组标签,例如,接收到的报警信息里面有许多具有 cluster=A 和 alertname=LatncyHigh 这样的标签的报警信息将会批量被聚合到一个分组里面
      group_by: ['alertname', 'cluster']
      # 当一个新的报警分组被创建后,需要等待至少group_wait时间来初始化通知,这种方式可以确保您能有足够的时间为同一分组来获取多个警报,然后一起触发这个报警信息。
      group_wait: 30s

      # 当第一个报警发送后,等待'group_interval'时间来发送新的一组报警信息。
      group_interval: 5m

      # 如果一个报警信息已经发送成功了,等待'repeat_interval'时间来重新发送他们
      repeat_interval: 5m

      # 默认的receiver:如果一个报警没有被一个route匹配,则发送给默认的接收器
      receiver: default

      # 上面所有的属性都由所有子路由继承,并且可以在每个子路由上进行覆盖。
      routes:
      - receiver: email
        group_wait: 10s
        match:
          team: node
    receivers:
    - name: 'default'
      email_configs:
      - to: '517554016@qq.com'
        send_resolved: true
    - name: 'email'
      email_configs:
      - to: '517554016@qq.com'
        send_resolved: true
[root@node01 ~]# vim alertmanager-conf.yaml
[root@node01 ~]# cat alertmanager-conf.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: alert-config
  namespace: kube-ops
data:
  config.yml: |-
    global:
      resolve_timeout: 5m
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: '17310658206@163.com'
      smtp_auth_username: '17310658206@163.com'
      smtp_auth_password: '***'
      smtp_hello: '163.com'
      smtp_require_tls: false
    route:
      group_by: ['alertname', 'cluster']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 5m
      receiver: default
      routes:
      - receiver: email
        group_wait: 10s
        match:
          team: node
    receivers:
    - name: 'default'
      email_configs:
      - to: '1773464408@qq.com'
        send_resolved: true
    - name: 'email'
      email_configs:
      - to: '1773464408@qq.com'
        send_resolved: true

这是 AlertManager 的配置文件,我们先直接创建这个 ConfigMap 资源对象:

[root@node01 ~]# kubectl create -f alertmanager-conf.yaml
configmap "alert-config" created

然后配置 AlertManager 的容器,我们可以直接在之前的 Prometheus 的 Pod 中添加这个容器,对应的 YAML 资源声明如下:

 [root@node01 ~]# vim prometheus-deploy.yaml 
[root@node01 ~]# tail -25 prometheus-deploy.yaml 
    spec:
      serviceAccountName: prometheus
      containers:
      - name: alertmanager
        image: prom/alertmanager:v0.15.3
        imagePullPolicy: IfNotPresent
        args:
        - "--config.file=/etc/alertmanager/config.yml"
        ports:
        - containerPort: 9093
          name: http
        volumeMounts:
        - mountPath: "/etc/alertmanager"
          name: alertcfg
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 100m
            memory: 256Mi
      volumes:
      - name: alertcfg
        configMap:
          name: alert-config

这里我们将上面创建的 alert-config 这个 ConfigMap 资源对象以 Volume 的形式挂载到 /etc/alertmanager 目录下去,然后在启动参数中指定了配置文件--config.file=/etc/alertmanager/config.yml,然后我们可以来更新这个 Prometheus 的 Pod:

[root@node01 ~]# kubectl apply -f prometheus-deploy.yaml
deployment.apps "prometheus" configured

当然我们也可以将 AlertManager 的配置文件内容直接放入到之前的 Prometheus 的 ConfigMap 的资源对象中,也可以用一个单独的 Pod 来运行 AlertManager 这个容器,完整的资源清单文件可以参考这里:https://github.com/cnych/kubeapp/tree/master/prometheus

AlertManager 的容器启动起来后,我们还需要在 Prometheus 中配置下 AlertManager 的地址,让 Prometheus 能够访问到 AlertManager,在 Prometheus 的 ConfigMap 资源清单中添加如下配置:

[root@node01 ~]# vim prometheus-cm.yaml 
[root@node01 ~]# tail -4 prometheus-cm.yaml 
    alerting:
      alertmanagers:
        - static_configs:
          - targets: ["localhost:9093"]
更新这个资源对象
[root@node01 ~]# kubectl delete -f prometheus-cm.yaml 
configmap "prometheus-config" deleted

[root@node01 ~]# kubectl create -f prometheus-cm.yaml 
configmap "prometheus-config" created

更新完成后,我们查看 Pod 发现有错误,查看下 alertmanager 容器的日志,发现有如下错误信息:

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS             RESTARTS   AGE
...
prometheus-7949df7f4c-2wlqh   0/1       CrashLoopBackOff   2          48s
...

[root@node01 ~]# kubectl logs -f prometheus-7f654c6d97-qhvtk -n kube-ops
level=info ts=2021-11-21T15:52:41.37132905Z caller=main.go:174 msg="Starting Alertmanager" version="(version=0.15.3, branch=HEAD, revision=d4a7697cc90f8bce62efe7c44b63b542578ec0a1)"
level=info ts=2021-11-21T15:52:41.3713901Z caller=main.go:175 build_context="(go=go1.11.2, user=root@4ecc17c53d26, date=20181109-15:40:48)"
level=error ts=2021-11-21T15:52:41.371407589Z caller=main.go:179 msg="Unable to create data directory" err="mkdir data/: read-only file system"

这个是因为新版本dockerfile中的默认WORKDIR发生了变化,变成了/etc/alertmanager目录,默认情况下存储路径--storage.path是相对目录data/,因此,alertmanager 会在我们上面挂载的 ConfigMap 中去创建这个目录,所以会报错,我们可以通过覆盖--storage.path参数来解决这个问题,在容器启动参数中添加该参数:

- name: alertmanager
  image: prom/alertmanager:v0.15.3
  imagePullPolicy: IfNotPresent
  args:
  - "--config.file=/etc/alertmanager/config.yml"
  - "--storage.path=/alertmanager/data"

重新更新 Pod,可以发现 Prometheus 已经是 Running 状态了:

[root@node01 ~]# kubectl apply -f prometheus-deploy.yaml
deployment.apps "prometheus" configured

[root@node01 ~]# kubectl get pods -n kube-ops
NAME                          READY     STATUS      RESTARTS   AGE
...
prometheus-7949df7f4c-2wlqh   1/1       Running     0          39s

# 隔一会儿后执行 reload 操作:
[root@node01 ~]# kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
...
prometheus   NodePort    10.107.45.148   <none>        9090:31891/TCP      14d
...

[root@node01 ~]# curl -X POST "http://10.107.45.148:9090/-/reload"
[root@node01 ~]# kubectl logs -f prometheus-7949df7f4c-2wlqh -n kube-ops
level=info ts=2021-11-21T15:55:10.633795436Z caller=main.go:174 msg="Starting Alertmanager" version="(version=0.15.3, branch=HEAD, revision=d4a7697cc90f8bce62efe7c44b63b542578ec0a1)"
level=info ts=2021-11-21T15:55:10.63391506Z caller=main.go:175 build_context="(go=go1.11.2, user=root@4ecc17c53d26, date=20181109-15:40:48)"
level=info ts=2021-11-21T15:55:10.641622752Z caller=cluster.go:155 component=cluster msg="setting advertise address explicitly" addr=10.244.1.12 port=9094
level=info ts=2021-11-21T15:55:10.733558889Z caller=main.go:322 msg="Loading configuration file" file=/etc/alertmanager/config.yml
level=info ts=2021-11-21T15:55:10.734944861Z caller=cluster.go:570 component=cluster msg="Waiting for gossip to settle..." interval=2s
level=info ts=2021-11-21T15:55:10.736518011Z caller=main.go:398 msg=Listening address=:9093
level=info ts=2021-11-21T15:55:12.73560549Z caller=cluster.go:595 component=cluster msg="gossip not settled" polls=0 before=0 now=1 elapsed=2.000611799s
level=info ts=2021-11-21T15:55:20.738695251Z caller=cluster.go:587 component=cluster msg="gossip settled; proceeding" elapsed=10.003702899s

6.3 报警规则

现在我们只是把 AlertManager 容器运行起来了,也和 Prometheus 进行了关联,但是现在我们并不知道要做什么报警,因为没有任何地方告诉我们要报警,所以我们还需要配置一些报警规则来告诉我们对哪些数据进行报警。

警报规则允许你基于 Prometheus 表达式语言的表达式来定义报警报条件,并在触发警报时发送通知给外部的接收者。

同样在 Prometheus 的配置文件中添加如下报警规则配置:

rule_files:
  - /etc/prometheus/rules.yml

其中rule_files就是用来指定报警规则的,这里我们同样将rules.yml文件用 ConfigMap 的形式挂载到/etc/prometheus目录下面即可:

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-ops
data:
  prometheus.yml: |
    ...
  rules.yml: |
    groups:
    - name: test-rule
      rules:
      - alert: NodeMemoryUsage
        expr: (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100 > 20
        for: 2m
        labels:
          team: node
        annotations:
          summary: "{{$labels.instance}}: High Memory usage detected"
          description: "{{$labels.instance}}: Memory usage is above 20% (current value is: {{ $value }}"

上面我们定义了一个名为NodeMemoryUsage的报警规则,其中:

  • for语句会使 Prometheus 服务等待指定的时间, 然后执行查询表达式。
  • labels语句允许指定额外的标签列表,把它们附加在告警上。
  • annotations语句指定了另一组标签,它们不被当做告警实例的身份标识,它们经常用于存储一些额外的信息,用于报警信息的展示之类的。

为了方便演示,我们将的表达式判断报警临界值设置为20,重新更新 ConfigMap 资源对象,由于我们在 Prometheus 的 Pod 中已经通过 Volume 的形式将 prometheus-config 这个一个 ConfigMap 对象挂载到了/etc/prometheus目录下面,所以更新后,该目录下面也会出现rules.yml文件,所以前面配置的rule_files路径也是正常的,更新完成后,重新执行reload操作,这个时候我们去 Prometheus 的 Dashboard 中切换到alerts路径下面就可以看到有报警配置规则的数据了:

图片.png-146.6kB

我们可以看到页面中出现了我们刚刚定义的报警规则信息,而且报警信息中还有状态显示。一个报警信息在生命周期内有下面3种状态:

  • inactive: 表示当前报警信息既不是firing状态也不是pending状态
  • pending: 表示在设置的阈值时间范围内被激活了
  • firing: 表示超过设置的阈值时间被激活了

我们这里的状态现在是firing就表示这个报警已经被激活了,我们这里的报警信息有一个team=node这样的标签,而最上面我们配置 alertmanager 的时候就有如下的路由配置信息了:

routes:
- receiver: email
  group_wait: 10s
  match:
    team: node

所以我们这里的报警信息会被email这个接收器来进行报警,我们上面配置的是邮箱,所以正常来说这个时候我们会收到一封如下的报警邮件:

图片.png-96.9kB

我们可以看到收到的邮件内容中包含一个View In AlertManager的链接,我们同样可以通过 NodePort 的形式去访问到 AlertManager 的 Dashboard 页面:

$ kubectl get svc -n kube-ops
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGE
prometheus   NodePort    10.102.74.90    <none>        9093:31788/TCP,9090:30358/TCP    34d

然后通过<任一Node节点>:31788进行访问,我们就可以查看到 AlertManager 的 Dashboard 页面:

图片.png-100.8kB

在这个页面中我们可以进行一些操作,比如过滤、分组等等,里面还有两个新的概念:Inhibition(抑制)和 Silences(静默)。

  • Inhibition:如果某些其他警报已经触发了,则对于某些警报,Inhibition 是一个抑制通知的概念。例如:一个警报已经触发,它正在通知整个集群是不可达的时,Alertmanager 则可以配置成关心这个集群的其他警报无效。这可以防止与实际问题无关的数百或数千个触发警报的通知,Inhibition 需要通过上面的配置文件进行配置。
  • Silences:静默是一个非常简单的方法,可以在给定时间内简单地忽略所有警报。Silences 基于 matchers配置,类似路由树。来到的警告将会被检查,判断它们是否和活跃的 Silences 相等或者正则表达式匹配。如果匹配成功,则不会将这些警报发送给接收者。

由于全局配置中我们配置的repeat_interval: 5m,所以正常来说,上面的测试报警如果一直满足报警条件(CPU使用率大于20%)的话,那么每5分钟我们就可以收到一条报警邮件。

现在我们添加一个 Silences,如下图所示,匹配 node02 节点的内存报警:

图片.png-67.3kB

添加完成后,等下一次的报警信息触发后,我们可以看到报警信息里面已经没有了节点 node02 的报警信息了:

图片.png-83.3kB

由于我们上面添加的 Silences 是有过期时间的,所以在这个时间段过后,node02 的报警信息就会恢复了。

6.4 webhook接收器

上面我们配置的是 AlertManager 自带的邮件报警模板,我们也说了 AlertManager 支持很多中报警接收器,比如 slack、微信之类的,其中最为灵活的方式当然是使用 webhook 了,我们可以定义一个 webhook 来接收报警信息,然后在 webhook 里面去进行处理,需要发送怎样的报警信息我们自定义就可以。

比如我们这里用 Flask 编写了一个简单的处理钉钉报警的 webhook 的程序:

import os
import json
import requests

from flask import Flask
from flask import request

app = Flask(__name__)


@app.route('/', methods=['POST', 'GET'])
def send():
    if request.method == 'POST':
        post_data = request.get_data()
        send_alert(bytes2json(post_data))
        return 'success'
    else:
        return 'weclome to use prometheus alertmanager dingtalk webhook server!'


def bytes2json(data_bytes):
    data = data_bytes.decode('utf8').replace("'", '"')
    return json.loads(data)


def send_alert(data):
    token = os.getenv('ROBOT_TOKEN')
    if not token:
        print('you must set ROBOT_TOKEN env')
        return
    url = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % token
    send_data = {
        "msgtype": "text",
        "text": {
            "content": data
        }
    }
    req = requests.post(url, json=send_data)
    result = req.json()
    if result['errcode'] != 0:
        print('notify dingtalk error: %s' % result['errcode'])


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

代码非常简单,通过一个 ROBOT_TOKEN 的环境变量传入群机器人的 TOKEN,然后直接将 webhook 发送过来的数据直接以文本的形式转发给群机器人。

大家可以根据自己的需求来定制报警数据,上述代码仓库地址:github.com/cnych/alertmanager-dingtalk-hook

当然我们得将上面这个服务部署到集群中来,对应的资源清单如下:(dingtalk-hook.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dingtalk-hook
  namespace: kube-ops
spec:
  selector:
    matchLabels:
      app: dingtalk-hook
  template:
    metadata:
      labels:
        app: dingtalk-hook
    spec:
      containers:
      - name: dingtalk-hook
        image: cnych/alertmanager-dingtalk-hook:v0.2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5000
          name: http
        env:
        - name: ROBOT_TOKEN
          valueFrom:
            secretKeyRef:
              name: dingtalk-secret
              key: token
        resources:
          requests:
            cpu: 50m
            memory: 100Mi
          limits:
            cpu: 50m
            memory: 100Mi

---
apiVersion: v1
kind: Service
metadata:
  name: dingtalk-hook
  namespace: kube-ops
spec:
  selector:
    app: dingtalk-hook
  ports:
  - name: hook
    port: 5000
    targetPort: http

要注意上面我们声明了一个 ROBOT_TOKEN 的环境变量,由于这是一个相对于私密的信息,所以我们这里从一个 Secret 对象中去获取,通过如下命令创建一个名为 dingtalk-secret 的 Secret 对象,然后部署上面的资源对象即可:

$ kubectl create secret generic dingtalk-secret --from-literal=token=替换成钉钉群聊的机器人TOKEN -n kube-ops
secret "dingtalk-secret" created
$ kubectl create -f dingtalk-hook.yaml
deployment.extensions "dingtalk-hook" created
service "dingtalk-hook" created
$ kubectl get pods -n kube-ops
NAME                            READY     STATUS      RESTARTS   AGE
dingtalk-hook-c4fcd8cd6-6r2b6   1/1       Running     0          45m
......

部署成功后,现在我们就可以给 AlertManager 配置一个 webhook 了,在上面的配置中增加一个路由接收器

  routes:
  - receiver: webhook
    match:
      filesystem: node
receivers:
- name: 'webhook'
  webhook_configs:
  - url: 'http://dingtalk-hook:5000'
    send_resolved: true

我们这里配置了一个名为 webhook 的接收器,地址为:http://dingtalk-hook:5000,这个地址当然就是上面我们部署的钉钉的 webhook 的接收程序的 Service 地址。

然后我们也在报警规则中添加一条关于节点文件系统使用情况的报警规则,注意 labels 标签要带上filesystem=node,这样报警信息就会被 webook 这一个接收器所匹配:

- alert: NodeFilesystemUsage
  expr: (node_filesystem_size_bytes{device="rootfs"} - node_filesystem_free_bytes{device="rootfs"}) / node_filesystem_size_bytes{device="rootfs"} * 100 > 10
  for: 2m
  labels:
    filesystem: node
  annotations:
    summary: "{{$labels.instance}}: High Filesystem usage detected"
    description: "{{$labels.instance}}: Filesystem usage is above 10% (current value is: {{ $value }}"

更新 AlertManager 和 Prometheus 的 ConfigMap 资源对象(先删除再创建),更新完成后,隔一会儿执行 reload 操作是更新生效:

$ kubectl get svc -n kube-ops
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                          AGE
prometheus      NodePort    10.102.74.90    <none>        9093:31788/TCP,9090:30358/TCP    34d
$ curl -X POST "http://10.102.74.90:9093/-/reload"
$ curl -X POST "http://10.102.74.90:9090/-/reload"

AlertManager 和 Prometheus 都可以通过上面的 reload 操作进行重新加载

都完成更新后,再次去 Prometheus 的 Alert 路径下面查看报警信息:

图片.png-136.3kB

隔一会儿关于这个节点文件系统的报警就会被触发了,由于这个报警信息包含一个filesystem=node的 label 标签,所以会被路由到webhook这个接收器中,也就是上面我们自定义的这个 dingtalk-hook,触发后可以观察这个 Pod 的日志:

$ kubectl logs -f dingtalk-hook-cc677c46d-gf26f -n kube-ops
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

10.244.2.217 - - [28/Nov/2018 17:14:09] "POST / HTTP/1.1" 200 -

可以看到 POST 请求已经成功了,同时这个时候正常来说就可以收到一条钉钉消息了:

图片.png-100.6kB

由于我们程序中是用一个非常简单的文本形式直接转发的,所以这里报警信息不够友好,没关系,有了这个示例我们完全就可以根据自己的需要来定制消息模板了,可以参考钉钉自定义机器人文档:https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq

7. Prometheus Operator 的安装

前面的课程中我们学习了用自定义的方式来对 Kubernetes 集群进行监控,但是还是有一些缺陷,比如 Prometheus、AlertManager 这些组件服务本身的高可用,当然我们也完全可以用自定义的方式来实现这些需求,我们也知道 Promethues 在代码上就已经对 Kubernetes 有了原生的支持,可以通过服务发现的形式来自动监控集群,因此我们可以使用另外一种更加高级的方式来部署 Prometheus:Operator 框架。

7.1 Operator

Operator是由CoreOS公司开发的,用来扩展 Kubernetes API,特定的应用程序控制器,它用来创建、配置和管理复杂的有状态应用,如数据库、缓存和监控系统。Operator基于 Kubernetes 的资源和控制器概念之上构建,但同时又包含了应用程序特定的一些专业知识,比如创建一个数据库的Operator,则必须对创建的数据库的各种运维方式非常了解,创建Operator的关键是CRD(自定义资源)的设计。

CRD是对 Kubernetes API 的扩展,Kubernetes 中的每个资源都是一个 API 对象的集合,例如我们在YAML文件里定义的那些spec都是对 Kubernetes 中的资源对象的定义,所有的自定义资源可以跟 Kubernetes 中内建的资源一样使用 kubectl 操作。

Operator是将运维人员对软件操作的知识给代码化,同时利用 Kubernetes 强大的抽象来管理大规模的软件应用。目前CoreOS官方提供了几种Operator的实现,其中就包括我们今天的主角:Prometheus OperatorOperator的核心实现就是基于 Kubernetes 的以下两个概念:

  • 资源:对象的状态定义
  • 控制器:观测、分析和行动,以调节资源的分布

当然我们如果有对应的需求也完全可以自己去实现一个Operator,接下来我们就来给大家详细介绍下Prometheus-Operator的使用方法。

7.2 介绍

首先我们先来了解下Prometheus-Operator的架构图:

图片.png-90.5kB

上图是Prometheus-Operator官方提供的架构图,其中Operator是最核心的部分,作为一个控制器,他会去创建PrometheusServiceMonitorAlertManager以及PrometheusRule4个CRD资源对象,然后会一直监控并维持这4个资源对象的状态。

其中创建的prometheus这种资源对象就是作为Prometheus Server存在,而ServiceMonitor就是exporter的各种抽象,exporter前面我们已经学习了,是用来提供专门提供metrics数据接口的工具,Prometheus就是通过ServiceMonitor提供的metrics数据接口去 pull 数据的,当然alertmanager这种资源对象就是对应的AlertManager的抽象,而PrometheusRule是用来被Prometheus实例使用的报警规则文件。

这样我们要在集群中监控什么数据,就变成了直接去操作 Kubernetes 集群的资源对象了,是不是方便很多了。上图中的 Service 和 ServiceMonitor 都是 Kubernetes 的资源,一个 ServiceMonitor 可以通过 labelSelector 的方式去匹配一类 Service,Prometheus 也可以通过 labelSelector 去匹配多个ServiceMonitor。

7.3 安装

我们这里直接通过 Prometheus-Operator 的源码来进行安装,当然也可以用 Helm 来进行一键安装,我们采用源码安装可以去了解更多的实现细节。首页将源码 Clone 下来:

#首先我们下载operator
#我这里提供的yaml文件和下面的镜像版本是对应的,
[root@node01 ~]# wget -P /root/ http://down.i4t.com/abcdocker-prometheus-operator.yaml.zip
[root@node01 ~]# unzip abcdocker-prometheus-operator.yaml.zip 
 
#如果使用官网的请自行下载镜像
[root@node01 ~]# git clone -b release-0.7 https://github.com/prometheus-operator/kube-prometheus.git

最新的版本官方将资源https://github.com/coreos/prometheus-operator/tree/master/contrib/kube-prometheus迁移到了独立的 git 仓库中:https://github.com/coreos/kube-prometheus.git

进入到 manifests 目录下面,这个目录下面包含我们所有的资源清单文件,我们需要对其中的文件,prometheus-serviceMonitorKubelet.yaml (这个文件是用来收集我们service的metrics数据的) 这里不进行修改,感兴趣的可以点此查看完整代码

[root@node01 ~]# cat kube-prometheus-master/manifests/prometheus-serviceMonitorKubelet.yaml 
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    k8s-app: kubelet
  name: kubelet
  namespace: monitoring
spec:
  endpoints:
  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
    honorLabels: true
    interval: 30s
    port: https-metrics
    scheme: https
    tlsConfig:
      insecureSkipVerify: true
  - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
    honorLabels: true
    interval: 30s
    metricRelabelings:
    - action: drop
      regex: container_(network_tcp_usage_total|network_udp_usage_total|tasks_state|cpu_load_average_10s)
      sourceLabels:
      - __name__
    path: /metrics/cadvisor
    port: https-metrics
    scheme: https
    tlsConfig:
      insecureSkipVerify: true
  jobLabel: k8s-app
  namespaceSelector:
    matchNames:
    - kube-system
  selector:
    matchLabels:
      k8s-app: kubelet

由于国内网络问题,有一些镜像无法直接下载。我这里提供了所有的镜像,只要导入进去即可(三节点)

[root@node01 ~]# wget -c  http://down.i4t.com/abcdocker-prometheus-opertor.tar
[root@node01 ~]# docker load -i abcdocker-prometheus-opertor.tar

修改完成后,直接在该文件夹下面执行创建资源命令即可:

[root@node01 ~]# cd kube-prometheus-master/manifests/
[root@node01 manifests]# kubectl apply -f .

部署完成后,会创建一个名为monitoring的 namespace,所以资源对象对将部署在改命名空间下面,此外 Operator 会自动创建4个 CRD 资源对象:

[root@node01 manifests]# kubectl get crd |grep coreos
alertmanagers.monitoring.coreos.com     49s
podmonitors.monitoring.coreos.com       49s
prometheuses.monitoring.coreos.com      49s
prometheusrules.monitoring.coreos.com   49s
servicemonitors.monitoring.coreos.com   49s

可以在 monitoring 命名空间下面查看所有的 Pod,其中 alertmanager 和 prometheus 是用 StatefulSet 控制器管理的,其中还有一个比较核心的 prometheus-operator 的 Pod,用来控制其他资源对象和监听对象变化的:

[root@node01 manifests]# kubectl get pods -n monitoring
NAME                                   READY     STATUS    RESTARTS   AGE
alertmanager-main-0                    2/2       Running   0          57s
alertmanager-main-1                    2/2       Running   0          47s
alertmanager-main-2                    2/2       Running   0          39s
grafana-f997db786-4wkrx                1/1       Running   0          1m
kube-state-metrics-5ff45ddb4c-rbkvm    4/4       Running   0          43s
node-exporter-965d8                    2/2       Running   0          1m
node-exporter-bp2mf                    2/2       Running   0          1m
node-exporter-z5vb8                    2/2       Running   0          1m
prometheus-adapter-66454859bd-wddns    1/1       Running   0          1m
prometheus-k8s-0                       3/3       Running   1          56s
prometheus-k8s-1                       3/3       Running   1          56s
prometheus-operator-6486bd6bc9-mvdph   1/1       Running   0          1m

其中prometheus和alertmanager采用的StatefulSet,其他的Pod则采用deployment创建

[root@node01 manifests]# kubectl get deployments.apps -n monitoring
NAME                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
grafana               1         1         1            1           6m
kube-state-metrics    1         1         1            1           6m
prometheus-adapter    1         1         1            1           6m
prometheus-operator   1         1         1            1           6m

[root@node01 manifests]# kubectl get statefulsets.apps -n monitoring
NAME                DESIRED   CURRENT   AGE
alertmanager-main   3         3         6m
prometheus-k8s      2         2         6m

#其中prometheus-operator是我们的核心文件,它是监控我们prometheus和alertmanager的文件

查看创建的 Service:

[root@node01 manifests]# kubectl get svc -n monitoring
NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
alertmanager-main       ClusterIP   10.105.151.64   <none>        9093/TCP            1m
alertmanager-operated   ClusterIP   None            <none>        9093/TCP,6783/TCP   1m
grafana                 ClusterIP   10.107.45.148   <none>        3000/TCP            1m
kube-state-metrics      ClusterIP   None            <none>        8443/TCP,9443/TCP   1m
node-exporter           ClusterIP   None            <none>        9100/TCP            1m
prometheus-adapter      ClusterIP   10.97.23.241    <none>        443/TCP             1m
prometheus-k8s          ClusterIP   10.102.66.115   <none>        9090/TCP            1m
prometheus-operated     ClusterIP   None            <none>        9090/TCP            1m
prometheus-operator     ClusterIP   None            <none>        8080/TCP            1m

可以看到上面针对 grafana 和 prometheus 都创建了一个类型为 ClusterIP 的 Service,当然如果我们想要在外网访问这两个服务的话可以通过创建对应的 Ingress 对象或者使用 NodePort 类型的 Service,我们这里为了简单,直接使用 NodePort 类型的服务即可,编辑 grafana 和 prometheus-k8s 这两个 Service,将服务类型更改为 NodePort:

[root@node01 manifests]# kubectl edit svc grafana -n monitoring
service "grafana" edited
[root@node01 manifests]# kubectl edit svc -n monitoring prometheus-k8s
service "prometheus-k8s" edited
[root@node01 manifests]# kubectl edit svc -n monitoring alertmanager-main
service "alertmanager-main" edited

#三个文件都需要修改,不要修改错了。都是修改有clusterIP的
...
  type: NodePort   #将这行修改为NodePort

修改完毕后,我们在查看svc,就会发现这几个都包含node端口了,接下来在任意集群节点访问即可

[root@node01 manifests]# kubectl get svc -n monitoring |egrep "alertmanager-main|grafana|prometheus-k8s"
alertmanager-main       NodePort    10.105.151.64   <none>        9093:31891/TCP      20m
grafana                 NodePort    10.107.45.148   <none>        3000:30853/TCP      20m
prometheus-k8s          NodePort    10.102.66.115   <none>        9090:31837/TCP      20m

更改完成后,我们就可以通过去访问上面的两个服务了,比如查看 prometheus 的 targets 页面,我们访问的集群任意http://192.168.200.11:31837:

图片.png-141.1kB

7.4 配置

我们可以看到大部分的配置都是正常的,只有两三个没有管理到对应的监控目标,比如 kube-controller-manager 和 kube-scheduler 这两个系统组件,这就和 ServiceMonitor 的定义有关系了,我们先来查看下 kube-scheduler 组件对应的 ServiceMonitor 资源的定义:(prometheus-serviceMonitorKubeScheduler.yaml)

[root@node01 manifests]# cat prometheus-serviceMonitorKubeScheduler.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  labels:
    k8s-app: kube-scheduler
  name: kube-scheduler
  namespace: monitoring
spec:
  endpoints:
  - interval: 30s        # 每30s获取一次信息
    port: http-metrics   # 对应service的端口名
  jobLabel: k8s-app
  namespaceSelector: # 表示去匹配某一命名空间中的service,如果想从所有的namespace中匹配用any: true
    matchNames:
    - kube-system
  selector:  # 匹配的 Service 的labels,如果使用mathLabels,则下面的所有标签都匹配时才会匹配该service,如果使用matchExpressions,则至少匹配一个标签的service都会被选择
    matchLabels:
      k8s-app: kube-scheduler

上面是一个典型的 ServiceMonitor 资源文件的声明方式,上面我们通过selector.matchLabels在 kube-system 这个命名空间下面匹配具有k8s-app=kube-scheduler这样的 Service,但是我们系统中根本就没有对应的 Service,所以我们需要手动创建一个 Service:(prometheus-kubeSchedulerService.yaml)

[root@node01 manifests]# vim prometheus-kubeSchedulerService.yaml
[root@node01 manifests]# cat prometheus-kubeSchedulerService.yaml
apiVersion: v1
kind: Service
metadata:
  namespace: kube-system
  name: kube-scheduler
  labels:
    k8s-app: kube-scheduler
spec:
  selector:
    component: kube-scheduler
  ports:
  - name: http-metrics
    port: 10251
    targetPort: 10251
    protocol: TCP

10251是kube-scheduler组件 metrics 数据所在的端口,10252是kube-controller-manager组件的监控数据所在端口。

其中最重要的是上面 labels 和 selector 部分,labels 区域的配置必须和我们上面的 ServiceMonitor 对象中的 selector 保持一致,selector下面配置的是component=kube-scheduler,为什么会是这个 label 标签呢,我们可以去 describe 下 kube-scheduelr 这个 Pod:

[root@node01 manifests]# kubectl describe pod kube-scheduler-master -n kube-system
Name:         kube-scheduler-master
Namespace:    kube-system
Node:         master/192.168.200.10
Start Time:   Tue, 15 Jun 2021 02:26:02 +0800
Labels:       component=kube-scheduler
              tier=control-plane
......

我们可以看到这个 Pod 具有component=kube-schedulertier=control-plane这两个标签,而前面这个标签具有更唯一的特性,所以使用前面这个标签较好,这样上面创建的 Service 就可以和我们的 Pod 进行关联了,直接创建即可:

[root@node01 manifests]# kubectl create -f prometheus-kubeSchedulerService.yaml
service "kube-scheduler" created
[root@node01 manifests]# kubectl get svc -n kube-system -l k8s-app=kube-scheduler
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)     AGE
kube-scheduler   ClusterIP   10.107.89.163   <none>        10251/TCP   12s

创建完成后,隔一小会儿后去 prometheus 查看 targets 下面 kube-scheduler 的状态:

图片.png-83.2kB

我们可以看到现在已经发现了 target,但是抓取数据结果出错了,这个错误是因为我们集群是使用 kubeadm 搭建的,其中 kube-scheduler 默认是绑定在127.0.0.1上面的,而上面我们这个地方是想通过节点的 IP 去访问,所以访问被拒绝了,我们只要把 kube-scheduler 绑定的地址更改成0.0.0.0即可满足要求,由于 kube-scheduler 是以静态 Pod 的形式运行在集群中的,所以我们只需要更改静态 Pod 目录下面对应的 YAML 文件即可:

[root@master ~]# ls /etc/kubernetes/manifests/
etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

[root@master ~]# sed -e "s/- --address=127.0.0.1/- --address=0.0.0.0/" -i /etc/kubernetes/manifests/kube-controller-manager.yaml
[root@master ~]# sed -e "s/- --address=127.0.0.1/- --address=0.0.0.0/" -i /etc/kubernetes/manifests/kube-scheduler.yaml

将 kube-scheduler.yaml 文件中-command--address地址更改成0.0.0.0

containers:
- command:
- kube-scheduler
- --leader-elect=true
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --address=0.0.0.0

修改完成后我们将该文件从当前文件夹中移除,隔一会儿再移回该目录,就可以自动更新了,然后再去看 prometheus 中 kube-scheduler 这个 target 是否已经正常了:

图片.png-64.7kB

大家可以按照上面的方法尝试去修复下 kube-controller-manager 组件的监控。

上面的监控数据配置完成后,现在我们可以去查看下 grafana 下面的 dashboard,同样使用上面的 NodePort 访问即可,第一次登录使用 admin:admin 登录即可,进入首页后,可以发现已经和我们的 Prometheus 数据源关联上了,正常来说可以看到一些监控图表了:

图片.png-175.8kB

8. 自定义 Prometheus Operator 监控项

上节课和大家讲解了 Prometheus Operator 的安装和基本使用方法,这节课给大家介绍如何在 Prometheus Operator 中添加一个自定义的监控项。

除了 Kubernetes 集群中的一些资源对象、节点以及组件需要监控,有的时候我们可能还需要根据实际的业务需求去添加自定义的监控项,添加一个自定义监控的步骤也是非常简单的。

  • 第一步建立一个 ServiceMonitor 对象,用于 Prometheus 添加监控项
  • 第二步为 ServiceMonitor 对象关联 metrics 数据接口的一个 Service 对象
  • 第三步确保 Service 对象可以正确获取到 metrics 数据

接下来我们就来为大家演示如何添加 etcd 集群的监控。

无论是 Kubernetes 集群外的还是使用 Kubeadm 安装在集群内部的 etcd 集群,我们这里都将其视作集群外的独立集群,因为对于二者的使用方法没什么特殊之处。

8.1 etcd 证书

对于 etcd 集群一般情况下,为了安全都会开启 https 证书认证的方式,所以要想让 Prometheus 访问到 etcd 集群的监控数据,就需要提供相应的证书校验。

由于我们这里演示环境使用的是 Kubeadm 搭建的集群,我们可以使用 kubectl 工具去获取 etcd 启动的时候使用的证书路径:

[root@node01 ~]# kubectl get pod etcd-master -n kube-system
NAME          READY     STATUS    RESTARTS   AGE
etcd-master   1/1       Running   0          201d

[root@node01 ~]# kubectl get pod etcd-master -n kube-system -o yaml
......
spec:
  containers:
  - command:
    - etcd
    - --peer-client-cert-auth=true
    - --cert-file=/etc/kubernetes/pki/etcd/server.crt
    - --key-file=/etc/kubernetes/pki/etcd/server.key
    - --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
    - --listen-client-urls=https://127.0.0.1:2379
    - --client-cert-auth=true
    - --data-dir=/var/lib/etcd
    - --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
    - --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
    - --advertise-client-urls=https://127.0.0.1:2379
    image: k8s.gcr.io/etcd-amd64:3.1.12
    imagePullPolicy: IfNotPresent
    livenessProbe:
      exec:
        command:
        - /bin/sh
        - -ec
        - ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
          --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
          get foo
      failureThreshold: 8
      initialDelaySeconds: 15
      periodSeconds: 10
      successThreshold: 1
      timeoutSeconds: 15
    name: etcd
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
    - mountPath: /etc/kubernetes/pki/etcd
      name: etcd-certs

......
  tolerations:
  - effect: NoExecute
    operator: Exists
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data
  - hostPath:
      path: /etc/kubernetes/pki/etcd
      type: DirectoryOrCreate
    name: etcd-certs
......

我们可以看到 etcd 使用的证书都对应在节点的 /etc/kubernetes/pki/etcd 这个路径下面,所以首先我们将需要使用到的证书通过 secret 对象保存到集群中去:(在 etcd 运行的节点)

[root@master ~]# kubectl -n monitoring create secret generic etcd-certs --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.crt --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.key --from-file=/etc/kubernetes/pki/etcd/ca.crt
secret "etcd-certs" created

如果你是独立的二进制方式启动的 etcd 集群,同样将对应的证书保存到集群中的一个 secret 对象中去即可。

然后将上面创建的 etcd-certs 对象配置到 prometheus 资源对象中,直接更新 prometheus 资源对象即可:

[root@node01 ~]# kubectl edit prometheus k8s -n monitoring
prometheus.monitoring.coreos.com "k8s" edited

添加如下的 secrets 属性:

nodeSelector:
  beta.kubernetes.io/os: linux
replicas: 2
secrets:
- etcd-certs

更新完成后,我们就可以在 Prometheus 的 Pod 中获取到上面创建的 etcd 证书文件了,具体的路径我们可以进入 Pod 中查看:

[root@node01 ~]# kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/prometheus $ ls /etc/prometheus/secrets/etcd-certs/
ca.crt                  healthcheck-client.crt  healthcheck-client.key

8.2 创建 ServiceMonitor

现在 Prometheus 访问 etcd 集群的证书已经准备好了,接下来创建 ServiceMonitor 对象即可(prometheus-serviceMonitorEtcd.yaml)

[root@node01 ~]# vim prometheus-serviceMonitorEtcd.yaml
[root@node01 ~]# cat prometheus-serviceMonitorEtcd.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: etcd-k8s
  namespace: monitoring
  labels:
    k8s-app: etcd-k8s
spec:
  jobLabel: k8s-app
  endpoints:
  - port: port
    interval: 30s
    scheme: https
    tlsConfig:
      caFile: /etc/prometheus/secrets/etcd-certs/ca.crt
      certFile: /etc/prometheus/secrets/etcd-certs/healthcheck-client.crt
      keyFile: /etc/prometheus/secrets/etcd-certs/healthcheck-client.key
      insecureSkipVerify: true
  selector:
    matchLabels:
      k8s-app: etcd
  namespaceSelector:
    matchNames:
    - kube-system

上面我们在 monitoring 命名空间下面创建了名为 etcd-k8s 的 ServiceMonitor 对象,基本属性和前面章节中的一致,匹配 kube-system 这个命名空间下面的具有 k8s-app=etcd 这个 label 标签的 Service,jobLabel 表示用于检索 job 任务名称的标签,和前面不太一样的地方是 endpoints 属性的写法,配置上访问 etcd 的相关证书,endpoints 属性下面可以配置很多抓取的参数,比如 relabel、proxyUrl,tlsConfig 表示用于配置抓取监控数据端点的 tls 认证,由于证书 serverName 和 etcd 中签发的可能不匹配,所以加上了 insecureSkipVerify=true

图片.png-871.2kB

关于 ServiceMonitor 属性的更多用法可以查看文档:https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md 了解更多

直接创建这个 ServiceMonitor 对象:

[root@node01 ~]# kubectl create -f prometheus-serviceMonitorEtcd.yaml
servicemonitor.monitoring.coreos.com "etcd-k8s" created

8.3 创建 Service

ServiceMonitor 创建完成了,但是现在还没有关联的对应的 Service 对象,所以需要我们去手动创建一个 Service 对象(prometheus-etcdService.yaml):

[root@node01 ~]# vim prometheus-etcdService.yaml
[root@node01 ~]# cat prometheus-etcdService.yaml
apiVersion: v1
kind: Service
metadata:
  name: etcd-k8s
  namespace: kube-system
  labels:
    k8s-app: etcd
spec:
  type: ClusterIP
  clusterIP: None
  ports:
  - name: port
    port: 2379
    protocol: TCP

---
apiVersion: v1
kind: Endpoints
metadata:
  name: etcd-k8s
  namespace: kube-system
  labels:
    k8s-app: etcd
subsets:
- addresses:
  - ip: 192.168.200.10
    nodeName: etc-master
  ports:
  - name: port
    port: 2379
    protocol: TCP

我们这里创建的 Service 没有采用前面通过 label 标签的形式去匹配 Pod 的做法,因为前面我们说过很多时候我们创建的 etcd 集群是独立于集群之外的,这种情况下面我们就需要自定义一个 Endpoints,要注意 metadata 区域的内容要和 Service 保持一致,Service 的 clusterIP 设置为 None,对改知识点不太熟悉的,可以去查看我们前面关于 Service 部分的讲解。

Endpoints 的 subsets 中填写 etcd 集群的地址即可,我们这里是单节点的,填写一个即可,直接创建该 Service 资源:

[root@node01 ~]# kubectl create -f prometheus-etcdService.yaml
service "etcd-k8s" created
endpoints "etcd-k8s" created

创建完成后,隔一会儿去 Prometheus 的 Dashboard 中查看 targets,便会有 etcd 的监控项了:

图片.png-75.8kB

可以看到还是有一个明显的错误,和我们上节课监控 kube-scheduler 的错误比较类似于,因为我们这里的 etcd 的是监听在 127.0.0.1 这个 IP 上面的,所以访问会拒绝:

--listen-client-urls=https://127.0.0.1:2379

同样我们只需要在 /etc/kubernetes/manifest/ 目录下面(static pod 默认的目录)的 etcd.yaml 文件中将上面的listen-client-urls更改成 0.0.0.0 即可:

--listen-client-urls=https://0.0.0.0:2379

重启 etcd,生效后,查看 etcd 这个监控任务就正常了:

图片.png-58.1kB

数据采集到后,可以在 grafana 中导入编号为3070的 dashboard,获取到 etcd 的监控图表。

图片.png-269.9kB

8.4 配置 PrometheusRule

现在我们知道怎么自定义一个 ServiceMonitor 对象了,但是如果需要自定义一个报警规则的话呢?比如现在我们去查看 Prometheus Dashboard 的 Alert 页面下面就已经有一些报警规则了,还有一些是已经触发规则的了:

图片.png-637.6kB

但是这些报警信息是哪里来的呢?他们应该用怎样的方式通知我们呢?我们知道之前我们使用自定义的方式可以在 Prometheus 的配置文件之中指定 AlertManager 实例和 报警的 rules 文件,现在我们通过 Operator 部署的呢?我们可以在 Prometheus Dashboard 的 Config 页面下面查看关于 AlertManager 的配置:

alerting:
  alert_relabel_configs:
  - separator: ;
    regex: prometheus_replica
    replacement: $1
    action: labeldrop
  alertmanagers:
  - kubernetes_sd_configs:
    - role: endpoints
      namespaces:
        names:
        - monitoring
    scheme: http
    path_prefix: /
    timeout: 10s
    relabel_configs:
    - source_labels: [__meta_kubernetes_service_name]
      separator: ;
      regex: alertmanager-main
      replacement: $1
      action: keep
    - source_labels: [__meta_kubernetes_endpoint_port_name]
      separator: ;
      regex: web
      replacement: $1
      action: keep
rule_files:
- /etc/prometheus/rules/prometheus-k8s-rulefiles-0/*.yaml

上面 alertmanagers 实例的配置我们可以看到是通过角色为 endpoints 的 kubernetes 的服务发现机制获取的,匹配的是服务名为 alertmanager-main,端口名未 web 的 Service 服务,我们查看下 alertmanager-main 这个 Service:

[root@node01 ~]# kubectl describe svc alertmanager-main -n monitoring
Name:                     alertmanager-main
Namespace:                monitoring
Labels:                   alertmanager=main
Annotations:              kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"alertmanager":"main"},"name":"alertmanager-main","namespace":"monitoring"},...
Selector:                 alertmanager=main,app=alertmanager
Type:                     NodePort
IP:                       10.105.151.64
Port:                     web  9093/TCP
TargetPort:               web/TCP
NodePort:                 web  31891/TCP
Endpoints:                10.244.1.10:9093,10.244.2.6:9093,10.244.2.9:9093
Session Affinity:         ClientIP
External Traffic Policy:  Cluster
Events:                   <none>

可以看到服务名正是 alertmanager-main,Port 定义的名称也是 web,符合上面的规则,所以 Prometheus 和 AlertManager 组件就正确关联上了。而对应的报警规则文件位于:/etc/prometheus/rules/prometheus-k8s-rulefiles-0/目录下面所有的 YAML 文件。我们可以进入 Prometheus 的 Pod 中验证下该目录下面是否有 YAML 文件:

[root@node01 ~]# kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/prometheus $ ls /etc/prometheus/rules/prometheus-k8s-rulefiles-0/
monitoring-prometheus-k8s-rules.yaml
/prometheus $ cat /etc/prometheus/rules/prometheus-k8s-rulefiles-0/monitoring-prometheus-k8s-rules.yaml
groups:
- name: k8s.rules
  rules:
  - expr: |
      sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container!="POD"}[5m])) by (namespace)
    record: namespace:container_cpu_usage_seconds_total:sum_rate
......

这个 YAML 文件实际上就是我们之前创建的一个 PrometheusRule 文件包含的:

[root@node01 ~]# vim prometheus-rules.yaml
[root@node01 ~]# cat prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: prometheus-k8s-rules
  namespace: monitoring
spec:
  groups:
  - name: k8s.rules
    rules:
    - expr: |
        sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])) by (namespace)
      record: namespace:container_cpu_usage_seconds_total:sum_rate

我们这里的 PrometheusRule 的 name 为 prometheus-k8s-rules,namespace 为 monitoring,我们可以猜想到我们创建一个 PrometheusRule 资源对象后,会自动在上面的 prometheus-k8s-rulefiles-0 目录下面生成一个对应的<namespace>-<name>.yaml文件,所以如果以后我们需要自定义一个报警选项的话,只需要定义一个 PrometheusRule 资源对象即可。至于为什么 Prometheus 能够识别这个 PrometheusRule 资源对象呢?这就需要查看我们创建的 prometheus 这个资源对象了,里面有非常重要的一个属性 ruleSelector,用来匹配 rule 规则的过滤器,要求匹配具有 prometheus=k8s 和 role=alert-rules 标签的 PrometheusRule 资源对象,现在明白了吧?

ruleSelector:
  matchLabels:
    prometheus: k8s
    role: alert-rules

所以我们要想自定义一个报警规则,只需要创建一个具有 prometheus=k8s 和 role=alert-rules 标签的 PrometheusRule 对象就行了,比如现在我们添加一个 etcd 是否可用的报警,我们知道 etcd 整个集群有一半以上的节点可用的话集群就是可用的,所以我们判断如果不可用的 etcd 数量超过了一半那么就触发报警,创建文件 prometheus-etcdRules.yaml:

[root@node01 ~]# vim prometheus-etcdRules.yaml
[root@node01 ~]# cat prometheus-etcdRules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: etcd-rules
  namespace: monitoring
spec:
  groups:
  - name: etcd
    rules:
    - alert: EtcdClusterUnavailable
      annotations:
        summary: etcd cluster small
        description: If one more etcd peer goes down the cluster will be unavailable
      expr: |
        count(up{job="etcd"} == 0) > (count(up{job="etcd"}) / 2 - 1)
      for: 3m
      labels:
        severity: critical
        
[root@node01 ~]# kubectl create -f prometheus-etcdRules.yaml
prometheusrule.monitoring.coreos.com "etcd-rules" created

注意 label 标签一定至少要有 prometheus=k8s 和 role=alert-rules,创建完成后,隔一会儿再去容器中查看下 rules 文件夹:

[root@node01 ~]# kubectl exec -it prometheus-k8s-0 /bin/sh -n monitoring
Defaulting container name to prometheus.
Use 'kubectl describe pod/prometheus-k8s-0 -n monitoring' to see all of the containers in this pod.
/prometheus $ ls /etc/prometheus/rules/prometheus-k8s-rulefiles-0/
monitoring-etcd-rules.yaml            monitoring-prometheus-k8s-rules.yaml

可以看到我们创建的 rule 文件已经被注入到了对应的 rulefiles 文件夹下面了,证明我们上面的设想是正确的。然后再去 Prometheus Dashboard 的 Alert 页面下面就可以查看到上面我们新建的报警规则了:

图片.png-60.2kB

8.5 配置报警

我们知道了如何去添加一个报警规则配置项,但是这些报警信息用怎样的方式去发送呢?前面的课程中我们知道我们可以通过 AlertManager 的配置文件去配置各种报警接收器,现在我们是通过 Operator 提供的 alertmanager 资源对象创建的组件,应该怎样去修改配置呢?

首先我们将 alertmanager-main 这个 Service 改为 NodePort 类型的 Service,修改完成后我们可以在页面上的 status 路径下面查看 AlertManager 的配置信息:

图片.png-368kB

这些配置信息实际上是来自于我们之前在prometheus-operator/contrib/kube-prometheus/manifests目录下面创建的 alertmanager-secret.yaml 文件:

apiVersion: v1
data:
  alertmanager.yaml: Imdsb2JhbCI6IAogICJyZXNvbHZlX3RpbWVvdXQiOiAiNW0iCiJyZWNlaXZlcnMiOiAKLSAibmFtZSI6ICJudWxsIgoicm91dGUiOiAKICAiZ3JvdXBfYnkiOiAKICAtICJqb2IiCiAgImdyb3VwX2ludGVydmFsIjogIjVtIgogICJncm91cF93YWl0IjogIjMwcyIKICAicmVjZWl2ZXIiOiAibnVsbCIKICAicmVwZWF0X2ludGVydmFsIjogIjEyaCIKICAicm91dGVzIjogCiAgLSAibWF0Y2giOiAKICAgICAgImFsZXJ0bmFtZSI6ICJEZWFkTWFuc1N3aXRjaCIKICAgICJyZWNlaXZlciI6ICJudWxsIg==
kind: Secret
metadata:
  name: alertmanager-main
  namespace: monitoring
type: Opaque

可以将 alertmanager.yaml 对应的 value 值做一个 base64 解码:

[root@node01 ~]# echo "Imdsb2JhbCI6IAogICJyZXNvbHZlX3RpbWVvdXQiOiAiNW0iCiJyZWNlaXZlcnMiOiAKLSAibmFtZSI6ICJudWxsIgoicm91dGUiOiAKICAiZ3JvdXBfYnkiOiAKICAtICJqb2IiCiAgImdyb3VwX2ludGVydmFsIjogIjVtIgogICJncm91cF93YWl0IjogIjMwcyIKICAicmVjZWl2ZXIiOiAibnVsbCIKICAicmVwZWF0X2ludGVydmFsIjogIjEyaCIKICAicm91dGVzIjogCiAgLSAibWF0Y2giOiAKICAgICAgImFsZXJ0bmFtZSI6ICJEZWFkTWFuc1N3aXRjaCIKICAgICJyZWNlaXZlciI6ICJudWxsIg==" | base64 -d
"global": 
  "resolve_timeout": "5m"
"receivers": 
- "name": "null"
"route": 
  "group_by": 
  - "job"
  "group_interval": "5m"
  "group_wait": "30s"
  "receiver": "null"
  "repeat_interval": "12h"
  "routes": 
  - "match": 
      "alertname": "DeadMansSwitch"
    "receiver": "null"

我们可以看到内容和上面查看的配置信息是一致的,所以如果我们想要添加自己的接收器,或者模板消息,我们就可以更改这个文件:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.163.com:25'
  smtp_from: 'ych_1024@163.com'
  smtp_auth_username: 'ych_1024@163.com'
  smtp_auth_password: '<邮箱密码>'
  smtp_hello: '163.com'
  smtp_require_tls: false
route:
  group_by: ['job', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  receiver: default
  routes:
  - receiver: webhook
    match:
      alertname: CoreDNSDown
receivers:
- name: 'default'
  email_configs:
  - to: '517554016@qq.com'
    send_resolved: true
- name: 'webhook'
  webhook_configs:
  - url: 'http://dingtalk-hook.kube-ops:5000'
    send_resolved: true

将上面文件保存为 alertmanager.yaml,然后使用这个文件创建一个 Secret 对象:

# 先将之前的 secret 对象删除
$ kubectl delete secret alertmanager-main -n monitoring
secret "alertmanager-main" deleted
$ kubectl create secret generic alertmanager-main --from-file=alertmanager.yaml -n monitoring
secret "alertmanager-main" created

我们添加了两个接收器,默认的通过邮箱进行发送,对于 CoreDNSDown 这个报警我们通过 webhook 来进行发送,这个 webhook 就是我们前面课程中定义的一个钉钉接收的 Server,上面的步骤创建完成后,很快我们就会收到一条钉钉消息:

图片.png-677.5kB

同样邮箱中也会收到报警信息:

图片.png-295.9kB

我们再次查看 AlertManager 页面的 status 页面的配置信息可以看到已经变成上面我们的配置信息了:

图片.png-333kB

AlertManager 配置也可以使用模板(.tmpl文件),这些模板可以与 alertmanager.yaml 配置文件一起添加到 Secret 对象中,比如:

apiVersion:v1
kind:secret
metadata:
   name:alertmanager-example
data:
  alertmanager.yaml:{BASE64_CONFIG}
  template_1.tmpl:{BASE64_TEMPLATE_1}
  template_2.tmpl:{BASE64_TEMPLATE_2}
  ...

模板会被放置到与配置文件相同的路径,当然要使用这些模板文件,还需要在 alertmanager.yaml 配置文件中指定:

templates:
- '*.tmpl'

创建成功后,Secret 对象将会挂载到 AlertManager 对象创建的 AlertManager Pod 中去。

9. Prometheus Operator 高级配置

上节课我们一起学习了如何在 Prometheus Operator 下面自定义一个监控选项,以及自定义报警规则的使用。那么我们还能够直接使用前面课程中的自动发现功能吗?如果在我们的 Kubernetes 集群中有了很多的 Service/Pod,那么我们都需要一个一个的去建立一个对应的 ServiceMonitor 对象来进行监控吗?这样岂不是又变得麻烦起来了?

9.1 自动发现配置

为解决上面的问题,Prometheus Operator 为我们提供了一个额外的抓取配置的来解决这个问题,我们可以通过添加额外的配置来进行服务发现进行自动监控。和前面自定义的方式一样,我们想要在 Prometheus Operator 当中去自动发现并监控具有prometheus.io/scrape=true这个 annotations 的 Service,之前我们定义的 Prometheus 的配置如下:

[root@node01 ~]# vim prometheus-additional.yaml
[root@node01 ~]# cat prometheus-additional.yaml
- job_name: 'kubernetes-service-endpoints'
  kubernetes_sd_configs:
  - role: endpoints
  relabel_configs:
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
    action: keep
    regex: true
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
    action: replace
    target_label: __scheme__
    regex: (https?)
  - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
    action: replace
    target_label: __metrics_path__
    regex: (.+)
  - source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
    action: replace
    target_label: __address__
    regex: ([^:]+)(?::\d+)?;(\d+)
    replacement: $1:$2
  - action: labelmap
    regex: __meta_kubernetes_service_label_(.+)
  - source_labels: [__meta_kubernetes_namespace]
    action: replace
    target_label: kubernetes_namespace
  - source_labels: [__meta_kubernetes_service_name]
    action: replace
    target_label: kubernetes_name

如果你对上面这个配置还不是很熟悉的话,建议去查看下前面关于 Kubernetes常用资源对象监控章节的介绍,要想自动发现集群中的 Service,就需要我们在 Service 的annotation区域添加prometheus.io/scrape=true的声明,将上面文件直接保存为 prometheus-additional.yaml,然后通过这个文件创建一个对应的 Secret 对象:

[root@node01 ~]# kubectl create secret generic additional-configs --from-file=prometheus-additional.yaml -n monitoring
secret "additional-configs" created

注意我们所有的操作都在 Prometheus Operator 源码contrib/kube-prometheus/manifests/目录下面。

创建完成后,会将上面配置信息进行 base64 编码后作为 prometheus-additional.yaml 这个 key 对应的值存在:

[root@node01 ~]# kubectl get secret additional-configs -n monitoring -o yaml
apiVersion: v1
data:
  prometheus-additional.yaml: LSBqb2JfbmFtZTogJ2t1YmVybmV0ZXMtc2VydmljZS1lbmRwb2ludHMnCiAga3ViZXJuZXRlc19zZF9jb25maWdzOgogIC0gcm9sZTogZW5kcG9pbnRzCiAgcmVsYWJlbF9jb25maWdzOgogIC0gc291cmNlX2xhYmVsczogW19fbWV0YV9rdWJlcm5ldGVzX3NlcnZpY2VfYW5ub3RhdGlvbl9wcm9tZXRoZXVzX2lvX3NjcmFwZV0KICAgIGFjdGlvbjoga2VlcAogICAgcmVnZXg6IHRydWUKICAtIHNvdXJjZV9sYWJlbHM6IFtfX21ldGFfa3ViZXJuZXRlc19zZXJ2aWNlX2Fubm90YXRpb25fcHJvbWV0aGV1c19pb19zY2hlbWVdCiAgICBhY3Rpb246IHJlcGxhY2UKICAgIHRhcmdldF9sYWJlbDogX19zY2hlbWVfXwogICAgcmVnZXg6IChodHRwcz8pCiAgLSBzb3VyY2VfbGFiZWxzOiBbX19tZXRhX2t1YmVybmV0ZXNfc2VydmljZV9hbm5vdGF0aW9uX3Byb21ldGhldXNfaW9fcGF0aF0KICAgIGFjdGlvbjogcmVwbGFjZQogICAgdGFyZ2V0X2xhYmVsOiBfX21ldHJpY3NfcGF0aF9fCiAgICByZWdleDogKC4rKQogIC0gc291cmNlX2xhYmVsczogW19fYWRkcmVzc19fLCBfX21ldGFfa3ViZXJuZXRlc19zZXJ2aWNlX2Fubm90YXRpb25fcHJvbWV0aGV1c19pb19wb3J0XQogICAgYWN0aW9uOiByZXBsYWNlCiAgICB0YXJnZXRfbGFiZWw6IF9fYWRkcmVzc19fCiAgICByZWdleDogKFteOl0rKSg/OjpcZCspPzsoXGQrKQogICAgcmVwbGFjZW1lbnQ6ICQxOiQyCiAgLSBhY3Rpb246IGxhYmVsbWFwCiAgICByZWdleDogX19tZXRhX2t1YmVybmV0ZXNfc2VydmljZV9sYWJlbF8oLispCiAgLSBzb3VyY2VfbGFiZWxzOiBbX19tZXRhX2t1YmVybmV0ZXNfbmFtZXNwYWNlXQogICAgYWN0aW9uOiByZXBsYWNlCiAgICB0YXJnZXRfbGFiZWw6IGt1YmVybmV0ZXNfbmFtZXNwYWNlCiAgLSBzb3VyY2VfbGFiZWxzOiBbX19tZXRhX2t1YmVybmV0ZXNfc2VydmljZV9uYW1lXQogICAgYWN0aW9uOiByZXBsYWNlCiAgICB0YXJnZXRfbGFiZWw6IGt1YmVybmV0ZXNfbmFtZQo=
kind: Secret
metadata:
  creationTimestamp: 2022-01-03T05:43:41Z
  name: additional-configs
  namespace: monitoring
  resourceVersion: "256854"
  selfLink: /api/v1/namespaces/monitoring/secrets/additional-configs
  uid: 1b5a0975-6c58-11ec-a485-000c2927597c
type: Opaque

然后我们只需要在声明 prometheus 的资源对象文件中添加上这个额外的配置:(prometheus-prometheus.yaml)

[root@node01 ~]# vim prometheus-prometheus.yaml
[root@node01 ~]# cat prometheus-prometheus.yaml
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  labels:
    prometheus: k8s
  name: k8s
  namespace: monitoring
spec:
  alerting:
    alertmanagers:
    - name: alertmanager-main
      namespace: monitoring
      port: web
  baseImage: quay.io/prometheus/prometheus
  nodeSelector:
    beta.kubernetes.io/os: linux
  replicas: 2
  secrets:
  - etcd-certs
  resources:
    requests:
      memory: 400Mi
  ruleSelector:
    matchLabels:
      prometheus: k8s
      role: alert-rules
  securityContext:
    fsGroup: 2000
    runAsNonRoot: true
    runAsUser: 1000
  additionalScrapeConfigs:
    name: additional-configs
    key: prometheus-additional.yaml
  serviceAccountName: prometheus-k8s
  serviceMonitorNamespaceSelector: {}
  serviceMonitorSelector: {}
  version: v2.5.0

添加完成后,直接更新 prometheus 这个 CRD 资源对象:

[root@node01 ~]# kubectl apply -f prometheus-prometheus.yaml
prometheus.monitoring.coreos.com "k8s" configured

隔一小会儿,可以前往 Prometheus 的 Dashboard 中查看配置是否生效:

图片.png-175.6kB

在 Prometheus Dashboard 的配置页面下面我们可以看到已经有了对应的的配置信息了,但是我们切换到 targets 页面下面却并没有发现对应的监控任务,查看 Prometheus 的 Pod 日志:

[root@node01 ~]# kubectl logs -f prometheus-k8s-0 prometheus -n monitoring
......
level=error ts=2022-01-03T05:55:30.904Z caller=klog.go:94 component=k8s_client_runtime func=ErrorDepth msg="/app/discovery/kubernetes/kubernetes.go:263: Failed to list *v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list endpoints at the cluster scope"
level=error ts=2022-01-03T05:55:30.907Z caller=klog.go:94 component=k8s_client_runtime func=ErrorDepth msg="/app/discovery/kubernetes/kubernetes.go:265: Failed to list *v1.Pod: pods is forbidden: User \"system:serviceaccount:monitoring:prometheus-k8s\" cannot list pods at the cluster scope"
......

可以看到有很多错误日志出现,都是xxx is forbidden,这说明是 RBAC 权限的问题,通过 prometheus 资源对象的配置可以知道 Prometheus 绑定了一个名为 prometheus-k8s 的 ServiceAccount 对象,而这个对象绑定的是一个名为 prometheus-k8s 的 ClusterRole:(prometheus-clusterRole.yaml)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus-k8s
rules:
- apiGroups:
  - ""
  resources:
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get

上面的权限规则中我们可以看到明显没有对 Service 或者 Pod 的 list 权限,所以报错了,要解决这个问题,我们只需要添加上需要的权限即可:

[root@node01 ~]# vim prometheus-clusterRole.yaml
[root@node01 ~]# cat prometheus-clusterRole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus-k8s
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  - services
  - endpoints
  - pods
  - nodes/proxy
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - configmaps
  - nodes/metrics
  verbs:
  - get
- nonResourceURLs:
  - /metrics
  verbs:
  - get
[root@node01 ~]# kubectl apply -f prometheus-clusterRole.yaml 
clusterrole.rbac.authorization.k8s.io "prometheus-k8s" configured

更新上面的 ClusterRole 这个资源对象,然后重建下 Prometheus 的所有 Pod,正常就可以看到 targets 页面下面有 kubernetes-service-endpoints 这个监控任务了:

图片.png-373.3kB

我们这里自动监控了两个 Service,第一个就是我们之前创建的 Redis 的服务,我们在 Redis Service 中有两个特殊的 annotations:

annotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "9121"

所以被自动发现了,当然我们也可以用同样的方式去配置 Pod、Ingress 这些资源对象的自动发现。

9.2 数据持久化

上面我们在修改完权限的时候,重启了 Prometheus 的 Pod,如果我们仔细观察的话会发现我们之前采集的数据已经没有了,这是因为我们通过 prometheus 这个 CRD 创建的 Prometheus 并没有做数据的持久化.

首先部署NFS-Server

#安装nfs
[root@node01 ~]# yum install nfs-utils -y rpcbind

#接下来设置nfs存储目录
[root@node01 ~]# mkdir /data1/k8s-volume -p
[root@node01 ~]# chmod 755 /data1/k8s-volume/
 
#编辑nfs配置文件
[root@node01 ~]# vim /etc/exports
[root@node01 ~]# cat /etc/exports
/data1/k8s-volume  *(rw,no_root_squash,sync)
#存储目录,*允许所有人连接,rw读写权限,sync文件同时写入硬盘及内存,no_root_squash 使用者root用户自动修改为普通用户
 
接下来启动rpcbind
[root@node01 ~]# systemctl start rpcbind
[root@node01 ~]# systemctl enable rpcbind

#启动NFS
[root@node01 ~]# systemctl restart nfs
[root@node01 ~]# systemctl enable nfs

#检查rpcbind及nfs是否正常
[root@node01 ~]# rpcinfo |grep nfs
    100003    3    tcp       0.0.0.0.8.1            nfs        superuser
    100003    4    tcp       0.0.0.0.8.1            nfs        superuser
    100227    3    tcp       0.0.0.0.8.1            nfs_acl    superuser
    100003    3    udp       0.0.0.0.8.1            nfs        superuser
    100003    4    udp       0.0.0.0.8.1            nfs        superuser
    100227    3    udp       0.0.0.0.8.1            nfs_acl    superuser
    100003    3    tcp6      ::.8.1                 nfs        superuser
    100003    4    tcp6      ::.8.1                 nfs        superuser
    100227    3    tcp6      ::.8.1                 nfs_acl    superuser
    100003    3    udp6      ::.8.1                 nfs        superuser
    100003    4    udp6      ::.8.1                 nfs        superuser
    100227    3    udp6      ::.8.1                 nfs_acl    superuser
 
#查看nfs目录挂载权限
[root@node01 ~]# cat /var/lib/nfs/etab
/data1/k8s-volume	*(rw,sync,wdelay,hide,nocrossmnt,secure,no_root_squash,no_all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,rw,secure,no_root_squash,no_all_squash)

NFS安装完毕后我们可以看一下prometheus operator数据存储的目录:

[root@node01 ~]# kubectl get pod prometheus-k8s-0 -n monitoring -o yaml
......
    volumeMounts:
    - mountPath: /etc/prometheus/config_out
      name: config-out
      readOnly: true
    - mountPath: /prometheus
      name: prometheus-k8s-db
......
  volumes:
......
  - emptyDir: {}
    name: prometheus-k8s-db
......

这里/prometheus目录使用的是emptyDir进行挂载,我们重建Pod之后之前的数据就没有了,由于我们的Prometheus使用Statefulset控制器进行部署的,为了保证数据一致性,这里采用storageclass来做持久化

因为我们要使用NFS作为后端存储,这里需要一个nfs-client

#现在还需要创建NFS-Client,不然prometheus pod现在是无法Running状态
[root@node01 ~]# vim nfs-client.yaml
[root@node01 ~]# cat nfs-client.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nfs-client-provisioner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: quay.io/external_storage/nfs-client-provisioner:latest
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: fuseim.pri/ifs
            - name: NFS_SERVER
              value: 192.168.200.11           #nfs server 地址
            - name: NFS_PATH
              value: /data1/k8s-volume     #nfs共享目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.200.11
            path: /data1/k8s-volume

创建nfs-client rbac文件

[root@node01 ~]# vim nfs-rbac.yaml
[root@node01 ~]# cat nfs-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io

创建

[root@node01 ~]# kubectl apply -f nfs-rbac.yaml
serviceaccount "nfs-client-provisioner" created
clusterrole.rbac.authorization.k8s.io "nfs-client-provisioner-runner" created
clusterrolebinding.rbac.authorization.k8s.io "run-nfs-client-provisioner" created
[root@node01 ~]# kubectl apply -f nfs-client.yaml
deployment.apps "nfs-client-provisioner" created
 
[root@node01 ~]# kubectl get pod
NAME                                      READY     STATUS    RESTARTS   AGE
......
nfs-client-provisioner-5c7bd65c55-58vgs   1/1       Running   0          40s

我们可以看到 Prometheus 的数据目录 /prometheus 实际上是通过 emptyDir 进行挂载的,我们知道 emptyDir 挂载的数据的生命周期和 Pod 生命周期一致的,所以如果 Pod 挂掉了,数据也就丢失了,这也就是为什么我们重建 Pod 后之前的数据就没有了的原因,对应线上的监控数据肯定需要做数据的持久化的,同样的 prometheus 这个 CRD 资源也为我们提供了数据持久化的配置方法,由于我们的 Prometheus 最终是通过 Statefulset 控制器进行部署的,所以我们这里需要通过 storageclass 来做数据持久化,首先创建一个 StorageClass 对象:

[root@node01 ~]# vim prometheus-storageclass.yaml
[root@node01 ~]# cat prometheus-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: prometheus-data-db
provisioner: fuseim.pri/ifs

这里我们声明一个 StorageClass 对象,其中 provisioner=fuseim.pri/ifs,则是因为我们集群中使用的是 nfs 作为存储后端,而前面我们课程中创建的 nfs-client-provisioner 中指定的 PROVISIONER_NAME 就为 fuseim.pri/ifs,这个名字不能随便更改,将该文件保存为 prometheus-storageclass.yaml:

[root@node01 ~]# kubectl create -f prometheus-storageclass.yaml
storageclass.storage.k8s.io "prometheus-data-db" created

然后在 prometheus 的 CRD 资源对象中添加如下配置:

storage:
  volumeClaimTemplate:
    spec:
      storageClassName: prometheus-data-db
      resources:
        requests:
          storage: 10Gi

注意这里的 storageClassName 名字为上面我们创建的 StorageClass 对象名称,然后更新 prometheus 这个 CRD 资源。更新完成后会自动生成两个 PVC 和 PV 资源对象:

[root@node01 ~]# kubectl get pvc -n monitoring
NAME                                 STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS         AGE
prometheus-k8s-db-prometheus-k8s-0   Bound     pvc-0db8fa30-6c8a-11ec-ac0e-000c2927597c   10Gi       RWO            prometheus-data-db   3h
prometheus-k8s-db-prometheus-k8s-1   Bound     pvc-0dbb82bc-6c8a-11ec-ac0e-000c2927597c   10Gi       RWO            prometheus-data-db   3h

[root@node01 ~]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM                                           STORAGECLASS         REASON    AGE
pvc-0db8fa30-6c8a-11ec-ac0e-000c2927597c   10Gi       RWO            Delete           Bound     monitoring/prometheus-k8s-db-prometheus-k8s-0   prometheus-data-db             5m
pvc-0dbb82bc-6c8a-11ec-ac0e-000c2927597c   10Gi       RWO            Delete           Bound     monitoring/prometheus-k8s-db-prometheus-k8s-1   prometheus-data-db             5m

现在我们再去看 Prometheus Pod 的数据目录就可以看到是关联到一个 PVC 对象上了。

[root@node01 ~]# kubectl get pod prometheus-k8s-0 -n monitoring -o yaml
......
    volumeMounts:
    - mountPath: /etc/prometheus/config_out
      name: config-out
      readOnly: true
    - mountPath: /prometheus
      name: prometheus-k8s-db
......
  volumes:
......
  - name: prometheus-k8s-db
    persistentVolumeClaim:
      claimName: prometheus-k8s-db-prometheus-k8s-0
......

现在即使我们的 Pod 挂掉了,数据也不会丢失了,最后,下面是我们 Prometheus Operator 系列课程中最终的创建资源清单文件,更多的信息可以在https://github.com/cnych/kubernetes-learning 下面查看。

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  labels:
    prometheus: k8s
  name: k8s
  namespace: monitoring
spec:
  alerting:
    alertmanagers:
    - name: alertmanager-main
      namespace: monitoring
      port: web
  storage:
    volumeClaimTemplate:
      spec:
        storageClassName: prometheus-data-db
        resources:
          requests:
            storage: 10Gi
  baseImage: quay.io/prometheus/prometheus
  nodeSelector:
    beta.kubernetes.io/os: linux
  replicas: 2
  secrets:
  - etcd-certs
  additionalScrapeConfigs:
    name: additional-configs
    key: prometheus-additional.yaml
  resources:
    requests:
      memory: 400Mi
  ruleSelector:
    matchLabels:
      prometheus: k8s
      role: alert-rules
  securityContext:
    fsGroup: 2000
    runAsNonRoot: true
    runAsUser: 1000
  serviceAccountName: prometheus-k8s
  serviceMonitorNamespaceSelector: {}
  serviceMonitorSelector: {}
  version: v2.5.0