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

wip: detect aks cluster name #3

Closed
wants to merge 11 commits into from
27 changes: 27 additions & 0 deletions .chloggen/resourcedetectionprocessor-azure-cluster-name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: processor/resourcedetectionprocessor

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Detect Azure cluster name from IMDS metadata

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [26794]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
29 changes: 29 additions & 0 deletions processor/resourcedetectionprocessor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ processors:

* cloud.provider ("azure")
* cloud.platform ("azure_aks")
* k8s.cluster.name

```yaml
processors:
Expand All @@ -409,6 +410,34 @@ processors:
override: false
```

#### Cluster Name

Cluster name detection is disabled by default, and can be enabled with the
following configuration:

```yaml
processors:
resourcedetection/aks:
detectors: [aks]
timeout: 2s
override: false
aks:
resource_attributes:
k8s.cluster.name: true
```

Azure AKS cluster name is derived from the Azure Instance Metadata Service's (IMDS) infrastructure resource group field. This field contains the resource group and name of the cluster, separated by underscores. e.g: `MC_<resource group>_<cluster name>_<location>`.

Example:
- Resource group: my-resource-group
- Cluster name: my-cluster
- Location: eastus
- Generated name: MC_my-resource-group_my-cluster_eastus

The cluster name is detected if it does not contain underscores and if a custom infrastructure resource group name was not used.

If accurate parsing cannot be performed, the infrastructure resource group value is returned. This value can be used to uniquely identify the cluster, as Azure will not allow users to create multiple clusters with the same infrastructure resource group name.

### Consul

Queries a [consul agent](https://www.consul.io/docs/agent) and reads its' [configuration endpoint](https://www.consul.io/api-docs/agent#read-configuration) to retrieve the following resource attributes:
Expand Down
44 changes: 40 additions & 4 deletions processor/resourcedetectionprocessor/internal/azure/aks/aks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package aks // import "github.com/open-telemetry/opentelemetry-collector-contrib
import (
"context"
"os"
"strings"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/processor"
Expand Down Expand Up @@ -42,8 +43,9 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem
return res, "", nil
}

m, err := d.provider.Metadata(ctx)
// If we can't get a response from the metadata endpoint, we're not running in Azure
if !azureMetadataAvailable(ctx, d.provider) {
if err != nil {
return res, "", nil
}

Expand All @@ -54,6 +56,9 @@ func (d *Detector) Detect(ctx context.Context) (resource pcommon.Resource, schem
if d.resourceAttributes.CloudPlatform.Enabled {
attrs.PutStr(conventions.AttributeCloudPlatform, conventions.AttributeCloudPlatformAzureAKS)
}
if d.resourceAttributes.K8sClusterName.Enabled {
attrs.PutStr(conventions.AttributeK8SClusterName, parseClusterName(m.ResourceGroupName))
}

return res, conventions.SchemaURL, nil
}
Expand All @@ -62,7 +67,38 @@ func onK8s() bool {
return os.Getenv(kubernetesServiceHostEnvVar) != ""
}

func azureMetadataAvailable(ctx context.Context, p azure.Provider) bool {
_, err := p.Metadata(ctx)
return err == nil
// parseClusterName parses the cluster name from the infrastructure
// resource group name. AKS IMDS returns the resource group name in
// the following formats:
//
// 1. Generated group: MC_<resource group>_<cluster name>_<location>
// - Example:
// - Resource group: my-resource-group
// - Cluster name: my-cluster
// - Location: eastus
// - Generated name: MC_my-resource-group_my-cluster_eastus
//
// 2. Custom group: custom-infra-resource-group-name
//
// When using the generated infrastructure resource group, the resource
// group will include the cluster name. If the cluster's resource group
// or cluster name contains underscores, parsing will fall back on the
// unparsed infrastructure resource group name.
//
// When using a custom infrastructure resource group, the resource group name
// does not contain the cluster name. The custom infrastructure resource group
// name is returned instead.
//
// It is safe to use the infrastructure resource group name as a unique identifier
// because Azure will not allow the user to create multiple AKS clusters with the same
// infrastructure resource group name.
func parseClusterName(resourceGroup string) string {
// Code inspired by https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/datadogexporter/internal/hostmetadata/internal/azure/provider.go#L36
splitAll := strings.Split(resourceGroup, "_")

if len(splitAll) == 4 && strings.ToLower(splitAll[0]) == "mc" {
return splitAll[len(splitAll)-2]
}

return resourceGroup
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ func TestDetector_Detect_K8s_Azure(t *testing.T) {
assert.Equal(t, map[string]any{
"cloud.provider": "azure",
"cloud.platform": "azure_aks",
// Cluster name will not be detected if resource grup is not set
"k8s.cluster.name": "",
}, res.Attributes().AsRaw(), "Resource attrs returned are incorrect")
}

Expand Down Expand Up @@ -64,3 +66,52 @@ func mockProvider() *azure.MockProvider {
mp.On("Metadata").Return(&azure.ComputeMetadata{}, nil)
return mp
}

func TestParseClusterName(t *testing.T) {
cases := []struct {
name string
resourceGroup string
expected string
}{
{
name: "Return cluster name",
resourceGroup: "MC_myResourceGroup_AKSCluster_eastus",
expected: "AKSCluster",
},
{
name: "Return resource group name, resource group contains underscores",
resourceGroup: "MC_Resource_Group_AKSCluster_eastus",
expected: "MC_Resource_Group_AKSCluster_eastus",
},
{
name: "Return resource group name, cluster name contains underscores",
resourceGroup: "MC_myResourceGroup_AKS_Cluster_eastus",
expected: "MC_myResourceGroup_AKS_Cluster_eastus",
},
{
name: "Custom infrastructure resource group name, return resource group name",
resourceGroup: "infra-group_name",
expected: "infra-group_name",
},
{
name: "Custom infrastructure resource group name with four underscores, return resource group name",
resourceGroup: "dev_infra_group_name",
expected: "dev_infra_group_name",
},
// This case is unlikely because it would require the user to create
// a custom infrastructure resource group with the MC prefix and the
// correct number of underscores.
{
name: "Custom infrastructure resource group name with MC prefix",
resourceGroup: "MC_group_name_location",
expected: "name",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
actual := parseClusterName(tc.resourceGroup)
assert.Equal(t, tc.expected, actual)
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ all_set:
enabled: true
cloud.provider:
enabled: true
k8s.cluster.name:
enabled: true
none_set:
resource_attributes:
cloud.platform:
enabled: false
cloud.provider:
enabled: false
k8s.cluster.name:
enabled: false
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ resource_attributes:
cloud.platform:
description: The cloud.platform
type: string
enabled: true
enabled: true
k8s.cluster.name:
description: The k8s.cluster.name parsed from the Azure Instance Metadata Service's infrastructure resource group field
type: string
enabled: false
Loading