1# =============================================================================
2# OpenTelemetry Collector (contrib) — Deployment + Service, ns `monitoring`.
3#
4# The four apps' OTel SDKs export OTLP/HTTP to this collector (the
5# factory-config configmap, infrastructure/kubernetes/configmap.yaml:
6# OTEL_ENABLED: "true" (flipped on in staging)
7# OTEL_EXPORTER_OTLP_ENDPOINT: http:
8# — that FQDN is why this stack must live in namespace `monitoring`; see the
9# kustomization.yaml header in this directory for the full rationale).
10#
11# Pipeline (rendered inline — kept minimal + commented):
12# receivers: otlp (http 4318)
13# processors: batch + memory_limiter
14# traces: otlp → batch → otlp out (endpoint placeholder — Jaeger/Tempo/APM)
15# logs: otlp → batch → debug (staging only; switch to a real exporter)
16#
17# METRICS DOUBLE-COUNT GUARD: with OTEL_ENABLED=true the apps run BOTH an
18# OTLP metrics push (30 s periodic reader → this collector's /v1/metrics,
19# see packages/observability/src/otel.ts) AND a serverless Prometheus reader
20# (each app's own /metrics route, scraped by the factory-* jobs in
21# prometheus.yml). Feeding BOTH into the same Prometheus would double-count
22# every series. Staging default: the direct-scrape jobs (prometheus.yml)
23# are the metrics path and this collector's metrics pipeline is DISABLED
24# (commented below) — the collector carries TRACES + logs only. To switch
25# to collector-scrape: enable the prometheus exporter + add the
26# `otel-collector` scrape job, and REMOVE the factory-* scrape jobs.
27#
28# Traces: OTLP/HTTP in → batch → OTLP out (the endpoint is a placeholder —
29# point it at Jaeger/Tempo or a vendor; the apps tolerate an unreachable
30# trace pipeline: initOtel logs the export failure and continues).
31# Image pinned: otel/opentelemetry-collector-contrib:0.98.0 (0.98.x — the
32# line whose OTLP proto matches the SDK 1.30/0.57 exporter train the apps
33# pin; see the TASK-0135 report).
34# =============================================================================
35apiVersion: apps/v1
36kind: Deployment
37metadata:
38 name: otel-collector
39 namespace: monitoring
40 labels:
41 app.kubernetes.io/name: otel-collector
42 app.kubernetes.io/part-of: ai-software-factory
43spec:
44 replicas: 1
45 selector:
46 matchLabels:
47 app.kubernetes.io/name: otel-collector
48 strategy:
49 type: Recreate
50 template:
51 metadata:
52 labels:
53 app.kubernetes.io/name: otel-collector
54 spec:
55 containers:
56 - name: otel-collector
57 image: otel/opentelemetry-collector-contrib:0.98.0
58 imagePullPolicy: IfNotPresent
59 args:
60 - --config=/etc/otelcol/config.yaml
61 ports:
62 - containerPort: 4318
63 name: otlp-http
64 volumeMounts:
65 - name: config
66 mountPath: /etc/otelcol
67 readOnly: true
68 livenessProbe:
69 httpGet:
70 path: /
71 port: 13133
72 initialDelaySeconds: 15
73 periodSeconds: 15
74 readinessProbe:
75 httpGet:
76 path: /
77 port: 13133
78 initialDelaySeconds: 5
79 periodSeconds: 10
80 resources:
81 requests:
82 cpu: 100m
83 memory: 256Mi
84 limits:
85 cpu: 500m
86 memory: 1Gi
87 securityContext:
88 allowPrivilegeEscalation: false
89 runAsNonRoot: true
90 # the contrib image runs as `otel` (uid 1000)
91 runAsUser: 1000
92 runAsGroup: 1000
93 seccompProfile:
94 type: RuntimeDefault
95 volumes:
96 - name: config
97 configMap:
98 name: otel-collector-config
99---
100apiVersion: v1
101kind: ConfigMap
102metadata:
103 name: otel-collector-config
104 namespace: monitoring
105 labels:
106 app.kubernetes.io/name: otel-collector
107 app.kubernetes.io/part-of: ai-software-factory
108data:
109 config.yaml: |
110 receivers:
111 otlp:
112 protocols:
113 http:
114 endpoint: 0.0.0.0:4318
115 # (a `prometheus` receiver scraping the collector's own :8888 goes here
116 # if the metrics pipeline below is enabled)
117
118 processors:
119 memory_limiter:
120 check_interval: 1s
121 limit_percentage: 80
122 spike_limit_percentage: 20
123 batch:
124 timeout: 5s
125 send_batch_size: 1024
126
127 exporters:
128 debug:
129 verbosity: basic
130 otlp/traces:
131 endpoint: trace-otlp.monitoring.svc.cluster.local:4317 # PLACEHOLDER: Jaeger/Tempo/your APM
132 tls:
133 insecure: true
134 # prometheus: # DISABLED by default — the apps are scraped DIRECTLY
135 # endpoint: 0.0.0.0:8888 # (jobs factory-* in prometheus.yml). Enable
136 # metrics_endpoint: /metrics # this AND remove the factory-* scrape
137 # force_flush_interval: 10s # jobs if you switch to collector-scrape.
138
139 service:
140 pipelines:
141 traces:
142 receivers: [otlp]
143 processors: [memory_limiter, batch]
144 exporters: [otlp/traces, debug]
145 # metrics: # see the note on the prometheus exporter above
146 # receivers: [otlp]
147 # processors: [memory_limiter, batch]
148 # exporters: [prometheus]
149 logs:
150 receivers: [otlp]
151 processors: [memory_limiter, batch]
152 exporters: [debug]
153---
154apiVersion: v1
155kind: Service
156metadata:
157 name: otel-collector
158 namespace: monitoring
159 labels:
160 app.kubernetes.io/name: otel-collector
161 app.kubernetes.io/part-of: ai-software-factory
162spec:
163 selector:
164 app.kubernetes.io/name: otel-collector
165 ports:
166 - name: otlp-http
167 port: 4318
168 targetPort: 4318
169