Dozzle 告警与 Webhook 通知实战指南:基于表达式引擎的容器日志、指标与事件监控
2026/9/14 20:14:26 网站建设 项目流程

Dozzle 告警与 Webhook 通知实战指南:基于表达式引擎的容器日志、指标与事件监控

【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle

Dozzle 是一个支持 Docker、Swarm 与 Kubernetes 的实时容器日志查看器,其内置的告警系统允许你针对容器日志内容、CPU/内存指标和 Docker 生命周期事件自定义规则,并通过 Webhook(Slack、Discord、ntfy 或自定义端点)即时送达通知。本指南以 docs/fr/guide/alerts-and-webhooks.md 为主线,结合internal/notification后端实现与前端表单代码,完整讲解告警规则的三种类型、表达式语法、目的地配置、持久化要求,以及从表达式编译到消息分发的内部处理链路,读完即可在生产环境落地一套可复用的容器监控告警方案。

告警机制总览:规则永远留在自托管实例上

Dozzle 可以同时监听三种数据源,并在你描述的任意条件满足时发出通知:

告警类型触发依据典型用途
日志告警日志消息匹配指定模式5xx 错误、程序异常堆栈
指标告警CPU / 内存使用率越过阈值容器 CPU 超过 90%
事件告警Docker 上报的容器生命周期事件OOM 被杀、容器变为 unhealthy

每条告警都同时绑定两个表达式:容器表达式(选择要监控哪些容器)与触发表达式(决定什么条件下触发),二者都使用 expr-lang/expr 表达式语法,并且全部在 Dozzle 自己的实例上本地求值——日志、指标和事件不会离开你的主机。

一个值得强调的架构事实是:告警规则与目的地配置持久化在容器的/data目录中,internal/notification/persist.go 中定义了两个默认路径:

const ( DefaultNotificationConfigPath = "./data/notifications.yml" DefaultCloudConfigPath = "./data/cloud.yml" )

如果你希望配置在容器重启、镜像升级后仍然保留,就必须把/data以卷的形式挂载出来。

数据卷挂载与配置持久化

docker run 方式:

docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/data:/data -p 8080:8080 amir20/dozzle:latest

docker-compose 方式:

services: dozzle: image: amir20/dozzle:latest volumes: - /var/run/docker.sock:/var/run/docker.sock - /path/to/data:/data ports: - 8080:8080

从源码层面看,/data目录的写入由Persister统一负责:每次你在界面上保存改动,persist.go 都会把当前内存中的Config(包含SubscriptionsDispatchers)以 YAML 格式写回notifications.yml;服务启动时再通过Load()读回并应用。因此notifications.yml就是你告警体系的"地面真相",你也可以在仓库的 e2e 测试数据 e2e/data/notifications.yml 中看到该文件的示例结构。cloud.yml则单独保存 Dozzle Cloud 的 API Key 与元数据(persist.go),云端目的地不会被混入notifications.yml

配置通知目的地(Webhook 与 Dozzle Cloud)

在创建任何告警之前,你需要先在Notifications页面点击Add destination配置至少一个通知目的地。

Webhook 目的地

Webhook 会向指定 URL 发送 HTTP POST 请求。Dozzle 为最常见的服务内置了现成的 payload 模板,这些模板直接定义在前端源码 assets/components/notifications/payloadTemplates.ts 中:

  • Slack:使用 Slack 的 blocks + markdown 结构,正文为*{{ .Container.Name }}*{{ .Detail }},底部 context 区展示 Host 与 Image 信息;
  • Discord:适配 Discord Webhook API 的content+embeds结构,包含 Host、Image 两个内联字段;
  • ntfy:推送主题默认为dozzle-{{ .Container.HostName }},标题为容器名、正文为{{ .Detail }}
  • Custom:通用的{ container, message }JSON 结构,完全开放给你改造。

除了选模板,你还可以用 Go 的text/template语法编写完全自定义的 payload。渲染器在 internal/notification/dispatcher/webhook.go 中实现:若模板整体是合法 JSON,则先解析成 JSON 结构、再逐个把字符串字段中的{{ }}占位符解析渲染后重新序列化,从而保证日志消息里的引号、花括号等特殊字符被正确 JSON 转义;若模板不是合法 JSON,则退化为对整段文本执行 Go 模板。

自定义模板中可用的变量如下:

