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

[Resources.Container] Add Kubernetes support #1699

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
df0e7a4
Add Kubernetes support in Container Resource Detector
joegoldman2 Apr 27, 2024
7a966f3
Update CHANGELOG.md
joegoldman2 Apr 27, 2024
c7a08b0
Fix warnings
joegoldman2 Apr 27, 2024
0beaa51
Return string.Empty if container id is null or empty
joegoldman2 Apr 27, 2024
966b860
Update CHANGELOG.md
joegoldman2 Apr 27, 2024
fde4504
Remove blank line
joegoldman2 Apr 27, 2024
2af92bc
Apply suggestions from review
joegoldman2 Apr 30, 2024
1a3b5e6
Merge branch 'main' into fix/1562
joegoldman2 May 7, 2024
9139514
Merge branch 'main' into fix/1562
cijothomas May 17, 2024
c1fe35f
Apply suggestions from review
joegoldman2 May 23, 2024
c59199c
Update README.md
joegoldman2 May 23, 2024
ac24d7f
Update README.md
joegoldman2 May 23, 2024
803528e
Merge branch 'main' into fix/1562
joegoldman2 May 23, 2024
d5d67d4
Update README.md
joegoldman2 May 23, 2024
310975b
Add unit test
joegoldman2 May 23, 2024
d609b39
Add null check
joegoldman2 May 23, 2024
60f2137
Update README
joegoldman2 May 25, 2024
e66dde2
Fix README lint errors
joegoldman2 May 25, 2024
a96d991
Fast fail for non K8s environment
joegoldman2 May 25, 2024
6f810af
Remove extra line
joegoldman2 May 25, 2024
d6521c3
Merge branch 'main' into fix/1562
joegoldman2 May 25, 2024
2977bc7
Fast fail for non K8s env and container name not provided
joegoldman2 May 25, 2024
63918f1
Merge branch 'main' into fix/1562
joegoldman2 May 27, 2024
a263fc4
Merge branch 'main' into fix/1562
joegoldman2 May 29, 2024
48b728a
Merge branch 'main' into fix/1562
joegoldman2 May 30, 2024
fdd3d27
Merge branch 'main' into fix/1562
joegoldman2 May 30, 2024
056b4b2
Merge branch 'main' into fix/1562
joegoldman2 Jun 4, 2024
81d068b
Merge branch 'main' into fix/1562
joegoldman2 Jun 4, 2024
be15a32
Fix EventSource name
joegoldman2 Jun 4, 2024
f5c4122
Fix CHANGELOG
joegoldman2 Jun 4, 2024
811ba2b
Apply suggestions from review
joegoldman2 Jun 4, 2024
f093b0c
Merge branch 'main' into fix/1562
joegoldman2 Jun 4, 2024
7cdcdc9
Merge branch 'main' into fix/1562
joegoldman2 Jun 6, 2024
09d1785
Apply suggestions from review
joegoldman2 Jun 6, 2024
5442c3f
Simplify deserialization
joegoldman2 Jun 6, 2024
4dd428d
Remove unnecessary using
joegoldman2 Jun 6, 2024
2f1ab7e
Merge branch 'main' into fix/1562
joegoldman2 Jun 28, 2024
72463c5
Remove unnecessary usings
joegoldman2 Jun 28, 2024
0c3bad3
Move and use AsyncHelper
joegoldman2 Jun 28, 2024
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: 2 additions & 0 deletions opentelemetry-dotnet-contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
ProjectSection(SolutionItems) = preProject
src\Shared\ActivityInstrumentationHelper.cs = src\Shared\ActivityInstrumentationHelper.cs
src\Shared\AssemblyVersionExtensions.cs = src\Shared\AssemblyVersionExtensions.cs
src\Shared\AsyncHelper.cs = src\Shared\AsyncHelper.cs
src\Shared\DiagnosticSourceListener.cs = src\Shared\DiagnosticSourceListener.cs
src\Shared\DiagnosticSourceSubscriber.cs = src\Shared\DiagnosticSourceSubscriber.cs
src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs
Expand All @@ -252,6 +253,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
src\Shared\PropertyFetcher.AOT.cs = src\Shared\PropertyFetcher.AOT.cs
src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs
src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.cs
src\Shared\ResourceDetectorUtils.cs = src\Shared\ResourceDetectorUtils.cs
src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs
src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs
src\Shared\ServerCertificateValidationHandler.cs = src\Shared\ServerCertificateValidationHandler.cs
Expand Down
4 changes: 1 addition & 3 deletions src/OpenTelemetry.Resources.AWS/AWSEKSDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public Resource Detect()
{
try
{
var stringBuilder = new StringBuilder();
var stringBuilder = new StringBuilder("Bearer ");

using (var streamReader = ResourceDetectorUtils.GetStreamReader(path))
{
Expand All @@ -72,8 +72,6 @@ public Resource Detect()
}
}

stringBuilder.Insert(0, "Bearer ");

return stringBuilder.ToString();
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
</ItemGroup>

<ItemGroup>
<Compile Include="$(RepoRoot)\src\Shared\AsyncHelper.cs" Link="Includes\AsyncHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\Shared\IServerCertificateValidationEventSource.cs" Link="Includes\IServerCertificateValidationEventSource.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ResourceDetectorUtils.cs" Link="Includes\ResourceDetectorUtils.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ServerCertificateValidationHandler.cs" Link="Includes\ServerCertificateValidationHandler.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ServerCertificateValidationProvider.cs" Link="Includes\ServerCertificateValidationProvider.cs" />
</ItemGroup>
Expand Down
3 changes: 3 additions & 0 deletions src/OpenTelemetry.Resources.Container/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Add Kubernetes support.
([#1699](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1699))

## 1.0.0-beta.9

Released 2024-Jun-18
Expand Down
152 changes: 99 additions & 53 deletions src/OpenTelemetry.Resources.Container/ContainerDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ internal sealed class ContainerDetector : IResourceDetector
private const string Filepath = "/proc/self/cgroup";
private const string FilepathV2 = "/proc/self/mountinfo";
private const string Hostname = "hostname";
private const string K8sCertificatePath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";

private readonly IK8sMetadataFetcher k8sMetadataFetcher;

/// <summary>
/// Initializes a new instance of the <see cref="ContainerDetector"/> class.
/// </summary>
public ContainerDetector()
: this(new K8sMetadataFetcher())
{
}

/// <summary>
/// CGroup Parse Versions.
/// Initializes a new instance of the <see cref="ContainerDetector"/> class for testing.
/// </summary>
internal enum ParseMode
/// <param name="k8sMetadataFetcher">The <see cref="IK8sMetadataFetcher"/>.</param>
internal ContainerDetector(IK8sMetadataFetcher k8sMetadataFetcher)
{
/// <summary>
/// Represents CGroupV1.
/// </summary>
V1,

/// <summary>
/// Represents CGroupV2.
/// </summary>
V2,
this.k8sMetadataFetcher = k8sMetadataFetcher;
}

/// <summary>
Expand All @@ -37,33 +41,74 @@ internal enum ParseMode
/// <returns>Resource with key-value pairs of resource attributes.</returns>
public Resource Detect()
{
var cGroupBuild = this.BuildResource(Filepath, ParseMode.V1);
if (cGroupBuild == Resource.Empty)
var containerId = this.ExtractK8sContainerId();
if (!string.IsNullOrEmpty(containerId))
{
return BuildResource(containerId);
}

containerId = this.ExtractContainerId(Filepath, ParseMode.V1);
if (!string.IsNullOrEmpty(containerId))
{
return BuildResource(containerId);
}

containerId = this.ExtractContainerId(FilepathV2, ParseMode.V2);
if (!string.IsNullOrEmpty(containerId))
{
cGroupBuild = this.BuildResource(FilepathV2, ParseMode.V2);
return BuildResource(containerId);
}

return cGroupBuild;
return Resource.Empty;

static Resource BuildResource(string containerId)
{
return new Resource(new List<KeyValuePair<string, object>>(1) { new(ContainerSemanticConventions.AttributeContainerId, containerId!) });
}
}

/// <summary>
/// Builds the resource attributes from Container Id in file path.
/// Extracts Container Id from path using the cgroupv1 format.
/// </summary>
/// <param name="path">File path where container id exists.</param>
/// <param name="cgroupVersion">CGroup Version of file to parse from.</param>
/// <returns>Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource.</returns>
internal Resource BuildResource(string path, ParseMode cgroupVersion)
/// <param name="path">cgroup path.</param>
/// <param name="parseMode">CGroup Version of file to parse from.</param>
/// <returns>Container Id, <see langword="null" /> if not found or exception being thrown.</returns>
internal string? ExtractContainerId(string path, ParseMode parseMode)
{
var containerId = this.ExtractContainerId(path, cgroupVersion);

if (string.IsNullOrEmpty(containerId))
try
{
return Resource.Empty;
if (!File.Exists(path))
{
return null;
}

foreach (string line in File.ReadLines(path))
{
string? containerId = null;
if (!string.IsNullOrEmpty(line))
{
if (parseMode == ParseMode.V1)
{
containerId = GetIdFromLineV1(line);
}
else if (parseMode == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal))
{
containerId = GetIdFromLineV2(line);
}
}

if (!string.IsNullOrEmpty(containerId))
{
return containerId;
}
}
}
else
catch (Exception ex)
{
return new Resource(new List<KeyValuePair<string, object>>(1) { new(ContainerSemanticConventions.AttributeContainerId, containerId!) });
ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerDetector)} : Failed to extract Container id from path", ex);
}

return null;
}

