Triển khai Vector trên Kubernetes: Định tuyến log thông lượng cao với tài nguyên tối thiểu

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

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:

  1. 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ộ.
  2. 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.

Share: