Skip to main content

 

Splunk Lantern

Forwarding OCI Streaming to Splunk with Splunk OpenTelemetry Collector for Kafka

Oracle Cloud Infrastructure (OCI) Streaming speaks the Kafka protocol, which means you don't need a custom bridge to get records out of it - any Kafka-compatible consumer works. This guide shows you how to point the Splunk OpenTelemetry Collector for Kafka (SOC4Kafka) at an existing OCI Streaming stream and land those records in the Splunk platform over HTTP Event Collector (HEC), with no code in between. 

Following those steps from top to bottom will bring your data to the Splunk platform. A full command-by-command reference, including OCI network and Stream Pool provisioning is available at SOC4Kafka Installation Guide — OCI Streaming to Splunk. This guide assumes that infrastructure is already provisioned and focuses on configuring the collector. 

You'll pick one of two deployment forms: 

  • Deployment A - Bare metal / systemd: the otelcol binary runs directly on a VM. No container runtime required. Good for a simple single-host setup. 
  • Deployment B - MicroK8s: the official Helm chart runs on a single-node Kubernetes cluster. Good if you want pod-level isolation and rolling updates. The chart is distribution-agnostic - only a couple of OCI/MicroK8s-specific quirks below are specific to that combination. 

How it fits together

The pipeline has three parts, all running inside a single Splunk OTel Collector process: 

  • Source - OCI Streaming, consumed via its Kafka-compatible API. The collector authenticates over SASL_SSL/PLAIN on port 9092. If your Stream Pool has a private endpoint, the broker hostname resolves to a private VCN IP, so that traffic never has to leave the VCN - the VM (or cluster node) just needs network line-of-sight to it. 
  • Processor - the kafka receiver hands records to a resourcedetection processor, which tags each record with host/system metadata before it's exported. 
  • Sink - the splunk_hec exporter batches records and posts them to your Splunk HEC endpoint, reached outbound over the internet (or your private connectivity, if you have it). 

Because SOC4Kafka doesn't implement HEC Acknowledgement, the Splunk-side prerequisite is simple but non-negotiable: HEC Indexer Acknowledgement must be OFF, or the connection will stall waiting for an ack that never comes. 

Prerequisites

From OCI

What you need Where to find it Placeholder Example value
Kafka bootstrap endpoint Streaming → Stream Pools → select pool → Kafka Connection Settings → Bootstrap Servers  <KAFKA_BOOTSTRAP> cell-1.streaming.us-ashburn-1.oci.oraclecloud.com:9092
Kafka SASL username Same page → Username (fully formed, ready to copy) <SASL_USERNAME> axhqcaz9raiv/jane.doe@
example.com/ocid1.streampool
.oc1.iad.amaaaaaaak7gxbiamhoy
xvkihphudt7wxagjaz3fqxrmyg4z
5uwvlnjq3wca
 
OCI auth token (SASL password) Profile → User Settings → Auth Tokens → Generate Token — copy immediately, shown once <OCI_AUTH_TOKEN>  wJ>9Qs&2poK!zL8x 
Stream / topic name Streaming → Streams <TOPIC>  app-logs

Note the following:

  • SASL username format: the username is normally <tenancy-namespace>/<user>/<stream-pool-OCID>. If your tenancy uses an OCI identity domain, the username needs an extra segment: <tenancy-namespace>/<identity-domain>/<user>/<stream-pool-OCID>. Find your identity domain under Identity & Security → Domains (commonly Default). If authentication fails with SASL authentication error: Authentication failed despite a valid token, this four-part form is almost always the fix. 
  • Handling special characters: the OCI auth token may contain characters like &, |, >, `, or !, there is  no need to regenerate the token if it does for the bare-metal path. Keep the single quotes shown around KAFKA_SASL_PASS and --from-literal=... below when you set it: without them, the shell interprets those characters itself and can silently truncate or empty out the value before it ever reaches the collector.
  • MicroK8s is stricter: if the token starts with a YAML-special character (#, &, *, !, >, |, @, `), the collector's env provider re-parses the substituted value as YAML — and a leading # in particular collapses the password to null, even though the secret looks correct. If your token starts with one of these, regenerate it before using the MicroK8s path. 