变量说明
{{.Detail}}摘要内容(日志消息或指标数值)
{{.Container.Name}}容器名称
{{.Container.Image}}容器镜像
{{.Container.HostName}}Docker 主机名
{{.Container.State}}容器状态
{{.Log.Message}}日志消息正文
{{.Log.Level}}日志级别
{{.Log.Timestamp}}日志时间戳
{{.Log.Stream}}日志流类型(stdout/stderr)
{{.Stat.CPUPercent}}CPU 使用百分比
{{.Stat.MemoryPercent}}内存使用百分比
{{.Stat.MemoryUsage}}内存使用量(字节)
{{.Subscription.Name}}告警规则名称

这些字段与后端数据结构一一对应,可对照 types/notification.go 中的NotificationContainerNotificationLog定义核实。另外注意目的地还支持自定义 HTTP Header(DispatcherConfig.Headers,见 internal/notification/types.go),可用于携带鉴权令牌。

保存前务必使用Test按钮验证 Webhook 是否可用。该按钮调用后端的SendTest(webhook.go):它会真实发出一次 POST 请求,2xx 视为成功,非 2xx 会返回状态码与错误信息,方便你排查 URL、鉴权或模板问题。

关于安全性,webhook.go 的实现细节值得说明:

  • 仅允许httphttps两种 URL 协议;
  • 自定义DialContext会先解析目标 IP 并拒绝回环地址、链路本地地址、组播地址及0.0.0.0/8等存在 SSRF 风险的地址段(包括 6to4、NAT64、Teredo 等 IPv6 过渡地址中内嵌的 IPv4);RFC1918 私有网段被有意放行,因为自托管 Webhook(如内网 Mattermost、Home Assistant)通常就部署在私有网络中;
  • HTTP 客户端设置 10 秒超时(Timeout: 10 * time.Second)。

Dozzle Cloud 目的地

已绑定 Cloud 的实例会自动获得Dozzle Cloud目的地,无需逐项配置。与裸 Webhook 不同,Cloud 端会将重复故障聚合为一条通知、生成事件摘要,并分发到邮箱、Telegram、Discord、Slack、ntfy 以及浏览器推送,同时支持静音与移动端渠道。云端的配置(API Key、前缀、过期时间)单独保存在cloud.yml中(persist.go)。完整能力见 Dozzle Cloud 指南。

创建一条告警:三步表单与容器表达式

Notifications页面点击Add alert进入创建流程。前端表单 assets/components/notifications/AlertForm.vue 把创建过程组织为三个步骤:

  1. 选择告警类型(log / metric / event);
  2. 编写容器过滤表达式(决定监听哪些容器);
  3. 编写触发表达式(随类型不同分别渲染 LogAlertFields.vue、MetricAlertFields.vue 或 EventAlertFields.vue)。

容器表达式

容器表达式负责圈定要监控的容器,可用属性如下:

属性类型示例
name字符串name contains "api"
image字符串image == "nginx:latest"
state字符串state == "running"
health字符串health == "unhealthy"
hostName字符串hostName == "prod-host"
labelsmaplabels["env"] == "production"

对照后端 types/notification.go,NotificationContainer实际还额外暴露了idhostId两个字段,需要按容器 ID 精确匹配时也可使用。

条件之间用&&(与)、||(或)、!(非)组合:

name contains "api" && labels["env"] == "production"

表单内置了实时校验与自动补全ExpressionField),并在你输入时实时预览当前命中的容器数量与名称(见 AlertForm.vue 的匹配结果提示),确认无误后再保存。

日志告警(Log Alerts)

日志表达式

日志表达式过滤触发告警的日志消息,可用属性:

属性类型示例
message字符串/mapmessage contains "error"
level字符串level == "error"
stream字符串stream == "stderr"
type字符串type == "complex"

对于 JSON 格式的日志,可用点号访问嵌套字段:

message.status >= 500 && message.path contains "/api"

支持的字符串操作符包括containsstartsWithendsWithmatches(正则)。

这里有一个重要的实现细节:message字段之所以既可能是字符串也可能是 map,是因为后端在 types.go 的extractMessage中做了归一化——简单日志原样返回字符串;多片段(分组)日志按行拼接为单个字符串;JSON/对象日志则把OrderedMap转换为普通 map 以兼容 expr 求值。另外在 processing.go 中,Dozzle 会主动跳过自己容器(镜像名含amir20/dozzle)的日志,避免告警-日志-告警的反馈循环。

日志告警示例

