Skip to content

Prometheus

Prometheus

目录

[toc]

Prometheus基础

1.简介

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

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

2.特点

Prometheus 最初是 SoundCloud构建的开源系统监控和报警工具,是一个独立的开源项目,于2016年加入了 CNCF 基金会,作为继 Kubernetes 之后的第二个托管项目。Prometheus 相比于其他传统监控工具主要有以下几个特点:

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

3.架构

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

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

大多数 Prometheus 组件都是用 Go 编写的,因此很容易构建和部署为静态的二进制文件。下图是 Prometheus 官方提供的架构及其一些相关的生态系统组件:

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

注意:prometheus的数据可以也可以存储在ceph里,但不能存储在nfs里。测试环境,你可以用nfs,prometheus是可以跑起来的。但生产环境一定不能用nfs,否则重建之后,以前的历史数据可其他能会被损坏!

💘 实战:prometheus部署(测试成功)-2022.4.29

实验环境

bash
centos7prometheus-2.35.0

实验软件

链接:https:-rw-rw-rw-1rootroot77MApr2812:38prometheus-2.35.0.linux-amd64.tar.gz[root@master1 ~]#tar -xf prometheus-2.35.0.linux-amd64.tar.gz [root@master1 ~]#cd prometheus-2.35.0.linux-amd64/[root@master1 prometheus-2.35.0.linux-amd64]#lltotal198236drwxr-xr-x23434343438Apr2117:59console_libraries#2个与prometheus dashboard相关的drwxr-xr-x234343434173Apr2117:59consoles-rw-r--r--13434343411357Apr2117:59LICENSE-rw-r--r--1343434343773Apr2117:59NOTICE-rwxr-xr-x134343434105576684Apr2117:55prometheus#二进制文件-rw-r--r--134343434934Apr2117:59prometheus.yml-rwxr-xr-x13434343497394322Apr2117:58promtool#校验相关的

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

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

上面这个配置文件中包含了3个模块:globalrule_filesscrape_configs

由于 prometheus 通过 HTTP 的方式来暴露的它本身的监控数据,prometheus 也能够监控本身的健康情况。在默认的配置里有一个单独的 job,叫做 prometheus,它采集 prometheus 服务本身的时间序列数据。这个 job 包含了一个单独的、静态配置的目标:监听 localhost 上的 9090 端口。prometheus 默认会通过目标的 /metrics路径采集 metrics。所以,默认的 job 通过 URL:http:prometheus:prom/prometheus:v2.34.0

实验软件

链接:https:namespace/monitorcreated

2.将 prometheus.yml文件用 ConfigMap 的形式进行管理

本次实验目录如下:

bash
[root@master1 ~]#mkdir prometheus-example[root@master1 ~]#cd prometheus-example/
  • 为了能够方便的管理配置文件,我们这里将 prometheus.yml文件用 ConfigMap 的形式进行管理:

[root@master1 prometheus-example]#vim prometheus-cm.yaml

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']

注意:

3.创建prometheus 的 Pod 资源

现在我们来创建 prometheus 的 Pod 资源:

[root@master1 prometheus-example]#vim prometheus-deploy.yaml

yaml
# prometheus-deploy.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:prometheusnamespace:monitorlabels:app:prometheusspec:selector:matchLabels:app:prometheustemplate:metadata:labels:app:prometheusspec:serviceAccountName:prometheuscontainers:- image:prom/prometheus:v2.34.0name:prometheusargs:- "--config.file=/etc/prometheus/prometheus.yml"- "--storage.tsdb.path=/prometheus"# 指定tsdb数据路径- "--storage.tsdb.retention.time=24h"#保存时间- "--web.enable-admin-api"# 控制对admin HTTP API的访问,其中包括删除时间序列等功能- "--web.enable-lifecycle"# 支持热更新,直接执行localhost:9090/-/reload立即生效ports:- containerPort:9090name:httpvolumeMounts:- mountPath:"/etc/prometheus"name:config-volume- mountPath:"/prometheus"name:dataresources:requests:cpu:100mmemory:512Milimits:cpu:100mmemory:512Mivolumes:- name:data#tsdb数据路径:/prometheus,这里使用的是pvcpersistentVolumeClaim:claimName:prometheus-data- configMap:#name:prometheus-configname:config-volume
yaml
……#上面突然写成那种我还看不懂了哈哈……老铁,没毛病!volumes:#这里要再次熟悉下关于volumes/volumeMounts数据卷挂载的使用方法!!!- name:data#tsdb数据路径:/prometheus,这里使用的是pvcpersistentVolumeClaim:claimName:prometheus-data- name:config-volumeconfigMap:#name:prometheus-config

另外为了 prometheus 的性能和数据持久化我们这里是直接将通过一个 LocalPV 来进行数据持久化的,通过 --storage.tsdb.path=/prometheus指定数据目录。

4.创建prometheus的pv/pvc资源

创建如下所示的一个 PVC 资源对象,注意是一个 LocalPV,和 node1 节点具有亲和性:

  • 先在宿主机node1节点上上创建这个下面LocalPV要使用到的本地目录:
bash
[root@master1 ~]#ssh node1Lastlogin:FriApr2909:57:062022frommaster1[root@node1 ~]#mkdir -p /data/k8s/prometheus
  • 创建prometheus-pvc.yaml文件:

[root@master1 prometheus-example]#vim prometheus-pvc.yaml

yaml
#prometheus-pvc.yamlapiVersion:v1kind:PersistentVolumemetadata:name:prometheus-locallabels:app:prometheusspec:accessModes:- ReadWriteOncecapacity:storage:20GistorageClassName:local-storagelocal:path:/data/k8s/prometheus#一定要先在宿主机上创建这个目录!!!nodeAffinity:#pv也是可以配置节点亲和性的哦!!!required:nodeSelectorTerms:- matchExpressions:- key:kubernetes.io/hostnameoperator:Invalues:- node1#这里是自己的node1节点persistentVolumeReclaimPolicy:Retain---apiVersion:v1kind:PersistentVolumeClaimmetadata:name:prometheus-datanamespace:monitorspec:selector:matchLabels:app:prometheusaccessModes:- ReadWriteOnceresources:requests:storage:20GistorageClassName:local-storage