From the Splunk platform

What you need Where to find it Placeholder Example value
HEC endpoint URL Settings → Data inputs → HTTP Event Collector → host + port 8088, path /services/collector  <SPLUNK_HEC_ENDPOINT>  https://splunk.example.com:
8088/services/collector
 
HEC token Settings → Data inputs → HTTP Event Collector → token's Token Value  <SPLUNK_HEC_TOKEN>  b5b3c8a4-1e6d-4f2a-9c7e-2d4f6a8b0c1d 
Target index Settings → Indexes  <SPLUNK_INDEX>  oci_streaming

Note the following:

  • HEC prerequisites: before installing the collector, make sure HEC is globally enabled (Global Settings → Enabled), that Indexer Acknowledgement is OFF, and that your HEC token's Allowed indexes include <SPLUNK_INDEX>

Choose a consumer group name

Pick a short, unique string for <CONSUMER_GROUP> (for example, soc4kafka-v1). This identifies your collector instance to the Kafka broker. Use a fresh name; reusing a group ID from a previous failed install can leave OCI Streaming's coordinator state poisoned for that group, causing the collector to loop indefinitely with NOT_COORDINATOR errors on startup. 

Solutions

Deployment A - Bare metal / systemd

All commands run on your VM over SSH.

A.1 Install dependencies

These packages are used for connectivity testing and producing test messages - not required for the collector itself.

sudo apt-get update 
sudo apt-get install -y kafkacat curl netcat-openbsd 

A.2 Download the collector library

mkdir -p ~/soc4kafka && cd ~/soc4kafka 
wget https://github.com/signalfx/splunk-otel-collector/releases/download/v0.155.0/otelcol_linux_amd64 
chmod +x otelcol_linux_amd64

Check the releases page for newer versions and substitute v0.155.0 accordingly.

A.3 Create the secrets file

Create ~/soc4kafka/collector.env and restrict its permissions. This keeps every secret out of the config file and out of process arguments. 

cat > ~/soc4kafka/collector.env <<'EOF' 
KAFKA_BOOTSTRAP=<KAFKA_BOOTSTRAP> 
KAFKA_SASL_USER=<SASL_USERNAME> 
KAFKA_SASL_PASS='<OCI_AUTH_TOKEN>' 
SPLUNK_HEC_URL=<SPLUNK_HEC_ENDPOINT> 
SPLUNK_HEC_TOKEN=<SPLUNK_HEC_TOKEN> 
SPLUNK_INDEX=<SPLUNK_INDEX> 
EOF 
chmod 600 ~/soc4kafka/collector.env

Wrap KAFKA_SASL_PASS in single quotes so the shell does not expand special characters in the token value. 

A.4 Create the collector config

Create ~/soc4kafka/config.yaml. Substitute <CONSUMER_GROUP> and <TOPIC> directly in the file; these aren't secrets and don't need to live in the env file. 

This is an example of a simple configuration to get data flowing. The kafka receiver and splunk_hec exporter both support many more settings. See the README configuration table and the kafkareceiver / splunkhecexporter upstream docs to explore them. 

receivers: 
  kafka: 
    brokers: 
      - ${env:KAFKA_BOOTSTRAP} 
    group_id: <CONSUMER_GROUP> 
    client_id: <CONSUMER_GROUP> 
    group_rebalance_strategy: range 
    initial_offset: earliest 
    tls: 
      insecure_skip_verify: false 
    auth: 
      sasl: 
        username: ${env:KAFKA_SASL_USER} 
        password: ${env:KAFKA_SASL_PASS} 
        mechanism: PLAIN 
    logs: 
      topics: 
        - <TOPIC> 
      encoding: text 

processors: 
  resourcedetection: 
    detectors: [system] 
    system: 
      hostname_sources: ["os"] 

exporters: 
  splunk_hec: 
    token: ${env:SPLUNK_HEC_TOKEN} 
    endpoint: ${env:SPLUNK_HEC_URL} 
    source: oci-streaming 
    sourcetype: oci:streaming:text 
    index: ${env:SPLUNK_INDEX} 
    tls: 
      insecure_skip_verify: true 
    splunk_app_name: soc4kafka
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 10000
      block_on_overflow: true
      sizer: items
      batch:
        min_size: 1000

