在 OSX 下使用 makecontext 官网文档的例子的时候,发现会进入一个死循环:

下面是完整代码(附注释):

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#define _XOPEN_SOURCE
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>

static ucontext_t uctx_main, uctx_func1, uctx_func2;

#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void
func1(void)
{
printf("func1: started\n");
printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
if (swapcontext(&uctx_func1, &uctx_func2) == -1)
handle_error("swapcontext");
printf("func1: returning\n");
}

static void
func2(void)
{
printf("func2: started\n");
printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
if (swapcontext(&uctx_func2, &uctx_func1) == -1)
handle_error("swapcontext");
printf("func2: returning\n");
}

int
main(int argc, char *argv[])
{
// 16384 16K
char func1_stack[16384];
char func2_stack[16384];

if (getcontext(&uctx_func1) == -1)
handle_error("getcontext");
uctx_func1.uc_stack.ss_sp = func1_stack;
uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
uctx_func1.uc_link = &uctx_main;
makecontext(&uctx_func1, func1, 0);

// 1. 初始化 uctx_func2 上下文环境,这个上下文恢复的时候,rip 指向 getcontext 汇编之后的下一条指令。
if (getcontext(&uctx_func2) == -1)
handle_error("getcontext");
// 2. uctx_func2 的 rip 如果没有被成功修改为 func2,将 uctx_func2 上下文恢复的时候,会从下面这一行开始执行。因为 getcontext 的调用是成功的。
uctx_func2.uc_stack.ss_sp = func2_stack;
uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
/* Successor context is f1(), unless argc > 1 */
uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;

// 3.调用失败,但是不会有任何报错信息,也没有返回值。
// 如果调用成功,uctx_func2 的 rip 指向的是 func2,从而在恢复 uctx_func2 的时候,去执行 func2 函数。
makecontext(&uctx_func2, func2, 0);

printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
// 4. 保存当前上下文到 uctx_main 中,还原到上下文 uctx_func2。
// 5. 由于 uctx_func2 指向 getcontext 的下一条指令,所以会从 `uctx_func2.uc_stack.ss_sp = func2_stack;` 这一行继续执行,进入一个死循环。
if (swapcontext(&uctx_main, &uctx_func2) == -1)
handle_error("swapcontext");

printf("main: exiting\n");
exit(EXIT_SUCCESS);
}

当在 OSX 下执行的时候,会无限输出 main: swapcontext(&uctx_main, &uctx_func2)

详细解答在: https://stackoverflow.com/questions/40299849/context-switching-is-makecontext-and-swapcontext-working-here-osx

OSX 的 makecontext 里面实现存在的问题:

源码:https://github.com/Apple-FOSS-Mirror/Libc/blob/2ca2ae74647714acfc18674c3114b1a5d3325d7d/x86_64/gen/makecontext.c

MINSIGSTKSZ 过大(32K),我们设置的栈大小只有 16K。

1
2
3
4
5
6
7
8
9
10
else if ((ucp->uc_stack.ss_sp == NULL) ||
(ucp->uc_stack.ss_size < MINSIGSTKSZ)) {
/*
* This should really return -1 with errno set to ENOMEM
* or something, but the spec says that makecontext is
* a void function. At least make sure that the context
* isn't valid so it can't be used without an error.
*/
ucp->uc_mcsize = 0;
}

总结来说就是:

  1. OSX 下的 MINSIGSTKSZ 为 32K,然后 OSX 里面的 makecontext 实现判断 ucontext_t 的栈大小小于 MINSIGSTKSZ 的时候,会直接返回,但是这个时候我们的调用是失败的, 因为成功的 makecontext 调用应该会将 ucontext_t 里面的 rip 修改为 func2 的地址。(rip 是保存下一条需要执行的指令的地址的寄存器)。

  2. getcontext 的作用是初始化一个 ucontext_t,初始化之后,这个 ucontext_t 指向 getcontext 调用的下一条指令(汇编层面的指令)。

结果就导致当我们调用 swapcontext 的时候,使用 uctx_func2 来恢复上下文环境的时候,实际上执行的是 getcontext 的下一条指令。然后后面调用 makecontext 仍然失败,从而无限循环。

解决方法

将栈的大小设置为 32K 或者更大,也就是把源码里面的 16384 修改为 32768。

