Prometheus介绍

首先看到Prometheus官方架构图:

prometheus监控面试题_kubernetes

exporter和pushgatway

数据通过exporterpushgatway收集存储起来,再由Prometheus服务进行拉取操作。这样的设计把数据压力分摊到了各个收集节点,不会导致收集节点过多而Prometheus承受不住数据冲击而宕机。

对于收集的方式通过exporterpushgatway暴露http/https端口地址进行采集,也就是除了官方提供的exporter之外,我们可以很好的进行定制exporter,只需满足Prometheus的数据格式,在暴露到地址即可。具体细节可以查看 开发自己的exporter。pushgatway 用于不方便去提供一个采集接口时,Prometheus提供的官方接口,可以直接把数据发到pushgatway中,这样也可以收集到发送到数据。但是pushgatway不保证数据的生命周期在你的管控之中,因为pushgatway的控制权在Prometheus,如果pushgatway宕机了,你发的数据就会丢失。所以官方推荐短期任务可以借助pushgatway,而不自己开发exporter

Prometheus Server

在使用Prometheus监控时,需要有一个念头:Prometheus监控指标,而不能详细到具体数据。而指标不是百分百精确,它有大概的趋势,来帮助我们发现和预判问题。如果需要详细的问题调查体系,则需要日志收集框架和链路追踪框架支撑。所以查看问题的顺序是这样子的:指标监控->日志查询->链路追踪查询,获得的信息逐渐增多,但是所要做的事也增多了。

了解了上面的信息,还需要注意的是:Prometheus认为只有最近的监控数据才有查询的需要,所有Prometheus本地存储的设计初衷只是保存短期(如一个月)的数据,不会针对大量的历史数据进行存储。也就是说,数据保留可能不会太久(长久的数据价值可能远超保留它的代价)。如果需要存储,则需要其他的扩展方案。在图中可以看到,对于存储Prometheus使用自带的TSDB进行存储,对于采集节点服务发现和Kubernetes集成,这也是很关键的一点。

数据展示、报警

Prometheus收集数据存储到本地,这些数据可以配置成rule、alert两种聚合数据,rule会被Grafana收集进行展示、alert会被Alertmanager进行收集报警。这两个组件的灵活度都特别的高,但是不是本节重点,继续往后看。

使用

在之前开发流程是这样的,下载Prometheus、Exporter、Grafana、Alertmanager。之后在Prometheus中配置对应的exporter信息,rule、alert。当然这些都可以通过Docker或者Kubernetes的配置文件一件部署,在kubernetes下则可能是这样子的:

prometheus监控面试题_prometheus_02

可以看到Prometheus连接着其他的组件。当然,连接会通过Service组件连接,防止exporter或组件宕机、升级在集群中重新调度时的ip丢失。但是如果需要添加组件,如新的采集节点,则需要手动修改配置文件。这样维护起来在大集群里可能成本比较高。于是之后就出现了Prometheus-Operator。

Prometheus-Operator是在Kubernetes中的一组CRD,所以它所有的操作都是依赖于Kubernetes的。通过Kubernetes平台优化了上面的问题和添加功能,先看看架构图:

prometheus监控面试题_jsonnet_03

可以看到在Prometheus和组件的连接中又抽象了一层ServiceMonitor,ServiceMonitor通过label进行选择组件的连接,和Kubernetes中的一样,我们通过操作类似label选择器的机制去管理组件。除了和Prometheus的连接管理之外,Operator还管理着Grafana和Alertmanager,它们各自的配置文件都可以动态进行更新,此外还抽象出了rule组件,这样rule、alert都可以进行动态更新和管理。

上面所有的管理工作都是由Operator组件完成,它通过prometheus对应reload的http接口/-/reload,如果Operator监听到了配置的变化,就对调用此接口进行配置动态更新。具体细节可能会更加复杂一点。

Kube-Prometheus介绍

有了Kube-Prometheus之后,虽然知道它扩展很方便,但是我们还是希望有一个通用的配置,之后我们在通过修改配置去定制自己的监控集群。原因是一套完整的监控架构需要写的配置文件还是挺多的。😦

于是就有了Kube-Prometheus项目,Kube-Prometheus项目可以通过一些配置快速的生成一套适应你集群的配置文件,之后你在修改生成的配置去适应你的集群,这样就节省了很多时间了。下面就介绍它是如何实现的。

  • 使用官方推荐的Kube-Prometheus进行部署,原理是使用Jasonnet工具生成配置文件。
  • 使用Helm社区维护的Chart部署

