边侧监控选型

2023-07-26
20分钟阅读时长

边侧监控方案

问题

边缘计算v2适配了目前所有流行的应用部署方式:

  1. 裸系统(物理机)部署;
  2. docker部署;
  3. k8s部署;

因此对应的监控系统也要考虑兼顾各种部署方式。并且,随着时间的发展,平台会接入越来越多的边侧集群或者设备,因此要考虑负载问题。

目前阶段主要需要的是基本的异常检测(应用、机器、网络等),和即时告警通知。由于边侧服务一般流量比较稳定,因此对于应用性能分析和统计数据等需求没有那么显著,而且这些都可以退化到基于日志来曲线解决问题。

除了监控自己平台的服务,还要考虑第三方服务接入的问题,因此在数据权限这块需要相对灵活。

选型

目前我们的云端服务也同时有这三种部署模式,使用的解决方案是基于ELK的技术栈。该方案能力强大,对Metric/Trace/Log都有完善的收集方案,缺点就是资源消耗很大。

考虑到上面分析的监控需求,以及云端资源的短缺问题,这里合理的方案是:

  1. 将监控服务配置在边缘侧而不是统一发送到云端;
  2. 尽量使用轻量级监控方案以减少资源占用;
  3. 需要支持通过命令行配置监控。因为目前边缘计算v2没做端口转发服务,依赖图形界面进行配置的,如zabbix,如果现场没有windows机器就很难配置了;
  4. 需要支持windows采集,在特殊情况下,还是会使用windows host的。这就排除了ilogtail等只支持linux的方案;

经过调研,这里还是使用基于prometheus的方案,prometheus本身就是基于配置文件进行配置的,查询可以使用命令行工具。主要依赖的东西:

  1. prometheus server. 部署在k8s内部或者裸机中。如果是混合部署环境(既需要k8s也需要裸机部署),应当部署在k8s里。这是因为prometheus是pull模型,可以访问k8s外面的服务;
  2. alert-manager. 配置告警,目前主要考虑钉钉告警,后续根据第三方需求可以增加短信告警等,反正都是调用API;
  3. mtail等轻量级日志收集工具,配合alert-manager完成错误日志告警;
  4. node-exporter等各种exporter,这个需要用户自行安装。当然我们可以在边缘计算脚本/应用模板里面一些常用的;

1~3都是使用golang编写,支持编译成各平台原生的二进制,能满足跨平台的需求。Trace相关的(OpenTelemetry)收集一般使用Jaeger等工具,边侧使用到的可能性较小。

如果Log/Metric/Trace都需要收集,那还是直接用EFK这种大型系统吧,零零碎碎的部署更麻烦。

架构

这套架构是很成熟的方案,资料较多。核心思想就是prometheus定时拉取各种exporter暴露出来的metrics端口,将数据存入时序数据库。AlertManager等工具通过PromQL查询语言与时序数据库交互,从而查询想要的数据:

img

在我们这里,主要使用prometheusAlertManager,图形化的grafana不是这个阶段的重点。这主要是因为prometheus是部署在边缘侧的,图形化工具在云端无法正常浏览,除非做端口转发。

端口转发需要引入其他agent(如rport或者frp),云端也需要打开端口段或者引入三级域名才能正常工作,运维上比较麻烦,现在暂时不考虑。

Metric & Alarm

即prometheus和alertManager部分。

一般安装

prometheus

如果在k8s外使用prometheus,就是一般的二进制安装。包括docker安装或者windows安装,原理都差不多。

prometheus-server本身就是一个二进制文件,如果是裸机安装,只要从github上下载最新版,然后通过

mv prometheus-*.tar.gz prometheus.tar.gz
tar xvfz prometheus.tar.gz
cd prometheus
mkdir -p data
./prometheus

启动服务就行,默认时序数据库存在当前目录下的data中,配置文件默认就是当前路径下的prometheus.yml文件。

如果是docker,建议将配置文件挂载到外面,方便修改:

mkdir -p /etc/prometheus
docker run -p 9090:9090 -v /etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

配置文件的格式:

# 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:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_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: "prometheus"

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

    static_configs:
      - targets: ["localhost:9090"]

里面是静态的抓取和告警配置。

默认抓取localhost:9090/metrics,即prometheus自身的metric.

prometheus自带了告警功能,但是不含通知功能,需要配合alertmanager来完成通知。

