Vấn đề thực tế: Gánh nặng tài nguyên từ logging trên Kubernetes
Năm ngoái, đội ngũ kỹ thuật của chúng tôi đã mở rộng cụm AWS EKS production từ 15 lên 85 worker node, phục vụ khoảng 420 microservice. Gần như chỉ sau một đêm, hóa đơn tài nguyên compute hàng tháng trên AWS đã tăng vọt thêm 3.400 USD. Tệ hơn nữa, cảnh báo quá tải node liên tục réo chuông trong các khung giờ cao điểm.
Kết quả profiling trên node đã chỉ đích danh thủ phạm: Fluentd. Mỗi agent tiêu tốn từ 350 MB đến 700 MB RAM và chiếm tới 1.4 CPU core trên mỗi node chỉ để tail stdout log, parse JSON và đẩy dữ liệu sang Elasticsearch. Trên các instance c6i.large (2 vCPU, 4 GB RAM), logging stack của chúng tôi đã chiếm hơn 60% lượng CPU khả dụng. Đó là mức đánh đổi tài nguyên hạ tầng không thể chấp nhận được cho một tác vụ đo kiểm nền (telemetry).
Mỗi khi lưu lượng truy cập tăng đột biến—chẳng hạn như khi upstream gateway ném ra 18.000 dòng log lỗi mỗi giây—collector chạm ngưỡng giới hạn bộ nhớ của Linux cgroup và sập vì OOMKilled (exit code 137). Dữ liệu đệm bị ứ đọng trên root volume, dung lượng ổ đĩa chạm mức 90%, và các kỹ sư on-call mất hoàn toàn khả năng quan sát đúng vào thời điểm sự cố nghiêm trọng diễn ra.
Phân tích nguyên nhân gốc rễ: Tại sao các collector truyền thống làm nghẽn node
Logging trên Kubernetes thoạt nhìn có vẻ khá đơn giản. Container runtime (containerd hoặc CRI-O) ghi các luồng stdout và stderr của container vào file trên host tại /var/log/pods/*/*/*.log. Một agent sẽ tail các file này, lấy metadata của pod từ API server, parse payload và chuyển tiếp dữ liệu đi.
Khi khối lượng ghi dữ liệu tăng cao, các logging engine truyền thống sẽ bộc lộ ba điểm yếu cốt lõi trong thiết kế:
- Tạm dừng do thu gom rác (Garbage Collection Pauses): Các agent được viết bằng Ruby hoặc chạy trên JVM phân bổ hàng triệu chuỗi buffer ngắn hạn trong quá trình parse từng dòng bằng regex. Khi tốc độ ingest vượt quá 10.000 dòng mỗi giây, các chu kỳ garbage collection làm nghẽn các luồng CPU và làm tắc nghẽn toàn bộ pipeline thu thập log.
- Gây quá tải API Server (API Server Saturation): Những agent chưa được tối ưu sẽ liên tục truy vấn Kubernetes API server cho từng container đang hoạt động để lấy pod label. Nếu không có cache cục bộ dùng chung, các lượt tra cứu metadata này sẽ làm quá tải cả tiến trình agent lẫn control plane kube-apiserver.
- Hàng đợi bộ nhớ không giới hạn (Unbounded Memory Queues): Khi các sink từ xa như OpenSearch hay S3 phản hồi chậm, agent sẽ giữ log lại trong bộ đệm RAM. Bộ đệm này phình to, vượt quá giới hạn bộ nhớ của container và gây ra vòng lặp crash loop dẫn đến mất mát dữ liệu.
Đánh giá các collector: Fluentd vs. Fluent Bit vs. Promtail vs. Vector
Trước khi chuẩn hóa hệ thống logging cho toàn cluster, chúng tôi đã tiến hành benchmark thử nghiệm trên một cụm Kubernetes độc lập gồm 3 node. Chúng tôi tạo ra 50.000 sự kiện log JSON mỗi giây (trung bình 512 byte mỗi sự kiện) để thử thách từng ứng viên:
| Collector | Runtime | RAM trung bình / Node | Cơ chế biến đổi (Transform Engine) | Thông lượng End-to-End |
|---|---|---|---|---|
| Fluentd | Ruby / C | 420 MB – 680 MB | Plugin Ruby & regex | 14.000 events/giây (nghẽn CPU) |
| Promtail | Go | 110 MB – 210 MB | Các stage trong pipeline | 26.000 events/giây (chỉ Loki) |
| Fluent Bit | C | 28 MB – 55 MB | Script Lua & parser C | 48.000 events/giây |
| Vector | Rust | 35 MB – 65 MB | Vector Remap Language (VRL) | 50.000+ events/giây |
Fluent Bit hoạt động khá tốt, nhưng logic định tuyến và các thao tác xử lý payload phức tạp của nó phụ thuộc rất lớn vào các script Lua tùy chỉnh. Những script này nhanh chóng trở nên mong manh và khó kiểm thử đồng bộ giữa các team phân tán của chúng tôi. Promtail tích hợp gọn gàng với Grafana Loki, nhưng lại không cung cấp giải pháp mượt mà nào để fan-out sang nhiều sink cùng lúc, ví dụ như vừa lưu trữ audit log tuân thủ lên Amazon S3 vừa gửi lỗi ứng dụng đến OpenSearch.
Vector mang lại sự cân bằng hoàn hảo mà chúng tôi cần. Được viết bằng Rust, Vector sở hữu hiệu năng native vượt trội và tính an toàn bộ nhớ mà không hề có độ trễ do GC. Ngôn ngữ tích hợp sẵn Vector Remap Language (VRL) cung cấp một DSL tường minh, an toàn về kiểu dữ liệu (type-safe) để parse, làm giàu (enrich) và làm sạch dữ liệu log ngay trong luồng xử lý.
Kiến trúc: Mô hình hai tầng (Two-Tier Topology)
Đừng chạy các tác vụ parse JSON phức tạp, làm mờ dữ liệu nhạy cảm bằng regex hoặc upload đa đám mây trực tiếp bên trong các DaemonSet ở tầng biên (edge). Thay vào đó, hãy tách hạ tầng logging của bạn thành hai tầng riêng biệt:
- Tầng 1 — Vector DaemonSet (Agent): Một collector siêu nhẹ được triển khai trên mỗi node. Nó mount thư mục
/var/log/pods, bổ sung thêm pod metadata cục bộ vào luồng log, hoàn toàn không thực hiện biến đổi phức tạp nào và chuyển tiếp log thô qua gRPC tới aggregator nội bộ. - Tầng 2 — Vector Deployment (Aggregator): Một nhóm pod Vector stateless chạy phía sau một Kubernetes Service kèm theo PodDisruptionBudget. Tầng aggregator này sẽ đảm nhận các tác vụ xử lý nặng: parse JSON, lọc bỏ số thẻ tín dụng và bearer token, loại bỏ các lượt health probe định kỳ và fan-out dữ liệu tới S3 cũng như Loki.
Thiết kế này giữ cho mức tiêu hao tài nguyên trên mỗi node luôn dưới 50 MB, trong khi Horizontal Pod Autoscaler cho phép cụm aggregator tự động mở rộng quy mô mượt mà trước các đợt tăng đột biến lưu lượng trên toàn cluster.
Bước 1: Triển khai Vector DaemonSet
Thêm Helm repository chính thức của Vector, sau đó tạo file cấu hình tối giản có tên values-daemonset.yaml:
role: "Agent"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
customConfig:
sources:
kubernetes_logs:
type: kubernetes_logs
auto_partial_merge: true
transforms:
add_cluster_meta:
type: remap
inputs:
- kubernetes_logs
source: |
.cluster = "production-us-east-1"
sinks:
to_aggregator:
type: vector
inputs:
- add_cluster_meta
address: "vector-aggregator.logging.svc.cluster.local:6000"
version: "2"
Triển khai DaemonSet vào cluster của bạn:
helm repo add vector https://helm.vectordotdev.com
helm repo update
helm upgrade --install vector-agent vector/vector \
--namespace logging \
--create-namespace \
-f values-daemonset.yaml
Bước 2: Triển khai Central Aggregator
Aggregator sẽ quản lý các tác vụ tiêu tốn nhiều CPU. Nó sử dụng VRL để parse các thông điệp JSON, loại bỏ các yêu cầu kiểm tra sức khỏe tự động (như /healthz và /ready), loại bỏ header Authorization và định tuyến các sự kiện audit đến S3 đồng thời gửi telemetry của ứng dụng tới Loki.
Tạo file cấu hình với tên values-aggregator.yaml:
role: "Stateless-Aggregator"
replicas: 3
podDisruptionBudget:
minAvailable: 2
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
service:
ports:
- name: vector-grpc
port: 6000
targetPort: 6000
protocol: TCP
customConfig:
sources:
from_agents:
type: vector
address: "0.0.0.0:6000"
version: "2"
transforms:
parse_and_clean:
type: remap
inputs:
- from_agents
source: |
# Parse payload JSON thành một map có cấu trúc
parsed, err = parse_json(.message)
if err == null {
.payload = parsed
del(.message)
}
# Bỏ qua các lượt probe readiness và liveness tự động
req_path = to_string(.payload.path) ?? ""
if contains(req_path, "/healthz") || contains(req_path, "/ready") {
abort
}
# Che thông tin xác thực nhạy cảm
if !is_null(.payload.authorization) {
.payload.authorization = "[REDACTED]"
}
route_logs:
type: route
inputs:
- parse_and_clean
route:
security_audit: '.kubernetes.pod_namespace == "kube-system"'
app_traffic: '.kubernetes.pod_namespace != "kube-system"'
sinks:
s3_cold_storage:
type: aws_s3
inputs:
- route_logs.security_audit
bucket: "company-prod-audit-logs"
region: "us-east-1"
encoding:
codec: json
batch:
max_bytes: 10485760
timeout_secs: 300
loki_apps:
type: loki
inputs:
- route_logs.app_traffic
endpoint: "http://loki-gateway.monitoring.svc.cluster.local"
encoding:
codec: json
labels:
namespace: "{{ kubernetes.pod_namespace }}"
pod: "{{ kubernetes.pod_name }}"
container: "{{ kubernetes.container_name }}"
Áp dụng cấu hình aggregator deployment vào namespace logging:
helm upgrade --install vector-aggregator vector/vector \
--namespace logging \
-f values-aggregator.yaml
Bước 3: Xác minh và kiểm tra trực tiếp
Xác nhận rằng các agent pod đang hoạt động trên tất cả các node và giao tiếp thông suốt với aggregator service:
# Kiểm tra trạng thái sẵn sàng của DaemonSet trên tất cả các node
kubectl get daemonset -n logging
# Kiểm tra log của aggregator để xem các luồng gRPC đang hoạt động
kubectl logs -n logging -l app.kubernetes.io/name=vector -c vector --tail=50
# Khởi chạy console top tích hợp sẵn của Vector
kubectl exec -it -n logging deploy/vector-aggregator -- vector top
Tiện ích vector top sẽ khởi chạy một console tương tác trực tiếp ngay trong terminal của bạn. Nó hiển thị số liệu throughput theo thời gian thực, thời gian xử lý ở cấp độ từng component và mức độ bão hòa buffer ở hạ nguồn, giúp bạn phát hiện sớm các vấn đề về backpressure trước khi chúng ảnh hưởng đến môi trường production.
Tổng kết quan trọng
Việc tách rời quá trình thu thập log khỏi khâu biến đổi dữ liệu sẽ giải quyết triệt để tình trạng cạn kiệt tài nguyên trên worker node. Bằng cách giới hạn DaemonSet ở tầng biên chỉ làm nhiệm vụ thu thập log đơn giản và chuyển toàn bộ việc parse sang một tầng aggregator chuyên biệt, chúng tôi đã giảm mức chiếm dụng RAM của logging từ 700 MB xuống còn 45 MB trên mỗi node. Sự thay đổi này đã giải phóng hàng chục CPU core trên toàn bộ cluster và chấm dứt vĩnh viễn các sự cố OOMKilled do logging gây ra.