通过代码描述一下这里的具体操作:

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
type Parent interface {
Test()
}

type Child struct {
Name string `json:"name"`
}

func (c Child) Test() {
}

func createChild() Parent {
return Child{}
}

func test() Parent {
// c 是一个 Parent 类型
c := createChild()

s := `{"name":"123"}`
err := json.Unmarshal([]byte(s), &c)
if err != nil {
panic(err)
}

// 问题:这里能不能得到 name 的值 123?
fmt.Printf("name=%s\n", c.(Child).Name)

return c
}

答案:不行

问题描述

具体就是,我有一个结构体 Child 实现了接口 Parent,然后 Child 里面有 name 属性,然后在反序列化的时候,用指向 Parent,类型的值来接收。

结果就是,结构体里面字段都丢失了。

如果我们写成下面这样,就可能错过了发现错误的机会(我一开始就是这么写的):

1
_ = json.Unmarshal([]byte(s), &c)

如果打印错误的话,其实编译器已经给出了明确的报错信息:

1
2
panic: json: cannot unmarshal object into Go value of type body.Parent [recovered]
panic: json: cannot unmarshal object into Go value of type body.Parent

原因

在反序列化的时候,反射处理是拿不到那个值的具体类型的。

参考链接:https://stackoverflow.com/questions/32428797/unmarshal-to-a-interface-type

原答案:

You can marshal from an interface type variable, because the object exists locally, so the reflector knows the underlying type.

You cannot unmarshal to an interface type, because the reflector does not know which concrete type to give to a new instance to receive the marshaled data.

启动命令

1
docker run -d --rm --name=alert-mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /usr/lnmp/alert-mysql-data:/var/lib/mysql mysql/mysql-server:8.0.26

参数说明:

  • -d: 后台运行
  • --rm: 停止之后删除容器
  • --name=alert-mysql: 指定容器名称
  • -p 3307:3306: 宿主机的 3307 端口映射到容器的 3306 端口
  • -e MYSQL_ROOT_PASSWORD=123456: mysql 的初始化密码
  • -v /usr/lnmp/alert-mysql-data:/var/lib/mysql: 宿主机的 /usr/lnmp/alert-mysql-data 映射到容器的 /var/lib/mysql

修改密码

默认的 root 用户只能在容器实例里面进行连接,在宿主机上连接不了。

如果我们想在容器外连接数据库,则需要进行以下操作:

1
2
3
4
5
$ docker exec -it mysqldb bash
# mysql -h localhost -u root -p
(now enter the passsword 123456 and hit enter)
mysql> create user 'root'@'%' identified WITH mysql_native_password by '123456';
mysql> grant all privileges on *.* to 'root'@'%' with grant option;

本来这一篇应该有很多图才是的,但是这玩意加个图实在是太麻烦了,没有搞,主要讲讲流程。

这个系统的另一部分是 grafana,不过这里不说了,那个需要很多图才能说明白,可以自行搜索。

前言

本文不会细说 Prometheus 的原理、机制等,这里只是简单说一下,详细的还得自己去看文档

文档链接:prometheus

Prometheus 是什么?

简单来说,就是一个时序数据库(TSDB),它可以存储时序数据,也可以向外提供查询(通过 PromQL)。

Prometheus 的作用是什么?

  • 通过存储时序数据,让我们可以监控一些我们感兴趣的系统指标,如 CPU、内存 占用等,然后可以通过 AlertManager 来在指标到达阈值的时候告知相关人员。从而做出及时处理。

  • 另外一个方面,我们也可以通过历史时序数据来看到指标的变化趋势,从而在趋势发生的初期就做出处理。

Prometheus 能监控什么?

  • 服务器基础指标,只有你想不到,没有它监控不到,如:CPU、内存、磁盘、网络、甚至上下文切换次数等

  • 常见软件的监控,如 MySQL、MongoDB,这些软件通过一些 exporter 来采集,目前有很多现成的,覆盖了常见的软件。具体可以在 exporters 上找到

  • 自定义指标的监控,其实 Prometheus 采集的数据格式很简单,只要我们按照指定的格式返回一段 HTTP 文本就行。

Prometheus 工作机制

