概述

我一直在负责维护的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代码了, 发现不能修改), 真是令人生气!!!