Rule đã trigger, alert đã sinh ra, nhưng team không biết. Vì sao? Vì connector không cấu hình đúng, hoặc cấu hình đúng nhưng template message sai. Connector là lớp giữa Kibana và thế giới ngoài, và nó là chỗ alert hay chết âm thầm nhất.
Bài này tổng hợp setup cho 4 loại connector dùng nhiều nhất trong production: Slack, Email, Webhook, PagerDuty. Mỗi loại có quirks riêng về authentication, rate limit, template syntax.
Mục tiêu:
- Setup từng loại connector qua GUI và qua API
- Hiểu Mustache template syntax để format message
- Quản lý secret an toàn, không leak webhook URL
- Tránh các pitfall hay gặp: rate limit, message format, retry logic
Phần 1: Mental model về Action và Connector
Trong Kibana:
- Connector: cấu hình kết nối tới một service ngoài. Lưu credential (webhook URL, SMTP password, API key).
- Action: liên kết rule với connector, kèm message template.
Một connector có thể dùng cho nhiều rule. Khi rule trigger, nó gọi action; action format message theo template rồi gửi qua connector.
Sequence chuẩn:
Rule triggers
-> Alert instance created
-> Action runs (for each alert)
-> Connector dispatches (HTTP/SMTP/API call)
-> External service receives
Mỗi bước có thể fail riêng. Khi debug, lần lượt check theo thứ tự này.
Phần 2: Slack connector
Cách phổ biến nhất, gần như mọi team SRE dùng.
Tạo Slack webhook
- Vào https://api.slack.com/apps → Create New App → From scratch.
- App name
Kibana Alerts, workspace của bạn. - Incoming Webhooks → Activate → Add New Webhook to Workspace.
- Chọn channel (
#alerts-prod). - Copy webhook URL:
https://hooks.slack.com/services/T.../B.../....
Setup connector
Stack Management → Connectors → Create connector → Slack.
curl -s -u "$KB_USER:$KB_PASS" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-X POST "$KIBANA_URL/api/actions/connector" \
-d '{
"name": "Slack #alerts-prod",
"connector_type_id": ".slack",
"secrets": {
"webhookUrl": "https://hooks.slack.com/services/REDACTED"
}
}'
Response trả về connector ID. Lưu lại để dùng khi gắn vào rule.
Template message
Trong rule action config:
:warning: *{{rule.name}}* triggered for *{{context.group}}*
Time: {{context.timestamp}}
Severity: {{rule.tags}}
Value: {{context.value}}
[View in Kibana]({{context.viewInAppUrl}})
Note: trong môi trường this blog không dùng emoji, nhưng trong Slack production message có thể dùng để team scan nhanh theo màu.
Mustache variables hay dùng:
| Variable | Nội dung |
|---|---|
{{rule.name}} | Tên rule |
{{rule.tags}} | Tags như severity, owner |
{{context.group}} | Group key (service.name nếu group by) |
{{context.value}} | Giá trị metric trigger |
{{context.timestamp}} | Thời điểm trigger |
{{context.viewInAppUrl}} | Link tới Kibana view |
{{context.hits}} | Số document match (cho ES query rule) |
Pitfall: webhook leak qua Git
Webhook URL Slack là bearer token. Ai có URL gửi được message vào channel. Nếu commit webhook vào git, bất kỳ ai clone repo đều spam được.
Phòng chống:
- Không bao giờ paste webhook URL trong markdown, docs, hay test script commit lên git.
- Nếu lỡ commit, revoke ngay (Slack app settings → Incoming Webhooks → Remove).
- Dùng secret manager (AWS Secrets Manager, Vault) hoặc Kibana stored secret. Connector secret được encrypt at rest trong ES
.kibanaindex, không expose qua GET API.
Rate limit
Slack giới hạn 1 message/giây per webhook (burst nhỏ cho phép). Nếu rule trigger 100 alert song song cùng lúc → Slack rate limit. Solution:
- Group alert ở rule level: thay vì 100 alert riêng, dùng
value_countaggregation và 1 alert “100 errors detected”. - Throttle action ở rule level (xem bài 11).
Phần 3: Email connector
Email là backup channel quan trọng cho on-call ngoài Slack.
SMTP setup
curl -s -u "$KB_USER:$KB_PASS" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-X POST "$KIBANA_URL/api/actions/connector" \
-d '{
"name": "SMTP via SendGrid",
"connector_type_id": ".email",
"config": {
"from": "[email protected]",
"host": "smtp.sendgrid.net",
"port": 587,
"secure": false,
"service": null,
"hasAuth": true
},
"secrets": {
"user": "apikey",
"password": "SG.xxx..."
}
}'
Pre-configured services
Kibana có shortcut cho service phổ biến. Nếu dùng Gmail, set service: "gmail" và hasAuth: true. Kibana tự fill host/port. Nhưng cần app-password (2FA), không phải password thường.
Microsoft Exchange / 365
Exchange Online dùng OAuth2 từ Kibana 8.7+. Setup:
{
"name": "Exchange Online",
"connector_type_id": ".email",
"config": {
"from": "[email protected]",
"service": "exchange_server",
"clientId": "<AAD app client_id>",
"tenantId": "<AAD tenant_id>"
},
"secrets": {
"clientSecret": "<AAD app secret>"
}
}
Cần tạo Azure AD app với permission Mail.Send. Setup này thường mất 1-2 ngày coordination với IT, plan trước.
Email template
Subject: [{{rule.tags}}] {{rule.name}} on {{context.group}}
Body:
Rule: {{rule.name}}
Triggered at: {{context.timestamp}}
Group: {{context.group}}
Value: {{context.value}}
View details:
{{context.viewInAppUrl}}
This alert is from Kibana monitoring.
Pitfall: SPF/DKIM bị reject
SMTP gửi từ Kibana với from: [email protected] nhưng domain example.com không có SPF record cho IP của Kibana server. Mail bị Gmail/Outlook đẩy vào spam hoặc reject.
Fix:
- Dùng email relay service (SendGrid, Mailgun, AWS SES) có sẵn SPF/DKIM.
- Hoặc thêm IP của Kibana server vào SPF record của domain.
- Hoặc dùng email subdomain riêng (
mon.example.com) với SPF/DKIM riêng.
Phần 4: Webhook connector
Generic nhất. Có thể gửi tới bất kỳ HTTP endpoint nào: custom integration, JIRA REST, internal alert hub.
Setup
curl -s -u "$KB_USER:$KB_PASS" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-X POST "$KIBANA_URL/api/actions/connector" \
-d '{
"name": "Internal Alert Hub",
"connector_type_id": ".webhook",
"config": {
"method": "post",
"url": "https://alerts.internal/hooks/kibana",
"headers": {
"Content-Type": "application/json",
"X-Source": "kibana"
},
"hasAuth": true,
"authType": "webhook-authentication-basic"
},
"secrets": {
"user": "kibana-bot",
"password": "..."
}
}'
Auth methods
Webhook connector support:
webhook-authentication-basic: HTTP Basic authwebhook-authentication-ssl: Client certificate- Không auth: để
hasAuth: false
Nếu endpoint dùng custom header auth (Bearer token), add vào config.headers:
"headers": {
"Authorization": "Bearer ${SECRET}",
"Content-Type": "application/json"
}
Lưu ý: header value không được template từ secret. Workaround: stored secret trong header trực tiếp (kém an toàn) hoặc dùng connector .webhook với secrets section riêng cho token.
Message body
Template body trong action config:
{
"alert_name": "{{rule.name}}",
"severity": "{{rule.tags}}",
"service": "{{context.group}}",
"value": "{{context.value}}",
"timestamp": "{{context.timestamp}}",
"kibana_url": "{{context.viewInAppUrl}}",
"raw_hits": {{context.hits}}
}
Lưu ý JSON syntax: nếu context.hits không tồn tại, Mustache render thành empty string, JSON invalid. Wrap trong {{#context.hits}}...{{/context.hits}} conditional hoặc dùng default value.
Pitfall: timeout
Kibana action timeout mặc định 60 giây. Nếu endpoint chậm hoặc treo, action retry 3 lần rồi fail. Trong khoảng thời gian đó, alert state là Active nhưng notification chưa tới.
Fix: ensure endpoint trả response < 5s. Nếu cần xử lý async, endpoint trả 202 Accepted ngay, processing background. Đừng để Kibana block.
Phần 5: PagerDuty connector
Cho on-call rotation và incident management nghiêm túc.
Setup
- PagerDuty UI → Services → Service Directory → Add new service.
- Trong service, Integrations → Add new integration → Events API v2.
- Copy Integration Key (routing key, không phải API key).
curl -s -u "$KB_USER:$KB_PASS" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-X POST "$KIBANA_URL/api/actions/connector" \
-d '{
"name": "PagerDuty Production",
"connector_type_id": ".pagerduty",
"secrets": {
"routingKey": "R..."
}
}'
Action params
PagerDuty action có nhiều field hơn Slack/Email vì PagerDuty hỗ trợ acknowledge, resolve, dedup_key.
{
"dedupKey": "{{rule.id}}-{{context.group}}",
"eventAction": "trigger",
"severity": "error",
"summary": "{{rule.name}}: {{context.group}}",
"source": "kibana-prod",
"component": "{{context.group}}",
"class": "monitoring",
"customDetails": {
"value": "{{context.value}}",
"url": "{{context.viewInAppUrl}}"
}
}
Dedup key
Đây là feature quan trọng. PagerDuty merge các trigger có cùng dedupKey thành 1 incident. Nếu rule trigger mỗi phút trong 10 phút và bạn dùng dedupKey = rule.id-context.group, chỉ có 1 incident được tạo (không phải 10).
Khi rule khôi phục, gửi event eventAction: "resolve" với cùng dedupKey để auto-close incident.
Setup recovery action:
- Trong rule, ngoài action
Triggered, thêm actionRecovered. - Recovery action gửi PagerDuty với
eventAction: "resolve"và cùngdedupKey.
Kết quả: PagerDuty incident tự open khi rule trigger, tự close khi resolve. On-call không phải acknowledge manually.
Pitfall: severity không map
PagerDuty severity chỉ accept: critical, error, warning, info. Nếu bạn gửi "high" hoặc "P1", PagerDuty reject event. Validate template trước khi prod.
Phần 6: Test connector
Mỗi connector có Test button trong UI. Click sẽ gửi test message với template mẫu.
Qua API:
curl -s -u "$KB_USER:$KB_PASS" \
-H "kbn-xsrf: true" \
-H "Content-Type: application/json" \
-X POST "$KIBANA_URL/api/actions/connector/<CONNECTOR_ID>/_execute" \
-d '{
"params": {
"message": "Test from Kibana"
}
}'
Response cho biết success hay fail với error detail. Luôn test connector ngay sau khi tạo, không chờ rule trigger thật.
Phần 7: Pitfall storytelling tổng hợp
Ca 1: Slack mất alert vì channel rename
Channel #alerts-prod bị rename thành #sre-alerts. Webhook URL cũ vẫn hoạt động nhưng gửi vào “ghost channel” không ai monitor. Fix: dùng channel ID (immutable) trong Slack app config, hoặc tạo channel mới với webhook mới và migrate.
Ca 2: Email dùng template HTML bị mangled
Outlook hiển thị <b>{{rule.name}}</b> literal vì template dùng text/plain nhưng có HTML tag. Fix: trong email action config, đặt messageHTML field nếu có (Kibana 8.4+) hoặc strip HTML, dùng plain text.
Ca 3: Webhook double-fire
Rule trigger 1 alert nhưng webhook nhận 2 request. Kibana retry vì lần 1 endpoint trả 503 (DB chậm 5s). Endpoint không idempotent → 2 record trong DB.
Fix:
- Endpoint ALWAYS idempotent: dedup theo
rule.id + alert.idở DB layer. - Hoặc tăng request timeout của endpoint để không trả 503 ngay.
- Hoặc giảm Kibana retry: trong rule, set
notifyWhen: onActionGroupChangethay vìonActiveAlert.
Ca 4: PagerDuty không close incident
Rule khôi phục mà PagerDuty incident vẫn open. Lý do: recovery action không cùng dedupKey với trigger action. Mỗi event ghi nhận thành incident riêng.
Fix: đảm bảo cả triggered VÀ recovered action dùng exact same dedupKey template.
Phần 8: Best practices
| Practice | Lý do |
|---|---|
| Tạo connector test trước khi gắn rule | Phát hiện lỗi sớm |
| Một connector per channel/recipient | Dễ disable từng kênh khi cần |
| Tag connector với owner | Tracking |
| Backup connector config qua saved objects export | Khôi phục nhanh |
| Rotate webhook URL/secret định kỳ | Security |
Set rule action notifyWhen: onActionGroupChange | Giảm spam |
| Group alert ở rule level | Tránh rate limit |
| Idempotent endpoint cho webhook | Tránh duplicate |
Cheatsheet
| Connector | Khi dùng | Auth |
|---|---|---|
.slack | Team channel notification | Webhook URL (bearer) |
.email | Backup, weekly report | SMTP basic / OAuth2 |
.webhook | Custom integration, JIRA, internal hub | Basic / SSL / Header token |
.pagerduty | On-call rotation, incident | Routing key |
.servicenow | Enterprise ITSM | OAuth |
.opsgenie | Alt on-call platform | API key |
.teams | Microsoft Teams channel | Webhook URL |
Lời kết
Connector là phần ít tài liệu chính thức nhất nhưng lại quyết định liệu alert có tới được người cần thấy. Setup đúng từ đầu, test thường xuyên, và đặc biệt là backup config qua saved objects export sẽ tránh được tình trạng “rule trigger mà không ai biết”.
Bài tiếp theo trong series Kibana từ A đến Z sẽ đi vào SLO tracking: cách định nghĩa SLI, error budget, và alert progression. Đây là phần biến từ “alert khi có lỗi” sang “alert theo mức độ tiêu budget”, một bước nhảy lớn về maturity của observability. Nếu bạn đang setup connector cho stack riêng và gặp pattern lạ, chia sẻ ở comment.