介绍
在上一篇讲解 Docker 接入的服务发现文章《Prometheus 服务发现:Docker》,我们介绍了 Prometheus 接入 Docker 的容器服务,过滤和发现了需要采集的容器,支持了 Docker Daemon、Docker Compose、Docker Swarm 的架构覆盖,也额外提到了当下云原生的容器监控的核心目标,还是容器编排王座上的 Kubernetes。也正因为 Kubernetes 的体量比较大,所以我们会分两个篇章来介绍,第一篇会详细讲解 Prometheus 对 Kubernetes 服务发现的基础用法,第二篇章会介绍常见的场景分享。这是第一篇,以 Docker 的服务发现作为引子,我们将会在这一篇文章中,一步步地讲解 Prometheus 对 Kubernetes 服务发现的原理,和经常遇到的监控问题,并给出一定的方案建议。
接触或使用过 Kubernetes (K8S) 的同学,一定对 kubeclt 这个官方出品的管理工具不会陌生,我们介绍 Prometheus 的服务发现时,也会结合这个工具,对比起来介绍,方便你能够从二者的关联性出发,更快地理解 Prometheus 的相关原理。
对比 kubectl 的 Watch:思考 Prometheus 如何设计类似的服务发现。 对比 kubectl 的 cli-runtime
库:研究 Prometheus 如何寻找近似的方式请求 Kubernetes。对比 kubectl 的认证方式:让 Prometheus 服用同样的模式完成认证。 对比 kubectl 的资源结果:理解 Prometheus 对各类资源的标签发现来源。
话不多说,我们开始今天的内容。
Kubernetes
Kubernetes 的体系非常庞大,单独把监控拎出来,涉及的知识面和案例也都错综复杂,为了全面吃透 Prometheus + Kubernetes
的这套组合,我们会花一点功夫,首先来剖析 Kubernetes 是如何支持资源 (Resources) 发现的,认识到 Prometheus 利用相同的机制去监听这部分资源,然后转化为一些列的标签集合,传递到采集流程。我们依旧会跟之前的文章一样,通过一系列的实验,演示和分析具体的功能,为此,我们将使用 KinD (K8S in Docker
) 部署一个精简的 Kuberntes 环境。
部署 KinD
我选用了一台 Ubuntu VM 作为测试主机,KinD 的模式是让 Kubernetes 运行在 Docker 内,为此,我们需要首先部署 Docker,在《Prometheus 服务发现:Docker》介绍了详细的部署过程。另外,我们要为 Docker 添加一些额外的设置,我使用的是多网卡的 VM,因此额外指定了容器网络使用的网卡 IP (192.168.1.21
),并且选择使用 systemd
的 cGroup。
cat > /etc/docker/daemon.json << EOF
{
"ip": "192.168.1.21",
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
systemctl restart docker.service
接着使用 KinD 构建一个 1*master + 2*worker
的 Kubernetees 集群,版本为 v1.27.3
。KinD 是 Kubernetes in Docker
的模式,因此我的们的本机并不是 Kubernetes 的 node
,所以不能直接连通集群内的 Pod 网络,所以,在初始化集群时,通过 extraPortMappings
将控制面节点的 32000 暴露到本机的 3128,这是 docker-proxy
完成的转发。
# 下载 kubectl
curl -L https://storage.googleapis.com/kubernetes-release/release/v1.27.3/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl
chmod +x /usr/local/bin/kubectl
# 下载 KinD
curl -L https://github.com/kubernetes-sigs/kind/releases/download/v0.20.0/kind-linux-amd64 -o /usr/local/bin/kind
chmod +x /usr/local/bin/kind
# 创建集群定义文件
cat > cls.yaml << EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
podSubnet: "172.0.0.0/16"
serviceSubnet: "172.255.0.0/16"
kubeProxyMode: ipvs
nodes:
- role: control-plane
image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
extraPortMappings:
- containerPort: 32000
hostPort: 3128
- role: worker
image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
- role: worker
image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72
EOF
# 应用配置创建集群
kind create cluster --config cls.yaml --name cls
KinD 部署集群完成后,我们可以使用下载的 kubectl
工具来使用我们的精简 Kubernetes 集群。
# 切换 kubeconfig 的 context 到部署的集群
kubectl cluster-info --context kind-cls
# 查看节点
kubectl get nodes
# 查看所有容器
kubectl get pod -A
如果一切顺利,那么会查询到以下的结果。
Kubernetes 的工作流程非常复杂,为了快速理解 Prometheus 如何应用其中的发现原理,我们将通过浅入深出的过程,渐渐掌握 Kubernetes 提供资源暴露和 Prometheus 接入发现的原理。我们先使用最常用的 kubectl 工具,分别在两个窗口执行如下的命令,观察在创建一个 Deployment 时,kubectl watch
了哪些事件 (Events) 和资源 (Resources)。
# 窗口1: 使用 kubectl 监听 default 命名空间下 Pod 资源的操作
kubectl get pods --watch --output-watch-events
# 窗口2: 在 default 命名空间创建一个3副本的 Deployment,然后缩容到2副本
kubectl create deployment blackbox-exporter --image=quay.io/prometheus/blackbox-exporter:latest --port=9115 --replicas=3
kubectl scale --replicas=2 deployment blackbox-exporter
在创建完 Deployment 后,watch
的监听里输出了这些 Pod 副本的动作,而且当副本缩容后,其中一个 Pod 的删除也被记录其中。
我们在窗口 1查看创建的 Pod,也能发现与窗口 2的信息一致,有三个 Pod 被成功创建,然后有一个 Pod 被删除。
这个 watch
能够获取到监听资源的变化事件,如果 Prometheus 可以像 kubectl 这样接入到 Kubernetes,那么服务发现也就不成问题了。如果让我们来设计 Prometheus 对 Kubernetes 的服务发现,也许会选择如下的方案:
首次接入到集群,执行 kubectl get --watch --output-watch-events
类似的动作,获取全部的资源信息,开始采集。监听资源的变化事件,实时将目标增量同步到采集流程。
为了实现这个需求,我们来参考一下 kubectl 是怎么实现的,这里简单分析 kubectl 的源码。
在 watch
方法中构造了请求,获得资源信息 (resource.Info
),这里的请求操作位于 k8s.io/cli-runtime
,这是专门为 kubectl 构建的项目,我们简单地看一下实现细节。
兴许我们可以使用同样的方式,对 Kubernetes 的 APIServer 构造类似的 Request,获取全量的资源信息后,持续的监听变化情况。那么,是否有与 k8s.io/cli-runtime
职能相同,但是更加通用的公共库来实现这种能力?答案是有的,Kubernetes 官方为需要接入 Kubernetes 的外部服务,提供了 k8s.io/client-go
库来支持协议的交互,高度的封装也让我们不需要像 kubeclt 那般处理太多内部逻辑。
Prometheus 内置了对 Kubernetes 的服务发现模块 kubernetes_sd_configs
,我们来分析一下 Prometheus 怎么通过 client-go
来完成 kubeclt 的 --watch --output-watch-events
操作。我们单独以 Pod 资源的服务发现机制来说明,构造器的代码非常少,关键之处是使用 AddEventHandler
将 Pod 的 Add
、Delete
、Update
的事件添加处理函数,其实内部的操作都是一致的,就是将新的对象传入队列内。
在主方法 Run
中,主要通过 process
来监听这个队列的数据,如果不在全量存储 (store
) 里,就将构建 Pod 同名标识 (Source
) 的空标签集合,发送到采集流程,相关的 Pod 也会从采集中被剔除。后续的子流程 buildPod
也会将一些无效的 (如预备中、未分配 IP 和被驱逐等状态) Pod 拦截并清理,最终,把正常的更新和新增的 Pod,提取关联的标签信息,组成 targetgroup
同步至采集流程。
除了 Pod 外,Prometheus 现有的 Kubernetes 服务发现的资源,还包括 EndpointSlice、Endpoint、Service、Ingress、Node,但都是采用了与 Pod 一致的逻辑,使用 client-go
的封装方法,把发现的元数据信息同步到采集,让采集资源池去请求对应的资源地址。所以,我们从 kubectl 出发,分析出其内部采用了 cli-runtime
实现资源获取和监听,再到 Prometheus 使用 client-go
完成资源对象的发现和同步,也大概地知道了 Prometheus 是如何凭借 Kubernetes 底座的能力,来发现所需采集的目标。接下来,我们就以实际场景,更加深入地认清这一块内容。
服务发现
Prometheus 对 Kubernetes 主要使用 kubernetes_sd_configs
模块来进行服务发现,我们会根据后续几个通用场景来介绍服务发现的配置和细节。
初步接入: 完成基础的 Pod 监控发现。 代理模式: 如何使用集群私有网络进行采集。 范围覆盖: 通过过滤条件筛选其他发现目标。 标签转换: 对发现的 Kubernetes 资源完成标签提取和转换。
初步接入
我们知道,kubectl 默认使用 kubeconfig
来接入到对应的 APIServer,完成 RESTful API 的认证,再调用相关的 API 来执行最终的实际操作。如果没有额外的定制,kubectl 使用的 kubeconfig
一般都在 ~/.kube/config
,我们将其拷贝到 Prometheus 的软件目录,以待使用。
mkdir -p /srv/prometheus/sd/kubernetes
cp ~/.kube/config /srv/prometheus/sd/kubernetes/kubeconfig.yml
chown -R prometheus:prometheus /srv/prometheus/sd/kubernetes
注意:为了复用集群的私有网络,这里的 Prometheus 运行在 KinD 的同一台服务器上,后续我们会说明如何从外部对私有网络进行采集的方案。
Pod 是 Kubernetes 里最小粒度的服务单元,也是业务应用监控最关键的一环,接下来,我们为 Prometheus 制订一个简单发现 Pod 的配置。
# /srv/prometheus/prometheus.yml
scrape_configs:
- job_name: kubernetes-cls
# 使用 Kubernetes 的服务发现模块
kubernetes_sd_configs:
# 添加一个发现 Pod 资源的服务发现
- role: pod
# 注入 kubeconfig 文件路径
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
使用 /targets
API 能够看到发现了所有的 Pod 信息:
curl -ks 127.0.0.1:9090/api/v1/targets | jq
我们单独拿 Kubernetes 的 coredns 容器来看,这里通过 __meta_kubernetes_pod_container_name
和 __meta_kubernetes_pod_container_port_name
标签过滤出两个标签集合:
discoveredLabels
中所有 __meta_kubernetes_
为前缀的标签,都是发现模块从 Kubernetes 的 Pod 数据提取而来,我们来看一下这个 Pod 在 Kubernetes 的信息。
可见,这些发现的键值对都是从 Pod 的自身信息转化而来,在 Prometheus 发现 Pod 的方式中采用如下的发现机制。
Pod 发现:发现 Pod 的 .status.podIP
,并从 .spec.containers
内每个 .ports
提取 TCP 的端口号,一起组成一个 __address__
作为 Target。如果没有额外声明容器端口,则使用空的默认 80 端口,在将必要的容器信息转换为标签发现。
Pod 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_namespace |
.metadata.namespace |
Pod 所属的 Namespace 名称 |
__meta_kubernetes_pod_name |
.metadata.name |
Pod 的名称 |
__meta_kubernetes_pod_ip |
.status.podIP |
Pod 分配的 IP 地址 |
__meta_kubernetes_pod_label_<labelname> |
.metadata.labels.<labelname> |
Pod 标签的键值对 |
__meta_kubernetes_pod_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 Pod 中存在,则值为 true |
__meta_kubernetes_pod_annotation_<annotationname> |
.metadata.annotations.<annotationname> |
Pod 注解的键值对 |
__meta_kubernetes_pod_annotationpresent_<annotationname> |
.metadata.annotations.<annotationname> |
如果注解名在 Pod 中存在,则值为 true |
__meta_kubernetes_pod_container_init |
.status.initContainerStatuses |
如果该容器是 Pod 的初始化容器,则值为 true |
__meta_kubernetes_pod_container_name |
`.spec.containers | .[].ports.[].name` |
__meta_kubernetes_pod_container_id |
.status.containerStatuses.[].containerID |
此 Pod 下该容器的 ID |
__meta_kubernetes_pod_container_image |
.status.containerStatuses.[].image |
此 Pod 下该容器的镜像 |
__meta_kubernetes_pod_container_port_name |
.spec.containers.[].ports.[].name |
此 Pod 下该容器端口的名称 |
__meta_kubernetes_pod_container_port_number |
.spec.containers.[].ports.[].containerPort |
此 Pod 下该容器端口的端口号 |
__meta_kubernetes_pod_container_port_protocol |
.spec.containers.[].ports.[].protocol |
此 Pod 下该容器端口的协议 |
__meta_kubernetes_pod_ready |
.status.containerStatuses.[].ready |
Pod 是否准备完毕 |
__meta_kubernetes_pod_phase |
.status.phase |
Pod 的运行状态 |
__meta_kubernetes_pod_node_name |
.spec.nodeName |
运行此 Pod 的 node 名称 |
__meta_kubernetes_pod_host_ip |
.status.hostIP |
运行此 Pod 的 node 的主机 IP |
__meta_kubernetes_pod_uid |
.metadata.uid |
此 Pod 在内部的唯一标识 ID |
__meta_kubernetes_pod_controller_kind |
.metadata.ownerReferences.[].kind |
负责控制此 Pod 的调度类型 |
__meta_kubernetes_pod_controller_name |
.metadata.ownerReferences.[].name |
负责控制此 Pod 的调度名称 |
代理模式
Kubernetes 的网络方案里,会为 Pod 分配私有的内部子网,一般而言,在集群外是没有路由能够访问到这个私有网络的。当然,如果使用 CNI 管理 SDN 路由,或者将集群内的路由反射出去,能够实现 Pod 容器可达 IP 的效果,不过,我们这里依旧还是讨论常规模式下的网络隔离问题。你也许注意到,即便在同一台主机,但是当前 Prometheus 仍无法对发现的 Pod 发起正常采集,因为这些 Pod 是在 KinD 拉起的 Docker 容器平面内。为了在这种情况下,可以将发现的 Pod 容器进行正常的采集,并考虑到采集使用了 HTTP 协议,我们可以在集群内部署一个正代服务,选择 Nginx、Squid、TinyProxy 等方案,既可以部署在 node 的 OS 上,也可以部署 Deployment 内。
我们这里将 Squid 部署到 Kubernetes 的 Deployment,使用 nodePort
的方式把服务暴露出来,将采集请求建立成功。我们在 KinD 部署的主机上,准备以下的 manifest
:
# squid.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: squid
namespace: squid
spec:
replicas: 1
selector:
matchLabels:
app: squid
template:
metadata:
labels:
app: squid
spec:
containers:
- name: squid
image: ubuntu/squid:latest
ports:
- containerPort: 3128
name: squid
protocol: TCP
volumeMounts:
- name: squid-config-volume
mountPath: /etc/squid/squid.conf
subPath: squid.conf
volumes:
- name: squid-config-volume
configMap:
name: squid-config
items:
- key: squid
path: squid.conf
准备 Squid 的配置文件,注意,这里是放开所有的访问,如果你在生产环境采用这个模式,请配置严格的 ACL 策略。
acl all src 0.0.0.0/0.0.0.0
http_access allow all
http_access deny all
http_port 3128
然后,将其部署到 squid 的命名空间:
kubectl create namespace squid
kubectl -n squid create configmap squid-config --from-file=squid=squid.conf
kubectl -n squid apply -f squid.yaml
# 这里 nodePort 选择的 node 端口,正是我们 KinD 转发到外部的端口号
kubectl -n squid create service nodeport squid --tcp=3128:3128 --node-port=32000
# 测试 Squid 服务
curl -skIv 127.0.0.1:3128
我们把 Pod 的发现配置,添加使用正代的 proxy_url
的字段:
# /srv/prometheus/prometheus.yml
scrape_configs:
- job_name: kubernetes-cls
# 配置采集的 HTTP 请求使用的正代地址
proxy_url: "http://127.0.0.1:3128"
kubernetes_sd_configs:
- role: pod
# 如果连接 APIServer 的服务发现请求也需要使用正代,则在此配置
# proxy_url: ""
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
重新 reload
后,我们在 Graph 上能够看到能够正常响应采集的 Pod,up
指标都变为正常的 1
了。
因为考虑到 KinD 的部署方便,所以接下来我们都会使用这种正代方式来采集容器内的目标,但它可能在其他环境里不是必须的。
范围覆盖
我们已经知道把 Pod 发现出来的方法了,但是 Kubernetes 还有多种的资源对象,不单单只有 Pod,下面列举目前 kubernetes_sd_configs
支持以下的 role
。
Prometheus role | K8S Resource | Comment |
---|---|---|
node |
nodes |
集群的节点信息,一般用来获取 node 的 IP |
service |
services |
Service 暴露的端口列表,一般做服务拨测监控 |
pod |
pods |
Pod 内的容器端口列表,一般做容器指标采集 |
endpoints |
endpoints |
Service 内的 Endpoints 列表,一般做服务指标采集 |
endpointslice |
endpointslices |
一种新的 endpoints 信息,可以更高效地发现 Service 的关联端点组 |
ingress |
ingresses |
Ingress 地址,一般做服务拨测监控 |
我们按顺序来讲解除 Pod 外的其他 role
,来分析它们的发现逻辑和标签转化关系。
node
node 发现:发现 Node 资源,从 .status.addresses
提取到 InternalIP 类型的 address
字段作为目标 IP,使用 kubelet 的端口信息 .status.daemonEndpoints.kubeletEndpoint.Port
作为目标的端口 Port,从而生产 IP:Port
作为目标地址 __address__
。
node 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_node_name |
.metadata.name |
Node 的名称 |
__meta_kubernetes_node_provider_id |
.spec.providerID |
在供应方的 ID,一般在云服务场景使用 |
__meta_kubernetes_node_label_<labelname> |
.metadata.labels.<labelname> |
Node 标签的键值对 |
__meta_kubernetes_node_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 Node 中存在,则值为 true |
__meta_kubernetes_node_annotation_<annotationname> |
.metadata.annotations |
Node 注解的键值对 |
__meta_kubernetes_node_annotationpresent_<annotationname> |
.metadata.annotations |
如果注解名在 Node 中存在,则值为 true |
__meta_kubernetes_node_address_<address_type> |
.status.addresses.[].type |
Node 地址信息,type 为 <address_type> ,值为对应的地址 |
配置 Prometheus 发现 node
:
scrape_configs:
- job_name: kubernetes-cls
# 直接采集 Node 在 KinD 内不需要 proxy
kubernetes_sd_configs:
- role: node
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
可以看到,Prometheus 发现了集群内的所有 node,采集地址为 kubelet 的监听地址,但是所有的采集都是 down
状态,这是因为我们的 kubelet 关闭了匿名认证模式,对非认证请求做了拒绝。为了能够在这个条件下正常采集 kubelet,我们可以设置一个专门给 Prometheus 采集使用的 ServiceAccount 来通过认证。
# 创建监控专用的命名空间
kubectl create namespace monitoring
# 创建采集 kubelet 的 SA
kubectl -n monitoring create serviceaccounts prometheus
# 为该 SA 绑定 system:kubelet-api-admin 角色
kubectl create clusterrolebinding prometheus --user=prometheus --clusterrole=system:kubelet-api-admin --serviceaccount=monitoring:prometheus
# 获取该 SA 的 Token
TOKEN=$(kubectl -n monitoring create token prometheus)
# 打印获得的 token
echo $TOKEN
我们把这个 Token 加入到采集配置:
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: node
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
# kubeclt API 使用 HTTPS 协议,因此要指定使用 https
scheme: https
tls_config:
# 因为非权威自签 CA,这里选择忽略安全
insecure_skip_verify: true
# 将入采集时使用的 Token
authorization:
credentials: eyJhbGciOiJSUzI1NiIsImtpZCI6IlEwemZJbWI0WVVGQkp1LXQwZThsUU90R2RHLXFzSGdpXzBVZnBwTjZxY2sifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzAxMzQwMDUxLCJpYXQiOjE3MDEzMzY0NTEsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJtb25pdG9yaW5nIiwic2VydmljZWFjY291bnQiOnsibmFtZSI6InByb21ldGhldXMiLCJ1aWQiOiI1MTYwYjk2Yy0xYjhmLTRhOTQtYmJlNC1hNDIzY2M4MjZiNmMifX0sIm5iZiI6MTcwMTMzNjQ1MSwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Om1vbml0b3Jpbmc6cHJvbWV0aGV1cyJ9.eNZO4EdTrXm2E-cbHNd25RNka09rWt3Zf9SznwnAFLZ1FgwvDwd7-_mWF0cgHQJkxrwtuq8fEJ5r-ePxg93pSNviMMfJflCtOPd4TfRcCIgjEk34lTFZzpG-zMSamg26icaAu6mzRhm26SwNh1YNbMnUWbt2SLsA143y9_x3BPcnzE_KxcxcfHs4F0OwngrserpEhyaoGKwf1Mwck7o-DMbqhpHoE-5mT10ApW88Z_YZuG9A1Z6mLKBktossijPBOwiIPS_QjHZ7dRdyvaia9mf-iA7pZkii5yS335mL9ktQ5_yv7A3G_cKGNgzHx3nGFPpE3gp_wsVPo5lqWyLVNA
查看采集结果,能看到 up
指标正常激活。
这里接入 Node 时,我们又重新使用了在介绍采集配置《Prometheus 监控使用:采集详解》时提到的的几个老朋友字段(scheme
、tls_config
、authorization
),希望你还能记得它们。另外,我们也分享了如何在 1.27
版本的 Kubernetse 里创建采集 kubelet 的 Token 的方式,注意,Kubernetes 在 1.24
版本之前,创建了 ServiceAccount 会默认分配关联的 <ServiceAccount>-token
Secret,这里有一个差异需要提醒你知道。
service
service 发现:发现 Service 资源,从 .metadata.name
获取服务的名称 Name,从 .metadata.namespace
获取所在的命名空间 Namespace,遍历 .spec.ports
获取 TCP 的端口 Port,最终根据 Kubernetes 的 FQDN 格式拼接为 <Name>.<Namespace>.svc:<Port>
作为 __address__
发现给采集流程。
service 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_namespace |
.metadata.namespace |
Service 所属的 Namespace 名称 |
__meta_kubernetes_service_annotation_<annotationname> |
.metadata.annotations.<annotationname> |
Service 注解的键值对 |
__meta_kubernetes_service_annotationpresent_<annotationname> |
.metadata.annotations.<annotationname> |
如果注解名在 Service 中存在,则值为 true |
__meta_kubernetes_service_cluster_ip |
.spec.clusterIP |
Service 的集群 IP 地址,可能是 serviceSubnet 分配或者外部 LB 地址 |
__meta_kubernetes_service_loadbalancer_ip |
.spec.loadBalancerIP |
Service 接入的 LB 地址 |
__meta_kubernetes_service_external_name |
.spec.externalName |
接入的外部地址,可以供外部地址转发到服务 |
__meta_kubernetes_service_label_<labelname> |
.metadata.labels.<labelname> |
Service 标签的键值对 |
__meta_kubernetes_service_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 Service 中存在,则值为 true |
__meta_kubernetes_service_name |
.metadata.name |
Service 的名称 |
__meta_kubernetes_service_port_name |
.spec.ports.[].name |
端口列表里当前端口的名称 |
__meta_kubernetes_service_port_number |
.spec.ports.[].port |
端口列表里当前端口的端口号 |
__meta_kubernetes_service_port_protocol |
.spec.ports.[].protocol |
端口列表里当前端口的协议 |
__meta_kubernetes_service_type |
.spec.type |
Service 类型 |
配置 Prometheus 发现 node
:
scrape_configs:
- job_name: kubernetes-cls
proxy_url: "http://127.0.0.1:3128"
kubernetes_sd_configs:
- role: service
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
此时,Prometheus 将所有的 Service 的 TCP 协议端口都发现了,但是我们很少直接去采集服务的指标,在 Endpoint 后端,它们可能转发了多个 Pod。所以,我们通常会使用拨测的形式,对发现的服务地址进行检测,保证对外服务的有效性,并不会直接去采集服务的指标,这一点,我们会在下面的场景分享中详细说道说道。
endpoints
在 Kubernetes 内,Endpoints 是负责将 Service 关联到具体 Pod 的资源对象,通过选择条件 (Selector) 来过滤需要关联的 Pod,承接对应 Service 的请求。因此,不同于上述的 Service,Endpoint 可以具体到服务提供的单元,因此更有监控的意义。在实际应用中,Endpoints 也是核心的监控对象,毕竟 Pod 可能有功能性的组件,如 SideCar 服务或者 Kubernetes 自身的 Job Container 等,但 Endpoints 关联的 Pod,一定是需要响应 Service 引入的服务请求,监控的覆盖价值更大。
endpoints 发现:发现 Endpoints 资源,从 .subsets.addresses.[].targetRef
中拿到关联 Pod 的 Namespace 与 Name,然后在该命名空间内获取 Pod 资源,提取 Pod 的 .status.podIP
作为地址的 IP,再从 Pod 的容器端口列表遍历与 Endpoint 对应的端口号 Port,最终组成 <IP>:<Port>
作为发现目标的 __address__
标签。注意,Endpoint 一端是 Service,所以自然会去获取对应 Service 的信息,也会同步到发现标签内,而另一端 Endpoint 的 targetRef
,一般都是 Pod,所以会重新去获取这些关联 Pod 的信息,补充到发现标签内。
endpoints 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_pod_* |
- |
与 role: pod 一致 |
__meta_kubernetes_service_* |
- |
与 role: service 一致 |
__meta_kubernetes_namespace |
.metadata.namespace |
Endpoint 所属的 Namespace 名称 |
__meta_kubernetes_endpoints_name |
.metadata.name |
Endpoint 的名称 |
__meta_kubernetes_endpoints_label_<labelname> |
.metadata.labels.<labelname> |
Endpoint 标签的键值对 |
__meta_kubernetes_endpoints_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 Endpoint 中存在,则值为 true |
__meta_kubernetes_endpoints_annotation_<annotationname> |
.metadata.annotations.<annotationname> |
Endpoint 注解的键值对 |
__meta_kubernetes_endpoints_annotationpresent_<annotationname> |
.metadata.annotations.<annotationname> |
如果注解名在 Endpoint 中存在,则值为 true |
__meta_kubernetes_endpoint_hostname |
.subsets.[].addresses.[].hostName |
Endpoint 的主机名 |
__meta_kubernetes_endpoint_node_name |
.subsets.[].addresses.[].nodeName |
接收 Endpoint 地址的 node 名称 |
__meta_kubernetes_endpoint_ready |
`.subsets | .[].notReadyAddresses` |
__meta_kubernetes_endpoint_port_name |
- |
从 Pod 的容器端口中获取名称 |
__meta_kubernetes_endpoint_port_protocol |
- |
从 Pod 的容器端口中获取协议 |
__meta_kubernetes_endpoint_address_target_kind |
.subsets.[].addresses.[].targetRef.kind |
关联后端的类型,大部分为 Pod |
__meta_kubernetes_endpoint_address_target_name |
.subsets.[].addresses.[].targetRef.name |
关联后端的名称 |
使用以下的配置进行采集:
scrape_configs:
- job_name: kubernetes-cls
proxy_url: "http://127.0.0.1:3128"
kubernetes_sd_configs:
# 使用 role 为 endpoints
- role: endpoints
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
加载后可以查询有 Pod 被采集,但是没有使用 role: pod
那么多,因为这里只有关联了 Service 的 Pod 被发现了,这些关系到服务的质量,是需要着重监控的单元。
endpointslice
看到 endpointslice,你是否困惑这长得像 endpoints 的家伙,两者之间有什么区别。我们简单的讲解一些 Kubernetes 的知识,以便理解这个 role
的使用场景。当我们使用 kubectl 列举 API 资源对象,能够看到两者的差异,其中 EndpointSlice 的 APIVERSION 是 discovery.k8s.io/v1
,可以知道其主要是更新的一种注重服务发现能力的资源。
我们查看两者的资源结果,能够看出,EndpointSlice 是封装了 Endpoints 之上的一层资源,正如名字所言是 Endpoint 的切片形式,为了减少集群在大规模时,太多 Endpoints 资源导致的臃肿问题,分拆出 Endpoints 单元,提高服务发现的性能和可伸缩性。所以,endpointslice 的服务发现结果其实与 endpoints 大同小异,如果你的 Kubernetes 版本支持,请尽可能地选择使用 endpointslice。
endpointslice 发现:与 endpoints 类似,但没有再根据 Ready
状态来过滤后端目标,而是遍历 .endpoints.[].addresses
地址提取到目标 IP,然后直接在 .ports
获取到对应的端口 Port,最终组成了 <IP>:<Port>
作为 __address__
传递给采集流程。至于关联的 Service 和 Pod,仍然会查询获取相关的信息,转化到发现标签内。
endpointslice 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_pod_* |
- |
与 role: pod 一致 |
__meta_kubernetes_service_* |
- |
与 role: service 一致 |
__meta_kubernetes_namespace |
.metadata.namespace |
EndpointSlice 所属的 Namespace 名称 |
__meta_kubernetes_endpointslice_name |
.metadata.name |
EndpointSlice 的名称 |
__meta_kubernetes_endpointslice_label_<labelname> |
.metadata.labels.<labelname> |
EndpointSlice 标签的键值对 |
__meta_kubernetes_endpointslice_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 EndpointSlice 中存在,则值为 true |
__meta_kubernetes_endpointslice_annotation_<annotationname> |
.metadata.annotations.<annotationname> |
EndpointSlice 注解的键值对 |
__meta_kubernetes_endpointslice_annotationpresent_<annotationname> |
.metadata.annotations.<annotationname> |
如果注解名在 EndpointSlice 中存在,则值为 true |
__meta_kubernetes_endpointslice_address_target_kind |
.endpoints.[].targetRef.kind |
后端目标的资源类型 |
__meta_kubernetes_endpointslice_address_target_name |
.endpoints.[].targetRef.name |
后端目标的名称 |
__meta_kubernetes_endpointslice_address_type |
.addressType |
协议簇类型 |
__meta_kubernetes_endpointslice_endpoint_conditions_ready |
.endpoints.[].conditions.ready |
目标成员的准备状态 |
__meta_kubernetes_endpointslice_endpoint_conditions_serving |
.endpoints.[].conditions.serving |
目标成员的服务状态 |
__meta_kubernetes_endpointslice_endpoint_conditions_terminating |
.endpoints.[].conditions.terminating |
目标成员的退出状态 |
__meta_kubernetes_endpointslice_endpoint_topology_<topologyname> |
.endpoints.[].deprecatedTopology.<topologyname> |
Endpoint 拓扑内容的键值对 |
__meta_kubernetes_endpointslice_endpoint_topology_present_<topologyname> |
.endpoints.[].deprecatedTopology.<topologyname> |
如果拓扑内容名在 Endpoint 中存在,则值为 true |
__meta_kubernetes_endpointslice_port |
.ports.[].port |
目标端口组的端口号 |
__meta_kubernetes_endpointslice_port_name |
.ports.[].name |
目标端口组的名称 |
__meta_kubernetes_endpointslice_port_protocol |
.ports.[].protocol |
目标端口组的协议 |
可见比 endpoints 多了更多的内部数据,并且在发现的性能和数据关联上,具有更好的表现,推荐使用这种方式来替换 endpoints。
scrape_configs:
- job_name: kubernetes-cls
proxy_url: "http://127.0.0.1:3128"
kubernetes_sd_configs:
# 使用 role 为 endpointslice
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
ingress
ingress 发现:发现 Ingress 资源,提取每个路由规则的 Host 信息,作为 __address__
传入采集流程。
ingress 标签转化的逻辑如下:
Prometheus | Kubernetes | Comment |
---|---|---|
__meta_kubernetes_namespace |
.metadata.namespace |
Ingress 所属的 Namespace 名称 |
__meta_kubernetes_ingress_name |
.metadata.name |
Ingress 的名称 |
__meta_kubernetes_ingress_label_<labelname> |
.metadata.labels.<labelname> |
Ingress 标签的键值对 |
__meta_kubernetes_ingress_labelpresent_<labelname> |
.metadata.labels.<labelname> |
如果标签名在 Ingress 中存在,则值为 true |
__meta_kubernetes_ingress_annotation_<annotationname> |
.metadata.annotations.<annotationname> |
Ingress 注解的键值对 |
__meta_kubernetes_ingress_annotationpresent_<annotationname> |
.metadata.annotations.<annotationname> |
如果注解名在 Ingress 中存在,则值为 true |
__meta_kubernetes_ingress_class_name |
.spec.ingressClassName |
Ingress 使用的控制器类型 |
__meta_kubernetes_ingress_scheme |
- |
路由规则的协议 |
__meta_kubernetes_ingress_path |
.spec.rules.[].ingressRuleValue |
路由路径 |
配置如下的采集:
scrape_configs:
- job_name: kubernetes-cls
proxy_url: "http://127.0.0.1:3128"
kubernetes_sd_configs:
# 使用 role 为 ingress
- role: ingress
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
Ingress 也是服务的入口方式,比 Service 有更高的服务提供高度,因此也往往不会直接监控采集,需要配合拨测来完成监控。
条件过滤
当你读到这里,已经表示你已经了解了 Prometheus 支持的所有 Kubernetse 服务发现资源,它们与我们使用 kubectl get -A
查询具体资源对象获取到的资源是一致的,但并不是所有场景都适合检索全部资源,对于那些超大规格的集群,一次性查询全部所有命名空间也会加大 APIServer 的性能损耗。所以,我们需要设置一些条件来限制检索范围和采集范围。
namespace 限制
按 Namespace 的维度,类似于 kubectl -n xxx get --watch
来建立 Watch,能够降低性能的消耗,加快资源检索的速率,例如我们需要查询 biz-1
和 ops-1
两个 Namespace 的服务后端 Pod,可以如下设置服务发现配置:
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
# 指定 Namespace
namespaces:
# 在此处添加需要监控的 Namespace 名称列表
names:
- 'biz-1'
- 'ops-1'
如果我们的 Prometheus 是运行在 Kubernetes 上的,可以通过 namespaces.own_namespace
字段来自动获取 Prometheus 所在的 Namespace,其原理就是去读取本地文件 /var/run/secrets/kubernetes.io/serviceaccount/namespace
,为此,我们可以如下设置额外采集我们 Prometheus 所在的 Namespace:
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
namespaces:
# 获取 Prometheus 自身所在的 Namespace,进行服务发现
own_namespace: true
names:
- 'biz-1'
- 'ops-1'
selectors 条件
Kubernets 库 client-go
的 ListWatch
内的事件处理函数,原生支持字段条件 (FieldSelector
) 和标签条件 (LabelSelector
),可以进一步减少 APIServer 的查询压力,Prometheus 利用配置中的 selectors
字段来注入条件规则。例如,对 biz-2
Namespace 下的 EndpointSlice 发现后端的 Pod 目标,这些目标的运行状态 (status.phase
) 为 Running
且满足标签 app
为 web
。
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
namespaces:
names:
- 'biz-2'
selectors:
- role: endpointslice
# 标签条件
label: 'app=web'
- role: pod
# 字段条件
field: 'status.phase=Running'
selectors
是 client-go
层面的 options
注入,需要理解一定的 kubernetes_sd_configs
代码逻辑,才能比较如预期地使用,如果你并不能从源码去分析 Prometheus 如何使用 client-go
,那么不建议你贸然使用这个条件过滤。这里举一个例子,就拿我们部署的 Squid 为例,如果你使用如下的配置来采集,会产生让人困惑的结果:EndpointSlice 后端的 Pod 正常被发现,但是少了 Pod 的标签信息。这是 endpointslice
模式发现流程顺序的原因,先发现到对应的 EndpointSlice Address 后再查询 Pod 来补充标签信息,而 Pod 的 LabelSelector 不满足条件,因而缺失了这些标签,但依旧会使用 EndpointSlice Address 发现的标签来产生服务发现的结果。
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
namespaces:
names:
- 'squid'
selectors:
- role: pod
# 这里使用不存在的字段为条件
label: 'app=not-exists'
field: 'status.phase=Running'
但是,我们依旧会有类似的需求,那该如何实现呢?这个时候,希望你能想起《Prometheus 监控使用:Relabel》里介绍的 relabel
功能。
relabel 过滤
relabel_configs
可以控制 Targets 的保留 (Keep
) 与丢弃 (Drop
) 的行为,我们可以使用这些 action
来实现上面提到的,如何过滤 Squid EndpointSlice 的需求。
scrape_configs:
- job_name: kubernetes-cls
kubernetes_sd_configs:
- role: endpointslice
kubeconfig_file: /srv/prometheus/sd/kubernetes/kubeconfig.yml
namespaces:
names:
- 'squid'
relabel_configs:
- action: keep
source_labels: [__meta_kubernetes_pod_label_app]
regex: 'not-exists'
- action: keep
source_labels: [__meta_kubernetes_pod_phase]
regex: 'Running'
这样,结果就能满足我们的要求,并且不依赖 client-go
的代码实现,通过 relabel
简单易懂的链式动作,完成多种多样的组合条件。我们查看 /targets
API 能够发现,不满足条件的 Pod 目标已经被丢弃:
{
"status": "success",
"data": {
"activeTargets": [],
"droppedTargets": [
{
"discoveredLabels": {
"__address__": "172.0.1.2:3128",
"__meta_kubernetes_endpointslice_address_target_kind": "Pod",
"__meta_kubernetes_endpointslice_address_target_name": "squid-77df85f4b9-kfsgt",
"__meta_kubernetes_endpointslice_address_type": "IPv4",
"__meta_kubernetes_endpointslice_annotation_endpoints_kubernetes_io_last_change_trigger_time": "2023-11-30T09:42:15Z",
"__meta_kubernetes_endpointslice_annotationpresent_endpoints_kubernetes_io_last_change_trigger_time": "true",
"__meta_kubernetes_endpointslice_endpoint_conditions_ready": "true",
"__meta_kubernetes_endpointslice_endpoint_conditions_serving": "true",
"__meta_kubernetes_endpointslice_endpoint_conditions_terminating": "false",
"__meta_kubernetes_endpointslice_label_app": "squid",
"__meta_kubernetes_endpointslice_label_endpointslice_kubernetes_io_managed_by": "endpointslice-controller.k8s.io",
"__meta_kubernetes_endpointslice_label_kubernetes_io_service_name": "squid",
"__meta_kubernetes_endpointslice_labelpresent_app": "true",
"__meta_kubernetes_endpointslice_labelpresent_endpointslice_kubernetes_io_managed_by": "true",
"__meta_kubernetes_endpointslice_labelpresent_kubernetes_io_service_name": "true",
"__meta_kubernetes_endpointslice_name": "squid-6wndg",
"__meta_kubernetes_endpointslice_port": "3128",
"__meta_kubernetes_endpointslice_port_name": "3128-3128",
"__meta_kubernetes_endpointslice_port_protocol": "TCP",
"__meta_kubernetes_namespace": "squid",
"__meta_kubernetes_service_label_app": "squid",
"__meta_kubernetes_service_labelpresent_app": "true",
"__meta_kubernetes_service_name": "squid",
"__metrics_path__": "/metrics",
"__scheme__": "http",
"__scrape_interval__": "1m",
"__scrape_timeout__": "10s",
"job": "kubernetes-cls"
}
}
],
"droppedTargetCounts": {
"kubernetes-cls": 1
}
}
}
总结
Kubernets 的服务发现基础使用的内容已经介绍完了,不管是任何 Kubernetes 架构,你都已经可以使用 Prometheus 快速地自动接入,让云原生监控全面护航容器服务。在下一篇文章里,我们会继续探讨 Kubernetes 的服务发现,在一些场景的使用案例,敬请期待。
原文始发于微信公众号(AcidPool):Prometheus 服务发现:Kubernetes (上)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/181747.html