最近手头的项目开始从 PHP,Lua 迁移到 Golang,心想正好趁此机会夯实监控,提到 Golang 的监控,不得不说 prometheus[1] 已经是标配,在 Golang 里 集成[2] 起来非常简单:
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func main() {
http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":6060", nil)
}
如果想在本地 docker 里部署 prometheus,那么只需一条 docker run 命令:
shell > docker run -p 9090:9090 prom/prometheus
运行后打开浏览器浏览 即可,这里显示了相关的监控信息,缺省情况下,监控了 prometheus 本身。
虽然在本地 docker 里部署非常简单,但是如果想在 kubenetes 里 部署[3] 的话却是另一番经景象了,加之 官方文档[4] 语焉不详,以至于我几次想中途而废,还好最后坚持下来了,本文记录了我在部署过程中遇到的一些坑坑洼洼以及解决方法。
Prometheus 缺省的配置文件是「/etc/prometheus/prometheus.yml」,如果我们要修改配置文件的话,那么按照 官方文档[5] 里的说明,需要自定义一个 Dockerfile 文件:
FROM prom/prometheus
ADD prometheus.yml /etc/prometheus/
然后再构建新的镜像:
shell> docker build -t my-prometheus .
shell> docker run -p 9090:9090 my-prometheus
不得不说有点繁琐,实际上利用 kubenetes 的 ConfigMap[6] 即可:
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus
namespace : bpd-ie
data:
prometheus.yml: |
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9090']
至于如何把 ConfigMap 挂到容器里,可以参考后面的配置。
传统监控软件往往采用的是 push 模式,而 prometheus 采用的是 pull 模式。好处是架构简单,被监控的节点不需要部署 agent 之类的代理进程,坏处是 prometheus 必须知道所有需要被监控的节点,比如缺省配置(/etc/prometheus/prometheus.yml)就会通过 static_configs[7] 抓取 prometheus 服务本身的节点信息:
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
如果需要被监控的节点比较固定的话,那么通过 static_configs 来硬编码倒也无妨,不过在 kubenetes 中,各个业务需要被监控的容器个数随时可能会发生变化,相应的容器地址也随时可能会发生变化,此时如果再通过 static_configs 来硬编码的话,那么无疑是自讨苦吃,相对而言更合理的方法是使用 kubernetes_sd_config[8] 打通服务发现机制,从而实现自动配置节点信息,不过前提条件是我们必须先在 kubenetes 中配置 RBAC[9] 信息:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: prometheus
rules:
- apiGroups: [""]
resources:
- nodes
- nodes/metrics
- services
- endpoints
- pods
verbs: ["get", "list", "watch"]
- apiGroups:
- extensions
- networking.k8s.io
resources:
- ingresses
verbs: ["get", "list", "watch"]
- nonResourceURLs: ["/metrics", "/metrics/cadvisor"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: prometheus
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: prometheus
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: prometheus
subjects:
- kind: ServiceAccount
name: prometheus
namespace: default
不过我的 kubenetes 账号权限不够,配置 RBAC 信息的时候会报错:
找 kubenetes 管理员来配置固然可以解决问题,但是以后有相关问题的话会不可避免的依赖上别人了,最好还是能自己解决问题:既然 kubernetes_sd_config 有困难,那么我们不妨考虑换一种服务发现机制,比如说 dns_sd_config[10] ,通过域名解析动态获取节点:
scrape_configs:
- job_name: 'foo'
dns_sd_configs:
- names: ['foo.default.svc.cluster.local']
type: A
port: 6060
当然,我们还需要在 kubenetes 里把需要被监控的业务配置成 Headless Service,以便 kubenets 能够为每一个 POD 提供一个稳定的并且唯一的网络标识,也就是内网域名:
apiVersion: v1
kind: Service
metadata:
name: foo
namespace: default
spec:
clusterIP: None
说明:关于 Headeless Service 和内网域名的相关知识,如果不清楚,那么可以参考我以前写的文章:「 手把手教你用ETCD[11] 」,里面有详细的介绍。
此外,说一点题外话,假如使用 kubernetes_sd_config 作为服务发现方式,设想下面一个场景:集群里有非常多的节点,但是你只想监控其中的一部分,那么如何配置呢?答案是通过 kubenetes 的 annotations[12] 功能,标识出你要监控的节点,然后在 prometheus 里 relable[13] ,详细的介绍可以参考 stackoverflow 上的 相关内容[14] 。
一般来说,我们通过 kubenetes 部署的都是一些无状态的服务,而对于 prometheus 服务而言,它应该是一个有状态的服务(StatefulSet),也就是说需要考虑数据持久化, 否则一重启,监控信息的历史记录都丢失了,肯定不是我们所希望看到的:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: prometheus
namespace: default
labels:
app: prometheus
spec:
serviceName: prometheus
replicas: 1
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
dnsPolicy: ClusterFirst
containers:
- name: prometheus
image: prom/prometheus:v2.22.0
imagePullPolicy: Always
ports:
- name: http
containerPort: 9090
protocol: TCP
volumeMounts:
- name: datadir
mountPath: /prometheus
- name: configfile
mountPath: "/etc/prometheus/prometheus.yml"
subPath: prometheus.yml
resources:
limits:
cpu: "1"
memory: 512Mi
requests:
cpu: "1"
memory: 512Mi
volumes:
- name: configfile
configMap:
name: prometheus
# securityContext:
# runAsNonRoot: true
# runAsUser: 65534
# runAsGroup: 65534
# fsGroup: 65534
volumeClaimTemplates:
- metadata:
name: datadir
spec:
storageClassName: ...
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
不过当我配置 StatefulSet 的时候,kubenetes 却报错了:
pod has unbound immediate PersistentVolumeClaims
相关描述无法直观看出问题出在哪,好在还可以查日志:
shell> kubectl logs prometheus-0
结果看到了真正的错误原因:
open /prometheus/queries.active: permission denied
以此信息为关键字去 github 上搜索,可以找到相关的 issue[15] ,确认是权限问题。网上能查到一些解决方法,比如 GoogleCloudPlatform[16] 是通过在 initContainers 里执行 chmod 解决的,不过更好的方法是通过 securityContext[17] 设置 非 root 权限:
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
至于为什么是 65534,可以本地运行 docker 容器后看看使用的是什么账号:
shell> docker exec $(docker ps -qf ancestor=prom/prometheus) id
uid=65534(nobody) gid=65534(nogroup)
差点忘了说 grafana[18] ,既然说 prometheus,怎么能忘了 grafana 呢!grafana 对 prometheus 的支持很好,使用起来非常简单,按照 官问文档[19] 的说明配置即可,没有什么可说的,我要说的是关于 Dashboard[20] 的选择,现在最流行的是 Go Metrics(10826)[21] ,多数时候,它也是最好的,不过它有一个缺点:它是基于 kubenetes 里的 namespace / pod 筛选的,如果你没有使用基于 kubenetes 的服务发现机制,比如本文使用的是基于 dns 的服务发现机制,那么筛选功能就失效了,基于此,我做了一个修改版本的 Go Metrics(13240)[22] ,它是基于 job / instance 筛选的,效果如下:
最后,推荐一些资料:「 Prometheus book[23] 」,「 Prometheus监控Kubernetes系列[24] 」。
[1]
prometheus:
[2]
集成:
[3]
部署:
[4]
官方文档:
[5]
官方文档:
[6]
ConfigMap:
[7]
static_configs: #static_config
[8]
kubernetes_sd_config: #kubernetes_sd_config
[9]
RBAC:
[10]
dns_sd_config: #dns_sd_config
[11]
手把手教你用ETCD:
[12]
annotations:
[13]
relable:
[14]
相关内容:
[15]
issue:
[16]
GoogleCloudPlatform:
[17]
securityContext:
[18]
grafana:
[19]
官问文档:
[20]
Dashboard:
[21]
Go Metrics(10826):
[22]
Go Metrics(13240):
[23]
Prometheus book:
[24]
Prometheus监控Kubernetes系列: