极客时间:运维监控系统实战笔记
目录:
运维监控系统实战笔记
开篇词
故障的生命周期:故障恢复->故障发现->故障定位->止损动作->故障恢复
减少故障有两个层面:
- 做好常态预防,不让故障发生
- 故障发生及时止损,减少故障时长
监控的主要功能体现在:故障发现和故障定位两个部分,还有日常巡检,以及性能调优的佐证
可监控和观测也是开发软件必须考虑的一环
常见的中间件暴露指标
- MySQL:基于全局变量sys,performance schema
- Redis:基于info all命令
- Kafka:基于JMX方式
- ElasticSearch:基于HTTP JSON接口
- etcd和Zookeeper等:基于prometheus接口
Java生态有Micrometer埋点,还可以引入链路追踪的skywalking、zipkin和jaeger等
1. 监控需求以及开源方案的横评对比
监控需求的来源,主要就是系统出了问题能及时进行感知
- 了解数据趋势,知道系统在未来某个时刻出现问题
- 了解系统的水位情况,为服务扩容提供数据支撑
- 给系统把脉,感知哪里需要优化
- 洞悉业务,提供业务决策的数据依据,及时感知业务异常
监控维度
- 指标监控:只能处理数字,优点就是历史数据存储成本较低,实时性好,生态庞大,例如zabbix、openfalcon、prometheus和nightingale
- 日志:可以了解软件的运行和运营情况,例如操作系统日志、接入层日志、服务运行日志,处理日志的有ELK和Loki等
- 链路追踪:随着微服务普及,原本单体应用被拆分很多小的服务,服务之间有错综复杂的调用关系,链路追踪的主要目的是以请求串联上下游模块,在追查问题的时候通过请求id进行串联,例如:Skywalking、Jaeger和Zipkin
Zabbix的优点
- 设备兼容性好,agent可以在windows和linux上运行
- 架构简单,数据库维护,备份和转储容易
- 12年开源,资料比较多
Zabbix的缺点
- 因为数据库存储,无法水平扩展,容量有限
- 面向资产管理逻辑,监控指标数据固化,没有灵活的标签设计
OpenFalcon基于RRD实现了分布式存储Graph,提升海量数据的存储
OpenFalcon的优点
- 可以监控大规模场景
- python和golang实现,易于开发
OpenFalcon的缺点
- 生态不庞大
- 开源治理不行
Prometheus就是为kubernetes而生
Prometheus的优点
- kubernetes支持好
- 生态庞大,有各种exporter,支持各种时序存储,支持不同语言的SDK和业务程序埋点
Prometheus的缺点
- 配置易用性差
- exporter功能单一,管理成本高
- 集群需要依赖其他功能
2. 监控行业黑话
早期的监控系统都是,一个指标一个数据,没有办法对多个同类指标进行汇总
10年,OpenTSDB时序数据库诞生,提供了一种标签描述指标的方法,随后时序数据库都引入了标签的概念,例如Prometheus认为指标名也是一种特殊的标签(key为__name__
)
早期的监控指标是不区分类型的,在RRDtool提出了指标的类型,包括GAUGE、COUNTER、DERIVE、DCOUNTER、DDERIVE和ABSOLUTE等多种数据类型
Prometheus主要支持4种
- Gauge 测量值,可正可负
- Counter 单调递增的值,例如网卡数据流量包,是持续增加的
- Histogram 直方图,描述数据的分布,例如延迟数据,计算90分位,因为访问量高每秒几百万的次数排序计算分位置代价太高了,可以规划延迟<200ms,<1s,<3s,<∞
- Summary 直方图,只不过是客户端计算好的
3. 一个监控系统的典型架构
架构:采集器->时序库->告警/数据展示
采集器:
- Telegraf:InfluxData开源,主要配合InfluxDB
- Exporter:Prometheus生态
- Grafana-Agent:All-In-One采集器,可以采集日志和监控数据,可以汇总exporter数据
时序库:
- OpenTSDB:基于Hbase和Cassandra封装
- InfluxDB:开源为单机版
- TDEngine:InfluxDB的国内开源,直接Prometheus的remote_read和remote_write,但是不支持Query
- M3db:Uber的时序数据库,架构复杂,CPU和内存占用较高
- VictoriaMetrics:
- TimescaleDB:PostgreSQL的扩展
告警引擎主要分两类:
- 数据触发式:服务端接收到监控数据,除了存储到时序数据库,还会转发一份数据给到监控引擎,实时性非常好,不过如果需要关联计算就很麻烦
- 周期轮询式:架构简单,按照用户配置的频率,周期性判断
数据展示只有Grafana
4. 快速搭建Prometheus系统
systemd托管
mkdir -p /opt/prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.37.1/prometheus-2.37.1.linux-amd64.tar.gz
tar xf prometheus-2.37.1.linux-amd64.tar.gz
cp -far prometheus-2.37.1.linux-amd64/* /opt/prometheus/
# service
cat <<EOF >/etc/systemd/system/prometheus.service
[Unit]
Description="prometheus"
Documentation=https://prometheus.io/
After=network.target
[Service]
Type=simple
ExecStart=/opt/prometheus/prometheus --config.file=/opt/prometheus/prometheus.yml --storage.tsdb.path=/opt/prometheus/data --web.enable-lifecycle --enable-feature=remote-write-receiver --query.lookback-delta=2m --web.enable-admin-api
Restart=on-failure
SuccessExitStatus=0
LimitNOFILE=65536
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=prometheus
[Install]
WantedBy=multi-user.target
EOF
systemctl enable prometheus
systemctl start prometheus
systemctl status prometheus
进程启动参数
--config.file=/opt/prometheus/prometheus.yml
指定 Prometheus 的配置文件路径
--storage.tsdb.path=/opt/prometheus/data
指定 Prometheus 时序数据的硬盘存储路径
--web.enable-lifecycle
启用生命周期管理相关的 API,比如调用 /-/reload 接口就需要启用该项
--enable-feature=remote-write-receiver
启用 remote write 接收数据的接口,启用该项之后,categraf、grafana-agent 等 agent 就可以通过 /api/v1/write 接口推送数据给 Prometheus
--query.lookback-delta=2m
即时查询在查询当前最新值的时候,只要发现这个参数指定的时间段内有数据,就取最新的那个点返回,这个时间段内没数据,就不返回了
--web.enable-admin-api
启用管理性 API,比如删除时间序列数据的 /api/v1/admin/tsdb/delete_series 接口
启动命令
nohup ./node_exporter &> output.log &
具体使用那些插件可以参考node_exporter的README.md
更改Prometheus配置
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node_exporter'
static_configs:
- targets: ['localhost:9100']
通过kill命令让Prometheus重新加载配置
kill -HUP <prometheus pid>
添加rule
rule_files:
- "node_exporter.yml"
配置报警
groups:
- name: node_exporter
rules:
- alert: HostDown
expr: up{job="node_exporter"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: Host down {{ $labels.instance }}
- alert: MemUtil
expr: 100 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100 > 1
for: 1m
labels:
severity: warn
annotations:
summary: Mem usage larger than 1%, instance:{{ $labels.instance }}
部署alertmanager
[Unit]
Description="alertmanager"
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/alertmanager/alertmanager
WorkingDirectory=/usr/local/alertmanager
Restart=on-failure
SuccessExitStatus=0
LimitNOFILE=65536
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=alertmanager
[Install]
WantedBy=multi-user.target
修改配置文件,同级目录下的alertmanager.yml
global:
smtp_from: 'username@163.com'
smtp_smarthost: 'smtp.163.com:465'
smtp_auth_username: 'username@163.com'
smtp_auth_password: '这里填写授权码'
smtp_require_tls: false
route:
group_by: ['alertname']
group_wait: 30s
group_interval: 1m
repeat_interval: 1h
receiver: 'email'
receivers:
- name: 'web.hook'
webhook_configs:
- url: 'http://127.0.0.1:5001/'
- name: 'email'
email_configs:
- to: 'ulricqin@163.com'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
grafana采用1860模板
5 Prometheus中有那些关键设计
标准先行,会进行标签的统一
- 指标名称metrics
- 唯一的服务标识service
- 实例名instance
- 服务分类job
- 可用区zone
- 集群名称cluster
- 环境类型env
主要是用拉取模式,辅以推送模式,可以控制数据获取频率,可以使用只出不进的ACL规则
基于服务发现实现
基于配置文件的管理方式
查询语言灵活
6 PromQL有那些常见的应用场景
主要用于时序数据的查询和二次计算
PromQL的第一个需求就是过滤,例如
{__name__=~"node_load.*", zone="sh"}
这是一个即时查询,返回的内容是一个即时向量,是最后一个时间点的数据,时间受--query.lookback-delta=1m
参数控制,建议为1m
在prometheus中,标签变为新的,就是一个新的时间序列了
向量匹配
匹配主要是三种
- one-to-one
- one-to-many
- many-to-one
对于one-to-one可以使用on和ignoring来限制用来匹配的标签集
示例,两者通过instance标签来进行匹配
mysql_slave_status_slave_sql_running == 0
and ON (instance)
mysql_slave_status_master_server_id > 0
one-to-many和many-to-one,需要使用group_left和group_right,left和right指向高基数那一侧的向量
## example series
method_code:http_errors:rate5m{method="get", code="500"} 24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120
## promql
method_code:http_errors:rate5m
/ ignoring(code) group_left
method:http_requests:rate5m
## result
{method="get", code="500"} 0.04 // 24 / 600
{method="get", code="404"} 0.05 // 30 / 600
{method="post", code="500"} 0.05 // 6 / 120
{method="post", code="404"} 0.175 // 21 / 120
所以可以使用全量信息label,指标值为1的元信息,通过group_left来做数据分组
# 统计5xx的请求数量,按Pod的version画一个饼图
# meta data
kube_pod_labels{
[...]
label_name="frontdoor",
label_version="1.0.1",
label_team="blue"
namespace="default",
pod="frontdoor-xxxxxxxxx-xxxxxx",
} = 1
# promql
sum(
rate(http_request_count{code=~"^(?:5..)$"}[5m])) by (pod)
*
on (pod) group_left(label_version) kube_pod_labels
聚合计算
聚合计算分
- 纵向聚合
- 横向聚合
纵向聚合是把折线图上的多条线聚合在一起
# 求取 clickhouse 的机器的平均内存可用率
avg(mem_available_percent{app="clickhouse"})
# 把 clickhouse 的机器的内存可用率排个序,取最小的两条记录
bottomk(2, mem_available_percent{app="clickhouse"})
avg(mem_available_percent{app=~"clickhouse|canal"}) by (app)
横向聚合是一个时间段有多个值,对多个值做运算
max_over_time(target_up[2m])
increase和rate
increase用于求增量,rate用于求变化量,在时间段内没有数据的部分,会进行估算
7 Prometheus的存储问题
Prometheus的单机容量上限,每秒接收80万个数据点,大概每个采集周期200个系统指标,采集间隔为10s,平均每秒上报20个数据点,可以支持4w台机器
- 多个Prometheus的联邦机制,可以将多个Prometheus数据汇聚到一个中心的Prometheus,但是不是抓去全量指标,感觉有点蠢
- 远程存储,针对实现了remote_read和remote_write的接口协议的时序数据库都可以
- VictoriaMetrics
- Thanos,每个Prometheus都需要一个Thanos Sider的组件来完成查询和数据块上传到对象存储
- Prometheus自身搭建集群,通过remote_read接口来查询其他Prometheus的数据
8 Nightingale解决报警问题
没讲清楚,有夹带私货嫌疑
9 监控数据采集方法
Google黄金指标
- 延迟:服务请求花费的时间
- 流量:对于HTTP就是每秒请求数,RPC就是每秒RPCCall数,数据库可以用事务量
- 错误:请求失败的速率,每秒请求失败数,状态码为200但是返回数据不符合预期也认为是请求失败
- 饱和度:应用程序有多满,例如CPU密集型的应用,CPU使用率就是饱和度指标
只要上述指标正常,服务就是健康的
RED方法
- Rate:请求速率,每秒请求数
- Errors:错误,每秒错误请求数
- Duration:延迟,每个请求的延迟分布情况
USE方法
- 使用率:内存使用率、CPU使用率等,是一个百分比
- 饱和度:资源排队工作的指标,无法再处理额外的工作。通常用队列长度表示,比如在iostat里看到的aqu-sz就是队列长度
- 错误:资源错误事件的计数。比如malloc()失败次数、通过ifconfig看到的errors、dropped包量。有很多错误是以系统错误日志的方式暴露的,没法直接拿到某个统计指标,此时可以进行日志关键字监控
监控分类
- 业务监控:订单量/用户数
- 应用监控:延迟/流量/错误/饱和度
- 组件监控:数控库/中间件
- 资源监控:服务器/网络设备/基础网路
业务监控是管理层关注的,有两个特点:
- 精确度要求没有特别高,主要是看趋势
- 对实时性要就高
应用监控的指标来源,也就是log,trace,metric
- 指标埋点
- 链路追踪
- 日志分析
组件监控需要搞懂组件原理,采集需要的数据
资源监控又会有细分
- 设备监控
- 网络监控
10 监控数据采集方法和原理
采集方式有很多种
- 读取proc目录
- 执行命令/连接到目标对象执行命令
- 远程黑盒探测:网络应用较多,例如丢包率,RTT等
- 拉取特定协议数据
- 代码埋点
- 日志解析
11 操作系统需要关注的指标
除了常见的cpu,内存,磁盘和网络使用率,还有
- conntrack使用率
- processes机器进程数
- tcp_time_wait
- oom_kill
12 网络链路和网络设备
网络链路
- 连通性依靠探针
- 流量大小可以通过交换机网口,机器网卡,外网出口等检测
- 流量内容有netflow,J-Flow,sFlow,netstream,ipfix等手段
网络设备
13 MySQL
延迟
- Client埋点记录SQL请求耗时
- SlowSQL
SHOW VARIABLES LIKE 'long_query_time';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.001 sec)
show global status like 'Slow_queries';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries | 107 |
+---------------+-------+
1 row in set (0.000 sec)
流量
- 读数量select
- 写数量insert+update+delete
- 总量
错误
- 客户端埋点
- 全局变量获取的连接错误
饱和度
- OS层面
- 连接饱和度
- innodb buffer pool(page使用率/请求穿透率)
14 Redis
延迟
- redis-cli --latency监控延迟
- slowlog设置: slow
15 Kafka
JMX是一个为应用程序植入管理功能的框架
Kafka开启JMX,修改bin/kafka-run-class.sh,KAFKA_JMX_OPTS添加-Dcom.sun.management.jmxremote.port
启动服务
export JMX_PORT=3457; ./bin/kafka-server-start.sh config/server.properties
通过jconsole(JDK内的工具),远程连接到3457端口即可
可以通过jmx_exporter指定MBean来采集需要的指标
Kafka开启Jolokia,下载地址
更改启动文件
# JMX settings
if [ -z "$KAFKA_JMX_OPTS" ]; then
KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false "
fi
# JMX port to use
if [ $JMX_PORT ]; then
KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -Dcom.sun.management.jmxremote.port=$JMX_PORT "
fi
########## adding lines
if [ "x$JOLOKIA" != "x" ]; then
KAFKA_JMX_OPTS="$KAFKA_JMX_OPTS -javaagent:/opt/jolokia/jolokia-jvm-1.6.2-agent.jar=host=0.0.0.0,port=3456"
fi
启动
export JOLOKIA=true; export JMX_PORT=3457; nohup ./bin/kafka-server-start.sh config/server.properties &> stdout.log &
curl -s 10.206.16.8:3456/jolokia/version
相关指标
- JVM
- Kafka broker的JMX指标
JVM
ThreadCount表示JVM里的线程数,类似的还有DaemonThreadCount,表示后台线程数,PeakThreadCount表示历史峰值线程数
JVM要重点关注GC的情况和内存的情况,GC主要看次数和时间,分为YongGC FullGC
- YongGC很正常,频率也比较高
- FullGC正常情况下很少发生,如果经常发生,FullGC程序的性能就会受影响
具体指标
- GC次数的指标是kafka_java_garbage_collector_CollectionCount,是一个Counter类型单调递增的值
- GC时间的指标是kafka_java_garbage_collector_CollectionTime,是一个Counter类型单调递增的值
- 内存的指标是kafka_java_memory_pool_Usage_used,单位是byte,name标签标识了具体是哪个区域的内存大小,比如Eden区、Survivor区、Old区
Brocker指标
- 活跃控制器数量 MBean:kafka.controller:type=KafkaController,name=ActiveControllerCount
一个Kafka集群有多个Broker,只有一个Broker会是活跃控制器,应该为1,如果SUM的结果为 2,也就是说,有两个Broker都认为自己是活跃控制器。这可能是网络分区导致的,需要重启Kafka Broker 进程。如果重启了不好使,可能是依赖的 ZooKeeper出现了网络分区,需要先去解决ZooKeeper的问题,然后重启Broker进程
- 非同步分区数量 MBean:kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions
指标是对每个Topic的每个Partition的统计,如果某个分区主从同步出现问题,对应的数值就会大于0。常见的原因比如某个Broker出问题了,一般是Kafka进程问题或者所在机器的硬件问题,那么跟这个Broker相关的分区就全部都有问题,这个时候出问题的分区数量大致是恒定的。如果出问题的分区数量不恒定,可能是集群性能问题导致的,需要检查硬盘I/O、CPU之类的指标
- 离线分区数量 MBean:kafka.controller:type=KafkaController,name=OfflinePartitionsCount
这个指标只有集群控制器才有,其他Broker 这个指标的值是 0,表示集群里没有 leader 的分区数量。Kafka 主要靠 leader 副本提供读写能力,如果有些分区没有 leader 副本了,显然就无法读写了,是一个非常严重的问题。
- 离线日志目录数量MBean:kafka.log:type=LogManager,name=OfflineLogDirectoryCountKafka
是把收到的消息存入log目录,如果log目录有问题,比如写满了,就会被置为Offline,及时监控离线日志目录的数量显然非常有必要。如果这个值大于 0,我们想进一步知道具体是哪个目录出问题了,可以查询 MBean:kafka.log:type=LogManager,name=LogDirectoryOffline,logDirectory=*",LogDirectory 字段会标明具体是哪个目录。流入流出字节和流入消息这是典型的吞吐指标,既有 Broker 粒度的,也有 Topic 粒度的,名字都一样,Topic 粒度的指标数据 MBean ObjectName 会多一个 topic=xx 的后缀
- 流入字节MBean:kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec
这个指标 Kafka 在使用Yammer Metrics埋点的时候,设置为了Meter类型,所以Yammer会自动计算出 Count、OneMinuteRate、FiveMinuteRate、FifteenMinuteRate、MeanRate等指标,也就是1分钟、5分钟、15分钟内的平均流入速率,以及整体平均流入速率。而Count表示总量,如果时序库支持PromQL,我们就只采集 Count,其他的不用采集,后面使用PromQL对Count值做irate计算即可
- 流出字节MBean:kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec
和BytesInPerSec类似,表示出向流量,除了普通消费者的消费流量,也包含了副本同步流量
- 流入消息MBean:kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSecBytesInPerSec
和BytesOutPerSec都是以byte为单位统计的,而MessagesInPerSec是以消息个数为单位统计的,也是Meter类型,相关属性都一样。需要解释一下的是,Kafka不提供 MessagesOutPerSec,你可能觉得有点儿奇怪,有入就得有出才正常嘛。这是因为消息被拉取的时候,Broker会把整个“消息批次”发送给消费者,并不会展开“批次”,也就没法计算具体有多少条消息了。
- 分区数量MBean:kafka.server:type=ReplicaManager,name=PartitionCount
这个指标表示某个Broker上面总共有多少个分区,包括leader分区和follower分区。如果多个Broker分区不均衡,可能会造成有些Broker消耗硬盘空间过快,这是需要注意的。
- eader分区数量MBean:kafka.server:type=ReplicaManager,name=LeaderCount
表示某个Broker上面总共有多少个leader分区,leader分区负责数据读写,承接流量,所以leader分区如果不均衡,会导致某些Broker过分繁忙而另一些 Broker 过分空闲,这种情况也是需要我们注意的。
以及consumergroup的消费滞后量,可以通过kafka_exporter
16 ElasticSearch
指标暴露方式为http接口
$ curl -s -uwhy:123456 'http://0.0.0.0:9200/_cluster/health?pretty'
{
"cluster_name" : "elasticsearch-cluster",
"status" : "green",
"timed_out" : false,
"number_of_nodes" : 3,
"number_of_data_nodes" : 3,
"active_primary_shards" : 216,
"active_shards" : 432,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 0,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 100.0
}
全部接口有
- /_cluster/health
- /_cluster/health?level=indices
- /_nodes/stats 节点状态
- /_nodes/_local/stats
- /_cluster/stats
- /_all/_stats
获取指定数据
/_nodes/stats/indices,os
索引相关数据
"indices": {
"docs": {
"count": 45339548,
"deleted": 2
},
"shard_stats": {
"total_count": 144
},
"store": {
"size_in_bytes": 15882899598,
"total_data_set_size_in_bytes": 15882899598,
"reserved_in_bytes": 0
},
- docs统计了文档的数量,包括还没有从段(segments)里清除的已删除文档数量
- shard_stats统计了分片的数量
- store统计了存储的情况,包括主分片和副本分片总共耗费了多少物理存储
各个阶段的吞吐和耗时
"indexing": {
"index_total": 18595844,
"index_time_in_millis": 1868991,
"index_current": 0,
"index_failed": 0,
"delete_total": 0,
"delete_time_in_millis": 0,
"delete_current": 0,
"noop_update_total": 0,
"is_throttled": false,
"throttle_time_in_millis": 0
},
"search": {
"open_contexts": 0,
"query_total": 140847,
"query_time_in_millis": 59976,
"query_current": 0,
"fetch_total": 1333,
"fetch_time_in_millis": 990,
"fetch_current": 0,
"scroll_total": 3,
"scroll_time_in_millis": 86,
"scroll_current": 0,
"suggest_total": 0,
"suggest_time_in_millis": 0,
"suggest_current": 0
},
"merges": {
"current": 0,
"current_docs": 0,
"current_size_in_bytes": 0,
"total": 64937,
"total_time_in_millis": 7170896,
"total_docs": 1341992706,
"total_size_in_bytes": 140455785325,
"total_stopped_time_in_millis": 0,
"total_throttled_time_in_millis": 160061,
"total_auto_throttle_in_bytes": 6707112372
},
- indexing:是统计索引过程,ES 的架构里,索引是非常关键的一个东西,索引的吞吐和耗时都应该密切关注,index_total和index_time_in_millis都是Counter类型的指标,单调递增。如果要求取最近一分钟的索引数量和平均延迟,就需要使用 increase 函数求增量。throttle_time_in_millis表示受限的时间,索引不能耗费太多资源,如果占用了太多资源就影响其他的操作,应该限制一下,这个指标就表示总计被限制的时间。
- search:描述在活跃中的搜索(open_contexts)数量、查询的总数量,以及自节点启动以来在查询上消耗的总时间。用 increase(query_time_in_millis[1m]) / increase(query_total[1m])计算出来的比值,可以用来粗略地评价你的查询有多高效。比值越大,每个查询花费的时间越多,到一定程度就要考虑调优了。
- fetch:统计值展示了查询处理的后一半流程,也就是query-then-fetch里的fetch部分。如果fetch耗时比query还多,说明磁盘较慢,可能是获取了太多文档,或者搜索请求设置了太大的分页。
- merges:包括了Lucene段合并相关的信息。它会告诉你目前在运行几个合并,合并涉及的文档数量,正在合并的段的总大小,以及在合并操作上消耗的总时间。合并要消耗大量的磁盘I/O和CPU资源,如果merge操作耗费太多资源,也会被限制,即total_throttled_time_in_millis指标。
其他的就是JVM数据
17 kubernetes node指标
kube-proxy
- 10249暴露监控指标
- 10256健康检测端口
curl -s localhost:10249/metrics
主要指标
# 请求APIServer的耗时
rest_client_request_duration_seconds
# 请求APIServer的调用量
rest_client_requests_total
# 规则同步耗时
kubeproxy_sync_proxy_rules_duration_seconds
# endpoint变化的总次数
kubeproxy_sync_proxy_rules_endpoint_changes_total
# iptables restore失败总次数
kubeproxy_sync_proxy_rules_iptables_restore_failures_total
# 最近一次开始同步的时间戳
kubeproxy_sync_proxy_rules_last_queued_timestamp_seconds
# 最近一次完成同步的时间戳
kubeproxy_sync_proxy_rules_last_timestamp_seconds
kubelet
在10250暴露两类metrics数据
- /metrics暴露kubelet数据
- /metrics/cadvisor暴露容器数据
主要指标
# 操作docker引擎的耗时
kubelet_docker_operations_duration_seconds
# 操作docker引擎的失败次数
kubelet_docker_operations_errors_total
# 操作docker引擎的超时次数
kubelet_docker_operations_timeout_total
# 操作docker引擎的次数
kubelet_docker_operations_total
# 操作网络插件的失败次数
kubelet_network_plugin_operations_errors_total
# 操作网络插件的超时次数
kubelet_network_plugin_operations_timeout_total
# 操作网络插件的次数
kubelet_network_plugin_operations_total
容器
容器层面主要就是CPU,内存,磁盘和网络,以及容器的状态
18 kubernetes控制面指标
控制组件就是
- apiserver
- controller-manager
etcd修复
--listen-metrics-urls=http://0.0.0.0:2381
KSM修复
git clone https://github.com/kubernetes/kube-state-metrics
kubectl apply -f kube-state-metrics/examples/standard/
KSM提供了
- 8080返回kubernetes对象信息
- 8081返回KSM自身指标
可以通过参数裁剪返回数据
# watch的对象类型
--resources=daemonsets,deployments
# 不想要的指标
--metric-denylist=kube_deployment_spec_.*
主要指标
# 请求量的指标,可以统计每秒请求数、成功率
apiserver_request_total
# 请求耗时的指标
apiserver_request_duration_seconds
# APIServer 当前处理的请求数,分为mutating(非get、list、watch的请求)和readOnly(get、list、watch 请求)两种,请求量过大就会被限流,所以这个指标对我们观察容量水位很有帮助
apiserver_current_inflight_requests
# 各个controller接收到的任务总数
workqueue_adds_total
# 各个controller的队列深度,表示各个controller中的任务的数量,数量越大表示越繁忙
workqueue_depth
# 任务在队列中的等待耗时,按照控制器分别统计
workqueue_queue_duration_seconds
# 任务出队到被处理完成的时间,按照控制器分别统计
workqueue_work_duration_seconds
# 任务进入队列的重试次数
workqueue_retries_total
# 调度器的选主状态,1表示master,0 表示 backup
leader_election_master_status
# 进入调度队列的Pod数量
scheduler_queue_incoming_pods_total
# Pending的Pod数量
scheduler_pending_pods
# Pod 调度成功前,调度重试的次数分布。
scheduler_pod_scheduling_attempts
# 调度框架的扩展点延迟分布,按extension_point统计
scheduler_framework_extension_point_duration_seconds
# 按照调度结果统计的尝试次数,unschedulable表示无法调度,error表示调度器内部错误
scheduler_schedule_attempts_total
# etcd是否有leader
etcd_server_has_leader
# 偶尔切主问题不大,频繁切主就要关注了
etcd_server_leader_changes_seen_total
# 提案失败次数
etcd_server_proposals_failed_total
# 提交花费的耗时
etcd_disk_backend_commit_duration_seconds
# wal日志同步耗时
etcd_disk_wal_fsync_duration_seconds
# Node节点状态,状态不正常、有磁盘压力等都可以通过这个指标发现
kube_node_status_condition
# 容器停止原因
kube_pod_container_status_last_terminated_reason
# 容器处于waiting状态的原因
kube_pod_container_status_waiting_reason
# 容器重启次数
kube_pod_container_status_restarts_total
# deployment配置期望的副本数
kube_deployment_spec_replicas
# deployment实际可用的副本数。
kube_deployment_status_replicas_available
KSM经典报警
# 长时间版本不一致需要告警
kube_deployment_status_observed_generation{job="kube-state-metrics"}
!=
kube_deployment_metadata_generation{job="kube-state-metrics"}
# deployment 副本数不一致
(
kube_deployment_spec_replicas{job="kube-state-metrics"}
!=
kube_deployment_status_replicas_available{job="kube-state-metrics"}
)
and
(
changes(kube_deployment_status_replicas_updated{job="kube-state-metrics"}[5m]) == 0
)
# 容器有 Error 或者 OOM 导致的退出
(sum(kube_pod_container_status_last_terminated_reason{reason=~"Error|OOMKilled"}) by (namespace,pod,container) > 0)
* on(namespace,pod,container)
sum(increase(kube_pod_container_status_restarts_total[10m]) > 0) by(namespace,pod,container)
19 应用埋点监控
一般采用封装SDK的方式
- StatsD
- Prometheus
示例go-statsd埋点
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
"github.com/smira/go-statsd"
)
var client *statsd.Client
func homeHandler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// random sleep
num := rand.Int31n(100)
time.Sleep(time.Duration(num) * time.Millisecond)
fmt.Fprintf(w, "duration: %d", num)
client.Incr("requests.counter,page=home", 1)
client.PrecisionTiming("requests.latency,page=home", time.Since(start))
}
func main() {
// init client
client = statsd.NewClient("localhost:8125",
statsd.TagStyle(statsd.TagFormatInfluxDB),
statsd.MaxPacketSize(1400),
statsd.MetricPrefix("http."),
statsd.DefaultTags(statsd.StringTag("service", "n9e-webapi"), statsd.StringTag("region", "bj")),
)
defer client.Close()
http.HandleFunc("/", homeHandler)
http.ListenAndServe(":8000", nil)
}
gin可以通过中间件暴露数据
func stat() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
code := fmt.Sprintf("%d", c.Writer.Status())
method := c.Request.Method
labels := []string{promstat.Service, code, c.FullPath(), method}
promstat.RequestCounter.WithLabelValues(labels...).Inc()
promstat.RequestDuration.WithLabelValues(labels...).Observe(float64(time.Since(start).Seconds()))
}
}
...
r := gin.New()
r.Use(stat())
...
import (
...
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func configRoute(r *gin.Engine, version string) {
...
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
}
20 日志监控
mtail, mtail的git地址
示例统计varlogmessage中的Out of memory数量
# 在 mtail 二进制同级目录下创建 progs 目录,下面放置 syslogs 子目录
# syslogs 子目录用于放置系统日志文件对应的提取规则
mkdir -p progs/syslogs
# 用于统计 Out of memory 关键字的 mtail 规则文件内容如下(我命名为
# syslogs.mtail):
counter oom_total
/Out of memory/ {
oom_total++
}
配置解释
- 第一行是声明了一个变量,类型是counter,变量名是oom_total
- 第二行是在
//
之间定义了一个正则表达式,用来匹配Out of memory,如果匹配成功,就执行大括号里的内容,对oom_total变量加1
启动命令
# 通过-progs参数指定mtail文件所在目录,指定具体的文件也可以
# 通过-logs参数指定要提取的日志文件是哪个,支持glob匹配,也支持传入多次-logs参数
$ ./mtail -progs ./progs/syslogs/ -logs /var/log/messages
# 默认启动3903端口
$ ss -tlnp|grep mtail
LISTEN 0 128 [::]:3903 [::]:* users:(("mtail",pid=18972,fd=7))
$ curl -s localhost:3903/metrics | grep oom_total
# HELP oom_total defined at syslogs.mtail:1:9-17
# TYPE oom_total counter
oom_total{prog="syslogs.mtail"} 0
也可以nginx日志
$ tail -n 5 /var/log/nginx/access.log
119.45.249.92 - - [17/Dec/2022:22:35:39 +0800] "GET / HTTP/1.1" 200 14849 "-" "clb-healthcheck" "-"
119.45.249.92 - - [17/Dec/2022:22:35:40 +0800] "GET / HTTP/1.1" 200 14849 "-" "clb-healthcheck" "-"
119.45.249.92 - - [17/Dec/2022:22:35:40 +0800] "GET / HTTP/1.1" 200 14849 "-" "clb-healthcheck" "-"
119.45.249.92 - - [17/Dec/2022:22:35:41 +0800] "GET / HTTP/1.1" 200 14849 "-" "clb-healthcheck" "-"
119.45.249.92 - - [17/Dec/2022:22:35:41 +0800] "GET / HTTP/1.1" 200 14849 "-" "clb-healthcheck" "-"
配置文件
counter request_total by method, url, code
counter body_bytes_sent_total by method, url, code
/"(?P<method>\S+) (?P<url>\S+) HTTP\/1.1" (?P<code>\d+) (?P<body_bytes_sent>\d+)/ {
request_total[$method][$url][$code]++
body_bytes_sent_total[$method][$url][$code] += $body_bytes_sent
}
启动采集
# 通过下面的命令加载 nginx 的 mtail,指定 nginx 的 access.log
./mtail -progs ./progs/nginx/ -logs /var/log/nginx/access.log
# 下面请求一下 /metrics 接口,看看是否采集成功
[root@fc-demo-02 qinxiaohui]# curl -s localhost:3903/metrics | grep -P 'request_total|body_bytes_sent_total'
# HELP body_bytes_sent_total defined at nginx.mtail:2:9-29
# TYPE body_bytes_sent_total counter
body_bytes_sent_total{code="200",method="GET",prog="nginx.mtail",url="/"} 1.143373e+06
# HELP request_total defined at nginx.mtail:1:9-21
# TYPE request_total counter
request_total{code="200",method="GET",prog="nginx.mtail",url="/"} 77
添加label
hidden text zone
zone = "beijing"
counter request_total by method, url, code, zone
counter body_bytes_sent_total by method, url, code, zone
/"(?P<method>\S+) (?P<url>\S+) HTTP\/1.1" (?P<code>\d+) (?P<body_bytes_sent>\d+)/ {
request_total[$method][$url][$code][zone]++
body_bytes_sent_total[$method][$url][$code][zone] += $body_bytes_sent
}
21 事件降噪
报警过多
- 规则设置不合理
- 底层问题导致上层所有依赖都进行报警
- 渠道错配
- 预期内维护动作
可以进行的规避措施
- 需要优化报警规则,针对每次故障也针对故障报警进行复盘
- 建立runbook报警处理手册,在报警发生需要排查那些方面,执行那些动作,或者准备一些预案
- 报警分级,分不同渠道
- 不同时间不同阈值
- 支持报警屏蔽
- 支持报警抑制
- 聚合报警发送
报警分级
- critical:重要需要立刻处理
- warning:无需立刻处理,完成紧急事情再来完成,会升级到critical
- info:报告类
22 报警如何闭环处理
报警需要团队一起处理
- 排班
- 告警升级机制,第一责任人没响应,会通知二线和三线
- 告警收敛逻辑
- 故障协同处理
- 告警自动处理