使用哪一种方法好?

  • jsonnet本质是类似模版语言工具进行生成配置文件, helm 从某种程度上来说也是做这件事的,由于 Prometheus 相关社区(Grafana、Prometheus-Operator、 Kube-prometheus) 都使用 Jsonnet 做配置管理, 应该有它的理由。
  • Helm维护的配置文件没有Jsonnet来的完备、及时。(官方亲儿子)
  • Json格式上的优势,它不需要考虑空格锁进问题。

我们这里就选择Jsonnet方式进行生成配置然后部署,在开始前先简单介绍jsonnet。

Jsonnet介绍

Jsonnet是一个模版语言工具,语法酷似Python,所以上手起来还是满快的。下面通过例子快速了解下它能做到什么,具体细节和语法推荐到官方上学习。jsonnet官网

下面你可以跟着做,然后观察它的输出。当然,安装jsonnet是必须的,如果你还要使用Kube-Prometheus的话。

最后的引用库文件代码会生成一个库文件,除此之外不会有其他输出文件。

# 安装 Jsonnet(C 实现)
brew install jsonnet

# 也可以安装 Go 实现
go get github.com/google/go-jsonnet/cmd/jsonnet

# 基本用法: 解释运行一个 jsonnet 源码文件
echo "{hello: 'world'}" > test.jsonnet
jsonnet test.jsonnet

# 对于简单的代码也可以使用 -e 直接运行
jsonnet -e '{hello: "world"}'

# 字段可以不加引号 自动格式化
jsonnet -e '{key: 1+2, hello: "world"}'

# 类比: Jsonnet 支持与主流语言类似的四则运算, 条件语句, 字符串拼接, 
# 字符串格式化, 数组拼接, 数组切片以及 python 风格的列表生成式
jsonnet - <<EOF
{
  array: [1, 2] + [3],
  math: (4 + 5) / 3 * 2,
  format: 'Hello, %s' % 'world',
  concat: 'Hello, ' + 'world',
  slice: [1,2,3,4][1:3],
  'list': [x * x for x in [1,2,3,4]],
  condition:
    if 2 > 1 then
    'true'
    else
    'false',
}
EOF

# 使用变量:
#   使用 :: 定义的字段是隐藏的(不会被输出到最后的 JSON 结果中), 
#   这些字段可以作为内部变量使用(非常常用)

#   使用 local 关键字也可以定义变量
#   JSON 的值中可以引用字段或变量, 引用方式:
#     变量名
#     self 关键字: 指向当前对象
#     $ 关键字: 指向根对象
jsonnet - <<EOF
{
  local hello = 'hello',
  world:: 'world',
  message: {
    hello: hello,
    world: $.world,
    _hello: self.world,
  }
}
EOF

# 使用函数:
# 定义与引用方式与变量相同, 函数语法类似 python
jsonnet - <<EOF
{
  local hello(name) = 'hello %s' % name,
  sum(x, y):: x + y,
  newObj(name='hello', age=23):: {
    name: name,
    age: age
  },
  call_sum: $.sum(1, 2),
  call_hello: hello('world'),
  me: $.newObj(age=24),
}
EOF

# Jsonnet 使用组合来实现面向对象的特性(类似 Go)
#   Json Object 就是 Jsonnet 中的对象
#   使用 + 运算符来组合两个对象, 假如有字段冲突, 
#   使用右侧对象(子对象)中的字段

#   子对象中使用 super 关键字可以引用父对象, 
#   用这个办法可以访问父对象中被覆盖掉的字段
jsonnet - <<EOF
local base = {
  o: 'hello world',
  no: {
      hello: 'world'
  }
};
base + {
  o: 'hello zhangsan',
  # +: 组合而非覆盖
  no+: {
      nihao: 'zhangsan'
  },
  super_o: super.o
}
EOF

# 库与 import:
#  jsonnet 共享库复用方式其实就是将库里的代码整合到当前文件中来,
#  引用方式也很暴力, 使用 -J 参数指定 lib 文件夹, 再在代码里 import 即可
#  jsonnet 约定库文件的后缀名为 .libsonnet
cat > hello.libsonnet <<EOF
{
  helloObj(hello='world', name):: {
    hello: hello,
    names: [], #声明数组
    sayhello(name):: self + {
      names +: [name],
    },
  }
}
EOF