上面说了,Prometheus 的定位其实是一个数据库,所以在实际使用中,一般会配合使用 grafana 来将 Prometheus 采集到的数据展示为图表。

使用环境

  • CentOS 7 64位

Prometheus 安装

下载 Prometheus

下载地址:Prometheus

1
2
3
wget https://github.com/prometheus/prometheus/releases/download/v2.29.1/prometheus-2.29.1.linux-amd64.tar.gz
tar -xvf prometheus-2.29.1.linux-amd64.tar.gz
cd prometheus-2.29.1.linux-amd64

修改配置文件

prometheus.yml

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
33
34
35
# 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:
# - localhost:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
#rule_files:
# - "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=<job_name>` to any timeseries scraped from this config.
- job_name: 'server_status'

# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.

static_configs:
- targets: ['test1.com:9092', 'test2.com:9092', 'test3.com:9092']

relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+)(:[0-9]+)?'
replacement: '${1}'

这里先把 alertingrule_files 的配置注释掉,后面安装了 AlertManager 之后再来配置。

需要注意的时候,到这一步的时候,我们的 prometheus 是还不能正常使用的,因为我们的数据源实际上还没有配置。

这里需要详细说明的是:

  • global.scrape_interval 间隔多久采集一次数据
  • scrape_configs 采集的数据源配置

scrape_configs 配置详解

这个是最核心的一个配置了,主要配置了我们的 Prometheus 需要从哪里采集数据,先说一下上面每一个配置项:

  • job_name 这个是区分我们的时序数据源类型的标识,这个标识是我们自己区分数据使用的,可以自定义。这里命名为 server_status 是因为我这个配置就是采集服务器的状态数据的。
  • static_configs 主要作用是配置我们的数据源地址
  • relabel_configs 我们在这个配置里面可以改写一些标签的值,比如上面这个配置会将 test1.com:9092 里面的端口移除,最后我们使用 PromQL 查询的时候就可以不填写端口了。

exporter 安装

Exporter 是什么?

Exporter 就是部署在我们要监控的服务器上,协助 Prometheus 采集数据的一个轻量的服务。(目标服务器->exporter)->Prometheus

Exporter 可以看作是一个 HTTP 服务器,它运行在我们的服务器上,当我们请求它的时候,它会去采集服务器上的指标数据,然后返回给发起请求的客户端。

比如,node_exporter,假设我们在 1.2.3.4:9092 这个服务器上启动了 node_exporter,当我们请求 http://1.2.3.4:9092/metrics 的时候,会返回如下数据:

1
2
3
4
5
6
7
8
9
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.8845e-05
go_gc_duration_seconds{quantile="0.25"} 4.7691e-05
go_gc_duration_seconds{quantile="0.5"} 8.7005e-05
go_gc_duration_seconds{quantile="0.75"} 0.000117043
go_gc_duration_seconds{quantile="1"} 0.002664528
go_gc_duration_seconds_sum 74.855671321
go_gc_duration_seconds_count 681610

这就是 exporter 返回的格式,HELP 说明接下来的数据是什么样的数据,而 TYPE 指定了接下来数据的类型。Prometheus 总共有四种数据类型,上面的 go_gc_duration_seconds 的类型为 summary

安装一个 node_exporter

到 node_exporter 的 releases 页面下载一个来安装:

1
2
3
wget https://github.com/prometheus/node_exporter/releases/download/v1.2.2/node_exporter-1.2.2.linux-amd64.tar.gz
tar -xvf node_exporter-1.2.2.linux-amd64.tar.gz
cd node_exporter-1.2.2.linux-amd64

启动 node_exporter

1
./node_exporter --web.listen-address=:9092

测试是否安装成功

1
curl http://1.2.3.4:9092

如果能返回类似上面格式的数据,说明我们的 exporter 部署成功了。

修改 Prometheus 配置

将里面的 scrape_configs 修改为以下内容:

1
2
3
4
5
6
7
8
9
10
11
scrape_configs:
- job_name: 'server_status'

static_configs:
- targets: ['1.2.3.4:9092']

relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+)(:[0-9]+)?'
replacement: '${1}'

这里的 1.2.3.4 在我们配置的时候替换为实际的 ip 即可,这里的 relabel_configs 配置表明我们最终的目标主机地址的标签会替换为名字为 instance 的标签。