5.创建prometheus的rbac资源

由于 prometheus 可以访问 Kubernetes 的一些资源对象,所以需要配置 rbac 相关认证,这里我们使用了一个名为 prometheus 的 serviceAccount 对象:

[root@master1 prometheus-example]#vim prometheus-rbac.yaml

yaml
# prometheus-rbac.yamlapiVersion:v1kind:ServiceAccount#创建一个ServiceAccountmetadata:name:prometheusnamespace:monitor---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRolemetadata:name:prometheusrules:- apiGroups:- ""resources:- nodes- services- endpoints- pods- nodes/proxyverbs:- get- list- watch- apiGroups:- "extensions"resources:- ingressesverbs:- get- list- watch- apiGroups:- ""resources:- configmaps- nodes/metricsverbs:- get- nonResourceURLs:- /metricsverbs:- get---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRoleBindingmetadata:name:prometheusroleRef:apiGroup:rbac.authorization.k8s.iokind:ClusterRolename:prometheussubjects:- kind:ServiceAccountname:prometheusnamespace:monitor

由于我们要获取的资源信息,在每一个 namespace 下面都有可能存在,所以我们这里使用的是 ClusterRole的资源对象。

值得一提的是我们这里的权限规则声明中有一个 nonResourceURLs的属性,是用来对非资源型 metrics 进行操作的权限声明,这个在以前我们很少遇到过。

6.创建以上资源

  • 创建prometheus.yml

我们这里暂时只配置了对 prometheus本身的监控,直接创建该资源对象:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yamlconfigmap/prometheus-configcreated

配置文件创建完成了,以后如果我们有新的资源需要被监控,我们只需要将上面的 ConfigMap 对象更新即可。

  • 创建prometheus-pv-pvc.yaml
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-pvc.yaml persistentvolume/prometheus-localcreatedpersistentvolumeclaim/prometheus-datacreated#验证[root@master1 prometheus-example]#kubectl get pvc -nmonitorNAMESTATUSVOLUMECAPACITYACCESSMODESSTORAGECLASSAGEprometheus-dataBoundprometheus-local20GiRWOlocal-storage33s[root@master1 prometheus-example]#kubectl get pvNAMECAPACITYACCESSMODESRECLAIMPOLICYSTATUSCLAIMSTORAGECLASSREASONAGEprometheus-local20GiRWORetainBoundmonitor/prometheus-datalocal-storage35s

  • 创建prometheus-rbac.yaml
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-rbac.yamlserviceaccount"prometheus"createdclusterrole.rbac.authorization.k8s.io"prometheus"createdclusterrolebinding.rbac.authorization.k8s.io"prometheus"created
  • 现在我们就可以添加 promethues 的资源对象了:
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-deploy.yaml deployment.apps/prometheuscreated[root@master1 prometheus-example]#kubectl get pods -n monitorNAMEREADYSTATUSRESTARTSAGEprometheus-58f59fd485-7hncv0/1CrashLoopBackOff2(24s ago) 112s[root@master1 prometheus-example]#kubectl logs prometheus-58f59fd485-7hncv -nmonitorts=2022-04-29T04:42:09.982Zcaller=main.go:516level=infomsg="Starting Prometheus"version="(version=2.34.0,branch=HEAD,revision=881111fec4332c33094a6fb2680c71fffc427275)"ts=2022-04-29T04:42:09.982Zcaller=main.go:521level=infobuild_context="(go=go1.17.8,user=root@121ad7ea5487,date=20220315-15:18:00)"ts=2022-04-29T04:42:09.982Zcaller=main.go:522level=infohost_details="(Linux 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 prometheus-58f59fd485-7hncv (none))"ts=2022-04-29T04:42:09.982Zcaller=main.go:523level=infofd_limits="(soft=65536,hard=65536)"ts=2022-04-29T04:42:09.982Zcaller=main.go:524level=infovm_limits="(soft=unlimited,hard=unlimited)"ts=2022-04-29T04:42:09.983Zcaller=query_logger.go:90level=errorcomponent=activeQueryTrackermsg="Error opening query log file"file=/prometheus/queries.activeerr="open /prometheus/queries.active:permission denied"panic:Unabletocreatemmap-edactivequeryloggoroutine1[running]:github.com/prometheus/prometheus/promql.NewActiveQueryTracker({0x7ffe09113e00,0xb},0x14,{0x3637a40,0xc0002032c0})/app/promql/query_logger.go:120+0x3d7main.main()/app/cmd/prometheus/main.go:569+0x6049

创建 Pod 后,我们可以看到并没有成功运行,出现了 open /prometheus/queries.active:permission denied这样的错误信息,这是因为我们的 prometheus 的镜像中是使用的 nobody 这个用户,然后现在我们通过 LocalPV 挂载到宿主机上面的目录的 ownership却是 root

bash
[root@node1 ~]#ls -la /data/k8stotal0drwxr-xr-x3rootroot24Apr2909:57.drwxr-xr-x3rootroot17Apr2909:57..drwxr-xr-x2rootroot6Apr2909:57prometheus

所以当然会出现操作权限问题了,这个时候我们就可以通过 securityContext来为 Pod 设置下 volumes 的权限,通过设置 runAsUser=0指定运行的用户为 root;也可以通过设置一个 initContainer 来修改数据目录权限(本次使用后面这种方法):

yaml
[root@master1 prometheus-example]#vim prometheus-deploy.yaml......initContainers:- name:fix-permissionsimage:busyboxcommand:[chown,-R,"nobody:nobody",/prometheus]volumeMounts:- name:datamountPath:/prometheus

