Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure resource providers #1228

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
35 changes: 35 additions & 0 deletions azure-resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Azure Resource Detectors for OpenTelemetry

This module provides Azure resource detectors for OpenTelemetry.

The following OpenTelemetry semantic conventions will be detected:

| Resource attribute | VM | Functions | App Service | Containers |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add aks (azure Kubernetes service)?

Copy link
Contributor

@heyams heyams Mar 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zeitlinger do you think it's a good idea to add AKS?

cloud.provider=Azure
cloud.platform=azure_aks
cloud.resource_id=${armId},
cloud.region=${armRegion},
k8s.cluster.name=
k8s.namespace.name=
k8s.node.name=
k8s.pod.name=
k8s.pod.uid
k8s.container.name

k8s.deployment.name
k8s.deployment.uid

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, definitely. So far, I've started with the existing implementations in other languages.

Can you point me to a starting point to implement an AKS provider?

|-------------------------|----------|-----------------|-------------------|----------------------|
| cloud.platform | azure_vm | azure_functions | azure_app_service | azure_container_apps |
| cloud.provider | azure | azure | azure | azure |
| cloud.resource.id | auto | | auto | |
| cloud.region | auto | auto | auto | |
| deployment.environment | | | auto | |
| host.id | auto | | auto | |
| host.name | auto | | | |
| host.type | auto | | | |
| os.type | auto | | | |
| os.version | auto | | | |
| azure.vm.scaleset.name | auto | | | |
| azure.vm.sku | auto | | | |
| service.name | | | auto | auto |
| service.version | | | | auto |
| service.instance.id | | | auto | auto |
| azure.app.service.stamp | | | auto | |
| faas.name | | auto | | |
| faas.version | | auto | | |
| faas.instance | | auto | | |
| faas.faas.max_memory | | auto | | |

## Component Owners