#import调用
jsonnet -J . - <<EOF
local helloPackage = import './hello.libsonnet';
helloPackage.helloObj(name='world')
  .sayhello('zhangsan')
  .sayhello('lisi')
EOF

Kube-Prometheus安装、配置

有了上面的知识之后,就可以着手定制自己的配置文件了。我会逐一介绍,按照官方的配置的定制流程。

首先看生成脚本

#!/usr/bin/env bash

# This script uses arg $1 (name of *.jsonnet file to use) to generate the manifests/*.yaml files.

set -e
set -x
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail

# Make sure to use project tooling
#PATH="$(pwd)/tmp/bin:${PATH}"

# Make sure to start with a clean 'manifests' dir
rm -rf manifests
mkdir -p manifests/setup

# 执行jsonnet生成定制的文件,引入的包为vendor,配置文件为脚本传入(默认为example.jsonnet)之后gojsontoyaml将json转化为yaml格式,最后输出到文件里
jsonnet -J vendor -m manifests "${1-example.jsonnet}" | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml' -- {}

# Make sure to remove json files
find manifests -type f ! -name '*.yaml' -delete
rm -f kustomization

了解到安装方式之后,只要下载项目中的 vendor 包就可以了。之后创建一个定制的配置文件。安装完成之后的目录是这样的:

.
├── build.sh #生成配置文件的脚本
├── jsonnetfile.json #版本信息
├── jsonnetfile.lock.json
├── monitor.jsonnet # 手动创建脚本的配置信息
└── vendor
    ├──...jsonnet依赖的包

之后编写定制文件:

local kp =
  (import 'kube-prometheus/kube-prometheus.libsonnet') + //基本配置
  // (import 'kube-prometheus/kube-prometheus-kube-aws.libsonnet') + //Aws集群特殊配置
  // (import 'kube-prometheus/kube-prometheus-strip-limits.libsonnet') +
  // Uncomment the following imports to enable its patches
  // (import 'kube-prometheus/kube-prometheus-anti-affinity.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-managed-cluster.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-node-ports.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-static-etcd.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-thanos-sidecar.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-custom-metrics.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-external-metrics.libsonnet') //+
  // 配置全局或提供的动态配置,这里只设置了全局的命名空间  
  {
    _config+:: {
      namespace: 'monitoring'
    }
  };

{ ['setup/0namespace-' + name]: kp.kubePrometheus[name] for name in std.objectFields(kp.kubePrometheus) } +
{
  ['setup/prometheus-operator-' + name]: kp.prometheusOperator[name]
  for name in std.filter((function(name) name != 'serviceMonitor'), std.objectFields(kp.prometheusOperator))
} +
// 如果要添加更多的export 在这里添加
// serviceMonitor is separated so that it can be created after the CRDs are ready
{ 'prometheus-operator-serviceMonitor': kp.prometheusOperator.serviceMonitor } +
{ ['node-exporter-' + name]: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } +
//{ ['blackbox-exporter-' + name]: kp.blackboxExporter[name] for name in std.objectFields(kp.blackboxExporter) } + #如果需要黑盒则放开
{ ['kube-state-metrics-' + name]: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } +
{ ['alertmanager-' + name]: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } +
{ ['prometheus-' + name]: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } +
{ ['prometheus-adapter-' + name]: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) } +
{ ['grafana-' + name]: kp.grafana[name] for name in std.objectFields(kp.grafana) }

AWS集群由于kube-system中组件的label可能丢失,导致监控丢失。所以会帮我们自动生成service文件。这里只要记住如果是aws管理的集群添加这个是保险的。

运行脚本

./build.sh monitor.jsonnet

脚本会生成监控相关的所有配置文件,CRD文件存放在./manifests/setup中,资源文件存放在./manifests中。查看manifests的文件如下。现在就可以开始修改一些配置信息,来满足你的需要了。接下来挑几个重要的文件,并添加常用的一些配置提供参考。

