为什么prometheus天然适合监控kubernetes
指标分析
容器基础资源指标 | kubelet 内置cadvisor metrics接口 | 查看容器cpu、mem利用率等 |k8s_sd node级别直接访问node_ip|
k8s资源指标 通过coredns访问
k8s服务组件指标| 服务组件 metrics接口 | 查看apiserver 、scheduler、etc、coredns请求延迟等 | k8s_sd endpoint级别
部署在pod中业务埋点指标| pod 的metrics接口 | 依据业务指标场景 | k8s_sd pod级别,访问pod ip的metricspath |
适配分析
k8s服务发现 | 通过watch即时发现资源变化
| 各个组件metrics自暴露 | 所有组件将自身指标暴露在各自的服务端口上,prometheus通过pull过来拉取指标
| 鉴权 | k8s的组件接口都是要鉴权的,所以k8s的采集器要支持配置鉴权 | 支持配置token和tls证书 |
| 标签relabel能力 | 过滤服务发现标的 | `labelmap`去掉服务发现标签的长前缀 |
奥妙
- prometheus是pull模型采集的,各个被监控的源只需要将自身指标暴露在本地http端口中,prometheus就可以访问接口来采集指标
- prometheus在k8s中也是这样的,组件需要暴露自身指标,如我们在容器基础资源指标中提到的kubelet 内置cadvisor指标就是暴露在10250端口下的/metrics/cadvisor下。
- prometheus通过 k8s服务发现这些指标源完成采集
举例
- 一、endpoint级别的服务发现 :举例 在采集apiserver、kube-controller-manager等
kubernetes_sd_configs:
role: endpoints
- 二、node级别的服务发现 :举例 在采集cadvisor和kubelet自身指标时
kubernetes_sd_configs:
- role: node
解读:watch即时更新
- 通过watch即时发现资源变化,就满足了我们一开始提出的云原生情况下监控的挑战之一,要及时感知到采集源的变化。
- 同时在k8s大二层环境中,prometheus可以访问到发现出来的的 endpoint 、node、pod
适配3. 采集鉴权:token & 证书
k8s中很多接口都要鉴权,甚至还需要tls双向认证
- 同时我们知道在k8s中很多接口都是带有访问鉴权的,比如我们直接访问k8s node上的kubelet的/metrics/cadvisor接口会返回未授权。如下面所示
[root@Kubernetes-node01 logs]# curl -k https://localhost:10250/metrics/cadvisor
Unauthorized
- prometheus在采集cadvisor指标时同样面临鉴权问题
解决方法
- 聪明的prometheus开发人员通过在采集中支持配置中相关token和证书来解决这个问题,如下面的配置代表有一个token文件,同时还有一个证书文件。
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
insecure_skip_verify: true
适配4. 强大的relabel能力 做标签截取、变换、静态分片
prometheus relabel说明
- 文档地址 https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config
应用1: labelmap 在采集cadvisor指标时 对服务发现标签key名字截取
- 在采集cadvisor时可以看到服务发现源给添加了很多
__meta_kubernetes_node_label_
开头的标签
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MTaCNEEW-1687484851787)(/img/bVcSC9K)]
- 但是这些标签名字太长了,需要精简。我们使用如下的relabel配置
relabel_configs:
- separator: ;
regex: __meta_kubernetes_node_label_(.+)
replacement: $1
action: labelmap
- 以这个标签为例,
__meta_kubernetes_node_label_kubernetes_io_os="linux"
,上面的配置代表匹配_meta_kubernetes_node_label_开头的key,只保留后面的部分,所以在最终的标签看到的就是beta_kubernetes_io_os="linux"
。 - labelmap代表匹配到的标签赋值给目标标签
应用2: replace 在采集pod自定义指标 对标签进行赋值
- 我们在使用pod自定义指标时在pod yaml 的spec.template.metadata.annotations中需要定义三个以
prometheus.io
开头的配置,分布代表是否需要prometheus采集、metrics暴露的端口、metrics的http path信息,详细配置如下:
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
annotations:
prometheus.io/scrape: 'true'
prometheus.io/port: '9102'
prometheus.io/path: 'metrics'
- 在采集pod自定义指标时采用如下
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
separator: ;
regex: "true"
replacement: $1
action: keep
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
separator: ;
regex: (.+)
target_label: __metrics_path__
replacement: $1
action: replace
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
separator: ;
regex: ([^:]+)(?::\d+)?;(\d+)
target_label: __address__
replacement: $1:$2
action: replace
- 意思是将
__meta_kubernetes_pod_annotation_prometheus_io_path
赋值给__metrics_path__
- 意思是将相关
__meta_kubernetes_pod_annotation_prometheus_io_port
赋值给__address__
后面的端口
应用2: keep 做过滤,在采集服务组件endpoint时
- endpoint资源是暴露一个服务的ip地址和port的列表
- 代表采用k8s服务发现 endpoint,endpoint会非常多,所以需要过滤apiserver的
kubernetes_sd_configs:
- role: endpoints
- 过滤手段为 标签 __meta_kubernetes_namespace匹配default并且 __meta_kubernetes_service_name 匹配kubernetes 并且 __meta_kubernetes_endpoint_port_name 匹配https,咋样呢 :
keep
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
separator: ;
regex: default;kubernetes;https
replacement: $1
action: keep
- k8s 会在default namespace中创建apiserver的 service
$ kubectl get svc -A |grep 443
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9d
- 最后获取到的endpoint转换为采集路径为:
https://masterip:6443/metrics
一个完整的配置如下
- job_name: 'kubernetes-node'
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-node-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
- job_name: 'kubernetes-apiserver'
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
- 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
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- action: keep
regex: true
source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_scrape
- action: replace
regex: (.+)
source_labels:
- __meta_kubernetes_pod_annotation_prometheus_io_path
target_label: __metrics_path__
- action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
source_labels:
- __address__
- __meta_kubernetes_pod_annotation_prometheus_io_port
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- action: replace
source_labels:
- __meta_kubernetes_namespace
target_label: kubernetes_namespace
- action: replace
source_labels:
- __meta_kubernetes_pod_name
target_label: kubernetes_pod_name
- job_name: 'kubernetes-schedule'
scrape_interval: 5s
static_configs:
- targets: ['192.168.40.180:10251']
- job_name: 'kubernetes-controller-manager'
scrape_interval: 5s
static_configs:
- targets: ['192.168.40.180:10252']
- job_name: 'kubernetes-kube-proxy'
scrape_interval: 5s
static_configs:
- targets: ['192.168.40.180:10249','192.168.40.181:10249']
- job_name: 'kubernetes-etcd'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/k8s-certs/etcd/ca.crt
cert_file: /var/run/secrets/kubernetes.io/k8s-certs/etcd/server.crt
key_file: /var/run/secrets/kubernetes.io/k8s-certs/etcd/server.key
scrape_interval: 5s
static_configs:
- targets: ['192.168.40.180:2379']
指标分析
容器基础资源指标
cpu
- cpu 在Kubernetes中CPU属于可压缩资源,意思是pod中服务使用CPU超过设置的limits,pod不会被kill掉但会被限制。所以我们应该通过观察容器CPU被限制的情况来考虑是否将CPU的limit调大。
- 有这样的两个CPU指标,
container_cpu_cfs_periods_total
代表 container生命周期中度过的CPU周期总数,container_cpu_cfs_throttled_periods_total
代表container生命周期中度过的受限的CPU周期总数。 - 所以我们可以使用下面的表达式来查出最近5分钟,超过25%的CPU执行周期受到限制的container有哪些。
100 * sum by(container_name, pod_name, namespace) (increase(container_CPU_cfs_throttled_periods_total{container_name!=""}[5m]))
/ sum by(container_name, pod_name, namespace) (increase(container_CPU_cfs_periods_total[5m])) > 25
- 我们可以用下面的计算方式表示容器CPU使用率,其中
container_cpu_usage_seconds_total
代表CPU的计数器,container_spec_cpu_quota
是容器的CPU配额,它的值是容器指定的CPU个数*100000。
sum(rate(container_CPU_usage_seconds_total{image!=""}[1m])) by (container, pod) / (sum(container_spec_CPU_quota{image!=""}/100000) by (container, pod) )* 100
mem
- 在Kubernetes中mem属于不可压缩资源,pod之间是无法共享的,完全独占的。所以一旦容器内存使用超过limits,会导致oom,然后重新调度。
container_memory_working_set_bytes
是容器真实使用的内存量, kubelet通过比较container_memory_working_set_bytes
和container_spec_memory_limit_bytes
来决定oom container。- 同时还有
container_memory_usage_bytes
用来表示容器使用内存,其中包含了很久没用的缓存,该值比container_memory_working_set_bytes
要大 - 所以内存使用率可以使用下面的公式计算
(container_memory_working_set_bytes/container_spec_memory_limit_bytes )*100
其他
容器网卡流量的计算和机器上类似,通过对相关counter做rate即可,比如
- 容器网卡入流量 rate(container_network_receive_bytes_total[1m])
- 网卡出流量 rate(container_network_transmit_bytes_total[1m])
- 文件系统使用率,可以用下面计算
- container_fs_usage_bytes代表使用
- container_fs_limit_bytes代表限制
- (container_fs_usage_bytes/container_fs_limit_bytes) *100
k8s对象资源指标
pod状态
下面的表格中一系列指标描述容器的运行状态。
指标名 | 含义 | 标签举例 |
---|---|---|
kube_pod_status_phase | 容器当前的运行状态,非Running的都是异常的。 | Pending Succeeded Failed Running Unknown |
kube_pod_container_status_waiting | 容器处于waiting状态 | 值为1代表waiting |
kube_pod_container_status_waiting_reason | pod处于waiting状态原因 | ContainerCreating CrashLoopBackOff pod启动崩溃,再次启动然后再次崩溃 CreateContainerConfigError ErrImagePull ImagePullBackOff CreateContainerError InvalidImageName |
kube_pod_container_status_terminated | pod处于terminated状态 | 值为1代表terminated |
kube_pod_container_status_terminated_reason | pod处于terminated状态原因 | OOMKilled Completed Error ContainerCannotRun DeadlineExceeded Evicted |
kube_pod_container_status_restarts_total | pod中的容器重启次数 | - |
-
下面给出一些经常使用的告警。
-
比如查看下因为拉取镜像失败导致waiting的容器
kube_pod_container_status_waiting_reason{reason="ErrImagePull"}==1
-
查看下发生oom的容器
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}==1
-
最近十分钟内有重启
(kube_pod_container_status_restarts_total - kube_pod_container_status_restarts_total offset 10m >= 1)
node状态
- 那么站在Kubernetes集群管理员的角度,也需要关心下node整体的资源情况。
指标名 | 含义 | 标签举例 |
---|---|---|
kube_node_status_condition | node节点的运行状态,非Ready都是异常 同时可以看到因为哪种资源压力导致的 | condition: NetworkUnavailable MemoryPressure DiskPressure PIDPressure Ready |
kube_node_status_allocatable_CPU_cores | 节点可以分配CPU核数 | |
kube_node_status_allocatable_memory_bytes | 节点可以分配内存总量(单位:字节) | |
kube_node_spec_taint | 节点污点情况 |
-
下面给出一些经常使用的告警。
-
比如查看节点因为内存有压力不可用
kube_node_status_condition{condition="MemoryPressure",status="true"}==1
其他资源的常见指标
- dep 副本数不正常
kube_deployment_spec_replicas!= kube_deployment_status_replicas_available
- daemonset中不可用的数量 :
kube_daemonset_status_number_unavailable > 0
来表示 - 可以使用
kube_job_failed > 0
来表示失败的job - 在kube-state-metrics中还有很多其他对象的指标,你可以自行查阅使用。
k8s服务组件指标
四大黄金指标
延迟、请求qps、错误数、饱和度
- 站在Kubernetes集群管理员的角度,服务组件的健康状况需要额外的关注。
apiserver
-
在监控apiserver时,我们可以重点关注四大黄金指标:延迟、请求qps、错误数、饱和度。
-
apiserver_request_total代表apiserver的请求计数器,所以我们可以使用sum(rate(apiserver_request_total{job=“kubernetes-apiservers”,code=~“2…”}[5m]))来计算apiserver请求成功的qps。
-
所以响应=2xx的qps除以总的qps就是apiserver的请求成功率,表达式如下。可以设置成功率低于95%的告警。
-
100 * sum(rate(apiserver_request_total{job=“kubernetes-apiservers”,code=~“2…”}[5m])) /sum(rate(apiserver_request_total{job=“kubernetes-apiservers”}[5m]))
- 同理也可以关注4xx和5xx的错误qps,表达式如下
- sum(rate(apiserver_request_total{job=“kubernetes-apiservers”,code=~"[45] [5m]))
- 错误的qps过高,可能是服务组件有问题,需要尽快排查。
-
对于延迟,可以使用下面的表达式计算。
-
histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket{job=“kubernetes-apiservers”}[5m])) by (verb, le))
-
可以得到各个http的请求方法的99分位延迟值。
{verb="WATCH"} 60
{verb="DELETE"} NaN
{verb="PATCH"} 0.0495
{verb="PUT"} 0.08797499999999975
{verb="GET"} 0.06524999999999985
{verb="LIST"} 0.09421428571428572
{verb="POST"} 0.0495
-
如果99分位延迟值很高,可能是apiserver处理能力达到上限,可以考虑扩容一下。
-
对于饱和度可以查看apiserver请求队列的情况,如apiserver_current_inqueue_requests很大的话,说明排队严重。
etcd作为Kubernetes中元信息存储的数据库也需要额外关注下。
-
etcd存储文件大小相关指标,比如etcd_db_total_size_in_bytes表征db物理文件大小。
- 使用下面表达式可以得到etcd存储空间使用率: 当前使用量/配额。如果使用率大于80%需要扩容
- (etcd_mvcc_db_total_size_in_bytes / etcd_server_quota_backend_bytes)*100
-
关于etcd的网络流量可以使用下面两个指标表示。
- etcd_network_client_grpc_received_bytes_total代表client调etcd的流量。
- etcd_network_client_grpc_sent_bytes_total代表etcd发送的流量。
-
etcd中存储key和相关key操作的qps指标,如etcd_debugging_mvcc_keys_total代表etcd中存储的key总数,数量太多也会影响性能。
- 同时关于etcd key的操作的qps,rate(etcd_debugging_mvcc_put_total[1m])代表put的qps,同理 rate(etcd_debugging_mvcc_delete_total[1m])代表删除的qps。
-
存储的fsync刷盘99分位延迟可以使用下面的分位值计算得到
-
histogram_quantile(0.99, sum(rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) by (instance, le))
kube-scheduler是调度器,所以有关调度成功统计的指标都应被关注。
-
如scheduler_pod_scheduling_attempts_sum/scheduler_pod_scheduling_attempts_count代表成功调度一个pod 的平均尝试次数。如果尝试次数过高,可能当前node剩余量不多,或者集群出错,建议排查下。
-
如histogram_quantile(0.99, sum(rate(scheduler_pod_scheduling_duration_seconds_bucket[5m])) by ( le)) 代码pod调度的99分位延迟,如果过高,考虑schduler压力大或者其他原因。
-
在kube-controller-manager负责集群内的 Node、Pod 等所有资源的管理。
-
如rate(workqueue_adds_total[2m])表征工作队列新增的qps,其实就是请求的qps,太高考虑压力大。
-
如histogram_quantile(0.99, sum(rate(rest_client_request_duration_seconds_bucket{job=“kube-controller-manager”}[5m])) by (verb, url, le)),可以查看和apiserver通信的延迟99分位值,太高考虑扩容下apiserver。
pod业务埋点指标
各个业务自行决定即可。
prometheus与consul结合
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: consul
spec:
serviceName: consul
replicas: 3
selector:
matchLabels:
app: consul
template:
metadata:
labels:
app: consul
spec:
terminationGracePeriodSeconds: 10
containers:
- name: consul
image: consul:latest
imagePullPolicy: IfNotPresent
args:
- "agent"
- "-server"
- "-bootstrap-expect=3"
- "-ui"
- "-data-dir=/consul/data"
- "-bind=0.0.0.0"
- "-client=0.0.0.0"
- "-advertise=$(PODIP)"
- "-retry-join=consul-0.consul.$(NAMESPACE).svc.cluster.local"
- "-retry-join=consul-1.consul.$(NAMESPACE).svc.cluster.local"
- "-retry-join=consul-2.consul.$(NAMESPACE).svc.cluster.local"
- "-domain=cluster.local"
- "-disable-host-node-id"
env:
- name: PODIP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 8500
name: ui-port
- containerPort: 8443
name: https-port
---
apiVersion: v1
kind: Service
metadata:
name: consul
labels:
name: consul
spec:
type: NodePort
ports:
- name: http
port: 8500
nodePort: 30007
targetPort: 8500
- name: https
port: 8443
nodePort: 30008
targetPort: 8443
selector:
app: consul
使用python向consul发送请求
import consul
import asyncio
import logging
logging.basicConfig(
# TODO console 日志,上线时删掉
# filename=LOG_PATH,
format='%(asctime)s %(levelname)s %(filename)s %(funcName)s [line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S",
level="INFO"
)
class ConsulWork(object):
def __init__(self, host, port):
self.consul = consul.Consul(host, port)
def set_key_v(self, key, value):
res = self.consul.kv.put(key, value)
msg = "[set_key_v_res][key:{}][value:{}][res:{}]".format(
key,
value,
res,
)
logging.info(msg)
def get_key_v(self, key):
index, data = self.consul.kv.get(key)
msg = "[get_key_v_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def block_get_key_v(self, key, index):
index, data = self.consul.kv.get(key, index=index)
msg = "[block_get_key_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
if __name__ == '__main__':
c_host = '192.168.150.24'
c_port = 30007
c = ConsulWork(c_host, c_port)
# 简单put get
c.set_key_v("key_a", "value_a")
index,data = c.get_key_v("key_a")
# 使用index 作为参数会阻塞住请求
# 直到有数据更新或者超时
c.block_get_key_v("key_a", index)
接受请求
import consul
import asyncio
import logging
logging.basicConfig(
# TODO console 日志,上线时删掉
# filename=LOG_PATH,
format='%(asctime)s %(levelname)s %(filename)s %(funcName)s [line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S",
level="INFO"
)
class ConsulWork(object):
def __init__(self, host, port):
self.consul = consul.Consul(host, port)
def set_key_v(self, key, value):
res = self.consul.kv.put(key, value)
msg = "[set_key_v_res][key:{}][value:{}][res:{}]".format(
key,
value,
res,
)
logging.info(msg)
def get_key_v(self, key):
index, data = self.consul.kv.get(key)
msg = "[get_key_v_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def block_get_key_v(self, key, index):
index, data = self.consul.kv.get(key, index=index)
msg = "[block_get_key_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
if __name__ == '__main__':
c_host = '192.168.3.50'
c_port = 8500
c = ConsulWork(c_host, c_port)
# 简单put get
index = None
key = 'key_a'
while True:
index, data = c.block_get_key_v(key, index)
msg = "[key:{}][value:{}][index:{}]".format(
key,
data['Value'],
index
)
# chan <- data
logging.info(msg)
# 此时再开一个窗口执行更新 key_a操作,可以看到key_a的变化
向consul注册一个服务
import consul
import asyncio
import logging
logging.basicConfig(
# TODO console 日志,上线时删掉
# filename=LOG_PATH,
format='%(asctime)s %(levelname)s %(filename)s %(funcName)s [line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S",
level="INFO"
)
class ConsulWork(object):
def __init__(self, host, port):
self.consul = consul.Consul(host, port)
def set_key_v(self, key, value):
res = self.consul.kv.put(key, value)
msg = "[set_key_v_res][key:{}][value:{}][res:{}]".format(
key,
value,
res,
)
logging.info(msg)
def get_key_v(self, key):
index, data = self.consul.kv.get(key)
msg = "[get_key_v_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def block_get_key_v(self, key, index):
index, data = self.consul.kv.get(key, index=index)
msg = "[block_get_key_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def register_service(self, name, host, port, tags=None):
tags = tags or []
# 注册服务
id = "{}_{}_{}".format(name, host, port)
return self.consul.agent.service.register(
name,
id,
host,
port,
tags,
# 健康检查ip端口,检查时间:5,超时时间:30,注销时间:30s
# check=consul.Check().tcp(host, port, "5s", "5s", "60s"))
check=consul.Check().tcp(host, port, "5s", "5s"))
def register_service_with_deregister(self, name, host, port, tags=None):
tags = tags or []
# 注册服务
id = "{}_{}_{}".format(name, host, port)
return self.consul.agent.service.register(
name,
id,
host,
port,
tags,
# 健康检查ip端口,检查时间:5,超时时间:30,注销时间:15s
# 意思是15秒后 还是检测失败就自动注销掉服务
check=consul.Check().tcp(host, port, "5s", "5s", "15s"))
# check=consul.Check().tcp(host, port, "5s", "5s"))
if __name__ == '__main__':
c_host = '192.168.150.24'
c_port = 30007
c = ConsulWork(c_host, c_port)
res1 = c.register_service("pgw", '192.168.3.50', 9091)
# res2 = c.register_service_with_deregister("pushgateway", '172.20.70.205', 9991)
res2 = c.register_service("pgw", '192.168.3.51', 9091)
print(res1,res2)
watch服务
"""
curl -vvv --request PUT 'http://172.20.70.205:8500/v1/agent/service/deregister/pushgateway_172.20.70.205_9991'
curl -vvv --request PUT 'http://172.20.70.205:8500/v1/agent/service/deregister/pushgateway_172.20.70.205_9091'
"""You have new mail in /var/spool/mail/root
[root@k8s-master1 consel]# cat 005watch服务.py
import time
import consul
import asyncio
import logging
logging.basicConfig(
# TODO console 日志,上线时删掉
# filename=LOG_PATH,
format='%(asctime)s %(levelname)s %(filename)s %(funcName)s [line:%(lineno)d]:%(message)s',
datefmt="%Y-%m-%d %H:%M:%S",
level="INFO"
)
class ConsulWork(object):
def __init__(self, host, port):
self.consul = consul.Consul(host, port)
def set_key_v(self, key, value):
res = self.consul.kv.put(key, value)
msg = "[set_key_v_res][key:{}][value:{}][res:{}]".format(
key,
value,
res,
)
logging.info(msg)
def get_key_v(self, key):
index, data = self.consul.kv.get(key)
msg = "[get_key_v_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def block_get_key_v(self, key, index):
index, data = self.consul.kv.get(key, index=index)
msg = "[block_get_key_res][key:{}][index:{}][data:{}]".format(
key,
index,
data,
)
logging.info(msg)
return index, data
def register_service(self, name, host, port, tags=None):
tags = tags or []
# 注册服务
id = "{}_{}_{}".format(name, host, port)
return self.consul.agent.service.register(
name,
id,
host,
port,
tags,
# 健康检查ip端口,检查时间:5,超时时间:30,注销时间:30s
# check=consul.Check().tcp(host, port, "5s", "5s", "60s"))
check=consul.Check().tcp(host, port, "5s", "5s"))
def register_service_with_deregister(self, name, host, port, tags=None):
tags = tags or []
# 注册服务
id = "{}_{}_{}".format(name, host, port)
return self.consul.agent.service.register(
name,
id,
host,
port,
tags,
# 健康检查ip端口,检查时间:5,超时时间:30,注销时间:15s
# 意思是15秒后 还是检测失败就自动注销掉服务
check=consul.Check().tcp(host, port, "5s", "5s", "15s"))
# check=consul.Check().tcp(host, port, "5s", "5s"))
def watch_service(self, service_name):
index = None
while True:
try:
last_index = index
index, d = self.consul.health.service(service_name, passing=True, index=index, wait='10s')
if last_index == index or last_index == None:
print('没变')
continue
print("变了")
data = d
new_nodes = []
num = len(data)
print(data)
for num_index, x in enumerate(data):
Service = x.get("Service")
address = Service.get("Address")
id = Service.get("ID")
msg = "[alive_node_detail][num:{}/{}][addr:{}][id:{}]".format(
num_index + 1,
num,
address,
id
)
print(msg)
logging.info(msg)
if address:
new_nodes.append(address)
except Exception as e:
logging.error("[watch_error,service:{},error:{}]".format(service_name, e))
time.sleep(5)
continue
if __name__ == '__main__':
c_host = '192.168.3.50'
c_port = 8500
c = ConsulWork(c_host, c_port)
# c.watch_service('pushgateway')
c.watch_service('pgw')
# step 1
"""
首先能看到正常的两个节点信息
2021-04-02 19:20:05 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 19:20:05 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
"""
# step 2
# 此时到机器上停止一台pushgateway服务
# systemctl stop pushgateway
"""
可以看到 只有一个存活节点
2021-04-02 19:21:08 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:1/1][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
"""
# step 3
# 此时到机器上启动pushgateway服务
# systemctl start pushgateway
"""
可以看到 又打印两个节点信息了
2021-04-02 19:22:08 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 19:22:08 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
"""
# step 4
# 过了5分钟发现又打印了一遍两个节点
# 这是因为consul.health.service函数有个 wait参数代表本地请求的最长时间等待时间,默认为5分钟
# 所以我们需要根据index 是否变换判断节点到底变没变
# *wait* the maximum duration to wait (e.g. '10s') to retrieve
# a given index. this parameter is only applied if *index* is also
# specified. the wait time by default is 5 minutes.
"""
2021-04-02 19:48:16 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 19:48:16 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
2021-04-02 19:53:17 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 19:53:17 INFO 005watch服务.py watch_service [line:99]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
"""
"""
没变
2021-04-02 20:01:25 INFO 005watch服务.py watch_service [line:105]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 20:01:25 INFO 005watch服务.py watch_service [line:105]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
没变
2021-04-02 20:01:35 INFO 005watch服务.py watch_service [line:105]:[alive_node_detail][num:1/2][addr:172.20.70.205][id:pushgateway_172.20.70.205_9091]
2021-04-02 20:01:35 INFO 005watch服务.py watch_service [line:105]:[alive_node_detail][num:2/2][addr:172.20.70.215][id:pushgateway_172.20.70.215_9091]
"""
将pushgateway 服务改造成consul 服务发现模式
- job_name: 'pushgateway'
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
consul_sd_configs:
- server: 172.20.70.205:8500
services:
- pushgateway
relabel_configs:
- source_labels: ["__meta_consul_dc"]
target_label: "dc"
基于文件的服务发现配置
之前配置了很多个traget
- redis
- mysql
- grafana
- pushgateway
…
文档地址
- https://prometheus.io/docs/prometheus/latest/configuration/configuration/#file_sd_config
特点
- 基于文件的服务发现提供了一种配置静态目标的更通用的方法
- 并充当了插入自定义服务发现机制的接口。
- 摆脱对特定服务发现源的依赖
- 只要能正确给出 json/yaml文件即可
- 和服务树的最好匹配方案
文件类型
- yaml
YAML yaml - targets: [ - '<host>' ] labels: [ <labelname>: <labelvalue> ... ]
- json
json [ { "targets": [ "<host>", ... ], "labels": { "<labelname>": "<labelvalue>", ... } }, ... ]
文件路径 支持通配符
文件刷新间隔 refresh_interval
prometheus配置样例
- job_name: 'ECS'
scrape_interval: 30s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
honor_timestamps: false
file_sd_configs:
- files:
- /App/prometheus/sd/file_sd_by_prome_shared.json
refresh_interval: 5m
json文件样例
[
{
"targets": [
"172.20.70.205:9100"
],
"labels": {
"name": "prometheus-storage-01",
"account": "aliyun-01",
"region": "ap-south-1",
"env": "prod",
"group": "inf",
"project": "monitor",
"stree_gpa": "inf.monitor.prometheus"
}
},
{
"targets": [
"172.20.70.205:9091",
"172.20.70.215:9091"
],
"labels": {
"account": "aliyun-01",
"region": "ap-south-2",
"env": "prod",
"group": "inf",
"project": "middleware",
"stree_gpa": "inf.middleware.kafka"
}
}
]
下面来解读一下
- targets 是一组实例地址的列表
- labels 是这组实例的标签,应用到列表中所有实例
- 如果想每个实例不同的标签,可以将targets列表保留一个实例即可
- 标签可以自定义,下面举几个例子
- account 代表公有云账户,多账户情况
- region 代表区域
- env 代表所属环境 prod代表生产,pre代表预发,test代表测试
- group代表业务大组
- project 代表项目
- stree_gpa 代表服务树三级标签
- 那么prometheus在采集对应target时就会将对应标签打入其metrics中
- 为后续我们按照标签过滤提供方便