修改上面的rule_files添加告警规则文件。在后者里面填入告警规则,例如:

groups:
- name: example
  rules:
  - alert: HighErrorRate
    expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
    for: 10m
    labels:
      severity: page
    annotations:
      summary: High request latency
      description: description info

这个结构其实很像是k8s api object, 核心字段主要就是.groups[].rules[].expr的PromQL表达式,以及for的持续时间。labelsannotations是为告警添加的kv.

注意annotations中的value支持模板化语法,方便填充实际值到告警信息,如:

{{$labels.<labelname>}}
{{$value}}

alerting配置下面就是alertmanager的静态配置,通过这里关联下面的alertmanager

除了告警之外,rule_files还支持类似物化视图的异步计算时间序列,语法类似:

groups:
  - name: example
    rules:
    - record: job:http_inprogress_requests:sum
      expr: sum(http_inprogress_requests) by (job)

其中record就是新产生的metric名称,expr是PromQL表达式。

alertmanager

如果是docker安装,可以使用docker-compose将二者一起安装。如果是二进制安装,方法类似prometheus,直接下载解压,创建data文件夹之后启动即可。配置文件格式如下:

global:
  resolve_timeout: 5m

route:
  group_by: ['alertname']
  group_wait: 5s
  group_interval: 60s
  repeat_interval: 1h
  receiver: 'web.hook'
receivers:
- name: 'web.hook'
  webhook_configs:
  - url: 'http://127.0.0.1:5001/'
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'dev', 'instance']

alertmanager的原理有点类似api网关,通过路由进行匹配,然后分发到不同的receiver中。

route的配置例子:

route:
  receiver: 'default-receiver'
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  group_by: [cluster, alertname]
  routes:
  - receiver: 'database-pager'
    group_wait: 10s
    match_re:
      service: mysql|cassandra
  - receiver: 'frontend-pager'
    group_by: [product, environment]
    match:
      team: frontend

首先是全局配置,默认接收器是default-receiver,分组的依据是集群和告警名称。

group_wait是告警触发之后等待合并同组告警的时间阈值;group_interval是相同group发送通知的冷却时间;repeat_interval是同一个告警的冷却时间。

routes下面定义具体的子路由,通过match或者match_re来匹配Prometheus发送过来数据的label.

可以添加continue字段配置子路由匹配之后是否继续向下匹配,默认是false.

receiver的格式取决于其类型,alertmanager内置了很多告警媒介,如邮件、telegram甚至微信。如钉钉这种虽然没有内置进去,但是可以通过webhook扩展,参考这里

告警信息同样可以使用消息模板,如果消息比较大也可以写成单独的模板文件。模板的语法和prometheus一样,同样遵从go template语法。

inhibit_rules用来抑制告警,语法如下:

- source_match:
    alertname: NodeDown
    severity: critical
  target_match:
    severity: critical
  equal:
    - node

当已经发送的告警匹配到target_matchtarget_match_re规则,此时新的告警满足source_match或者source_match_re,则新的告警不会发送。

此外,管理员还可以通过alertmanager的界面配置临时静默。

高可用

对于边缘侧而言,prometheus一般部署一个就够了,因为边侧节点和服务的数量不会特别多。不过如果需要的话,也可以利用prometheus的联邦集群特性搭建集群。这个功能的核心就是/federate接口可以直接将prometheus的监控数据暴露出去。例如配置prometheus如下:

scrape_configs:
  - job_name: 'federate'
    scrape_interval: 15s
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="prometheus"}'
        - '{__name__=~"job:.*"}'
        - '{__name__=~"node.*"}'
    static_configs:
      - targets:
        - '192.168.77.11:9090'
        - '192.168.77.12:9090'

则当前节点会自动抓取targets中其他两个prometheus中的满足match[]条件的数据。

但是集群联邦实际上解决的是高负载问题,单点的中心集群或者边侧集群仍然存在不可用风险。

由于k8s自己就是高可用的解决方案,所以k8s内的prometheus一般不需要考虑该问题。但是对于docker或者裸机部署,这个就需要思考一下。

官方给出的方案其实比较简单粗暴:部署多套一模一样的prometheus即可,因为是pull机制,并没有冲突。

alertmanager其实类似,也可以部署多套。不同的是,alertmanager的重复通知对用户影响很大。所以alertmanager的不同实例之间需要通信以组成真正的集群。alertmanager的启动参数中可以添加:

