44.OpenObserve
OpenObserve 是一个 Rust 开发的开源的高性能云原生可观测平台(日志、指标、追踪),比起 Elasticsearch 它大约可以节省 140 倍的存储成本,OpenObserve 能够处理 PB 级的数据,如果你正在寻找一个用于日志、指标、追踪的可观测工具,那么 OpenObserve 是非常值得尝试的。
OpenObserve 目前处于 alpha 阶段,但其实也进行了广泛的测试,下表是目前的可用功能和路线图。
# | Feature | Status |
---|---|---|
1 | 日志搜索 | Available |
2 | 高压缩比的数据存储 | Available |
3 | 动态演化模式 | Available |
4 | 开箱即用的身份验证 | Available |
5 | 支持使用 S3、MinIO、GCS、Azure blob 进行数据存储 | Available |
6 | 高级图像界面 | Available |
7 | 基于 SQL 的查询语言 | Available |
8 | 支持高基数的数据 | Available |
9 | 搜索日志周围数据 | Available |
10 | 用户自定义摄取和查询函数(基于 VRL) | Available |
11 | 多组户 | Available |
12 | 兼容 Elasticsearch 的摄取 API | Available |
13 | 兼容 Elasticsearch 的搜索和聚合 API | Through zPlane |
14 | 计划报警 (Based on logs) | Available |
15 | 实时报警 (Based on logs) | Available |
16 | 高可用和集群 | Available |
17 | 无状态节点 | Available |
18 | 多语言支持 | Available |
19 | 多平台支持的预构建的二进制文件 | Available |
20 | 多平台支持的预构建的容器镜像 | Available |
21 | 带 SIMD 加速的预构建容器镜像 | Available |
22 | SIMD 支持向量化处理 (AVX512 and Neon) | Available |
23 | Dashboards | Available |
24 | Metrics | Available |
25 | 支持 metrics 的 PromQL | Available (部分支持 - 进行中) |
26 | Traces | Available |
27 | 计划报警 (Based on metrics) | Available |
28 | 实时报警 (Based on metrics) | Available |
29 | 基于模板的报警目标 (允许发生到 slack、teams 等) | Available |
30 | 发生报警到 Alertmanager | Available |
31 | 使用 Kinesis firehose 摄取 AWS 日志 (cloudwatch、VPC 流量日志、AWS WAF 等) | Available |
32 | 单点登录(SSO) | 正在启动... |
OpenObserve 与 Elasticsearch 的比较
Elasticsearch 是一个通用搜索引擎,可以使用应用程序搜索或日志搜索。OpenObserve 是专门为日志搜索而构建的,如果你正在寻找 Elasticsearch 的轻量级替代品,那么您应该看看 ZincSearch,如果只是想要一个日志搜索引擎,那么 OpenObserve 是一个非常好的选择。
OpenObserve 不依赖于数据索引,它将未索引的数据以压缩格式存储在本地磁盘或以 parquet 列格式的对象存储中。这使得数据摄取期间的计算要求大大降低,并且压缩率非常高,从而使存储成本降低约 140 倍。没有数据索引意味着全扫描搜索可能比 Elasticsearch 慢,但由于分区和缓存等多种其他技术,仍然应该很快。Uber 发现其生产环境中 80% 的查询是聚合查询,而 OpenObserve 的列式数据存储意味着聚合查询通常比 Elasticsearch 快得多。
下面是我们使用 Fluentbit 将真实日志数据从 Kubernetes 集群发送到 Elasticsearch 和 OpenObserve 时的结果,这只与存储有关。EBS 卷的成本为 8 美分/GB/月 (GP3),s3 的成本为 2.3 美分/GB/月。在 Elasticsearch 的 HA 模式下,通常有 1 个主节点和 2 个副本。无需复制 s3 来实现数据持久性/可用性,因为 AWS 会将你的对象冗余存储在 Amazon S3 区域中至少三个可用区 (AZ) 的多个设备上。
在上述场景中,OpenObserve 具有比 Elasticsearch 低 140 倍的存储成本的显着优势,这甚至没有考虑额外未使用的 EBS 卷容量(为了不耗尽磁盘空间而需要提供这些容量)以及持续监控磁盘使用情况以使其不被填满所需的工作。
无状态节点架构允许 OpenObserve 水平扩展,而无需担心数据复制或损坏。与 Elasticsearch 相比,您通常会发现管理 OpenObserve 集群的运维工作量和成本要低得多。
OpenObserve 内置的图形用户界面消除了对 Kibana 等其他组件的需求,而且由于 Rust 的优势,性能出色,而无需面对 JVM 所带来的问题。
与 Elasticsearch 相比,Elasticsearch 是一个通用性的搜索引擎,同时也兼具观测工具的功能。而 OpenObserve 是从头开始构建的观测工具,非常注重提供优秀的可观测性能。
架构
OpenObserve 可以在单节点下运行,也可以在集群中以 HA 模式运行。
单节点模式
单节点模式也分几种架构,主要是数据存储的方式不同,主要有如下几种:
Sled 和本地磁盘模式
如果你只需要进行简单使用和测试,或者对高可用性没有要求,可以使用此模式。当然你仍然可以在一台机器上每天处理超过 2 TB 的数据。在我们的测试中,使用默认配置,Mac M2 的处理速度为约 31 MB/秒,即每分钟处理 1.8 GB,每天处理 2.6 TB。该模式也是运行 OpenObserve 的默认模式。
Sled 和对象存储模式
该模式和 OpenObserve 的默认模式基本上一致,只是数据存在了对象存储中,这样可以更好的支持高可用性,因为数据不会丢失。
Etcd 和对象存储模式
该模式是使用 Etcd 来存储元数据,数据仍然存储在对象存储中。
HA 模式
HA 模式不支持本地磁盘存储,集群模式下 OpenObserve 会运行多个节点,每个节点都是无状态的,数据存储在对象存储中,元数据存储在 Etcd 中,这样可以更好的支持高可用性,因为数据不会丢失。
在该模式下 OpenObserve 主要包括 Router、Querier、Ingester 和 Compactor 四个组件,这些组件都可以水平扩展;Etcd 用于存储用户、函数、报警规则和集群节点信息等元数据;对象存储(例如 s3、minio、gcs 等等)存储 parquet
文件和文件列表索引的所有数据。
Router
Router
路由器将请求分发给 ingester 或 querier,它还通过浏览器提供 UI 界面。Router 实际上就是一个非常简单的代理,用于在数据摄入程序和查询程序之间发送适当的请求并进行响应。
Ingester
Ingester
用于接收摄取请求并将数据转换为 parquet
格式然后存储在对象存储中,它们在将数据传输到对象存储之前将数据临时存储在 WAL
中。
Querier
Querier
用于查询数据,查询器节点是完全无状态的。
Compactor
Compactor
会将小文件合并成大文件,使搜索更加高效。 Compactor
还处理数据保留策略、full stream 删除和文件列表索引更新。
AlertManager
Alertmanager
报警模块。
安装
OpenObserve 的安装非常简单,只需要下载二进制文件即可,它支持 Linux、Windows 和 MacOS,也支持 Docker 镜像。我们这里当然还是将其安装到 Kubernetes 集群中,为简单这里我们直接使用默认的 Sled 和本地磁盘模式。
首先创建一个命名空间:
$ kubectl create ns openobserve
然后创建如下所示的资源清单文件:
# openobserve.yaml
apiVersion: v1
kind: Service
metadata:
name: openobserve
namespace: openobserve
spec:
clusterIP: None
selector:
app: openobserve
ports:
- name: http
port: 5080
targetPort: 5080
---
# create statefulset
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: openobserve
namespace: openobserve
labels:
app: openobserve
spec:
serviceName: openobserve
replicas: 1
selector:
matchLabels:
app: openobserve
template:
metadata:
labels:
app: openobserve
spec:
securityContext:
fsGroup: 2000
runAsUser: 10000
runAsGroup: 3000
runAsNonRoot: true
containers:
- name: openobserve
image: public.ecr.aws/zinclabs/openobserve:latest
env:
- name: ZO_ROOT_USER_EMAIL # 指定管理员邮箱
value: root@example.com
- name: ZO_ROOT_USER_PASSWORD # 指定管理员密码
value: root321
- name: ZO_DATA_DIR
value: /data
imagePullPolicy: Always
resources:
limits:
cpu: 4096m
memory: 2048Mi
requests:
cpu: 256m
memory: 50Mi
ports:
- containerPort: 5080
name: http
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: cfsauto # 指定一个可用的存储类
resources:
requests:
storage: 10Gi
上面的资源清单中,我们使用了一个 StatefulSet 来创建 OpenObserve,需要注意的是需要配置 ZO_ROOT_USER_EMAIL
和 ZO_ROOT_USER_PASSWORD
两个环境变量用来指定管理员邮箱和密码。然后在 PVC 模板中指定一个可用的 StorageClass,用于持久化存储数据。
然后直接应用上面的资源清单文件即可:
$ kubectl apply -f openobserve.yaml
$ kubectl get pods -n openobserve
NAME READY STATUS RESTARTS AGE
openobserve-0 1/1 Running 0 2m31s
$ kubectl get svc -n openobserve
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
openobserve ClusterIP None <none> 5080/TCP 2m52s
快速使用
创建后我们可以查看一下 OpenObserve 的日志来验证是否启动成功:
$ kubectl logs -f openobserve-0 -n openobserve
[2023-08-04T10:18:06Z INFO openobserve] Starting OpenObserve v0.5.1
[2023-08-04T10:18:06Z INFO openobserve::service::db::user] get; org_id=Some("default") name="root@example.com"
[2023-08-04T10:18:06Z INFO tracing::span] set;
[2023-08-04T10:18:06Z INFO openobserve::service::db::user] Users Cached
# ......
[2023-08-04T10:18:06Z INFO openobserve::common::meta::telemetry] sending event OpenObserve - Starting server
[2023-08-04T10:18:07Z INFO actix_server::builder] starting 4 workers
[2023-08-04T10:18:07Z INFO actix_server::server] Tokio runtime found; starting in existing Tokio runtime
[2023-08-04T10:18:07Z INFO openobserve] starting HTTP server at: 0.0.0.0:5080, thread_id: 0
[2023-08-04T10:18:07Z INFO openobserve] starting HTTP server at: 0.0.0.0:5080, thread_id: 0
[2023-08-04T10:18:07Z INFO openobserve] starting HTTP server at: 0.0.0.0:5080, thread_id: 0
启动后我们可以通过 kubectl port-forward
命令将 OpenObserve 的 5080 端口映射到本地,然后在浏览器中访问 http://localhost:5080
即可看到 OpenObserve 的 UI 界面。
$ kubectl port-forward svc/openobserve 5080:5080 -n openobserve
Forwarding from 127.0.0.1:5080 -> 5080
Forwarding from [::1]:5080 -> 5080
使用上面指定的管理员邮箱和密码即可登录,然后就可以看到 OpenObserve 的主界面:
因为现在还没有数据,所以页面中没有任何内容,在 ingestion 页面提供了 Logs、Metrics、Traces 数据的各种摄取方法:
这里我们可以先使用 JSON API 来加载一些示例日志数据来了解一下 OpenObserve 的使用方法。先使用下面命令下载示例日志数据:
$ curl -L https://zinc-public-data.s3.us-west-2.amazonaws.com/zinc-enl/sample-k8s-logs/k8slog_json.json.zip -o k8slog_json.json.zip
$ unzip k8slog_json.json.zip
然后使用下面命令将示例日志数据导入到 OpenObserve 中:
$ curl http://localhost:5080/api/default/default/_json -i -u "root@example.com:root321" -d "@k8slog_json.json"
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
content-length: 71
vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
content-type: application/json
date: Fri, 04 Aug 2023 10:46:46 GMT
{"code":200,"status":[{"name":"default","successful":3846,"failed":0}]}%
收据导入成功后,刷新页面即可看到有数据了:
在 Stream 页面可以看到我们导入的数据元信息:
然后可以切换到 Logs
页面就可以看到日志数据了:
现在我们就可以去根据直接的需求去查询日志了,常用的一些查询语法如所示:
- 对于值
error
的全文搜索,在查询编辑器中使用match_all('error')
- 对于值
error
的不区分大小写的全文搜索,使用match_all_ignore_case('error')
- 对于值
error
的列搜索,使用str_match(fieldname, 'error')
,这比match_all
更有效,因为它在单个字段中搜索。 - 要搜索
code
列的值 200,使用code=200
- 要搜索列
stream
列的值为stderr
,使用stream='stderr'
- 要在日志
log
列上搜索和使用查询函数extract_ip
,使用extract_ip(log) | code=200
部署 Fluentd
上面我们通过手动导入示例日志数据来了解 OpenObserve 的基本使用,在生产环境中我们通常会使用日志采集器来收集日志数据,这里我们就来使用 Fluentd 来收集日志数据。
完整的资源清单文件如下所示:
# fluentd.yaml
kind: ConfigMap
apiVersion: v1
metadata:
name: fluentd-conf
namespace: kube-log
data:
# 容器日志
containers.input.conf: |-
<source>
@id fluentd-containers.log
@type tail # Fluentd 内置的输入方式,其原理是不停地从源文件中获取新的日志
path /var/log/containers/*.log # 容器日志路径
pos_file /var/log/es-containers.log.pos # 记录读取的位置
tag raw.kubernetes.* # 设置日志标签
read_from_head true # 从头读取
<parse> # 多行格式化成JSON
# 可以使用我们介绍过的 multiline 插件实现多行日志
@type multi_format # 使用 multi-format-parser 解析器插件
<pattern>
format json # JSON解析器
time_key time # 指定事件时间的时间字段
time_format %Y-%m-%dT%H:%M:%S.%NZ # 时间格式
</pattern>
<pattern>
format /^(?<time>.+) (?<stream>stdout|stderr) [^ ]* (?<log>.*)$/
time_format %Y-%m-%dT%H:%M:%S.%N%:z
</pattern>
</parse>
</source>
# 在日志输出中检测异常(多行日志),并将其作为一条日志转发
# https://github.com/GoogleCloudPlatform/fluent-plugin-detect-exceptions
<match raw.kubernetes.**> # 匹配 tag 为 raw.kubernetes.** 日志信息
@id raw.kubernetes
@type detect_exceptions # 使用 detect-exceptions 插件处理异常栈信息
remove_tag_prefix raw # 移除 raw 前缀
message log
multiline_flush_interval 5
</match>
<filter **> # 拼接日志
@id filter_concat
@type concat # Fluentd Filter 插件,用于连接多个日志中分隔的多行日志
key message
multiline_end_regexp /\n$/ # 以换行符 \n 拼接
separator ""
</filter>
# 添加 Kubernetes metadata 数据
<filter kubernetes.**>
@id filter_kubernetes_metadata
@type kubernetes_metadata
annotation_match ["^k8slog"] # 保留匹配以 `k8slog` 的注解日志
skip_labels true
skip_container_metadata true
skip_namespace_metadata true
skip_master_url true
</filter>
# 只保留具有 `k8slog: true` 注解的Pod日志
<filter kubernetes.**>
@id filter_log
@type grep
<regexp>
key $['kubernetes']['annotations']['k8slog']
pattern true
</regexp>
</filter>
# 删除一些多余的属性
<filter kubernetes.**>
@type record_transformer
remove_keys $.docker.container_id,$.kubernetes.pod_id,$.kubernetes.annotations.k8slog
</filter>
output.conf: |-
<match **>
@type http # 使用http插件
endpoint http://openobserve.openobserve.svc.cluster.local:5080/api/default/K8sLogs/_json # 指定接收日志的地址
content_type json
json_array true
<auth>
method basic
username root@example.com
password root321
</auth>
<buffer>
flush_interval 2s # 每2秒发送一次
</buffer>
</match>
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: fluentd
namespace: kube-log
labels:
k8s-app: fluentd
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
labels:
k8s-app: fluentd
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
rules:
- apiGroups:
- ""
resources:
- "namespaces"
- "pods"
verbs:
- "get"
- "watch"
- "list"
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: fluentd
labels:
k8s-app: fluentd
kubernetes.io/cluster-service: "true"
addonmanager.kubernetes.io/mode: Reconcile
subjects:
- kind: ServiceAccount
name: fluentd
namespace: kube-log
apiGroup: ""
roleRef:
kind: ClusterRole
name: fluentd
apiGroup: ""
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
namespace: kube-log
labels:
app: fluentd
kubernetes.io/cluster-service: "true"
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
kubernetes.io/cluster-service: "true"
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule
serviceAccountName: fluentd
containers:
- name: fluentd
image: quay.io/fluentd_elasticsearch/fluentd:v4.4.3
volumeMounts:
- name: fluentconfig
mountPath: /etc/fluent/config.d
- name: varlog
mountPath: /var/log
volumes:
- name: fluentconfig
configMap:
name: fluentd-conf
- name: varlog
hostPath:
path: /var/log
上面的资源清单文件中,我们使用了 Fluentd 的 tail
插件来收集容器日志,通过 kubernetes_metadata
插件添加 Kubernetes 的元数据,然后使用 grep
插件来过滤出具有 k8slog: true
注解的 Pod 日志,最后使用 http
插件将日志数据发送到 OpenObserve 中,其中 endpoint
参数指定了 OpenObserve 的 API 地址,格式为 http://localhost:5080/api/{organization}/{stream}/_json
,username
和 password
参数指定了管理员的邮箱和密码。
直接应用上面的资源清单文件即可:
$ kubectl apply -f fluentd.yaml
$ kubectl get pods -n kube-log
NAME READY STATUS RESTARTS AGE
fluentd-46brr 1/1 Running 0 22m
fluentd-7wbqv 1/1 Running 0 22m
fluentd-qblqz 1/1 Running 0 22m
然后创建一个带有 k8slog: "true"
的注解的 Pod 来测试一下:
# counter.yaml
apiVersion: v1
kind: Pod
metadata:
name: counter
annotations:
k8slog: "true" # 开启日志收集
spec:
containers:
- name: count
image: busybox
args:
[
/bin/sh,
-c,
'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done',
]
同样直接应用上面的资源清单文件即可:
$ kubectl apply -f counter.yaml
正常该 Pod 的日志就会被 Fluentd 收集到 OpenObserve 中了,我们可以在 OpenObserve 的 UI 界面中查看该日志流:
在日志页面选择 K8sLogs
流就可以查看到日志数据了:
指标
OpenObserve 除了支持日志之外,也支持指标数据的摄取,它支持 Prometheus 的远程写入协议,这样我们就可以直接将 Prometheus 的数据远程写入到 OpenObserve 中了。
下面的资源清单就是一个简单的 Prometheus 示例,我们使用 node_exporter
来采集节点的指标数据,然后通过 Prometheus 将其远程写入到 OpenObserve 中:
# prometheus.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: openobserve
data:
prometheus.yaml: |
global:
scrape_interval: 15s
scrape_timeout: 15s
remote_write: # 写入到远程 OO,url 是远程写入接口地址
- url: http://openobserve.openobserve.svc.cluster.local:5080/api/default/prometheus/api/v1/write
basic_auth:
username: root@example.com
password: root321
# queue_config: # 如果 Prometheus 抓取指标很大,可以加调整 queue,但是会提高内存占用
# max_samples_per_send: 10000 # 每次发送的最大样本数
# capacity: 20000
# max_shards: 30 # 最大分片数,即并发量。
scrape_configs:
- job_name: "nodes"
static_configs:
- targets: ['10.206.16.6:9100', '10.206.16.5:9100', '10.206.16.10:9100']
relabel_configs: # 通过 relabeling 从 __address__ 中提取 IP 信息,为了后面验证 VM 是否兼容 relabeling
- source_labels: [__address__]
regex: "(.*):(.*)"
replacement: "${1}"
target_label: 'ip'
action: replace
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus
namespace: openobserve
spec:
selector:
matchLabels:
app: prometheus
template:
metadata:
labels:
app: prometheus
spec:
containers:
- args:
- --config.file=/etc/prometheus/prometheus.yaml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=4h
- --web.enable-lifecycle
image: prom/prometheus:v2.44.0
imagePullPolicy: IfNotPresent
name: prometheus
ports:
- containerPort: 9090
name: http
protocol: TCP
securityContext:
runAsUser: 0
volumeMounts:
- mountPath: /etc/prometheus
name: config-volume
- mountPath: /prometheus
name: data
volumes:
- name: data
emptyDir: {}
- configMap:
defaultMode: 420
name: prometheus-config
name: config-volume
---
apiVersion: v1
kind: Service
metadata:
name: prometheus
namespace: openobserve
spec:
ports:
- name: http
port: 9090
targetPort: 9090
selector:
app: prometheus
type: NodePort
上面的资源清单文件中,我们使用了 Prometheus 的 remote_write
配置项来将数据远程写入到 OpenObserve 中,其中 url
参数指定了远程写入接口地址,username
和 password
参数指定了管理员的邮箱和密码。
直接应用上面的资源清单文件即可:
$ kubectl apply -f prometheus.yaml
$ kubectl get pods -n openobserve
NAME READY STATUS RESTARTS AGE
openobserve-0 1/1 Running 0 2d18h
prometheus-756c8c78f5-kvvbl 1/1 Running 0 20s
$ kubectl get svc -n openobserve
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
openobserve ClusterIP None <none> 5080/TCP 2d18h
prometheus NodePort 10.107.32.131 <none> 9090:31019/TCP 37s
部署后我们依然可以使用 Prometheus 的 UI 界面来查看指标数据:
正常现在 Prometheus 的指标数据就会被写入到 OpenObserve 中了,我们可以在 OpenObserve 的 UI 界面中查看该指标流:
可以看到 OpenObserve 中是将每个指标看成一个独立的 stream 流来进行管理的,这样无疑大大增加了对指标数据管理的灵活性,但要想针对某个 job 进行管理难度也就大大增加了。现在我们就可以在 OpenObserve 的 UI 界面中查看指标数据了,比如查询 node_load5
指标:
可以和 Prometheus 中的查询结果进行对比:
从图形中可以看到 OpenObserve 的查询结果和 Prometheus 的查询结果是一致的。但是目前 OpenObserve 的 UI 界面中支持的 promql 语法还比较有限,比如不支持向量运算等操作(本周发布的版本将支持了)。
此外我们也可以使用 SQL 语法来查询指标数据,比如查询 node_load5
指标:
除了使用 Prometheus 的远程写入方式之外,OpenObserve 还支持通过 OpenTelemetry Collector(后面会讲解)来写入指标数据,只需要在 exporters 中配置 prometheusremotewrite
即可,如下所示配置:
exporters:
prometheusremotewrite:
endpoint: "http://<oo-url>/api/org_name/prometheus/api/v1/write"
headers:
Authorization: Basic base64_encoded_data_of(userid:password)
在指标页面查询数据的时候我们还可以将查询结果保存为 Dashboard:
在 Dashboard 里面还可以添加变量,比如我们这里添加一个变量 instance
:
然后编辑 Panel,将查询语句中的 instance
替换成 $instance
:
然后就可以在 Dashboard 中选择不同的 instance
来查看不同的指标数据了:
链路追踪
OpenObserve 除了支持日志和指标之外,还支持链路追踪,OpenObserve 遵循 OpenTelemetry 的追踪标准,我们可以使用通过 OpenTelemetry SDK 检测的代码将 Trace 数据发送到 OpenObserve,或通过自动检测将跟踪发送到 OpenObserve。
下面是一个集成 OpenTelemetry SDK 的 Python 示例,代码位于 git clone https://github.com/openobserve/sample-tracing-python
:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# Service name is required for most backends
resource = Resource(attributes={
SERVICE_NAME: "python-service"
})
# create a tracer provider
tracer_provider = TracerProvider(resource=resource)
# create an OTLP trace exporter
url = 'HTTP_Endpoint'
headers = {"Authorization": "Authorization"}
exporter = OTLPSpanExporter(endpoint=url, headers=headers)
# create a span processor to send spans to the exporter
span_processor = BatchSpanProcessor(exporter)
# add the span processor to the tracer provider
tracer_provider.add_span_processor(span_processor)
# set the tracer provider as the global provider
trace.set_tracer_provider(tracer_provider)
我们只需要将 HTTP_Endpoint
和 Authorization
替换成 OpenObserve 的地址和管理员的认证信息即可。其中地址为 https://url:5080/api/<orgname>/traces
,Authorization
为 Basic base64(userid:password)
。然后运行上面的代码即可将 Trace 数据发送到 OpenObserve 中。
除此之外 OpenObserve 还支持通过 OpenTelemetry Collector 来收集链路追踪数据,只需要在 OpenTelemetry Collector 的配置文件中配置 otlp
输出即可,如下所示:
# ......
exporters: # 导出器,用于导出数据
jaeger:
endpoint: "jaeger:14250" # 使用 jaeger 容器名称
tls:
insecure: true # 不使用 TLS
# Data sources: traces, metrics
otlphttp:
traces_endpoint: http://url:5080/api/<orgname>/traces # OpenObserve 的接口地址,这里要用 traces_endpoint
headers:
Authorization: Basic base64(userid:password) # 管理员的认证信息
service: # 服务配置
pipelines: # 管道配置
traces: # 链路追踪配置
receivers: [otlp] # 接收器
exporters: [otlphttp] # 导出器
processors: [batch] # 处理器
然后我们在应用中只需要通过 OpenTelemetry SDK 配置 OTLPTraceExporter 地址为 OpenTelemetry Collector 的地址即可,正常链路追踪数据就可以被收集到 OpenObserve 中去了。
如下图所示,我们可以在 OpenObserve 的 UI 界面中查看链路追踪数据:
点击某个 Trace 可以查看详细信息:
关于如何使用 OpenTelemetry 来生成并收集 Traces 数据、OpenTelemetry Collector 的部署等知识点我们后面会详细讲解。
报警
OpenObserve 支持两种警报方式,针对指定流进行评估:
- 计划报警
- 实时报警
要使用报警我们首先需要创建一个报警模板,当发送警报通知时使用模板,模板构建发送到目的地的请求正文,例如。对于 slack,可以创建如下模板:
{
"text": "For stream {stream_name} of organization {org_name} alert {alert_name} of type {alert_type} is active"
}
在报警页面先添加一个报警模板,如下所示:
发送通知时,OpenObserve 会将 {stream_name}
、{org_name}
等占位符替换为 stream
、alert
、organization
的实际值。可以在模板中使用的变量有:
变量 | 值 | 描述 |
---|---|---|
stream_name | 流名称 | 报警创建的流名称 |
org_name | 组织名 | 组织名称 |
alert_name | 报警名 | 报警名称 |
alert_type | 报警类型 | 可以取的值 : real time 或者 scheduled |
然后接下来需要创建一个 Destinations
目的地,用于发送警报通知,创建后可以在多个报警中使用。
比如我们这里创建一个 Slack
目的地,用于发送警报通知,如下所示:
关于 Slack 如何创建 Webhook 可以参考 Slack 官方文档
最后我们就可以创建一个报警了,如下所示:
这里我们添加了一个计划报警,当 K8sLogs
日志流在 1 分钟内的日志数量大于 50 条时就会触发报警,然后发送到 Slack
目的地中,需要注意的是这里查询语句最终会加上时间范围进行过滤,比如我们这里的查询语句在执行的时候实际执行的语句如下所示:
select count(*) as echocnt FROM 'K8sLogs' WHERE (_timestamp >= 1691488182902275 AND _timestamp < 1691488242902275) LIMIT 100
正常情况下我们可以在 Slack 中看到报警信息:
另外对于实时报警是根据指定的条件在摄取时进行评估:
函数
OpenObserve 中的函数是使用 Vector Remap Language (vrl) 定义的,并且可以在数据摄取或查询期间用于帮助实现高级功能,例如增强、遮蔽、日志减少、合规性等。还有内置的查询函数,如 match_all
和 match_all_ignore_case
等,可用于根据用户的流设置或默认设置进行全文搜索。
Vector Remap Language (VRL) 是一种面向表达式的语言,专门设计用于以安全和高效的方式转换可观察性数据(日志和指标)。它具有简单的语法和丰富的内置函数集,专门针对可观察性用例而设计。
可以使用 VRL Playground 来调试
del(.kubernetes_pod_ip)
parsed, err = parse_json(.log)
if err == null {
.source = parsed.source
.event = parsed.event
.cycle = parsed.cycle
del(.log)
}
......(当前版本有 bug)
高可用模式
前面我们了解到 OpenObserve 的架构支持单节点和 HA 两种模式,接下来我们来了解下 OpenObserve 的 HA 模式是如何使用的。
OpenObserve 可以在裸机服务器、虚拟机、Kubernetes 和其他平台上以 HA 模式安装和运行,但目前官方提供的使用 Helm Chart 的安装方式,所以需要提前准备一个可用的 Kubernetes 集群。
由于 HA 模式不支持本地磁盘存储,因此必须配置对象存储(例如 s3、minio、gcs 等等),这里我们就以 minio 为例进行说明。
首先添加 openobserve 的 helm 仓库:
$ helm repo add openobserve https://charts.openobserve.ai
$ helm repo update
# 或者直接 clone chart 仓库
$ git clone https://github.com/openobserve/openobserve-helm-chart.git
然后可以使用下面的命令来获取 helm chat 包:
$ tree openobserve-helm-chart
openobserve-helm-chart
├── Chart.lock
├── Chart.yaml
├── LICENSE
├── README.md
├── charts
│ ├── etcd-8.10.1.tgz
│ ├── etcd-8.11.4.tgz
│ └── minio-5.0.7.tgz
├── index.yaml
├── publish.sh
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── alertmanager-deployment.yaml
│ ├── alertmanager-service.yaml
│ ├── compactor-deployment.yaml
│ ├── compactor-hpa.yaml
│ ├── compactor-service.yaml
│ ├── configmap.yaml
│ ├── ingester-hpa.yaml
│ ├── ingester-service.yaml
│ ├── ingester-statefulset.yaml
│ ├── ingress.yaml
│ ├── issuer.yaml
│ ├── querier-deployment.yaml
│ ├── querier-hpa.yaml
│ ├── querier-service.yaml
│ ├── router-deployment.yaml
│ ├── router-hpa.yaml
│ ├── router-service.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── zplane-deployment.yaml
│ ├── zplane-ingress.yaml
│ └── zplane-service.yaml
└── values.yaml
2 directories, 34 files
整个包里面还包括 etcd 和 minio 两个依赖的 helm chart 包,创建一个 oo-values.yaml
文件,然后添加如下内容:
auth:
ZO_ROOT_USER_EMAIL: "root@example.com"
ZO_ROOT_USER_PASSWORD: "root321"
# 启用了 minio 不用设置这个
# ZO_S3_ACCESS_KEY: "console"
# ZO_S3_SECRET_KEY: "console123"
config:
# ZO_S3_SERVER_URL: "http://minio:9000" # 启用了 minio 不用设置这个
ZO_S3_BUCKET_NAME: "openobserve"
ZO_S3_REGION_NAME: "us-east-1"
ZO_S3_PROVIDER: "minio" # 只有在使用 minio 进行对象存储时才需要设置这个,如果启用了minio,将会自动设置。
ZO_TELEMETRY: "false" # 发送匿名遥测信息以改进OpenObserve,您可以将其设置为false来禁用。
# ZO_ETCD_ADDR: "openobserve-etcd-headless.openobserve.svc.cluster.local:2379" # etcd endpoint,启用 etcd 会自动配置
ZO_DATA_DIR: "./data/" # 指定数据目录,主要是 WAL 日志
ZO_WAL_MEMORY_MODE_ENABLED: "false" # 开启内存模式,开启后不再写入本地文件,wal的数据直接在内存中,然后从内存转存到对象存储,为了解决某些云服务器,本地磁盘性能很差的问题,但是需要大内存。
ZO_WAL_LINE_MODE_ENABLED: "true" # wal的写入模式,一般写入数据的时候都会批量写入,但是这有个潜在的风险。比如你一次写入了10k数据,如果我一次性写入wal,有可能遇到系统崩溃,掉电,这一批数据不知道断在哪儿,wal文件会出错。可能写入了一半。如果开启line模式,就是虽然你是一次给了我10k数据,我写入wal的时候,一行行的调用write接口,这样wal损坏的可能性小一些,但是性能稍微差一些。
replicaCount: # 可以根据需要调整副本数
ingester: 1
querier: 1
router: 1
alertmanager: 1
compactor: 1
ingester:
persistence: # 持久化 data 目录,主要是 WAL 日志
enabled: true
size: 10Gi
storageClass: "cfsauto" # 指定可用的 storage class
accessModes:
- ReadWriteOnce
ingress:
enabled: true
className: "nginx"
annotations:
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/connection-proxy-header: keep-alive
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-body-size: 32m
hosts:
- host: oo.k8s.local
paths:
- path: /
pathType: ImplementationSpecific
etcd:
enabled: true # 指定 etcd 是否启用
replicaCount: 1 # 奇数
persistence:
size: 20Gi
storageClass: "cfsauto" # 指定可用的 storage class
accessModes:
- ReadWriteOnce
minio:
enabled: true # 指定 minio 是否启用
region: "us-east-1"
rootUser: rootuser
rootPassword: rootpass123
drivesPerNode: 1
replicas: 2
mode: distributed # or standalone
buckets:
- name: openobserve
policy: none
purge: false
persistence:
enabled: true
size: 10Gi
storageClass: "cfsauto" # 指定可用的 storage class
accessModes:
- ReadWriteOnce
consoleIngress:
enabled: true
ingressClassName: "nginx"
annotations:
kubernetes.io/ingress.allow-http: "true"
nginx.ingress.kubernetes.io/secure-backends: "true"
nginx.ingress.kubernetes.io/whitelist-source-range: 0.0.0.0/0
path: /
hosts:
- minio.k8s.local
在这个文件中我们指定了管理员的邮箱和密码,还有对象存储的相关配置,然后指定了 etcd 和 minio 的相关配置,要注意 auth
和 config
部分的配置,如果启用了 minio,那么 ZO_S3_ACCESS_KEY
、ZO_S3_SECRET_KEY
、ZO_S3_SERVER_URL
这些配置都可以省略,因为这些配置项会自动设置,包括如果启用了 etcd,ZO_ETCD_ADDR
参数也会自动配置。
另外我们可以看到 ingester
组件我们配置了数据持久化,这主要是为了对 WAL 日志数据的持久化,关于 WAL 主要有两种模式:
ZO_WAL_MEMORY_MODE_ENABLED
: 内存模式,开启后不再写入本地文件,wal 的数据直接在内存中,然后从内存转存到对象存储,为了解决某些云服务器,本地磁盘性能很差的问题,但是需要大内存。ZO_WAL_LINE_MODE_ENABLED
: WAL 的写入模式,默认开启,一般写入数据的时候都会批量写入,但是这有个潜在的风险。比如你一次写入了 10k 数据,如果我一次性写入 wal,有可能遇到系统崩溃,掉电,这一批数据不知道断在哪儿,wal 文件会出错。可能写入了一半。如果开启 line 模式,就是虽然你是一次给了我 10k 数据,我写入 wal 的时候,一行行的调用 write 接口,这样 wal 损坏的可能性小一些,但是性能稍微差一些。
所以如果我们使用内存模式的话理论上可以不用持久化数据目录,但有一种情况会落盘,就是当内存中还有数据的时候程序接收到关闭指令,会把内存中的数据 dump 到磁盘,下次启动的时候再转移到对象存储中去。
更多配置可以参考官方文档关于环境变量的说明:https://openobserve.ai/docs/environment-variables/。
然后使用如下命令即可一件安装 OpenObserve:
$ helm upgrade --install openobserve -f oo-values.yaml --namespace openobserve ./openobserve-helm-chart
Release "openobserve" does not exist. Installing it now.
NAME: openobserve
LAST DEPLOYED: Thu Aug 10 15:31:37 2023
NAMESPACE: openobserve
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
http://oo.k8s.local/
安装后我们可以查看一下 OpenObserve 的 Pod 状态:
$ kubectl get pods -n openobserve
NAME READY STATUS RESTARTS AGE
openobserve-alertmanager-7957d8fb79-xzh8z 1/1 Running 4 (31m ago) 32m
openobserve-compactor-d679d4765-gpkgk 1/1 Running 4 (31m ago) 32m
openobserve-etcd-0 1/1 Running 0 32m
openobserve-ingester-0 1/1 Running 0 4m31s
openobserve-minio-0 1/1 Running 0 32m
openobserve-minio-1 1/1 Running 0 32m
openobserve-querier-56456d48c5-dth52 1/1 Running 4 (31m ago) 32m
openobserve-router-7bd4fcbc6c-nrmbs 1/1 Running 3 (32m ago) 32m
prometheus-756c8c78f5-gkwl8 1/1 Running 0 2d
$ kubectl get svc -n openobserve
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
openobserve-alertmanager ClusterIP 10.102.247.215 <none> 5080/TCP 71m
openobserve-compactor ClusterIP 10.96.24.228 <none> 5080/TCP 71m
openobserve-etcd ClusterIP 10.103.96.225 <none> 2379/TCP,2380/TCP 71m
openobserve-etcd-headless ClusterIP None <none> 2379/TCP,2380/TCP 71m
openobserve-ingester ClusterIP 10.108.17.186 <none> 5080/TCP 71m
openobserve-minio ClusterIP 10.108.196.221 <none> 9000/TCP 71m
openobserve-minio-console ClusterIP 10.103.65.90 <none> 9001/TCP 71m
openobserve-minio-svc ClusterIP None <none> 9000/TCP 71m
openobserve-querier ClusterIP 10.99.221.44 <none> 5080/TCP 71m
openobserve-router ClusterIP 10.101.230.112 <none> 5080/TCP 71m
$ kubectl get ingress -n openobserve
NAME CLASS HOSTS ADDRESS PORTS AGE
openobserve nginx oo.k8s.local 10.98.12.94 80 53m
openobserve-minio-console nginx minio.k8s.local 10.98.12.94 80 53m
可以看到 OpenObserve 的几个组件 Router、Querier、Ingester、Alertmanager、Compactor、Minio、Etcd 都已经正常运行了,此外我们为 MinIO 和 OpenObserve 都添加了 Ingress,只需要将 oo.k8s.local
和 minio.k8s.local
映射到 Ingress 控制器即可访问。
比如现在我们可以通过 minio.k8s.local
来访问 MINIO 的 UI 界面:
使用用户名 rootuser
和密码 rootpass123
即可登录成功。
可以看到指定的 openobserve
bucket 也已经创建成功了:
同样我们也可以通过 oo.k8s.local
来访问 OpenObserve 的 UI 界面:
只是现在还没有任何数据:
接下来我们只需要将前面日志、指标、链路追踪的数据发送到新的 OpenObserve 地址 http://openobserve-router.openobserve.svc.cluster.local:5080
即可,比如前面我们已经部署的 Fluentd,只需要将日志输出地址修改即可:
# fluentd.yaml
# ...... 省略部分配置
output.conf: |-
<match **>
@type http # 使用http插件
endpoint http://openobserve-router.openobserve:5080/api/default/K8sLogs/_json # 指定接收日志的地址
content_type json
json_array true
<auth>
method basic
username root@example.com
password root321
</auth>
<buffer>
flush_interval 2s # 每2秒发送一次
</buffer>
</match>
然后重启 fluentd 即可,隔一会儿就可以在 OpenObserve 的 UI 界面中看到日志数据了:
同样对于 Prometheus 也是一样的,只需要修改 Prometheus 的配置文件中的远程写入地址即可:
remote_write: # 写入到远程 OO,url 是远程写入接口地址
- url: http://openobserve-router.openobserve:5080/api/default/prometheus/api/v1/write
basic_auth:
username: root@example.com
password: root321
对于链路追踪数据也是一样的,只需要修改 OpenTelemetry Collector 的配置文件中的远程写入地址即可。
最后我们可以去 MINIO 中验证下数据是否已经写入到对象存储中了:
不过需要注意的是数据并不是实时上传到对象存储中的,默认 10 分钟或者 WAL 达到 32MB 后会上传一次。
到这里 OpenObserve 的 HA 模式就部署完成了,我们可以根据需要设置各个组件的副本数量进行横向扩展,也可以启用 HPA 来自动扩缩容。44.OpenObserve