- [Trask Stalnaker](https://github.com/trask), Microsoft
- [Gregor Zeitlinger](https://github.com/zeitlinger), Grafana

Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).
37 changes: 37 additions & 0 deletions azure-resources/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
plugins {
id("otel.java-conventions")

id("otel.publish-conventions")
id("maven-publish")
}

description = "OpenTelemetry GCP Resources Support"
otelJava.moduleName.set("io.opentelemetry.contrib.gcp.resource")

// enable publishing to maven local
java {
withSourcesJar()
}

dependencies {
api("io.opentelemetry:opentelemetry-api")
api("io.opentelemetry:opentelemetry-sdk")

implementation("io.opentelemetry.semconv:opentelemetry-semconv")

compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")

implementation("com.fasterxml.jackson.core:jackson-core")
implementation("com.squareup.okhttp3:okhttp")

testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")

// testImplementation("org.mockito:mockito-core")
testImplementation("com.google.guava:guava")

testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.assertj:assertj-core")
testImplementation("com.linecorp.armeria:armeria-junit5")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.azure.resource;

import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.CloudPlatformIncubatingValues.AZURE_AKS;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.K8S_CLUSTER_NAME;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

public class AzureAksResourceProvider extends CloudResourceProvider {

private static final Map<String, AzureVmResourceProvider.Entry> COMPUTE_MAPPING = new HashMap<>();

static {
COMPUTE_MAPPING.put(
"resourceGroupName",
new AzureVmResourceProvider.Entry(
K8S_CLUSTER_NAME, AzureAksResourceProvider::parseClusterName));
}

// visible for testing
static String parseClusterName(String resourceGroup) {
// Code inspired by
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/datadogexporter/internal/hostmetadata/internal/azure/provider.go#L36
String[] splitAll = resourceGroup.split("_");
if (splitAll.length == 4 && splitAll[0].equalsIgnoreCase("mc")) {
return splitAll[splitAll.length - 2];
}
return resourceGroup;
}

// Environment variable that is set when running on Kubernetes
static final String KUBERNETES_SERVICE_HOST = "KUBERNETES_SERVICE_HOST";
private final Supplier<Optional<String>> client;
private final Map<String, String> environment;

// SPI
public AzureAksResourceProvider() {
this(AzureMetadataService.defaultClient(), System.getenv());
}

// visible for testing
public AzureAksResourceProvider(
zeitlinger marked this conversation as resolved.
Show resolved Hide resolved
Supplier<Optional<String>> client, Map<String, String> environment) {
this.client = client;
this.environment = environment;
}

@Override
public int order() {
// run after the fast cloud resource providers that only check environment variables
// and before the AKS provider
return 100;
}

@Override
public Resource createResource(ConfigProperties configProperties) {
if (environment.get(KUBERNETES_SERVICE_HOST) == null) {
return Resource.empty();
}
return client
.get()
.map(body -> AzureVmResourceProvider.parseMetadata(body, COMPUTE_MAPPING, AZURE_AKS))
.orElse(Resource.empty());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.azure.resource;

import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.CLOUD_REGION;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.CLOUD_RESOURCE_ID;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.CloudPlatformIncubatingValues.AZURE_APP_SERVICE;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.HOST_ID;
import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.SERVICE_INSTANCE_ID;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.internal.StringUtils;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;

public class AzureAppServiceResourceProvider extends CloudResourceProvider {

static final AttributeKey<String> AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE =
AttributeKey.stringKey("azure.app.service.stamp");
static final String REGION_NAME = "REGION_NAME";
private static final String WEBSITE_HOME_STAMPNAME = "WEBSITE_HOME_STAMPNAME";
private static final String WEBSITE_HOSTNAME = "WEBSITE_HOSTNAME";
static final String WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID";
private static final String WEBSITE_OWNER_NAME = "WEBSITE_OWNER_NAME";
private static final String WEBSITE_RESOURCE_GROUP = "WEBSITE_RESOURCE_GROUP";
static final String WEBSITE_SITE_NAME = "WEBSITE_SITE_NAME";
private static final String WEBSITE_SLOT_NAME = "WEBSITE_SLOT_NAME";

private static final Map<AttributeKey<String>, String> ENV_VAR_MAPPING = new HashMap<>();

static {
ENV_VAR_MAPPING.put(CLOUD_REGION, REGION_NAME);
ENV_VAR_MAPPING.put(DEPLOYMENT_ENVIRONMENT_NAME, WEBSITE_SLOT_NAME);
ENV_VAR_MAPPING.put(HOST_ID, WEBSITE_HOSTNAME);
ENV_VAR_MAPPING.put(SERVICE_INSTANCE_ID, WEBSITE_INSTANCE_ID);
ENV_VAR_MAPPING.put(AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE, WEBSITE_HOME_STAMPNAME);
}

private final Map<String, String> env;

// SPI
public AzureAppServiceResourceProvider() {
this(System.getenv());
}

// Visible for testing
AzureAppServiceResourceProvider(Map<String, String> env) {
this.env = env;
}

@Override
public Resource createResource(ConfigProperties config) {
return Resource.create(getAttributes());
}

public Attributes getAttributes() {
AzureEnvVarPlatform detect = AzureEnvVarPlatform.detect(env);
if (detect != AzureEnvVarPlatform.APP_SERVICE) {
return Attributes.empty();
}
String name = Objects.requireNonNull(env.get(WEBSITE_SITE_NAME));
AttributesBuilder builder = AzureVmResourceProvider.azureAttributeBuilder(AZURE_APP_SERVICE);
builder.put(SERVICE_NAME, name);

String resourceUri = resourceUri(name);
if (resourceUri != null) {
builder.put(CLOUD_RESOURCE_ID, resourceUri);
}

AzureEnvVarPlatform.addAttributesFromEnv(ENV_VAR_MAPPING, env, builder);

return builder.build();
}

@Nullable
private String resourceUri(String websiteName) {
String websiteResourceGroup = env.get(WEBSITE_RESOURCE_GROUP);
String websiteOwnerName = env.get(WEBSITE_OWNER_NAME);

String subscriptionId;
if (websiteOwnerName != null && websiteOwnerName.contains("+")) {
subscriptionId = websiteOwnerName.substring(0, websiteOwnerName.indexOf("+"));
} else {
subscriptionId = websiteOwnerName;
}

if (StringUtils.isNullOrEmpty(websiteResourceGroup)
|| StringUtils.isNullOrEmpty(subscriptionId)) {
return null;
}

return String.format(
"/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Web/sites/%s",
subscriptionId, websiteResourceGroup, websiteName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.azure.resource;

import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.SERVICE_INSTANCE_ID;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import java.util.HashMap;
import java.util.Map;

public class AzureContainersResourceProvider extends CloudResourceProvider {

static final String CONTAINER_APP_NAME = "CONTAINER_APP_NAME";

private static final String CONTAINER_APP_REPLICA_NAME = "CONTAINER_APP_REPLICA_NAME";
private static final String CONTAINER_APP_REVISION = "CONTAINER_APP_REVISION";

private static final Map<AttributeKey<String>, String> ENV_VAR_MAPPING = new HashMap<>();

static {
ENV_VAR_MAPPING.put(SERVICE_NAME, CONTAINER_APP_NAME);
ENV_VAR_MAPPING.put(SERVICE_INSTANCE_ID, CONTAINER_APP_REPLICA_NAME);
ENV_VAR_MAPPING.put(SERVICE_VERSION, CONTAINER_APP_REVISION);
}

private final Map<String, String> env;

// SPI
public AzureContainersResourceProvider() {
this(System.getenv());
}

// Visible for testing
AzureContainersResourceProvider(Map<String, String> env) {
this.env = env;
}

@Override
public Resource createResource(ConfigProperties config) {
return Resource.create(getAttributes());
}

public Attributes getAttributes() {
AzureEnvVarPlatform detect = AzureEnvVarPlatform.detect(env);
if (detect != AzureEnvVarPlatform.CONTAINER_APP) {
return Attributes.empty();
}

AttributesBuilder builder =
AzureVmResourceProvider.azureAttributeBuilder("azure_container_apps");

AzureEnvVarPlatform.addAttributesFromEnv(ENV_VAR_MAPPING, env, builder);

return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.azure.resource;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import java.util.Map;

public enum AzureEnvVarPlatform {
APP_SERVICE,
FUNCTIONS,
CONTAINER_APP,
NONE;

public static AzureEnvVarPlatform detect(Map<String, String> env) {
String appName = env.get(AzureContainersResourceProvider.CONTAINER_APP_NAME);
if (appName != null) {
return CONTAINER_APP;
}
String name = env.get(AzureAppServiceResourceProvider.WEBSITE_SITE_NAME);
if (name == null) {
return NONE;
}
if (env.get(AzureFunctionsResourceProvider.FUNCTIONS_VERSION) != null) {
return FUNCTIONS;
}
return APP_SERVICE;
}

static void addAttributesFromEnv(
Map<AttributeKey<String>, String> mapping,
Map<String, String> env,
AttributesBuilder builder) {
mapping.forEach(
(key, value) -> {
String envValue = env.get(value);
if (envValue != null) {
builder.put(key, envValue);
}
});
}
}
Loading
Loading