alertmanager  --web.listen-address=":9093" --cluster.listen-address="127.0.0.1:8001" --config.file=/etc/prometheus/alertmanager.yml  --storage.path=/data/alertmanager/ 

之后新增的alertmanager的参数中,通过cluster.peer指定之前启动的实例:

alertmanager  --web.listen-address=":9094" --cluster.listen-address="127.0.0.1:8002" --cluster.peer=127.0.0.1:8001 --config.file=/etc/prometheus/alertmanager.yml  --storage.path=/data/alertmanager2/

两者的配置文件一模一样,但是数据存储的位置不同。这些集群实例之间通过gossip协议进行通信。

服务发现

prometheus抓取除了支持static_configs之外,还可以集成服务发现动态添加目标服务,不过很遗憾不支持nacos.

变通的方法是写一个nacos的客户端订阅nacos中的服务列表,然后变化时将数据写入文件,使用基于文件的服务发现。

不过在边侧,建议还是用静态配置吧,因为节点不会很多。

自定义标签

除了exporter自己暴露的标签外,在scrape_configs下面可以增加labels或者relabel_config,用来添加自定义标签,后者一般配合服务发现使用。

k8s内安装

安装

在k8s里面安装,不要直接安装prometheus,而是选择第三方封装的prometheus-operator.

从上面可以看到,prometheus只有一个单纯的二进制文件,依赖配置文件进行配置,所以每次修改配置都要重启服务,这显然不符合k8s的设计思路。将服务封装成operator,然后通过修改yaml来进行配置才是更合适的方案。

CoreOS引入了Operator的概念,这里我们使用helm来安装,用到的包是kube-prometheusbitnami中就有这个包:

helm install kube-prometheus bitnami/kube-prometheus --namespace monitor --create-namespace --version 8.15.3

好消息是这个chart里面的镜像都是docker.io上面的,所以docker正常配置代理就可以拉下来了,无须修改chart.

装好之后在monitor下就可以看到对应的服务了:

NAME                                                     READY   STATUS    RESTARTS   AGE
pod/kube-prometheus-node-exporter-k5gd6                  1/1     Running   0          43m
pod/kube-prometheus-blackbox-exporter-8567bbfd99-7k8ms   1/1     Running   0          43m
pod/kube-prometheus-kube-state-metrics-5c9f68d87-5vdn6   1/1     Running   0          43m
pod/kube-prometheus-operator-c9c58b595-mv6kk             1/1     Running   0          43m
pod/alertmanager-kube-prometheus-alertmanager-0          2/2     Running   0          40m
pod/prometheus-kube-prometheus-prometheus-0              2/2     Running   0          40m

NAME                                         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
service/kube-prometheus-alertmanager         ClusterIP   10.43.34.93    <none>        9093/TCP                     43m
service/kube-prometheus-prometheus           ClusterIP   10.43.96.98    <none>        9090/TCP                     43m
service/kube-prometheus-kube-state-metrics   ClusterIP   10.43.31.57    <none>        8080/TCP                     43m
service/kube-prometheus-operator             ClusterIP   10.43.33.45    <none>        8080/TCP                     43m
service/kube-prometheus-blackbox-exporter    ClusterIP   10.43.54.206   <none>        19115/TCP                    43m
service/kube-prometheus-node-exporter        ClusterIP   10.43.135.99   <none>        9100/TCP                     43m
service/alertmanager-operated                ClusterIP   None           <none>        9093/TCP,9094/TCP,9094/UDP   40m
service/prometheus-operated                  ClusterIP   None           <none>        9090/TCP                     40m

NAME                                           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
daemonset.apps/kube-prometheus-node-exporter   1         1         1       1            1           <none>          43m

NAME                                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kube-prometheus-blackbox-exporter    1/1     1            1           43m
deployment.apps/kube-prometheus-kube-state-metrics   1/1     1            1           43m
deployment.apps/kube-prometheus-operator             1/1     1            1           43m

NAME                                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/kube-prometheus-blackbox-exporter-8567bbfd99   1         1         1       43m
replicaset.apps/kube-prometheus-kube-state-metrics-5c9f68d87   1         1         1       43m
replicaset.apps/kube-prometheus-operator-c9c58b595             1         1         1       43m

