前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >prometheus内核

prometheus内核

原创
作者头像
王磊-字节跳动
修改2019-12-29 23:18:12
2.4K0
修改2019-12-29 23:18:12
举报
文章被收录于专栏:01ZOO01ZOO

架构

image.png
image.png

这篇文章会着重分析 其中的 discovery => scrap => storage 的流程

配置

配置有两部分,一部分来自命令行的启动参数,一部分来自 prometheus.yml

来自命令行的配置参数

配置核心的部分分成几块,其中以 web.Options 为重点,比如 notifier.Options 等其他配置在初始化的过程中最终会被转换为 web.Options 中的一部分。

  • configFile: prometheus.yml 文件路径
  • storage: 本地存储配置,可配置的有
    • localStoragePath: 数据存储位置
    • WALSegmentSize
    • newFlagRetentionDuration 存储时间
    • NoLockfile/ AllowOverlappingBlocks/ WALCompression: lock 压缩等
    • RemoteFlushDeadline: shutdown或者reload等时候需要 flush,flush超时时间
    • RemoteReadSampleLimit/ RemoteReadConcurrencyLimit/ RemoteReadBytesInFrame: Remote其他配置 sample数量、并发、大小等
  • alert
    • outageTolerance/ forGracePeriod/ resendDelay: alert 触发相关
    • notifier.QueueCapacity/notifierTimeout: notifier相关
  • query
    • lookbackDelta: maximum lookback duratio
    • queryTimeout/queryConcurrency/queryMaxSamples: 超时/并发/最大数量
  • web: prometheus 作为一个 web server的配置
    • web.ListenAddress/ webTimeout/ MaxConnections/ prometheusURL/ web.RoutePrefix/ web.UserAssetsPath 基本 web参数
    • web.EnableLifecycle/ web.EnableAdminAPI/ web.ConsoleTemplatesPath/ web.ConsoleLibrariesPath
    • web.PageTitle/ corsRegexString/
// prometheus/prometheus/cmd/prometheus/main.go

来自 prometheus.yml 的配置参数

来自 prometheus.yml 的参数对大部分组件是 reloadable 的,实现方式为 这些组件都实现了一个 ApplyConfig(*config.Config) error 的函数, 比如:

  • remoteStorage: 远程存储。支持比如 influx db/ open tsdb作为存储后端的核心
  • webHandler: web 核心
  • scrapeManager: 指标抓取
  • discoveryManagerScrape:发现抓取目标
  • discoveryManagerNotify:发现 alert 推送目标
  • ruleManager: 抓取规则(recording / alerting rules)管理
  • notifierManager: 分发 alert notifications 到 alert manager

也就是 当 prometheus.yml 本更新之后,这些组件无需重启(SIGHUP,web触发)就可以得到更新

配置的格式参考这里, 这里我们做简要的解释。

global:

  # 这两个是全局的刮取间隔以及超时设置 
  # How frequently to scrape targets by default.
  [ scrape_interval: <duration> | default = 1m ]
  # How long until a scrape request times out.
  [ scrape_timeout: <duration> | default = 10s ]

  # 多久evaluate rules一次
  [ evaluation_interval: <duration> | default = 1m ]

  # 全局的 external label, 当 prometheus (federation, remote storage, Alertmanager) 
  # 和外部交互的时候很有用 . 举个例子:当多个prometheus数据聚合到同一个federation prometheus
  # 或者 remote storage 的时候,可以加一个 id/cluster label作为区分
  external_labels:
    [ <labelname>: <labelvalue> ... ]

# 规则文件路径,规则分为两种,一种叫 recoding rule, 另一种叫 alter rule
# 他们都是 隔一段时间内部 evaluate 一次,生成新的 metrics 或者 产生 alter notification
rule_files:
  [ - <filepath_glob> ... ]

# 抓取配置,这个是配置文件的大头,各种抓取规则都在这里,下面会分解细讲
scrape_configs:
  [ - <scrape_config> ... ]

# Alertmanager相关的配置
alerting:
  alert_relabel_configs:
    [ - <relabel_config> ... ]
  alertmanagers:
    [ - <alertmanager_config> ... ]

# remote_write/read 相关的配置
remote_write:
  [ - <remote_write> ... ]
remote_read:
  [ - <remote_read> ... ]
  

scrape_configs

# 1. 每一个抓取任务叫一个 job,通常配置里面会有多个抓取任务,比如抓取node监控,server监控等
job_name: <job_name>

# 2. 对于这个job设置的抓取间隔和超时时间 
[ scrape_interval: <duration> | default = <global_config.scrape_interval> ]
[ scrape_timeout: <duration> | default = <global_config.scrape_timeout> ]