启动 Prometheus

到现在为止,我们的配置文件如下:

prometheus.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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_configs:
- job_name: 'server_status'

static_configs:
- targets: ['1.2.3.4:9092']

relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+)(:[0-9]+)?'
replacement: '${1}'

我们使用这个配置文件来启动我们的 Prometheus 服务器:

1
prometheus --config.file=prometheus.yml --web.listen-address=:9091

这里的 --config.file 指定了配置文件的路径,--web.listen-address 指定了我们的 Prometheus 监听的地址。

测试

假设我们的 Prometheus 的服务器 ip 为 192.168.2.168,我们通过浏览器访问 http://192.168.2.168:9091 就可以看到我们的 Prometheus 自带的 web 页面,这个页面可以看到我们的采集目标是否正常运作(也就是上面的 scrape_configs 指定的地址能否正常访问),也可以通过 PromQL 来做一些查询。

Prometheus 报警规则设置

我们在 prometheus 安装目录下定义一个名字为 rules.yml 的配置文件,这个文件主要用来配置 Prometheus 的报警规则的。下面是一个示例文件:

rules.yml

1
2
3
4
5
6
7
8
9
10
11
groups:
- name: test_cpu_monitor
rules:
- alert: Cpu
expr: (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) * 100 > 90
for: 1m
labels:
severity: "warning"
annotations:
summary: "{{ $labels.instance }} {{ $labels.job }} cpu usage larger than 90%"
description: "cpu usage {{ $labels.instance }}"
  • expr 是一个 PromQL 表达式,这个表达式的结果是 true 的时候,这个报警规则报警状态为 pending(还不会报警),如果 5 分钟之后,这个表达式依然是 true,则会触发报警。

我们可以使用 prometheus 安装目录下面的 promtool 来检查报警规则配置是否正确:

1
./promtool check config prometheus.yml

在增加了这个报警规则配置文件之后,接下来我们还需要修改 Prometheus 的配置文件,添加以下配置:

1
2
rule_files:
- "rules.yml"

AlertManager 安装配置

我们可以在官方的 github releases 上找到我们对应系统版本的安装包。

1
2
3
wget https://github.com/prometheus/alertmanager/releases/download/v0.23.0-rc.0/alertmanager-0.23.0-rc.0.linux-amd64.tar.gz
tar -xvf alertmanager-0.23.0-rc.0.linux-amd64.tar.gz
cd alertmanager-0.23.0-rc.0.linux-amd64

我们添加以下配置文件:

alertmanager.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
global:
smtp_smarthost: 'smtp.qq.com:25'
smtp_from: 'xx@qq.com'
smtp_auth_username: 'xxx@qq.com'
smtp_auth_password: 'xxyy'
route:
group_by: ['alertname']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: 'web.hook' # 发送报警信息给下面哪一个接收方,匹配 receivers 里面的 name
receivers:
- name: 'ruby'
email_configs:
- to: 'xx@gmail.com'
- name: 'web.hook'
webhook_configs:
- url: 'http://localhost:8010/dingtalk/webhook2/send'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']

测试配置文件是否正确

在 alertmanager 的安装目录下面有一个名为 amtool 的文件,我们可以通过这个文件来检查我们的配置文件是否正确

1
./amtool check-config alertmanager.yml

安装配置钉钉 webhook

在这里我们就不使用邮件来发送了,我们使用钉钉来发送报警通知。

这里的报警需要用到钉钉机器人,具体可以搜索一下,然后去钉钉群里面配置一个即可。

prometheus-webhook-dingtalk

在 github 上已经有一个实现的钉钉报警webhook版本了,直接使用即可:

1
2
3
wget https://github.com/timonwong/prometheus-webhook-dingtalk/releases/download/v2.0.0/prometheus-webhook-dingtalk-2.0.0.linux-amd64.tar.gz
tar -xvf prometheus-webhook-dingtalk-2.0.0.linux-amd64.tar.gz
cd prometheus-webhook-dingtalk-2.0.0.linux-amd64

我们在这个目录下创建一个 config.yml 配置文件:

1
2
3
targets:
webhook2:
url: https://oapi.dingtalk.com/robot/send?access_token=xxx