service: 
  pipelines: 
    logs: 
      receivers: [kafka] 
      processors: [resourcedetection] 
      exporters: [splunk_hec] 

A.5 Verify the connectivity before starting

Check that the VM can reach Splunk HEC:

nc -vz <SPLUNK_HEC_HOST> 8088 

Confirm HEC accepts a posted event: 

set -a; source ~/soc4kafka/collector.env; set +a 
curl -kv \ 
  -H "Authorization: Splunk $SPLUNK_HEC_TOKEN" \ 
  -d '{"event":"hello","index":"'"$SPLUNK_INDEX"'"}' \ 
  "$SPLUNK_HEC_URL/event"

Expect {"text":"Success","code":0}. An Invalid token or Incorrect index error means Splunk setup needs fixing before you go further. 

Check that the VM can reach the Kafka broker and authenticate - the single best end-to-end connectivity test, since success means DNS, routing, TLS, and SASL all work: 

kafkacat -L \ 
  -b "$KAFKA_BOOTSTRAP" \ 
  -X security.protocol=SASL_SSL \ 
  -X sasl.mechanisms=PLAIN \ 
  -X sasl.username="$KAFKA_SASL_USER" \ 
  -X sasl.password="$KAFKA_SASL_PASS" | head -20

You should see <TOPIC> listed. If it times out or returns an auth error, resolve that before proceeding - the collector will exhibit the same failure. 

A.6 Start the collector

Run in the foreground first to watch startup logs:

cd ~/soc4kafka 
set -a; source ./collector.env; set +a 
./otelcol_linux_amd64 --config config.yaml 

A healthy startup looks like: 

Everything is ready. Begin running and processing data. 
...joined, balancing group   group: <CONSUMER_GROUP> 
...synced                    assigned: <TOPIC>[0] 
...beginning heartbeat loop

If you see NOT_COORDINATOR repeating, stop the collector, change group_id and client_id to a new name, and restart. 

A.7 Install as a systemd service

After the collector starts cleanly, promote it to a managed service so it restarts automatically and its logs land in journald.

sudo mkdir -p /opt/soc4kafka /etc/soc4kafka 
sudo cp ~/soc4kafka/otelcol_linux_amd64 /opt/soc4kafka/ 
sudo cp ~/soc4kafka/config.yaml /opt/soc4kafka/ 
sudo cp ~/soc4kafka/collector.env /etc/soc4kafka/collector.env 
sudo chmod 600 /etc/soc4kafka/collector.env 

sudo useradd --system --no-create-home --shell /usr/sbin/nologin otel 
sudo chown otel:otel /opt/soc4kafka/otelcol_linux_amd64 
sudo chown otel:otel /opt/soc4kafka/config.yaml 
sudo chown otel:otel /etc/soc4kafka/collector.env 

sudo tee /etc/systemd/system/soc4kafka.service > /dev/null <<'EOF' 
[Unit] 
Description=SOC4Kafka collector (OCI Streaming -> Splunk) 
After=network-online.target 
Wants=network-online.target 

[Service] 
User=otel 
Group=otel 
EnvironmentFile=/etc/soc4kafka/collector.env 
ExecStart=/opt/soc4kafka/otelcol_linux_amd64 --config /opt/soc4kafka/config.yaml 
Restart=on-failure 
RestartSec=5s 

[Install] 
WantedBy=multi-user.target 
EOF 

sudo systemctl daemon-reload 
sudo systemctl enable --now soc4kafka 
sudo journalctl -u soc4kafka -f

A.8 Send a test message and confirm in the Splunk platform

set -a; source ~/soc4kafka/collector.env; set +a 
printf '{"hello":"splunk","ts":"%s"}\n' "$(date -u +%FT%TZ)" | \ 
  kafkacat -P \ 
    -b "$KAFKA_BOOTSTRAP" \ 
    -t <TOPIC> \ 
    -X security.protocol=SASL_SSL \ 
    -X sasl.mechanisms=PLAIN \ 
    -X sasl.username="$KAFKA_SASL_USER" \ 
    -X sasl.password="$KAFKA_SASL_PASS" 