# 3. 这个job的抓取路径
[ metrics_path: <path> | default = /metrics ]
[ scheme: <scheme> | default = http ]
params:
  [ <string>: [<string>, ...] ]
  
# 4. 其他抓取相关的配置略,主要是抓取鉴权/代理相关,配置 prometheus 如何发送抓取请求
basic_auth/ bearer_token/ bearer_token_file/ tls_config/ proxy_url


# 5. 当 抓取到的指标 label 和 服务端生成的 label产生冲突的时候如何处理,服务端生成的 label 
# 包括 job / instance/ 配置 labels, labels 和 service discovery (sd) implementations 生成的 label;
# 最后一种很重要也很容易让人迷惑,后面会具体的讲一下 sd 是如何生成和配置的
[ honor_labels: <boolean> | default = false ]
# 是否完全使用 抓取到的指标中的时间戳
[ honor_timestamps: <boolean> | default = true ]



# 6. sd 相关的配置,由于 prometheus 是主动抓取,而抓取目标往往是快速变化的,比如一个容器,他的生命周期可能很短
# 那么就存在一个如何自动发现抓取目标,已经在抓取数据上添加各种 [元Label] 的问题
# sd 就是为这个产生,根据抓取目标、服务发现机制的不同,sd 有多种实现,下面 discovery 会我们把用的比较多的
# kubernetes_sd_configs 细讲一下,其他(azure_sd_configs/consul_sd_configs/dns_sd_configs等等)略
kubernetes_sd_configs:
  [ - <kubernetes_sd_config> ... ]
  
# 这里面的主要参数就是 api_server/role/namespaces,其他鉴权相关略
# kubernetes api server 地址,不填就是 incluster config
    [ api_server: <host> ]
    # 角色 见discover一节,支持endpoints, service, pod, node, or ingress
    role: <role>
    # 抓取对象的 namespace,不填就是所有空间
    namespaces:
      names:
        [ - <string> ]


# 7. 固定的静态配置的 label
static_configs:
  [ - <static_config> ... ]

# 8. 这部分和 如何 抓取/保存 metrics 数据有关系,也比较令人困惑,下面的 scrape 一节会细讲
# 注意这里 relabel_configs,metric_relabel_configs 都是用的 relabel_config,不同的是 relabel_configs
# 还会影响如何抓取(在 scrape 之前)的动作,而 metric_relabel_configs 只会影响 抓取之后的 存储
relabel_configs:
  [ - <relabel_config> ... ]
    # 静态配置的地址
    targets:
      [ - '<host>' ]
    # Labels assigned to all metrics scraped from the targets.
    labels:
      [ <labelname>: <labelvalue> ... ]

# List of metric relabel configurations.
metric_relabel_configs:
  [ - <relabel_config> ... ]

# Per-scrape limit on number of scraped samples that will be accepted.
# If more than this number of samples are present after metric relabelling
# the entire scrape will be treated as failed. 0 means no limit.
[ sample_limit: <int> | default = 0 ]

Discovery

一个sd config对应一个 provider, 下面等代码以 kubernetes sd 为例. 从 discover.Manager 里面启动,读取 sd config的部分

// Run implements the discoverer interface.
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) 

discover 更新流程

  • 根据 Role 的不同,启动不同的封装, 比如 Role endpoint会启动 endpointsInformer; serviceInformer; podInformer; 也就是说对于这个 namespace的 endpoint; service; pod 都会 watch
  • 从下面的代码可以看出,对于每个 provider 都要run 一次,也就是要 watch k8s api一次,而NewSharedInformer 其实什么也没 share,会不会造成对 k8s api的压力,和重复对缓存 ?答案是不会,因为如果 provider的配置相同在上一步就被过滤了。
// prometheus/prometheus/discovery/targetgroup/targetgroup.go
type Group struct {
	// Targets is a list of targets identified by a label set. Each target is
	// uniquely identifiable in the group by its address label.
	Targets []model.LabelSet
	// Labels is a set of labels that is common across all targets in the group.
	Labels model.LabelSet

	// Source is an identifier that describes a group of targets.
	Source string
}


