Quan sát
Stack monitoring chạy trong namespace nx-watcher. Staging cung cấp metrics và log cơ bản; production bổ sung distributed tracing với Tempo và OpenTelemetry Collector.
Tổng quan Kiến trúc
Staging
Chưa Triển khai
Stack observability cho staging (Prometheus, Grafana, Loki, Promtail) đã được lên kế hoạch nhưng chưa triển khai. Namespace nx-watcher tồn tại nhưng không có pod nào. Các manifest bên dưới mô tả kiến trúc mục tiêu.
Production
Staging vs Production
| Thành phần | Staging | Production |
|---|---|---|
| Prometheus | Có | Có |
| Grafana | Có | Có |
| Loki + Promtail | Có | Có |
| Tempo | Không | Có |
| OTel Collector | Không | Có |
| Lên lịch node | Pool default (không taint) | Node monitoring chuyên dụng (dedicated=monitoring:NoSchedule) |
| Alertmanager | Tùy chọn | Có |
Lên lịch Node
Staging
Pod monitoring chạy trên node default cùng với workload ứng dụng. Không cần taint hay toleration.
spec:
nodeSelector:
node.kubernetes.io/pool: defaultProduction
Pod monitoring chạy trên node chuyên dụng với taint dedicated=monitoring:NoSchedule. Tất cả workload monitoring phải bao gồm toleration.
Pod monitoring chạy trên node chuyên dụng với taint `dedicat...
spec:
nodeSelector:
node.kubernetes.io/pool: monitoring
tolerations:
- key: dedicated
operator: Equal
value: monitoring
effect: NoSchedulePrometheus
Thu thập metrics từ tất cả dịch vụ, Traefik, và các thành phần tầng dữ liệu.
Deployment
StatefulSet: nx-prometheus
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nx-prometheus
namespace: nx-watcher
spec:
serviceName: nx-prometheus
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: prometheus
template:
spec:
serviceAccountName: prometheus
nodeSelector:
node.kubernetes.io/pool: default # staging: default, production: monitoring
# Chỉ Production:
# tolerations:
# - key: dedicated
# operator: Equal
# value: monitoring
# effect: NoSchedule
containers:
- name: prometheus
image: prom/prometheus:v3.2.1
args:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle
ports:
- containerPort: 9090
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
volumeMounts:
- name: prometheus-config
mountPath: /etc/prometheus
- name: prometheus-data
mountPath: /prometheus
volumes:
- name: prometheus-config
configMap:
name: prometheus-config
volumeClaimTemplates:
- metadata:
name: prometheus-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: csi-sc-vnpaycloud
resources:
requests:
storage: 10GiCấu hình Thu thập
ConfigMap: prometheus-config
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-config
namespace: nx-watcher
data:
prometheus.yml: |
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
# Metrics Traefik (trong nx-internal)
- job_name: traefik
static_configs:
- targets: ['nx-traefik.nx-internal.svc.cluster.local:8080']
metrics_path: /metrics
# Dịch vụ backend (tự động khám phá qua K8s service discovery)
- job_name: nx-backend
kubernetes_sd_configs:
- role: pod
namespaces:
names: [nx-backend]
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_component]
regex: backend
action: keep
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
target_label: service
# PostgreSQL exporter
- job_name: postgresql
static_configs:
- targets: ['nx-pg-exporter.nx-persistent.svc.cluster.local:9187']
# Redis exporter
- job_name: redis
static_configs:
- targets: ['nx-redis-exporter.nx-broker.svc.cluster.local:9121']
# Kafka (JMX exporter)
- job_name: kafka
static_configs:
- targets:
- 'nx-kafka-0.nx-kafka-headless.nx-broker.svc.cluster.local:9404'
- 'nx-kafka-1.nx-kafka-headless.nx-broker.svc.cluster.local:9404'
- 'nx-kafka-2.nx-kafka-headless.nx-broker.svc.cluster.local:9404'
alerting:
alertmanagers:
- static_configs:
- targets: ['nx-alertmanager.nx-watcher.svc.cluster.local:9093']
rule_files:
- /etc/prometheus/alerts/*.ymlGrafana
Deployment
Deployment: nx-grafana
apiVersion: apps/v1
kind: Deployment
metadata:
name: nx-grafana
namespace: nx-watcher
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: grafana
template:
spec:
nodeSelector:
node.kubernetes.io/pool: default # staging: default, production: monitoring
# Chỉ Production:
# tolerations:
# - key: dedicated
# operator: Equal
# value: monitoring
# effect: NoSchedule
containers:
- name: grafana
image: grafana/grafana:11.5.2
ports:
- containerPort: 3000
env:
- name: GF_SECURITY_ADMIN_USER
valueFrom:
secretKeyRef:
name: grafana-secret
key: admin-user
- name: GF_SECURITY_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-secret
key: admin-password
- name: GF_USERS_ALLOW_SIGN_UP
value: "false"
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumeMounts:
- name: grafana-data
mountPath: /var/lib/grafana
- name: grafana-datasources
mountPath: /etc/grafana/provisioning/datasources
- name: grafana-dashboards-config
mountPath: /etc/grafana/provisioning/dashboards
- name: grafana-dashboards
mountPath: /var/lib/grafana/dashboards
volumes:
- name: grafana-data
persistentVolumeClaim:
claimName: grafana-data
- name: grafana-datasources
configMap:
name: grafana-datasources
- name: grafana-dashboards-config
configMap:
name: grafana-dashboards-config
- name: grafana-dashboards
configMap:
name: grafana-dashboardsNguồn Dữ liệu
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: nx-watcher
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://nx-prometheus.nx-watcher.svc.cluster.local:9090
isDefault: true
- name: Loki
type: loki
access: proxy
url: http://nx-loki.nx-watcher.svc.cluster.local:3100apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
namespace: nx-watcher
data:
datasources.yaml: |
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://nx-prometheus.nx-watcher.svc.cluster.local:9090
isDefault: true
- name: Loki
type: loki
access: proxy
url: http://nx-loki.nx-watcher.svc.cluster.local:3100
- name: Tempo
type: tempo
access: proxy
url: http://nx-tempo.nx-watcher.svc.cluster.local:3200
jsonData:
tracesToLogsV2:
datasourceUid: loki
filterByTraceID: true
tracesToMetrics:
datasourceUid: prometheus
serviceMap:
datasourceUid: prometheusDashboard
| Dashboard | Mô tả |
|---|---|
| Tổng quan Traefik | Tần suất request, độ trễ (p50/p95/p99), tỉ lệ lỗi, kết nối đang hoạt động |
| Dịch vụ Backend | CPU, bộ nhớ, số lượng request, thời gian phản hồi theo từng dịch vụ |
| PostgreSQL | Kết nối đang hoạt động, thời gian truy vấn, tỉ lệ cache hit, sử dụng đĩa |
| Redis | Sử dụng bộ nhớ, tỉ lệ hit/miss, client kết nối, tần suất lệnh |
| Kafka | Sức khỏe broker, partition thiếu bản sao, consumer lag, throughput |
| Tài nguyên Node | CPU, bộ nhớ, đĩa, mạng theo từng node |
| BullMQ Job | Độ sâu hàng đợi, tốc độ xử lý, job thất bại, số lần thử lại |
| Bản đồ Dịch vụ | (Chỉ Production) Tự động tạo từ trace Tempo |
Loki + Promtail
Tổng hợp log sử dụng Loki để lưu trữ và Promtail làm agent thu thập.
Deployment Loki
StatefulSet: nx-loki
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nx-loki
namespace: nx-watcher
spec:
serviceName: nx-loki
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: loki
template:
spec:
nodeSelector:
node.kubernetes.io/pool: default # staging: default, production: monitoring
# Chỉ Production:
# tolerations:
# - key: dedicated
# operator: Equal
# value: monitoring
# effect: NoSchedule
containers:
- name: loki
image: grafana/loki:3.4.2
args:
- -config.file=/etc/loki/loki.yaml
ports:
- containerPort: 3100
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumeMounts:
- name: loki-config
mountPath: /etc/loki
- name: loki-data
mountPath: /loki
volumes:
- name: loki-config
configMap:
name: loki-config
volumeClaimTemplates:
- metadata:
name: loki-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: csi-sc-vnpaycloud
resources:
requests:
storage: 5GiDaemonSet Promtail
Promtail chạy trên tất cả node (bao gồm node system, stateful, và monitoring trong production) để thu thập log container. Nó sử dụng toleration để lên lịch trên node có taint.
DaemonSet: promtail
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: promtail
namespace: nx-watcher
spec:
selector:
matchLabels:
app.kubernetes.io/name: promtail
template:
spec:
tolerations:
# Chấp nhận TẤT CẢ taint để Promtail chạy trên mọi node
- operator: Exists
containers:
- name: promtail
image: grafana/promtail:3.4.2
args:
- -config.file=/etc/promtail/promtail.yaml
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 100m
memory: 128Mi
volumeMounts:
- name: promtail-config
mountPath: /etc/promtail
- name: varlog
mountPath: /var/log
readOnly: true
- name: containers
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: promtail-config
configMap:
name: promtail-config
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/lib/docker/containersINFO
Mục tolerations: [{ operator: Exists }] có nghĩa Promtail chấp nhận tất cả taint, bao gồm dedicated=system:NoSchedule trên node system và dedicated=monitoring:NoSchedule trên node monitoring. Điều này đảm bảo log được thu thập từ mọi node trong cluster.
Cấu hình Promtail
ConfigMap: promtail-config
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: nx-watcher
data:
promtail.yaml: |
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://nx-loki.nx-watcher.svc.cluster.local:3100/loki/api/v1/push
scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
target_label: service
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
pipeline_stages:
- docker: {}Tempo (Chỉ Production)
Grafana Tempo lưu trữ distributed trace được gửi bởi OpenTelemetry Collector.
Deployment
StatefulSet: nx-tempo
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nx-tempo
namespace: nx-watcher
spec:
serviceName: nx-tempo
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: tempo
template:
spec:
nodeSelector:
node.kubernetes.io/pool: monitoring
tolerations:
- key: dedicated
operator: Equal
value: monitoring
effect: NoSchedule
containers:
- name: tempo
image: grafana/tempo:2.7.1
args:
- -config.file=/etc/tempo/tempo.yaml
ports:
- name: http
containerPort: 3200
- name: otlp-grpc
containerPort: 4317
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 500m
memory: 1Gi
volumeMounts:
- name: tempo-config
mountPath: /etc/tempo
- name: tempo-data
mountPath: /var/tempo
volumes:
- name: tempo-config
configMap:
name: tempo-config
volumeClaimTemplates:
- metadata:
name: tempo-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: csi-sc-vnpaycloud
resources:
requests:
storage: 10GiCấu hình Tempo
ConfigMap: tempo-config
apiVersion: v1
kind: ConfigMap
metadata:
name: tempo-config
namespace: nx-watcher
data:
tempo.yaml: |
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
storage:
trace:
backend: local
local:
path: /var/tempo/traces
wal:
path: /var/tempo/wal
compactor:
compaction:
block_retention: 72h
metrics_generator:
registry:
external_labels:
source: tempo
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://nx-prometheus.nx-watcher.svc.cluster.local:9090/api/v1/writeOpenTelemetry Collector (Chỉ Production)
OTel Collector nhận trace từ Traefik và dịch vụ backend qua OTLP gRPC và chuyển tiếp đến Tempo.
Deployment
Deployment: nx-otel-collector
apiVersion: apps/v1
kind: Deployment
metadata:
name: nx-otel-collector
namespace: nx-watcher
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: otel-collector
template:
spec:
nodeSelector:
node.kubernetes.io/pool: monitoring
tolerations:
- key: dedicated
operator: Equal
value: monitoring
effect: NoSchedule
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.118.0
args:
- --config=/etc/otel/otel-collector.yaml
ports:
- name: otlp-grpc
containerPort: 4317
- name: otlp-http
containerPort: 4318
- name: metrics
containerPort: 8888
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 300m
memory: 512Mi
volumeMounts:
- name: otel-config
mountPath: /etc/otel
volumes:
- name: otel-config
configMap:
name: otel-collector-configCấu hình OTel Collector
ConfigMap: otel-collector-config
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-collector-config
namespace: nx-watcher
data:
otel-collector.yaml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1024
memory_limiter:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 100
resource:
attributes:
- key: cluster
value: bana-production
action: upsert
exporters:
otlp/tempo:
endpoint: nx-tempo.nx-watcher.svc.cluster.local:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, resource]
exporters: [otlp/tempo]
telemetry:
metrics:
address: 0.0.0.0:8888Service OTel
Service: nx-otel-collector
apiVersion: v1
kind: Service
metadata:
name: nx-otel-collector
namespace: nx-watcher
spec:
selector:
app.kubernetes.io/name: otel-collector
ports:
- name: otlp-grpc
port: 4317
targetPort: 4317
- name: otlp-http
port: 4318
targetPort: 4318
- name: metrics
port: 8888
targetPort: 8888
type: ClusterIPNguồn Trace
Trace được gửi đến OTel Collector từ hai nguồn:
Traefik — cấu hình qua tham số CLI trong Traefik Deployment:
args:
- --tracing.otlp=true
- --tracing.otlp.grpc.endpoint=nx-otel-collector.nx-watcher.svc.cluster.local:4317
- --tracing.otlp.grpc.insecure=trueDịch vụ backend — cấu hình qua biến môi trường trong ConfigMap dùng chung:
OTEL_EXPORTER_OTLP_ENDPOINT: http://nx-otel-collector.nx-watcher.svc.cluster.local:4317
OTEL_SERVICE_NAME: banaMỗi dịch vụ IGNIS đọc OTEL_EXPORTER_OTLP_ENDPOINT và tự động instrument các HTTP request, truy vấn database, và lệnh Redis.
Quy tắc Cảnh báo
ConfigMap: prometheus-alerts
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-alerts
namespace: nx-watcher
data:
alerts.yml: |
groups:
- name: bana
rules:
# Pod liên tục crash
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total{namespace=~"nx-.*"}[15m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} đang liên tục crash"
# Tỉ lệ lỗi cao
- alert: HighErrorRate
expr: |
sum(rate(traefik_service_requests_total{code=~"5.."}[5m])) by (service)
/ sum(rate(traefik_service_requests_total[5m])) by (service) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Dịch vụ {{ $labels.service }} có tỉ lệ lỗi >5%"
# Kết nối PostgreSQL gần giới hạn
- alert: PostgreSQLHighConnections
expr: pg_stat_activity_count > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Kết nối PostgreSQL vượt 80"
# Sử dụng bộ nhớ Redis
- alert: RedisHighMemory
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Sử dụng bộ nhớ Redis vượt 90%"
# Kafka partition thiếu bản sao
- alert: KafkaUnderReplicated
expr: kafka_server_replicamanager_underreplicatedpartitions > 0
for: 10m
labels:
severity: critical
annotations:
summary: "Kafka có partition thiếu bản sao"
# Sử dụng đĩa
- alert: HighDiskUsage
expr: |
(kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} sử dụng vượt 85%"
# Dịch vụ identity ngừng hoạt động
- alert: IdentityServiceDown
expr: up{job="nx-backend", service="identity"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Dịch vụ identity ngừng hoạt động — mọi xác thực sẽ thất bại"