In Splunk search: index=<SPLUNK_INDEX> sourcetype=oci:streaming:text 

You can also watch collector throughput on the VM - receiver_accepted_log_records_total should increment when you produce, followed shortly by exporter_sent_log_records_total as the batch flushes: 

curl -s http://127.0.0.1:8888/metrics | grep -E 
'otelcol_(receiver_accepted|exporter_sent)' 

Deployment B - MicroK8s

This guide uses MicroK8s as a representative single-node Kubernetes setup. The SOC4Kafka Helm chart is distribution-agnostic and runs on any conformant cluster. If you're on something else, swap in your cluster's regular kubectl/helm for the microk8s kubectl/microk8s helm3 commands below. The DNS pin and the OCI-host-firewall fix in this section are specific to running MicroK8s on an OCI VM; skip them on other clouds/distros. 

MicroK8s ships its own bundled helm3 and kubectl. The commands below use microk8s helm3 and microk8s kubectl — not the system-level tools. 

B.1 Install MicroK8s

sudo snap install microk8s --classic --channel=1.33/stable 
sudo usermod -a -G microk8s "$USER" 
sudo chown -f -R "$USER" ~/.kube 
newgrp microk8s 

microk8s enable hostpath-storage 
microk8s enable rbac 
microk8s enable metrics-server 

Enable DNS pinned to the OCI VCN resolver, 169.254.169.254 - the same address in every OCI VCN. It answers both private OCI names (your broker's private endpoint) and public names (your Splunk HEC host), so using it as the single upstream matters: 

microk8s enable dns:169.254.169.254 

Don't add a public resolver like `8.8.8.8` alongside it. The OCI Streaming broker resolves to a private VCN IP, and a public resolver returns NXDOMAIN for it. If CoreDNS load-balances across both, you'll see intermittent connection failures—roughly half your lookups fail. 

B.2 Fix the OCI host firewall

The OCI Ubuntu image ships a firewall rule that blocks forwarded traffic, which prevents pods from reaching the Kubernetes API server and causes CoreDNS and Calico to crash-loop (no route to host to the ClusterIP). Remove it: 

sudo iptables -L FORWARD -n --line-numbers | head 
sudo iptables -D FORWARD 1    # removes the REJECT rule (usually at position 1)

Pods recover within about 60 seconds. Make the fix permanent; the rule returns on reboot otherwise:

# Edit the persisted ruleset and remove the REJECT line, then reload: 
sudo grep -nE 'REJECT|icmp-host-prohibited' /etc/iptables/rules.v4 
# Delete the matching line from the file, then: 
sudo netfilter-persistent reload

On a test VM, you can instead disable the OS firewall entirely. The OCI VCN security list still controls ingress at the cloud layer: 

sudo systemctl disable --now netfilter-persistent 

If Calico still crash-loops after removing the FORWARD rule, your image also has an INPUT-chain REJECT that blocks pod traffic to the Kubernetes API server VIP (10.152.183.1) and pod CIDR (10.1.0.0/16) — standard MicroK8s defaults. Allow them:  ``bash sudo iptables -I INPUT 4 -s 10.152.183.0/24 -j ACCEPT sudo iptables -I INPUT 4 -d 10.152.183.0/24 -j ACCEPT sudo iptables -I INPUT 4 -s 10.1.0.0/16    -j ACCEPT sudo iptables -I INPUT 4 -d 10.1.0.0/16    -j ACCEPT `  -I INPUT 4 inserts before the catch-all REJECT — confirm the position with sudo iptables -L INPUT -n --line-numbers first. If you customized MicroK8s CIDRs, use your actual service CIDR (grep service-cluster-ip-range /var/snap/microk8s/current/args/*) and pod CIDR (grep cluster-cidr /var/snap/microk8s/current/args/*`) instead. 

B.3 Create the namespace

microk8s kubectl create namespace soc4kafka 

B.4 Create the Kubernetes secrets

The collector reads credentials from Kubernetes Secrets injected as environment variables; they never appear in the Helm values file. The key names below are required by the chart. Don't rename them. 

