Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[codespell]
# skipping auto generated folders
skip = ./.tox,./.mypy_cache,./docs/_build,./target,*/LICENSE,./venv,*/cassettes
ignore-words-list = ot
ignore-words-list = aks,ot
Comment thread
JacksonWeber marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-resource-detector-azure`: add AKS resource detector
44 changes: 41 additions & 3 deletions resource/opentelemetry-resource-detector-azure/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ OpenTelemetry Resource detectors for Azure
:target: https://pypi.org/project/opentelemetry-resource-detector-azure/

This library contains OpenTelemetry `Resource Detectors <https://opentelemetry.io/docs/specs/otel/resource/sdk/#detecting-resource-information-from-the-environment>`_ for the following Azure resources:
* `Azure Kubernetes Service <https://azure.microsoft.com/en-us/products/kubernetes-service>`_
* `Azure App Service <https://azure.microsoft.com/en-us/products/app-service>`_
* `Azure Virtual Machines <https://azure.microsoft.com/en-us/products/virtual-machines>`_
* `Azure Functions (Experimental) <https://azure.microsoft.com/en-us/products/functions>`_
Expand All @@ -26,10 +27,9 @@ Usage example for ``opentelemetry-resource-detector-azure``

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.resource.detector.azure.app_service import (
from opentelemetry.resource.detector.azure import (
AzureAKSResourceDetector,
AzureAppServiceResourceDetector,
)
from opentelemetry.resource.detector.azure.vm import (
AzureVMResourceDetector,
)
from opentelemetry.sdk.resources import get_aggregated_resources
Expand All @@ -39,6 +39,7 @@ Usage example for ``opentelemetry-resource-detector-azure``
TracerProvider(
resource=get_aggregated_resources(
[
AzureAKSResourceDetector(),
AzureAppServiceResourceDetector(),
AzureVMResourceDetector(),
]
Expand All @@ -49,6 +50,43 @@ Usage example for ``opentelemetry-resource-detector-azure``
Mappings
--------

The Azure Kubernetes Service Resource Detector reads the cluster resource ID from the
``CLUSTER_RESOURCE_ID`` environment variable or from a mounted ``aks-cluster-metadata``
ConfigMap at ``/etc/kubernetes/aks-cluster-metadata``. It sets the following Resource
Attributes:

* ``cloud.platform`` set to ``azure_aks``.
* ``cloud.provider`` set to ``azure``.
* ``cloud.resource_id`` set to the full Azure Resource Manager cluster resource ID.

The native AKS ConfigMap is named ``aks-cluster-metadata`` and contains a
``clusterResourceId`` key. It can be exposed to a pod as an environment variable:

.. code-block:: yaml

env:
- name: CLUSTER_RESOURCE_ID
valueFrom:
configMapKeyRef:
name: aks-cluster-metadata
key: clusterResourceId

Alternatively, mount the ConfigMap as a volume:

.. code-block:: yaml

volumes:
- name: aks-cluster-metadata
configMap:
name: aks-cluster-metadata
volumeMounts:
- name: aks-cluster-metadata
mountPath: /etc/kubernetes/aks-cluster-metadata

Kubernetes resolves ConfigMap references within the pod's namespace. Because the native
ConfigMap is in ``kube-public``, copy it into the workload namespace or use tooling such
as an init container to expose its value through one of the supported locations.

The Azure App Service Resource Detector sets the following Resource Attributes:
* ``service.name`` set to the value of the ``WEBSITE_SITE_NAME`` environment variable.
* ``cloud.platform`` set to ``azure_app_service``.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
]

[project.entry-points.opentelemetry_resource_detector]
azure_aks = "opentelemetry.resource.detector.azure.aks:AzureAKSResourceDetector"
azure_app_service = "opentelemetry.resource.detector.azure.app_service:AzureAppServiceResourceDetector"
azure_functions = "opentelemetry.resource.detector.azure.functions:AzureFunctionsResourceDetector"
azure_vm = "opentelemetry.resource.detector.azure.vm:AzureVMResourceDetector"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

# pylint: disable=import-error

from .aks import AzureAKSResourceDetector
from .app_service import AzureAppServiceResourceDetector
from .functions import AzureFunctionsResourceDetector
from .version import __version__
from .vm import AzureVMResourceDetector

__all__ = [
"AzureAKSResourceDetector",
"AzureAppServiceResourceDetector",
"AzureFunctionsResourceDetector",
"AzureVMResourceDetector",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
# Azure Kubernetes

_AKS_ARM_NAMESPACE_ID = "AKS_ARM_NAMESPACE_ID"
_AKS_CLUSTER_RESOURCE_ID = "CLUSTER_RESOURCE_ID"
_AKS_CLUSTER_RESOURCE_ID_KEY = "clusterResourceId"
_AKS_METADATA_FILE_PATH = "/etc/kubernetes/aks-cluster-metadata"

# AppService

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
from os import environ
from pathlib import Path

from ._constants import (
_AKS_ARM_NAMESPACE_ID,
_AKS_CLUSTER_RESOURCE_ID,
_AKS_METADATA_FILE_PATH,
_FUNCTIONS_WORKER_RUNTIME,
_WEBSITE_OWNER_NAME,
_WEBSITE_RESOURCE_GROUP,
Expand All @@ -12,7 +15,11 @@


def _is_on_aks() -> bool:
return environ.get(_AKS_ARM_NAMESPACE_ID) is not None
return (
environ.get(_AKS_ARM_NAMESPACE_ID) is not None
or environ.get(_AKS_CLUSTER_RESOURCE_ID) is not None
or Path(_AKS_METADATA_FILE_PATH).exists()
)


def _is_on_app_service() -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

from logging import getLogger
from os import environ
from pathlib import Path

from opentelemetry.sdk.resources import Resource, ResourceDetector

from ._constants import (
_AKS_CLUSTER_RESOURCE_ID,
_AKS_CLUSTER_RESOURCE_ID_KEY,
_AKS_METADATA_FILE_PATH,
)

_logger = getLogger(__name__)

_CLOUD_ACCOUNT_ID = "cloud.account.id"
_CLOUD_PLATFORM = "cloud.platform"
_CLOUD_PROVIDER = "cloud.provider"
_CLOUD_RESOURCE_ID = "cloud.resource_id"


def _extract_subscription_id(resource_id: str) -> str | None:
segments = resource_id.split("/")
for index, segment in enumerate(segments):
if segment.lower() == "subscriptions" and index < len(segments) - 1:
return segments[index + 1] or None
return None


def _parse_aks_metadata(content: str) -> str | None:
keyed_resource_id: str | None = None
bare_values: list[str] = []

for line in content.splitlines():
stripped_line = line.strip().lstrip("\ufeff")
if not stripped_line or stripped_line.startswith("#"):
continue

key, separator, value = stripped_line.partition("=")
if not separator:
bare_values.append(stripped_line)
elif key.strip() == _AKS_CLUSTER_RESOURCE_ID_KEY and value.strip():
keyed_resource_id = value.strip()

if keyed_resource_id:
return keyed_resource_id
if len(bare_values) == 1:
return bare_values[0]
return None


def _get_aks_metadata_from_file() -> str | None:
metadata_path = Path(_AKS_METADATA_FILE_PATH)
try:
if metadata_path.is_dir():
metadata_path = metadata_path / _AKS_CLUSTER_RESOURCE_ID_KEY
content = metadata_path.read_text(encoding="utf-8")
except (OSError, UnicodeError):
_logger.debug(
"Failed to read AKS metadata from %s",
metadata_path,
exc_info=True,
)
return None

return _parse_aks_metadata(content)


class AzureAKSResourceDetector(ResourceDetector):
def detect(self) -> Resource:
resource_id = environ.get(_AKS_CLUSTER_RESOURCE_ID) or _get_aks_metadata_from_file()
if not resource_id:
return Resource({})

attributes = {
_CLOUD_PROVIDER: "azure",
_CLOUD_PLATFORM: "azure.aks",
_CLOUD_RESOURCE_ID: resource_id,
}
subscription_id = _extract_subscription_id(resource_id)
if subscription_id:
attributes[_CLOUD_ACCOUNT_ID] = subscription_id

return Resource(attributes)
Loading
Loading