manifests
# alertmanager相关配置
├── alertmanager-alertmanager.yaml
├── alertmanager-secret.yaml # 告警配置
├── alertmanager-service.yaml
├── alertmanager-serviceAccount.yaml
├── alertmanager-serviceMonitor.yaml
# grafana相关配置
├── grafana-dashboardDatasources.yaml
├── grafana-dashboardDefinitions.yaml
├── grafana-dashboardSources.yaml
├── grafana-deployment.yaml
├── grafana-service.yaml
├── grafana-serviceAccount.yaml
├── grafana-serviceMonitor.yaml
# kubernetes集群指标监控
├── kube-state-metrics-clusterRole.yaml
├── kube-state-metrics-clusterRoleBinding.yaml
├── kube-state-metrics-deployment.yaml
├── kube-state-metrics-service.yaml
├── kube-state-metrics-serviceAccount.yaml
├── kube-state-metrics-serviceMonitor.yaml
# 各个几点指标监控
├── node-exporter-clusterRole.yaml
├── node-exporter-clusterRoleBinding.yaml
├── node-exporter-daemonset.yaml
├── node-exporter-service.yaml
├── node-exporter-serviceAccount.yaml
├── node-exporter-serviceMonitor.yaml
# kubernetes hpa相关适配器
├── prometheus-adapter-apiService.yaml
├── prometheus-adapter-clusterRole.yaml
├── prometheus-adapter-clusterRoleAggregatedMetricsReader.yaml
├── prometheus-adapter-clusterRoleBinding.yaml
├── prometheus-adapter-clusterRoleBindingDelegator.yaml
├── prometheus-adapter-clusterRoleServerResources.yaml
├── prometheus-adapter-configMap.yaml
├── prometheus-adapter-deployment.yaml
├── prometheus-adapter-roleBindingAuthReader.yaml
├── prometheus-adapter-service.yaml
├── prometheus-adapter-serviceAccount.yaml
├── prometheus-adapter-serviceMonitor.yaml
# prometheus相关配置
├── prometheus-clusterRole.yaml
├── prometheus-clusterRoleBinding.yaml
├── prometheus-kubeControllerManagerPrometheusDiscoveryService.yaml
├── prometheus-kubeSchedulerPrometheusDiscoveryService.yaml
├── prometheus-operator-serviceMonitor.yaml
├── prometheus-prometheus.yaml # 监控配置
├── prometheus-roleBindingConfig.yaml
├── prometheus-roleBindingSpecificNamespaces.yaml
├── prometheus-roleConfig.yaml
├── prometheus-roleSpecificNamespaces.yaml
├── prometheus-rules.yaml # 默认监控项
├── prometheus-service.yaml
├── prometheus-serviceAccount.yaml
# serviceMonitor相关配置
├── prometheus-serviceMonitor.yaml
├── prometheus-serviceMonitorApiserver.yaml
├── prometheus-serviceMonitorCoreDNS.yaml
├── prometheus-serviceMonitorKubeControllerManager.yaml
├── prometheus-serviceMonitorKubeScheduler.yaml
├── prometheus-serviceMonitorKubelet.yaml
└── setup # CRD文件
    ├── 0namespace-namespace.yaml
    ├── prometheus-operator-0alertmanagerConfigCustomResourceDefinition.yaml
    ├── prometheus-operator-0alertmanagerCustomResourceDefinition.yaml
    ├── prometheus-operator-0podmonitorCustomResourceDefinition.yaml
    ├── prometheus-operator-0probeCustomResourceDefinition.yaml
    ├── prometheus-operator-0prometheusCustomResourceDefinition.yaml
    ├── prometheus-operator-0prometheusruleCustomResourceDefinition.yaml
    ├── prometheus-operator-0servicemonitorCustomResourceDefinition.yaml
    ├── prometheus-operator-0thanosrulerCustomResourceDefinition.yaml
    ├── prometheus-operator-clusterRole.yaml
    ├── prometheus-operator-clusterRoleBinding.yaml
    ├── prometheus-operator-deployment.yaml
    ├── prometheus-operator-service.yaml
    └── prometheus-operator-serviceAccount.yaml

prometheus配置

查看 prometheus-prometheus.yaml 文件

apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
  labels:
    prometheus: k8s
  name: k8s
  namespace: monitoring