// prometheus/prometheus/discovery/kubernetes/kubernetes.go
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
	switch d.role {
	case RoleEndpoint:
		for _, namespace := range namespaces {
		    // ....
			eps := NewEndpoints(
				log.With(d.logger, "role", "endpoint"),
				cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
				cache.NewSharedInformer(elw, &apiv1.Endpoints{}, resyncPeriod),
				cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
			)
			d.discoverers = append(d.discoverers, eps)
			go eps.endpointsInf.Run(ctx.Done())
			go eps.serviceInf.Run(ctx.Done())
			go eps.podInf.Run(ctx.Done())
			
  • watch 之后的变化根据 role 类型会做转化,但是最终转化为的东西叫 Group, Group 的定义非常简单,就是一组 label; 那么怎么体现出 发现 这个过程呢。核心就在一些特殊的 label 里面。

下面以 service 为例子

Label

含义

__address__

service 的地址, 比如 aa.default.svc:80; 这个 label 更为特殊,决定了具体 scrape 的目标地址

__meta_kubernetes_service_port_name

service 端口名

__meta_kubernetes_service_port_protocol

service 协议

__meta_kubernetes_service_name

serivce 名字

__meta_kubernetes_service_cluster_ip

serivce cluster ip

__meta_kubernetes_service_external_name

serivce external name

__meta_kubernetes_namespace

namespace 名字

__meta_kubernetes_service_label_xxx

其他来自 service 的 labels 的 label

__meta_kubernetes_service_labelpresent_xxx

true

__meta_kubernetes_service_annotation_xxx

其他来自 service 的 annotation 的 label

__meta_kubernetes_service_annotationpresent_xxx

true

  • 除了 __address__ 其实还有 __scheme____metrics_path__ 会影响抓取目标,但是因为 relabel 的灵活性,可以在上面的 任何一个 label 里面(可能是 pod 的label)得到 相关的信息,在relabel的过程中写入到 __scheme____metrics_path__就可以了
  • 对于 role 为endpoint,__address__ 是 endpoint 地址,同时还会把 关联的 pod 和 service label都加上去。
  • role 为 node 的 __address__ 是各个node对应的 kubelet 地址
  • role 为 pod 的 __address__ 是 podip+container port,同时会被其 contoller的 name/id 也加上去
  • 所以可以看出 role 的不同,影响是抓取对象地址 以及对应数据上的 meta_label; 区别并不大,所以在很多配置上 可以看到 抓取的 role 全都是 endpoint, 因为这种 role 已经能把大部分目标都囊括了 (endpoint 接近 pod,同时 可以体现 service 和 node, 比如用 kubelet 的 endpoint 就抓取了 node 目标,只是有部分 label的差异,比如 pod 有 pod ready的相关label)
  • 官方介绍

Scrape

Scrape 的核心是 scrapePool

  • scrapePools => scrapePool, 这部分由 discovery 发现的 targetSets map[string][]*targetgroup.Group 触发生成并 定期 reload, 每一个 scrape_configs 对应了一个 scrapePool, key 都是 job_name
  • 生成 target 的过程会 populateLabels(/scrape/target.go),这个过程 就会做 relabel 操作, 同时一些特殊的配置也变成label了 方便后面统一处理
  • scrapePool.sync 中 对 targets中的每一个 target 都生成了一个 loop
  • scrapeloop 中由几个缓存,一种 pool(buffer) 是缓存各种大小的 []byte, 避免频繁内存申请;另一种是 scrapeCache
  • scrapeCache 保存 metric string和 label sets的关系,同时跟踪 scrape 之间的 staleness of series

relabel

  • label 在 prometheus 被广泛的使用,在prometheus甚至任何数据都看看成是一组 label.
  • relabel 的动作不仅会影响数据的抓取 (target address / filtering targets)也会影响数据的保存 (filtering data / label-modification)
// prometheus/scrape/scrape.go
// 抓取数据
func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) 

=> 

// 通过 append 保存
func (sl *scrapeLoop) append(b []byte, contentType string, ts time.Time) (total, added, seriesAdded int, err error) 

=>

// 修改 label
// 1. lset是数据中的 label 
// 2. target 里面有 配置的, discover出来的 label,经过了 relabel_configs 的修改
// 3. rc 是 metric_relabel_configs,这里再次 relabel process
// 这里有个潜规则,label 修改为空了,那么表示 metrics 都不要了,整体丢弃
func mutateSampleLabels(lset labels.Labels, target *Target, honor bool, rc []*relabel.Config) labels.Labels 

relabel 部分的配置格式

# 来源 labels (label key),多个用, 分隔 => 用于 replace, keep, and drop 
[ source_labels: '[' <labelname> [, ...] ']' ]
# label value 连接的分隔符
[ separator: <string> | default = ; ]

# 生成的 label 名,用于 replace 必填
[ target_label: <labelname> ]

# 用于提取 source label 内容的正则表达式,默认 (.*) 
[ regex: <regex> | default = (.*) ]

# hash 使用
[ modulus: <uint64> ]

# 选择 regex 使用,用于 replace
[ replacement: <string> | default = $1 ]

# Action,可用 replace;keep;drop;hashmod;labelmap;labeldrop;labelkeep
[ action: <relabel_action> | default = replace ]

relabel 的处理功能强大,但是代码却十分简洁

action

类别

作用

Drop

抓取动作

Regex 匹配 label value, 匹配则丢弃数据

Keep

抓取动作

Regex 匹配 label value, 不匹配则丢弃数据

Replace

修改 label: 增删改

Regex 匹配 label value,根据TargetLabel和Replacement模板生成新 label 对修改

HashMod

修改 label: 增

target label: hash(source label value)

LabelMap

修改 label: 改

匹配 label key, 修改 label key,

LabelDrop

修改 label: 删

匹配 label key 则丢弃这个 label

LabelKeep

修改 label: 删

不匹配 label key 则丢弃这个 label

// prometheus/pkg/relabel/relabel.go
func Process(labels labels.Labels, cfgs ...*Config) labels.Labels {
	for _, cfg := range cfgs {
		labels = relabel(labels, cfg)
		if labels == nil {
			return nil
		}
	}
	return labels
}

func relabel(lset labels.Labels, cfg *Config) labels.Labels {
	// 省略部分代码,val 是 label key 对应的 label value 连接
	val := strings.Join(values, cfg.Separator)
	lb := labels.NewBuilder(lset)

	switch cfg.Action {
	case Drop: if cfg.Regex.MatchString(val)  return nil
	case Keep: if !cfg.Regex.MatchString(val) return nil
	case Replace: lb[TargetLabel(val)] = Replacement(val)
	case HashMod: lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus))
	case LabelMap: for l in lset { if cfg.Regex.MatchString(l.Name) { lb.Set(cfg.Regex.ReplaceAllString(l.Name, cfg.Replacement), l.Value) } }
	case LabelDrop: for l in lset { if cfg.Regex.MatchString(l.Name) { lb.Del(l.Name) } }
	case LabelKeep: for l in lset { if !cfg.Regex.MatchString(l.Name) { lb.Del(l.Name) } }
	}

	return lb.Labels()
}
  • 举个使用得比较多的 keep 和 replace 的例子, 下面这个例子表示: 满足条件 endpoint.service.labels["k8s_app"] == "kube-state-metrics" && endpoint.port_name == "http-main" 的 target 才会被抓取, 并且会设置 label namespace=endpoint.namespace
  relabel_configs:
  - source_labels: [__meta_kubernetes_service_label_k8s_app]
    separator: ;
    regex: kube-state-metrics
    replacement: $1
    action: keep
  - source_labels: [__meta_kubernetes_endpoint_port_name]
    separator: ;
    regex: http-main
    replacement: $1
    action: keep
  - source_labels: [__meta_kubernetes_namespace]
    separator: ;
    regex: (.*)
    target_label: namespace
    replacement: $1
    action: replace