监控生产环境所有容器的错误日志:

Container: labels["env"] == "production" Log: level == "error"

监控 API 容器的 HTTP 5xx 错误:

Container: name contains "api" Log: message.status >= 500

监控特定镜像的所有 stderr 输出:

Container: image startsWith "myapp/" Log: stream == "stderr"

监控生产环境 API 的慢响应:

Container: name contains "api" && labels["env"] == "production" Log: message.duration > 5000 && message.path contains "/api"

用正则监控认证失败(大小写不敏感):

Container: name contains "auth" || name contains "gateway" Log: message matches "(?i)(unauthorized|forbidden|invalid token)"

在 internal/notification/processing.go 的日志处理链路中,每一条命中表达式的日志都会立即触发一次通知(日志告警没有冷却窗口,由表达式本身的精确度来控制消息量),Detail字段会带上完整的日志消息或 JSON 序列化结果(formatLogMessage)。

指标告警(Metric Alerts)

指标告警在容器 CPU 或内存使用率越过阈值时触发。触发表达式作用于在滑动窗口内采样的统计值的平滑均值上,从而避免短暂尖峰造成误报。

指标表达式

可用属性:

属性类型说明
cpu数值CPU 使用百分比(0–100),与界面显示一致
memory数值内存使用百分比(0–100)
memoryUsage数值内存使用量(字节)

cpu与界面上数值一致这点有源码保证:processing.go 会先用容器的CPULimit(无限制时回退到宿主机核心数)对原始 per-core 百分比做归一化,得到 0–100 的整体负载百分比。此外,NotificationStat还额外暴露了mounts字段(挂载点磁盘占用),支持如any(mounts, .usedPercent >= 85)的表达式(types/notification.go),可用于磁盘空间告警。

采样窗口与冷却时间

  • 采样窗口(Sample Window):表达式求值前参与平均的统计秒数。窗口越长越平滑、短则响应更快。代码实现见 types.go:默认 15 秒,合法范围被钳制在 1–300 秒。
  • 冷却时间(Cooldown):同一容器两次连续触发之间的最小间隔秒数,用于避免容器长期超标时告警刷屏。代码默认值为 300 秒,钳制在 0–3600 秒(types.go)。

指标告警还有一个"缓冲确认"机制:RecordMetricSample(types.go)为每个容器维护一个环形缓冲,只有当窗口内至少 80% 的采样点都命中表达式时才真正触发;窗口为 1 秒时则退化为即时判断。也就是说,"持续超标"才会告警,一次偶然的毛刺不会打扰你。

指标告警示例

监控生产环境容器的高 CPU:

Container: labels["env"] == "production" Metric: cpu > 90

监控特定服务的内存压力:

Container: name contains "api" Metric: memory > 85

监控绝对内存用量(1 GiB):

Container: name == "postgres" Metric: memoryUsage > 1073741824

触发后通知的Detail字段会附带实时数值,格式为CPU: 87.3%, Memory: 45.1%(见 processing.go)。

事件告警(Event Alerts)

事件告警直接监听 Docker 上报的容器生命周期事件,非常适合在不分析日志的前提下捕捉崩溃、OOM 杀死与健康状态变化。

事件表达式

可用属性:

属性类型说明
name字符串事件名称(见下方列表)
actorId字符串Docker actor 标识(通常是容器 ID)
attributesmapDocker 事件属性(随事件类型而异)
timestamp时间事件发生时刻

常见的 Docker 事件名包括startstopdiekilloomrestartdestroyhealth_status。从实现层面看,事件监听器在 internal/notification/event_listener.go 内部维护了一个事件白名单startstopdierestarthealth_statusoomkill),白名单之外的事件会被直接丢弃,这可以理解为可编写规则的稳定事件子集。

对于health_status事件,Docker 原生上报的名称形如health_status: healthy/health_status: unhealthy。Dozzle 在normalizeEvent(event_listener.go)中把事件名归一化为裸的health_status,并把健康状态写入attributes["healthStatus"],因此你可以直接写:

name == "health_status" && attributes["healthStatus"] == "unhealthy"

事件告警示例

生产环境容器停止时告警:

Container: labels["env"] == "production" Event: name == "die"

OOM 被杀时告警:

Container: true Event: name == "oom"

容器变为 unhealthy 时告警:

Container: true Event: name == "health_status" && attributes["healthStatus"] == "unhealthy"

监控异常退出(排除正常停机):