/// <summary>
Expand Down Expand Up @@ -119,7 +164,6 @@ internal Resource BuildResource(string path, ParseMode cgroupVersion)
private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex, int endIndex)
{
startIndex = (startIndex == -1) ? 0 : startIndex + 1;

if (endIndex == -1)
{
endIndex = input.Length;
Expand All @@ -128,45 +172,47 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex
return input.Substring(startIndex, endIndex - startIndex);
}

/// <summary>
/// Extracts Container Id from path using the cgroupv1 format.
/// </summary>
/// <param name="path">cgroup path.</param>
/// <param name="cgroupVersion">CGroup Version of file to parse from.</param>
/// <returns>Container Id, Null if not found or exception being thrown.</returns>
private string? ExtractContainerId(string path, ParseMode cgroupVersion)
private string? ExtractK8sContainerId()
{
try
{
if (!File.Exists(path))
var baseUrl = this.k8sMetadataFetcher.GetServiceBaseUrl();
var containerName = this.k8sMetadataFetcher.GetContainerName();
if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(containerName))
{
return null;
Kielek marked this conversation as resolved.
Show resolved Hide resolved
}

foreach (string line in File.ReadLines(path))
var @namespace = this.k8sMetadataFetcher.GetNamespace();
var hostname = this.k8sMetadataFetcher.GetPodName() ?? this.k8sMetadataFetcher.GetHostname();
var url = $"{baseUrl}/api/v1/namespaces/{@namespace}/pods/{hostname}";
var credentials = this.k8sMetadataFetcher.GetApiCredential();
if (string.IsNullOrEmpty(credentials))
{
string? containerId = null;
if (!string.IsNullOrEmpty(line))
{
if (cgroupVersion == ParseMode.V1)
{
containerId = GetIdFromLineV1(line);
}
else if (cgroupVersion == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal))
{
containerId = GetIdFromLineV2(line);
}
}
return null;
}

if (!string.IsNullOrEmpty(containerId))
{
return containerId;
}
using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log);
var response = AsyncHelper.RunSync(() => ResourceDetectorUtils.SendOutRequestAsync(url, HttpMethod.Get, new KeyValuePair<string, string>("Authorization", credentials), httpClientHandler));
var pod = ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.K8sPod);
if (pod?.Status?.ContainerStatuses == null)
{
return null;
}