完整代码如下:

yaml
# prometheus-deploy.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:prometheusnamespace:monitorlabels:app:prometheusspec:selector:matchLabels:app:prometheustemplate:metadata:labels:app:prometheusspec:serviceAccountName:prometheusinitContainers:- name:fix-permissionsimage:busyboxcommand:[chown,-R,"nobody:nobody",/prometheus]volumeMounts:- name:datamountPath:/prometheuscontainers:- image:prom/prometheus:v2.34.0name:prometheusargs:- "--config.file=/etc/prometheus/prometheus.yml"- "--storage.tsdb.path=/prometheus"# 指定tsdb数据路径- "--storage.tsdb.retention.time=24h"#保存时间- "--web.enable-admin-api"# 控制对admin HTTP API的访问,其中包括删除时间序列等功能- "--web.enable-lifecycle"# 支持热更新,直接执行localhost:9090/-/reload立即生效ports:- containerPort:9090name:httpvolumeMounts:- mountPath:"/etc/prometheus"name:config-volume- mountPath:"/prometheus"name:dataresources:requests:cpu:100mmemory:512Milimits:cpu:100mmemory:512Mivolumes:#这里要再次熟悉下关于volumes/volumeMounts数据卷挂载的使用方法!!!- name:data#tsdb数据路径:/prometheus,这里使用的是pvcpersistentVolumeClaim:claimName:prometheus-data- configMap:#name:prometheus-configname:config-volume
  • 这个时候我们重新更新下 prometheus:
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-deploy.yamldeployment.apps/prometheusconfigured#注意:这里下载镜像要耐心等待一会儿!!!74M大小![root@master1 prometheus-example]#kubectl get pods -n monitorNAMEREADYSTATUSRESTARTSAGEprometheus-849c8456c7-dz6rt1/1Running054s[root@master1 prometheus-example]#kubectl logs prometheus-849c8456c7-dz6rt -nmonitor……ts=2022-04-29T05:00:09.184Zcaller=main.go:1142level=infomsg="Loading configuration file"filename=/etc/prometheus/prometheus.ymlts=2022-04-29T05:00:09.185Zcaller=main.go:1179level=infomsg="Completed loading of configuration file"filename=/etc/prometheus/prometheus.ymltotalDuration=1.128855msdb_storage=1.302µsremote_storage=5.18µsweb_handler=721nsquery_engine=2.705µsscrape=773.91µsscrape_sd=66.725µsnotify=1.443µsnotify_sd=2.966µsrules=4.859µstracing=20.118µsts=2022-04-29T05:00:09.185Zcaller=main.go:910level=infomsg="Server is ready to receive web requests."

7.创建prometheus的svc资源

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

[root@master1 prometheus-example]#vim prometheus-svc.yaml

yaml
# prometheus-svc.yamlapiVersion:v1kind:Servicemetadata:name:prometheusnamespace:monitorlabels:app:prometheusspec:selector:app:prometheustype:NodePortports:- name:webport:9090targetPort:http
  • 为了方便测试,我们这里创建一个 NodePort类型的服务,当然我们可以创建一个 Ingress对象,通过域名来进行访问:
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-svc.yamlservice/prometheuscreated[root@master1 prometheus-example]#kubectl get svc -nmonitorNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEprometheusNodePort10.108.47.93<none>9090:30092/TCP9s

8.测试

  • 现在我们就可以通过 http:具有go环境:go1.16.2

1、下载 Prometheus 的 Go 客户端库并运行示例

  • 首先确保已经安装了 Go 环境并启用 go modules,下载 Prometheus 的 Go 客户端库并运行这三个示例:

至于,如何在linux上安装go环境,可以查看我的另一篇文章:

https:[root@master1 ~]#git clone https:[root@master1 ~]#cd client_golang/examples/random/[root@master1 random]#lsmain.go(2)编译生成二进制文件[root@master1 random]#go buildgo:downloadinggithub.com/beorn7/perksv1.0.1go:downloadinggithub.com/cespare/xxhash/v2v2.1.2go:downloadinggithub.com/golang/protobufv1.5.2go:downloadinggithub.com/prometheus/client_modelv0.2.0go:downloadinggithub.com/prometheus/commonv0.33.0go:downloadinggithub.com/prometheus/procfsv0.7.3go:downloadinggoogle.golang.org/protobufv1.28.0go:downloadinggithub.com/matttproud/golang_protobuf_extensionsv1.0.1go:downloadinggolang.org/x/sysv0.0.0-20220328115105-d36c6a25d886[root@master1 random]#ll -htotal12M-rw-r--r--1rootroot4.5KApr2816:52main.go-rwxr-xr-x1rootroot12MApr2816:56random

这个时候我们可以得到3个不同的监控接口:http:[root@master1 prometheus-2.35.0.linux-amd64]#lsconsole_libraries consoles data LICENSE NOTICE prometheus prometheus.yml promtool[root@master1 prometheus-2.35.0.linux-amd64]#vim prometheus.ymlscrape_configs:- job_name:'example-random'# Override the global default and scrape targets from this job every 5 seconds.scrape_interval:5sstatic_configs:- targets:['localhost:8080','localhost:8081']labels:group:'production'- targets:['localhost:8082']labels:group:'canary'

3、验证

修改完prometheus.yml配置文件后,我们重启下prometheus。以上就是添加监控配置最基本的配置方式了,非常简单,只需要提供一个符合 metrics 格式的可访问的接口配置给 Prometheus 就可以了。

实验结束。😘

💘 实战:prometheus应用监控-2022.4.30(测试成功)

(监控k8s集群的CoreDns和redis服务)

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

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

实验环境

bash
k8s:v1.22.2(1master,2node)containerd:v1.5.5prometneus:docker.io/prom/prometheus:v2.34.0

实验软件