在这里其他无关的配置就不列出来了,关键的配置就这三行。url 就是我们钉钉机器人的请求地址。这个地址可以在配置钉钉机器人的界面获取。

启动 webhook

配置完之后,我们可以通过以下命令来启动:

1
./prometheus-webhook-dingtalk --config.file=config.yml --web.listen-address=:8010

修改 AlertManager 配置

在上一步启动的时候,我们会看到它输出了一个 webhook 地址,如 http://localhost:8010/dingtalk/webhook2/send,而这个地址就是沟通 alertmanager 跟 钉钉 的桥梁了。

我们在上面安装配置 AlertManager 那一步已经写入了这个配置,所以这一步就直接跳过了。

给 Prometheus 添加报警配置

我们安装了 AlertManager、也安装了钉钉报警的 webhook 服务,但是这个时候我们的 Prometheus 依然是不能报警的,我们还需要修改 Prometheus 的配置,让 Prometheus 在判断到需要报警的时候,去告知 AlertManager。

prometheus.yml 添加以下配置:

1
2
3
4
5
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093

这个 localhost:9093 是我们 AlertManager 监听的端口。

修改完配置之后,我们将 rules.yml 里面的 PromQL 表达式的报警值设置低一点,这样我们就可以看到报警了,待我们测试没有问题,再将报警值修改为我们预定的值即可。比如:

1
expr: (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) * 100 > 1  # CPU 占用大于 1% 就报警

修改完 prometheus.yml 以及 rules.yml 这两个配置文件之后,重启一下 Prometheus,让配置生效。

不出意外的话,过 5 分钟我们就可以收到报警通知了。

最终的目录结构以及配置文件、启动命令

Prometheus

目录结构:

1
2
3
4
5
6
7
8
9
10
.
├── console_libraries
├── consoles
├── data
├── LICENSE
├── NOTICE
├── prometheus # prometheus 启动的入口文件
├── prometheus.yml # Prometheus 配置文件
├── promtool # 检查 prometheus 配置文件是否有效的工具
└── rules.yml # 报警规则配置,会在 prometheus.yml 引入
  1. prometheus.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.

alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093

rule_files:
- "rules.yml"

scrape_configs:
- job_name: 'server_status'

static_configs:
- targets: ['1.2.3.4:9092'] # 这里为监控目标服务器上 node_exporter 的地址

relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+)(:[0-9]+)?'
replacement: '${1}'
  1. rules.yml
1
2
3
4
5
6
7
8
9
10
11
groups:
- name: test_cpu_monitor
rules:
- alert: Cpu
expr: (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance)) * 100 > 90 # cpu 占用大于 90 的时候报警
for: 1m
labels:
severity: "warning"
annotations:
summary: "{{ $labels.instance }} {{ $labels.job }} cpu usage larger than 90%"
description: "cpu usage {{ $labels.instance }}"

启动命令:

1
./prometheus --config.file=prometheus.yml --web.listen-address=:9091

node_exporter

这个需要在我们的目标服务器上安装,当然,我们也可以安装在 Prometheus 所在的服务器上。

目录结构:

1
2
3
├── LICENSE
├── node_exporter
└── NOTICE

这个没有需要配置的,启动的时候指定一个监听地址即可,如下:

启动命令:

1
./node_exporter --web.listen-address=:9092

AlertManager

目录结构:

1
2
3
4
5
6
├── alertmanager     # AlertManager 的启动入口文件
├── alertmanager.yml # AlertManager 配置文件
├── amtool # 检查配置文件是否有效的工具
├── data
├── LICENSE
└── NOTICE
  1. alertmanager.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
global:
smtp_smarthost: 'smtp.qq.com:25'
smtp_from: 'xx@qq.com'
smtp_auth_username: 'xx@qq.com'
smtp_auth_password: 'xx'
route:
group_by: ['alertname']
group_wait: 30s
group_interval: 5m
repeat_interval: 1h
receiver: 'web.hook'
receivers:
- name: 'web.hook'
webhook_configs:
- url: 'http://localhost:8010/dingtalk/webhook2/send'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']

启动命令:

1
./alertmanager --config.file=alertmanager.yml

prometheus-webhook-dingtalk

目录结构:

