Skip to content

Commit 35eaf61

Browse files
authored
distro: add cloud resource detectors (#198)
* distro: add cloud resource detectors This add some code to dinamically filter the available resource detectors in order to avoid any eventual costly detection. * Remove container resource detector that has not been published * Update distributoin default settings * Update README.md * Update dev-requirements with the dependencies
1 parent 598a58e commit 35eaf61

File tree

7 files changed

+149
-6
lines changed

7 files changed

+149
-6
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,14 @@ This distribution sets the following defaults:
7878

7979
- `OTEL_TRACES_EXPORTER`: `otlp`
8080
- `OTEL_METRICS_EXPORTER`: `otlp`
81+
- `OTEL_LOGS_EXPORTER`: `otlp`
8182
- `OTEL_EXPORTER_OTLP_PROTOCOL`: `grpc`
82-
- `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS`: `process_runtime,os,otel,telemetry_distro`
83+
- `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS`: `process_runtime,os,otel,telemetry_distro,_gcp,aws_ec2,aws_ecs,aws_elastic_beanstalk,azure_app_service,azure_vm`
8384
- `OTEL_METRICS_EXEMPLAR_FILTER`: `always_off`
85+
- `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`: `DELTA`
86+
87+
> [!NOTE]
88+
> `OTEL_EXPERIMENTAL_RESOURCE_DETECTORS` cloud resource detectors are dynamically set. When running in a Kubernetes Pod it will be set to `process_runtime,os,otel,telemetry_distro,_gcp,aws_eks`.
8489
8590
### Distribution specific configuration variables
8691

dev-requirements.txt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ opentelemetry-api==1.28.2
4343
# opentelemetry-exporter-otlp-proto-http
4444
# opentelemetry-instrumentation
4545
# opentelemetry-instrumentation-system-metrics
46+
# opentelemetry-resourcedetector-gcp
4647
# opentelemetry-sdk
4748
# opentelemetry-semantic-conventions
4849
# oteltest
@@ -68,11 +69,20 @@ opentelemetry-proto==1.28.2
6869
# opentelemetry-exporter-otlp-proto-grpc
6970
# opentelemetry-exporter-otlp-proto-http
7071
# oteltest
72+
opentelemetry-resource-detector-azure==0.1.5
73+
# via elastic-opentelemetry (pyproject.toml)
74+
opentelemetry-resourcedetector-gcp==1.7.0a0
75+
# via elastic-opentelemetry (pyproject.toml)
7176
opentelemetry-sdk==1.28.2
7277
# via
7378
# elastic-opentelemetry (pyproject.toml)
7479
# opentelemetry-exporter-otlp-proto-grpc
7580
# opentelemetry-exporter-otlp-proto-http
81+
# opentelemetry-resource-detector-azure
82+
# opentelemetry-resourcedetector-gcp
83+
# opentelemetry-sdk-extension-aws
84+
opentelemetry-sdk-extension-aws==2.0.2
85+
# via elastic-opentelemetry (pyproject.toml)
7686
opentelemetry-semantic-conventions==0.49b2
7787
# via
7888
# elastic-opentelemetry (pyproject.toml)
@@ -83,6 +93,7 @@ oteltest==0.24.0
8393
packaging==24.2
8494
# via
8595
# build
96+
# elastic-opentelemetry (pyproject.toml)
8697
# opentelemetry-instrumentation
8798
# pytest
8899
pip-tools==7.4.1
@@ -103,14 +114,18 @@ pyproject-hooks==1.2.0
103114
pytest==8.3.4
104115
# via elastic-opentelemetry (pyproject.toml)
105116
requests==2.32.3
106-
# via opentelemetry-exporter-otlp-proto-http
117+
# via
118+
# opentelemetry-exporter-otlp-proto-http
119+
# opentelemetry-resourcedetector-gcp
107120
tomli==2.2.1
108121
# via
109122
# build
110123
# pip-tools
111124
# pytest
112125
typing-extensions==4.12.2
113-
# via opentelemetry-sdk
126+
# via
127+
# opentelemetry-resourcedetector-gcp
128+
# opentelemetry-sdk
114129
urllib3==2.2.3
115130
# via requests
116131
wheel==0.45.1

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ dependencies = [
3131
"opentelemetry-exporter-otlp == 1.28.2",
3232
"opentelemetry-instrumentation == 0.49b2",
3333
"opentelemetry-instrumentation-system-metrics == 0.49b2",
34-
"opentelemetry-semantic-conventions == 0.49b2",
34+
"opentelemetry-resourcedetector-gcp ~= 1.7.0a0",
35+
"opentelemetry-resource-detector-azure ~= 0.1.5",
3536
"opentelemetry-sdk == 1.28.2",
37+
"opentelemetry-sdk-extension-aws ~= 2.0.2",
38+
"opentelemetry-semantic-conventions == 0.49b2",
3639
"packaging",
3740
]
3841

@@ -48,6 +51,7 @@ distro = "elasticotel.distro:ElasticOpenTelemetryDistro"
4851
[project.entry-points.opentelemetry_resource_detector]
4952
process_runtime = "elasticotel.sdk.resources:ProcessRuntimeResourceDetector"
5053
telemetry_distro = "elasticotel.sdk.resources:TelemetryDistroResourceDetector"
54+
_gcp = "opentelemetry.resourcedetector.gcp_resource_detector._detector:GoogleCloudResourceDetector"
5155

5256
[project.scripts]
5357
edot-bootstrap = "elasticotel.instrumentation.bootstrap:run"

src/elasticotel/distro/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from opentelemetry.util._importlib_metadata import EntryPoint
5757

5858
from elasticotel.distro.environment_variables import ELASTIC_OTEL_SYSTEM_METRICS_ENABLED
59+
from elasticotel.distro.resource_detectors import get_cloud_resource_detectors
5960

6061

6162
logger = logging.getLogger(__name__)
@@ -139,8 +140,11 @@ def _configure(self, **kwargs):
139140
os.environ.setdefault(OTEL_METRICS_EXPORTER, "otlp")
140141
os.environ.setdefault(OTEL_LOGS_EXPORTER, "otlp")
141142
os.environ.setdefault(OTEL_EXPORTER_OTLP_PROTOCOL, "grpc")
142-
os.environ.setdefault(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, "process_runtime,os,otel,telemetry_distro")
143143
# disable exemplars by default for now
144144
os.environ.setdefault(OTEL_METRICS_EXEMPLAR_FILTER, "always_off")
145145
# preference to use DELTA temporality as we can handle only this kind of Histograms
146146
os.environ.setdefault(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE, "DELTA")
147+
148+
base_resource_detectors = ["process_runtime", "os", "otel", "telemetry_distro"]
149+
detectors = base_resource_detectors + get_cloud_resource_detectors()
150+
os.environ.setdefault(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS, ",".join(detectors))
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
# or more contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import os
18+
19+
AWS_LAMBDA_DETECTORS = ["aws_lambda"]
20+
AZURE_FUNCTIONS_DETECTORS = ["azure_functions"]
21+
GCP_CLOUD_RUN_DETECTORS = ["_gcp"]
22+
KUBERNETES_DETECTORS = ["_gcp", "aws_eks"]
23+
OTHER_CLOUD_DETECTORS = [
24+
"_gcp",
25+
"aws_ec2",
26+
"aws_ecs",
27+
"aws_elastic_beanstalk",
28+
"azure_app_service",
29+
"azure_vm",
30+
]
31+
32+
33+
def _on_aws_lambda():
34+
"""Cheap check to detect if we are running on AWS lambda"""
35+
return "AWS_LAMBDA_FUNCTION_NAME" in os.environ
36+
37+
38+
def _on_azure_functions():
39+
"""Cheap check to detect if we are running on Azure functions"""
40+
return "FUNCTIONS_WORKER_RUNTIME" in os.environ
41+
42+
43+
def _on_gcp_cloud_run():
44+
"""Cheap check to detect if we are running inside Google Cloud Run"""
45+
return "K_CONFIGURATION" in os.environ
46+
47+
48+
def _on_k8s():
49+
"""Cheap check to detect if we are running inside a Kubernetes pod or not"""
50+
return "KUBERNETES_SERVICE_HOST" in os.environ
51+
52+
53+
def get_cloud_resource_detectors():
54+
"""Helper to get a subset of the available cloud resource detectors depending on the environment
55+
56+
This is done to avoid loading resource detectors doing HTTP requests for metadata that will fail"""
57+
if _on_aws_lambda():
58+
return AWS_LAMBDA_DETECTORS
59+
elif _on_azure_functions():
60+
return AZURE_FUNCTIONS_DETECTORS
61+
elif _on_gcp_cloud_run():
62+
return GCP_CLOUD_RUN_DETECTORS
63+
elif _on_k8s():
64+
return KUBERNETES_DETECTORS
65+
return OTHER_CLOUD_DETECTORS

tests/distro/test_distro.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def test_default_configuration(self):
4242
self.assertEqual("otlp", os.environ.get(OTEL_LOGS_EXPORTER))
4343
self.assertEqual("grpc", os.environ.get(OTEL_EXPORTER_OTLP_PROTOCOL))
4444
self.assertEqual(
45-
"process_runtime,os,otel,telemetry_distro", os.environ.get(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS)
45+
"process_runtime,os,otel,telemetry_distro,_gcp,aws_ec2,aws_ecs,aws_elastic_beanstalk,azure_app_service,azure_vm",
46+
os.environ.get(OTEL_EXPERIMENTAL_RESOURCE_DETECTORS),
4647
)
4748
self.assertEqual("always_off", os.environ.get(OTEL_METRICS_EXEMPLAR_FILTER))
4849
self.assertEqual("DELTA", os.environ.get(OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
# or more contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright
4+
# ownership. Elasticsearch B.V. licenses this file to you under
5+
# the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
from unittest import TestCase, mock
18+
19+
from elasticotel.distro.resource_detectors import get_cloud_resource_detectors
20+
21+
22+
class TestGetCloudResourceDetectors(TestCase):
23+
@mock.patch.dict("os.environ", {"AWS_LAMBDA_FUNCTION_NAME": "lambda"}, clear=True)
24+
def test_aws_lambda(self):
25+
resource_detectors = get_cloud_resource_detectors()
26+
self.assertEqual(resource_detectors, ["aws_lambda"])
27+
28+
@mock.patch.dict("os.environ", {"FUNCTIONS_WORKER_RUNTIME": "azure"}, clear=True)
29+
def test_azure_functions(self):
30+
resource_detectors = get_cloud_resource_detectors()
31+
self.assertEqual(resource_detectors, ["azure_functions"])
32+
33+
@mock.patch.dict("os.environ", {"K_CONFIGURATION": "cloudrun"}, clear=True)
34+
def test_gcp_cloud_run(self):
35+
resource_detectors = get_cloud_resource_detectors()
36+
self.assertEqual(resource_detectors, ["_gcp"])
37+
38+
@mock.patch.dict("os.environ", {"KUBERNETES_SERVICE_HOST": "k8s"}, clear=True)
39+
def test_kubernetes_pod(self):
40+
resource_detectors = get_cloud_resource_detectors()
41+
self.assertEqual(resource_detectors, ["_gcp", "aws_eks"])
42+
43+
@mock.patch.dict("os.environ", {}, clear=True)
44+
def test_other_cloud_detectors(self):
45+
resource_detectors = get_cloud_resource_detectors()
46+
self.assertEqual(
47+
resource_detectors,
48+
["_gcp", "aws_ec2", "aws_ecs", "aws_elastic_beanstalk", "azure_app_service", "azure_vm"],
49+
)

0 commit comments

Comments
 (0)