概述

我一直在负责维护的PaaS平台引入了Kubernetes作为底层支持, 可以借助Kubernetes的生态做更多的事情, 这篇博客主要介绍如何为普通用户提供图表监控服务(承接上一篇提供dashboard支持). 默认读者有能力自己搭建Kubernetes集群以及初级的监控系统, 以及可以简单的使用PromQL, 因为博客主要想介绍的内容不在此.

我早于dashboard就增加了这个功能, 但是讲解起来实在复杂. 一直拖着没写, 今天有空, 正好来分享一下吧.

方案拓扑

这里我懒一下, 不想画图了.

图中是整个方案的左半边, 由采集工具采集机器以及容器信息, 上传至Prometheus中, 右下角的Grafana通过PromQL查询以及展示数据, 下面两个部分会简单介绍Prometheus以及Grafana, 但重点在后面哈.

朴素的收集与展示工具-Prometheus

这是一张Ingress的CPU利用率统计图:

朴素的原因主要是这个工具做作用是收集数据, 每次只能查询一条语句, 而且没有什么历史记录功能, 权限控制几乎没有, 这样基础的工具肯定不能直接提供给用户.

漂亮的展示工具-Grafana

相比上面简陋的图片, Grafana的功能就强大很多了, 可以展示多张图表, 而且有了权限控制, 非常适合建立大盘监控, 对于PaaS平台中的每个用户来讲, 看到自己应用的监控是多么美妙的一件事.

分界线 ———

如果仅仅是介绍到这里, 大家在自己的Kubernetes集群中鼓捣一下应该很快就可以用了, 我们既然作为PaaS平台的开发者, 自然要考虑多用户以及多应用的组织管理方式, 呈现给用户需要的资料的同时, 也要保证用户以及应用间不会相互影响. 没错这与上一篇dashboard支持方案类似, 也要进行权限设计以及控制.

权限同步策略

PaaS平台的权限系统

在PaaS平台中, 假设我们有一个用户A, 他拥有自己的一个或多个应用群组(G1, G2), 每个群组中部署了一系列应用程序(a1, a2…).

Grafana中的权限系统

在Grafana权限系统中, 有一些Teams, 用户可以属于一个或多个Team.

每个Folder可以拥有一个或多个dashboard.

映射

考虑到上述的关系, 我们的思路就很明确了, 我的设计方案是:

  1. 用户的每个群组对应一个Team, 该Team拥有一个Folder的访问权限
  2. 每个应用拥有一个dashboard

你也可以使用别的设计方案(比如说不要Folder, 每个Team拥有一些dashboard), 上述方案只是我想方便管理, 每个Team拥有自己的Folder.

Grafana相关类库及其使用(tl;dr)

我用的是Python定时同步权限以及图表信息, 当时也想过用Golang, 但我说真的, 这种代码用Python太适合了. 下面的部分太过于偏了, 建议不要去纠结, 你大概率用不上, 肯定要自己去读文档的.

1
2
grafana-api==1.0.3  # 用来与Grafana API通信
grafanalib==0.5.7 # 创建Grafana图表

User, Team, Folder创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 下面是一个team的创建, 其他类似
def ensure_team(g_api, team_name):
team_id = -1
try:
# {'message': 'Team created', 'teamId': 121}
g_team = g_api.teams.add_team({'name': team_name})
team_id = g_team['teamId']
except GrafanaClientError as e:
if e.status_code == 409:
# 'Client Error 409: Team name taken'
g_team = g_api.teams.get_team_by_name(team_name)[0]
team_id = g_team['id']
assert team_id != -1
return team_id

Folder与Team权限同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 下面是同步team以及其用户, 因为用户有可能增减, 需要对应的增减
def sync_user_team(g_api, team_id, user_ids):
orig_members = g_api.teams.get_team_members(team_id)
now_user_ids = set(user_ids)
orig_user_ids = set([u['userId'] for u in orig_members])

for user_id in now_user_ids - orig_user_ids:
try:
g_api.teams.add_team_member(team_id, user_id)
except GrafanaBadInputError:
pass

for user_id in orig_user_ids - now_user_ids:
g_api.teams.remove_team_member(team_id, user_id)

# 下面是同步folder以及team权限, 所有普通用户只有只读权限
def sync_folder_permission(folder, team_id=None):
# https://grafana.com/docs/grafana/latest/http_api/folder_permissions/
g_api = get_grafana_api() # type: GrafanaFace
permissions = []
if team_id != None:
permissions.append({
"teamId": team_id,
"permission": 1 # 只有查看权限
})

g_api.folder.update_folder_permissions(
folder['uid'],
{
"items": permissions,
}
)

dashboard页面设计以及同步

这部分其实挺难的, 我想使用类似声名式的写法, 类似kubectl apply -f xxx.yaml, 不过Grafana的API太难用了, 一种解决方案是: 导入dashboard图表数据, 可以看到我这里的api都是手动加的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def sync_dashboard_data(dashboard_uid, folder_id, dashboard_json_data, inputs=[]):
"""同步dashboard的组信息以及dashboard的pannel数据信息
"""
g_api = get_grafana_api() # type: GrafanaFace
d = g_api.dashboard.get_dashboard(dashboard_uid)

data = {
'dashboard': dashboard_json_data,
'folderId': folder_id,
"inputs": inputs,
'overwrite': True
}
put_dashboard_path = "/dashboards/import"
r = g_api.api.POST(put_dashboard_path, json=data)

# 数据样式的生成请参考这里:
# https://github.com/weaveworks/grafanalib/blob/master/grafanalib/tests/examples/example-elasticsearch.dashboard.py

最终效果

针对我们系统中的每个应用, 基本都有这么一张图表:

常用的图表推荐

我平时作为管理员一般也不会看每个应用, 可能看个大概吧.

ingress

https://github.com/kubernetes/ingress-nginx/blob/master/deploy/grafana/dashboards/nginx.json

node_exporter

https://github.com/rfrail3/grafana-dashboards/blob/master/prometheus/node-exporter-full.json

总结

这篇文章我主要想介绍下我们的系统是如何设计的, 希望能够在大家设计系统接入方案时能有所参考, 也欢迎大家留言讨论.

顺便吐槽一下Grafana, 居然不能设置URL头像(为了改头像我都去读grafana代码了, 发现不能修改), 真是令人生气!!!