OpenTelemetry : standard universel pour l’observabilité
OpenTelemetry (OTel) est le standard CNCF pour la collecte de logs, métriques et traces distribuées. Il unifie des agents auparavant disparates (Fluentd, Jaeger, Prometheus exporters) en un seul pipeline configurable.
L’OpenTelemetry Collector est le composant central : il reçoit des données de multiples sources, les transforme (filtrage, enrichissement, routage), puis les exporte vers plusieurs backends simultanément.
Architecture visée dans cet article :
Pods (OTLP) ──────────────────────────┐
Nodes (kubelet, journal) ─────────────┤
▼
OTel Collector (DaemonSet + Deployment)
│
┌───────────────┴──────────────────┐
▼ ▼
Kafka Topic Splunk HEC
(logs applicatifs (logs critiques,
par namespace) audit, sécurité)
Installation via Helm
Ajouter le dépôt OpenTelemetry
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
# Voir les versions disponibles
helm search repo open-telemetry/opentelemetry-collector --versions | head -5
Créer le namespace et les secrets
oc new-project opentelemetry
# Secret pour Splunk HEC
oc create secret generic splunk-hec-secret \
--from-literal=token=<votre-hec-token> \
-n opentelemetry
# Secret pour les credentials Kafka (si SASL activé)
oc create secret generic kafka-credentials \
--from-literal=username=otel-producer \
--from-literal=password=<mot-de-passe-kafka> \
-n opentelemetry
SCC pour le DaemonSet (lecture des logs nœuds)
# Le DaemonSet doit lire /var/log sur les nœuds
oc adm policy add-scc-to-user privileged \
-z opentelemetry-collector \
-n opentelemetry
Configuration du Collector : values.yaml
Le fichier values.yaml est le cœur du déploiement. Il configure les receivers, processors et exporters.
# values.yaml — OpenTelemetry Collector sur OpenShift
mode: daemonset # DaemonSet pour la collecte sur chaque nœud
# Utiliser "deployment" pour un collector centralisé
image:
repository: otel/opentelemetry-collector-contrib
tag: "0.113.0"
serviceAccount:
create: true
name: opentelemetry-collector
# Monter les logs des nœuds
extraVolumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
extraVolumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
# Ressources du Collector
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
config:
# ── RECEIVERS ────────────────────────────────────────────
receivers:
# Réception OTLP depuis les applications instrumentées
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# Collecte des logs des conteneurs via filelog
filelog:
include:
- /var/log/pods/*/*/*.log
exclude:
- /var/log/pods/opentelemetry_*/*/*.log # Éviter boucle de collecte
start_at: beginning
include_file_path: true
include_file_name: false
operators:
# Parser JSON (format CRI-O sur OpenShift)
- type: json_parser
id: parser-docker
on_error: send
timestamp:
parse_from: attributes.time
layout: '%Y-%m-%dT%H:%M:%S.%LZ'
# Extraire les métadonnées du chemin de fichier
- type: regex_parser
id: extract-metadata-from-filepath
regex: '^.*\/(?P<namespace>[^_]+)_(?P<pod_name>[^_]+)_(?P<uid>[a-f0-9\-]+)\/(?P<container_name>[^\._]+)\/(?P<restart_count>\d+)\.log$'
parse_from: attributes["log.file.path"]
cache:
size: 128
- type: move
from: attributes.namespace
to: resource["k8s.namespace.name"]
- type: move
from: attributes.pod_name
to: resource["k8s.pod.name"]
- type: move
from: attributes.container_name
to: resource["k8s.container.name"]
# Métriques Kubernetes depuis le kubelet
kubeletstats:
collection_interval: 30s
auth_type: serviceAccount
endpoint: "https://${env:MY_NODE_NAME}:10250"
insecure_skip_verify: true
metric_groups:
- node
- pod
- container
# ── PROCESSORS ───────────────────────────────────────────
processors:
# Enrichissement avec les métadonnées Kubernetes (labels, annotations)
k8sattributes:
auth_type: serviceAccount
passthrough: false
filter:
node_from_env_var: MY_NODE_NAME
extract:
metadata:
- k8s.namespace.name
- k8s.pod.name
- k8s.pod.uid
- k8s.node.name
- k8s.deployment.name
- k8s.statefulset.name
- k8s.daemonset.name
- k8s.container.name
- container.image.name
- container.image.tag
# IMPORTANT : extraire les labels du namespace pour le filtrage
labels:
- tag_name: ns.environment
key: environment
from: namespace
- tag_name: ns.team
key: team
from: namespace
- tag_name: ns.tier
key: tier
from: namespace
- tag_name: ns.log-level
key: log-forwarding-level
from: namespace
annotations:
- tag_name: ns.owner
key: owner
from: namespace
# Filtrage par niveau de criticité défini sur le namespace
filter/critical-namespaces:
logs:
include:
match_type: expr
expressions:
# Inclure si le namespace est labellisé "critical" ou "security"
- 'resource["ns.tier"] == "critical" or resource["ns.tier"] == "security"'
filter/standard-namespaces:
logs:
include:
match_type: expr
expressions:
# Inclure les namespaces applicatifs standards (pas critiques)
- 'resource["ns.tier"] == "standard" or resource["ns.tier"] == "dev"'
exclude:
match_type: expr
expressions:
# Exclure les namespaces systèmes
- 'resource["k8s.namespace.name"] matches "^kube-" or resource["k8s.namespace.name"] == "openshift-monitoring"'
filter/high-severity:
logs:
include:
match_type: expr
expressions:
# Envoyer vers Splunk uniquement les logs WARN/ERROR/CRITICAL
- 'severity_number >= SEVERITY_NUMBER_WARN'
# Transformation et enrichissement
transform/add-cluster-info:
log_statements:
- context: resource
statements:
- set(attributes["k8s.cluster.name"], "production-cluster-paris")
- set(attributes["k8s.cluster.env"], "production")
# Batch pour optimiser l'envoi
batch:
send_batch_size: 1000
timeout: 5s
send_batch_max_size: 2000
# Limitation de débit (éviter la saturation)
memory_limiter:
check_interval: 1s
limit_mib: 400
spike_limit_mib: 128
# ── EXPORTERS ────────────────────────────────────────────
exporters:
# Export vers Kafka — logs applicatifs par namespace
kafka/app-logs:
brokers:
- kafka-broker-1.kafka.svc.cluster.local:9092
- kafka-broker-2.kafka.svc.cluster.local:9092
- kafka-broker-3.kafka.svc.cluster.local:9092
topic: otel.logs.applications
encoding: otlp_json
auth:
sasl:
username: "${env:KAFKA_USERNAME}"
password: "${env:KAFKA_PASSWORD}"
mechanism: SCRAM-SHA-512
tls:
insecure: false
ca_file: /etc/ssl/kafka/ca.crt
producer:
max_message_bytes: 1000000
compression: snappy
timeout: 10s
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 120s
# Export vers Splunk HEC — logs critiques/sécurité
splunk_hec/security-logs:
endpoint: "https://splunk.entreprise.com:8088/services/collector"
token: "${env:SPLUNK_HEC_TOKEN}"
source: "opentelemetry"
sourcetype: "kube:container:otel"
index: "security_prod"
tls:
insecure_skip_verify: false
ca_file: /etc/ssl/splunk/ca.crt
max_content_length_logs: 2097152 # 2 MB
disable_compression: false
heartbeat:
interval: 60s
telemetry:
enabled: true
override_metrics_names:
otelcol_exporter_queue_size: true
# Export Kafka pour les métriques (topic séparé)
kafka/metrics:
brokers:
- kafka-broker-1.kafka.svc.cluster.local:9092
- kafka-broker-2.kafka.svc.cluster.local:9092
topic: otel.metrics.cluster
encoding: otlp_json
producer:
compression: lz4
# Export de débogage (désactiver en prod)
debug:
verbosity: basic
# ── PIPELINES ────────────────────────────────────────────
service:
pipelines:
# Pipeline : logs applicatifs → Kafka
logs/to-kafka:
receivers: [filelog]
processors:
- memory_limiter
- k8sattributes
- transform/add-cluster-info
- filter/standard-namespaces
- batch
exporters: [kafka/app-logs]
# Pipeline : logs critiques/sécurité → Splunk
logs/to-splunk:
receivers: [filelog, otlp]
processors:
- memory_limiter
- k8sattributes
- transform/add-cluster-info
- filter/critical-namespaces
- filter/high-severity
- batch
exporters: [splunk_hec/security-logs]
# Pipeline : métriques cluster → Kafka
metrics/to-kafka:
receivers: [kubeletstats, otlp]
processors:
- memory_limiter
- k8sattributes
- batch
exporters: [kafka/metrics]
# Pipeline : traces → OTLP (ex. Jaeger ou Tempo)
traces:
receivers: [otlp]
processors:
- memory_limiter
- k8sattributes
- batch
exporters: [debug]
telemetry:
logs:
level: info
metrics:
address: 0.0.0.0:8888
Déployer avec Helm
helm install otel-collector open-telemetry/opentelemetry-collector \
--namespace opentelemetry \
--values values.yaml \
--set env[0].name=MY_NODE_NAME \
--set env[0].valueFrom.fieldRef.fieldPath=spec.nodeName \
--set env[1].name=SPLUNK_HEC_TOKEN \
--set env[1].valueFrom.secretKeyRef.name=splunk-hec-secret \
--set env[1].valueFrom.secretKeyRef.key=token \
--set env[2].name=KAFKA_USERNAME \
--set env[2].valueFrom.secretKeyRef.name=kafka-credentials \
--set env[2].valueFrom.secretKeyRef.key=username \
--set env[3].name=KAFKA_PASSWORD \
--set env[3].valueFrom.secretKeyRef.name=kafka-credentials \
--set env[3].valueFrom.secretKeyRef.key=password
Labelliser les namespaces pour le filtrage
Le routage vers Kafka ou Splunk est piloté par les labels du namespace. C’est le mécanisme clé de cet article.
# Namespace applicatif standard → Kafka
oc label namespace mon-app \
tier=standard \
team=backend \
environment=production \
log-forwarding-level=standard
# Namespace de sécurité/audit → Splunk
oc label namespace openshift-authentication \
tier=security \
team=ops \
log-forwarding-level=critical
# Namespace de production critique → Splunk
oc label namespace payments \
tier=critical \
team=fintech \
environment=production \
log-forwarding-level=critical
# Namespace de dev → Kafka, logs moins stricts
oc label namespace dev-feature-x \
tier=dev \
team=frontend \
environment=development
Vérifier les labels d’un namespace
oc get namespace mon-app --show-labels
oc describe namespace payments | grep Labels -A 10
Déploiement centralisé (Gateway Pattern)
Pour les environnements avec de nombreux clusters, ajouter un Collector Gateway centralisé :
# values-gateway.yaml
mode: deployment
replicaCount: 3
config:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
processors:
batch:
send_batch_size: 5000
timeout: 10s
exporters:
kafka/all-logs:
brokers: [kafka-broker:9092]
topic: otel.logs.all-clusters
encoding: otlp_json
producer:
compression: snappy
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [kafka/all-logs]
helm install otel-gateway open-telemetry/opentelemetry-collector \
--namespace opentelemetry \
--values values-gateway.yaml
Les DaemonSets des clusters satellites envoient vers le Gateway via OTLP :
exporters:
otlp/gateway:
endpoint: "otel-gateway.opentelemetry.svc.cluster.local:4317"
tls:
insecure: false
Vérification et debug
# Vérifier que les pods tournent
oc get pods -n opentelemetry
# Logs du Collector
oc logs -f daemonset/otel-collector-opentelemetry-collector -n opentelemetry
# Métriques internes du Collector (port 8888)
oc port-forward svc/otel-collector-opentelemetry-collector 8888:8888 -n opentelemetry
curl localhost:8888/metrics | grep otelcol_exporter
# Vérifier les messages dans Kafka
oc exec -it kafka-broker-0 -n kafka -- \
kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic otel.logs.applications \
--max-messages 5
Mise à jour via Helm
# Mettre à jour la configuration
helm upgrade otel-collector open-telemetry/opentelemetry-collector \
--namespace opentelemetry \
--values values.yaml \
--reuse-values
# Rollback si problème
helm rollback otel-collector -n opentelemetry
Conclusion
OpenTelemetry Collector sur OpenShift offre une flexibilité maximale pour router les données d’observabilité. L’utilisation des labels de namespace comme critère de filtrage est une approche élégante et déclarative : les équipes applicatives labellisent leurs namespaces, et le Collector route automatiquement vers le bon backend. Cette architecture découple la stratégie de collecte (définie par ops) des applications (ignorantes du backend d’observabilité), tout en permettant d’alimenter simultanément Kafka pour l’analyse temps-réel et Splunk pour la sécurité et la conformité.