链接:https:apiVersion:v1data:Corefile:|.:53 {errorshealthreadykubernetes cluster.local in-addr.arpa ip6.arpa {pods insecurefallthrough in-addr.arpa ip6.arpattl 30}prometheus :9153forward . /etc/resolv.confcache 30loopreloadloadbalance}kind:ConfigMapmetadata:creationTimestamp:"2019-11-08T11:59:49Z"name:corednsnamespace:kube-systemresourceVersion:"188"selfLink:/api/v1/namespaces/kube-system/configmaps/corednsuid:21966186-c2d9-467a-b87f-d061c5c9e4d7

上面 ConfigMap 中 prometheus :9153就是开启 prometheus 的插件。

我们可以看到可以正常访问到,从这里可以看到 CoreDNS 的监控数据接口是正常的了。

2.将CoreDns的/metrics接口配置到prometheus的ConfigMap

注意:这里是直接加到上次实验环境里,将prometheus部署到k8s里的prometheus-cm.yamlconfigmap里。

csdn文章:https:apiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']

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

现在我们重新更新这个 ConfigMap 资源对象:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yamlconfigmap/prometheus-configconfigured

现在 Prometheus 的配置文件内容已经更改了,隔一会儿被挂载到 Pod 中的 prometheus.yml 文件也会更新。

我们可以看下prometheus pd里面的数据是否已经发生了改变?

bash
[root@master1 prometheus-example]#kubectl exec prometheus-698b6858c9-5xgsm -n monitor -- cat /etc/prometheus/prometheus.yml

由于我们之前的 Prometheus 启动参数中添加了 --web.enable-lifecycle参数,所以现在我们只需要执行一个 reload命令即可让配置生效:

bash
[root@master1 prometheus-example]#kubectl get pods -n monitor -o wideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESprometheus-698b6858c9-5xgsm1/1Running064m10.244.1.86node1<none><none>[root@master1 prometheus-example]#curl -X POST "http:[root@master1 prometheus-example]#

热更新

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

3.验证

可以看到我们刚刚添加的 coredns 这个任务已经出现了。

然后同样的我们可以切换到 Graph 下面去,我们可以找到一些 CoreDNS 的指标数据,至于这些指标数据代表什么意义,一般情况下,我们可以去查看对应的 /metrics接口,里面一般情况下都会有对应的注释。

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

测试结束。😘

2.使用 exporter 监控:监控k8s集群的redis应用

上面我们也说过有一些应用可能没有自带 /metrics接口供 Prometheus 使用,在这种情况下,我们就需要利用 exporter服务来为 Prometheus 提供指标数据了。Prometheus 官方为许多应用就提供了对应的 exporter应用,也有许多第三方的实现,我们可以前往官方网站进行查看:exporters,当然如果你的应用本身也没有 exporter 实现,那么就要我们自己想办法去实现一个 /metrics接口了,只要你能提供一个合法的 /metrics接口,Prometheus 就可以监控你的应用。

1.创建pod资源

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

[root@master1 ~]#cd prometheus-example/

[root@master1 prometheus-example]#vim prometheus-redis.yaml

yaml
# prometheus-redis.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:redisnamespace:monitorspec:selector:matchLabels:app:redistemplate:metadata:labels:app:redisspec:containers:- name:redis#redis不可能简单地写成多副本,因为它是有状态服务image:redis:4resources:requests:cpu:100mmemory:100Miports:- containerPort:6379- name:redis-exporter#它只会提供监控指标数据,主应用的metricsimage:oliver006/redis_exporter:latestresources:requests:cpu:100mmemory:100Miports:- containerPort:9121---kind:ServiceapiVersion:v1metadata:name:redisnamespace:monitorspec:selector:app:redisports:- name:redisport:6379targetPort:6379- name:promport:9121targetPort:9121

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

现在直接创建上面的应用:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-redis.yaml deployment.apps/rediscreatedservice/rediscreated
bash
[root@master1 prometheus-example]#kubectl get pods -n monitorNAMEREADYSTATUSRESTARTSAGEprometheus-698b6858c9-5xgsm1/1Running013hredis-7fb8ff6779-5ts8m2/2Running048s[root@master1 prometheus-example]#kubectl get svc -n monitorNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEprometheusNodePort10.101.131.61<none>9090:32700/TCP13hredisClusterIP10.99.36.173<none>6379/TCP,9121/TCP56s

bash
curl10.99.36.173:9121/metrics# HELP go_gc_duration_seconds A summary of the GC invocation durations.# TYPE go_gc_duration_seconds summarygo_gc_duration_seconds{quantile="0"}0go_gc_duration_seconds{quantile="0.25"}0go_gc_duration_seconds{quantile="0.5"}0go_gc_duration_seconds{quantile="0.75"}0go_gc_duration_seconds{quantile="1"}0go_gc_duration_seconds_sum0go_gc_duration_seconds_count0......# HELP redis_up Information about the Redis instance# TYPE redis_up gaugeredis_up1# HELP redis_uptime_in_seconds uptime_in_seconds metric# TYPE redis_uptime_in_seconds gaugeredis_uptime_in_seconds100

踩坑:注意:我这里没有现象………………一直卡在这里……😥

故障现象:

解决过程:

参考链接:https:将net.core.somaxconn=1024添加到/etc/sysctl.conf中,然后执行sysctl-p生效配置。第二个警告将vm.overcommit_memory=1添加到/etc/sysctl.conf中,然后执行sysctl-p生效配置。第三个警告将echonever>/sys/kernel/mm/transparent_hugepage/enabled添加到/etc/rc.local中,然后执行source/etc/rc.local生效配置。

验证:经实际测试后,发现要重启服务器的,重启就立马就有现象了!🤣

但是,后面再次测试时,发现有没有以上配置,redis都是可以正常提供/metrics接口的。😥(先搁置这里吧)

