极客时间:运维监控系统实战笔记

时间:March 2, 2023 分类:

目录:

运维监控系统实战笔记

开篇词

故障的生命周期:故障恢复->故障发现->故障定位->止损动作->故障恢复

减少故障有两个层面:

  1. 做好常态预防,不让故障发生
  2. 故障发生及时止损,减少故障时长

监控的主要功能体现在:故障发现和故障定位两个部分,还有日常巡检,以及性能调优的佐证

可监控和观测也是开发软件必须考虑的一环

常见的中间件暴露指标

  • 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系统

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 接口

node exporter下载链接

启动命令

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 报警如何闭环处理

报警需要团队一起处理

  • 排班
  • 告警升级机制,第一责任人没响应,会通知二线和三线
  • 告警收敛逻辑
  • 故障协同处理
  • 告警自动处理