Storage

参考

  • fanout storage 通过一个 Appendable 的interface 暴露给 scrape, Appendable 返回 Appender, fanout storage 实现 fanoutAppender,底层委托给 local storageremote storage, 分别为 primary, 和 secondaries (secondary可以有多个)
  • fanoutAppender 的逻辑很简单,就是依次对 primary, secondaries storage 执行对应 interface的操作
  • 查询的时候 也是有个 mergeQuerierprimaryQuerier 和 其他 queriers进行包装
type Appender interface {
	Add(l labels.Labels, t int64, v float64) (uint64, error)

	AddFast(l labels.Labels, ref uint64, t int64, v float64) error

	// Commit submits the collected samples and purges the batch.
	Commit() error

	Rollback() error
}

// Querier provides reading access to time series data.
type Querier interface {
	// Select returns a set of series that matches the given label matchers.
	Select(*SelectParams, ...*labels.Matcher) (SeriesSet, Warnings, error)

	// LabelValues returns all potential values for a label name.
	LabelValues(name string) ([]string, Warnings, error)

	// LabelNames returns all the unique label names present in the block in sorted order.
	LabelNames() ([]string, Warnings, error)

	// Close releases the resources of the Querier.
	Close() error
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 架构
  • 配置
    • 来自命令行的配置参数
      • 来自 prometheus.yml 的配置参数
      • Discovery
      • Scrape
        • relabel
        • Storage
        相关产品与服务
        前端性能监控
        前端性能监控(Real User Monitoring,RUM)是一站式前端监控解决方案,专注于 Web、小程序等场景监控。前端性能监控聚焦用户页面性能(页面测速,接口测速,CDN 测速等)和质量(JS 错误,Ajax 错误等),并且联动腾讯云应用性能监控实现前后端一体化监控。用户只需要安装 SDK 到自己的项目中,通过简单配置化,即可实现对用户页面质量的全方位守护,真正做到低成本使用和无侵入监控。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档