使用Prometheus监控Kubernetes集群

    下表中,梳理了监控Kubernetes集群监控的各个维度以及策略:

    Kubelet组件运行在Kubernetes集群的各个节点中,其负责维护和管理节点上Pod的运行状态。kubelet组件的正常运行直接关系到该节点是否能够正常的被Kubernetes集群正常使用。

    基于Node模式,Prometheus会自动发现Kubernetes中所有Node节点的信息并作为监控的目标Target。 而这些Target的访问地址实际上就是Kubelet的访问地址,并且Kubelet实际上直接内置了对Promtheus的支持。

    修改prometheus.yml配置文件,并添加以下采集任务配置:

    这里使用Node模式自动发现集群中所有Kubelet作为监控的数据采集目标,同时通过labelmap步骤,将Node节点上的标签,作为样本的标签保存到时间序列当中。

    重新加载promethues配置文件,并重建Promthues的Pod实例后,查看kubernetes-kubelet任务采集状态,我们会看到以下错误提示信息:

    这是由于当前使用的ca证书中,并不包含192.168.99.100的地址信息。为了解决该问题,第一种方法是直接跳过ca证书校验过程,通过在tls_config中设置 insecure_skip_verify为true即可。 这样Promthues在采集样本数据时,将会自动跳过ca证书的校验过程,从而从kubelet采集到监控数据:

    1. - job_name: 'kubernetes-kubelet'
    2. scheme: https
    3. tls_config:
    4. ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    5. insecure_skip_verify: true
    6. bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    7. kubernetes_sd_configs:
    8. - role: node
    9. relabel_configs:
    10. - action: labelmap
    11. regex: __meta_kubernetes_node_label_(.+)

    第二种方式,不直接通过kubelet的metrics服务采集监控数据,而通过Kubernetes的api-server提供的代理API访问各个节点中kubelet的metrics服务,如下所示:

    1. - job_name: 'kubernetes-kubelet'
    2. scheme: https
    3. tls_config:
    4. ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    5. bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    6. kubernetes_sd_configs:
    7. - role: node
    8. relabel_configs:
    9. - action: labelmap
    10. regex: __meta_kubernetes_node_label_(.+)
    11. - target_label: __address__
    12. replacement: kubernetes.default.svc:443
    13. - source_labels: [__meta_kubernetes_node_name]
    14. regex: (.+)
    15. target_label: __metrics_path__
    16. replacement: /api/v1/nodes/${1}/proxy/metrics

    通过relabeling,将从Kubernetes获取到的默认地址__address__替换为kubernetes.default.svc:443。同时将__metrics_path__替换为api-server的代理地址/api/v1/nodes/${1}/proxy/metrics。

    通过api-server代理获取kubelet监控指标

    通过获取各个节点中kubelet的监控指标,用户可以评估集群中各节点的性能表现。例如,通过指标kubelet_pod_start_latency_microseconds可以获得当前节点中Pod启动时间相关的统计数据。

    1. kubelet_pod_start_latency_microseconds{quantile="0.99"}

    Pod平均启动时间大致为42s左右(包含镜像下载时间):

    1. kubelet_pod_start_latency_microseconds_sum / kubelet_pod_start_latency_microseconds_count

    Pod平均启动时间

    各节点的kubelet组件中除了包含自身的监控指标信息以外,kubelet组件还内置了对cAdvisor的支持。cAdvisor能够获取当前节点上运行的所有容器的资源使用情况,通过访问kubelet的/metrics/cadvisor地址可以获取到cadvisor的监控指标,因此和获取kubelet监控指标类似,这里同样通过node模式自动发现所有的kubelet信息,并通过适当的relabel过程,修改监控采集任务的配置。 与采集kubelet自身监控指标相似,这里也有两种方式采集cadvisor中的监控指标:

    方式一:直接访问kubelet的/metrics/cadvisor地址,需要跳过ca证书认证:

    1. - job_name: 'kubernetes-cadvisor'
    2. scheme: https
    3. tls_config:
    4. ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    5. insecure_skip_verify: true
    6. bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    7. kubernetes_sd_configs:
    8. - role: node
    9. relabel_configs:
    10. - source_labels: [__meta_kubernetes_node_name]
    11. regex: (.+)
    12. target_label: __metrics_path__
    13. replacement: metrics/cadvisor
    14. - action: labelmap
    15. regex: __meta_kubernetes_node_label_(.+)

    方式二:通过api-server提供的代理地址访问kubelet的/metrics/cadvisor地址:

    1. - job_name: 'kubernetes-cadvisor'
    2. scheme: https
    3. tls_config:
    4. ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
    5. bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
    6. kubernetes_sd_configs:
    7. - role: node
    8. relabel_configs:
    9. - target_label: __address__
    10. replacement: kubernetes.default.svc:443
    11. - source_labels: [__meta_kubernetes_node_name]
    12. regex: (.+)
    13. target_label: __metrics_path__
    14. replacement: /api/v1/nodes/${1}/proxy/metrics/cadvisor
    15. - action: labelmap
    16. regex: __meta_kubernetes_node_label_(.+)

    使用api-server代理

    为了能够采集集群中各个节点的资源使用情况,我们需要在各节点中部署一个Node Exporter实例。在本章的“部署Prometheus”小节,我们使用了Kubernetes内置的控制器之一Deployment。Deployment能够确保Prometheus的Pod能够按照预期的状态在集群中运行,而Pod实例可能随机运行在任意节点上。而与Prometheus的部署不同的是,对于Node Exporter而言每个节点只需要运行一个唯一的实例,此时,就需要使用Kubernetes的另外一种控制器Daemonset。顾名思义,Daemonset的管理方式类似于操作系统中的守护进程。Daemonset会确保在集群中所有(也可以指定)节点上运行一个唯一的Pod实例。

    创建node-exporter-daemonset.yml文件,并写入以下内容:

    1. apiVersion: extensions/v1beta1
    2. kind: DaemonSet
    3. metadata:
    4. name: node-exporter
    5. spec:
    6. template:
    7. metadata:
    8. annotations:
    9. prometheus.io/scrape: 'true'
    10. prometheus.io/port: '9100'
    11. prometheus.io/path: 'metrics'
    12. labels:
    13. app: node-exporter
    14. name: node-exporter
    15. spec:
    16. containers:
    17. imagePullPolicy: IfNotPresent
    18. name: node-exporter
    19. - containerPort: 9100
    20. hostPort: 9100
    21. name: scrape
    22. hostNetwork: true
    23. hostPID: true

    由于Node Exporter需要能够访问宿主机,因此这里指定了hostNetwork和hostPID,让Pod实例能够以主机网络以及系统进程的形式运行。同时YAML文件中也创建了NodeExporter相应的Service。这样通过Service就可以访问到对应的NodeExporter实例。

    查看Daemonset以及Pod的运行状态

    1. $ kubectl get daemonsets
    2. NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
    3. node-exporter 1 1 1 1 1 <none> 15s
    4. $ kubectl get pods
    5. NAME READY STATUS RESTARTS AGE
    6. ...
    7. node-exporter-9h56z 1/1 Running 0 51s

    由于Node Exporter是以主机网络的形式运行,因此直接访问MiniKube的虚拟机IP加上Pod的端口即可访问当前节点上运行的Node Exporter实例:

    1. $ minikube ip
    2. 192.168.99.100
    3. $ curl http://192.168.99.100:9100/metrics
    4. ...
    5. process_start_time_seconds 1.5251401593e+09
    6. # HELP process_virtual_memory_bytes Virtual memory size in bytes.
    7. # TYPE process_virtual_memory_bytes gauge
    8. process_virtual_memory_bytes 1.1984896e+08

    目前为止,通过Daemonset的形式将Node Exporter部署到了集群中的各个节点中。接下来,我们只需要通过Prometheus的pod服务发现模式,找到当前集群中部署的Node Exporter实例即可。 需要注意的是,由于Kubernetes中并非所有的Pod都提供了对Prometheus的支持,有些可能只是一些简单的用户应用,为了区分哪些Pod实例是可以供Prometheus进行采集的,这里我们为Node Exporter添加了注解:

    1. prometheus.io/scrape: 'true'

    由于Kubernetes中Pod可能会包含多个容器,还需要用户通过注解指定用户提供监控指标的采集端口:

    1. prometheus.io/port: '9100'

    而有些情况下,Pod中的容器可能并没有使用默认的/metrics作为监控采集路径,因此还需要支持用户指定采集路径:

    1. prometheus.io/path: 'metrics'

    为Prometheus创建监控采集任务kubernetes-pods,如下所示:

    1. - job_name: 'kubernetes-pods'
    2. kubernetes_sd_configs:
    3. - role: pod
    4. relabel_configs:
    5. - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
    6. action: keep
    7. regex: true
    8. - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
    9. action: replace
    10. target_label: __metrics_path__
    11. regex: (.+)
    12. - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
    13. action: replace
    14. regex: ([^:]+)(?::\d+)?;(\d+)
    15. replacement: $1:$2
    16. target_label: __address__
    17. - action: labelmap
    18. regex: __meta_kubernetes_pod_label_(.+)
    19. - source_labels: [__meta_kubernetes_namespace]
    20. action: replace
    21. target_label: kubernetes_namespace
    22. - source_labels: [__meta_kubernetes_pod_name]
    23. action: replace
    24. target_label: kubernetes_pod_name

    在开始正式内容之前,我们需要先了解一下Kubernetes中Service是如何实现负载均衡的,如下图所示,一般来说Service有两个主要的使用场景:

    Service负载均衡

    • 代理对集群内部应用Pod实例的请求:当创建Service时如果指定了标签选择器,Kubernetes会监听集群中所有的Pod变化情况,通过Endpoints自动维护满足标签选择器的Pod实例的访问信息;
    • 代理对集群外部服务的请求:当创建Service时如果不指定任何的标签选择器,此时需要用户手动创建Service对应的Endpoint资源。例如,一般来说,为了确保数据的安全,我们通常讲数据库服务部署到集群外。 这是为了避免集群内的应用硬编码数据库的访问信息,这是就可以通过在集群内创建Service,并指向外部的数据库服务实例。

    kube-apiserver扮演了整个Kubernetes集群管理的入口的角色,负责对外暴露Kubernetes API。kube-apiserver组件一般是独立部署在集群外的,为了能够让部署在集群内的应用(kubernetes插件或者用户应用)能够与kube-apiserver交互,Kubernetes会默认在命名空间下创建一个名为kubernetes的服务,如下所示:

    1. $ kubectl get svc kubernetes -o wide
    2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
    3. kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 166d <none>

    而该kubernetes服务代理的后端实际地址通过endpoints进行维护,如下所示:

    1. $ kubectl get endpoints kubernetes
    2. NAME ENDPOINTS AGE
    3. kubernetes 10.0.2.15:8443 166d

    通过这种方式集群内的应用或者系统主机就可以通过集群内部的DNS域名kubernetes.default.svc访问到部署外部的kube-apiserver实例。

    因此,如果我们想要监控kube-apiserver相关的指标,只需要通过endpoints资源找到kubernetes对应的所有后端地址即可。

    如下所示,创建监控任务kubernetes-apiservers,这里指定了服务发现模式为endpoints。Promtheus会查找当前集群中所有的endpoints配置,并通过relabel进行判断是否为apiserver对应的访问地址:

    在relabel_configs配置中第一步用于判断当前endpoints是否为kube-apiserver对用的地址。第二步,替换监控采集地址到kubernetes.default.svc:443即可。重新加载配置文件,重建Promthues实例,得到以下结果。

    为了能够对Ingress和Service进行探测,我们需要在集群部署Blackbox Exporter实例。 如下所示,创建blackbox-exporter.yaml用于描述部署相关的内容:

    1. apiVersion: v1
    2. kind: Service
    3. metadata:
    4. labels:
    5. app: blackbox-exporter
    6. name: blackbox-exporter
    7. spec:
    8. ports:
    9. - name: blackbox
    10. port: 9115
    11. protocol: TCP
    12. selector:
    13. app: blackbox-exporter
    14. type: ClusterIP
    15. ---
    16. apiVersion: extensions/v1beta1
    17. kind: Deployment
    18. metadata:
    19. labels:
    20. app: blackbox-exporter
    21. name: blackbox-exporter
    22. replicas: 1
    23. selector:
    24. matchLabels:
    25. template:
    26. metadata:
    27. labels:
    28. app: blackbox-exporter
    29. spec:
    30. containers:
    31. - image: prom/blackbox-exporter
    32. imagePullPolicy: IfNotPresent
    33. name: blackbox-exporter

    通过kubectl命令部署Blackbox Exporter实例,这里将部署一个Blackbox Exporter的Pod实例,同时通过服务blackbox-exporter在集群内暴露访问地址blackbox-exporter.default.svc.cluster.local,对于集群内的任意服务都可以通过该内部DNS域名访问Blackbox Exporter实例:

    1. $ kubectl get pods
    2. NAME READY STATUS RESTARTS AGE
    3. blackbox-exporter-f77fc78b6-72bl5 1/1 Running 0 4s
    4. $ kubectl get svc
    5. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    6. blackbox-exporter ClusterIP 10.109.144.192 <none> 9115/TCP 3m

    为了能够让Prometheus能够自动的对Service进行探测,我们需要通过服务发现自动找到所有的Service信息。 如下所示,在Prometheus的配置文件中添加名为kubernetes-services的监控采集任务:

    1. - job_name: 'kubernetes-services'
    2. metrics_path: /probe
    3. params:
    4. module: [http_2xx]
    5. kubernetes_sd_configs:
    6. - role: service
    7. relabel_configs:
    8. - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
    9. action: keep
    10. regex: true
    11. - source_labels: [__address__]
    12. target_label: __param_target
    13. - target_label: __address__
    14. replacement: blackbox-exporter.default.svc.cluster.local:9115
    15. - source_labels: [__param_target]
    16. target_label: instance
    17. - action: labelmap
    18. regex: __meta_kubernetes_service_label_(.+)
    19. - source_labels: [__meta_kubernetes_namespace]
    20. target_label: kubernetes_namespace
    21. - source_labels: [__meta_kubernetes_service_name]
    22. target_label: kubernetes_name

    在该任务配置中,通过指定kubernetes_sd_config的role为service指定服务发现模式:

    1. kubernetes_sd_configs:
    2. - role: service

    为了区分集群中需要进行探测的Service实例,我们通过标签‘prometheus.io/probe: true’进行判断,从而过滤出需要探测的所有Service实例:

    1. - source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
    2. action: keep
    3. regex: true

    并且将通过服务发现获取到的Service实例地址__address__转换为获取监控数据的请求参数。同时将__address执行Blackbox Exporter实例的访问地址,并且重写了标签instance的内容:

    1. - source_labels: [__address__]
    2. target_label: __param_target
    3. - target_label: __address__
    4. replacement: blackbox-exporter.default.svc.cluster.local:9115
    5. - source_labels: [__param_target]
    6. target_label: instance
    1. - action: labelmap
    2. regex: __meta_kubernetes_service_label_(.+)
    3. - source_labels: [__meta_kubernetes_namespace]
    4. target_label: kubernetes_namespace
    5. - source_labels: [__meta_kubernetes_service_name]
    6. target_label: kubernetes_name

    对于Ingress而言,也是一个相对类似的过程,这里给出对Ingress探测的Promthues任务配置作为参考:

    1. - job_name: 'kubernetes-ingresses'
    2. metrics_path: /probe
    3. params:
    4. module: [http_2xx]
    5. kubernetes_sd_configs:
    6. - role: ingress
    7. relabel_configs:
    8. - source_labels: [__meta_kubernetes_ingress_annotation_prometheus_io_probe]
    9. action: keep
    10. regex: true
    11. - source_labels: [__meta_kubernetes_ingress_scheme,__address__,__meta_kubernetes_ingress_path]
    12. regex: (.+);(.+);(.+)
    13. replacement: ${1}://${2}${3}
    14. target_label: __param_target
    15. - target_label: __address__
    16. replacement: blackbox-exporter.default.svc.cluster.local:9115
    17. - source_labels: [__param_target]
    18. target_label: instance
    19. - action: labelmap
    20. regex: __meta_kubernetes_ingress_label_(.+)
    21. - source_labels: [__meta_kubernetes_namespace]
    22. target_label: kubernetes_namespace
    23. - source_labels: [__meta_kubernetes_ingress_name]