Kubernetes Integration
Deploy the LogPulse agent to your Kubernetes cluster using a Helm chart. The agent runs as a DaemonSet with one OpenTelemetry Collector pod per node, automatically collecting container logs and enriching them with Kubernetes metadata — pod name, namespace, labels, node, and more. Logs are shipped to LogPulse via OTLP over HTTPS with gzip compression.
Overview
The LogPulse Kubernetes integration uses the OpenTelemetry Collector deployed as a DaemonSet. Each node runs a collector pod that reads container log files, enriches them with Kubernetes metadata via the K8s API, and ships them to LogPulse. The Helm chart handles all RBAC, service accounts, secrets, and configuration automatically.
Architecture
The LogPulse agent is deployed as a Kubernetes DaemonSet, ensuring one collector pod runs on every node in your cluster. Each pod reads container log files from the node's filesystem, parses the container log format, enriches logs with Kubernetes metadata from the API server, and exports them to LogPulse via OTLP HTTP.
The data flow follows these steps:
- Container runtime (containerd/Docker) writes stdout/stderr to /var/log/containers/*.log on the node
- The OTel Collector's filelog receiver reads new log lines and parses the container log format (timestamp, stream, log body)
- The k8sattributes processor queries the Kubernetes API to enrich each log with pod, namespace, deployment, node, and label metadata
- The resource processor adds the cluster name to all logs
- The batch processor groups logs (1000 per batch, 5s timeout) and exports via OTLP HTTP with gzip to LogPulse
Prerequisites
Before installing the LogPulse agent, ensure you have:
- Kubernetes 1.24 or later (any distribution: EKS, GKE, AKS, k3s, kind, minikube, etc.)
- Helm 3.8 or later installed on your workstation
- kubectl configured to access your target cluster
- A LogPulse API key (create one in Integrations → HTTP API in the dashboard)
- Cluster-admin permissions (to create ClusterRole and ClusterRoleBinding)
Helm Installation
The LogPulse agent Helm chart (logpulse-agent) deploys an OpenTelemetry Collector as a DaemonSet. It automatically creates the ServiceAccount, ClusterRole, ClusterRoleBinding, ConfigMap, and Secret required for operation.
Quick Start
Get up and running in under 2 minutes:
helm repo add logpulse https://charts.logpulse.io
helm repo updatekubectl create namespace logpulsehelm install logpulse-agent logpulse/logpulse-agent \
--namespace logpulse \
--set apiKey="lp_your_api_key_here" \
--set cluster.name="my-production-cluster"# Check DaemonSet status
kubectl get daemonset -n logpulse
# Check pods are running on each node
kubectl get pods -n logpulse -o wide
# View collector logs
kubectl logs -n logpulse -l app.kubernetes.io/name=logpulse-agent --tail=50Configuration Values
The Helm chart supports the following configuration values. Override them with --set flags or a custom values.yaml file.
| Parameter | Default | Description |
|---|---|---|
| apiKey | "" | Your LogPulse API key (required). Stored as a Kubernetes Secret. |
| cluster.name | "production" | Cluster name for identification. Appears in logs as the 'cluster' field. |
| endpoint | "https://api.logpulse.nl" | LogPulse API endpoint. Change for self-hosted deployments. |
| image.repository | "otel/opentelemetry-collector-contrib" | OpenTelemetry Collector container image. The contrib variant includes the k8sattributes processor. |
| image.tag | "0.96.0" | OTel Collector version. Tested with 0.96.0. |
| resources.limits.cpu | "200m" | Maximum CPU allocation per collector pod. |
| resources.limits.memory | "256Mi" | Maximum memory allocation per collector pod. |
| resources.requests.cpu | "50m" | Minimum guaranteed CPU per collector pod. |
| resources.requests.memory | "128Mi" | Minimum guaranteed memory per collector pod. |
| logs.excludeNamespaces | [kube-system, ...] | Namespaces to exclude from log collection. Default: kube-system, kube-public, kube-node-lease. |
| logs.includeNamespaces | [] | Only collect logs from these namespaces. Empty means all namespaces (minus excludes). |
| logs.containerLogPath | "/var/log/containers" | Path to container log files on each node. |
| logs.includePodLabels | true | Include pod labels as log metadata. Enables filtering by label in LPQL. |
| logs.includePodAnnotations | false | Include pod annotations as log metadata. Disabled by default to reduce data volume. |
| serviceAccount.create | true | Create a dedicated ServiceAccount for the agent. |
| healthCheck.port | 13133 | Port for the OTel Collector health check endpoint (liveness + readiness probes). |
Namespace Filtering
By default, the agent collects logs from all namespaces except kube-system, kube-public, and kube-node-lease. You can customize this with include and exclude lists.
helm install logpulse-agent logpulse/logpulse-agent \
--namespace logpulse \
--set apiKey="lp_your_key" \
--set logs.excludeNamespaces[0]=kube-system \
--set logs.excludeNamespaces[1]=kube-public \
--set logs.excludeNamespaces[2]=kube-node-lease \
--set logs.excludeNamespaces[3]=monitoring \
--set logs.excludeNamespaces[4]=logpulseTo collect only from specific namespaces, use includeNamespaces. When set, only these namespaces are collected and excludeNamespaces is ignored.
# Only collect logs from 'production' and 'staging' namespaces
helm install logpulse-agent logpulse/logpulse-agent \
--namespace logpulse \
--set apiKey="lp_your_key" \
--set logs.includeNamespaces[0]=production \
--set logs.includeNamespaces[1]=stagingKubernetes Metadata
The k8sattributes processor enriches each log event with Kubernetes metadata from the API server. These fields are stored as dedicated columns in the LogPulse database, optimized for fast filtering and RBAC-scoped queries.
Metadata Fields
The following Kubernetes metadata fields are automatically extracted and stored as first-class fields in LogPulse. They are queryable via LPQL without using the attributes map.
| Field | OTLP Attribute | Description |
|---|---|---|
| cluster | k8s.cluster.name | The cluster name (set via Helm values cluster.name) |
| namespace | k8s.namespace.name | Kubernetes namespace the pod runs in |
| pod | k8s.pod.name | Full pod name (e.g., api-server-7d4b8c9f6-x2k9m) |
| container | k8s.container.name | Container name within the pod |
| node | k8s.node.name | Node the pod is scheduled on |
| labels | k8s.pod.labels.* | Pod labels as key-value map (e.g., labels.app, labels.version) |
| annotations | k8s.pod.annotation.* | Pod annotations as key-value map (disabled by default) |
Labels & Annotations
Pod labels are included by default and stored as a Map(String, String). This lets you query by any label key-value pair. Pod annotations are disabled by default to reduce storage, but can be enabled in the Helm values.
logs:
includePodLabels: true # Enabled by default
includePodAnnotations: true # Disabled by default, enable if neededQuery logs by label in LPQL:
labels.app="payment-service" level="error"
| stats count by pod, containerOpenTelemetry Collector
The Helm chart deploys the OpenTelemetry Collector contrib image (otel/opentelemetry-collector-contrib) which includes the k8sattributes processor, filelog receiver, and all required components. The collector configuration is generated by the Helm chart and stored as a ConfigMap.
Pipeline Configuration
The OTel Collector pipeline is configured with a filelog receiver, k8sattributes processor, resource processor, batch processor, and OTLP HTTP exporter. Below is the full configuration generated by the Helm chart:
receivers:
filelog:
include:
- /var/log/containers/*.log
exclude:
- /var/log/containers/*_kube-system_*.log
- /var/log/containers/*_kube-public_*.log
- /var/log/containers/*_kube-node-lease_*.log
start_at: end
include_file_path: true
operators:
# Parse container log format: <timestamp> <stream> <flags> <log>
- type: regex_parser
id: container_parser
regex: '^(?P<time>[^ ]+) (?P<stream>stdout|stderr) (?P<logtag>[^ ]*) (?P<log>.*)$'
timestamp:
parse_from: attributes.time
layout: '%Y-%m-%dT%H:%M:%S.%fZ'
# Parse Kubernetes metadata from filename
- type: regex_parser
id: k8s_filename_parser
regex: '^.*\/(?P<pod_name>[^_]+)_(?P<namespace>[^_]+)_(?P<container_name>[^-]+)-(?P<container_id>[^.]+)\.log$'
parse_from: attributes["log.file.path"]
# Move parsed log to body
- type: move
from: attributes.log
to: body
processors:
k8sattributes:
auth_type: "serviceAccount"
passthrough: false
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.pod.uid
- k8s.deployment.name
- k8s.daemonset.name
- k8s.statefulset.name
- k8s.job.name
- k8s.cronjob.name
- k8s.node.name
- k8s.container.name
labels:
- tag_name: k8s.pod.labels.$$1
key_regex: (.*)
from: pod
pod_association:
- sources:
- from: resource_attribute
name: k8s.pod.name
- from: resource_attribute
name: k8s.namespace.name
resource:
attributes:
- key: k8s.cluster.name
value: "my-cluster"
action: upsert
batch:
timeout: 5s
send_batch_size: 1000
exporters:
otlphttp:
endpoint: https://api.logpulse.nl/v1/logs
headers:
Authorization: "Bearer ${env:LOGPULSE_API_KEY}"
compression: gzip
retry_on_failure:
enabled: true
initial_interval: 1s
max_interval: 30s
max_elapsed_time: 300s
extensions:
health_check:
endpoint: 0.0.0.0:13133
service:
extensions: [health_check]
pipelines:
logs:
receivers: [filelog]
processors: [k8sattributes, resource, batch]
exporters: [otlphttp]k8sattributes Processor
The k8sattributes processor is the key component that enriches log events with Kubernetes metadata. It authenticates with the K8s API server using the ServiceAccount token and watches for pod events to maintain an in-memory cache of pod metadata.
The following metadata is extracted from the Kubernetes API:
| Metadata | Description |
|---|---|
| k8s.namespace.name | The namespace the pod belongs to |
| k8s.pod.name | The full pod name |
| k8s.pod.uid | The unique pod identifier |
| k8s.deployment.name | The owning Deployment (if applicable) |
| k8s.daemonset.name | The owning DaemonSet (if applicable) |
| k8s.statefulset.name | The owning StatefulSet (if applicable) |
| k8s.job.name | The owning Job (if applicable) |
| k8s.cronjob.name | The owning CronJob (if applicable) |
| k8s.node.name | The node the pod is scheduled on |
| k8s.container.name | The container name within the pod |
RBAC Permissions
The Helm chart automatically creates a ClusterRole and ClusterRoleBinding with the minimum required permissions. The k8sattributes processor needs read-only access to Kubernetes resources to look up pod metadata.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: logpulse-agent
rules:
# Required for k8sattributes processor
- apiGroups: [""]
resources:
- pods
- namespaces
- nodes
verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
resources:
- replicasets
- deployments
- daemonsets
- statefulsets
verbs: ["get", "watch", "list"]
- apiGroups: ["batch"]
resources:
- jobs
- cronjobs
verbs: ["get", "watch", "list"]Each permission group serves a specific purpose:
| API Group | Resources | Verbs | Reason |
|---|---|---|---|
| "" (core) | pods, namespaces, nodes | get, watch, list | Look up pod metadata, namespace labels, and node information for log enrichment |
| apps | replicasets, deployments, daemonsets, statefulsets | get, watch, list | Resolve owning Deployment/DaemonSet/StatefulSet to add workload context to logs |
| batch | jobs, cronjobs | get, watch, list | Resolve owning Job/CronJob for batch workload logs |
DaemonSet Details
The DaemonSet ensures one collector pod runs on every eligible node. The pod mounts the node's container log directory as read-only and watches for new log lines. A config checksum annotation triggers automatic rolling restarts when the ConfigMap changes.
Volume Mounts
The collector pod mounts three volumes, all in read-only mode:
| Volume | Host Path | Mount Path | Description |
|---|---|---|---|
| config | (ConfigMap) | /etc/otel-collector | OTel Collector YAML configuration (generated by Helm chart) |
| varlog | /var/log | /var/log | Node log directory containing container log files and symlinks |
| varlibdockercontainers | /var/lib/docker/containers | /var/lib/docker/containers | Actual container log files (referenced by symlinks in /var/log/containers) |
Environment Variables
The DaemonSet injects these environment variables into each collector pod:
| Variable | Source | Description |
|---|---|---|
| LOGPULSE_API_KEY | Secret (api-key) | LogPulse API key injected from a Kubernetes Secret (never exposed in ConfigMap) |
| OTEL_K8S_NODE_NAME | fieldRef: spec.nodeName | Current node name from the Kubernetes downward API, used by k8sattributes |
| OTEL_K8S_POD_NAME | fieldRef: metadata.name | Collector pod name from the downward API |
| OTEL_K8S_NAMESPACE | fieldRef: metadata.namespace | Collector pod namespace from the downward API |
Health Checks
The OTel Collector exposes a health check endpoint used by Kubernetes liveness and readiness probes. If the health check fails, Kubernetes automatically restarts the pod.
| Probe | Path | Port | Timing |
|---|---|---|---|
| Liveness | / | 13133 | Initial delay: 10s, period: 10s — restarts pod if collector hangs |
| Readiness | / | 13133 | Initial delay: 5s, period: 5s — marks pod ready for service |
Security
The agent follows security best practices. The filesystem is read-only, the container is not privileged, and the API key is stored as a Kubernetes Secret. The pod runs as root (UID 0) only because it needs read access to /var/log/containers, which is owned by root on most distributions.
| Setting | Value | Description |
|---|---|---|
| runAsNonRoot | false | Set to false because /var/log access requires root on most distributions |
| runAsUser | 0 | Root user for file access (container log files are owned by root) |
| readOnlyRootFilesystem | true | Entire root filesystem is mounted read-only for defense in depth |
| privileged | false | Container runs unprivileged (no kernel capabilities) |
The API key is stored as a Kubernetes Secret and injected via environment variable. It never appears in the ConfigMap, pod spec, or collector logs.
apiVersion: v1
kind: Secret
metadata:
name: logpulse-agent-secret
type: Opaque
stringData:
api-key: "lp_your_api_key_here"Querying Kubernetes Logs
Once logs are flowing, you can query them using LPQL with Kubernetes-specific fields. The dedicated K8s columns (cluster, namespace, pod, container, node, labels) are indexed for fast filtering.
LPQL Examples
Here are common query patterns for Kubernetes log analysis:
namespace="production" level="error"namespace="production" pod="api-server-*" level="error"
| stats count by podlevel="error" cluster="production"
| stats count by namespace, pod
| sort count desc"OOMKilled" OR "CrashLoopBackOff" OR "Back-off restarting"
| stats count by namespace, pod, containerlabels.app="payment-service" namespace="production"
| timechart count by levelnode="ip-10-0-1-42" level="error"
| stats count by namespace, podlevel="error" last 1h
| stats count as errors by namespace, pod
| sort errors desc
| head 20Advanced Configuration
Customize the agent for your specific cluster requirements using a custom values file or --set overrides.
Custom Values File
For complex configurations, create a custom-values.yaml file. This gives you full control over all Helm chart parameters:
# LogPulse API key (required)
apiKey: "lp_your_api_key_here"
# Cluster identification
cluster:
name: "production-eu-west-1"
# Custom endpoint (self-hosted)
# endpoint: "https://your-logpulse-instance.internal"
# Collect from specific namespaces only
logs:
includeNamespaces:
- production
- staging
excludeNamespaces: []
includePodLabels: true
includePodAnnotations: true
# Resource tuning for high-volume clusters
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
# Run on specific nodes
nodeSelector:
node-role.kubernetes.io/worker: ""
# Service account annotations (e.g., for IRSA on EKS)
serviceAccount:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/logpulse-agent"helm install logpulse-agent logpulse/logpulse-agent \
--namespace logpulse \
--create-namespace \
-f custom-values.yamlResource Tuning
The default resource limits (200m CPU, 256Mi memory) work well for most clusters. Adjust based on your log volume and node count:
| Cluster Size | CPU Limit | Memory Limit | Notes |
|---|---|---|---|
| Small (< 10 nodes, < 1K events/sec) | 200m | 256Mi | Default settings, suitable for development and small production clusters |
| Medium (10-50 nodes, 1-10K events/sec) | 500m | 512Mi | Increase limits to handle higher throughput without backpressure |
| Large (50+ nodes, 10K+ events/sec) | 1000m | 1Gi | Maximum allocation for high-volume clusters with many pods per node |
Tolerations & Affinity
By default, the agent tolerates all taints (NoSchedule and NoExecute with operator: Exists) to ensure it runs on every node, including master/control-plane nodes. This ensures complete log coverage.
tolerations:
- operator: Exists
effect: NoSchedule
- operator: Exists
effect: NoExecuteTo restrict the agent to specific nodes (e.g., only worker nodes):
tolerations: []
nodeSelector:
node-role.kubernetes.io/worker: ""Vector Alternative
If you prefer Vector over the OpenTelemetry Collector, you can deploy Vector as a DaemonSet using its native kubernetes_logs source. Vector automatically discovers and collects container logs with Kubernetes metadata. Note that Vector sends logs via the HTTP ingest API instead of OTLP, so Kubernetes metadata fields are stored in the attributes map rather than dedicated columns.
sources:
k8s_logs:
type: kubernetes_logs
self_node_name: "${VECTOR_SELF_NODE_NAME}"
extra_label_selector: "app!=logpulse-agent"
extra_namespace_label_selector: "kubernetes.io/metadata.name!=kube-system"
transforms:
enrich:
type: remap
inputs:
- k8s_logs
source: |
.index = "kubernetes"
.source = string!(.kubernetes.pod_namespace) + "/" + string!(.kubernetes.pod_name)
.sourcetype = "kubernetes"
.fields.namespace = string!(.kubernetes.pod_namespace)
.fields.pod = string!(.kubernetes.pod_name)
.fields.container = string!(.kubernetes.container_name)
.fields.node = string!(.kubernetes.pod_node_name)
sinks:
logpulse:
type: http
inputs:
- enrich
uri: "https://api.logpulse.io/api/v1/ingest/vector"
method: post
encoding:
codec: json
auth:
strategy: bearer
token: "${LOGPULSE_API_KEY}"
batch:
max_bytes: 1048576
timeout_secs: 5Troubleshooting
Common issues and solutions when deploying the LogPulse agent on Kubernetes:
| Issue | Diagnosis | Solution |
|---|---|---|
| No logs appearing in LogPulse | kubectl logs shows the collector running, but no data in dashboard | Verify the API key is correct and the endpoint is reachable. Check for 401 errors in collector logs. Ensure the Secret exists: kubectl get secret -n logpulse |
| Logs from some namespaces missing | Some pods' logs appear but others don't | Check excludeNamespaces in your values. The namespace may be excluded. Verify the DaemonSet is running on the correct nodes: kubectl get pods -n logpulse -o wide |
| Logs arrive without K8s metadata | Logs show in LogPulse but namespace, pod, and labels are empty | Check RBAC permissions: kubectl auth can-i list pods --as=system:serviceaccount:logpulse:logpulse-agent --all-namespaces. The ClusterRole may not be bound correctly. |
| Collector pods OOMKilled | Pods restart with OOMKilled status | Increase memory limits: --set resources.limits.memory=512Mi. High-volume clusters may need 1Gi or more. Also check batch.send_batch_size isn't too large. |
| 401 Unauthorized errors in collector logs | Exporter logs show HTTP 401 errors | Verify the Secret contains a valid API key: kubectl get secret -n logpulse logpulse-agent-secret -o jsonpath='{.data.api-key}' | base64 -d. Ensure the key is active in the LogPulse dashboard. |
| Collector pod in CrashLoopBackOff | Pod keeps restarting | Check pod logs for config errors: kubectl logs -n logpulse -l app.kubernetes.io/name=logpulse-agent --previous. Common causes: invalid YAML in custom values, missing API key, or incorrect endpoint URL. |
# Check DaemonSet status
kubectl get ds -n logpulse
# View collector pod logs
kubectl logs -n logpulse -l app.kubernetes.io/name=logpulse-agent --tail=100
# Check pod events for errors
kubectl describe pod -n logpulse -l app.kubernetes.io/name=logpulse-agent
# Verify RBAC permissions
kubectl auth can-i list pods --as=system:serviceaccount:logpulse:logpulse-agent --all-namespaces
# Check Secret exists
kubectl get secret -n logpulse logpulse-agent-secret -o jsonpath='{.data.api-key}' | base64 -d
# View generated OTel config
kubectl get configmap -n logpulse logpulse-agent-config -o yamlUninstalling
To remove the LogPulse agent from your cluster, uninstall the Helm release. This removes the DaemonSet, ConfigMap, Secret, ServiceAccount, ClusterRole, and ClusterRoleBinding. Previously collected logs remain in LogPulse.
# Remove the Helm release
helm uninstall logpulse-agent --namespace logpulse
# Optionally remove the namespace
kubectl delete namespace logpulse
# Verify cleanup (ClusterRole/ClusterRoleBinding are cluster-scoped)
kubectl get clusterrole | grep logpulse
kubectl get clusterrolebinding | grep logpulse