Skip to content

Commit 743ed25

Browse files
committed
feat: otel collector profiling
1 parent dc3180c commit 743ed25

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
---
2+
title: "Setting up continuous profiling for open-telemetry collector"
3+
date: 2024-07-30
4+
tags:
5+
- programming
6+
- golang
7+
- monitoring
8+
---
9+
10+
One of the biggest problem we have found at work is that the speed of opentelemetry (otel) moves way faster than other parts of the infrastructure.
11+
Namely, the [opentelemtry collector](https://github.com/open-telemetry/opentelemetry-collector) (and the [contrib](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main)) usually has a release every 2 weeks.
12+
As the opentelemetry collector sits in either the data gathering or transport layer, it is essentially a requirement to keep up with the latest release due to bug fixes and/or performance improvements.
13+
For bug fixes, it is often easy to test in a non-production environment to verify.
14+
Performance improvements?? Especially ones on paper which may or may not hold for your particular workload, well, one solution is to do continuous profiling and track the changes in real time.
15+
This can be done by enabling the [pprof extension](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/extension/pprofextension) in the collector and [pyroscope](https://pyroscope.io/) which pulls the profiles and stores in a backend.
16+
17+
To demonstrate, We make use of the `v1beta1` of the OpenTelemetryCollector CRD which is only available after helm version `0.58.0`.
18+
Refer to the official [opentelemetry operator helm chart](https://github.com/open-telemetry/opentelemetry-helm-charts/tree/main/charts/opentelemetry-operator) for more details.
19+
20+
We install the base minimum for the operator
21+
22+
```shell
23+
# assuming we are already in kubernetes and is operating without the namespace otel
24+
helm install opentelemetry-operator open-telemetry/opentelemetry-operator \
25+
--set manager.collectorImage.repository=otel/opentelemetry-collector-k8s \
26+
--set admissionWebhooks.certManager.enabled=false \
27+
--set admissionWebhooks.autoGenerateCert.enabled=true
28+
```
29+
30+
before applying the CRO. Note that we have to specify the port explicitly because the operator is not able to recognize the port and mutate the svc/pod even if defined in the config.
31+
32+
```yaml
33+
apiVersion: opentelemetry.io/v1beta1
34+
kind: OpenTelemetryCollector
35+
metadata:
36+
name: pprof
37+
namespace: otel
38+
spec:
39+
config:
40+
receivers:
41+
otlp:
42+
protocols:
43+
grpc: {}
44+
http: {}
45+
exporters:
46+
debug: {}
47+
extensions:
48+
pprof: {}
49+
service:
50+
extensions: [pprof]
51+
pipelines:
52+
traces:
53+
receivers: [otlp]
54+
processors: []
55+
exporters: [debug]
56+
ports:
57+
- name: pprof
58+
port: 1777
59+
```
60+
61+
Now we have a collector running, all we got to do is setup pyroscope to be in ["pull mode"](https://grafana.com/docs/pyroscope/latest/configure-client/grafana-agent/go_pull/).
62+
The snippets of the pyroscope setup here forms the basis of our standard production upgrade where we begin continuous profiling 30 minutes before a change and ends 30 minutes after the change (be it a success or rollback).
63+
The general flow is pretty self-explanatory for anyone who is familiar with how Prometheus scrape works, as this is essentially the same but for profiles instead of metrics.
64+
65+
```river
66+
// Pod discovery, can also do service discovery.
67+
discovery.kubernetes "pods" {
68+
role = "pod"
69+
namespaces {
70+
names = ["otel"]
71+
}
72+
}
73+
// Filter and select the correct pods, and do "relabeling" to insert correct metadata into the profiles.
74+
discovery.relabel "otel" {
75+
targets = discovery.kubernetes.pods.targets
76+
rule {
77+
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_component"]
78+
regex = "opentelemetry-collector"
79+
action = "keep"
80+
}
81+
// Since the collector will have many ports open to receive traffic, we should filter on pods that
82+
// has pprof enabled, which by default is port `1777` in the pprof extension.
83+
rule {
84+
source_labels = ["__meta_kubernetes_pod_container_port_number"]
85+
regex = "1777"
86+
action = "keep"
87+
}
88+
rule {
89+
target_label = "__port__"
90+
action = "replace"
91+
replacement = "1777"
92+
}
93+
rule {
94+
source_labels = ["__address__"]
95+
target_label = "__address__"
96+
regex = "(.+):(\\d+)"
97+
action = "replace"
98+
replacement = "${1}"
99+
}
100+
rule {
101+
source_labels = ["__address__", "__port__"]
102+
target_label = "__address__"
103+
separator = "@"
104+
regex = "(.+)@(\\d+)"
105+
replacement = "$1:$2"
106+
action = "replace"
107+
}
108+
// Create standard labels so that is is easier to understand, these are prometheus conventions.
109+
rule {
110+
action = "replace"
111+
source_labels = ["__meta_kubernetes_namespace"]
112+
target_label = "namespace"
113+
}
114+
rule {
115+
action = "replace"
116+
source_labels = ["__meta_kubernetes_pod_name"]
117+
target_label = "pod"
118+
}
119+
rule {
120+
action = "replace"
121+
source_labels = ["__meta_kubernetes_node_name"]
122+
target_label = "node"
123+
}
124+
rule {
125+
action = "replace"
126+
source_labels = ["__meta_kubernetes_pod_container_name"]
127+
target_label = "container"
128+
}
129+
// Both service_name and service_version are opentelemetry conventions.
130+
rule {
131+
action = "replace"
132+
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_version"]
133+
target_label = "service_version"
134+
}
135+
rule {
136+
source_labels = ["__meta_kubernetes_namespace", "__meta_kubernetes_pod_label_app_kubernetes_io_name"]
137+
target_label = "service_name"
138+
separator = "@"
139+
regex = "(.*)@(.*)"
140+
replacement = "${1}/${2}"
141+
action = "replace"
142+
}
143+
// Always good to have some sort of unique identifier to track changes through time. This is the
144+
// sha of the config which the operator computes for us.
145+
rule {
146+
action = "replace"
147+
source_labels = ["__meta_kubernetes_pod_annotation_opentelemetry_operator_config_sha256"]
148+
target_label = "config_sha"
149+
}
150+
}
151+
```
152+
153+
then we define the backend which we want to ship to
154+
155+
```river
156+
pyroscope.write "backend" {
157+
endpoint {
158+
url = env("PYROSCOPE_URL")
159+
basic_auth {
160+
username = env("PYROSCOPE_USERNAME")
161+
password = env("PYROSCOPE_PASSWORD")
162+
}
163+
}
164+
}
165+
```
166+
167+
and finally we define the pipeline which chains all the stages `discovery` -> `relabel` -> `scrape` -> `export`.
168+
169+
```river
170+
pyroscope.scrape "otel_settings" {
171+
targets = [discovery.relabel.otel]
172+
forward_to = [pyroscope.write.backend.receiver]
173+
profiling_config {
174+
profile.goroutine {
175+
enabled = true
176+
path = "/debug/pprof/goroutine"
177+
delta = false
178+
}
179+
profile.process_cpu {
180+
enabled = true
181+
path = "/debug/pprof/profile"
182+
delta = true
183+
}
184+
profile.godeltaprof_memory {
185+
enabled = false
186+
path = "/debug/pprof/delta_heap"
187+
}
188+
profile.memory {
189+
enabled = true
190+
path = "/debug/pprof/heap"
191+
delta = false
192+
}
193+
profile.godeltaprof_mutex {
194+
enabled = false
195+
path = "/debug/pprof/delta_mutex"
196+
}
197+
profile.mutex {
198+
enabled = false
199+
path = "/debug/pprof/mutex"
200+
delta = false
201+
}
202+
profile.godeltaprof_block {
203+
enabled = false
204+
path = "/debug/pprof/delta_block"
205+
}
206+
profile.block {
207+
enabled = false
208+
path = "/debug/pprof/block"
209+
delta = false
210+
}
211+
}
212+
}
213+
```
214+
215+
Applying all of the above during and upgrade to `v0.101.0` of the opentelemetry collector contrib where the loadbalancing exporter gain a massive improvements, straight diff:
216+
217+
![profile-diff](/images/2024-07-30-profile-diff-101.png.png)
218+
219+
and when focused on the `mergeTrace` function where the upgrade took place
220+
221+
![merge-traces](/images/2024-07-30-merge-traces-diff-101.png)
222+
223+
allowed us to confirm that the performance enhancement in the CHANGELOG matches (and exceeds) our expectation.
224+
Furthermore, by understanding how the collector behaves, we were able to fine tune the resources and various settings to improve the stability of our system.
181 KB
Loading
386 KB
Loading

0 commit comments

Comments
 (0)