NAME                                                         READY   AGE
statefulset.apps/alertmanager-kube-prometheus-alertmanager   1/1     40m
statefulset.apps/prometheus-kube-prometheus-prometheus       1/1     40m

主要是安装了prometheus、alertmanager和node exporter,满足基本使用需求. 其中prometheus和alertmanager都是sts,存储用的是emptydir:

image-20230721094436210

这也意味着重启pod会导致时序数据库中的数据丢失,如果想要不丢失需要配置一下pvc(不过丢失一般也没啥,毕竟只是监控数据):

# ref: https://artifacthub.io/packages/helm/bitnami/kube-prometheus
kubectl create ns monitor
helm install prometheus-stack bitnami/kube-prometheus -n monitor --version 8.15.4 \
  --set global.storageClass=local-path \
  --set prometheus.persistence.enabled=true \
  --set alertmanager.persistence.enabled=true \
  --set prometheus.retention="72h" \
  --set alertmanager.retention="72h" \
  --set kubelet.serviceMonitor.resourcePath=/metrics/resource \
  --set prometheus.service.type=LoadBalancer \
  --set alertmanager.service.type=LoadBalancer \
  --set prometheus.ruleSelector.matchLabels.monitor=default \
  --set alertmanager.configSelector.matchLabels.monitor=default \
  --set alertmanager.externalConfig=true

默认会启动2个ClusterIP类型的service,对应prometheusalertmanager的server,端口分别是9090和9093,内部域名分别是prometheus-stack-prometheus.monitor.svc.cluster.localprometheus-stack-alertmanager.monitor.svc.cluster.local.

上面配置将service的type改为LoadBalancer,这样就可以通过外网访问对应的端口了。当然理论上也可以通过配置ingress来达到该目的,请参考这里.

默认情况下,kube-prometheus会安装NodeExporter,所以在安装结束之后,可以打开prometheus的web页,输入up查看一下该功能是否正常。或者简单点输入curl localhost:9090/api/v1/query?query=up看一下也行。

监控配置

Operator通过CRD对象进行配置,增加一个服务监控就是增加一个ServiceMonitor,如下:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: example-app
  namespace: monitor
  labels:
    team: frontend
spec:
  namespaceSelector:
    matchNames:
    - default
  selector:
    matchLabels:
      app: example-app
  endpoints:
    - port: 80

显然namespaceSelector定义服务所在命名空间;selector用来通过label匹配服务;endpoints里面定义服务暴露出来的端口和路径。

如果想要匹配所有命名空间,则格式为:

spec:
  namespaceSelector:
    any: true

同理,还有PodMonitor用来监控Pod.

告警配置

通过PrometheusRule来配置告警,格式如下:

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    monitor: default
  name: prometheus-example-rules
spec:
  groups:
  - name: example-rules
    rules:
    - alert: ExampleAlert
      expr: vector(1)

groups下面的语法和普通的prometheus的rules文件没啥区别。

AlertManager则通过AlertmanagerConfig来配置:

apiVersion: monitoring.coreos.com/v1alpha1
kind: AlertmanagerConfig
metadata:
  name: config-example
  labels:
    monitor: default
spec:
  route:
    groupBy: ['job']
    groupWait: 30s
    groupInterval: 5m
    repeatInterval: 12h
    receiver: 'webhook'
  receivers:
  - name: 'webhook'
    webhookConfigs:
    - url: 'http://example.com/'

.spec的内容和直接配置alertmanager的配置文件格式也是一样的。

注意:比较蛋疼的是,Alertmanager上面的配置会被强制注入一个namespace的匹配标签,所以无法在所有命名空间生效。解决办法是自己创建一个全局的配置,而不是使用默认的全局配置。

所以在使用helm安装之前,需要自己创建一个secret,如果helm应用叫prometheus-stack,那么secret应该叫alertmanager-prometheus-stack-kube-prom-alertmanager,里面有个keyalertmanager.yaml,内容是(base64):

global:
  resolve_timeout: 5m
route:
  group_by: ['job']
  group_wait: 5s
  group_interval: 1m
  repeat_interval: 15m
  receiver: 'null'
  routes:
  - matchers:
    - alertname = Watchdog
    receiver: 'null'
  - matchers:
    - severity =~ "critical|warning"
    receiver: webhook