2.更新Prometheus 的配置文件

注意:这里同样在上次的prometheus的configmap文件里更新配置:

[root@master1 prometheus-example]#pwd

/root/prometheus-example

[root@master1 prometheus-example]#vim prometheus-cm.yaml

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']

由于我们这里是通过 Service 去配置的 redis 服务,当然直接配置 Pod IP 也是可以的,因为和 Prometheus 处于同一个 namespace,所以我们直接使用 servicename 即可。

这样写也是可以的哦:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured# 隔一会儿执行reload操作[root@master1 prometheus-example]# curl -X POST "http:已经把prometheus应用部署到k8s环境里;

关于如何将prometheus应用部署到k8s环境里,请查看我的另一篇文章,获取完整的部署方法!。

https:containerd:v1.5.5prometneus:docker.io/prom/prometheus:v2.34.0

🍀 实验软件

链接:https:apiVersion:apps/v1kind:DaemonSetmetadata:name:node-exporternamespace:monitorlabels:app:node-exporterspec:selector:matchLabels:app:node-exportertemplate:metadata:labels:app:node-exporterspec:hostPID:truehostIPC:truehostNetwork:true#因为这里用的是hostNetwortk模式,所以后面就不需要创建svc了!!!nodeSelector:kubernetes.io/os:linuxcontainers:- name:node-exporterimage:prom/node-exporter:v1.3.1args:- --web.listen-address=$(HOSTIP):9100- --path.procfs=/host/proc- --path.sysfs=/host/sys- --path.rootfs=/host/root- --no-collector.hwmon# 禁用不需要的一些采集器- --no-collector.nfs- --no-collector.nfsd- --no-collector.nvme- --no-collector.dmi- --no-collector.arp- --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/containerd/.+|/var/lib/docker/.+|var/lib/kubelet/pods/.+)($|/)- --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$ports:- containerPort:9100env:- name:HOSTIPvalueFrom:fieldRef:fieldPath:status.hostIP#Downward APIresources:requests:cpu:150mmemory:180Milimits:cpu:150mmemory:180MisecurityContext:runAsNonRoot:truerunAsUser:65534volumeMounts:- name:procmountPath:/host/proc- name:sysmountPath:/host/sys- name:rootmountPath:/host/rootmountPropagation:HostToContainer#这个是what??readOnly:truetolerations:- operator:"Exists"volumes:- name:prochostPath:path:/proc- name:devhostPath:path:/dev- name:syshostPath:path:/sys- name:roothostPath:path:/

注意:这里是可以更改监听端口的。

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

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

另外由于我们集群使用的是 kubeadm搭建的,所以如果希望 master 节点也一起被监控,则需要添加相应的容忍。

🍀 部署资源

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-node-exporter.yaml daemonset.apps/node-exportercreated[root@master1 prometheus-example]#kubectl get pods -nmonitor -l app=node-exporter-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESnode-exporter-6z7dx1/1Running039s172.29.9.52node1<none><none>node-exporter-bbsh61/1Running039s172.29.9.51master1<none><none>node-exporter-lm46b1/1Running039s172.29.9.53node2<none><none>

部署完成后,我们可以看到在几个节点上都运行了一个 Pod,由于我们指定了 hostNetwork=true,所以在每个节点上就会绑定一个端口 9100。

bash
curl172.29.9.51:9100/metrics...node_filesystem_device_error{device="shm",fstype="tmpfs",mountpoint="/rootfs/var/lib/docker/containers/aefe8b1b63c3aa5f27766053ec817415faf8f6f417bb210d266fef0c2da64674/shm"}1node_filesystem_device_error{device="shm",fstype="tmpfs",mountpoint="/rootfs/var/lib/docker/containers/c8652ca72230496038a07e4fe4ee47046abb5f88d9d2440f0c8a923d5f3e133c/shm"}1node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/dev"}0node_filesystem_device_error{device="tmpfs",fstype="tmpfs",mountpoint="/dev/shm"}0...

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

bash
helmupgrade--installnode-exporter--namespacemonitorstable/prometheus-node-exporter

2、prometheus的服务发现

由于我们这里每个节点上面都运行了 node-exporter程序,如果我们通过一个 Service 来将数据收集到一起用静态配置的方式配置到 Prometheus 去中,就只会显示一条数据,我们得自己在指标数据中去过滤每个节点的数据。

当然我们也可以手动的把所有节点用静态的方式配置到 Prometheus 中去,但是以后要新增或者去掉节点的时候就还得手动去配置。

那么有没有一种方式可以让 Prometheus 去自动发现我们节点的 node-exporter程序,并且按节点进行分组呢?这就是 Prometheus 里面非常重要的服务发现功能了。

在 Kubernetes 下,Promethues 通过与 Kubernetes API 集成,主要支持5种服务发现模式,分别是:NodeServicePodEndpointsIngress

bash
[root@master1 prometheus-example]# kubectl get nodeNAMESTATUSROLESAGEVERSIONmaster1Readycontrol-plane,master181dv1.22.2node1Ready<none>181dv1.22.2node2Ready<none>181dv1.22.2

1.部署prometheus ConfigMap配置文件

[root@master1 prometheus-example]#pwd

/root/prometheus-example

[root@master1 prometheus-example]#vim prometheus-cm.yaml

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:node

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

🍀 部署prometheus配置文件

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured[root@master1 prometheus-example]#kubectl exec prometheus-698b6858c9-5xgsm -nmonitor -- cat /etc/prometheus/prometheus.yml

可以看到prometheus pod的配置文件已经被更新了:

此时就可以执行reload操作了:

bash
[root@master1 prometheus-example]# curl -X POST "http:apiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace

这里就是一个正则表达式,去匹配 __address__这个标签,然后将 host 部分保留下来,port 替换成了 9100。

更新configmap文件:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured

bash
[root@master1 prometheus-example]# curl -X POST "http:- job_name:'kubernetes-nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)

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

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured

