👉️URL: https://engineering.bitnami.com/articles/implementing-slos-using-prometheus.html 📝Author: 由 JuanJo Ciarlante 发表 于 2018 年 10 月 4 日
在线服务应旨在提供符合业务需求的服务可用性。这个过程的一个关键部分应该涉及组织中的不同团队,例如,从业务开发团队到工程团队。
为了验证服务如何符合这些目标,应该可以定义具有可衡量的「阈值」,例如,「服务必须在 99.9% 的时间内可用」,这反过来应该符合用户的期望和业务连续性。
已经有很多关于主题的文章:
•CRE life 课程[1]
•SLO、SLI、SLA - CRE life 课程[2]
如果您不熟悉这些术语,我强烈建议您先阅读 Google 的 SRE 书中关于服务级别目标[3] 的文章 。
总之:
•SLA:服务水平协议
•您承诺向用户提供何种服务,如果您无法满足,可能会受到处罚。
•示例:“99.5%”可用性。
•关键词:合同
•SLO:服务水平目标
•您在内部设置的目标,推动您的测量阈值(例如,在仪表板和警报上)。通常,它应该比您的 SLA 更严格。
•示例:“99.9%”可用性(所谓的“三个 9”)。
•关键词:阈值
•SLI:服务水平指标
•您实际测量的内容,以断言您的 SLO 是否符合/偏离目标。
•示例:错误率、延迟
•关键词:指标
那么99%
可用性是什么意思呢?- 这不是 1% 的错误率(失败的 HTTP 响应百分比),而是 在预定义的时间段内服务可用的时间百分比。
SLO grafana 仪表板截图
在上面的仪表板中,该服务在 1 小时内的错误率超过 0.1%(y 轴为 0.001)(错误尖峰顶部的红色小水平段),从而提供99.4%
的 7 天的可用性:
SLO 公式示例
此结果的一个关键因素是您选择衡量可用性的时间跨度(在上例中为 7 天)。较短的时间段通常用作所涉及的工程团队(例如,SRE 和 SWE)的检查点,以跟踪服务的运行情况,而较长的时间段通常用于组织/更广泛的团队的审查目的。
例如,如果您设置了99.9%
SLO,则服务可以关闭的总时间如下:
•30 天内:43 分钟(3/4 小时)
•90 天内:129 分钟(约 2 小时)
另一个微不足道的“数字事实”是向 SLO 添加额外的9具有明显的指数影响。请参阅以下总时间跨度为1 年的时间段:
•2 个 9: 99%
: 5250min (87hrs or 3.64days)
•3 个 9: 99.9%
: 525min (8.7hrs)•4 个 9: 99.99%
: 52.5min
•5 个 9:99.999%
5 分钟
服务可以关闭的允许时间的上述数字可以被认为是错误预算(error budget),您可以在以下事件中消耗它:
•计划中的维护
•失败的升级
•意外中断
实际结果是,上述任何一项都会消耗您的服务的错误预算,例如,意外中断可能会耗尽它,以至于在该时间段内阻止进一步的维护工作。
从上面可以清楚地看出,我们必须有服务指标来告诉我们服务何时被认为(不)可用。有几种方法可以解决这个问题:
•RED[4] : Rate(比率), Errors(错误), Duration(时长) - 由 @tom_wilkie[5] 引入
•USE[6] : Utilization(利用率), Saturation(饱和度), and Errors(错误) - 由 @brendangregg[7] 引入
让我们举一个具体的例子,遵循 RED 方法[8](因为我们已经拥有的指标更适合这种方法):通过通常用于监控目的的工具,创建警报和仪表板以支持 Kubernetes API 的目标 SLO:Prometheus 和 Grafana。
此外,我们将使用 jsonnet 来构建我们的规则和仪表板文件,利用现有的 library helpers。
•Prometheus[9]
•Grafana[10]
•jsonnet[11]
本文不解释如何在您的服务超出阈值时发出信号,而是重点介绍如何记录服务处于此条件下的时间。
本文的其余部分将重点介绍创建 Prometheus 规则以根据特定指标 (SLI) 的阈值捕获“SLO 超时”。
让我们定义一个简单的目标:
•SLO : 99%
,来自以下内容:
•SLI:
•错误率低于 1%
•90 百分位数 (90th percentile)的请求的延迟低于 200ms
将规范写为 jsonnet:
slo:: {
target: 0.99,
error_ratio_threshold: 0.01,
latency_percentile: 90,
latency_threshold: 200,
},
Kubernetes API 公开了几个我们可以用作 SLI 的指标,在短时间内(这里我们选择 5 分钟,这个数字应该是抓取间隔的几倍)使用 Prometheus 函数 rate()
:
•apiserver_request_count
: 按verb
, code
, 计算所有请求resource
,例如获取最近 5 分钟的总错误率:
sum(rate(apiserver_request_count{code=~"5.."}[5m]))
/
sum(rate(apiserver_request_count[5m]))
•上面的公式会丢弃所有指标标签(例如,http 的 verb
, code
)。如果要保留一些标签,则需要执行类似于以下操作:
sum by (verb, code) (rate(apiserver_request_count{code=~"5.."}[5m]))
/ ignoring (verb, code) group_left
sum (rate(apiserver_request_count[5m]))
•apiserver_request_latencies_bucket
:不同 verb
的延迟直方图。例如,要获得以毫秒为单位的第 90 个延迟百分位数:(注意le
“小于或等于”标签是特殊的,因为它设置了直方图桶间隔):
histogram_quantile (
0.90,
sum by (le, verb, instance)(
rate(apiserver_request_latencies_bucket[5m])
)
) / 1e3
了解更多信息:
•bitnami-labs/kubernetes-grafana-dashboards[12]
•spec-kubeapi.jsonnet[13]•promql-histogram[14]
PromQL[15] 是一个非常强大的语言,但截至 2018 年 10 月,它还不支持嵌套子查询的范围(详见普罗米修斯问题 1227[16]),我们需要一个功能,能够计算time ratio
的error ratio
或latency
。
此外,作为一种良好的做法,为了降低查询时 Prometheus 资源的使用,建议始终将 记录规则(recording rules)[17] 添加到预先计算的表达式中,例如sum(rate(...))
。
作为如何执行此操作的示例,以下记录规则集是从我们的 bitnami-labs/kubernetes-grafana-dashboards[18] 存储库构建的,以捕获上述内容time ratio
:
•创建一个新kubernetes:job_verb_code_instance:apiserver_requests:rate5m
指标来记录请求率:
record: kubernetes:job_verb_code_instance:apiserver_requests:rate5m
expr: |
sum by(job, verb, code, instance) (rate(apiserver_request_count[5m]))
•使用上述指标,为请求比率(超过总数)创建一个新的kubernetes:job_verb_code_instance:apiserver_requests:ratio_rate5m
:
record: kubernetes:job_verb_code_instance:apiserver_requests:ratio_rate5m
expr: |
kubernetes:job_verb_code_instance:apiserver_requests:rate5m
/ ignoring(verb, code) group_left()
sum by(job, instance) (
kubernetes:job_verb_code_instance:apiserver_requests:rate5m
)
•使用上述比率指标(基于每个 http 的 code
和verb
),创建一个新指标来捕获错误率:
record: kubernetes:job:apiserver_request_errors:ratio_rate5m
expr: |
sum by(job) (
kubernetes:job_verb_code_instance:apiserver_requests:ratio_rate5m
{code=~"5..",verb=~"GET|POST|DELETE|PATCH"}
)
•使用上述错误率(以及其他类似创建过去 5 分钟内记录的第 90 个百分位延迟kubernetes::job:apiserver_latency:pctl90rate5m
,为简单起见未在上面显示),最后创建一个布尔指标来记录我们的 SLO 违例:
record: kubernetes::job:slo_kube_api_ok
expr: |
kubernetes:job:apiserver_request_errors:ratio_rate5m < bool 0.01
*
kubernetes::job:apiserver_latency:pctl90rate5m < bool 200
上述kubernetes::job:slo_kube_api_ok
最终指标对于仪表板和考虑 SLO 合规性非常有用,但我们应该报警上述哪个指标正在推动 SLO,如下面的 Prometheus 警报规则所示:
•高 API 错误率警报:
alert: KubeAPIErrorRatioHigh
expr: |
sum by(instance) (
kubernetes:job_verb_code_instance:apiserver_requests:ratio_rate5m
{code=~"5..",verb=~"GET|POST|DELETE|PATCH"}
) > 0.01
for: 5m
•高 API 延迟警报
alert: KubeAPILatencyHigh
expr: |
max by(instance) (
kubernetes:job_verb_instance:apiserver_latency:pctl90rate5m
{verb=~"GET|POST|DELETE|PATCH"}
) > 200
for: 5m
请注意,Prometheus 规则取自已显示的 jsonnet 输出,可在 bitnami-labs/kubernetes-grafana-dashboards[19] 中找到,阈值分别从.slo.error_ratio_threshold和 .slo.latency_threshold 评估。
创建 Grafana 仪表板通常是通过与 UI 交互来完成的。这对于简单和/或“标准”仪表板(例如,从 https://grafana.com/dashboards 下载)很好 ,但如果您想实施最佳 devops 实践,尤其是对于 gitops 工作流[20],则会变得很麻烦。
社区正在通过各种努力解决这个问题,例如用于 jsonnet[21]、python[22] 和 Javascript[23]的 Grafana 库。鉴于我们的jsonnet
实现,我们选择了 grafonnet-lib[24]。
使用jsonnet
设置我们的 SLO 阈值和编码我们的 Prometheus 规则的一个非常有用的结果是,我们可以重用这些来构建我们的 Grafana 仪表板,而无需复制和粘贴它们。
例如:
•$.slo.error_ratio_threshold
在我们的 Grafana 仪表板中引用来设置 Grafana 图形面板的thresholds
属性,就像我们上面为我们的 Prometheus 警报规则所做的那样。
•通过参考 jsonnet[25] 摘录创建的 Prometheus 记录规则,请注意metric.rules.requests_ratiorate_job_verb_code.record
用法(而不是逐字记录'kubernetes:job_verb_code_instance:apiserver_requests:ratio_rate5m'
):
// Graph showing all requests ratios
req_ratio: $.grafana.common {
title: 'API requests ratios',
formula: metric.rules.requests_ratiorate_job_verb_code.record,
legend: '{{ verb }} - {{ code }}',
},
您可以在 dash-kubeapi.jsonnet[26] 阅读我们的实现,以下是生成的仪表板的屏幕截图:
SLO Grafana 仪表板屏幕截图
我们在 bitnami-labs/kubernetes-grafana-dashboards[27] 文件夹下的bitnami-labs/kubernetes-grafana-dashboards存储库中实现了上述jsonnet
想法。
我们构建的 Prometheus 规则和 Grafana 仪表板文件是从 jsonnet 源生成的,如下所示:
SLO jsonnet 工作流程
•spec-kubeapi.jsonnet
:尽可能多的纯数据规范(阈值、规则和仪表板公式)
•rules-kubeapi.jsonnet[28]:输出 Prometheus 记录规则和警报•dash-kubeapi.jsonnet[29]:输出 Grafana 仪表板,通过我们的 bitnami_grafana.libsonnet
使用 grafonnet-lib[30]。
自从我们开始这个项目以来,社区已经创建了许多其他有用的 Prometheus 规则。查看 srecon17_americas_slides_wilkinson.pdf[31] 了解更多信息。如果我们不得不从头开始,我们可能会将 kubernetes-mixin[32] 与 jsonnet-bundler[33] 一起使用。
[1]
CRE life 课程: https://cloudplatform.googleblog.com/2017/01/available-or-not-that-is-the-question-CRE-life-lessons.html
[2]
SLO、SLI、SLA - CRE life 课程: https://cloudplatform.googleblog.com/2017/01/availability-part-deux--CRE-life-lessons.html
[3]
Google 的 SRE 书中关于服务级别目标: https://landing.google.com/sre/book/chapters/service-level-objectives.html
[4]
RED: https://grafana.com/files/grafanacon_eu_2018/Tom_Wilkie_GrafanaCon_EU_2018.pdf
[5]
@tom_wilkie: https://twitter.com/tom_wilkie
[6]
USE: http://www.brendangregg.com/usemethod.html
[7]
@brendangregg: https://twitter.com/brendangregg
[8]
RED 方法: https://grafana.com/files/grafanacon_eu_2018/Tom_Wilkie_GrafanaCon_EU_2018.pdf
[9]
Prometheus: https://prometheus.io/
[10]
Grafana: https://grafana.com/
[11]
jsonnet: https://jsonnet.org/
[12]
bitnami-labs/kubernetes-grafana-dashboards: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/
[13]
spec-kubeapi.jsonnet: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/blob/master/jsonnet/spec-kubeapi.jsonnet
[14]
promql-histogram: https://prometheus.io/docs/practices/histograms/
[15]
PromQL: https://prometheus.io/docs/prometheus/latest/querying/basics/
[16]
详见普罗米修斯问题 1227: https://github.com/prometheus/prometheus/issues/1227
[17]
记录规则(recording rules): https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/
[18]
bitnami-labs/kubernetes-grafana-dashboards: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/
[19]
bitnami-labs/kubernetes-grafana-dashboards: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/
[20]
gitops 工作流: https://www.weave.works/blog/category/gitops/
[21]
jsonnet: https://github.com/grafana/grafonnet-lib
[22]
python: https://github.com/weaveworks/grafanalib
[23]
Javascript: http://docs.grafana.org/reference/scripting/
[24]
grafonnet-lib: https://github.com/grafana/grafonnet-lib
[25]
jsonnet: https://github.com/grafana/grafonnet-lib
[26]
dash-kubeapi.jsonnet: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/blob/master/jsonnet/dash-kubeapi.jsonnet
[27]
bitnami-labs/kubernetes-grafana-dashboards: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/
[28]
rules-kubeapi.jsonnet: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/blob/master/jsonnet/rules-kubeapi.jsonnet
[29]
dash-kubeapi.jsonnet: https://github.com/bitnami-labs/kubernetes-grafana-dashboards/blob/master/jsonnet/dash-kubeapi.jsonnet
[30]
grafonnet-lib: https://github.com/grafana/grafonnet-lib
[31]
srecon17_americas_slides_wilkinson.pdf: https://www.usenix.org/sites/default/files/conference/protected-files/srecon17_americas_slides_wilkinson.pdf
[32]
kubernetes-mixin: https://github.com/kubernetes-monitoring/kubernetes-mixin
[33]
jsonnet-bundler: https://github.com/jsonnet-bundler/jsonnet-bundler