receivers:
- name: 'null'
- name: 'webhook'
  webhook_configs:
  - url: 'http://edge-webhook:9095/api/v1/alert'
    send_resolved: true

特别需要注意的是,这个配置文件里面有个默认的WatchDog路径,这是不能删掉的,不然alertmanager就不work了。

黑盒探测

注意到这种方法只能添加k8s内的pod或者服务,如果想要从k8s外的http接口拉取数据。则需要使用Probe,配合blackbox-exporter来使用。

如果查看deployments,可以看到black-box的deploy,configmap中也有一个相关的配置。实际上black-box就是一个通过ping/tcp/udp探测的服务。使用方法就是创建Probe:

apiVersion: monitoring.coreos.com/v1
kind: Probe
metadata:
  name: domain-probe
  namespace: monitoring
  labels:
    monitor: default
spec:
  jobName: domain-probe
  prober:
    url: prometheus-stack-kube-prom-blackbox-exporter:19115
  module: http_2xx
  targets:
    # ingress:{}
    staticConfig:
      static:
        - baidu.com

module支持各种探测方法,包括:http_2xx, http_post_2xx, tcp_connect, pop3s_banner, ssh_banner, irc_banner,这些都默认配置在blackbox的configmap里了。实际上还可以增加更多,但是默认没打开,如grpc/icmp/dns这些,具体参考这里

.spec.prober.url就是blackbox服务的容器地址,如果不在同一个namespace就要使用完全限定名了,即prometheus-stack-kube-prom-blackbox-exporter.monitor.svc.cluster.local:19115.

注意目前尚不支持UDP探测。

关联配置

默认情况下,ServiceMonitor/Probe创建后会自动生效。但是PrometheusRule则不会,我在上面配置了selector中的matchLabels条件,所以需要添加.metadata.labels.monitor=default来匹配上这个规则。同理AlertManagerConfig也需要通过label关联。

这里的设计实际上有点混乱,有的API对象是直接默认就会关联,有的必须要加标签。如果记不住就都加上monitor:default的标签即可。

常用告警配置

请参考这个项目,基本常用的exporter对应的告警都有。

Log

实际上我们一般需要做的是错误日志告警,即出现ERROR日志的时候通过alertmanager发送告警,而不需要真正的收集日志。边缘侧收集日志推荐使用loki,比efk更加轻量级,简单的日志统计功能推荐使用Google的mtail.

特别注意的是:如果能通过prometheus收集错误响应(5xx)的次数等数据,就没必要部署mtail来统计错误日志了。不过非请求(如定时任务或异步任务)就需要自己在代码里统计metric暴露了。

mtail

mtail实际上是一种特殊的exporter, 它使用正则过滤日志,并做简单的统计,最后将统计值暴露出去。不同的是mtail还支持push模式。

Google并没有提供mtail的官方镜像,更别说helm chart了,所以它的安装有点麻烦。

一般安装

mtail仍然只是一个go写的二进制文件,依赖配置文件驱动。所以从github上下载二进制文件之后直接启动就行:

mtail --progs /etc/mtail --logs /var/log/syslog --logs /var/log/ntp/peerstats --novm_logs_runtime_errors

--progs对应的是mtail的配置文件,--logs对应的是需要监控日志文件所在的文件夹。其他选项请参考这里

配置

mtail的配置文件语法有点像awk,一个例子:

counter total
/^[a-z]+((?P<response_size>\d+)|-)$/ {
  $1 != "-" {
    total = $response_size
  }
}

首先是声明变量,支持变量类型就是Prometheus的metric type,即:

  • counter
  • gauge
  • histogram

根据文档描述,不支持summary. 变量支持hidden修饰符,即不export该统计数据.

下面就是正则匹配,//之间是golang的正则形式,后面应该有个空格。特别地,需要通过strptime解析出日志中的时间戳。

需要复用的正则可以使用const定义在最前面;如果解析时间戳的逻辑比较复杂,也可以使用装饰器语法定义一个函数:

# The `syslog' decorator defines a procedure.  When a block of mtail code is
# "decorated", it is called before entering the block.  The block is entered
# when the keyword `next' is reached.
def syslog {
    /(?P<date>(?P<legacy_date>\w+\s+\d+\s+\d+:\d+:\d+)|(?P<rfc3339_date>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+[+-]\d{2}:\d{2}))/ +
        /\s+(?:\w+@)?(?P<hostname>[\w\.-]+)\s+(?P<application>[\w\.-]+)(?:\[(?P<pid>\d+)\])?:\s+(?P<message>.*)/ {
        # If the legacy_date regexp matched, try this format.
        len($legacy_date) > 0 {
            strptime($legacy_date, "Jan _2 15:04:05")
        }
        # If the RFC3339 style matched, parse it this way.
        len($rfc3339_date) > 0 {
            strptime($rfc3339_date, "2006-01-02T15:04:05-07:00")
        }
        # Call into the decorated block
        next
    }
}

上面是官方的例子,它定义了两种可能的时间格式。使用方法是类似python的语法:

@syslog
/foo/ {
  ...
}

如果时间戳里面有一些奇怪的符号,可以用subst函数先替换掉。

/x/的意思就是 $1 =~ /x/,也可以写$1 !~ /x/表示不匹配,当然也支持else语法;同时还支持otherwise语法,类似switchdefault,即:

/pattern/ {
  ...
} else {
  ...
}

{
  /pattern1/ {}
  /pattern2/ {}
  otherwise {}
}

完整的语法描述参考这里. 一个简单的统计错误日志的计数器如下:

counter error_line_total by filename

/ERROR/ {
    error_line_total[getfilename()]++
}

通过3903端口将统计值暴露出去,prometheus那边配置好采集,加上一个简单的alert rule即可:

groups:
  - name: LogStatExporter
    rules:
      - alert: FileErrorAlert
        expr: sum(increase(error_line_total[1m])) by (filename) > 0
        labels:
          severity: critical
        annotations:
          summary: "Log file {{ $labels.filename }} has excessive errors"
          description: "Log file {{ $labels.filename }} has experienced {{ $value }} errors in the last 1m"

k8s安装

如果k8s容器的日志都以stdout/stderr打出来(按k8s官方推荐的方法)或者挂载到了宿主机,那么mtail可以使用DaemonSet部署。stdout的日志路径都在/var/log/container下面,宿主机路径也是固定的,所以静态配置DaemonSet即可。

如果k8s容器的日志打在了容器里面,或者使用了分布式文件存储(如longhorn),那只能使用sidecar部署模式,需要每个app自行部署。

部署完成之后,需要增加Prometheus的ServiceMonitor配置,然后配置PrometheusRule来配置告警。

Loggie

这个是在调研中意外发现的一个国人项目,应该是网易团队开源的,他的功能比mtail更加丰富,但是又不像loki那么重,而且也是golang编写,比阿里用C++写的logtail适用范围更广。更重要的是,loggie是完全开源的,比阿里的半吊子开源部分组件还要商业化要友好的多。

mtail的问题有几个:

  1. 只能满足简单的统计需求,value只能是number;
  2. prometheus本身就不适合用来做日志统计,因为日志收集的信息各种各样,比如错误日志一般要将错误信息记录下来,这个放在Prometheus的tag里很不合适(重复度低,而且信息太长)。这就触发告警时能携带的上下文消息太少:比如错误日志告警就只能提示有错误日志,无法将错误信息也带上。
  3. mtail没有云原生支持,还是通过传统的配置文件来管理;

Loggie则较好的解决了这几个问题,而且他自带了logAlert功能,可以完成告警的需求。

一般安装

官方的Release实际上只有linux amd64的二进制,其他的要自己build。写这篇文档的时候windows兼容性还有点问题,建议windows上还是用mtail作为平替。

linux版本的安装参考这里即可。

k8s安装

支持helm安装,参考这里.

特别需要注意的是:如果日志挂载到hostpath,需要在配置里面把对应的路径挂载上。

helm install loggie https://ghproxy.com/https://github.com/loggie-io/installation/releases/download/v1.4.0/loggie-v1.4.0.tgz -n monitor \
--set image=loggieio/loggie:v1.4.1 \
--set config.loggie.discovery.enabled=true \
--set config.loggie.discovery.kubernetes.containerRuntime=docker \
--set config.loggie.discovery.kubernetes.rootFsCollectionEnabled=true \
--set config.loggie.discovery.kubernetes.parseStdout=true \
--set serviceMonitor.enabled=true

配置

支持通过CRD配置,主要参考这里.

由于是国人项目,官方文档比较容易看懂,这里就不再赘述,请参考以下配置:

apiVersion: loggie.io/v1beta1
kind: ClusterLogConfig
metadata:
  name: error-log-grep
