k8s 监控(一)安装 Prometheus
原文地址:https://juejin.cn/post/6844903908251451406
k8s 监控我们要完成以下几点:
- 监控 master/node 本身;
- 监控集群组件 apiServer、etcd 等;
- 监控需要关注的 pod;
- 自定义的监控,包括 jmx 等。
有了这些监控指标之后,我们还需要做到以下几点:
- 制定相应的告警规则;
- 提供对应的 webhook 发出告警;
- 部署 grafana 进行图形展示;
- 监控相关组件的高可用;
- 和 k8s metrics-server 进行结合。
目前而言,k8s 的监控业界公认的标准就是使用 Prometheus,Prometheus 也能很好的完成 k8s 的监控工作。只不过如果使用原生 Prometheus 进行监控的话,还要完成上述操作,需要做的工作非常多,需要对 k8s 以及 Prometheus 本身有一定的理解。
为什么不使用 prometheus-operator
为了方便操作,coreos 提供了 prometheus-operator 这样一个产品,它包装了 Prometheus,并且还提供了四个自定义的 k8s 类型(CustomResourceDefinitions),让你通过定义 manifest 的方式还完成新监控(job)以及告警规则的添加,而无需手动操作 Prometheus 的配置文件,让整个过程更 k8s。
并且在此基础之上,coreos 还有推出了 kube-prometheus 这样的升级版,它在 prometheus-operator 的基础之上高可用了 Prometheus 和 Alertmanager,提供了 node-exporter 用于宿主机的监控,还有 Kubernetes Metrics APIs 的 Prometheus 适配器和 grafana,让你实现一键部署。
老外喜欢这么搞,是不是一定合适我不知道,但是肯定是存在问题的,毕竟这个 prometheus-operator 依然处于 bate 状态。并且它里面多出的很多组件都只是为了避免让你直接操作配置文件,而这些组件都是额外的消耗。
同时也是因为你不能直接操纵配置文件,所以一旦你想修改配置文件就非常困难了,因为配置文件是自动生成的,一旦你想要修改它的 relabel_config
配置,你只能在它生成的规则后面添加。
这样会出现一种情况,就是你可能想删掉它为你自动生成的标签,但是这个标签本来就没有,是它为你生成的,但是生成之后你又想删除,这样就平白多了两台规则。并且一旦你配置定义错了,因为是 prometheus-operator 帮你 reload 的,因此就算有错误你也收不到。
如果你只是简单使用的话可以直接使用 kube-prometheus。但是只要你想要了解其中的原理部分,或者有自己定制化的需求,那就搞原生的吧,所有 prometheus-operator 能够实现的,原生都能实现。
怎么做
本文会从 0 开始,一点一点完成一开始的监控需求。即使你还是想用 kube-prometheus,等你看完我的所有文章之后,你使用起来也就没有丝毫的障碍了。
我们要做的就是将 Prometheus 镜像部署到 k8s 中,然后使用 kubernetes_sd_config
对 k8s 进行完成监控。当然,prometheus-operator 也是这么做的。
虽然 Prometheus 可以做到对 k8s 集群中所有 pod 的发现,但是 pod 的 ip 会随时改变,而且你进行所有 pod 的发现让你无从对它们进行分类管理,这样就会非常乱。
因此,我这里会和 prometheus-operator 一样,只对 endpoint 进行发现。因为创建 service 就会创建对应的 endpoint,所以我们完全可以通过 service 对 pod 进行分类,然后针对一类 pod 我们使用一个 Prometheus 的 job,这样就非常简洁明了了。
文章中所有的文件我都已上传到 GitHub,你可以直接 clone 下来,而不需要频繁的复制粘贴。
本文中 k8s 版本为 1.14.2,采用 kubeadm 安装。此外,本文不会对 Prometheus 进行过多的介绍,也就是说你需要有一定的 Prometheus 基础。
查询 kubelet 指标页面
大家应该清楚,应用如果想要被 Prometheus 监控,就应该提供一个 url,一旦访问这个 url,那么所有监控项就会以文本的形式一行行打印出来,Prometheus 就通过访问这个 url 来获得所有的监控数据,这个 uri 默认为 http[s]://IP:PORT/metrics
。
因为 Prometheus 已经成为了一个标准,因此 k8s 的所有组件都提供了 /metrics
这个 url。对于一些主机层面或者没有提供这个 url 应用的监控,可以使用一个中间产品,这个产品收集应用相关的监控指标,然后提供这个 url,让 Prometheus 进行采集。
这种产品称为 XXX_exporter,比如 node_exporter、jmx_exporter 等,官方收录了很多的 exporter,有官方和非官方的,你也可以通过它们提供的客户端库自己实现一个。
既然 Prometheus 能够通过 http 收集,那通过 curl 一样也行。因此在使用 Prometheus 收集之前,我会使用 curl 命令将所有要收集的指标数据先输出出来,以便大家心中有数。
当然,因为是在 k8s 环境,监控指标在收取之后会附加上一些标签,比如它所在的名称空间、所属的 service、pod 名称,以及 ip 端口等,这些标签你也可以选择加还是不加。
话不多说,我们先看看 kubelet 的指标数据。先创建一个目录,用于存放后续所有的 manifest 文件:
mkdir -p /opt/kube/prometheus
首先创建一个名称空间,所有监控相关的资源都放在这个名称空间之下:
# vim 00namespace.yml
apiVersion: v1
kind: Namespace
metadata:
name: monitoring
# kubectl apply -f 00namespace.yml
我们知道,pod 都是由 kubelet 创建的,因此 pod 的相关指标(包括使用的 cpu、内存、流量等)是由 kubelet 提供的,我们现在就可以访问 kubelet 的指标页面,看看有哪些指标数据。
作为一个守护进程,kubelet 默认监听 10250 端口,因此可以在 master 或者 node 上直接访问:
# curl https://127.0.0.1:10250/metrics/cadvisor -k
Unauthorized
其中:
- 必须使用 https;
metrics/cadvisor
是 kubelet pod 相关的监控指标,它还有一个metrics
,这是 kubelet 自身的监控指标;-k
表示不验证 kubelet 证书,因为整个集群都是使用自签署证书,因此没必要验证;
上面提示我们没有认证,看不到指标数据。认证很简单,使用 token 即可,那么我们首先要创建这个 token。大家应该清楚,当我们创建一个 serviceAccount 之后,k8s 会自动为其生成一个 secret,这个 secret 中就有 token 信息。
因此我们需要创建了一个 clusterRole,并创建一个 clusterrolebinding 将权限绑定到一个 serviceAccount 上,那么我们就拿到了这个权限的 token 了。
那我们需要创建 prometheus-clusterRole.yml
、prometheus-clusterRoleBinding.yml
和 prometheus-serviceAccount.yml
这三个文件,它们的内容如下。
prometheus-clusterRole.yml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus-k8s
rules:
- apiGroups:
- ""
resources:
- nodes/metrics
verbs:
- get
- nonResourceURLs:
- /metrics
verbs:
- get
prometheus-clusterRoleBinding.yml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus-k8s
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus-k8s
subjects:
- kind: ServiceAccount
name: prometheus-k8s
namespace: monitoring
prometheus-serviceAccount.yml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus-k8s
namespace: monitoring
这样我们就创建了一个 ServiceAccount,名为 prometheus-k8s,这个 ServiceAccount 不仅现在可以用来获取 kubelet 的监控指标,后续 Prometheus 也会使用这个 serviceAccount 启动。
kubectl apply -f prometheus-clusterRole.yml
kubectl apply -f prometheus-clusterRoleBinding.yml
kubectl apply -f prometheus-serviceAccount.yml
创建完成后,会自动在生成一个 secret,里面包含了 token:
# kubectl -n monitoring get secret
NAME TYPE DATA AGE
prometheus-k8s-token-xmkd4 kubernetes.io/service-account-token 3 3h26m
获取 token:
token=`kubectl -n monitoring get secret prometheus-k8s-token-xmkd4 -o jsonpath={.data.token} | base64 -d`
然后使用这个 token 访问 kubelet 的指标页面:
curl https://127.0.0.1:10250/metrics/cadvisor -k -H "Authorization: Bearer $token"
只需要将这个 token 放到请求头中就行,然后就可以看到所有的监控指标了。
可以看到里面有很多这样的标签的指标存在:
{container="",container_name="",id="/system.slice/tuned.service",image="",name="",namespace="",pod="",pod_name="",state="stopped"}
我完全没有搞懂这些是干啥的,不知道有没有用,反正我是准备全删除的。使用 Prometheus 就是有这样的问题,什么样的指标数据都有,恨不得把所有的都暴露出来,如果你是默认使用而没有管里面到底有什么指标数据的话,你可能接收了几倍的无用数据(对于很多人来讲,确实是没用的,因为从来都不会关注),造成了大量的资源浪费。
kubelet 除了 /metrics/cadvisor
这个 url 之外,还有一个 /metrics
,这是它本身的监控指标而非 pod 的。说实话,里面的数据我都看不懂,我在考虑要不要接收。
通过这种方式,其他几个 k8s 组件你应该都能够访问了,但是 etcd 不行,它需要验证客户端证书。
查询 etcd 指标页面
etcd 的指标页面的 url 也是 /metrics
,但是你想要访问它需要提供证书,因为它会验证客户端证书。当然你可以在它的启动参数中通过 --listen-metrics-urls http://ip:port
让监控指标页使用 http 而非 https,这样就不用提供证书了。
etcd 虽然部署在容器中,但是由于使用了 hostNetwork
,所以我们可以通过直接访问 master 的 2379 端口访问它。默认它会采用了 https,因此我们需要提供它的 peer 证书。如果 k8s 是使用 kubeadm 安装的,etcd 的证书在 /etc/kubernetes/pki/etcd/
目录下。
因此访问 etcd 的命令为:
curl https://127.0.0.1:2379/metrics --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/healthcheck-client.crt --key /etc/kubernetes/pki/etcd/healthcheck-client.key
后面我们需要将这三个文件挂载到 Prometheus 容器中,以便它能收集 etcd 监控数据。
安装 Prometheus
Prometheus 本身会依赖一些东西,因此在安装之前我们必须做一些准备工作。
我们先创建两个 configmap,一个是 Prometheus 的配置文件,另一个是告警的规则文件。配置文件一定要使用 configmap 进行保存,不能直接放在容器中,不然容器挂了配置文件就没了。
Prometheus 配置文件
先创建 Prometheus 配置文件 configmap prometheus-config.yml
,它的内容如下:
apiVersion: v1
data:
prometheus.yml: |
global:
evaluation_interval: 30s
scrape_interval: 30s
external_labels:
prometheus: monitoring/k8s
rule_files:
- /etc/prometheus/rules/*.yml
scrape_configs:
- job_name: prometheus
honor_labels: false
kubernetes_sd_configs:
- role: endpoints
namespaces:
names:
- monitoring
scrape_interval: 30s
relabel_configs:
- action: keep
source_labels:
- __meta_kubernetes_service_label_prometheus
regex: k8s
- source_labels:
- __meta_kubernetes_endpoint_address_target_kind
- __meta_kubernetes_endpoint_address_target_name
separator: ;
regex: Pod;(.*)
replacement: ${1}
target_label: pod
- source_labels:
- __meta_kubernetes_namespace
target_label: namespace
- source_labels:
- __meta_kubernetes_service_name
target_label: service
- source_labels:
- __meta_kubernetes_pod_name
target_label: pod
- source_labels:
- __meta_kubernetes_service_name
target_label: job
replacement: ${1}
- target_label: endpoint
replacement: web
kind: ConfigMap
metadata:
name: prometheus
namespace: monitoring
简单的说明下这个配置文件的内容:这个配置文件只是一个初版,可以看到里面只有一个 job,就是监控 Prometheus 本身。可以看到,这里使用了 kubernetes_sd_configs
,使用这个配置可以自动发现 k8s 中 node、service、pod、endpoint、ingress,并为其添加监控,更多的内容可以直接查看官方文档。
这里使用的是 endpoint 的方式对 Prometheus 本身进行发现,你可以有疑问了,为什么不直接对自身的 127.0.0.1:9090 进行采集呢?因为考虑到 Prometheus 可能会有多台,这样即使有多台,它们也都在一个 job 下面。
当然,你如果嫌麻烦也可以直接对自身进行采集,没有任何问题。然后下面就是一堆的 relabel_configs 配置了,我一个个解释这些配置是干啥的。
首先看第一个配置:
- action: keep
source_labels:
- __meta_kubernetes_service_label_prometheus
regex: k8s
大家应该知道,每创建一个 service 就会创建一个对应的 endpoint,但是 prometheus 的 endpoint 的发现会对 k8s 指定名称空间下所有 endpoint 进行发现,那么怎么保证 Prometheus 只发现我们需要的 endpoint 呢?答案是通过 relabel_configs
,这里 keep 就是干这个的。
上面配置的意思是只有 service 的标签包含 prometheus=k8s
,k8s 才会对其对应的 endpoint 进行采集。所以我们后面要为 Prometheus 创建一个 service,并且要为这个 service 加上 prometheus: k8s
这样的标签。
这里没有指定 url,Prometheus 会采集默认的 url /metrics
。
接着看下一段配置:
- source_labels:
- __meta_kubernetes_endpoint_address_target_kind
- __meta_kubernetes_endpoint_address_target_name
separator: ;
regex: Pod;(.*)
replacement: ${1}
target_label: pod
如果 __meta_kubernetes_endpoint_address_target_kind
的值为 Pod,__meta_kubernetes_endpoint_address_target_name
的值为 prometheus-0,在它们之间加上一个 ;
之后,它们合起来就是 Pod;prometheus-0
。使用正则表达式 Pod;(.*)
对其进行匹配,那么 ${1}
就是取第一个分组,它值就是 prometheus-0,最后将这个值交给 pod 这个标签。
因此这一段配置就是为所有采集到的监控指标增加一个 pod=prometheus-0
的标签。
如果
__meta_kubernetes_endpoint_address_target_kind
的值不是 Pod,那么不会添加任何标签。
后面的配置我想就不用多说了,无非就是将指定的元标签转换为指定的标签,因为不转换的话,元标签会在 relabel 之后都会被干掉。
创建它:
kubectl apply -f prometheus-config.yml
Prometheus 规则文件
我们目前不需要任何规则文件,但是由于会将对应 configmap 挂载进容器中,所以我们创建一个空的规则文件。
先创建一个 prometheus-config-rulefiles.yml
文件,它的内容如下:
apiVersion: v1
data:
k8s.yml: ""
kind: ConfigMap
metadata:
name: prometheus-rulefiles
namespace: monitoring
创建:
kubectl apply -f prometheus-config-rulefiles.yml
role 和 roleBinding
因为 Prometheus 会使用之前创建的 sa(serviceAccount)prometheus-k8s 运行,那么光现在 prometheus-k8s 这个 sa 的权限是没有办法查看 service 以及 endpoint 的。
我们使用 kubernetes_sd_config 主要会使用 endpoint 进行发现,因此 prometheus-k8s 必须具备更多的权限。
我们需要创建更多的 role,并通过 roleBinding 将这些权限绑定到 prometheus-k8s 这个 sa 上,之所以不使用 clusterRole 是为了权限最小化。
这里会创建 prometheus-roleConfig.yml
、prometheus-roleBindingConfig.yml
、prometheus-roleSpecificNamespaces.yml
、prometheus-roleBindingSpecificNamespaces.yml
这四个文件,它们的内容如下。
prometheus-roleConfig.yml:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prometheus-k8s-config
namespace: monitoring
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
prometheus-roleBindingConfig.yml:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: prometheus-k8s-config
namespace: monitoring
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: prometheus-k8s-config
subjects:
- kind: ServiceAccount
name: prometheus-k8s
namespace: monitoring
prometheus-roleSpecificNamespaces.yml:
apiVersion: rbac.authorization.k8s.io/v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prometheus-k8s
namespace: default
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- pods
verbs:
- get
- list
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prometheus-k8s
namespace: kube-system
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- pods
verbs:
- get
- list
- watch
- apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: prometheus-k8s
namespace: monitoring
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- pods
verbs:
- get
- list
- watch
kind: RoleList
prometheus-roleBindingSpecificNamespaces.yml:
apiVersion: rbac.authorization.k8s.io/v1
items:
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: prometheus-k8s
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: prometheus-k8s
subjects:
- kind: ServiceAccount
name: prometheus-k8s
namespace: monitoring
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: prometheus-k8s
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: prometheus-k8s
subjects:
- kind: ServiceAccount
name: prometheus-k8s
namespace: monitoring
- apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: prometheus-k8s
namespace: monitoring
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: prometheus-k8s
subjects:
- kind: ServiceAccount
name: prometheus-k8s
namespace: monitoring
kind: RoleBindingList
上面的权限中,config 是用来读 configmap 的,后面的就是 Prometheus 用来进行 k8s 发现时必须要的权限了,最后使用 rulebinding 将这些所有的权限都绑定到 prometheus-k8s 这个 sa 上。
这样后续 Prometheus 容器访问 api server 以及集群内的组件时,就会使用这些权限访问。
最后应用:
kubectl apply -f prometheus-roleBindingConfig.yml
kubectl apply -f prometheus-roleBindingSpecificNamespaces.yml
kubectl apply -f prometheus-roleConfig.yml
kubectl apply -f prometheus-roleSpecificNamespaces.yml
创建 pv
Prometheus 因为会将数据存储到磁盘上,因此我们必须使用 statefulset,这样就需要一个存储了,我这里就直接使用 nfs 了,你可能需要搭建一个,这里就不多提了。
先创建 prometheus-pv.yml
:
apiVersion: v1
kind: PersistentVolume
metadata:
name: prometheus
labels:
name: prometheus
spec:
nfs:
path: /data/prometheus
server: 10.1.1.3
accessModes: ["ReadWriteMany", "ReadWriteOnce"]
capacity:
storage: 1Ti
然后应用:
kubectl apply -f prometheus-pv.yml
创建 service
statefulset 必须要一个无头的 service,同时我们要进行 endpoint 发现也需要创建 service,创建一个 service 正好满足它们,不过要为这个 service 添加 prometheus=k8s
这个标签。
因此创建文件 prometheus-service.yml
,它的内容如下:
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: monitoring
labels:
prometheus: k8s
spec:
clusterIP: None
ports:
- name: web
port: 9090
protocol: TCP
targetPort: web
selector:
app: prometheus
type: ClusterIP
上面定义了 app=prometheus
这样的标签选择器,因此 Prometheus 容器必须存在这个标签。
创建:
kubectl apply -f prometheus-service.yml
创建 etcd secret
如果你不打算监控 etcd,那么可以直接跳过,并且将下面 Prometheus yml 文件中的 secret 相关的挂载删掉。
直接创建一个 secret 就行:
kubectl -n monitoring create secret generic etcd-client-cert --from-file=/etc/kubernetes/pki/etcd/ca.crt --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.crt --from-file=/etc/kubernetes/pki/etcd/healthcheck-client.key
为了方便后续使用,建议你在这个命令之后加上 --dry-run -o yaml
,然后将输出的内容保存在 prometheus-secret.yml
中。因为加了 --dry-run
之后不会执行,你还需要手动创建:
kubectl apply -f prometheus-secret.yml
部署 Prometheus
到此所有前置工作完成,接下来就可以直接部署 prometheus 了。先创建文件 prometheus.yml
,它的内容如下:
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: prometheus
prometheus: k8s
name: prometheus
namespace: monitoring
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: prometheus
prometheus: k8s
serviceName: prometheus
template:
metadata:
creationTimestamp: null
labels:
app: prometheus
prometheus: k8s
spec:
serviceAccount: prometheus-k8s
containers:
- args:
- --web.console.templates=/etc/prometheus/consoles
- --web.console.libraries=/etc/prometheus/console_libraries
- --config.file=/etc/prometheus/config/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.enable-admin-api
- --storage.tsdb.retention.time=20d
- --web.enable-lifecycle
- --storage.tsdb.no-lockfile
- --web.external-url=http://example.com/
- --web.route-prefix=/
image: prom/prometheus:v2.11.1
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 6
httpGet:
path: /-/healthy
port: web
scheme: HTTP
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
name: prometheus
ports:
- containerPort: 9090
name: web
protocol: TCP
readinessProbe:
failureThreshold: 120
httpGet:
path: /-/ready
port: web
scheme: HTTP
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
resources:
requests:
memory: 400Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/prometheus/config
name: config
readOnly: true
- mountPath: /prometheus
name: prometheus-data
#subPath: prometheus-db
- mountPath: /etc/prometheus/rules/
name: prometheus-rulefiles
- mountPath: /etc/prometheus/secrets/etcd-client-cert
name: secret-etcd-client-cert
readOnly: true
volumes:
- name: config
configMap:
defaultMode: 420
name: prometheus
- name: prometheus-rulefiles
configMap:
defaultMode: 420
name: prometheus-rulefiles
- name: secret-etcd-client-cert