sealos-notify 是 Sealos 平台的统一通知服务,支持多渠道通知投递,具备可靠重试、幂等保证和高可用能力。
- 多渠道支持 — 站内信(CRD)、邮件、短信、语音、飞书 Webhook、飞书应用
- 飞书加急通知 — 发送消息后自动触发应用内 / 短信 / 电话加急提醒
- 模板驱动 — 所有通知内容通过数据库中管理的模板渲染,API 管理模板 CRUD
- 接口认证与审计 — 所有
/api/v1接口使用appId+appSecret认证,通知记录保存发送方appId - 可靠投递 — 数据库任务队列 + 指数退避重试,支持最大重试次数配置
- 幂等 API — 相同
idempotencyKey的重复请求安全幂等 - 高可用 — 多副本共享任务队列,通过数据库级
FOR UPDATE SKIP LOCKED实现无冲突任务分配 - 配置热加载 — 修改渠道、Provider 配置无需重启服务
- 优雅退出 — 停机时等待所有进行中的投递任务完成
HTTP API → Engine → delivery_tasks 表 → Dispatcher → Channel Adapters
↕
delivery_attempts 表
POST /api/v1/notifications创建通知记录、接收人和投递任务(每个接收人 × 渠道生成一条任务)。- Dispatcher 按配置间隔轮询任务队列,通过
FOR UPDATE SKIP LOCKED并发获取待执行和待重试任务(两类任务并行拉取)。 - 每条任务在独立 goroutine 中加载模板、渲染内容、调用对应 Adapter,结果写入
delivery_attempts;失败按退避调度重试,超过maxRetry后标记为dead。
- Go 1.21+
- PostgreSQL 14+
git clone https://github.com/labring/sealos-notify.git
cd sealos-notify
cp config.example.yaml config.yaml
# 按需修改 config.yaml 中的数据库配置和渠道配置docker run -d --name postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=sealos_notify \
-p 5432:5432 postgres:16-alpine本地开发可以先创建一个认证凭证文件:
mkdir -p /tmp/sealos-notify-auth
cat >/tmp/sealos-notify-auth/apps.yaml <<'EOF'
apps:
- appId: "notify-console"
appSecret: "dev-secret"
name: "Notify Console"
enabled: true
EOF并将 config.yaml 中的 auth.credentialsFilePath 改成 /tmp/sealos-notify-auth/apps.yaml。
go run . -c config.yaml或使用 Docker:
docker build -t sealos-notify .
docker run -p 8080:8080 -v $(pwd)/config.yaml:/config.yaml sealos-notify -c /config.yaml# 先创建模板
curl -X POST http://localhost:8080/api/v1/templates \
-H "Content-Type: application/json" \
-H "X-App-Id: notify-console" \
-H "X-App-Secret: dev-secret" \
-d '{
"name": "feishu-alert",
"channel": "feishu_app",
"msgType": "text",
"body": "【告警】{{ .incident }}(严重级别:{{ .severity }})"
}'
# 再发送通知(模板参数放在 channels 里,recipients 用 type+value 结构)
curl -X POST http://localhost:8080/api/v1/notifications \
-H "Content-Type: application/json" \
-H "X-App-Id: notify-console" \
-H "X-App-Secret: dev-secret" \
-d '{
"idempotencyKey": "incident-001",
"channels": {
"feishu_app": {
"template": "feishu-alert",
"params": {"incident": "DB 主节点不可用", "severity": "P0"}
}
},
"recipients": [
{"type": "feishu_user_id", "value": "ou_xxxxxxxx"},
{"type": "feishu_user_id", "value": "ou_yyyyyyyy"}
]
}'除 GET /health 外,所有 /api/v1/* 接口都需要认证。推荐使用 Header:
X-App-Id: notify-console
X-App-Secret: dev-secret也支持 Authorization: Bearer <appId>:<appSecret>。详细说明见 docs/api-authentication.md。
请求体:
{
"idempotencyKey": "唯一标识,相同 key 的请求只执行一次",
"channels": {
"feishu_app": {
"template": "feishu-alert",
"params": {"incident": "DB 主节点不可用", "severity": "P0"}
},
"email": {
"template": "email-alert",
"params": {"incident": "DB 主节点不可用", "severity": "P0"}
}
},
"recipients": [
{"type": "feishu_user_id", "value": "ou_xxxxxxxx"},
{"type": "email", "value": "alice@example.com"},
{"type": "phone", "value": "+8613800000000"}
]
}channels:map[渠道名 → {template, params}]template:模板名称(必填)params:渲染模板时注入的参数,所有接收人共享同一份渲染结果
recipients:{type, value}列表,每条记录是一个接收地址type:地址类型(见下表),用于匹配渠道value:实际地址(open_id、邮箱、手机号等)
recipient type 与渠道的对应关系:
type 值 |
匹配的渠道 |
|---|---|
email |
email、feishu_app、feishu_webhook |
phone |
sms、voice |
user_id |
inapp |
feishu_user_id |
feishu_app、feishu_webhook |
响应:
{"notificationId": "uuid", "status": "accepted"}返回通知详情及所有投递任务,包含发送方 senderAppId。
返回该通知的所有投递任务列表。
模板存储在数据库中,通过 API 管理。每个模板绑定一个渠道,包含用 Go text/template 语法编写的消息体(以及可选的邮件主题)。
请求体:
{
"name": "feishu-incident",
"channel": "feishu_app",
"description": "飞书故障告警模板",
"subject": "",
"body": "【{{ .severity }}】{{ .incident }}\n影响用户:{{ .name }}",
"msgType": "text",
"templateCode": ""
}| 字段 | 说明 |
|---|---|
name |
唯一模板名,发送通知时引用此名称(必填) |
channel |
渠道名,如 feishu_app、email、sms(必填) |
body |
消息体,Go text/template 语法,变量来自 recipient KV map |
subject |
邮件主题,同样支持模板语法(email 渠道使用) |
msgType |
消息格式,飞书渠道使用:text / post / interactive |
templateCode |
短信 / 语音服务商的模板 code(sms / voice 渠道使用) |
响应: 返回创建的模板对象(HTTP 201)
可选 ?channel=feishu_app 过滤特定渠道的模板。
请求体与创建相同,name 和 channel 字段不可更改(通过 URL 参数指定)。
数据库可达时返回 200 {"status":"healthy"}。
完整示例见 config.example.yaml。
| 字段 | 默认值 | 说明 |
|---|---|---|
address |
:8080 |
HTTP 监听地址 |
readTimeout |
30s |
读超时 |
writeTimeout |
30s |
写超时 |
idleTimeout |
60s |
空闲超时 |
| 字段 | 默认值 | 说明 |
|---|---|---|
host |
localhost |
PostgreSQL 主机 |
port |
5432 |
PostgreSQL 端口 |
user |
postgres |
数据库用户 |
password |
数据库密码 | |
dbname |
sealos_notify |
数据库名 |
sslMode |
disable |
SSL 模式 |
maxOpenConns |
25 |
最大连接数 |
maxIdleConns |
5 |
最大空闲连接数 |
connMaxLifetime |
5m |
连接最大生命周期 |
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
是否启用调度器 |
interval |
10s |
轮询间隔 |
batchSize |
100 |
每轮次每类任务(pending/retry)的最大拉取数量 |
leaseTimeout |
5m |
任务租约超时(超时后任务可被其他副本重新获取) |
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled |
true |
是否开启 /api/v1 接口认证 |
credentialsFilePath |
应用凭证文件路径,推荐挂载 Kubernetes Secret |
凭证文件变更会自动热加载到通知中心内存中。详细 Secret 格式、轮换方式和审计字段见 docs/api-authentication.md。
| 字段 | 默认值 | 说明 |
|---|---|---|
maxRetry |
3 |
最大重试次数(超过则标记 dead) |
retryBackoffSeconds |
[30, 120, 300] |
各次重试的等待秒数 |
每个 channel 项:
channels:
feishu_app:
enabled: true
provider: feishu-app-urgent # 引用 providers 中的 provider 名称每个 provider 的 type 决定使用的 Adapter,其余字段作为 data 传入 Adapter 构造函数。
飞书加急(紧急通知)是飞书应用消息的特殊功能,可在普通消息基础上触发额外提醒:应用内弹窗加急、短信通知、电话通知。
- 在飞书开放平台创建企业自建应用。
- 开通权限("权限管理"页面):
im:message:send_as_bot— 以机器人身份发消息im:message.group_urgent_app:create— 应用内加急(urgentType: app)im:message.group_urgent_sms:create— 短信加急(urgentType: sms)im:message.group_urgent_phone:create— 电话加急(urgentType: phone)
- 在应用"凭证与基础信息"页获取 App ID 和 App Secret。
- 将机器人添加到目标群组,或确保有权限给用户发送单聊消息。
channels:
feishu_app:
enabled: true
provider: feishu-app-urgent
providers:
feishu-app-urgent:
type: feishu_app
appId: "cli_xxxxxxxxxxxxxxxx"
appSecret: "xxxxxxxxxxxxxxxx"
receiveIdType: "open_id" # open_id | user_id | union_id | email
urgentUserIdType: "open_id" # open_id | user_id | union_id;默认跟随 receiveIdType(email/chat_id 除外)
msgType: "text" # text | post | interactive
urgentType: "app" # app | sms | phone | ""(空为不加急)- 调用飞书
im.v1.message.createAPI 发送消息。 - 获取返回的
message_id,调用对应加急 API(urgent_app/urgent_sms/urgent_phone)。 - 加急调用失败不影响主消息的投递结果(非致命错误,记录在
details.urgent_error)。
receiveIdType |
recipient map 中的键 |
|---|---|
open_id |
feishu_user_id |
user_id |
feishu_user_id |
union_id |
feishu_user_id |
email |
email |
sealos-notify/
├── main.go # 程序入口
├── config.example.yaml # 配置示例
├── pkg/
│ ├── config/ # 配置加载与热重载
│ ├── logger/ # 日志初始化
│ ├── database/ # 数据库连接(GORM)与 Schema 初始化
│ ├── storage/ # 数据访问层(GORM ORM)
│ │ ├── notification.go # 通知与接收人存储
│ │ ├── delivery.go # 投递任务与投递记录存储
│ │ └── template.go # 模板存储(CRUD)
│ ├── render/ # 模板渲染(text/template)
│ ├── engine/ # 通知引擎(请求校验、任务生成)
│ ├── dispatcher/ # 任务调度器(轮询、并发分发、重试)
│ └── adapter/
│ ├── adapter.go # Adapter 接口定义
│ └── feishu_app/ # 飞书应用加急通知 Adapter
├── server/ # HTTP 服务器、路由与 Handler
└── deploy/kubernetes/ # K8s 部署 manifests
- 在
pkg/adapter/<channel_name>/下创建目录,实现adapter.Adapter接口:type Adapter interface { Send(ctx context.Context, request *SendRequest) (*SendResponse, error) Name() string ChannelType() ChannelType Validate() error }
- 在
pkg/adapter/adapter.go的RecipientIdentifierKeys()中为新渠道添加标识键映射。 - 在
server/server.go的initAdapters()中注册该类型:case "my_channel": a, err := mychannel.New(providerConfig.Data) s.adapters[providerName] = a
- 在
config.example.yaml中添加对应的 channel 和 provider 示例配置。
# 构建并推送镜像到 DockerHub
make docker-build IMAGE=docker.io/<dockerhub-user>/sealos-notify VERSION=test
make docker-push IMAGE=docker.io/<dockerhub-user>/sealos-notify VERSION=test
# 按镜像名更新 deploy/kubernetes/deployment.yaml 后创建飞书凭证 Secret 和 API 认证 Secret
kubectl create namespace ns-admin --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic sealos-notify-feishu \
--from-literal=app-id=cli_xxxxxxxxxxxxxxxx \
--from-literal=app-secret=xxxxxxxxxxxxxxxx \
-n ns-admin
kubectl create secret generic sealos-notify-api-auth \
--from-file=apps.yaml=/path/to/apps.yaml \
-n ns-admin
# 部署
kubectl apply -f deploy/kubernetes/默认测试环境清单使用:
| 项目 | 值 |
|---|---|
| namespace | ns-admin |
| PostgreSQL host | sealos-notify-pg-postgresql-0.sealos-notify-pg-postgresql-hl.ns-admin.svc.cluster.local |
| PostgreSQL Secret | sealos-notify-pg-postgresql / postgres-password |
| 服务地址 | http://sealos-notify.ns-admin.svc.cluster.local:8080 |
验收发送链路:
kubectl -n ns-admin port-forward svc/sealos-notify 8080:8080
curl -X POST http://localhost:8080/api/v1/templates \
-H "Content-Type: application/json" \
-H "X-App-Id: notify-console" \
-H "X-App-Secret: CHANGE_ME_TO_A_LONG_RANDOM_SECRET" \
-d '{"name":"feishu-urgent-test","channel":"feishu_app","body":"【测试加急】{{ .message }}","msgType":"text"}'
curl -X POST http://localhost:8080/api/v1/notifications \
-H "Content-Type: application/json" \
-H "X-App-Id: notify-console" \
-H "X-App-Secret: CHANGE_ME_TO_A_LONG_RANDOM_SECRET" \
-d '{"idempotencyKey":"feishu-urgent-test-001","channels":{"feishu_app":{"template":"feishu-urgent-test","params":{"message":"sealos-notify 测试环境联调"}}},"recipients":[{"type":"feishu_user_id","value":"ou_xxxxxxxxxxxxxxxx"}]}'多副本场景下直接调整 Deployment 的 replicas,各副本通过数据库任务队列自动分工,无需额外配置。
所有配置字段均可通过环境变量覆盖:
| 环境变量前缀 | 对应配置节 |
|---|---|
SERVER_ |
server |
DATABASE_ |
database |
LOGGING_ |
logging |
DISPATCHER_ |
dispatcher |
AUTH_ |
auth |
示例:DATABASE_HOST=db.prod DATABASE_PASSWORD=secret ./sealos-notify -c config.yaml
make build # 构建二进制
make docker-build # 构建 Docker 镜像
make run # 本地运行(需要 PostgreSQL)Apache 2.0