spec:
  selector:
    type: pod
    labelSelector:
      alertError: open
  pipeline:
    sources: |
      - type: file
        name: stdout
        addonMeta: true
        paths:
          - stdout
    sink: |
      type: alertWebhook
      addr: http://edge-webhook:9095/api/v1/loggie
    interceptors: |
      - type: rateLimit
        qps: 4096
      - type: logAlert
        matcher:
          contains: ["ERROR"]
        sendOnlyMatched: true

Loki

上面两个组件做简单的日志过滤都不错,如果有日志收集的需求,建议还是使用Loki.

粗略看来,Loki的设计和Prometheus有很多相似之处,配置文件的字段都差不多,LogQL查询语言和PromQL也很像。在k8s中,也有Loki Operator项目,不过很遗憾bitnami中没有对应的chart,官方仓库需要翻墙。

但是日志场景实际上比较复杂,Loki并不是pull模型的一般由promtail来解析日志然后通过Loki的HTTP接口推送日志。Loki的架构比Prometheus也复杂很多,虽然也是单个二进制文件,但是支持-target参数,使其扮演不同的角色,从而拆分成微服务架构。

Loki的数据分为ChunksIndex两种,二者需要不同存储介质。在边缘侧部署,一般使用Loki的单体模式就够了。这种情况下,Loki将所有chunk数据存入文件系统;否则,必须指定一个块文件存储系统,如minio.

Loki相关资料比较复杂,下面只是简述供参考。

一般安装

github下载二进制文件(或者使用docker安装也行),包括:

  • logcli: 这是命令行客户端,Prometheus也有类似的promql-cli,不过后者是社区提供的;
  • loki:核心服务器程序;
  • promtail:抓取日志的客户端;

启动loki服务端:

loki -config.file=loki.local-config.yaml

需要一个配置文件,例如:

auth_enabled: false

server:
  http_listen_port: 3100

common:
  ring:
    instance_addr: 127.0.0.1
    kvstore:
      store: inmemory
  replication_factor: 1
  path_prefix: /tmp/loki

schema_config:
  configs:
  - from: 2020-05-15
    store: boltdb-shipper
    object_store: filesystem
    schema: v11
    index:
      prefix: index_
      period: 24h

storage_config:
  boltdb_shipper:
    active_index_directory: /data/loki/boltdb-shipper-active
    cache_location: /data/loki/boltdb-shipper-cache
    cache_ttl: 24h
    shared_store: filesystem
  filesystem:
    directory: /data/loki/chunks

table_manager:
  retention_deletes_enabled: true
  retention_period: 72h

上面的配置主要就是使用了本地存储,并且指定了index和存储路径,最后指明日志仅存储3天(注意这个值必须是24h的整数倍)。

然后启动promtail(也可以用docker),也需要一个配置文件,例如:

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /var/log/positions.yaml # This location needs to be writeable by Promtail.

client:
  backoff_config:
    max_period: 5s
    max_retries: 20
    min_period: 100ms
  batchsize: 102400
  batchwait: 1s
  timeout: 10s

clients:
  - url: http://ip_or_hostname_where_Loki_run:3100/loki/api/v1/push

scrape_configs:
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: varlogs  # A `job` label is fairly standard in prometheus and useful for linking metrics and logs.
      host: yourhost # A `host` label will help identify logs from this machine vs others
      __path__: /var/log/*.log  # The path matching uses a third party library: https://github.com/bmatcuk/doublestar

上面是一个静态文件夹下的日志抓取配置。

除了这些以外,还有很多配置选项,这里不再描述,使用的时候自己看文档即可。

k8s安装

官方的loki-stack被墙了,这里还是用bitnamigrafana-loki来装:

helm install loki-stack bitnami/grafana-loki -n monitor --version 2.10.1 \
--set global.storageClass=longhorn \
--set metrics.enabled=true \
--set metrics.serviceMonitor.enabled=true \
--set gateway.service.type=LoadBalancer \
--set gateway.service.ports.http=9092

需要注意的loki本身没有界面,需要安装grafana,或者使用logcli工具来交互。

告警配置

类似prometheus,也是通过rule来配置,请参考这里.

总结

Linux/K8s环境使用Loggie;windows环境使用mtail。

需要一个webhook服务,用来将告警发送到钉钉等im工具。