退出码 0(成功)、130(SIGINT)、143(SIGTERM)、137(SIGKILL)通常出现在docker stop、Ctrl+C 或更新换代的正常流程中,需排除以免噪音;真正的错误退出(1、2、125 等)始终触发:

Container: name contains "worker" Event: name == "die" && !(attributes["exitCode"] in ["0", "130", "143", "137"])

后端还会把退出码翻译成可读的信号名:describeExitCode(processing.go)维护了 129=SIGHUP、130=SIGINT、131=SIGQUIT、134=SIGABRT、137=SIGKILL、139=SIGSEGV、143=SIGTERM 的映射,因此die事件的Detail会呈现为Container event: die (exit code 137, SIGKILL)这样的信息。特别地,137 常被误读为 OOM,而真正的 OOM 会以独立的oom事件上报——代码注释中对此有明确解释。

事件告警同样受冷却时间控制,同一容器在冷却窗口内不会重复触发(processing.go)。

告警管理:启用、统计与删除

在 Notifications 页面,每条告警都支持:

  • 启用/停用:无需删除即可临时关停某条规则;
  • 编辑:随时修改表达式与绑定的目的地;
  • 查看统计:包括累计触发次数、命中的容器集合、最近一次触发时间;
  • 删除:移除不再需要的规则。

这些运行期统计在后端有对应的数据结构支撑:Subscription上的TriggerCount(原子计数器)、LastTriggeredAt(最近触发时间指针)与TriggeredContainerIDs(触发过的容器 ID 集合)都是不落盘的运行时字段(internal/notification/types.go)。并且当主节点向 Agent 同步配置时,HandleNotificationConfig会保留已有订阅的触发统计,只替换表达式与目的地,避免每次同步都清零历史数据(internal/notification/config.go)。

从表达式到通知:内部处理链路

了解底层原理有助于你写出更高效、更准确的规则。完整链路可分为三层,全部位于 internal/notification 目录:

第一层:表达式编译。每次创建或加载规则时,CompileExpressions(types.go)会用expr-lang/expr把容器表达式、日志表达式、指标表达式、事件表达式分别编译为预编译的vm.Program,并绑定对应的环境类型(NotificationContainer/NotificationLog/NotificationStat/NotificationEvent)。编译失败(如语法错误)会在保存时立即报错,而不是等到触发时才暴露。运行时MatchesContainerMatchesLog等则对编译产物求值,类型不匹配(例如对 JSON 日志写字符串操作)会被安全地视为不匹配并记录调试日志。

第二层:数据监听。三个监听器并行订阅三类数据源:

  • ContainerLogListener(log_listener.go):遍历所有 client 的容器列表,只对匹配容器表达式的容器建立日志流(StreamLogs),并支持容器动态启停时增量增删流;
  • ContainerStatsListener(stats_listener.go):订阅每秒统计流,并通过 5 秒 TTL 缓存解析容器与主机信息;
  • ContainerEventListener(event_listener.go):订阅 Docker 事件流,先归一化health_status,再过滤白名单与 Dozzle 自身容器。

第三层:匹配与分发。processing.go 中的三条处理循环(processLogEvents/processStatEvents/processDockerEvents)逐一检查订阅的容器表达式与触发表达式,命中后更新统计并构造types.Notification投递给订阅绑定的目的地。分发过程由信号量限流(防止积压时雪崩),单次发送带 30 秒超时(processing.go),失败会记录错误日志但不会重试。

一句话总结这条链路:规则编译为字节码 → 监听器按容器表达式定向采集数据 → 处理器按触发表达式实时求值 → 命中后通过目的地分发。理解了这条链路,你就能根据实际负载合理选择采样窗口与冷却时间,也能理解为什么规则必须保存在本地/data——因为整个求值过程都发生在你的实例上,日志与指标从不外流。

至此,你已经掌握了 Dozzle 告警系统的全部核心:从挂载/data持久化规则、配置 Slack/Discord/ntfy 等 Webhook 目的地,到编写容器、日志、指标、事件四类表达式,再到理解底层编译与分发机制。你可以直接从上述示例复制规则开始,再逐步调整采样窗口与冷却参数,让告警噪音与漏报之间达到最佳平衡。

【免费下载链接】dozzleRealtime log viewer for containers. Supports Docker, Swarm and K8s.项目地址: https://gitcode.com/GitHub_Trending/do/dozzle

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询