bash
[root@master1 prometheus-example]# curl -X POST "http:- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)

但是这里需要特别注意的是这里必须使用 https协议访问,这样就必然需要提供证书,我们这里是通过配置 insecure_skip_verify:true来跳过了证书校验。但是除此之外,要访问集群的资源,还必须要有对应的权限才可以,也就是对应的 ServiceAccount 棒的 权限允许才可以,我们这里部署的 prometheus 关联的 ServiceAccount 对象前面我们已经提到过了,这里我们只需要将 Pod 中自动注入的 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt/var/run/secrets/kubernetes.io/serviceaccount/token文件配置上,就可以获取到对应的权限了。

bash
token就是pod里面内置的,每一个pod都有;serviceaccount->secret->tokeninsecure_skip_verify:true#加上这个:只是为了不让他校验我们访问的ip;[root@master1 ~]#kubectl get po prometheus-698b6858c9-5xgsm -nmonitor -oyaml

以下不影响,是可以出效果的:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured
bash
[root@master1 prometheus-example]# curl -X POST "http:containerd:v1.5.5prometneus:docker.io/prom/prometheus:v2.34.0

实验软件

链接:https:apiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15s scrape_timeout:15s scrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)

上面的配置和我们之前配置 node-exporter的时候几乎是一样的,区别是我们这里使用了 https 的协议。

另外需要注意的是配置了 ca.cart 和 token 这两个文件,这两个文件是 Pod 启动后自动注入进来的

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured
bash
[root@master1 ~]# curl -X POST "http:apiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15s scrape_timeout:15s scrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:nodemetrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured
bash
[root@master1 prometheus-example]# curl -X POST "http:apiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured
bash
[root@master1 prometheus-example]# curl -X POST "http:kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtbearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)replacement:$1- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor# <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__# 下面的方式不推荐使用# - 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

4、标签查询

bash
container_memory_usage_bytes

可以看到有狠多年的实例,因为这里列出了集群里的所有容器。

bash
container_memory_usage_bytes{namespace="monitor",instance="node1"}#这里会自动提示的!

注意:这里的Discovered Labels是relabel之前的标签哦!

测试结束。😘

💘 实战:prometheus监控 apiserver-2022.5.2(测试成功)

实验环境

bash
k8s:v1.22.2(1master,2node)containerd:v1.5.5prometneus:docker.io/prom/prometheus:v2.34.0

实验软件

链接:https:NAMETYPECLUSTER-IPEXTERNAL-IPPORT(S) AGEkubernetesClusterIP10.96.0.1<none>443/TCP33d

上面这个 Service 就是我们集群的 apiserver 在集群内部的 Service 地址,要自动发现 Service 类型的服务,我们就需要用到 role 为 Endpoints 的 kubernetes_sd_configs

2、在 ConfigMap 对象中添加上一个 Endpoints 类型的服务的监控任务

[root@master1 ~]#cd prometheus-example/

[root@master1 prometheus-example]#vim prometheus-cm.yaml

yaml
- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpoints

本yaml完整代码如下:

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpoints

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

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured# 隔一会儿执行reload操作[root@master1 prometheus-example]# curl -X POST "http:kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https

完整yaml如下:

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https

4、验证

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yamlconfigmap/prometheus-configconfigured[root@master1 prometheus-example]# curl -X POST "http:kubernetes_sd_configs:- role:endpoints

本次完整yaml如下:

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https- job_name:'kubernetes-endpoints'kubernetes_sd_configs:- role:endpoints
bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured#稍等一会儿[root@master1 prometheus-example]# curl -X POST "http:kubernetes_sd_configs:- role:endpointsrelabel_configs:- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scrape]action:keepregex:true- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scheme]action:replacetarget_label:__scheme__regex:(https?)- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_path]action:replacetarget_label:__metrics_path__regex:(.+)- source_labels:[__address__,__meta_kubernetes_service_annotation_prometheus_io_port]action:replacetarget_label:__address__regex:([^:]+)(?::\d+)?;(\d+)# 这里的难点在正则这里,RE2 正则规则,+是一次多多次,?是0次或1次,其中?:表示非匹配组(意思就是不获取匹配结果)replacement:$1:$2- action:labelmapregex:__meta_kubernetes_service_label_(.+)- source_labels:[__meta_kubernetes_namespace]action:replacetarget_label:namespace- source_labels:[__meta_kubernetes_service_name]action:replacetarget_label:service- source_labels:[__meta_kubernetes_pod_name]action:replacetarget_label:pod- source_labels:[__meta_kubernetes_node_name]action:replacetarget_label:node

本次完整yaml如下:

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'prometheus'static_configs:- targets:['localhost:9090']- job_name:'coredns'static_configs:- targets:['10.244.0.8:9153','10.244.0.10:9153']- job_name:'redis'static_configs:- targets:['redis:9121']- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https- job_name:'kubernetes-endpoints'kubernetes_sd_configs:- role:endpointsrelabel_configs:- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scrape]action:keepregex:true- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scheme]action:replacetarget_label:__scheme__regex:(https?)- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_path]action:replacetarget_label:__metrics_path__regex:(.+)- source_labels:[__address__,__meta_kubernetes_service_annotation_prometheus_io_port]action:replacetarget_label:__address__regex:([^:]+)(?::\d+)?;(\d+) # 这里的难点在正则这里,RE2 正则规则,+是一次多多次,?是0次或1次,其中?:表示非匹配组(意思就是不获取匹配结果) replacement:$1:$2- action:labelmapregex:__meta_kubernetes_service_label_(.+)- source_labels:[__meta_kubernetes_namespace]action:replacetarget_label:namespace- source_labels:[__meta_kubernetes_service_name]action:replacetarget_label:service- source_labels:[__meta_kubernetes_pod_name]action:replacetarget_label:pod- source_labels:[__meta_kubernetes_node_name]action:replacetarget_label:node

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

