feat(observability): implement enterprise-grade k3s monitoring guardrails
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
name: Observability Guardrails
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "deploy/monitoring/**"
|
||||||
|
- "scripts/check-observability-k3s.sh"
|
||||||
|
- "scripts/deploy-observability.sh"
|
||||||
|
- "docs/04-operations/monitoring/**"
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "deploy/monitoring/**"
|
||||||
|
- "scripts/check-observability-k3s.sh"
|
||||||
|
- "scripts/deploy-observability.sh"
|
||||||
|
- "docs/04-operations/monitoring/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-observability:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate shell scripts syntax
|
||||||
|
run: |
|
||||||
|
bash -n scripts/check-observability-k3s.sh
|
||||||
|
bash -n scripts/deploy-observability.sh
|
||||||
|
|
||||||
|
- name: Validate dashboard JSON
|
||||||
|
run: |
|
||||||
|
jq -e . deploy/monitoring/grafana/provisioning/dashboards/ljwx-boot-dashboard.json >/dev/null
|
||||||
|
jq -e . deploy/monitoring/grafana/provisioning/dashboards/ljwx-bigscreen-dashboard.json >/dev/null
|
||||||
|
jq -e . deploy/monitoring/grafana/provisioning/dashboards/ljwx-health-enterprise-slo-dashboard.json >/dev/null
|
||||||
|
|
||||||
|
- name: Validate YAML structure and required resources
|
||||||
|
run: |
|
||||||
|
python - <<'PY'
|
||||||
|
import sys, yaml, pathlib
|
||||||
|
|
||||||
|
files = [
|
||||||
|
"deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml",
|
||||||
|
"deploy/monitoring/alertmanager/ljwx-health-alertmanagerconfig.yaml",
|
||||||
|
"deploy/monitoring/grafana/grafana.yaml",
|
||||||
|
]
|
||||||
|
for f in files:
|
||||||
|
p = pathlib.Path(f)
|
||||||
|
if not p.exists():
|
||||||
|
raise SystemExit(f"missing required file: {f}")
|
||||||
|
with p.open("r", encoding="utf-8") as fp:
|
||||||
|
docs = [d for d in yaml.safe_load_all(fp) if d]
|
||||||
|
if not docs:
|
||||||
|
raise SystemExit(f"empty yaml: {f}")
|
||||||
|
|
||||||
|
with open("deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml", "r", encoding="utf-8") as fp:
|
||||||
|
rule = yaml.safe_load(fp)
|
||||||
|
if rule.get("kind") != "PrometheusRule":
|
||||||
|
raise SystemExit("ljwx-health-k3s-prometheusrule.yaml kind must be PrometheusRule")
|
||||||
|
groups = rule.get("spec", {}).get("groups", [])
|
||||||
|
names = {g.get("name") for g in groups}
|
||||||
|
required = {
|
||||||
|
"ljwx_health_k3s_availability",
|
||||||
|
"ljwx_health_k3s_latency_and_errors",
|
||||||
|
"ljwx_health_k3s_dependencies",
|
||||||
|
"ljwx_health_k3s_recording_rules",
|
||||||
|
"ljwx_health_k3s_slo_alerts",
|
||||||
|
}
|
||||||
|
missing = required - names
|
||||||
|
if missing:
|
||||||
|
raise SystemExit(f"PrometheusRule missing groups: {sorted(missing)}")
|
||||||
|
print("observability yaml validation passed")
|
||||||
|
PY
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
apiVersion: monitoring.coreos.com/v1alpha1
|
||||||
|
kind: AlertmanagerConfig
|
||||||
|
metadata:
|
||||||
|
name: ljwx-health-routing
|
||||||
|
namespace: monitoring
|
||||||
|
labels:
|
||||||
|
release: monitoring
|
||||||
|
app.kubernetes.io/name: ljwx-health
|
||||||
|
spec:
|
||||||
|
route:
|
||||||
|
groupBy:
|
||||||
|
- alertname
|
||||||
|
- service
|
||||||
|
- category
|
||||||
|
- severity
|
||||||
|
groupWait: 30s
|
||||||
|
groupInterval: 5m
|
||||||
|
repeatInterval: 2h
|
||||||
|
receiver: ljwx-health-warning
|
||||||
|
routes:
|
||||||
|
- matchers:
|
||||||
|
- name: severity
|
||||||
|
value: critical
|
||||||
|
matchType: "="
|
||||||
|
receiver: ljwx-health-critical
|
||||||
|
continue: false
|
||||||
|
- matchers:
|
||||||
|
- name: severity
|
||||||
|
value: warning
|
||||||
|
matchType: "="
|
||||||
|
receiver: ljwx-health-warning
|
||||||
|
continue: false
|
||||||
|
- matchers:
|
||||||
|
- name: severity
|
||||||
|
value: info
|
||||||
|
matchType: "="
|
||||||
|
receiver: ljwx-health-info
|
||||||
|
continue: false
|
||||||
|
receivers:
|
||||||
|
- name: ljwx-health-critical
|
||||||
|
webhookConfigs:
|
||||||
|
- url: http://alert-webhook.monitoring.svc.cluster.local:8080/critical
|
||||||
|
sendResolved: true
|
||||||
|
maxAlerts: 0
|
||||||
|
- name: ljwx-health-warning
|
||||||
|
webhookConfigs:
|
||||||
|
- url: http://alert-webhook.monitoring.svc.cluster.local:8080/warning
|
||||||
|
sendResolved: true
|
||||||
|
maxAlerts: 0
|
||||||
|
- name: ljwx-health-info
|
||||||
|
webhookConfigs:
|
||||||
|
- url: http://alert-webhook.monitoring.svc.cluster.local:8080/info
|
||||||
|
sendResolved: true
|
||||||
|
maxAlerts: 0
|
||||||
@@ -83,6 +83,11 @@ kubectl create configmap grafana-dashboard-ljwx-bigscreen \
|
|||||||
--from-file=ljwx-bigscreen-dashboard.json=deploy/monitoring/grafana/provisioning/dashboards/ljwx-bigscreen-dashboard.json \
|
--from-file=ljwx-bigscreen-dashboard.json=deploy/monitoring/grafana/provisioning/dashboards/ljwx-bigscreen-dashboard.json \
|
||||||
-n infra \
|
-n infra \
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
kubectl create configmap grafana-dashboard-ljwx-health-enterprise-slo \
|
||||||
|
--from-file=ljwx-health-enterprise-slo-dashboard.json=deploy/monitoring/grafana/provisioning/dashboards/ljwx-health-enterprise-slo-dashboard.json \
|
||||||
|
-n infra \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
```
|
```
|
||||||
|
|
||||||
### 方法 2: 更新 Grafana Deployment
|
### 方法 2: 更新 Grafana Deployment
|
||||||
|
|||||||
@@ -217,6 +217,12 @@ spec:
|
|||||||
subPath: ljwx-bigscreen-dashboard.json
|
subPath: ljwx-bigscreen-dashboard.json
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
|
||||||
|
# 挂载 Enterprise SLO Dashboard JSON
|
||||||
|
- name: dashboard-ljwx-health-enterprise-slo
|
||||||
|
mountPath: /etc/grafana/provisioning/dashboards/ljwx-health-enterprise-slo-dashboard.json
|
||||||
|
subPath: ljwx-health-enterprise-slo-dashboard.json
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
# 挂载数据持久化目录(可选)
|
# 挂载数据持久化目录(可选)
|
||||||
- name: grafana-storage
|
- name: grafana-storage
|
||||||
mountPath: /var/lib/grafana
|
mountPath: /var/lib/grafana
|
||||||
@@ -272,6 +278,12 @@ spec:
|
|||||||
name: grafana-dashboard-ljwx-bigscreen
|
name: grafana-dashboard-ljwx-bigscreen
|
||||||
defaultMode: 0644
|
defaultMode: 0644
|
||||||
|
|
||||||
|
# Enterprise SLO Dashboard ConfigMap
|
||||||
|
- name: dashboard-ljwx-health-enterprise-slo
|
||||||
|
configMap:
|
||||||
|
name: grafana-dashboard-ljwx-health-enterprise-slo
|
||||||
|
defaultMode: 0644
|
||||||
|
|
||||||
# 数据持久化(可选,使用 emptyDir 或 PVC)
|
# 数据持久化(可选,使用 emptyDir 或 PVC)
|
||||||
- name: grafana-storage
|
- name: grafana-storage
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
|
|||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
{
|
||||||
|
"id": null,
|
||||||
|
"uid": "ljwx-health-enterprise-slo",
|
||||||
|
"title": "LJWX Health - Enterprise SLO",
|
||||||
|
"tags": ["ljwx", "slo", "enterprise"],
|
||||||
|
"timezone": "browser",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"version": 1,
|
||||||
|
"refresh": "30s",
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Boot Error Ratio (5m)",
|
||||||
|
"gridPos": {"h": 6, "w": 6, "x": 0, "y": 0},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ljwx_health:boot:http_error_ratio:rate5m",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percentunit",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{"color": "green", "value": null},
|
||||||
|
{"color": "yellow", "value": 0.001},
|
||||||
|
{"color": "red", "value": 0.005}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"], "fields": ""}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Boot Request Rate (5m)",
|
||||||
|
"gridPos": {"h": 6, "w": 6, "x": 6, "y": 0},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ljwx_health:boot:http_requests:rate5m",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {"defaults": {"unit": "reqps"}, "overrides": []},
|
||||||
|
"options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"], "fields": ""}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Boot P95 Latency",
|
||||||
|
"gridPos": {"h": 6, "w": 6, "x": 12, "y": 0},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ljwx_health:boot:http_latency_p95:5m",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "s",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{"color": "green", "value": null},
|
||||||
|
{"color": "yellow", "value": 0.5},
|
||||||
|
{"color": "red", "value": 1.0}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"], "fields": ""}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "stat",
|
||||||
|
"title": "Critical Alerts Firing",
|
||||||
|
"gridPos": {"h": 6, "w": 6, "x": 18, "y": 0},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "sum(ALERTS{alertstate=\"firing\",severity=\"critical\",namespace=\"monitoring\",alertname=~\"LjwxHealth.*\"})",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "none",
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{"color": "green", "value": null},
|
||||||
|
{"color": "red", "value": 1}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"], "fields": ""}}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "SLO Error Ratio Trends",
|
||||||
|
"gridPos": {"h": 9, "w": 12, "x": 0, "y": 6},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "ljwx_health:boot:http_error_ratio:rate5m",
|
||||||
|
"legendFormat": "5m",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "ljwx_health:boot:http_error_ratio:rate1h",
|
||||||
|
"legendFormat": "1h",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"unit": "percentunit"
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"legend": {"displayMode": "table", "placement": "bottom"},
|
||||||
|
"tooltip": {"mode": "multi"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timeseries",
|
||||||
|
"title": "Availability/Saturation Signals",
|
||||||
|
"gridPos": {"h": 9, "w": 12, "x": 12, "y": 6},
|
||||||
|
"datasource": {"type": "prometheus", "uid": "prometheus"},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"expr": "kube_deployment_status_replicas_available{namespace=\"ljwx-health\",deployment=~\"ljwx-health-(boot|admin|bigscreen)\"}",
|
||||||
|
"legendFormat": "{{deployment}} available",
|
||||||
|
"refId": "A"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expr": "kube_deployment_spec_replicas{namespace=\"ljwx-health\",deployment=~\"ljwx-health-(boot|admin|bigscreen)\"}",
|
||||||
|
"legendFormat": "{{deployment}} desired",
|
||||||
|
"refId": "B"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fieldConfig": {"defaults": {"unit": "none"}, "overrides": []},
|
||||||
|
"options": {
|
||||||
|
"legend": {"displayMode": "table", "placement": "bottom"},
|
||||||
|
"tooltip": {"mode": "multi"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -154,3 +154,82 @@ spec:
|
|||||||
summary: "ljwx-health 副本未达到期望值"
|
summary: "ljwx-health 副本未达到期望值"
|
||||||
description: "Deployment {{ $labels.deployment }} 可用副本持续低于期望值。"
|
description: "Deployment {{ $labels.deployment }} 可用副本持续低于期望值。"
|
||||||
runbook: "docs/04-operations/monitoring/observability-review-checklist-k3s.md"
|
runbook: "docs/04-operations/monitoring/observability-review-checklist-k3s.md"
|
||||||
|
|
||||||
|
- name: ljwx_health_k3s_recording_rules
|
||||||
|
interval: 30s
|
||||||
|
rules:
|
||||||
|
- record: ljwx_health:boot:http_requests:rate5m
|
||||||
|
expr: sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[5m]))
|
||||||
|
|
||||||
|
- record: ljwx_health:boot:http_errors_5xx:rate5m
|
||||||
|
expr: sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[5m]))
|
||||||
|
|
||||||
|
- record: ljwx_health:boot:http_error_ratio:rate5m
|
||||||
|
expr: |
|
||||||
|
ljwx_health:boot:http_errors_5xx:rate5m
|
||||||
|
/
|
||||||
|
clamp_min(ljwx_health:boot:http_requests:rate5m, 0.001)
|
||||||
|
|
||||||
|
- record: ljwx_health:boot:http_error_ratio:rate1h
|
||||||
|
expr: |
|
||||||
|
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[1h]))
|
||||||
|
/
|
||||||
|
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[1h])), 0.001)
|
||||||
|
|
||||||
|
- record: ljwx_health:boot:http_latency_p95:5m
|
||||||
|
expr: |
|
||||||
|
histogram_quantile(
|
||||||
|
0.95,
|
||||||
|
sum(rate(http_server_requests_seconds_bucket{namespace="ljwx-health",service="ljwx-health-boot"}[5m])) by (le)
|
||||||
|
)
|
||||||
|
|
||||||
|
- name: ljwx_health_k3s_slo_alerts
|
||||||
|
interval: 30s
|
||||||
|
rules:
|
||||||
|
- alert: LjwxHealthBootSLOErrorBudgetBurnFast
|
||||||
|
expr: |
|
||||||
|
(
|
||||||
|
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[5m]))
|
||||||
|
/
|
||||||
|
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[5m])), 0.001)
|
||||||
|
) > (14.4 * (1 - 0.999))
|
||||||
|
and
|
||||||
|
(
|
||||||
|
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[1h]))
|
||||||
|
/
|
||||||
|
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[1h])), 0.001)
|
||||||
|
) > (14.4 * (1 - 0.999))
|
||||||
|
for: 2m
|
||||||
|
labels:
|
||||||
|
severity: critical
|
||||||
|
category: slo
|
||||||
|
service: ljwx-health-boot
|
||||||
|
slo_target: "99.9"
|
||||||
|
annotations:
|
||||||
|
summary: "ljwx-health-boot 错误预算快速燃烧"
|
||||||
|
description: "5m/1h 多窗口 burn-rate 超标,SLO(99.9%) 可能快速失守。"
|
||||||
|
runbook: "docs/04-operations/monitoring/observability-review-checklist-k3s.md"
|
||||||
|
|
||||||
|
- alert: LjwxHealthBootSLOErrorBudgetBurnSlow
|
||||||
|
expr: |
|
||||||
|
(
|
||||||
|
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[30m]))
|
||||||
|
/
|
||||||
|
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[30m])), 0.001)
|
||||||
|
) > (6 * (1 - 0.999))
|
||||||
|
and
|
||||||
|
(
|
||||||
|
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot",status=~"5.."}[6h]))
|
||||||
|
/
|
||||||
|
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-health-boot"}[6h])), 0.001)
|
||||||
|
) > (6 * (1 - 0.999))
|
||||||
|
for: 15m
|
||||||
|
labels:
|
||||||
|
severity: warning
|
||||||
|
category: slo
|
||||||
|
service: ljwx-health-boot
|
||||||
|
slo_target: "99.9"
|
||||||
|
annotations:
|
||||||
|
summary: "ljwx-health-boot 错误预算持续燃烧"
|
||||||
|
description: "30m/6h 多窗口 burn-rate 超标,需在值班窗口内处理。"
|
||||||
|
runbook: "docs/04-operations/monitoring/observability-review-checklist-k3s.md"
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# 企业级可观测性实施方案(LJWX Health)
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 建立企业级三支柱能力:Metrics、Logs、Traces
|
||||||
|
- 建立 SLO 与错误预算治理机制(99.9% 可用性目标)
|
||||||
|
- 建立值班可执行机制:分级告警、路由、Runbook、门禁脚本
|
||||||
|
|
||||||
|
## 已落地资产
|
||||||
|
|
||||||
|
- Prometheus 告警与记录规则:
|
||||||
|
- `deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml`
|
||||||
|
- Alertmanager 路由配置(CRD):
|
||||||
|
- `deploy/monitoring/alertmanager/ljwx-health-alertmanagerconfig.yaml`
|
||||||
|
- SLO 总览仪表盘:
|
||||||
|
- `deploy/monitoring/grafana/provisioning/dashboards/ljwx-health-enterprise-slo-dashboard.json`
|
||||||
|
- 发布门禁脚本:
|
||||||
|
- `scripts/check-observability-k3s.sh`
|
||||||
|
- 监控变更 CI 守护:
|
||||||
|
- `.github/workflows/observability-guardrails.yml`
|
||||||
|
|
||||||
|
## 企业级能力映射
|
||||||
|
|
||||||
|
### 1) 可用性治理
|
||||||
|
|
||||||
|
- Deployment 不可用告警(boot/admin/bigscreen)
|
||||||
|
- CrashLoopBackOff 告警
|
||||||
|
- 副本覆盖率不足告警
|
||||||
|
|
||||||
|
### 2) 质量治理(错误率与延迟)
|
||||||
|
|
||||||
|
- 5xx 错误率告警(warning/critical)
|
||||||
|
- P95 延迟告警
|
||||||
|
- SLI recording rules:
|
||||||
|
- 请求率
|
||||||
|
- 错误率(5m/1h)
|
||||||
|
- P95 延迟
|
||||||
|
|
||||||
|
### 3) SLO 与错误预算
|
||||||
|
|
||||||
|
- SLO 目标:可用性 99.9%
|
||||||
|
- 多窗口 burn-rate 告警:
|
||||||
|
- Fast burn:5m/1h(critical)
|
||||||
|
- Slow burn:30m/6h(warning)
|
||||||
|
|
||||||
|
### 4) 值班分级路由
|
||||||
|
|
||||||
|
- severity = critical/warning/info 分流
|
||||||
|
- 支持 resolved 回调,减少人工噪音
|
||||||
|
|
||||||
|
## 部署步骤(生产/准生产)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 应用告警和记录规则
|
||||||
|
kubectl apply -f deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml
|
||||||
|
|
||||||
|
# 2. 应用 Alertmanager 路由
|
||||||
|
kubectl apply -f deploy/monitoring/alertmanager/ljwx-health-alertmanagerconfig.yaml
|
||||||
|
|
||||||
|
# 3. 部署/刷新 Grafana(含 enterprise SLO dashboard)
|
||||||
|
bash scripts/deploy-observability.sh
|
||||||
|
|
||||||
|
# 4. 运行门禁验收
|
||||||
|
./scripts/check-observability-k3s.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 发布门禁(必须全部通过)
|
||||||
|
|
||||||
|
- ArgoCD:`Synced + Healthy + selfHeal=true`
|
||||||
|
- 业务核心服务:`boot/admin/bigscreen` 均为 `Ready`
|
||||||
|
- PrometheusRule 存在并可被 Prometheus Operator 校验
|
||||||
|
- 对外健康探针返回 `HTTP 200`
|
||||||
|
|
||||||
|
## 运行期管理建议
|
||||||
|
|
||||||
|
- 值班流程:
|
||||||
|
- Critical:5 分钟内响应,必须回填事件编号
|
||||||
|
- Warning:30 分钟内确认影响与处置
|
||||||
|
- Info:日内归档处理
|
||||||
|
- 周期审计:
|
||||||
|
- 每周审计 Top 10 告警噪音来源
|
||||||
|
- 每周审计 SLO burn 事件与根因分类
|
||||||
|
- 每月审计阈值是否与业务峰值匹配
|
||||||
|
|
||||||
|
## 已知边界
|
||||||
|
|
||||||
|
- AlertmanagerConfig 中 webhook 地址为占位服务,需要替换成企业内部通知网关。
|
||||||
|
- 若集群未安装 `AlertmanagerConfig` CRD,路由对象不会生效,但不会影响 PrometheusRule。
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
- 评审清单: `docs/04-operations/monitoring/observability-review-checklist-k3s.md`
|
- 评审清单: `docs/04-operations/monitoring/observability-review-checklist-k3s.md`
|
||||||
- 门禁脚本: `scripts/check-observability-k3s.sh`
|
- 门禁脚本: `scripts/check-observability-k3s.sh`
|
||||||
- K3s 告警规则: `deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml`
|
- K3s 告警规则: `deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml`
|
||||||
|
- 企业级实施方案: `docs/04-operations/monitoring/observability-enterprise-implementation.md`
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
|
|||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "[1/4] Apply PrometheusRule"
|
||||||
|
kubectl apply -f deploy/monitoring/prometheus/alerts/ljwx-health-k3s-prometheusrule.yaml
|
||||||
|
|
||||||
|
echo "[2/4] Apply AlertmanagerConfig"
|
||||||
|
kubectl apply -f deploy/monitoring/alertmanager/ljwx-health-alertmanagerconfig.yaml
|
||||||
|
|
||||||
|
echo "[3/4] Deploy/refresh Grafana observability assets"
|
||||||
|
bash scripts/deploy-observability.sh
|
||||||
|
|
||||||
|
echo "[4/4] Run observability gate"
|
||||||
|
./scripts/check-observability-k3s.sh
|
||||||
|
|
||||||
|
echo "Observability enterprise rollout completed."
|
||||||
@@ -4,6 +4,7 @@ set -u
|
|||||||
|
|
||||||
NS_APP="${NS_APP:-ljwx-health}"
|
NS_APP="${NS_APP:-ljwx-health}"
|
||||||
NS_ARGO="${NS_ARGO:-argocd}"
|
NS_ARGO="${NS_ARGO:-argocd}"
|
||||||
|
NS_GRAFANA="${NS_GRAFANA:-monitoring}"
|
||||||
APP_NAME="${APP_NAME:-ljwx-health-k3s-eu}"
|
APP_NAME="${APP_NAME:-ljwx-health-k3s-eu}"
|
||||||
API_HEALTH_URL="${API_HEALTH_URL:-https://health-admin.lingjingwanxiang.cn/api/actuator/health}"
|
API_HEALTH_URL="${API_HEALTH_URL:-https://health-admin.lingjingwanxiang.cn/api/actuator/health}"
|
||||||
|
|
||||||
@@ -74,7 +75,26 @@ else
|
|||||||
bad "ServiceMonitor 数量不足:$sm_count"
|
bad "ServiceMonitor 数量不足:$sm_count"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "4) 对外健康检查"
|
info "4) 规则与路由资源"
|
||||||
|
if kubectl -n monitoring get prometheusrule ljwx-health-k3s-alerts >/dev/null 2>&1; then
|
||||||
|
ok "PrometheusRule ljwx-health-k3s-alerts 存在"
|
||||||
|
else
|
||||||
|
bad "PrometheusRule ljwx-health-k3s-alerts 缺失"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if kubectl -n monitoring get alertmanagerconfig ljwx-health-routing >/dev/null 2>&1; then
|
||||||
|
ok "AlertmanagerConfig ljwx-health-routing 存在"
|
||||||
|
else
|
||||||
|
warn "AlertmanagerConfig ljwx-health-routing 缺失(若未启用该 CRD 可忽略)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if kubectl -n "$NS_GRAFANA" get configmap grafana-dashboard-ljwx-health-enterprise-slo >/dev/null 2>&1; then
|
||||||
|
ok "Grafana enterprise SLO dashboard ConfigMap 存在"
|
||||||
|
else
|
||||||
|
warn "Grafana enterprise SLO dashboard ConfigMap 缺失(请运行 scripts/deploy-observability.sh)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "5) 对外健康检查"
|
||||||
http_code="$(curl -skI --max-time 8 "$API_HEALTH_URL" | awk '/^HTTP/{print $2}' | tail -n 1)"
|
http_code="$(curl -skI --max-time 8 "$API_HEALTH_URL" | awk '/^HTTP/{print $2}' | tail -n 1)"
|
||||||
if [[ "$http_code" == "200" ]]; then
|
if [[ "$http_code" == "200" ]]; then
|
||||||
ok "API health 返回 HTTP 200"
|
ok "API health 返回 HTTP 200"
|
||||||
@@ -82,7 +102,7 @@ else
|
|||||||
bad "API health 返回 HTTP ${http_code:-N/A}"
|
bad "API health 返回 HTTP ${http_code:-N/A}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "5) 近 10 分钟 boot 错误日志扫描"
|
info "6) 近 10 分钟 boot 错误日志扫描"
|
||||||
err_lines="$(kubectl -n "$NS_APP" logs deploy/ljwx-health-boot --since=10m 2>/dev/null | grep -E 'ERROR|Exception' | wc -l | tr -d ' ')"
|
err_lines="$(kubectl -n "$NS_APP" logs deploy/ljwx-health-boot --since=10m 2>/dev/null | grep -E 'ERROR|Exception' | wc -l | tr -d ' ')"
|
||||||
if [[ "$err_lines" -eq 0 ]]; then
|
if [[ "$err_lines" -eq 0 ]]; then
|
||||||
ok "boot 最近 10 分钟无 ERROR/Exception"
|
ok "boot 最近 10 分钟无 ERROR/Exception"
|
||||||
|
|||||||
@@ -165,6 +165,16 @@ deploy_grafana() {
|
|||||||
log_warning " ✗ ljwx-bigscreen-dashboard.json 未找到"
|
log_warning " ✗ ljwx-bigscreen-dashboard.json 未找到"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$DASHBOARD_DIR/ljwx-health-enterprise-slo-dashboard.json" ]; then
|
||||||
|
kubectl create configmap grafana-dashboard-ljwx-health-enterprise-slo \
|
||||||
|
--from-file=ljwx-health-enterprise-slo-dashboard.json="$DASHBOARD_DIR/ljwx-health-enterprise-slo-dashboard.json" \
|
||||||
|
-n $NAMESPACE_INFRA \
|
||||||
|
--dry-run=client -o yaml | kubectl apply -f -
|
||||||
|
log_success " ✓ ljwx-health Enterprise SLO Dashboard ConfigMap 已创建"
|
||||||
|
else
|
||||||
|
log_warning " ✗ ljwx-health-enterprise-slo-dashboard.json 未找到"
|
||||||
|
fi
|
||||||
|
|
||||||
# 2. 部署 Grafana Deployment 和 Service
|
# 2. 部署 Grafana Deployment 和 Service
|
||||||
log_info "部署 Grafana Deployment 和 Service..."
|
log_info "部署 Grafana Deployment 和 Service..."
|
||||||
kubectl apply -f "$MONITORING_DIR/grafana/grafana.yaml" -n $NAMESPACE_INFRA
|
kubectl apply -f "$MONITORING_DIR/grafana/grafana.yaml" -n $NAMESPACE_INFRA
|
||||||
|
|||||||
Reference in New Issue
Block a user