Deploy
Deploy the Receiver app to Kubernetes via Helm.
The chart deploys your forwarder with the 10x Engine as a sidecar process. The workload kind (DaemonSet, Deployment, StatefulSet) and the exact integration shape are determined by the upstream chart for your forwarder. Pick yours below.
Step 1: Prerequisites
| Requirement | Description |
|---|---|
| Log10x License | Your license key (get one) |
| Helm | Helm CLI installed |
| kubectl | Configured to access your cluster |
| GitHub Token | Personal access token for config repo (create one) |
| Output Destination | Elasticsearch, Splunk, or other log backend configured |
Step 2: Add Helm Repository
Fluentd uses the upstream fluent/fluentd chart. The 10x sidecar is injected via a Helm post-renderer with a kustomize overlay.
Fluent Bit uses the upstream fluent/fluent-bit chart. The integration is a values overlay added to your existing Fluent Bit deployment.
Filebeat uses the upstream elastic/filebeat chart. The integration is an image swap: replace the chart's default docker.elastic.co/beats/filebeat image with the prebuilt log10x/filebeat-10x image, which runs Filebeat under a filebeat ... 2>&1 | tenx run ... pipe inside the container. The 10x engine runs as a child process of Filebeat's entrypoint, not as a separate container. The rest of the integration is a values overlay (image: + daemonset.filebeatConfig.filebeat.yml).
The OpenTelemetry Collector uses the upstream open-telemetry/opentelemetry-collector chart. The integration is a values overlay added to your existing Collector deployment.
Vector uses the upstream vector/vector chart. The integration is a values overlay added to your existing Vector deployment.
Logstash uses the upstream elastic/logstash chart. The integration is a values overlay added to your existing Logstash deployment.
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
View all chart values:
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Step 3: Configure Deployment Settings
Create a new file called my-receiver.yaml in your working directory. This Helm values file will be used in all subsequent steps. Pick your forwarder below; each tab is self-contained.
Fluentd's setup has two parts: a values file for the upstream chart, and a small kustomize overlay that injects the 10x sidecar. Both live in your GitOps repo and stay versioned together.
a. Values file. Standard upstream-chart options:
kind: Deployment # or DaemonSet for host-log tailing
replicaCount: 1
# Use the plain fluent/fluentd image (variant images that bundle the
# ES/Splunk/etc. plugins also work; `fluent-plugin-prometheus` is
# NOT required since the overlay replaces the default probes).
image:
repository: fluent/fluentd
tag: v1.18-debian-1
# No host log mounts in this example (the source below uses the
# `dummy` plugin). For real container-log tailing flip these to
# true and switch `kind` to DaemonSet.
mountVarLogDirectory: false
mountDockerContainersDirectory: false
rbac:
create: false
serviceAccount:
create: true
service:
enabled: false
podSecurityPolicy:
enabled: false
# Replace the chart's default config with the @INGEST / @OUTPUT
# routing pattern. `@INGEST` runs your enrichment filters once and
# forwards events to 10x on :24224; `@OUTPUT` receives processed
# events back on :24225 and writes them to your real destinations.
fileConfigs:
01_sources.conf: |-
# Replace this dummy source with your real sources (tail,
# http, k8s, …). Every source MUST route to `@INGEST`.
<source>
@type dummy
@id in_dummy
tag k8s.demo
rate 1
dummy {"log":"hello from fluentd dummy plugin","level":"info"}
auto_increment_key seq
@label @INGEST
</source>
02_filters.conf: |-
<label @INGEST>
# Enrichment filters go here: k8s_metadata, parsers,
# record_transformer, etc. They fire exactly once per event;
# the return path skips this label.
<filter **>
@type record_transformer
enable_ruby false
<record>
cluster "${ENV['CLUSTER_NAME'] || 'unset'}"
</record>
</filter>
# Hand off to the 10x sidecar. `keepalive true` is
# important for the sidecar topology; without it Fluentd
# opens a fresh TCP connection per chunk (the default is
# `keepalive false`), generating one connect/disconnect
# cycle per `flush_interval` on the loopback path.
# `require_ack_response false` is safe because both
# processes share the pod's network namespace; for
# cross-host forwarding flip it to `true` and pair with a
# file-backed buffer.
<match **>
@type forward
@id out_to_tenx
keepalive true
keepalive_timeout 60s
send_timeout 5s
require_ack_response false
<server>
host 127.0.0.1
port 24224
</server>
<buffer>
@type memory
flush_interval 1s
flush_thread_count 1
</buffer>
</match>
</label>
03_dispatch.conf: |-
# Forward source receiving processed events back from 10x.
# Bind on 0.0.0.0 so kubelet's tcpSocket probe can reach it
# from the pod IP. The sidecar still connects via 127.0.0.1.
<source>
@type forward
@id in_from_tenx
bind 0.0.0.0
port 24225
@label @OUTPUT
</source>
04_outputs.conf: |-
# `@OUTPUT` is the terminal label; replace `stdout` with your
# destinations (out_elasticsearch, out_splunk_hec, out_kafka2,
# out_s3, …). Keep this label filter-free.
<label @OUTPUT>
<match **>
@type stdout
@id out_stdout
</match>
</label>
b. Kustomize overlay. Four files in a tenx-kustomize/ directory next to your values file:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- helm-output.yaml # populated by post-render.sh at install time
patches:
- path: sidecar-patch.yaml
# The chart names the Deployment `<release>-fluentd`; the
# examples below use `my-receiver` as the release name (Step 7).
target: { kind: Deployment, name: my-receiver-fluentd }
# Adds the log10x sidecar container to the Fluentd pod. Kubernetes
# restarts the container automatically if the 10x process exits.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-receiver-fluentd # match your kustomization.yaml target
spec:
template:
spec:
containers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/fluentd"
- "@apps/receiver"
# ─── Actions ────────────────────────────────────────
# The Receiver applies a per-pattern action (pass,
# sample, compact, tier_down, offload, drop). An agent
# picks the action per service via the log10x MCP
# `configure_engine` tool, which writes a cap/action CSV
# the engine hot-reloads through the GitOps repo. The
# args here set the engine-wide default before any CSV.
#
# Default (no extra args): pass: events go through
# unchanged.
#
# Add `receiverOptimize true` to default to the compact
# action: the `log` field's value is replaced with a
# losslessly-encoded form and a separate `tenx-template`
# event is emitted per pattern.
#
# Add `symbolMessageHashField <name>` to also surface
# the symbol-pattern hash as a top-level field on each
# event (useful as a dedup key, metric dimension, or
# correlation ID).
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
volumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
#!/usr/bin/env bash
# Bridges Helm's stdin/stdout post-renderer protocol to kustomize's
# file-based model. Captures Helm's rendered manifests, then runs
# `kubectl kustomize` to apply the patches.
set -euo pipefail
DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cat > "$DIR/helm-output.yaml"
exec kubectl kustomize "$DIR"
@echo off
REM Windows shim. Helm.exe can't directly exec a .sh on Windows.
REM Use this path as `--post-renderer` on Windows; Linux/macOS use
REM the .sh directly.
bash "%~dp0post-render.sh"
Make the shell script executable: chmod +x tenx-kustomize/post-render.sh.
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to the log10x container's env: block in tenx-kustomize/sidecar-patch.yaml:
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. Fluentd's output destinations are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
The Fluent Bit integration is a values overlay (extraContainers + config: replacement) layered on top of your existing Fluent Bit values via helm -f. The 10x sidecar and Fluent Bit run as peer containers in the same pod and talk over loopback TCP.
Fluent Bit routes purely on tag, so the bypass (what keeps Fluent Bit's filters and the ingest [OUTPUT] forward from re-firing on events coming back from log10x) is tag-prefix namespacing: the egress [INPUT] forward uses Tag_Prefix tenx. to prepend tenx. to every returning tag. Filters and the ingest [OUTPUT] forward then Match app.* (or whatever your source-tag scheme is) and skip the returning tenx.* events.
# Layer this on top of your existing Fluent Bit values:
# helm upgrade --install my-receiver fluent/fluent-bit \
# -f your-existing-fluent-bit-values.yaml \
# -f my-receiver.yaml \
# --namespace logging
# 10x sidecar container in the same pod as `fluent-bit`. They share
# the pod's network namespace, so 127.0.0.1:24224 / :24225 are
# reachable between containers without any shared volume.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/fluentbit"
- "@apps/receiver"
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` tool as a cap/action CSV the engine
# hot-reloads through GitOps. These args set the engine-wide
# default before any CSV.
#
# Default (no extra args): pass: events go through unchanged.
#
# Add `receiverOptimize true` to default to the compact action: the
# `log` field's value is replaced with a losslessly-encoded
# form and a separate `tag: tenx-template` event is emitted
# per first-seen pattern.
#
# Add `symbolMessageHashField <name>` to also surface the
# symbol-pattern hash as a top-level field on each event
# (useful as a dedup key, metric dimension, or correlation ID).
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
# Replace the chart's default config with the sidecar pattern.
# `inputs`/`filters`/`outputs` are inline strings that the chart
# mounts into /fluent-bit/etc/conf/. Keep `[SERVICE] HTTP_Server On`
# so the chart's default health probe (httpGet :2020) keeps working.
config:
service: |
[SERVICE]
Daemon Off
Flush {{ .Values.flush }}
Log_Level {{ .Values.logLevel }}
Parsers_File /fluent-bit/etc/parsers.conf
Parsers_File /fluent-bit/etc/conf/custom_parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port {{ .Values.metricsPort }}
Health_Check On
# Replace these with your real sources (tail, http, systemd, …).
# Tag with ANY scheme that does not start with `tenx.`; the
# egress input below prepends `tenx.` to returning tags, and the
# ingest `forward` output below matches on the non-`tenx.*` tag.
inputs: |
[INPUT]
Name tail
Path /var/log/containers/*.log
multiline.parser docker, cri
Tag kube.*
Mem_Buf_Limit 5MB
Skip_Long_Lines On
# Egress: receive processed events back from 10x. `Tag_Prefix
# tenx.` is the bypass; every returning event gets `tenx.`
# prepended to its original tag, so filters and the ingest
# `forward` output (which Match `kube.*`) skip it.
#
# Listen on 0.0.0.0 so kubelet's httpGet probe (on :2020) and
# the 10x sidecar (on 127.0.0.1:24225) can both reach it.
[INPUT]
Name forward
Listen 0.0.0.0
Port 24225
Tag_Prefix tenx.
# Enrichment filters Match the user-tag scheme only (NOT `*`) so
# they don't re-fire on returning `tenx.*` events.
filters: |
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude On
outputs: |
# Hand off to the 10x sidecar.
[OUTPUT]
Name forward
Match kube.*
Host 127.0.0.1
Port 24224
Retry_Limit False
# Destinations Match the `tenx.*` namespace; replace this stdout
# block with your real destinations (es, splunk, kafka, s3, …).
# The `tenx.` prefix on the wire is what keeps enrichment from
# re-firing and the ingest `forward` output from looping events.
[OUTPUT]
Name stdout
Match tenx.*
Format json_lines
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to the log10x container's env: block under extraContainers in my-receiver.yaml:
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. Fluent Bit's output destinations are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
The Filebeat integration is a values overlay applied to the upstream elastic/filebeat chart. The integration is an image swap: the chart's default Filebeat image is replaced with the prebuilt log10x/filebeat-10x image, which wraps Filebeat's entrypoint in filebeat ... 2>&1 | tenx run .... The 10x engine runs as a child process of Filebeat's entrypoint inside the same container; there is no separate sidecar container.
Every filebeat.input MUST carry the tenx-receive.js script processor: it tags the event, writes it to Filebeat's stdout (which the in-container engine reads), and cancels it locally. Processed events come back to Filebeat through a unix input loaded via filebeat.config.inputs from a snippet bundled with the image. output.console is not supported: it would write to the same stdout pipe that carries events to the engine and corrupt the stream.
# Layer this on top of any existing Filebeat values:
# helm upgrade --install my-receiver elastic/filebeat \
# -f your-existing-filebeat-values.yaml \
# -f my-receiver.yaml \
# --namespace logging
# Image swap: the log10x-built Filebeat image runs the engine as a
# child of Filebeat's entrypoint (filebeat -e 2>&1 | tenx run ...).
image: "log10x/filebeat-10x"
imageTag: "latest" # pin to a release tag in production
imagePullPolicy: IfNotPresent
daemonset:
extraEnvs:
# Point the engine at the mounted license file (Step 5).
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` tool as a cap/action CSV the engine
# hot-reloads through GitOps. TENX_RUN_ARGS sets the engine-wide
# default before any CSV.
#
# Default (no override): pass: events go through unchanged.
#
# Override TENX_RUN_ARGS to change the default action:
# - Add `receiverOptimize true` to default to the compact
# action: the value of the `message` field is replaced with a
# losslessly-encoded form and a separate `tenx-template`
# event is emitted per first-seen pattern.
# - Add `symbolMessageHashField <name>` to also emit the
# symbol-pattern hash as a top-level field (useful as a
# dedup key, metric dimension, or correlation ID).
#
# - name: TENX_RUN_ARGS
# value: "@run/input/forwarder/filebeat @apps/receiver receiverOptimize true"
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
extraVolumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
# `filebeat.config.inputs` loads the unix-socket input that
# receives processed events back from the engine. Every entry in
# `filebeat.inputs` must carry the tenx-receive.js script
# processor; without it the event never reaches the engine.
filebeatConfig:
filebeat.yml: |
filebeat.config.inputs:
enabled: true
path: ${TENX_MODULES}/pipelines/run/modules/input/forwarder/filebeat/conf/tenxNix.yml
filebeat.inputs:
- type: container
paths:
- /var/log/containers/*.log
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
# Hand every event off to 10x via Filebeat's stdout.
# tenx-receive.js cancels the event locally so destinations
# only ship the version that comes back on the unix socket.
- script:
lang: javascript
file: ${TENX_MODULES}/pipelines/run/modules/input/forwarder/filebeat/script/tenx-receive.js
# Use any non-stdout output. output.console must NOT be used:
# it would collide with the stdout pipe carrying events to
# the engine. See Step 6 for filling in your destination.
output.elasticsearch:
hosts: '["https://elasticsearch-master:9200"]'
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to daemonset.extraEnvs in my-receiver.yaml:
daemonset:
extraEnvs:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. Filebeat's output destinations are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
Layer this additive overlay on top of your existing Collector values: the chart's config: is deep-merged, so your existing receivers/processors/exporters stay; the overlay adds the sidecar container, the otlp/tenx exporter, the otlp/tenx receiver for the return path, and rewires your logs: pipeline through 10x. The 10x sidecar and the Collector run as peer containers in the same pod and talk over loopback TCP. Both directions are OTLP/gRPC, so the core otel/opentelemetry-collector image is sufficient. The OTLP wire carries the full record (resource attributes, scope info, log-record attributes, body) so k8s metadata round-trips back to your destinations.
# Layer this on top of your existing Collector values:
# helm upgrade --install my-receiver open-telemetry/opentelemetry-collector \
# -f your-existing-otel-values.yaml \
# -f my-receiver.yaml \
# --namespace logging
# 10x sidecar in the same pod as the Collector. Both containers
# share the pod network, so they reach each other over 127.0.0.1.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/otel-collector"
- "@apps/receiver"
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` tool as a cap/action CSV the engine
# hot-reloads through GitOps. These args set the engine-wide
# default before any CSV.
#
# Default (no extra args): pass: the record goes through
# unchanged.
#
# Add `receiverOptimize true` to default to the compact action: the
# message field is replaced with a losslessly-encoded form
# and a separate `tag: tenx-template` event is emitted per
# first-seen pattern.
#
# Add `symbolMessageHashField <name>` to also emit the
# symbol-pattern hash as a top-level field (surfaces as an
# attribute on the returning OTel log record, useful as a
# dedup key, metric dimension, or correlation ID).
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
# `config:` is deep-merged with the chart defaults and with the
# `config:` block in your existing values file. The overlay adds
# the new receiver/exporter, rewires your `logs` pipeline through
# `otlp/tenx`, and appends a `logs/from-tenx` pipeline for the
# return path.
config:
receivers:
# Receive processed events back from the 10x sidecar over
# OTLP/gRPC. Bind on 0.0.0.0 so the port is reachable from the
# pod IP (the sidecar still connects via 127.0.0.1).
otlp/tenx:
protocols:
grpc:
endpoint: 0.0.0.0:24225
exporters:
# Hand off to the 10x sidecar over OTLP/gRPC.
otlp/tenx:
endpoint: 127.0.0.1:4317
tls:
insecure: true
service:
pipelines:
# Replace your existing `logs` pipeline. Spell out every
# receiver/processor/exporter; Helm replaces these lists
# wholesale on merge.
logs:
receivers: [filelog] # ← your existing receivers
processors:
- memory_limiter
- batch
exporters: [otlp/tenx]
# New egress pipeline. Keep it processor-free so enrichment
# runs exactly once.
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [elasticsearch] # ← your existing destinations
# Optional: drop the chart-default `metrics` and `traces`
# pipelines (which fan chart-internal data into the `debug`
# exporter) by setting them to null.
# metrics: null
# traces: null
# Probes use the chart defaults (httpGet :13133/ on the
# health_check extension). The chart's values schema only allows
# httpGet probes, so leave these untouched.
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to the log10x container's env: block under extraContainers in my-receiver.yaml:
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. The Collector's exporters are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
The Vector integration is a values overlay (extraContainers + customConfig) layered on top of your existing Vector values via helm -f. The 10x sidecar and Vector run as peer containers in the same pod and talk over loopback TCP.
# Layer this on top of your existing Vector values:
# helm upgrade --install my-receiver vector/vector \
# -f your-existing-vector-values.yaml \
# -f my-receiver.yaml \
# --namespace logging
# 10x sidecar container in the same pod as `vector`. They share the
# pod's network namespace, so 127.0.0.1:9000 / :9001 are reachable
# between containers without any shared volume.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/vector"
- "@apps/receiver"
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` tool as a cap/action CSV the engine
# hot-reloads through GitOps. These args set the engine-wide
# default before any CSV.
#
# Default (no extra args): pass: events go through unchanged.
#
# Add `receiverOptimize true` to default to the compact action: the
# `message` field's value is replaced with a losslessly-encoded
# form and a separate `tag: tenx-template` event is emitted
# per first-seen pattern.
#
# Add `symbolMessageHashField <name>` to also surface the
# symbol-pattern hash as a top-level field on each event
# (useful as a dedup key, metric dimension, or correlation ID).
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
# The vector chart auto-publishes container ports from each entry
# in `customConfig.sources`, so the 9001 port for `tenx_out` is
# already declared on the `vector` container and reachable from
# the pod IP. The 10x sidecar connects out to it on 127.0.0.1, no
# port declaration of its own needed.
# Replace the chart's default config with the sidecar pattern.
# Sources route to the `ingest` transform; sinks consume `tenx_out`
# so enrichment runs exactly once. The to-tenx and from-tenx legs
# are structurally disconnected; Vector's DAG IS the bypass.
customConfig:
data_dir: /vector-data-dir
sources:
# Replace this with your real sources (file, kubernetes_logs,
# journald, http_server, ...). All sources MUST feed the
# `ingest` transform; anything wired directly to a destination
# sink skips 10x.
app_logs:
type: kubernetes_logs
# Receive processed events back from the 10x sidecar.
# Bind on 0.0.0.0 so kubelet's tcpSocket probe can reach the
# port from the pod IP. The 10x sidecar still connects via
# 127.0.0.1 (same pod, same network namespace).
tenx_out:
type: fluent
mode: tcp
address: 0.0.0.0:9001
transforms:
# Enrichment runs here exactly once before handoff to 10x.
# The return path skips this transform via the DAG wiring below.
ingest:
type: remap
inputs: [app_logs]
source: |
.cluster = get_env_var("CLUSTER_NAME") ?? "unset"
.tag = .source_type
sinks:
# Hand off to the 10x sidecar.
tenx_in:
type: socket
inputs: [ingest]
mode: tcp
address: 127.0.0.1:9000
encoding: { codec: json }
framing: { method: newline_delimited }
# Destinations consume `tenx_out` ONLY (never the raw sources
# or `ingest`). That structural wiring is the enrichment bypass.
destinations:
type: elasticsearch # or splunk_hec, kafka, aws_s3, ...
inputs: [tenx_out]
# ... destination-specific options ...
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to the log10x container's env: block under extraContainers in my-receiver.yaml:
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. Vector's destinations are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
The Logstash integration is a values overlay (extraContainers + logstashConfig + logstashPipeline) layered on top of the elastic/logstash chart. The 10x sidecar and Logstash run as peer containers in the same pod and talk over loopback TCP.
# 10x sidecar container in the same pod as `logstash`. They share
# the pod's network namespace, so 127.0.0.1:5044 / :5045 are
# reachable between containers without any shared volume.
extraContainers: |
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/logstash"
- "@apps/receiver"
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` tool as a cap/action CSV the engine
# hot-reloads through GitOps. These args set the engine-wide
# default before any CSV.
#
# Default (no extra args): pass: events go through unchanged.
#
# Add `receiverOptimize true` to default to the compact action: the
# `message` field's value is replaced with a losslessly-encoded
# form and a separate `tag: tenx-template` event is emitted
# per first-seen pattern.
#
# Add `symbolMessageHashField <name>` to also surface the
# symbol-pattern hash as a top-level field on each event
# (useful as a dedup key, metric dimension, or correlation ID).
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
secretMounts:
- name: tenx-license
secretName: receiver-credentials
path: /etc/tenx/license/license.jwt
subPath: license-jwt
# Multi-pipeline driver: Logstash runs the `ingest` and
# `destinations` pipelines side-by-side. Filters in `ingest`
# only see events on the way in; the `destinations` pipeline
# is filter-free so each event is enriched exactly once.
logstashConfig:
pipelines.yml: |
- pipeline.id: ingest
path.config: "/usr/share/logstash/pipeline/tenx-ingest.conf"
- pipeline.id: destinations
path.config: "/usr/share/logstash/pipeline/tenx-destinations.conf"
# Pipeline configs. Replace the `file` input below with your real
# sources; replace the destinations pipeline's `stdout` output
# with your real destinations.
logstashPipeline:
tenx-ingest.conf: |
input {
# Replace with your real Logstash inputs (file, beats, http, ...).
file {
path => "/var/log/containers/*.log"
codec => "json"
start_position => "beginning"
}
}
filter {
# Enrichment filters run once per event. The `tag` field
# becomes the event's source inside 10x. Set it to any
# value that identifies the stream.
mutate {
add_field => { "cluster" => "${CLUSTER_NAME:unset}" }
add_field => { "tag" => "k8s.containers" }
}
}
output {
# Hand off to the 10x sidecar.
tcp {
host => "127.0.0.1"
port => 5044
codec => json_lines
}
}
tenx-destinations.conf: |
input {
# Bind on 0.0.0.0 so the port is reachable from outside
# loopback (kubelet probe). The 10x sidecar connects via
# 127.0.0.1 from inside the same pod.
tcp {
host => "0.0.0.0"
port => 5045
codec => json_lines
}
}
output {
# Replace `stdout` with your real destinations (elasticsearch,
# opensearch, splunk_hec, kafka, s3, ...). Keep this pipeline
# filter-free; enrichment lives in `tenx-ingest.conf`.
stdout { codec => json_lines }
}
Airgapped mode (optional). For clusters with no path to the Log10x SaaS (security policy, isolated network), append TENX_AIRGAPPED=true to the log10x container's env: block under extraContainers in my-receiver.yaml:
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_AIRGAPPED # ← add this
value: "true" # ← add this
The engine then verifies the license JWT locally against the embedded public key and makes zero outbound calls to the Log10x gateway. Logstash's outputs are unaffected. demo and limited license types cannot run airgapped; the engine warns and falls back to online.
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Step 4: Load Configuration
Replace the config folder baked into the Log10x image with your own, either by cloning a git repository into an in-pod emptyDir via an init container, or by mounting a PersistentVolumeClaim that already holds the config. Both methods plug the config into the container running the 10x process; the exact chart hooks differ per forwarder.
If you skip this step, the default configuration bundled with the Log10x image is used.
- Fork the Config Repository (git path only) or pre-populate a PVC with the config (PVC path)
- Edit the app configuration to match your metric output and enrichment options
- Pick your forwarder and method below:
Fluentd's 10x sidecar lives in tenx-kustomize/sidecar-patch.yaml from Step 3 (the chart is wrapped in a Helm post-renderer with a kustomize overlay). Both methods are additions to that same patch file; the chart's values overlay isn't involved.
Git Repository. Clone a config repo into a shared emptyDir before the sidecar starts.
Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then replace tenx-kustomize/sidecar-patch.yaml with the version below; the ← add markers show what's new relative to Step 3:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-receiver-fluentd
spec:
template:
spec:
# ← add: init container clones the repo into /data; the
# emptyDir below makes /data === /etc/tenx/git in the sidecar.
initContainers:
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional, omit for repo default
- "my-receiver-config"
# If you also publish a symbol library, add:
# - "--symbols-repo"
# - "https://github.com/YOUR-ACCOUNT/symbols.git"
# - "--symbols-branch" # Optional
# - "main"
volumeMounts:
- name: tenx-git
mountPath: /data
containers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/fluentd"
- "@apps/receiver"
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
# If you fetched a symbols repo too, also add:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/git/config/data/shared/symbols
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
volumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-git # ← add
emptyDir: {}
Persistent Volume. Mount an existing PVC. No init container, no git token, no external network access.
Replace tenx-kustomize/sidecar-patch.yaml from Step 3 with this version (again, ← add marks what's new):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-receiver-fluentd
spec:
template:
spec:
containers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/fluentd"
- "@apps/receiver"
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
# If your PVC also carries a symbol library, also add:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/symbols
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { cpu: 500m, memory: 512Mi }
volumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-config-volume # ← add
persistentVolumeClaim:
claimName: my-config-pvc
The 10x process runs in the log10x sidecar from Step 3 (added via the chart's extraContainers:). The env + volumeMounts additions go inside that sidecar's spec; the pod-level volume goes under the chart's extraVolumes:; the init container (git path only) goes under the chart's top-level initContainers:.
Git Repository. Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then append to your my-receiver.yaml from Step 3. The ← add markers show the four surgical changes:
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
# If you also fetched a symbols repo:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/git/config/data/shared/symbols
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-git # ← add
emptyDir: {}
# Top-level `initContainers:` hook in fluent/fluent-bit, runs once
# per pod start and clones the repo into the shared emptyDir.
initContainers: # ← add
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional
- "my-receiver-config"
# If publishing a symbols repo, also add:
# - "--symbols-repo"
# - "https://github.com/YOUR-ACCOUNT/symbols.git"
# - "--symbols-branch" # Optional
# - "main"
volumeMounts:
- name: tenx-git
mountPath: /data
Persistent Volume. Same shape, minus the init container and the git token. The PVC just gets mounted into the sidecar.
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
# If the PVC also carries a symbol library:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/symbols
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-config-volume # ← add
persistentVolumeClaim:
claimName: my-config-pvc
Filebeat runs the 10x engine inside its only container (image swap). All hooks live under daemonset: in the elastic/filebeat chart, same scope as the license wiring from Step 3, except extraInitContainers:, which is at the chart root in this chart.
Git Repository. Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then append to your my-receiver.yaml from Step 3:
daemonset:
extraEnvs:
- name: TENX_LICENSE_FILE # already from Step 3
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
# If you also fetched a symbols repo:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/git/config/data/shared/symbols
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-git # ← add
emptyDir: {}
extraVolumeMounts:
- name: tenx-license # already from Step 3
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
# `extraInitContainers:` is at the chart root, NOT under daemonset:.
extraInitContainers: # ← add
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional
- "my-receiver-config"
# If publishing a symbols repo, also add:
# - "--symbols-repo"
# - "https://github.com/YOUR-ACCOUNT/symbols.git"
# - "--symbols-branch" # Optional
# - "main"
volumeMounts:
- name: tenx-git
mountPath: /data
Persistent Volume. Append to your my-receiver.yaml from Step 3:
daemonset:
extraEnvs:
- name: TENX_LICENSE_FILE # already from Step 3
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
# If the PVC also carries a symbol library:
# - name: TENX_SYMBOLS_PATH
# value: /etc/tenx/symbols
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-config-volume # ← add
persistentVolumeClaim:
claimName: my-config-pvc
extraVolumeMounts:
- name: tenx-license # already from Step 3
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
The 10x process runs in the log10x sidecar from Step 3 (added via the chart's extraContainers:). The env + volumeMounts additions go inside that sidecar's spec; the pod-level volume goes under the chart's extraVolumes:; the init container (git path only) goes under the chart's top-level initContainers:.
Git Repository. Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then append:
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-git # ← add
emptyDir: {}
initContainers: # ← add
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional
- "my-receiver-config"
volumeMounts:
- name: tenx-git
mountPath: /data
Persistent Volume:
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-config-volume # ← add
persistentVolumeClaim:
claimName: my-config-pvc
The 10x process runs in the log10x sidecar from Step 3 (added via the chart's extraContainers:). The env + volumeMounts additions go inside that sidecar's spec; the pod-level volume goes under the chart's extraVolumes:; the init container (git path only) goes under the chart's top-level initContainers:.
Git Repository. Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then append:
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-git # ← add
emptyDir: {}
initContainers: # ← add
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional
- "my-receiver-config"
volumeMounts:
- name: tenx-git
mountPath: /data
Persistent Volume:
extraContainers:
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
extraVolumes:
- name: tenx-license # already from Step 3
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
- name: tenx-config-volume # ← add
persistentVolumeClaim:
claimName: my-config-pvc
The 10x process runs in the log10x sidecar from Step 3 (added via the chart's extraContainers: |, string form, tpl'd by the chart). The env + volumeMounts additions go inside that sidecar's spec; the pod-level volume goes under the chart's extraVolumes:; the init container (git path only) goes under the chart's extraInitContainers: hook.
Git Repository. Add the git-token key to your Step 5 Secret (--from-literal=git-token=YOUR-GIT-TOKEN), then append:
extraContainers: |
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/git/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-git # ← add
mountPath: /etc/tenx/git
readOnly: true
# `secretMounts:` from Step 3 still mounts the license. `extraVolumes:`
# is a separate hook in this chart and is where the new emptyDir goes.
extraVolumes: # ← add
- name: tenx-git
emptyDir: {}
# Note the elastic-chart naming: `extraInitContainers:`, not
# `initContainers:`.
extraInitContainers: | # ← add
- name: tenx-git-config
image: log10x/git-config-fetcher:1.0.0
env:
- name: GIT_TOKEN
valueFrom:
secretKeyRef:
name: receiver-credentials
key: git-token
args:
- "--config-repo"
- "https://github.com/YOUR-ACCOUNT/config.git"
- "--config-branch" # Optional
- "my-receiver-config"
volumeMounts:
- name: tenx-git
mountPath: /data
Persistent Volume:
extraContainers: |
- name: log10x
# ... rest of the spec unchanged from Step 3 ...
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
- name: TENX_CONFIG # ← add
value: /etc/tenx/config
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
- name: tenx-config-volume # ← add
mountPath: /etc/tenx/config
readOnly: true
extraVolumes: # ← add
- name: tenx-config-volume
persistentVolumeClaim:
claimName: my-config-pvc
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based.
For Kubernetes, use the Fluent Bit or OTel Collector tab.
Step 5: Configure Secrets
Store sensitive credentials in Kubernetes Secrets. Only add secrets for metric outputs you've configured.
Create the secret:
kubectl create secret generic receiver-credentials \
--from-file=license-jwt=./license.jwt \
--from-literal=git-token=YOUR_GIT_TOKEN \
--from-literal=elasticsearch-username=elastic \
--from-literal=elasticsearch-password=YOUR_ES_PASSWORD \
--from-literal=datadog-api-key=YOUR_DATADOG_API_KEY
Note: Only include credentials for outputs you've configured. The license-jwt key is required; the sidecar mounts it as a file and points TENX_LICENSE_FILE at it. The git-token key is only required if you picked the Git Repository path in Step 4; the git-config-fetcher init container reads it via a secretKeyRef.
Add secret references to your my-receiver.yaml:
Fluentd uses the shared receiver-credentials secret (for the 10x license and any metric-output credentials). The Log10x sidecar image is on public Docker Hub, so no image-pull secret is needed.
a. The 10x license is mounted as a file by the sidecar via the volumes entry in sidecar-patch.yaml (Step 3). It reads the license-jwt key from receiver-credentials and the sidecar reads TENX_LICENSE_FILE; no change needed in my-receiver.yaml.
b. Forwarder-side metric output credentials (Datadog, Elasticsearch, etc.) ride on the chart's env: block:
env:
# For Datadog metrics
- name: DD_API_KEY
valueFrom:
secretKeyRef:
name: receiver-credentials
key: datadog-api-key
# For Elasticsearch metrics
# - name: ELASTIC_API_KEY
# valueFrom:
# secretKeyRef:
# name: receiver-credentials
# key: elastic-api-key
# For AWS CloudWatch metrics
# - name: AWS_ACCESS_KEY_ID
# valueFrom:
# secretKeyRef:
# name: receiver-credentials
# key: aws-access-key-id
# For SignalFx metrics
# - name: SIGNALFX_ACCESS_TOKEN
# valueFrom:
# secretKeyRef:
# name: receiver-credentials
# key: signalfx-access-token
The log10x/edge-10x image is on public DockerHub, so Fluent Bit needs no image-pull secret.
a. The 10x license is mounted as a file by the sidecar from the receiver-credentials secret's license-jwt key (Step 3); no extra config here.
b. Destination-side credentials for outputs configured under config.outputs (Elasticsearch, Splunk HEC, Kafka SASL, ...) ride on the chart's env: list with ${VAR} interpolation inside the Fluent Bit config:
daemonset:
extraEnvs:
# For Elasticsearch output
- name: ELASTICSEARCH_USERNAME
valueFrom:
secretKeyRef:
name: receiver-credentials
key: elasticsearch-username
- name: ELASTICSEARCH_PASSWORD
valueFrom:
secretKeyRef:
name: receiver-credentials
key: elasticsearch-password
# For Datadog metrics (optional)
# - name: DD_API_KEY
# valueFrom:
# secretKeyRef:
# name: receiver-credentials
# key: datadog-api-key
The log10x/edge-10x image is on public DockerHub, so Vector needs no image-pull secret.
a. The 10x license is mounted as a file by the sidecar from the receiver-credentials secret's license-jwt key (Step 3); no extra config here.
b. Destination-side credentials (Elasticsearch, Splunk HEC, Kafka SASL, ...) ride on Vector's own sink config in the customConfig block, typically via env-vars resolved with ${VAR} interpolation. Wire them through the chart's env: list:
The log10x/edge-10x image is on public DockerHub, so Logstash needs no image-pull secret.
a. The 10x license is mounted as a file by the sidecar from the receiver-credentials secret's license-jwt key (Step 3); no extra config here.
b. Destination-side credentials (Elasticsearch, Splunk HEC, Kafka SASL, ...) ride on the chart's extraEnvs: list. Reference them in your tenx-destinations.conf output blocks via ${VAR} interpolation:
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Step 6: Forwarder
Configure your forwarder for log collection and output destinations. The 10x receiver filters events before they reach your final destination.
Replace the placeholder stdout <match> under @OUTPUT (in 04_outputs.conf from Step 3) with your real destination(s). The @OUTPUT label runs only on events returning from the 10x sidecar, keep it filter-free so the enrichment filters in @INGEST aren't re-applied on the return path.
fileConfigs:
04_outputs.conf: |-
<label @OUTPUT>
<match **>
@type elasticsearch
host elasticsearch-master
port 9200
logstash_format true
logstash_prefix logs-filtered
</match>
</label>
The original Fluentd tag survives the round trip through 10x, so destinations that route on $TAG (Splunk index, S3 path, Kafka topic, …) see the same value they would have without 10x in the path.
Fluent Bit's forwarder config lives in the config: block of my-receiver.yaml from Step 3. Keep the egress [INPUT] forward Tag_Prefix tenx. and the ingest [OUTPUT] forward Match kube.* as-is; replace the placeholder [OUTPUT] stdout block (Match tenx.*) with your real destination(s). The destination MUST Match tenx.* (or whatever tag namespace you chose on egress); anything matching kube.* would receive events twice (once on the way in, once on the way back) or loop them back to 10x.
config:
outputs: |
# Hand off to the 10x sidecar (unchanged).
[OUTPUT]
Name forward
Match kube.*
Host 127.0.0.1
Port 24224
Retry_Limit False
# Replace the stdout placeholder with your real destination.
# Note: Match tenx.* (not *), otherwise events loop.
[OUTPUT]
Name es
Match tenx.*
Host elasticsearch-master
Logstash_Format On
The original Fluent Bit tag is preserved as the suffix of the returning wire tag (tenx.kube.foo), so destinations that route on the suffix behave the same as without 10x in the path.
Filebeat's forwarder config lives in the daemonset.filebeatConfig.filebeat.yml string from Step 3. Replace the placeholder output.elasticsearch: block at the bottom of that string with your real destination; keep the filebeat.config.inputs: and the script processor on each input intact, those are what wire the integration. output.console is not supported (it collides with the stdout pipe carrying events to the engine); any other output (elasticsearch, logstash, kafka, file, …) is fine.
# Replace the output block in Step 3's filebeat.yml with your real
# destination. Example for an Elasticsearch destination using the
# credentials wired up via extraEnvs in Step 5:
daemonset:
filebeatConfig:
filebeat.yml: |
# ... filebeat.config.inputs and filebeat.inputs from Step 3 ...
output.elasticsearch:
hosts: '["https://elasticsearch-master:9200"]'
username: '${ELASTICSEARCH_USERNAME}'
password: '${ELASTICSEARCH_PASSWORD}'
ssl.certificate_authorities: ["/usr/share/filebeat/certs/ca.crt"]
indices:
- index: "logs-filtered-%{+yyyy.MM.dd}"
The original Filebeat record structure is preserved end-to-end, so destinations that route on metadata fields (kubernetes.namespace, host.name, …) behave the same as without 10x in the path.
The Collector forwarder config lives in the config: block of my-receiver.yaml from Step 3. Keep your existing receivers; rewire the logs: pipeline through the otlp/tenx exporter and route the new logs/from-tenx pipeline to your real destinations:
config:
receivers:
# Replace with your real Collector receivers
filelog:
include: [/var/log/pods/*/*/*.log]
operators:
- type: container
id: container-parser
exporters:
# Your real destination(s), consumed by `logs/from-tenx`
elasticsearch:
endpoints: ["https://elasticsearch-master:9200"]
logs_index: logs-filtered
service:
pipelines:
logs:
receivers: [filelog]
processors: [memory_limiter, batch]
exporters: [otlp/tenx]
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [elasticsearch]
Vector's forwarder config lives in the customConfig block of my-receiver.yaml from Step 3. The overlay already wires the tenx_in socket sink and the tenx_out fluent source; keep customConfig.sources pointing at your real log inputs and replace the destinations sink with your real destination(s), consuming inputs: [tenx_out] so events arrive only after the 10x sidecar has processed them. Don't wire destinations to the raw sources or to ingest; that bypasses the round trip and skips the bypass guarantee.
Logstash's forwarder config lives in the logstashPipeline block of my-receiver.yaml from Step 3. Keep the tenx-ingest.conf ending in tcp { host => "127.0.0.1" port => 5044 codec => json_lines } (handoff to 10x) as-is, and replace the placeholder stdout output in tenx-destinations.conf with your real destination(s). The destinations pipeline runs only on events returning from the 10x sidecar, keep it filter-free so the enrichment in tenx-ingest.conf isn't re-applied on the return path.
logstashPipeline:
tenx-destinations.conf: |
input {
tcp {
host => "0.0.0.0"
port => 5045
codec => json_lines
}
}
output {
elasticsearch {
hosts => ["elasticsearch-master:9200"]
index => "logs-filtered-%{+YYYY.MM.dd}"
}
}
The tag field added by the ingest pipeline survives the round trip, so destinations that route on it (Splunk index, S3 prefix, Kafka topic, …) see the same value they would have without 10x in the path.
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Step 7: Deploy
Create your namespace (if needed) and deploy:
Helm installs the upstream chart; the post-renderer applies the kustomize overlay (from Step 3) on the way to the cluster, injecting the 10x sidecar.
helm install my-receiver fluent/fluentd \
-f my-receiver.yaml \
--post-renderer ./tenx-kustomize/post-render.sh \
--namespace logging
helm install my-receiver fluent/fluentd ^
-f my-receiver.yaml ^
--post-renderer .\tenx-kustomize\post-render.cmd ^
--namespace logging
helm get manifest my-receiver -n logging returns the patched form (with the sidecar added); Argo/Flux diff against this final state, no drift.
Layer the Log10x overlay (my-receiver.yaml from Step 3) on top of any existing Fluent Bit values:
Fluent Bit uses the upstream chart with a values overlay. The overlay drops the 10x sidecar into the same pod via extraContainers and rewrites the chart's config via config:. Both containers share the pod network and pair over loopback TCP on :24224 (Fluent Forward in) / :24225 (Fluent Forward back).
Layer the Log10x overlay (my-receiver.yaml from Step 3) on top of any existing Filebeat values:
Filebeat uses the upstream elastic/filebeat chart with a values overlay. The overlay swaps the chart's default Filebeat image for log10x/filebeat-10x, which runs Filebeat under a filebeat ... 2>&1 | tenx run ... pipe inside the container; Filebeat and the 10x engine share a process tree inside the single container.
Layer the Log10x overlay (my-receiver.yaml from Step 3) on top of any existing Collector values:
helm upgrade --install my-receiver open-telemetry/opentelemetry-collector \
-f my-receiver.yaml \
--namespace logging
The Collector uses the upstream chart with a values overlay. The overlay drops the 10x sidecar into the same pod via extraContainers and adds the sidecar receiver/exporter to the chart's config:. Both containers share the pod network and pair over loopback TCP on :4317 (OTLP in) / :24225 (Fluent Forward back).
Layer the Log10x overlay (my-receiver.yaml from Step 3) on top of your existing Vector values:
helm upgrade --install my-receiver vector/vector \
-f your-existing-vector-values.yaml \
-f my-receiver.yaml \
--namespace logging
Vector uses the upstream chart with a values overlay. The overlay drops the 10x sidecar container into the same pod via extraContainers and rewrites the chart's config via customConfig. Both containers share the pod network and pair over loopback TCP on :9000 (NDJSON in) / :9001 (Fluent Forward back).
Layer the Log10x overlay (my-receiver.yaml from Step 3) on top of any existing Logstash values:
Logstash uses the upstream elastic/logstash chart with a values overlay. The overlay drops the 10x sidecar container into the same pod via extraContainers and provides the multi-pipeline config via logstashConfig + logstashPipeline. Both containers share the pod network and pair over loopback TCP on :5044 (NDJSON in) / :5045 (NDJSON back).
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Step 8: Verify
Verify the install in three phases: pods Ready → 10x processor alive → processed events flowing. A probe passes when its commands exit 0.
Phase A: pods Ready
Pick your forwarder for the right kubectl label selector:
Phase B: 10x receiver plugin alive
Look for receiver initialization lines on the container that runs the 10x process. Pick your forwarder:
Fluentd's sidecar pattern runs 10x in its own container alongside fluentd in the same pod; check the dedicated container's startup banner (the 📥 / 📝 lines indicate the receiver is wired):
Fluent Bit's sidecar pattern runs 10x in its own container (log10x) alongside fluent-bit in the same pod; check the dedicated container's startup banner (the 📥 / 📝 lines indicate the receiver is wired):
The Filebeat container's stdout carries the engine's startup banner; the 📥 line confirms it is reading events off the Filebeat stdout pipe, the 📝 line confirms the unix-socket emitter back to Filebeat is wired:
Logstash's sidecar pattern runs 10x in its own container (log10x) alongside logstash in the same pod; check the dedicated container's startup banner (the 📥 / 📝 lines indicate the receiver is wired):
For Vector, 10x runs as a separate sidecar container (log10x) alongside the vector container; check its logs directly. The 📥 line confirms the Vector socket input is listening; the 📝 line confirms the Forward output is wired back to Vector's fluent source.
Phase C: processed events flowing
Confirm the forwarder is actually writing processed events to its destination. For a real destination, check the destination's UI (Elasticsearch index, Splunk sourcetype, Datadog logs view). For mock/stdout output, grep for the TENX-MOCK marker:
View mute/sample counts in the dashboard:
Once running, view your mute/sample activity in the Receiver Dashboard.
Step 9: Teardown
Uninstall the Helm release:
Clean up derived resources. Pick your forwarder for the right label selector:
Verify nothing remains:
Delete the namespace (optional):
Quickstart Full Sample
Fluentd's full deploy is a values file + a four-file kustomize overlay. The values file replaces the chart's default config with the @INGEST / @OUTPUT pattern; the kustomize overlay injects the 10x sidecar via Helm's --post-renderer. Layout:
./
├── my-receiver.yaml (values, see below)
└── tenx-kustomize/
├── kustomization.yaml
├── sidecar-patch.yaml
├── post-render.sh
└── post-render.cmd (Windows shim)
The four kustomize files are listed verbatim in Step 3: Configure Deployment Settings. Re-use them as-is; only my-receiver.yaml typically changes per deployment.
kind: Deployment # or DaemonSet for host-log tailing
replicaCount: 1
image:
repository: fluent/fluentd
tag: v1.18-debian-1
mountVarLogDirectory: false
mountDockerContainersDirectory: false
rbac:
create: false
serviceAccount:
create: true
service:
enabled: false
podSecurityPolicy:
enabled: false
# Secrets (license JWT + metric-output credentials) live in the
# `receiver-credentials` Secret from Step 5.
fileConfigs:
01_sources.conf: |-
# Replace this dummy source with your real sources (tail, http,
# k8s, …). Every source MUST route to @INGEST.
<source>
@type dummy
tag k8s.demo
rate 1
dummy {"log":"hello from fluentd dummy plugin","level":"info"}
auto_increment_key seq
@label @INGEST
</source>
02_filters.conf: |-
<label @INGEST>
<filter **>
@type record_transformer
<record>
cluster "${ENV['CLUSTER_NAME'] || 'unset'}"
</record>
</filter>
<match **>
@type forward
keepalive true
keepalive_timeout 60s
send_timeout 5s
require_ack_response false
<server>
host 127.0.0.1
port 24224
</server>
<buffer>
@type memory
flush_interval 1s
</buffer>
</match>
</label>
03_dispatch.conf: |-
<source>
@type forward
bind 0.0.0.0
port 24225
@label @OUTPUT
</source>
04_outputs.conf: |-
<label @OUTPUT>
<match **>
@type elasticsearch
host elasticsearch-master
port 9200
logstash_format true
logstash_prefix logs-filtered
</match>
</label>
Install:
helm install my-receiver fluent/fluentd \
-f my-receiver.yaml \
--post-renderer ./tenx-kustomize/post-render.sh \
--namespace logging
To set the engine-wide default action (pass / pass+hash / compact / compact+hash), edit the args list in tenx-kustomize/sidecar-patch.yaml; see the inline comments in that file (Step 3). Per-pattern actions come from the agent's configure_engine cap/action CSV through GitOps.
The Fluent Bit quickstart is a values overlay applied to your existing Fluent Bit chart install. The 10x sidecar and Fluent Bit share the pod network and talk over loopback TCP, with Tag_Prefix tenx. on the egress [INPUT] forward providing the bypass that keeps returning events from re-firing the ingest [OUTPUT] forward and the filters.
Save as my-receiver.yaml and apply with helm upgrade --install my-receiver fluent/fluent-bit -f your-existing-fluent-bit-values.yaml -f my-receiver.yaml.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/fluentbit"
- "@apps/receiver"
# Default action: pass. Append `receiverOptimize true` to
# default to compact, and/or `symbolMessageHashField <name>`
# for the symbol-pattern hash field. Per-pattern actions come
# from the agent's `configure_engine` CSV through GitOps.
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
config:
service: |
[SERVICE]
Daemon Off
Flush {{ .Values.flush }}
Log_Level {{ .Values.logLevel }}
Parsers_File /fluent-bit/etc/parsers.conf
Parsers_File /fluent-bit/etc/conf/custom_parsers.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port {{ .Values.metricsPort }}
Health_Check On
inputs: |
# Your real source: tag with anything NOT starting with `tenx.`.
[INPUT]
Name tail
Path /var/log/containers/*.log
multiline.parser docker, cri
Tag kube.*
Mem_Buf_Limit 5MB
Skip_Long_Lines On
# Egress: receive processed events back from log10x with the
# `tenx.` prefix as the bypass.
[INPUT]
Name forward
Listen 0.0.0.0
Port 24225
Tag_Prefix tenx.
filters: |
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
outputs: |
# Ingest: ship to log10x.
[OUTPUT]
Name forward
Match kube.*
Host 127.0.0.1
Port 24224
Retry_Limit False
# Destinations match the `tenx.*` namespace (replace stdout).
[OUTPUT]
Name es
Match tenx.*
Host elasticsearch-master
Logstash_Format On
To set the engine-wide default action (pass / pass+hash / compact / compact+hash), edit the args list under extraContainers[0] and helm upgrade --install; see the inline comments. Per-pattern actions come from the agent's configure_engine cap/action CSV through GitOps.
The Filebeat quickstart is a values overlay applied to the upstream elastic/filebeat chart. The 10x engine runs as a child process of Filebeat's entrypoint inside the prebuilt log10x/filebeat-10x image, which wraps filebeat -e 2>&1 | tenx run @run/input/forwarder/filebeat @apps/receiver. The overlay swaps the chart's default image and adds the script processor + return-path unix input to the chart's filebeat.yml.
Save as my-receiver.yaml and apply with helm upgrade --install my-receiver elastic/filebeat -f my-receiver.yaml.
# Image swap: the log10x-built Filebeat image runs the engine as a
# child of Filebeat's entrypoint.
image: "log10x/filebeat-10x"
imageTag: "latest" # pin to a release tag in production
imagePullPolicy: IfNotPresent
daemonset:
extraEnvs:
# Point the engine at the mounted license file (Step 5).
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
# Destination credentials for the output.elasticsearch block.
- name: ELASTICSEARCH_USERNAME
valueFrom:
secretKeyRef:
name: receiver-credentials
key: elasticsearch-username
- name: ELASTICSEARCH_PASSWORD
valueFrom:
secretKeyRef:
name: receiver-credentials
key: elasticsearch-password
# ─── Actions ────────────────────────────────────────────────
# Per-pattern action (pass, sample, compact, tier_down,
# offload, drop) is set by the agent via the log10x MCP
# `configure_engine` cap/action CSV through GitOps. TENX_RUN_ARGS
# sets the engine-wide default before any CSV.
#
# Default (no override): pass: events go through unchanged.
#
# Override TENX_RUN_ARGS to change the default action:
# - Append `receiverOptimize true` to default to compact.
# - Append `symbolMessageHashField <name>` to surface the
# symbol-pattern hash as a top-level field.
#
# - name: TENX_RUN_ARGS
# value: "@run/input/forwarder/filebeat @apps/receiver receiverOptimize true"
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
extraVolumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
# `filebeat.config.inputs` loads the unix-socket input that
# receives processed events back from the engine. Every entry in
# `filebeat.inputs` must carry the tenx-receive.js script
# processor; without it the event never reaches the engine.
# `output.console` must NOT be used: it shares the stdout pipe
# with the engine and corrupts the event stream.
filebeatConfig:
filebeat.yml: |
filebeat.config.inputs:
enabled: true
path: ${TENX_MODULES}/pipelines/run/modules/input/forwarder/filebeat/conf/tenxNix.yml
filebeat.inputs:
- type: container
paths:
- /var/log/containers/*.log
processors:
- add_kubernetes_metadata:
host: ${NODE_NAME}
matchers:
- logs_path:
logs_path: "/var/log/containers/"
- script:
lang: javascript
file: ${TENX_MODULES}/pipelines/run/modules/input/forwarder/filebeat/script/tenx-receive.js
output.elasticsearch:
hosts: '["https://elasticsearch-master:9200"]'
username: '${ELASTICSEARCH_USERNAME}'
password: '${ELASTICSEARCH_PASSWORD}'
indices:
- index: "logs-filtered-%{+yyyy.MM.dd}"
To set the engine-wide default action (pass / pass+hash / compact / compact+hash), uncomment the TENX_RUN_ARGS entry under daemonset.extraEnvs; see the inline comments. Per-pattern actions come from the agent's configure_engine cap/action CSV through GitOps.
The Collector quickstart is a values overlay applied to your existing Collector chart install. The 10x sidecar and the Collector share the pod network and talk over loopback TCP.
Save as my-receiver.yaml and apply with helm upgrade --install my-receiver open-telemetry/opentelemetry-collector -f your-existing-otel-values.yaml -f my-receiver.yaml.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/otel-collector"
- "@apps/receiver"
# Default action: pass. Append `receiverOptimize true` to
# default to compact, and/or `symbolMessageHashField <name>`
# for the symbol-pattern hash field. Per-pattern actions come
# from the agent's `configure_engine` CSV through GitOps.
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
config:
receivers:
# Replace with your real receivers
filelog:
include: [/var/log/pods/*/*/*.log]
# OTLP/gRPC receiver for processed events coming back from log10x
otlp/tenx:
protocols:
grpc:
endpoint: 0.0.0.0:24225
exporters:
otlp/tenx:
endpoint: 127.0.0.1:4317
tls:
insecure: true
elasticsearch:
endpoints: ["https://elasticsearch-master:9200"]
logs_index: logs-filtered
service:
pipelines:
logs:
receivers: [filelog]
processors: [memory_limiter, batch]
exporters: [otlp/tenx]
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [elasticsearch]
Vector's quickstart is a values overlay applied to your existing Vector chart install. The 10x sidecar and Vector share the pod network and talk over loopback TCP.
Save as my-receiver.yaml and apply with helm upgrade --install my-receiver vector/vector -f your-existing-vector-values.yaml -f my-receiver.yaml.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/vector"
- "@apps/receiver"
# Default action: pass. Append `receiverOptimize true` to
# default to compact, and/or `symbolMessageHashField <name>`
# for the symbol-pattern hash field. Per-pattern actions come
# from the agent's `configure_engine` CSV through GitOps.
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
# The vector chart auto-publishes ports from each `customConfig.sources`
# entry, so 9001 is already declared on the `vector` container.
# The sidecar connects out on 127.0.0.1, no port of its own needed.
customConfig:
data_dir: /vector-data-dir
sources:
# Replace with your real sources
app_logs:
type: kubernetes_logs
# Receive processed events back from the 10x sidecar (bind on
# 0.0.0.0 so kubelet's tcpSocket probe can reach the port from
# the pod IP).
tenx_out:
type: fluent
mode: tcp
address: 0.0.0.0:9001
transforms:
ingest:
type: remap
inputs: [app_logs]
source: |
.cluster = get_env_var("CLUSTER_NAME") ?? "unset"
.tag = .source_type
sinks:
tenx_in:
type: socket
inputs: [ingest]
mode: tcp
address: 127.0.0.1:9000
encoding: { codec: json }
framing: { method: newline_delimited }
# Replace `console` with your real destination: consume
# `tenx_out` so events arrive only after the round trip.
destinations:
type: console
inputs: [tenx_out]
encoding: { codec: json }
See the Vector forwarder guide for the architecture, the four-mode contract, and the matching local-install walkthrough.
Logstash's quickstart is a values overlay applied to the upstream elastic/logstash chart. The 10x sidecar and Logstash share the pod network and talk over loopback TCP.
Save as my-receiver.yaml and apply with helm upgrade --install my-receiver elastic/logstash -f my-receiver.yaml.
extraContainers: |
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/logstash"
- "@apps/receiver"
# Default action: pass. Append `receiverOptimize true` to
# default to compact, and/or `symbolMessageHashField <name>`
# for the symbol-pattern hash field. Per-pattern actions come
# from the agent's `configure_engine` CSV through GitOps.
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
secretMounts:
- name: tenx-license
secretName: receiver-credentials
path: /etc/tenx/license/license.jwt
subPath: license-jwt
logstashConfig:
pipelines.yml: |
- pipeline.id: ingest
path.config: "/usr/share/logstash/pipeline/tenx-ingest.conf"
- pipeline.id: destinations
path.config: "/usr/share/logstash/pipeline/tenx-destinations.conf"
logstashPipeline:
tenx-ingest.conf: |
input {
# Replace with your real sources
file {
path => "/var/log/containers/*.log"
codec => "json"
start_position => "beginning"
}
}
filter {
mutate {
add_field => { "cluster" => "${CLUSTER_NAME:unset}" }
add_field => { "tag" => "k8s.containers" }
}
}
output {
# Hand off to the 10x sidecar
tcp {
host => "127.0.0.1"
port => 5044
codec => json_lines
}
}
tenx-destinations.conf: |
input {
# Bind on 0.0.0.0 so the port is reachable from outside
# loopback (kubelet probe). The sidecar connects via
# 127.0.0.1 from inside the same pod.
tcp {
host => "0.0.0.0"
port => 5045
codec => json_lines
}
}
output {
# Replace `stdout` with your real destination: events arrive
# here only after the 10x sidecar has processed them.
stdout { codec => json_lines }
}
See the Logstash forwarder guide for the architecture, the four-mode contract, and the matching local-install walkthrough.
For Kubernetes, use the Fluent Bit tab. Splunk Connect for Kubernetes is Fluent Bit-based. For VM infrastructure, see the Splunk UF receiver guide.
For Kubernetes, use the Fluent Bit or OTel Collector tab. For VM infrastructure, see the Datadog Agent receiver guide.
Datadog Output Examples
To send filtered events to Datadog, use the file relay pattern: the forwarder writes processed events to a folder that the Datadog Agent monitors. This keeps the Datadog Agent as the shipper (handling buffering, retries, metadata enrichment) while 10x acts on events inline. Pick your forwarder below for the exact wiring.
Replace the destination [OUTPUT] (the one with Match tenx.*) in my-receiver.yaml from Step 3 with a file output. The rest of the values overlay (the extraContainers sidecar, the ingest [OUTPUT] forward, and the egress [INPUT] forward Tag_Prefix tenx.) stays the same; Fluent Bit writes only the processed events (those returning from log10x with the tenx. prefix) to the shared folder.
config:
outputs: |
# Ingest: ship to log10x (unchanged).
[OUTPUT]
Name forward
Match kube.*
Host 127.0.0.1
Port 24224
Retry_Limit False
# Destination: processed events only, Match tenx.* (NOT *).
[OUTPUT]
Name file
Match tenx.*
Path /var/log/processed
Format plain
Then configure the Datadog Agent to monitor the processed output folder:
logs:
- type: file
path: /var/log/processed/*.log
service: myapp
source: myapp
On EKS, mount a shared emptyDir volume between the Fluent Bit + 10x pod and the Datadog Agent DaemonSet at /var/log/processed.
File-relay pattern: the 10x sidecar wiring from Step 3 stays intact; only the egress pipeline's exporter changes, from your real destination (e.g. elasticsearch) to OTel's file exporter writing to a shared folder that the Datadog Agent monitors. This keeps the Datadog Agent as the forwarder (buffering, retries, metadata enrichment) while 10x acts on events inline.
Replace the exporters: list on the logs/from-tenx pipeline from Step 3; everything else (the otlp/tenx receiver + exporter, the logs ingest pipeline, the extraContainers sidecar) stays the same:
config:
exporters:
# Egress writer: only processed events (those coming back from
# log10x via otlp/tenx) land here.
file/tenx:
path: /var/log/processed/otel.log
service:
pipelines:
# Ingest pipeline unchanged from Step 3, still hands events
# off to the log10x sidecar via otlp/tenx.
logs:
receivers: [filelog] # ← your existing receivers
processors: [memory_limiter, batch]
exporters: [otlp/tenx]
# Egress pipeline: swap the destination from your real
# exporter (e.g. elasticsearch) to file/tenx.
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [file/tenx] # ← was [elasticsearch]
Then configure the Datadog Agent to monitor the processed output folder:
logs:
- type: file
path: /var/log/processed/*.log
service: myapp
source: myapp
On EKS, mount a shared emptyDir volume between the OTel Collector + 10x pod and the Datadog Agent DaemonSet at /var/log/processed.
Splunk HEC Output Examples
To send filtered events to Splunk instead of Elasticsearch, use Splunk HEC output.
Replace the @OUTPUT label's <match> in 04_outputs.conf with a splunk_hec destination. Everything else from the Quickstart Full Sample stays the same; the kustomize overlay is unchanged.
fileConfigs:
04_outputs.conf: |-
<label @OUTPUT>
<match **>
@type splunk_hec
hec_host splunk-hec.example.com
hec_port 8088
hec_token "#{ENV['SPLUNK_HEC_TOKEN']}"
index main
source kubernetes
</match>
</label>
Add the HEC token to your receiver-credentials secret (--from-literal=splunk-hec-token=...) and reference it from the chart's env: block:
Replace the destination [OUTPUT] (the one with Match tenx.*) in my-receiver.yaml from Step 3 with a splunk HEC output. Everything else (the sidecar extraContainers, the ingest [OUTPUT] forward, and the egress [INPUT] forward Tag_Prefix tenx.) stays the same.
config:
outputs: |
# Ingest: ship to log10x (unchanged).
[OUTPUT]
Name forward
Match kube.*
Host 127.0.0.1
Port 24224
Retry_Limit False
# Destination Match tenx.* (NOT *) so it doesn't loop events.
[OUTPUT]
Name splunk
Match tenx.*
Host splunk-hec.example.com
Port 8088
Splunk_Token ${SPLUNK_HEC_TOKEN}
Splunk_Send_Raw On
TLS On
TLS.Verify Off
Add the HEC token to your receiver-credentials secret (--from-literal=splunk-hec-token=...) and reference it from the chart's env: block:
Apply as an overlay on your existing Collector chart install: helm upgrade --install my-receiver open-telemetry/opentelemetry-collector -f your-existing-otel-values.yaml -f my-receiver.yaml.
extraContainers:
- name: log10x
image: log10x/edge-10x:latest
imagePullPolicy: IfNotPresent
args:
- "@run/input/forwarder/otel-collector"
- "@apps/receiver"
env:
- name: TENX_LICENSE_FILE
value: /etc/tenx/license/license.jwt
volumeMounts:
- name: tenx-license
mountPath: /etc/tenx/license
readOnly: true
extraVolumes:
- name: tenx-license
secret:
secretName: receiver-credentials
items:
- key: license-jwt
path: license.jwt
config:
receivers:
filelog:
include: [/var/log/pods/*/*/*.log]
otlp/tenx:
protocols:
grpc:
endpoint: 0.0.0.0:24225
exporters:
otlp/tenx:
endpoint: 127.0.0.1:4317
tls:
insecure: true
splunk_hec:
endpoint: "https://splunk-hec.example.com:8088/services/collector"
token: "YOUR-HEC-TOKEN"
index: main
tls:
insecure_skip_verify: true
service:
pipelines:
logs:
receivers: [filelog]
processors: [memory_limiter, batch]
exporters: [otlp/tenx]
logs/from-tenx:
receivers: [otlp/tenx]
exporters: [splunk_hec]
logstashPipeline:
tenx-destinations.conf: |
input {
tcp {
host => "0.0.0.0"
port => 5045
codec => json_lines
}
}
output {
http {
url => "https://splunk-hec.example.com:8088/services/collector"
http_method => "post"
headers => ["Authorization", "Splunk YOUR-HEC-TOKEN"]
format => "json"
}
}