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/PLAINon port9092. 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
kafkareceiver hands records to aresourcedetectionprocessor, which tags each record with host/system metadata before it's exported. - Sink - the
splunk_hecexporter 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@ |
| 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 withSASL authentication error: Authentication faileddespite 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 aroundKAFKA_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: |
| 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: Streaming → Streams → select stream → Produce 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 |
|
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
- Splunk GitHub: SOC4Kafka Installation Guide — OCI Streaming to Splunk - the full command-by-command reference this post is based on, including how to provision the OCI VCN, subnets, and Stream Pool from scratch.
- Splunk GitHub: Collecting events from multiple topics - consume from more than one topic.
- Splunk GitHub: Scaling SOC4Kafka - scale out horizontally using Kafka consumer groups and partitions.
- Splunk GitHub: Troubleshooting - general SOC4Kafka troubleshooting guides.
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.

