Vector: log pipeline from Kubernetes to Elasticsearch

Published: 2026-03-15

Fluentd and Logstash are the traditional log shippers. Vector is the modern alternative: written in Rust, significantly lower CPU and memory footprint, unified sources/transforms/sinks model, and a configuration format that's readable without consulting a wiki.


Architecture

Pods (stdout/stderr)
  → Vector DaemonSet (per node)
  → Transform (parse, enrich, filter)
  → Elasticsearch / Loki / S3

Vector runs as a DaemonSet. Each pod collects logs from container files on the same node. Transformations happen in the pipeline before forwarding.


Install via Helm

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: vector
  namespace: observability
spec:
  chart:
    spec:
      chart: vector
      version: "0.x"
      sourceRef:
        kind: HelmRepository
        name: vector
        namespace: flux-system
  values:
    role: Agent
    customConfig:
      data_dir: /vector-data-dir
      sources:
        kubernetes_logs:
          type: kubernetes_logs
          namespace_labels_field: namespace_labels
          node_metadata_fields: [node_ip]
      transforms:
        parse_json:
          type: remap
          inputs: [kubernetes_logs]
          source: |
            if is_string(.message) {
              parsed, err = parse_json(.message)
              if err == null {
                . = merge(., parsed)
              }
            }
        add_env_label:
          type: remap
          inputs: [parse_json]
          source: |
            .environment = "${VECTOR_ENV:-unknown}"
            .cluster = "${VECTOR_CLUSTER:-unknown}"
      sinks:
        elasticsearch:
          type: elasticsearch
          inputs: [add_env_label]
          endpoints:
            - http://elasticsearch.elk.svc.cluster.local:9200
          index: "k8s-%F"
          bulk:
            action: index
    env:
      - name: VECTOR_ENV
        value: "prod"
      - name: VECTOR_CLUSTER
        value: "infra"

VRL: Vector Remap Language

VRL is Vector's expression language for transforms. Purpose-built for log processing — no Lua, no Groovy, no JVM.

Parse JSON logs:

parsed, err = parse_json(.message)
if err == null {
  . = merge(., parsed)
}

If the log line is JSON, merge its fields into the event root. Structured logs from .NET (Microsoft.Extensions.Logging with JSON formatter) are handled automatically.

Drop noisy health check logs:

if starts_with(string!(.message), "GET /healthz") {
  abort
}

abort drops the event entirely. Health probes make up 30–40% of container logs in a busy cluster. Dropping them before shipping saves significant Elasticsearch storage.

Filter by namespace:

if !includes(["app", "production"], .kubernetes.namespace) {
  abort
}

Only ship logs from production namespaces; skip kube-system, monitoring, etc.

Redact sensitive fields:

if exists(.headers.authorization) {
  .headers.authorization = "REDACTED"
}

Redact token values at the pipeline level so you don't have to trust every app to sanitize its logs.


ServiceMonitor for Vector metrics

yamlvalues:
  serviceMonitor:
    enabled: true
    additionalLabels:
      release: kube-prom-stack

Useful metrics:

promql# Log processing rate
rate(vector_component_received_events_total[5m])

# Sink errors (failed deliveries to Elasticsearch)
rate(vector_component_errors_total{component_kind="sink"}[5m])

# Buffer size (backpressure)
vector_buffer_byte_size

Alert on sink errors:

yaml- alert: VectorSinkErrors
  expr: rate(vector_component_errors_total{component_kind="sink"}[5m]) > 0
  for: 5m
  labels:
    severity: warning
  annotations:
    description: Vector is failing to deliver logs to the sink.

Multiple sinks: Elasticsearch and S3

For long-term retention, all logs go to both Elasticsearch (hot, searchable) and S3 (cold, cheap):

yamlsinks:
  elasticsearch:
    type: elasticsearch
    inputs: [add_env_label]
    endpoints: [http://elasticsearch.elk.svc.cluster.local:9200]
    index: "k8s-%F"

  s3_archive:
    type: aws_s3
    inputs: [add_env_label]
    bucket: logs-archive-example
    region: ru-central1
    endpoint: https://storage.yandexcloud.net
    key_prefix: "k8s/%Y/%m/%d/"
    compression: gzip
    encoding:
      codec: ndjson
    auth:
      access_key_id: "${S3_ACCESS_KEY}"
      secret_access_key: "${S3_SECRET_KEY}"

S3 logs are gzip-compressed NDJSON. 1 GB/day of raw logs becomes ~120 MB.


Troubleshooting

Logs not appearing in Elasticsearch:

bash# Check Vector is running
kubectl get pods -n observability -l app.kubernetes.io/name=vector

# Check Vector logs for sink errors
kubectl logs -n observability daemonset/vector --tail=50

# Test the pipeline locally
vector test /etc/vector/vector.yaml

High memory usage: Vector buffers in memory by default. Add disk buffers for high-volume scenarios:

yamlsinks:
  elasticsearch:
    buffer:
      type: disk
      max_size: 268435488  # 256 MiB per node

Missing Kubernetes metadata: Ensure the ServiceAccount has permissions to read pods and namespaces. The Helm chart handles this by default.