上一篇介绍了如何借助kube-prometheus
生成的通用配置文件,对k8s进行最基础的监控。 一般情况下这是不够的,我们仍旧需要配置告警的发送渠道、增加额外的告警条件、持久化历史数据、对系统应用(如mysql,kafka)的监控和指标收集,对自有应用的监控等等。 从这一篇开始,逐步分享笔者在定制过程中的经验。
定制的原则
kube-prometheus
这个项目开发的其实是一个jsonnet的库,核心代码在jsonnet/kube-prometheus
目录下, 其他文件大多是说明和例子。 通过jsonnet解释执行example.jsonnet
这个入口,在manifests/
目录下生成之前提到的配置文件。
这个项目用到了很多上游的jsonnet库,比如:
- 用来生成k8s原生组件库: https://github.com/ksonnet/ksonnet-lib
- 生成grafana的组件库:https://github.com/grafana/jsonnet-libs
- 一部分预置的grafana面板和告警规则: https://github.com/kubernetes-monitoring/kubernetes-mixin
- ...
完整列表在jsonnet/kube-prometheus/jsonnetfile.json`。
由于项目本身处于很早期,变化大,问题也不少,所以我们要做好时不时更新上游库的准备。 笔者就遇到过几次莫须有的告警,其实是上游kubernetes-mixin
的告警策略不合理造成的。 好在几次问题修复都比较快,看到PR被合并后,用命令更新上游库,重新编译配置文件,再应用到集群中解决问题。
所以,定制要利用jsonnet的mixin,不是简单地修改manifests/
下生成的配置,也不能去修改库中的代码。 否则,每次更新库之后都需要重做修改。
关于jsonnet
定制绕不开要了解jsonnet。
jsonnet是google开发的模板语言(data templating language),可以定义和生成json。 jsonnet支持变量、函数、条件、运算、包管理、错误处理等,用来更方便的维护配置文件。 创造一个新的语言来解决工程问题,算是西方工程师的文化。
jsonnet有c++和golang两个版本, jsonnet-builder是jsonnet的包管理工具。
jsonnet虽然资料很少,但语言本身不复杂。3-4个小时就能大致读完官网提供的Tutorial和Standard Library。不过笔者并非专业coder,对这个语言运用的理解足足花了一两周的时间。 我理解的几个要点:
- 语言采用mixin,而非传统面向对象的继承。维基百科mixin(https://en.wikipedia.org/wiki/Mixin) 的解释大概是,一个类不需要继承就可以访问另一个类的方法。对jsonnet来说,我就简单理解成两个对象相加
{ a: 1, b: 2 }
+
{ b: 3, c: 4 }
,保留两边不冲突的元素,冲突的元素以+
右边为准,结果是{ a: 1, b: 3, c:4}
。 - "late bound"机制,即对元素引用时,引用的并不是当时的值,而是该元素后续做完所有mixin操作后的值。 这就代表你可以后续使用
+
操作修改变量中的某一个元素,对全局都有效。 - 用双冒号
::
定义的对象是隐藏的,不会出现在最终生成的json里。 主要用来定义一些有结构的变量。 - 一点感受,如果库作者没有提供接口(变量),后期对array的删改挺不好做的。
编译环境
照着readme里Customizing Kube-Prometheus
去做就行了。
安装golang运行环境
不细说了。
安装jsonnet-builder, jsonnet和gojsontoyaml
$ go get github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
$ brew install jsonnet
$ go get github.com/brancz/gojsontoyaml
克隆kube-prometheus库
$ mkdir kube-prometheus; cd kube-prometheus
$ jb init
$ jb install github.com/prometheus-operator/kube-prometheus/jsonnet/kube-prometheus@release-0.4
更新自身和依赖的库
$ jb update
所有库文件都保存在vender/
文件夹下。
编译
example.jsonnet
是定制的起点,我们先复制这个文件到你自己命名的项目文件,比如我们是wk-mixin.jsonnet
,然后用jsonnet执行。
$ cp example.jsonnet wk-mixin.jsonnet
$ ./build.sh wk-mixin.jsonnet
+ set -o pipefail
++ pwd
+ PATH=/Users/Roger/Documents/Program/git/kube-prometheus/tmp/bin:/usr/local/opt/mysql-client/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/usr/local/opt/mysql-client/bin:/Users/Roger/Documents/Program/golib/bin:/usr/local/go/bin:/Users/Roger/Documents/Program/golib/bin
+ rm -rf manifests
+ mkdir -p manifests/setup
+ jsonnet -J vendor -m manifests wk-mixin.jsonnet
+ xargs '-I{}' sh -c 'cat {} | gojsontoyaml > {}.yaml; rm -f {}' -- '{}'
如果运行成功,manifests/
目录下应该会生成很多yaml文件。 这代表你把基本编译环境跑通了。
如果你对这个定制项目使用版本管理,
vendor/
和manifests/
都是不需要提交的。
对示例的理解
在自己动手定制之前,我们先要理解当前的配置文件是如何生成出来的。
build.sh
我们先来看一下编译脚本build.sh
。 关键部分只有一行:
jsonnet -J vendor -m manifests "${1-example.jsonnet}" | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml' -- {}
-
-J
指定库文件的位置,这里只扫描vendor/
文件夹里的内容。 前面我们也提到了,jb update
会把所有库文件都保存在vendor
下。 -
-m
用到了jsonnet的一个特性,它会把生成的json的最顶层键名作为文件名,值作为内容,写入多个文件,并把文件名输出到标准输出。 目标文件夹是manifests/
-
"${1-example.jsonnet}"
bash的特性,如果没有传入$1
,则把example.jsonnet
作为输入。 -
xargs
这一段从pipe读取jsonnet输出的文件名,逐个用gojsontoyaml
转格式,并写入加了.yaml
后缀的文件。
example.jsonnet
再看example.jsonnet
的内容(以release-04
为例):
整个文件其实分两个部分:
-
L1-14
定义了变量kp
, 你可以不断用+
去添加和覆盖。 -
L2
的(import xxx.libsonnet)
就是简单地把libsonet的内容替换到这个位置。 -
L10-14
实际是一个替换操作。代码里约定俗成,所有用到namspace的地方,都会从$_config.namespace
获取。 这里利用+
和"late bound",等同于替换了所有引用namespace的地方。 另外,我们注意到L11
中的_config+::
用的是双引号,代表_config这个对象,不会出现在最后json的结果中。 -
L16-28
控制实际的输出。以L23
为例,上面提到build.sh
会用最顶层的键名作为文件名,这行输出的文件名是['node-exporter-' + name]
, 其中name
是通过std.objectFields()
这个标准函数,循环读取某个键下元素名,这个例子里是kp.nodeExporter
代表kp
变量中nodeExporter
下的元素。
有个要点,各个库习惯把可配置的变量写在{_config:: {<组件名>: { ... } } }
, 定制的时候用+
直接修改。那么如何知道哪些变量可以设置呢?一种方法是读项目里提供的例子,另一种就是读库的源码。 另外,对某些设置,可能库并没有提供_config
下的变量, 一般来说,这是库作者不推荐修改的,但是你仍旧可以利用jsonnet的特性去做,通常更复杂些。
如何开始定制?
针对已有组件的配置修改
在动手之前,推荐先阅读所有的例子,如果恰巧有匹配的,就照例子修改,主要是对变量kp
的修改。这些例子包括:
-
README.md
里Customization Example
-
example/
下的文件 -
jsonnet/kube-prometheus/
下的所有libsonnet - 项目"issue"和"discussion"
监控其他通用系统
如果是基于已有组件的配置修改,大部分在例子里都能找到。 但如果是要添加新的监控对象,比如kafka
,则需要做这些工作:
- 选择exporter: 用于获取目标的metrics。 参考Prometheus的官网,一些常用系统的exporter。
- 定制dashboard: 可以从grafana的社区中找模板,自己修改。
- 编写jsonnet生成相关的配置。 包括exporter的deployment, 设计serviceMonitor,设计告警规则,把dashboard加入grafana。
监控自有应用
自有应用需要自己编写全部内容。
- 开发exporter: 开发一个接口。这里可以重用k8s的liveness和readness,让它输出Prometheus的metrics的格式。 这样,在监控目标健康状况的同时,收集metrics。
- 创建dashboard: 自己用PromQL设计指标,在grafana中用设计呈现,再把设计好的dashboard导出成json供整合用。
- 编写jsonnet生成相关的配置。
测试环境和生产环境
最后提一下环境的切换。 从测试的角度,配置修改都需要在测试环境运行通过后,再应用到生产环境中。 这两个环境不完全相同,比如域名,告警邮件地址,告警的钉钉群等等。 如何能方便的切换生成这两个环境的配置?分享一下我的经验:
- 创建两个文件,比如
wk-env-lab.json
和wk-env-production.json
,把各环境不同变量分别定义在里面,比如
{
"domain": ".example.wukongbox.cn",
"email": "example@wukongbox.com",
"ding": "000000000000000000000000000000000000000000000000000000"
}
- 修改
build.sh
中执行jsonnet那行,增加--ext-str env="production"
,即从命令行传入变量,这是jsonnet的功能。 - 在wk-mixin.jsonnet中根据
env
判断要引入的哪个json文件,这样后续就可以通过$._config.wukongbox.变量名
,引用json里的值了。
local kp =
...
{
_config+:: {
...
wukongbox:
// read env type
local env = std.extVar("env");
// loading envs from external json
if env == 'production' then
(import 'wk-env-production.json')
else
(import 'wk-env-lab.json'),
...
- 复制
build.sh
到一个新的文件比如build-test.sh
,修改成--ext-str env="lab"
- 这样执行
./build.sh wk-mixin.jsonnet
和./build-test.sh wk-mixin.jsonnet
会对应生产和测试环境的配置。
后面的文章,对我们自己项目里曾经做过的定制,我会一个个来举例说明,希望对其他人有帮助。
有疑问加站长微信联系(非本文作者)