注意这里的正则

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured#稍等一会儿[root@master1 prometheus-example]# curl -X POST "http:apiVersion:v1metadata:name:redisnamespace:monitorannotations:prometheus.io/scrape:"true"#增加这2行信息prometheus.io/port:"9121"spec:selector:app:redisports:- name:redisport:6379targetPort:6379- name:promport:9121targetPort:9121

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

[root@master1 prometheus-example]#kubectl apply -f prometheus-redis.yaml deployment.apps/redis unchangedservice/redis configured

4、整合剩余的监控应用

[root@master1 prometheus-example]#vim prometheus-svc.yaml

yaml
# prometheus-svc.yamlapiVersion:v1kind:Servicemetadata:name:prometheusnamespace:monitorannotations:prometheus.io/scrape:"true"prometheus.io/port:"9090"labels:app:prometheusspec:selector:app:prometheustype:NodePortports:- name:webport:9090targetPort:http

更新prometheus-svc.yaml:

yaml
[root@master1 prometheus-example]#kubectl apply -f prometheus-svc.yamlservice/prometheus configured

验证:

最后,删除prometheus-cm.yaml里剩余的静态配置:

[root@master1 prometheus-example]#vim prometheus-cm.yaml

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https- job_name:'kubernetes-endpoints'kubernetes_sd_configs:- role:endpointsrelabel_configs:- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scrape]action:keepregex:true- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scheme]action:replacetarget_label:__scheme__regex:(https?)- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_path]action:replacetarget_label:__metrics_path__regex:(.+)- source_labels:[__address__,__meta_kubernetes_service_annotation_prometheus_io_port]action:replacetarget_label:__address__regex:([^:]+)(?::\d+)?;(\d+) # 这里的难点在正则这里,RE2 正则规则,+是一次多多次,?是0次或1次,其中?:表示非匹配组(意思就是不获取匹配结果) replacement:$1:$2- action:labelmapregex:__meta_kubernetes_service_label_(.+)- source_labels:[__meta_kubernetes_namespace]action:replacetarget_label:namespace- source_labels:[__meta_kubernetes_service_name]action:replacetarget_label:service- source_labels:[__meta_kubernetes_pod_name]action:replacetarget_label:pod- source_labels:[__meta_kubernetes_node_name]action:replacetarget_label:node

更新并reload:

bash
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yaml configmap/prometheus-configconfigured[root@master1 prometheus-example]# curl -X POST "http:containerd:v1.5.5prometneus:docker.io/prom/prometheus:v2.34.0kube-state-metrics:v2.4.2

前置条件

关于如何将prometheus应用部署到k8s环境里,请查看我的另一篇文章,获取完整的部署方法!。

https:cdkube-state-metrics/examples/standard

默认的镜像为 gcr 的,这里我们可以将 deployment.yaml下面的镜像替换成 cnych/kube-state-metrics:v2.4.2。此外我们上面为 Prometheus 配置了 Endpoints 的自动发现,所以我们可以给 kube-state-metrics 的 Service 配置上对应的 annotations 来自动被发现。

2.创建资源

将代码克隆后,我们来到kube-state-metrics/examples/standard目录:

先替换下deployment.yaml的镜像源以及在service.yaml里添加下annotations信息:

yaml
[root@master1 standard]#cat deployment.yamlapiVersion:apps/v1kind:Deploymentmetadata:labels:app.kubernetes.io/component:exporterapp.kubernetes.io/name:kube-state-metricsapp.kubernetes.io/version:2.4.2name:kube-state-metricsnamespace:kube-systemspec:replicas:1selector:matchLabels:app.kubernetes.io/name:kube-state-metricstemplate:metadata:labels:app.kubernetes.io/component:exporterapp.kubernetes.io/name:kube-state-metricsapp.kubernetes.io/version:2.4.2spec:automountServiceAccountToken:truecontainers:- image:cnych/kube-state-metrics:v2.4.2livenessProbe:httpGet:path:/healthzport:8080initialDelaySeconds:5timeoutSeconds:5name:kube-state-metricsports:- containerPort:8080name:http-metrics- containerPort:8081name:telemetryreadinessProbe:httpGet:path:/port:8081initialDelaySeconds:5timeoutSeconds:5securityContext:allowPrivilegeEscalation:falsecapabilities:drop:- ALLreadOnlyRootFilesystem:truerunAsUser:65534nodeSelector:kubernetes.io/os:linuxserviceAccountName:kube-state-metrics[root@master1 standard]#cat service.yamlapiVersion:v1kind:Servicemetadata:annotations:prometheus.io/scrape:"true"prometheus.io/port:"8080"labels:app.kubernetes.io/component:exporterapp.kubernetes.io/name:kube-state-metricsapp.kubernetes.io/version:2.4.2name:kube-state-metricsnamespace:kube-systemspec:clusterIP:Noneports:- name:http-metricsport:8080targetPort:http-metrics- name:telemetryport:8081targetPort:telemetryselector:app.kubernetes.io/name:kube-state-metrics

bash
[root@master1 standard]#pwd/root/kube-state-metrics/examples/standard[root@master1 standard]#lscluster-role-binding.yamlcluster-role.yamldeployment.yamlservice-account.yamlservice.yaml[root@master1 standard]#kubectl apply -f .clusterrolebinding.rbac.authorization.k8s.io/kube-state-metricscreatedclusterrole.rbac.authorization.k8s.io/kube-state-metricscreateddeployment.apps/kube-state-metricscreatedserviceaccount/kube-state-metricscreatedservice/kube-state-metricscreated

3.验证

部署完成后正常就可以被 Prometheus 采集到指标了:

4.过滤

安装完成。😘

3.水平缩放(分片)