spec:
  alerting:
    alertmanagers:
    - name: alertmanager-main
      namespace: monitoring
      port: web
  image: quay.io/prometheus/prometheus:v2.22.1
  nodeSelector:
    kubernetes.io/os: linux
  podMonitorNamespaceSelector: {}
  podMonitorSelector: {}
  probeNamespaceSelector: {}
  probeSelector: {}
  replicas: 2 # 设置副本数
  retention: 15d # 添加数据保留期限
  # 如果需要添加集群外部数据采集, 在这里添加配置文件
  #additionalScrapeConfigs:
    #name: additional-scrape-configs
    #key: prometheus-additional.yaml
  #添加sc
  storage:
    volumeClaimTemplate:
      spec:
        storageClassName: xxx
        resources:
          requests:
            storage: 50Gi
  resources:
    requests:
      memory: 400Mi
  ruleSelector:
    matchLabels:
      prometheus: k8s
      role: alert-rules
  securityContext:
    fsGroup: 2000
    runAsNonRoot: true
    runAsUser: 1000
  serviceAccountName: prometheus-k8s
  serviceMonitorNamespaceSelector: {}
  serviceMonitorSelector: {}
  version: v2.22.1

如果配置了外部采集节点,创建 prometheus-additional-secret.yaml

# prometheus-additional-secret.yaml
apiVersion: v1
data: {}
kind: Secret
metadata:
  name: additional-scrape-configs
  namespace: monitoring
stringData:
  prometheus-additional.yaml: |-
    - job_name: "prometheus-demo"
      metrics_path: "metrics"
      static_configs:
      - targets: ["xxx:8080"]
type: Opaque

prometheus-rule配置

查看 prometheus-rules.yaml 文件

prometheus监控面试题_kube-prometheus_04

可以看到该项目内置了很多告警项,可以根据自己需要删减。过多的rule可能会影响性能和消耗更多的内存,所以还是根据自己的需要来配置。当然这些rule对于后面Grafana展示都是有用到的。Grafana中的图表也是根据这些规则配置好了的,两者是相关联的。

alertmanager-secret.yaml配置

创建 alertmanager-secret.yaml文件。具体需要的配置看 alertmanager config

apiVersion: v1
data: {}
kind: Secret
metadata:
  name: alertmanager-main
  namespace: monitoring
stringData:
  alertmanager.yaml: |-
    global:
      resolve_timeout: 2h

      smtp_smarthost: 'smtp.qq.com:465'
      smtp_from: 'xxx@xx.com'
      smtp_auth_username: 'xxx@xx.com'
      smtp_auth_password: 'xxx'
      smtp_require_tls: false
    route:
      group_by: ['alertname']
      group_wait: 30s
      group_interval: 5m
      repeat_interval: 4h
      receiver: default
      routes:
      - receiver: 'email-receiver'
        group_wait: 30s
        match:
          notify: notify
    receivers:
      - name: email-receiver
        email_configs:
        - to: 'xxx@xx.com'
          send_resolved: true
        - to: 'xxx@xx.com'
          send_resolved: true
      - name: 'default'
        webhook_configs:
        - url: 'http://xxx'
          send_resolved: true
type: Opaque

查看监控信息

完成配置定制之后运行

kubectl apply -f manifests/setup
kubectl apply -f manifests/

以下内容为将端口映射到本地主机端口

kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090

访问地址 http://localhost:9090

kubectl --namespace monitoring port-forward svc/grafana 3000

访问地址 http://localhost:3000 用户名和密码为 admin:admin

kubectl --namespace monitoring port-forward svc/alertmanager-main 9093

访问地址 http://localhost:9093

扩展

Grafana仪表盘图

参考 https://grafana.com/grafana/dashboards

更多监控报警规则参考 https://awesome-prometheus-alerts.grep.to/rules

遇到的问题

manifests/prometheus-rules.yaml 部署失败

Error from server (InternalError): error when creating “manifests/prometheus-rules.yaml”: Internal error occurred: failed calling webhook “prometheusrulemutate.monitoring.coreos.com”: Post https://kube-prometheus-stack-1607-operator.default.svc:443/admission-prometheusrules/mutate?timeout=10s: service “kube-prometheus-stack-1607-operator” not found

具体问题查看 https://github.com/helm/charts/issues/21080

解决:

kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io
#do 删除 kube-prometheus..资源

kubectl get MutatingWebhookConfiguration
#do 删除 kube-prometheus..资源

#重新载入
kubectl apply -f manifests/prometheus-rules.yaml
两个监控任务没有对应的目标

访问 Prometheus

prometheus监控面试题_kube-prometheus_05

遇到这种情况一般是你下载的版本和你的Kubernetes集群版本不匹配,导致label丢失,所以ServiceMonitor找不到对应的Service

解决思路

  • 查看 ServiceMonitor 对应的服务端口是否存在
  • 服务端口是否匹配到正确到po(查看po是否含有匹配标签)
  • 通信协议是否正确(http/https