var container = pod.Status.ContainerStatuses.SingleOrDefault(p => p.Name == containerName);
if (string.IsNullOrEmpty(container?.Id))
{
return null;
}

// Container's ID is in <type>://<container_id> format.
var index = container.Id.LastIndexOf('/');
return container.Id.Substring(index + 1);
}
catch (Exception ex)
{
ContainerExtensionsEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerDetector)} : Failed to extract Container id from path", ex);
ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerDetector)}: Failed to extract container id", ex);
}

return null;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Tracing;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Resources.Container;

[EventSource(Name = "OpenTelemetry-Resources-Container")]
internal class ContainerResourceEventSource : EventSource, IServerCertificateValidationEventSource
{
public static ContainerResourceEventSource Log = new();

private const int EventIdFailedToExtractResourceAttributes = 1;
private const int EventIdFailedToValidateCertificate = 2;
private const int EventIdFailedToCreateHttpHandler = 3;
private const int EventIdFailedCertificateFileNotExists = 4;
private const int EventIdFailedToLoadCertificateInStorage = 5;

[NonEvent]
public void ExtractResourceAttributesException(string format, Exception ex)
{
if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
{
this.FailedToExtractResourceAttributes(format, ex.ToInvariantString());
}
}

[Event(EventIdFailedToExtractResourceAttributes, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Error)]
public void FailedToExtractResourceAttributes(string format, string exception)
{
this.WriteEvent(EventIdFailedToExtractResourceAttributes, format, exception);
}

[Event(EventIdFailedToValidateCertificate, Message = "Failed to validate certificate. Details: '{0}'", Level = EventLevel.Warning)]
public void FailedToValidateCertificate(string error)
{
this.WriteEvent(EventIdFailedToValidateCertificate, error);
}

[Event(EventIdFailedToCreateHttpHandler, Message = "Failed to create HTTP handler. Exception: '{0}'", Level = EventLevel.Warning)]
public void FailedToCreateHttpHandler(Exception exception)
{
this.WriteEvent(EventIdFailedToCreateHttpHandler, exception.ToInvariantString());
}

[Event(EventIdFailedCertificateFileNotExists, Message = "Certificate file does not exist. File: '{0}'", Level = EventLevel.Warning)]
public void CertificateFileDoesNotExist(string filename)
{
this.WriteEvent(EventIdFailedCertificateFileNotExists, filename);
}

[Event(EventIdFailedToLoadCertificateInStorage, Message = "Failed to load certificate in trusted storage. File: '{0}'", Level = EventLevel.Warning)]
public void FailedToLoadCertificateInTrustedStorage(string filename)
{
this.WriteEvent(EventIdFailedToLoadCertificateInStorage, filename);
}
}
19 changes: 19 additions & 0 deletions src/OpenTelemetry.Resources.Container/IK8sMetadataFetcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace OpenTelemetry.Resources.Container;

internal interface IK8sMetadataFetcher
{
string? GetApiCredential();

string? GetContainerName();

string? GetHostname();

string? GetPodName();

string? GetNamespace();

string? GetServiceBaseUrl();
}
Loading
Loading