kube-state-metrics已经内置实现了一些自动分片功能,可以通过 --shard--total-shards参数进行配置。现在还有一个实验性功能,如果将 kube-state-metrics部署在 StatefulSet 中,它可以自动发现其命名位置,以便自动配置分片,这是一项实验性功能,可能以后会被移除。

要启用自动分片,必须运行一个 kube-state-metrics 的 StatefulSet,并且必须通过 --pod--pod-namespace标志将 pod 名称和名称空间传递给 kube-state-metrics进程。可以参考 /examples/autosharding目录下面的示例清单文件进行说明。

4.使用(测试成功)-2022.5.2

使用 kube-state-metrics 的一些典型场景:

1.问题现象:采集到的标签冲突问题

现在有一个问题是前面我们做 endpoints类型的服务发现的时候做了一次 labelmap,将 namespace 和 pod 标签映射到了指标中。但是由于 kube-state-metrics暴露的指标中本身就包含 namespace 和 pod 标签,这就会产生冲突,这种情况会将映射的标签变成 exported_namespaceexported_pod,这变会对指标的查询产生影响,如下所示:

可以看到这里的exported前缀信息都是正确的,但是node,namespace,service等都是kube-state-metrics的信息,不符合预期。

2.解决办法:利用metric_relabel_configs功能

这个情况下我们可以使用 metric_relabel_configs这 Prometheus 保存数据前的最后一步重新编辑标签,metric_relabel_configs模块和 relabel_configs模块很相似,metric_relabel_configs一个很常用的用途就是可以将监控不需要的数据,直接丢掉,不在 Prometheus 中保存。比如我们这里可以重新配置 endpoints类型的指标发现配置:

yaml
- job_name:'endpoints'kubernetes_sd_configs:- role:endpointsmetric_relabel_configs:- source_labels:[__name__,exported_pod]regex:kube_pod_info;(.+)target_label:pod- source_labels:[__name__,exported_namespace]regex:kube_pod_info;(.+)target_label:namespace- source_labels:[__name__,exported_node]regex:kube_pod_info;(.+)target_label:node- source_labels:[__name__,exported_service]regex:kube_pod_info;(.+)target_label:servicerelabel_configs:# ......

本次完整yaml如下:

yaml
# prometheus-cm.yamlapiVersion:v1kind:ConfigMapmetadata:name:prometheus-confignamespace:monitordata:prometheus.yml:|global:scrape_interval:15sscrape_timeout:15sscrape_configs:- job_name:'nodes'kubernetes_sd_configs:- role:noderelabel_configs:- source_labels:[__address__]regex:'(.*):10250'replacement:'${1}:9100'target_label:__address__action:replace- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubelet'kubernetes_sd_configs:- role:nodescheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- job_name:'kubernetes-cadvisor'kubernetes_sd_configs:- role:node# metrics_path:/metrics/cadvisorscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- action:labelmapregex:__meta_kubernetes_node_label_(.+)- source_labels:[__meta_kubernetes_node_name]regex:(.+)replacement:/metrics/cadvisor # <nodeip>/metrics -> <nodeip>/metrics/cadvisortarget_label:__metrics_path__- job_name:'kubernetes-apiservers'kubernetes_sd_configs:- role:endpointsscheme:httpstls_config:ca_file:/var/run/secrets/kubernetes.io/serviceaccount/ca.crtinsecure_skip_verify:truebearer_token_file:/var/run/secrets/kubernetes.io/serviceaccount/tokenrelabel_configs:- source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_service_name,__meta_kubernetes_endpoint_port_name]action:keepregex:default;kubernetes;https- job_name:'kubernetes-endpoints'kubernetes_sd_configs:- role:endpointsrelabel_configs:- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scrape]action:keepregex:true- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scheme]action:replacetarget_label:__scheme__regex:(https?)- source_labels:[__meta_kubernetes_service_annotation_prometheus_io_path]action:replacetarget_label:__metrics_path__regex:(.+)- source_labels:[__address__,__meta_kubernetes_service_annotation_prometheus_io_port]action:replacetarget_label:__address__regex:([^:]+)(?::\d+)?;(\d+) # 这里的难点在正则这里,RE2 正则规则,+是一次多多次,?是0次或1次,其中?:表示非匹配组(意思就是不获取匹配结果) replacement:$1:$2- action:labelmapregex:__meta_kubernetes_service_label_(.+)- source_labels:[__meta_kubernetes_namespace]action:replacetarget_label:namespace- source_labels:[__meta_kubernetes_service_name]action:replacetarget_label:service- source_labels:[__meta_kubernetes_pod_name]action:replacetarget_label:pod- source_labels:[__meta_kubernetes_node_name]action:replacetarget_label:nodemetric_relabel_configs:- source_labels:[__name__,exported_pod]regex:kube_pod_info;(.+)target_label:pod- source_labels:[__name__,exported_namespace]regex:kube_pod_info;(.+)target_label:namespace- source_labels:[__name__,exported_node]regex:kube_node_info;(.+)target_label:node- source_labels:[__name__,exported_service]regex:kube_service_info;(.+)target_label:service
yaml
[root@master1 prometheus-example]#kubectl apply -f prometheus-cm.yamlconfigmap/prometheus-config unchanged[root@master1 prometheus-example]# curl -X POST "http:- source_labels:[__meta_xxx_label_xxx]regex:Example.*action:drop

那么将不会收集这个指标,而 metric_relabel_configs使用的时候指标已经采集过了:

yaml
metric_relabel_configs:- source_labels:[__name__]regex:'(container_tasks_state|container_memory_failures_total)'action:drop

所以 metric_relabel_configs相对来说,更加昂贵,因为指标已经采集了。

测试结束。😘

5.更多信息

关于 kube-state-metrics 的更多用法可以查看官方 GitHub 仓库:https:

版权:此文章版权归 One 所有,如有转载,请注明出处!

链接:可点击右上角分享此页面复制文章链接

上次更新时间:

最近更新