# Kafka SASL password — the key name "password" is required by the chart 
microk8s kubectl -n soc4kafka create secret generic kafka-sasl \ 
  --from-literal=password='<OCI_AUTH_TOKEN>' 

# Splunk HEC token — the key name "splunk-hec-token" is required by the chart 
microk8s kubectl -n soc4kafka create secret generic splunk-hec \ 
  --from-literal=splunk-hec-token='<SPLUNK_HEC_TOKEN>'

Wrap values in single quotes to prevent the shell from interpreting special characters. And remember the earlier note: if <OCI_AUTH_TOKEN> starts with a YAML-special character, regenerate it before creating this secret. 

B.5 Create values.yaml

Create this file on the VM (for example, at ~/soc4kafka_microk8s/values.yaml). Substitute all <PLACEHOLDERS> with your real values. 

This is an example of a simple configuration to get data flowing. The Helm chart supports many more settings for receivers, exporters, TLS, and secrets. See the chart's configuration, secrets, and TLS docs to explore them

replicaCount: 1 

kafkaReceivers: 
  - name: main 
    brokers: 
      - <KAFKA_BOOTSTRAP> 
    client_id: <CONSUMER_GROUP>       # e.g. soc4kafka-m8k-v1 — must be fresh 
    group_id: <CONSUMER_GROUP> 
    group_rebalance_strategy: range 
    initial_offset: earliest 
    logs: 
      topics: 
        - <TOPIC> 
      encoding: text 
    auth: 
      sasl: 
        username: <SASL_USERNAME> 
        mechanism: PLAIN 
        secret: kafka-sasl           # references the Secret created in step B.4 
    tls: 
      insecure_skip_verify: false    # OCI broker cert is publicly trusted 

splunkExporters: 
  - name: primary 
    endpoint: <SPLUNK_HEC_ENDPOINT> 
    secret: splunk-hec               # references the Secret created in step B.4 
    source: oci-streaming 
    sourcetype: oci:streaming:text 
    index: <SPLUNK_INDEX> 
    splunk_app_name: soc4kafka 
    tls: 
      insecure_skip_verify: true    # Splunk default self-signed cert has no SAN 
    sending_queue:
      enabled: true
      num_consumers: 10
      queue_size: 10000
      block_on_overflow: true
      sizer: items
      batch:
        min_size: 1000

pipelines: 
  - name: oci-to-splunk 
    type: logs 
    receivers: [main] 
    exporters: [primary] 
    processors: [resourcedetection] 

extraEnv: 
  - name: KAFKA_KAFKA_MAIN_SASL_PASSWORD 
    valueFrom: 
      secretKeyRef: 
        name: kafka-sasl 
        key: password 

resources: 
  limits: 
    cpu: 500m 
    memory: 512Mi 
  requests: 
    cpu: 100m 
    memory: 256Mi 

collectorLogs: 
  enabled: false 
collectorMetrics: 
  enabled: false 

B.6 Install the chart

microk8s helm3 repo add splunk-opentelemetry-collector-for-kafka \ 
  https://splunk.github.io/splunk-opentelemetry-collector-for-kafka 
microk8s helm3 repo update 

microk8s helm3 upgrade --install soc4kafka \ 
  splunk-opentelemetry-collector-for-kafka/splunk-opentelemetry-collector-for-kafka \ 
  -n soc4kafka \ 
  -f ~/soc4kafka_microk8s/values.yaml 

Always include -n soc4kafka. Without it the release lands in the default namespace and will be difficult to find. 

B.7 Verify the deployment

Check that all pods are running: 

microk8s kubectl get pods -A 

Tail the collector logs and look for the healthy startup sequence:

microk8s kubectl -n soc4kafka logs -f \ 
  deploy/soc4kafka-splunk-opentelemetry-collector-for-kafka 

Expected output: 

Everything is ready. Begin running and processing data. 
franz   joined, balancing group   group: <CONSUMER_GROUP> 
franz   synced                    assigned: <TOPIC>[0] 
franz   assigning partitions      ... 

If you see NOT_COORDINATOR repeating, change client_id and group_id to a new name in values.yaml and re-run the helm3 upgrade command from step B.6. 