1
2
3
4
5
├── config.example.yml          # 配置示例文件
├── config.yml # 实际使用的配置文件
├── contrib
├── LICENSE
└── prometheus-webhook-dingtalk # 启动的入口文件
  1. config.yml
1
2
3
targets:
webhook2:
url: https://oapi.dingtalk.com/robot/send?access_token=db655f2c0a13a5abc91368422e39d0e83bc397218047e66c24acf55af43e0fc2

启动命令:

1
./prometheus-webhook-dingtalk --config.file=config.yml --web.listen-address=:8010

再进一步

上面我们都是通过手动运行的方式来启动的,但是在实际应用中,我们可能会使用 supervisor 来配置。又或者有更高级的方式,k8s 然后服务发现啥的。

Prometheus 提供了一个叫 PromQL(Prometheus Query Language) 的函数式查询语言,允许用户实时地选择和聚合时间序列数据。查询结果可以展示为表格或者图表,或者被通过 HTTP API 来消费(比如被 grafana 使用)。

表达式语言数据类型

在 Prometheus 的表达式语言里面,一个表达式或者子表达式可以计算为以下四种类型之一:

  • 即时向量:一组时间序列,每个时间序列包含一个样本,所有时间序列都共享相同的时间戳
  • 范围向量:一组时间序列,包含每个时间序列随时间变化的数据点范围
  • 标量:一个简单的数字浮点值
  • String:一个简单的字符串值;目前未使用

时间序列选择器

即时向量选择器

  • 选择名称为 http_requests_total 的所有时间序列
1
http_requests_total

根据标签过滤

1
http_requests_total{job="prometheus"}

标签匹配运算符:

  • =:选择与提供的字符串完全相同的字符串
  • !=: 选择不等于提供的字符串的标签
  • =~: 选择与提供的字符串正则表达式匹配的标签
  • !~: 选择与提供的字符串不匹配的标签

例如,此选择所有环境为 stagingtestingdevelopment,以及 HTTP 方法不是 GET 的时间序列数据:

1
http_requests_total{environment=~"staging|testing|development", method!="GET"}
  • 根据指标名称匹配
1
{__name__=~"job:.*"}

范围向量选择器

范围向量字面量的工作方式与即时向量字面量类似,不同之处在于它们从当前时刻选择了一系列样本。从语法上讲,在向量选择器的末尾将持续时间附加在方括号([])中,以指定应该为每个结果范围向量元素提供多远的时间值。

  • 选择过去 5 分钟内名称为 http_requests_total 以及 job 标签为 prometheus 的所有时序数据
1
http_requests_total{job="prometheus"}[5m]

持续时间

持续时间指定为一个数字,后跟以下单位之一:

  • ms - 毫秒
  • s - 秒
  • m - 分钟
  • h - 小时
  • d - days
  • w - 周
  • y - 年

如:

1
2
3
4
5h
1h30m
5m
10s

偏移修改器

  • 返回 http_requests_total 过去 5 分钟相当于当前查询评估时间的值:
1
http_requests_total offset 5m

请注意,offset 修饰符总是需要立即跟随选择器,即以下是正确的:

1
sum(http_requests_total{method="GET"} offset 5m)

以下是不正确的:

1
sum(http_requests_total{method="GET"}) offset 5m
  • 同样适用于范围向量。返回 http_requests_total 一周前的 5 分钟频率:
1
rate(http_requests_total[5m] offset 1w)

@修饰符

  • 返回时间戳为 1609746000 的 http_requests_total 的值
1
http_requests_total @ 1609746000

请注意,@修饰符总是需要立即跟随选择器

1
2
sum(http_requests_total{method="GET"} @ 1609746000)  // 正确
sum(http_requests_total{method="GET"}) @ 1609746000 // 无效

这同样适用于范围向量。

1
rate(http_requests_total[5m] @ 1609746000)

offset 一起使用:

1
2
3
4
# offset after @
http_requests_total @ 1609746000 offset 5m
# offset before @
http_requests_total offset 5m @ 1609746000

默认情况禁用 @ 修饰符。

子查询

子查询允许你对给定的范围和分辨率运行即时查询。子查询的结果是一个范围向量:

语法:

1
<instant_query> '[' <range> ':' [<resolution>] ']' [ @ <float_literal> ] [ offset <duration> ]
  • <resolution> 是可选的。
0%