B.8 Send a test message and confirm in the Splunk platform

Produce a message from the VM (install kafkacat first if needed: sudo apt-get install -y kafkacat): 

echo "hello-from-microk8s-$(date -Is)" | kafkacat -P \ 
  -b <KAFKA_BOOTSTRAP> \ 
  -t <TOPIC> \ 
  -X security.protocol=SASL_SSL \ 
  -X sasl.mechanisms=PLAIN \ 
  -X sasl.username='<SASL_USERNAME>' \ 
  -X sasl.password='<OCI_AUTH_TOKEN>' \ 
  -X ssl.ca.location=/etc/ssl/certs/ca-certificates.crt 

Alternatively, use the OCI Console: StreamingStreamsselect streamProduce Test Message

In Splunk search: index=<SPLUNK_INDEX> sourcetype="oci:streaming:text" earliest=-5m

Verifying TLS on the Kafka leg

Both deployments encrypt the Kafka connection, but encryption isn't the same as verification. Verification means the collector actually checks the certificate chain and hostname. This is worth doing once you have data flowing. 

The collector is written in Go, and Go's TLS stack ignores the certificate's Common Name (CN) and requires a Subject Alternative Name (SAN) instead. A certificate with no SAN fails verification with x509: certificate relies on legacy Common Name field, use SANs instead, regardless of whether you connect by IP or hostname. 

The good news: OCI Streaming's broker certificate is issued by DigiCert (already trusted by the collector's CA bundle) and typically carries a SAN covering the broker's hostname. That's why both configs above already set insecure_skip_verify: false on the Kafka receiver. To confirm the SAN covers your specific broker hostname, run this from the VM (or a pod) that can reach the broker:

openssl s_client \ 
  -connect <KAFKA_BOOTSTRAP> \ 
  -servername <broker-fqdn> </dev/null 2>/dev/null \ 
  | openssl x509 -noout -ext subjectAltName 

If the SAN includes your broker's hostname (or a wildcard covering it), verification will pass with insecure_skip_verify: false, no further changes needed. If you see a trust error instead, you can force the system CA bundle explicitly: 

tls: 
  insecure_skip_verify: false 
  ca_file: /etc/ssl/certs/ca-certificates.crt

Troubleshooting quick reference

Symptom Cause Fix
NOT_COORDINATOR repeating on join  Consumer group ID left in a stale/poisoned state  Use a fresh group_id / client_id and redeploy 
password is required despite a correct secret  Token starts with a YAML-special char (for example, #); the collector's env provider re-parses it as YAML and the value collapses to null Regenerate the OCI auth token so it doesn't start with #, &, *, !, >, |, @, `, then recreate the secret and restart 
SASL authentication error: Authentication failed with a valid token Tenancy uses an OCI identity domain; the 3-part username is missing the domain segment  Use <tenancy-namespace>/<identity-domain>/<user>/<stream-pool-OCID> 
Splunk HEC exporter hangs / connection timed out Splunk host firewall doesn't allow inbound 8088 from the collector's IP  Open that inbound rule; confirm with nc -vz <SPLUNK_HEC_HOST> 8088 
(MicroK8s) Pods CrashLoop with no route to host to the ClusterIP  OCI Ubuntu image's FORWARD-chain REJECT blocks pod traffic  Remove the rule (see B.2); also check the INPUT-chain REJECT if Calico still crashes
(MicroK8s) Pod DNS times out or returns NXDOMAIN for the broker A public resolver (for example, 8.8.8.8) is mixed in with the OCI resolver Use only 169.254.169.254 as the CoreDNS upstream 
Broker closes connection: requires TLS but client is using plaintext 

tls: block was nested under

auth: instead of being top-level on the receiver 

Move tls: to be a top-level key of the kafka receiver 
max version 5 below the user defined min of 11 protocol_version pinned to a newer Kafka API version than OCI Streaming advertises  Don't set protocol_version at all 

Additional resources

The same collector binary and Helm chart work unmodified against other clouds and Kubernetes distributions, only connectivity and DNS configuration change. If OCI Streaming and your Splunk instance sit in different clouds or accounts, the concepts above still apply; you'll just need to handle DNS resolution and network routing between them.