From df0e7a4addec31fd59c83d64f5ad18f60bdc945c Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 08:55:03 +0000 Subject: [PATCH 01/30] Add Kubernetes support in Container Resource Detector --- opentelemetry-dotnet-contrib.sln | 1 + ...OpenTelemetry.ResourceDetectors.AWS.csproj | 1 + .../ContainerExtensionsEventSource.cs | 29 ---- .../ContainerResourceDetector.cs | 152 +++++++++++++++--- .../ContainerResourceEventSource.cs | 59 +++++++ .../Models/K8sContainerStatus.cs | 15 ++ .../Models/K8sPod.cs | 12 ++ .../Models/K8sPodStatus.cs | 13 ++ ...lemetry.ResourceDetectors.Container.csproj | 4 + .../SourceGenerationContext.cs | 24 +++ .../ResourceDetectorUtils.cs | 2 +- ...y.ResourceDetectors.Container.Tests.csproj | 4 - 12 files changed, 256 insertions(+), 60 deletions(-) delete mode 100644 src/OpenTelemetry.ResourceDetectors.Container/ContainerExtensionsEventSource.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/Models/K8sContainerStatus.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPod.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPodStatus.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/SourceGenerationContext.cs rename src/{OpenTelemetry.ResourceDetectors.AWS => Shared}/ResourceDetectorUtils.cs (98%) diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index a49632c03c..83965ed134 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -284,6 +284,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 diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj index 0cec692434..d518de345b 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj +++ b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj @@ -24,6 +24,7 @@ + diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerExtensionsEventSource.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerExtensionsEventSource.cs deleted file mode 100644 index 2eac9e80b2..0000000000 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerExtensionsEventSource.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -using System; -using System.Diagnostics.Tracing; -using OpenTelemetry.Internal; - -namespace OpenTelemetry.ResourceDetectors.Container; - -[EventSource(Name = "OpenTelemetry-ResourceDetectors-Container")] -internal class ContainerExtensionsEventSource : EventSource -{ - public static ContainerExtensionsEventSource Log = new(); - - [NonEvent] - public void ExtractResourceAttributesException(string format, Exception ex) - { - if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1))) - { - this.FailedToExtractResourceAttributes(format, ex.ToInvariantString()); - } - } - - [Event(1, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Error)] - public void FailedToExtractResourceAttributes(string format, string exception) - { - this.WriteEvent(1, format, exception); - } -} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index ec7d1705d3..ed4c2fd1c9 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text; using System.Text.RegularExpressions; +using OpenTelemetry.ResourceDetectors.Container.Models; using OpenTelemetry.ResourceDetectors.Container.Utils; using OpenTelemetry.Resources; @@ -18,6 +21,13 @@ public class ContainerResourceDetector : IResourceDetector private const string Filepath = "/proc/self/cgroup"; private const string FilepathV2 = "/proc/self/mountinfo"; private const string Hostname = "hostname"; + private const string K8sServiceHostKey = "KUBERNETES_SERVICE_HOST"; + private const string K8sServicePortKey = "KUBERNETES_SERVICE_PORT_HTTPS"; + private const string K8sNamespaceKey = "KUBERNETES_NAMESPACE"; + private const string K8sHostnameKey = "HOSTNAME"; + private const string K8sContainerNameKey = "KUBERNETES_CONTAINER_NAME"; + private const string K8sCertificatePath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; + private const string K8sCredentialPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; /// /// CGroup Parse Versions. @@ -33,6 +43,11 @@ internal enum ParseMode /// Represents CGroupV2. /// V2, + + /// + /// Represents Kubernetes. + /// + K8s, } /// @@ -41,24 +56,29 @@ internal enum ParseMode /// Resource with key-value pairs of resource attributes. public Resource Detect() { - var cGroupBuild = this.BuildResource(Filepath, ParseMode.V1); - if (cGroupBuild == Resource.Empty) + var resource = this.BuildResource(Filepath, ParseMode.K8s); + if (resource == Resource.Empty) { - cGroupBuild = this.BuildResource(FilepathV2, ParseMode.V2); + resource = this.BuildResource(Filepath, ParseMode.V1); } - return cGroupBuild; + if (resource == Resource.Empty) + { + resource = this.BuildResource(FilepathV2, ParseMode.V2); + } + + return resource; } /// /// Builds the resource attributes from Container Id in file path. /// /// File path where container id exists. - /// CGroup Version of file to parse from. + /// CGroup Version of file to parse from. /// Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource. - internal Resource BuildResource(string path, ParseMode cgroupVersion) + internal Resource BuildResource(string path, ParseMode parseMode) { - var containerId = this.ExtractContainerId(path, cgroupVersion); + var containerId = this.ExtractContainerId(path, parseMode); if (string.IsNullOrEmpty(containerId)) { @@ -132,45 +152,125 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex return input.Substring(startIndex, endIndex - startIndex); } + private static string? ExtractK8sContainerId() + { + try + { + var host = Environment.GetEnvironmentVariable(K8sServiceHostKey); + var port = Environment.GetEnvironmentVariable(K8sServicePortKey); + var @namespace = Environment.GetEnvironmentVariable(K8sNamespaceKey); + var hostname = Environment.GetEnvironmentVariable(K8sHostnameKey); + var containerName = Environment.GetEnvironmentVariable(K8sContainerNameKey); + var url = $"https://{host}:{port}/api/v1/namespaces/{@namespace}/pods/{hostname}"; + var credentials = GetK8sCredentials(K8sCredentialPath); + using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); + var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); + var pod = DeserializeK8sResponse(response); + + if (pod == null || pod.Status == null || pod.Status.ContainerStatuses == null) + { + return string.Empty; + } + + var container = pod.Status.ContainerStatuses.SingleOrDefault(p => p.Name == containerName); + if (container is null) + { + return string.Empty; + } + + // Container's ID is in :// format. + var index = container.Id.LastIndexOf('/'); + return container.Id.Substring(index + 1); + } + catch (Exception ex) + { + ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)}: Failed to extract container id", ex); + } + + return null; + + static string? GetK8sCredentials(string path) + { + try + { + var stringBuilder = new StringBuilder(); + + using (var streamReader = ResourceDetectorUtils.GetStreamReader(path)) + { + while (!streamReader.EndOfStream) + { + stringBuilder.Append(streamReader.ReadLine()?.Trim()); + } + } + + stringBuilder.Insert(0, "Bearer "); + + return stringBuilder.ToString(); + } + catch (Exception ex) + { + ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)}: Failed to load client token", ex); + } + + return null; + } + + static K8sPod? DeserializeK8sResponse(string response) + { +#if NET6_0_OR_GREATER + return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.K8sPod); +#else + return ResourceDetectorUtils.DeserializeFromString(response); +#endif + } + } + /// /// Extracts Container Id from path using the cgroupv1 format. /// /// cgroup path. - /// CGroup Version of file to parse from. - /// Container Id, Null if not found or exception being thrown. - private string? ExtractContainerId(string path, ParseMode cgroupVersion) + /// CGroup Version of file to parse from. + /// Container Id, if not found or exception being thrown. + private string? ExtractContainerId(string path, ParseMode parseMode) { try { - if (!File.Exists(path)) + if (parseMode == ParseMode.K8s) { - return null; + return ExtractK8sContainerId(); } - - foreach (string line in File.ReadLines(path)) + else { - string? containerId = null; - if (!string.IsNullOrEmpty(line)) + if (!File.Exists(path)) + { + return null; + } + + foreach (string line in File.ReadLines(path)) { - if (cgroupVersion == ParseMode.V1) + string? containerId = null; + if (!string.IsNullOrEmpty(line)) { - containerId = GetIdFromLineV1(line); + if (parseMode == ParseMode.V1) + { + containerId = GetIdFromLineV1(line); + } + else if (parseMode == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal)) + { + containerId = GetIdFromLineV2(line); + } } - else if (cgroupVersion == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal)) + + if (!string.IsNullOrEmpty(containerId)) { - containerId = GetIdFromLineV2(line); + return containerId; } } - - if (!string.IsNullOrEmpty(containerId)) - { - return containerId; - } } } catch (Exception ex) { - ContainerExtensionsEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)} : Failed to extract Container id from path", ex); + ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)} : Failed to extract Container id from path", ex); } return null; diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs new file mode 100644 index 0000000000..cffd2eb5ec --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs @@ -0,0 +1,59 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.Diagnostics.Tracing; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.ResourceDetectors.Container; + +[EventSource(Name = "OpenTelemetry-ResourceDetectors-Container")] +internal class ContainerResourceEventSource : EventSource, IServerCertificateValidationEventSource +{ + 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; + + public static ContainerResourceEventSource Log = new(); + + [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(1, 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); + } +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sContainerStatus.cs b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sContainerStatus.cs new file mode 100644 index 0000000000..eafe063be7 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sContainerStatus.cs @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json.Serialization; + +namespace OpenTelemetry.ResourceDetectors.Container.Models; + +internal sealed class K8sContainerStatus +{ + [JsonPropertyName("name")] + public string Name { get; set; } = default!; + + [JsonPropertyName("containerID")] + public string Id { get; set; } = default!; +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPod.cs b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPod.cs new file mode 100644 index 0000000000..292f314cb7 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPod.cs @@ -0,0 +1,12 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Text.Json.Serialization; + +namespace OpenTelemetry.ResourceDetectors.Container.Models; + +internal sealed class K8sPod +{ + [JsonPropertyName("status")] + public K8sPodStatus? Status { get; set; } +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPodStatus.cs b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPodStatus.cs new file mode 100644 index 0000000000..f3afc06795 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/Models/K8sPodStatus.cs @@ -0,0 +1,13 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenTelemetry.ResourceDetectors.Container.Models; + +internal sealed class K8sPodStatus +{ + [JsonPropertyName("containerStatuses")] + public IReadOnlyList ContainerStatuses { get; set; } = new List(); +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/OpenTelemetry.ResourceDetectors.Container.csproj b/src/OpenTelemetry.ResourceDetectors.Container/OpenTelemetry.ResourceDetectors.Container.csproj index c77f1af5e6..8a57acfe65 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/OpenTelemetry.ResourceDetectors.Container.csproj +++ b/src/OpenTelemetry.ResourceDetectors.Container/OpenTelemetry.ResourceDetectors.Container.csproj @@ -11,5 +11,9 @@ + + + + diff --git a/src/OpenTelemetry.ResourceDetectors.Container/SourceGenerationContext.cs b/src/OpenTelemetry.ResourceDetectors.Container/SourceGenerationContext.cs new file mode 100644 index 0000000000..fc19df95b6 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/SourceGenerationContext.cs @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET6_0_OR_GREATER +using System.Text.Json.Serialization; +using OpenTelemetry.ResourceDetectors.Container.Models; + +namespace OpenTelemetry.ResourceDetectors.Container; + +/// +/// "Source Generation" is feature added to System.Text.Json in .NET 6.0. +/// This is a performance optimization that avoids runtime reflection when performing serialization. +/// Serialization metadata will be computed at compile-time and included in the assembly. +/// . +/// . +/// . +/// +[JsonSerializable(typeof(K8sPod))] +[JsonSerializable(typeof(K8sPodStatus))] +[JsonSerializable(typeof(K8sContainerStatus))] +internal sealed partial class SourceGenerationContext : JsonSerializerContext +{ +} +#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs b/src/Shared/ResourceDetectorUtils.cs similarity index 98% rename from src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs rename to src/Shared/ResourceDetectorUtils.cs index 331f8d86dd..d476e46f22 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs +++ b/src/Shared/ResourceDetectorUtils.cs @@ -12,7 +12,7 @@ #endif using System.Threading.Tasks; -namespace OpenTelemetry.ResourceDetectors.AWS; +namespace OpenTelemetry.ResourceDetectors; /// /// Class for resource detector utils. diff --git a/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj b/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj index 6b11db9474..2bc9efb956 100644 --- a/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj +++ b/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj @@ -11,8 +11,4 @@ - - - - From 7a966f353246c0ab3800e463560244198dd1ca39 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:02:19 +0000 Subject: [PATCH 02/30] Update CHANGELOG.md --- src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md index c02fca6bb9..dcd4e556dc 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md @@ -5,6 +5,9 @@ * Update OpenTelemetry SDK version to `1.8.1`. ([#1668](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1668)) +* Add Kubernetes support in Container Resource Detector. + ([#1699](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1699)) + ## 1.0.0-beta.7 Released 2024-Apr-05 From c7a08b0bd77b950ab3fbfd4aefc73217d435bd1e Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:11:58 +0000 Subject: [PATCH 03/30] Fix warnings --- .../ContainerResourceDetector.cs | 5 +++++ .../ContainerResourceEventSource.cs | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index ed4c2fd1c9..90b47007b5 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -163,6 +163,11 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex var containerName = Environment.GetEnvironmentVariable(K8sContainerNameKey); var url = $"https://{host}:{port}/api/v1/namespaces/{@namespace}/pods/{hostname}"; var credentials = GetK8sCredentials(K8sCredentialPath); + if (string.IsNullOrEmpty(credentials)) + { + return string.Empty; + } + using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); var pod = DeserializeK8sResponse(response); diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs index cffd2eb5ec..0bc7afb24e 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceEventSource.cs @@ -10,14 +10,14 @@ namespace OpenTelemetry.ResourceDetectors.Container; [EventSource(Name = "OpenTelemetry-ResourceDetectors-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; - public static ContainerResourceEventSource Log = new(); - [NonEvent] public void ExtractResourceAttributesException(string format, Exception ex) { From 0beaa5135ae6d3fb3e5da6c356407b6e70c146f3 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 09:22:34 +0000 Subject: [PATCH 04/30] Return string.Empty if container id is null or empty --- .../ContainerResourceDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 90b47007b5..3ecee12a24 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -178,7 +178,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex } var container = pod.Status.ContainerStatuses.SingleOrDefault(p => p.Name == containerName); - if (container is null) + if (container is null || string.IsNullOrEmpty(container.Id)) { return string.Empty; } From 966b860019f18e8894a60ffd3fb1b6f74f0257e5 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:03:33 +0000 Subject: [PATCH 05/30] Update CHANGELOG.md --- src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md index dcd4e556dc..d7735ac8a3 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/CHANGELOG.md @@ -5,7 +5,7 @@ * Update OpenTelemetry SDK version to `1.8.1`. ([#1668](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1668)) -* Add Kubernetes support in Container Resource Detector. +* Add Kubernetes support. ([#1699](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1699)) ## 1.0.0-beta.7 From fde450411a3f69f6c355a3eba38ab39b7e1a90cc Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:06:25 +0000 Subject: [PATCH 06/30] Remove blank line --- .../ContainerResourceDetector.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 3ecee12a24..4fdc4c91d0 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -171,7 +171,6 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); var pod = DeserializeK8sResponse(response); - if (pod == null || pod.Status == null || pod.Status.ContainerStatuses == null) { return string.Empty; From 2af92bc5557dbbfbd57f15cb55dd2ed2864411c8 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Tue, 30 Apr 2024 19:10:02 +0000 Subject: [PATCH 07/30] Apply suggestions from review --- .../AWSEKSResourceDetector.cs | 4 +--- .../ContainerResourceDetector.cs | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index c8a616f2b7..f9991e0b89 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -67,7 +67,7 @@ internal static List> ExtractResourceAttributes(str { try { - var stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder("Bearer "); using (var streamReader = ResourceDetectorUtils.GetStreamReader(path)) { @@ -77,8 +77,6 @@ internal static List> ExtractResourceAttributes(str } } - stringBuilder.Insert(0, "Bearer "); - return stringBuilder.ToString(); } catch (Exception ex) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 4fdc4c91d0..3104bd99df 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -171,7 +171,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); var pod = DeserializeK8sResponse(response); - if (pod == null || pod.Status == null || pod.Status.ContainerStatuses == null) + if (pod?.Status?.ContainerStatuses == null) { return string.Empty; } @@ -197,7 +197,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex { try { - var stringBuilder = new StringBuilder(); + var stringBuilder = new StringBuilder("Bearer "); using (var streamReader = ResourceDetectorUtils.GetStreamReader(path)) { @@ -207,8 +207,6 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex } } - stringBuilder.Insert(0, "Bearer "); - return stringBuilder.ToString(); } catch (Exception ex) From c1fe35fa4464c261fb74c5881a6087bbb01b10d8 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Thu, 23 May 2024 06:26:46 +0000 Subject: [PATCH 08/30] Apply suggestions from review --- .../ContainerResourceDetector.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 3104bd99df..56d1a35257 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -23,9 +23,10 @@ public class ContainerResourceDetector : IResourceDetector private const string Hostname = "hostname"; private const string K8sServiceHostKey = "KUBERNETES_SERVICE_HOST"; private const string K8sServicePortKey = "KUBERNETES_SERVICE_PORT_HTTPS"; - private const string K8sNamespaceKey = "KUBERNETES_NAMESPACE"; private const string K8sHostnameKey = "HOSTNAME"; + private const string K8sPodNameKey = "KUBERNETES_POD_NAME"; private const string K8sContainerNameKey = "KUBERNETES_CONTAINER_NAME"; + private const string K8sNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; private const string K8sCertificatePath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; private const string K8sCredentialPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; @@ -158,8 +159,8 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex { var host = Environment.GetEnvironmentVariable(K8sServiceHostKey); var port = Environment.GetEnvironmentVariable(K8sServicePortKey); - var @namespace = Environment.GetEnvironmentVariable(K8sNamespaceKey); - var hostname = Environment.GetEnvironmentVariable(K8sHostnameKey); + var @namespace = File.ReadAllText(K8sNamespacePath); + var hostname = Environment.GetEnvironmentVariable(K8sPodNameKey) ?? Environment.GetEnvironmentVariable(K8sHostnameKey); var containerName = Environment.GetEnvironmentVariable(K8sContainerNameKey); var url = $"https://{host}:{port}/api/v1/namespaces/{@namespace}/pods/{hostname}"; var credentials = GetK8sCredentials(K8sCredentialPath); From c59199cc355769d85420bd167d0cd306fcc96799 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Thu, 23 May 2024 06:27:34 +0000 Subject: [PATCH 09/30] Update README.md --- .../README.md | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index 4cbfd046c0..8863e64f02 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -34,6 +34,64 @@ your application is running: - **ContainerResourceDetector**: container.id. +## Kubernetes +When running in a Kubernetes environment, the Container resource detector +requires permissions to access pod information in order to extract the container ID. +This is achieved using Kubernetes Role-Based Access Control (RBAC). + +You will need to create a Role and RoleBinding to grant the necessary permissions +to the service account used by your application. +Below is an example of how to configure these RBAC resources: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: pod-reader-account +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: pod-reader +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: read-pods + namespace: default +subjects: +- kind: ServiceAccount + name: pod-reader-account + namespace: default +roleRef: + kind: Role + name: pod-reader + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: Pod +metadata: + name: container-resolve-demo +spec: + serviceAccountName: pod-reader-account + volumes: + - name: shared-data + emptyDir: {} + containers: + - name: &container_name my_container_name + image: ubuntu:latest + command: [ "/bin/bash", "-c", "--" ] + args: [ "while true; do sleep 30; done;" ] + env: + - name: KUBERNETES_CONTAINER_NAME + value: *container_name +``` + ## References - [OpenTelemetry Project](https://opentelemetry.io/) From ac24d7f49c65106f74fda83465993d7fda43cce7 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Thu, 23 May 2024 06:36:41 +0000 Subject: [PATCH 10/30] Update README.md --- src/OpenTelemetry.ResourceDetectors.Container/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index 8863e64f02..b6fcbc141e 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -36,11 +36,12 @@ your application is running: ## Kubernetes When running in a Kubernetes environment, the Container resource detector -requires permissions to access pod information in order to extract the container ID. +requires permissions to access pod information in order +to extract the container ID. This is achieved using Kubernetes Role-Based Access Control (RBAC). -You will need to create a Role and RoleBinding to grant the necessary permissions -to the service account used by your application. +You will need to create a Role and RoleBinding to grant +the necessary permissions to the service account used by your application. Below is an example of how to configure these RBAC resources: ```yaml From d5d67d41aa0a0df7dea9cb1975053c595e2f44aa Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Thu, 23 May 2024 06:46:19 +0000 Subject: [PATCH 11/30] Update README.md --- src/OpenTelemetry.ResourceDetectors.Container/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index b6fcbc141e..9c516070fc 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -35,6 +35,7 @@ your application is running: - **ContainerResourceDetector**: container.id. ## Kubernetes + When running in a Kubernetes environment, the Container resource detector requires permissions to access pod information in order to extract the container ID. From 310975b8b9cc2b7c6fee8d4988efda513f584a18 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 23 May 2024 22:08:39 +0000 Subject: [PATCH 12/30] Add unit test --- .../ContainerResourceDetector.cs | 79 ++--- .../IK8sMetadataFetcher.cs | 19 ++ .../K8sMetadataFetcher.cs | 70 +++++ .../ParseMode.cs | 25 ++ .../ContainerResourceDetectorTests.cs | 131 ++++++-- ...y.ResourceDetectors.Container.Tests.csproj | 8 +- .../k8s/pod-response.json | 289 ++++++++++++++++++ 7 files changed, 545 insertions(+), 76 deletions(-) create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/IK8sMetadataFetcher.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs create mode 100644 src/OpenTelemetry.ResourceDetectors.Container/ParseMode.cs create mode 100644 test/OpenTelemetry.ResourceDetectors.Container.Tests/k8s/pod-response.json diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 56d1a35257..e657f869e9 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using OpenTelemetry.ResourceDetectors.Container.Models; using OpenTelemetry.ResourceDetectors.Container.Utils; @@ -21,34 +20,25 @@ public class ContainerResourceDetector : IResourceDetector private const string Filepath = "/proc/self/cgroup"; private const string FilepathV2 = "/proc/self/mountinfo"; private const string Hostname = "hostname"; - private const string K8sServiceHostKey = "KUBERNETES_SERVICE_HOST"; - private const string K8sServicePortKey = "KUBERNETES_SERVICE_PORT_HTTPS"; - private const string K8sHostnameKey = "HOSTNAME"; - private const string K8sPodNameKey = "KUBERNETES_POD_NAME"; - private const string K8sContainerNameKey = "KUBERNETES_CONTAINER_NAME"; - private const string K8sNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; private const string K8sCertificatePath = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"; - private const string K8sCredentialPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; + + private readonly IK8sMetadataFetcher k8sMetadataFetcher; + + /// + /// Initializes a new instance of the class. + /// + public ContainerResourceDetector() + : this(new K8sMetadataFetcher()) + { + } /// - /// CGroup Parse Versions. + /// Initializes a new instance of the class for testing. /// - internal enum ParseMode + /// The . + internal ContainerResourceDetector(IK8sMetadataFetcher k8sMetadataFetcher) { - /// - /// Represents CGroupV1. - /// - V1, - - /// - /// Represents CGroupV2. - /// - V2, - - /// - /// Represents Kubernetes. - /// - K8s, + this.k8sMetadataFetcher = k8sMetadataFetcher; } /// @@ -153,17 +143,16 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex return input.Substring(startIndex, endIndex - startIndex); } - private static string? ExtractK8sContainerId() + private string? ExtractK8sContainerId() { try { - var host = Environment.GetEnvironmentVariable(K8sServiceHostKey); - var port = Environment.GetEnvironmentVariable(K8sServicePortKey); - var @namespace = File.ReadAllText(K8sNamespacePath); - var hostname = Environment.GetEnvironmentVariable(K8sPodNameKey) ?? Environment.GetEnvironmentVariable(K8sHostnameKey); - var containerName = Environment.GetEnvironmentVariable(K8sContainerNameKey); - var url = $"https://{host}:{port}/api/v1/namespaces/{@namespace}/pods/{hostname}"; - var credentials = GetK8sCredentials(K8sCredentialPath); + var baseUrl = this.k8sMetadataFetcher.GetServiceBaseUrl(); + var @namespace = this.k8sMetadataFetcher.GetNamespace(); + var hostname = this.k8sMetadataFetcher.GetPodName() ?? this.k8sMetadataFetcher.GetHostname(); + var containerName = this.k8sMetadataFetcher.GetContainerName(); + var url = $"{baseUrl}/api/v1/namespaces/{@namespace}/pods/{hostname}"; + var credentials = this.k8sMetadataFetcher.GetApiCredential(); if (string.IsNullOrEmpty(credentials)) { return string.Empty; @@ -194,30 +183,6 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex return null; - static string? GetK8sCredentials(string path) - { - try - { - var stringBuilder = new StringBuilder("Bearer "); - - using (var streamReader = ResourceDetectorUtils.GetStreamReader(path)) - { - while (!streamReader.EndOfStream) - { - stringBuilder.Append(streamReader.ReadLine()?.Trim()); - } - } - - return stringBuilder.ToString(); - } - catch (Exception ex) - { - ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)}: Failed to load client token", ex); - } - - return null; - } - static K8sPod? DeserializeK8sResponse(string response) { #if NET6_0_OR_GREATER @@ -240,7 +205,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex { if (parseMode == ParseMode.K8s) { - return ExtractK8sContainerId(); + return this.ExtractK8sContainerId(); } else { diff --git a/src/OpenTelemetry.ResourceDetectors.Container/IK8sMetadataFetcher.cs b/src/OpenTelemetry.ResourceDetectors.Container/IK8sMetadataFetcher.cs new file mode 100644 index 0000000000..37943c529c --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/IK8sMetadataFetcher.cs @@ -0,0 +1,19 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.ResourceDetectors.Container; + +internal interface IK8sMetadataFetcher +{ + string? GetApiCredential(); + + string? GetContainerName(); + + string? GetHostname(); + + string? GetPodName(); + + string? GetNamespace(); + + string? GetServiceBaseUrl(); +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs b/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs new file mode 100644 index 0000000000..f4c4ca6710 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs @@ -0,0 +1,70 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System; +using System.IO; +using System.Text; + +namespace OpenTelemetry.ResourceDetectors.Container; + +internal sealed class K8sMetadataFetcher : IK8sMetadataFetcher +{ + private const string KubernetesServiceHostKey = "KUBERNETES_SERVICE_HOST"; + private const string KubernetesServicePortKey = "KUBERNETES_SERVICE_PORT_HTTPS"; + private const string KubernetesHostnameKey = "HOSTNAME"; + private const string KubernetesPodNameKey = "KUBERNETES_POD_NAME"; + private const string KubernetesContainerNameKey = "KUBERNETES_CONTAINER_NAME"; + private const string KubernetesNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; + private const string KubernetesCredentialPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; + + public string? GetApiCredential() + { + try + { + var stringBuilder = new StringBuilder("Bearer "); + + using (var streamReader = ResourceDetectorUtils.GetStreamReader(KubernetesCredentialPath)) + { + while (!streamReader.EndOfStream) + { + _ = stringBuilder.Append(streamReader.ReadLine()?.Trim()); + } + } + + return stringBuilder.ToString(); + } + catch (Exception ex) + { + ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerResourceDetector)}: Failed to load client token", ex); + } + + return null; + } + + public string? GetContainerName() + { + return Environment.GetEnvironmentVariable(KubernetesContainerNameKey); + } + + public string? GetHostname() + { + return Environment.GetEnvironmentVariable(KubernetesHostnameKey); + } + + public string? GetPodName() + { + return Environment.GetEnvironmentVariable(KubernetesPodNameKey); + } + + public string? GetNamespace() + { + return File.ReadAllText(KubernetesNamespacePath); + } + + public string? GetServiceBaseUrl() + { + var serviceHost = Environment.GetEnvironmentVariable(KubernetesServiceHostKey); + var servicePort = Environment.GetEnvironmentVariable(KubernetesServicePortKey); + return $"https://{serviceHost}:{servicePort}"; + } +} diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ParseMode.cs b/src/OpenTelemetry.ResourceDetectors.Container/ParseMode.cs new file mode 100644 index 0000000000..3dcaa93d09 --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.Container/ParseMode.cs @@ -0,0 +1,25 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.ResourceDetectors.Container; + +/// +/// CGroup Parse Versions. +/// +internal enum ParseMode +{ + /// + /// Represents CGroupV1. + /// + V1, + + /// + /// Represents CGroupV2. + /// + V2, + + /// + /// Represents Kubernetes. + /// + K8s, +} diff --git a/test/OpenTelemetry.ResourceDetectors.Container.Tests/ContainerResourceDetectorTests.cs b/test/OpenTelemetry.ResourceDetectors.Container.Tests/ContainerResourceDetectorTests.cs index 6ed321e0b1..a97e629016 100644 --- a/test/OpenTelemetry.ResourceDetectors.Container.Tests/ContainerResourceDetectorTests.cs +++ b/test/OpenTelemetry.ResourceDetectors.Container.Tests/ContainerResourceDetectorTests.cs @@ -1,9 +1,16 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; using OpenTelemetry.Resources; using Xunit; @@ -17,22 +24,22 @@ public class ContainerResourceDetectorTests name: "cgroupv1 with prefix", line: "13:name=systemd:/podruntime/docker/kubepods/crio-e2cc29debdf85dde404998aa128997a819ff", expectedContainerId: "e2cc29debdf85dde404998aa128997a819ff", - cgroupVersion: ContainerResourceDetector.ParseMode.V1), + cgroupVersion: ParseMode.V1), new( name: "cgroupv1 with suffix", line: "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23.aaaa", expectedContainerId: "ac679f8a8319c8cf7d38e1adf263bc08d23", - cgroupVersion: ContainerResourceDetector.ParseMode.V1), + cgroupVersion: ParseMode.V1), new( name: "cgroupv1 with prefix and suffix", line: "13:name=systemd:/podruntime/docker/kubepods/crio-dc679f8a8319c8cf7d38e1adf263bc08d23.stuff", expectedContainerId: "dc679f8a8319c8cf7d38e1adf263bc08d23", - cgroupVersion: ContainerResourceDetector.ParseMode.V1), + cgroupVersion: ParseMode.V1), new( name: "cgroupv1 with container Id", line: "13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356", expectedContainerId: "d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356", - cgroupVersion: ContainerResourceDetector.ParseMode.V1), + cgroupVersion: ParseMode.V1), }; private readonly List testValidCasesV2 = new() @@ -41,32 +48,32 @@ public class ContainerResourceDetectorTests name: "cgroupv2 with container Id", line: "13:name=systemd:/pod/d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356/hostname", expectedContainerId: "d86d75589bf6cc254f3e2cc29debdf85dde404998aa128997a819ff991827356", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), new( name: "cgroupv2 with full line", line: "473 456 254:1 /docker/containers/dc64b5743252dbaef6e30521c34d6bbd1620c8ce65bdb7bf9e7143b61bb5b183/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw", expectedContainerId: "dc64b5743252dbaef6e30521c34d6bbd1620c8ce65bdb7bf9e7143b61bb5b183", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), new( name: "cgroupv2 with minikube containerd mountinfo", line: "1537 1517 8:1 /var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3ff9320f4402ae6/hostname /etc/hostname rw,relatime - ext4 /dev/sda1 rw", expectedContainerId: "fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3ff9320f4402ae6", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), new( name: "cgroupv2 with minikube docker mountinfo", line: "2327 2307 8:1 /var/lib/docker/containers/a1551a1d7e1881d6c18d2c9ec462cab6ad3666825f0adb2098e9d5b198fd7e19/hostname /etc/hostname rw,relatime - ext4 /dev/sda1 rw", expectedContainerId: "a1551a1d7e1881d6c18d2c9ec462cab6ad3666825f0adb2098e9d5b198fd7e19", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), new( name: "cgroupv2 with minikube docker mountinfo2", line: "929 920 254:1 /docker/volumes/minikube/_data/lib/docker/containers/0eaa6718003210b6520f7e82d14b4c8d4743057a958a503626240f8d1900bc33/hostname /etc/hostname rw,relatime - ext4 /dev/vda1 rw", expectedContainerId: "0eaa6718003210b6520f7e82d14b4c8d4743057a958a503626240f8d1900bc33", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), new( name: "cgroupv2 with podman mountinfo", line: "1096 1088 0:104 /containers/overlay-containers/1a2de27e7157106568f7e081e42a8c14858c02bd9df30d6e352b298178b46809/userdata/hostname /etc/hostname rw,nosuid,nodev,relatime - tmpfs tmpfs rw,size=813800k,nr_inodes=203450,mode=700,uid=1000,gid=1000", expectedContainerId: "1a2de27e7157106568f7e081e42a8c14858c02bd9df30d6e352b298178b46809", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), }; private readonly List testInvalidCases = new() @@ -74,11 +81,11 @@ public class ContainerResourceDetectorTests new( name: "Invalid cgroupv1 line", line: "13:name=systemd:/podruntime/docker/kubepods/ac679f8a8319c8cf7d38e1adf263bc08d23zzzz", - cgroupVersion: ContainerResourceDetector.ParseMode.V1), + cgroupVersion: ParseMode.V1), new( name: "Invalid hex cgroupv2 line (contains a z)", line: "13:name=systemd:/var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/fb5916a02feca96bdeecd8e062df9e5e51d6617c8214b5e1f3fz9320f4402ae6/hostname", - cgroupVersion: ContainerResourceDetector.ParseMode.V2), + cgroupVersion: ParseMode.V2), }; [Fact] @@ -108,7 +115,7 @@ public void TestInvalidContainer() using var tempFile = new TempFile(); tempFile.Write(testCase.Line); Assert.Equal( - containerResourceDetector.BuildResource(tempFile.FilePath, ContainerResourceDetector.ParseMode.V2), + containerResourceDetector.BuildResource(tempFile.FilePath, ParseMode.V2), Resource.Empty); } @@ -118,7 +125,7 @@ public void TestInvalidContainer() using var tempFile = new TempFile(); tempFile.Write(testCase.Line); Assert.Equal( - containerResourceDetector.BuildResource(tempFile.FilePath, ContainerResourceDetector.ParseMode.V1), + containerResourceDetector.BuildResource(tempFile.FilePath, ParseMode.V1), Resource.Empty); } @@ -131,8 +138,19 @@ public void TestInvalidContainer() } // test invalid file - Assert.Equal(containerResourceDetector.BuildResource(Path.GetTempPath(), ContainerResourceDetector.ParseMode.V1), Resource.Empty); - Assert.Equal(containerResourceDetector.BuildResource(Path.GetTempPath(), ContainerResourceDetector.ParseMode.V2), Resource.Empty); + Assert.Equal(containerResourceDetector.BuildResource(Path.GetTempPath(), ParseMode.V1), Resource.Empty); + Assert.Equal(containerResourceDetector.BuildResource(Path.GetTempPath(), ParseMode.V2), Resource.Empty); + } + + [Fact] + public async Task TestK8sContainerId() + { + await using (_ = new MockK8sEndpoint("k8s/pod-response.json")) + { + var resourceAttributes = new ContainerResourceDetector(new MockK8sMetadataFetcher()).Detect().Attributes.ToDictionary(x => x.Key, x => x.Value); + + Assert.Equal(resourceAttributes[ContainerSemanticConventions.AttributeContainerId], "96724c05fa1be8d313f6db0e9872ca542b076839c4fd51ea4912a670ef538cbd"); + } } private static string GetContainerId(Resource resource) @@ -143,7 +161,7 @@ private static string GetContainerId(Resource resource) private sealed class TestCase { - public TestCase(string name, string line, ContainerResourceDetector.ParseMode cgroupVersion, string? expectedContainerId = null) + public TestCase(string name, string line, ParseMode cgroupVersion, string? expectedContainerId = null) { this.Name = name; this.Line = line; @@ -157,6 +175,83 @@ public TestCase(string name, string line, ContainerResourceDetector.ParseMode cg public string? ExpectedContainerId { get; } - public ContainerResourceDetector.ParseMode CgroupVersion { get; } + public ParseMode CgroupVersion { get; } + } + + private class MockK8sEndpoint : IAsyncDisposable + { + public readonly Uri Address; + private readonly IWebHost server; + + public MockK8sEndpoint(string responseJsonPath) + { + this.server = new WebHostBuilder() + .UseKestrel() + .UseUrls("http://127.0.0.1:5000") + .Configure(app => + { + app.Run(async context => + { + if (context.Request.Method == HttpMethods.Get && context.Request.Path == "/api/v1/namespaces/default/pods/pod1") + { + var content = await File.ReadAllTextAsync($"{Environment.CurrentDirectory}/{responseJsonPath}"); + var data = Encoding.UTF8.GetBytes(content); + context.Response.ContentType = "application/json"; + await context.Response.Body.WriteAsync(data); + } + else + { + context.Response.StatusCode = StatusCodes.Status404NotFound; + await context.Response.WriteAsync("Not found"); + } + }); + }).Build(); + this.server.Start(); + + this.Address = new Uri(this.server.ServerFeatures.Get()!.Addresses.First()); + } + + public async ValueTask DisposeAsync() + { + await this.DisposeAsyncCore(); + } + + protected virtual async ValueTask DisposeAsyncCore() + { + await this.server.StopAsync(); + } + } + + private sealed class MockK8sMetadataFetcher : IK8sMetadataFetcher + { + public string? GetApiCredential() + { + return Guid.NewGuid().ToString(); + } + + public string? GetContainerName() + { + return "my-container"; + } + + public string? GetHostname() + { + return "hostname"; + } + + public string? GetNamespace() + { + return "default"; + } + + public string? GetPodName() + { + return "pod1"; + } + + public string? GetServiceBaseUrl() + { + return "http://127.0.0.1:5000"; + } } } diff --git a/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj b/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj index 2bc9efb956..2c38f24e76 100644 --- a/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj +++ b/test/OpenTelemetry.ResourceDetectors.Container.Tests/OpenTelemetry.ResourceDetectors.Container.Tests.csproj @@ -7,8 +7,14 @@ enable + + + + PreserveNewest + + + - diff --git a/test/OpenTelemetry.ResourceDetectors.Container.Tests/k8s/pod-response.json b/test/OpenTelemetry.ResourceDetectors.Container.Tests/k8s/pod-response.json new file mode 100644 index 0000000000..b82a3e228a --- /dev/null +++ b/test/OpenTelemetry.ResourceDetectors.Container.Tests/k8s/pod-response.json @@ -0,0 +1,289 @@ +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "my-deployment-8d7c572790-46mgg", + "generateName": "my-deployment-8d7c572790-", + "namespace": "default", + "uid": "e393dfee-0ac3-4dd9-a10d-e748c0e3589a", + "resourceVersion": "14782", + "creationTimestamp": "2024-05-23T09:30:45Z", + "labels": { + "app": "my-app", + "pod-template-hash": "8d7c572790" + }, + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "name": "my-deployment-8d7c572790", + "uid": "4aebb45f-c8b0-4406-8b81-3cfc9619d147", + "controller": true, + "blockOwnerDeletion": true + } + ], + "managedFields": [ + { + "manager": "kube-controller-manager", + "operation": "Update", + "apiVersion": "v1", + "time": "2024-05-23T09:30:45Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"4aebb45f-c8b0-4406-8b81-3cfc9619d147\"}": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"my-container\"}": { + ".": {}, + "f:env": { + ".": {}, + "k:{\"name\":\"KUBERNETES_CONTAINER_NAME\"}": { + ".": {}, + "f:name": {}, + "f:value": {} + } + }, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:serviceAccount": {}, + "f:serviceAccountName": {}, + "f:terminationGracePeriodSeconds": {} + } + } + }, + { + "manager": "kubelet", + "operation": "Update", + "apiVersion": "v1", + "time": "2024-05-23T09:30:47Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"PodReadyToStartContainers\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:hostIPs": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"10.244.0.11\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "subresource": "status" + } + ] + }, + "spec": { + "volumes": [ + { + "name": "kube-api-access-npqxx", + "projected": { + "sources": [ + { + "serviceAccountToken": { + "expirationSeconds": 3607, + "path": "token" + } + }, + { + "configMap": { + "name": "kube-root-ca.crt", + "items": [ + { + "key": "ca.crt", + "path": "ca.crt" + } + ] + } + }, + { + "downwardAPI": { + "items": [ + { + "path": "namespace", + "fieldRef": { + "apiVersion": "v1", + "fieldPath": "metadata.namespace" + } + } + ] + } + } + ], + "defaultMode": 420 + } + } + ], + "containers": [ + { + "name": "my-container", + "image": "app:latest", + "env": [ + { + "name": "KUBERNETES_CONTAINER_NAME", + "value": "my-container" + } + ], + "resources": {}, + "volumeMounts": [ + { + "name": "kube-api-access-npqxx", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "Never" + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "my-service-account", + "serviceAccount": "my-service-account", + "nodeName": "minikube", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + } + ], + "priority": 0, + "enableServiceLinks": true, + "preemptionPolicy": "PreemptLowerPriority" + }, + "status": { + "phase": "Running", + "conditions": [ + { + "type": "PodReadyToStartContainers", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2024-05-23T09:30:47Z" + }, + { + "type": "Initialized", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2024-05-23T09:30:45Z" + }, + { + "type": "Ready", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2024-05-23T09:30:47Z" + }, + { + "type": "ContainersReady", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2024-05-23T09:30:47Z" + }, + { + "type": "PodScheduled", + "status": "True", + "lastProbeTime": null, + "lastTransitionTime": "2024-05-23T09:30:45Z" + } + ], + "hostIP": "192.168.49.1", + "hostIPs": [ + { + "ip": "192.168.49.1" + } + ], + "podIP": "10.244.0.11", + "podIPs": [ + { + "ip": "10.244.0.11" + } + ], + "startTime": "2024-05-23T09:30:45Z", + "containerStatuses": [ + { + "name": "my-container", + "state": { + "running": { + "startedAt": "2024-05-23T09:30:46Z" + } + }, + "lastState": {}, + "ready": true, + "restartCount": 0, + "image": "app:latest", + "imageID": "docker://sha256:7f8ec1d205a9364bf760b398cd21ae547ea08fd6213b594c64da80735bcf1e29", + "containerID": "docker://96724c05fa1be8d313f6db0e9872ca542b076839c4fd51ea4912a670ef538cbd", + "started": true + } + ], + "qosClass": "BestEffort" + } +} From d609b3918bbfb567074a2fae53cf649f6bd636e5 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 23 May 2024 23:17:11 +0000 Subject: [PATCH 13/30] Add null check --- .../K8sMetadataFetcher.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs b/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs index f4c4ca6710..4ab5666d8f 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/K8sMetadataFetcher.cs @@ -65,6 +65,12 @@ internal sealed class K8sMetadataFetcher : IK8sMetadataFetcher { var serviceHost = Environment.GetEnvironmentVariable(KubernetesServiceHostKey); var servicePort = Environment.GetEnvironmentVariable(KubernetesServicePortKey); + + if (string.IsNullOrWhiteSpace(serviceHost) || string.IsNullOrWhiteSpace(servicePort)) + { + return null; + } + return $"https://{serviceHost}:{servicePort}"; } } From 60f21371bd83949268188037d6955dae4a6a56a8 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 25 May 2024 07:53:14 +0000 Subject: [PATCH 14/30] Update README --- .../README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index 9c516070fc..5d8ac0a367 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -36,14 +36,13 @@ your application is running: ## Kubernetes -When running in a Kubernetes environment, the Container resource detector -requires permissions to access pod information in order -to extract the container ID. -This is achieved using Kubernetes Role-Based Access Control (RBAC). +To make container ID resolution work, container and pod name should be provided through KUBERNETES_CONTAINER_NAME and KUBERNETES_POD_NAME environment variable respectively and pod should have at least get permission to kubernetes resource pods. +It can be done by utilizing YAML anchoring, downwards API and RBAC (Role-Based Access Control). + +If KUBERNETES_POD_NAME is not provided, detector will use HOSTNAME as a fallback, but it may not work in some environments or if hostname was overridden in pod spec. + +Below is an example of how to configure sample pod to make container ID resolution working: -You will need to create a Role and RoleBinding to grant -the necessary permissions to the service account used by your application. -Below is an example of how to configure these RBAC resources: ```yaml apiVersion: v1 @@ -92,6 +91,10 @@ spec: env: - name: KUBERNETES_CONTAINER_NAME value: *container_name + - name: KUBERNETES_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name ``` ## References From e66dde2ae33cc5f3688af2da51513e10ff2d5313 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 25 May 2024 08:02:45 +0000 Subject: [PATCH 15/30] Fix README lint errors --- .../README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index 5d8ac0a367..772ebcb0a1 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -36,12 +36,19 @@ your application is running: ## Kubernetes -To make container ID resolution work, container and pod name should be provided through KUBERNETES_CONTAINER_NAME and KUBERNETES_POD_NAME environment variable respectively and pod should have at least get permission to kubernetes resource pods. -It can be done by utilizing YAML anchoring, downwards API and RBAC (Role-Based Access Control). - -If KUBERNETES_POD_NAME is not provided, detector will use HOSTNAME as a fallback, but it may not work in some environments or if hostname was overridden in pod spec. - -Below is an example of how to configure sample pod to make container ID resolution working: +To make container ID resolution work, container and pod name should be provided +through KUBERNETES_CONTAINER_NAME and KUBERNETES_POD_NAME environment variable +respectively and pod should have at least +get permission to kubernetes resource pods. +It can be done by utilizing YAML anchoring, downwards API +and RBAC (Role-Based Access Control). + +If KUBERNETES_POD_NAME is not provided, detector will use HOSTNAME +as a fallback, but it may not work in some environments +or if hostname was overridden in pod spec. + +Below is an example of how to configure sample pod +to make container ID resolution working: ```yaml From a96d991f6a7dfcf30a8be309c4d229d50ace0724 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 25 May 2024 08:02:58 +0000 Subject: [PATCH 16/30] Fast fail for non K8s environment --- .../ContainerResourceDetector.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index e657f869e9..12853d28d0 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -70,7 +70,6 @@ public Resource Detect() internal Resource BuildResource(string path, ParseMode parseMode) { var containerId = this.ExtractContainerId(path, parseMode); - if (string.IsNullOrEmpty(containerId)) { return Resource.Empty; @@ -134,7 +133,6 @@ internal Resource BuildResource(string path, ParseMode parseMode) private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex, int endIndex) { startIndex = (startIndex == -1) ? 0 : startIndex + 1; - if (endIndex == -1) { endIndex = input.Length; @@ -148,6 +146,11 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex try { var baseUrl = this.k8sMetadataFetcher.GetServiceBaseUrl(); + if (string.IsNullOrWhiteSpace(baseUrl)) + { + return null; + } + var @namespace = this.k8sMetadataFetcher.GetNamespace(); var hostname = this.k8sMetadataFetcher.GetPodName() ?? this.k8sMetadataFetcher.GetHostname(); var containerName = this.k8sMetadataFetcher.GetContainerName(); From 6f810af8fa056462b7d9a8bd8797cb4ec0e0af40 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 25 May 2024 08:07:02 +0000 Subject: [PATCH 17/30] Remove extra line --- src/OpenTelemetry.ResourceDetectors.Container/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/README.md b/src/OpenTelemetry.ResourceDetectors.Container/README.md index 772ebcb0a1..824064d5e6 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/README.md +++ b/src/OpenTelemetry.ResourceDetectors.Container/README.md @@ -50,7 +50,6 @@ or if hostname was overridden in pod spec. Below is an example of how to configure sample pod to make container ID resolution working: - ```yaml apiVersion: v1 kind: ServiceAccount From 2977bc7e66919e4c7652a2a60015f7338b5c3f9d Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 25 May 2024 19:57:52 +0000 Subject: [PATCH 18/30] Fast fail for non K8s env and container name not provided --- .../ContainerResourceDetector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs index 57ff1a5c0a..6709db9c33 100644 --- a/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.Container/ContainerResourceDetector.cs @@ -146,14 +146,14 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex try { var baseUrl = this.k8sMetadataFetcher.GetServiceBaseUrl(); - if (string.IsNullOrEmpty(baseUrl)) + var containerName = this.k8sMetadataFetcher.GetContainerName(); + if (string.IsNullOrEmpty(baseUrl) && string.IsNullOrEmpty(containerName)) { return null; } var @namespace = this.k8sMetadataFetcher.GetNamespace(); var hostname = this.k8sMetadataFetcher.GetPodName() ?? this.k8sMetadataFetcher.GetHostname(); - var containerName = this.k8sMetadataFetcher.GetContainerName(); var url = $"{baseUrl}/api/v1/namespaces/{@namespace}/pods/{hostname}"; var credentials = this.k8sMetadataFetcher.GetApiCredential(); if (string.IsNullOrEmpty(credentials)) From be15a322d8abec986ed8a45828f62446c6a7032b Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:43:59 +0000 Subject: [PATCH 19/30] Fix EventSource name --- .../ContainerResourceEventSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs index 333b206003..7982f80bca 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Resources.Container; -[EventSource(Name = "OpenTelemetry-ResourceDetectors-Container")] +[EventSource(Name = "OpenTelemetry-Resources-Container")] internal class ContainerResourceEventSource : EventSource, IServerCertificateValidationEventSource { public static ContainerResourceEventSource Log = new(); From f5c4122843c73d69c9f0e1fdc90c5644f293ad4d Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:24:03 +0000 Subject: [PATCH 20/30] Fix CHANGELOG --- src/OpenTelemetry.Resources.Container/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/CHANGELOG.md b/src/OpenTelemetry.Resources.Container/CHANGELOG.md index 140ebc63d0..2dd0092ea6 100644 --- a/src/OpenTelemetry.Resources.Container/CHANGELOG.md +++ b/src/OpenTelemetry.Resources.Container/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Add Kubernetes support. + ([#1699](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1699)) + ## 1.0.0-beta.8 Released 2024-Jun-04 @@ -20,9 +23,6 @@ from `OpenTelemetry-ResourceDetectors-Container` to `OpenTelemetry-Resources-Container`. ([#1849](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1849)) -* Add Kubernetes support. - ([#1699](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1699)) - ## 1.0.0-beta.7 Released 2024-Apr-05 From 811ba2b8b61db47b3a6c2cc415722832fe3ff877 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:48:11 +0000 Subject: [PATCH 21/30] Apply suggestions from review --- src/OpenTelemetry.Resources.Container/ContainerDetector.cs | 2 +- .../ContainerResourceEventSource.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index 7c63f069bc..73d13957f9 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -146,7 +146,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex { var baseUrl = this.k8sMetadataFetcher.GetServiceBaseUrl(); var containerName = this.k8sMetadataFetcher.GetContainerName(); - if (string.IsNullOrEmpty(baseUrl) && string.IsNullOrEmpty(containerName)) + if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(containerName)) { return null; } diff --git a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs index 7982f80bca..446919dfcf 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs @@ -30,7 +30,7 @@ public void ExtractResourceAttributesException(string format, Exception ex) [Event(EventIdFailedToExtractResourceAttributes, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Error)] public void FailedToExtractResourceAttributes(string format, string exception) { - this.WriteEvent(1, format, exception); + this.WriteEvent(EventIdFailedToExtractResourceAttributes, format, exception); } [Event(EventIdFailedToValidateCertificate, Message = "Failed to validate certificate. Details: '{0}'", Level = EventLevel.Warning)] From 09d178589529d6e7e0f22fe9b3b3987d43e7a7e5 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:36:58 +0000 Subject: [PATCH 22/30] Apply suggestions from review --- .../ContainerDetector.cs | 126 ++++++++---------- .../K8sMetadataFetcher.cs | 20 +-- .../ParseMode.cs | 5 - .../README.md | 4 +- .../SourceGenerationContext.cs | 2 - .../ContainerDetectorTests.cs | 24 ++-- ...Telemetry.Resources.Container.Tests.csproj | 2 +- 7 files changed, 77 insertions(+), 106 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index 73d13957f9..8955e85c4a 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -46,37 +46,74 @@ internal ContainerDetector(IK8sMetadataFetcher k8sMetadataFetcher) /// Resource with key-value pairs of resource attributes. public Resource Detect() { - var resource = this.BuildResource(Filepath, ParseMode.K8s); - if (resource == Resource.Empty) + var containerId = this.ExtractK8sContainerId(); + if (!string.IsNullOrEmpty(containerId)) { - resource = this.BuildResource(Filepath, ParseMode.V1); + return BuildResource(containerId); } - if (resource == Resource.Empty) + containerId = this.ExtractContainerId(Filepath, ParseMode.V1); + if (!string.IsNullOrEmpty(containerId)) { - resource = this.BuildResource(FilepathV2, ParseMode.V2); + return BuildResource(containerId); } - return resource; + containerId = this.ExtractContainerId(FilepathV2, ParseMode.V2); + if (!string.IsNullOrEmpty(containerId)) + { + return BuildResource(containerId); + } + + return Resource.Empty; + + static Resource BuildResource(string containerId) + { + return new Resource(new List>(1) { new(ContainerSemanticConventions.AttributeContainerId, containerId!) }); + } } /// - /// Builds the resource attributes from Container Id in file path. + /// Extracts Container Id from path using the cgroupv1 format. /// - /// File path where container id exists. + /// cgroup path. /// CGroup Version of file to parse from. - /// Returns Resource with list of key-value pairs of container resource attributes if container id exists else empty resource. - internal Resource BuildResource(string path, ParseMode parseMode) + /// Container Id, if not found or exception being thrown. + internal string? ExtractContainerId(string path, ParseMode parseMode) { - var containerId = this.ExtractContainerId(path, parseMode); - 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>(1) { new(ContainerSemanticConventions.AttributeContainerId, containerId!) }); + ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerDetector)} : Failed to extract Container id from path", ex); } + + return null; } /// @@ -157,7 +194,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex var credentials = this.k8sMetadataFetcher.GetApiCredential(); if (string.IsNullOrEmpty(credentials)) { - return string.Empty; + return null; } using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); @@ -165,13 +202,13 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex var pod = DeserializeK8sResponse(response); if (pod?.Status?.ContainerStatuses == null) { - return string.Empty; + return null; } var container = pod.Status.ContainerStatuses.SingleOrDefault(p => p.Name == containerName); - if (container is null || string.IsNullOrEmpty(container.Id)) + if (string.IsNullOrEmpty(container?.Id)) { - return string.Empty; + return null; } // Container's ID is in :// format. @@ -194,55 +231,4 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex #endif } } - - /// - /// Extracts Container Id from path using the cgroupv1 format. - /// - /// cgroup path. - /// CGroup Version of file to parse from. - /// Container Id, if not found or exception being thrown. - private string? ExtractContainerId(string path, ParseMode parseMode) - { - try - { - if (parseMode == ParseMode.K8s) - { - return this.ExtractK8sContainerId(); - } - else - { - 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; - } - } - } - } - catch (Exception ex) - { - ContainerResourceEventSource.Log.ExtractResourceAttributesException($"{nameof(ContainerDetector)} : Failed to extract Container id from path", ex); - } - - return null; - } } diff --git a/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs b/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs index 8a77db3e43..3007c112e8 100644 --- a/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs +++ b/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs @@ -9,11 +9,11 @@ namespace OpenTelemetry.Resources.Container; internal sealed class K8sMetadataFetcher : IK8sMetadataFetcher { - private const string KubernetesServiceHostKey = "KUBERNETES_SERVICE_HOST"; - private const string KubernetesServicePortKey = "KUBERNETES_SERVICE_PORT_HTTPS"; - private const string KubernetesHostnameKey = "HOSTNAME"; - private const string KubernetesPodNameKey = "KUBERNETES_POD_NAME"; - private const string KubernetesContainerNameKey = "KUBERNETES_CONTAINER_NAME"; + private const string KubernetesServiceHostEnvVar = "KUBERNETES_SERVICE_HOST"; + private const string KubernetesServicePortEnvVar = "KUBERNETES_SERVICE_PORT_HTTPS"; + private const string KubernetesHostnameEnvVar = "HOSTNAME"; + private const string KubernetesPodNameEnvVar = "KUBERNETES_POD_NAME"; + private const string KubernetesContainerNameEnvVar = "KUBERNETES_CONTAINER_NAME"; private const string KubernetesNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; private const string KubernetesCredentialPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"; @@ -43,17 +43,17 @@ internal sealed class K8sMetadataFetcher : IK8sMetadataFetcher public string? GetContainerName() { - return Environment.GetEnvironmentVariable(KubernetesContainerNameKey); + return Environment.GetEnvironmentVariable(KubernetesContainerNameEnvVar); } public string? GetHostname() { - return Environment.GetEnvironmentVariable(KubernetesHostnameKey); + return Environment.GetEnvironmentVariable(KubernetesHostnameEnvVar); } public string? GetPodName() { - return Environment.GetEnvironmentVariable(KubernetesPodNameKey); + return Environment.GetEnvironmentVariable(KubernetesPodNameEnvVar); } public string? GetNamespace() @@ -63,8 +63,8 @@ internal sealed class K8sMetadataFetcher : IK8sMetadataFetcher public string? GetServiceBaseUrl() { - var serviceHost = Environment.GetEnvironmentVariable(KubernetesServiceHostKey); - var servicePort = Environment.GetEnvironmentVariable(KubernetesServicePortKey); + var serviceHost = Environment.GetEnvironmentVariable(KubernetesServiceHostEnvVar); + var servicePort = Environment.GetEnvironmentVariable(KubernetesServicePortEnvVar); if (string.IsNullOrWhiteSpace(serviceHost) || string.IsNullOrWhiteSpace(servicePort)) { diff --git a/src/OpenTelemetry.Resources.Container/ParseMode.cs b/src/OpenTelemetry.Resources.Container/ParseMode.cs index 7f712ec9a0..5a3ce66bf7 100644 --- a/src/OpenTelemetry.Resources.Container/ParseMode.cs +++ b/src/OpenTelemetry.Resources.Container/ParseMode.cs @@ -17,9 +17,4 @@ internal enum ParseMode /// Represents CGroupV2. /// V2, - - /// - /// Represents Kubernetes. - /// - K8s, } diff --git a/src/OpenTelemetry.Resources.Container/README.md b/src/OpenTelemetry.Resources.Container/README.md index 8b5a2760d9..f9b128870d 100644 --- a/src/OpenTelemetry.Resources.Container/README.md +++ b/src/OpenTelemetry.Resources.Container/README.md @@ -38,13 +38,13 @@ your application is running: ## Kubernetes To make container ID resolution work, container and pod name should be provided -through KUBERNETES_CONTAINER_NAME and KUBERNETES_POD_NAME environment variable +through `KUBERNETES_CONTAINER_NAME` and `KUBERNETES_POD_NAME` environment variable respectively and pod should have at least get permission to kubernetes resource pods. It can be done by utilizing YAML anchoring, downwards API and RBAC (Role-Based Access Control). -If KUBERNETES_POD_NAME is not provided, detector will use HOSTNAME +If `KUBERNETES_POD_NAME` is not provided, detector will use `HOSTNAME` as a fallback, but it may not work in some environments or if hostname was overridden in pod spec. diff --git a/src/OpenTelemetry.Resources.Container/SourceGenerationContext.cs b/src/OpenTelemetry.Resources.Container/SourceGenerationContext.cs index 9653854db8..22cb03deb1 100644 --- a/src/OpenTelemetry.Resources.Container/SourceGenerationContext.cs +++ b/src/OpenTelemetry.Resources.Container/SourceGenerationContext.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET6_0_OR_GREATER using System.Text.Json.Serialization; using OpenTelemetry.Resources.Container.Models; @@ -21,4 +20,3 @@ namespace OpenTelemetry.Resources.Container; internal sealed partial class SourceGenerationContext : JsonSerializerContext { } -#endif diff --git a/test/OpenTelemetry.Resources.Container.Tests/ContainerDetectorTests.cs b/test/OpenTelemetry.Resources.Container.Tests/ContainerDetectorTests.cs index 6fefd6e2ff..d865a8b7ef 100644 --- a/test/OpenTelemetry.Resources.Container.Tests/ContainerDetectorTests.cs +++ b/test/OpenTelemetry.Resources.Container.Tests/ContainerDetectorTests.cs @@ -99,7 +99,7 @@ public void TestValidContainer() tempFile.Write(testCase.Line); Assert.Equal( testCase.ExpectedContainerId, - GetContainerId(containerDetector.BuildResource(tempFile.FilePath, testCase.CgroupVersion))); + containerDetector.ExtractContainerId(tempFile.FilePath, testCase.CgroupVersion)); } } @@ -113,9 +113,8 @@ public void TestInvalidContainer() { using var tempFile = new TempFile(); tempFile.Write(testCase.Line); - Assert.Equal( - containerDetector.BuildResource(tempFile.FilePath, ParseMode.V2), - Resource.Empty); + Assert.Null( + containerDetector.ExtractContainerId(tempFile.FilePath, ParseMode.V2)); } // Valid in cgroupv1 is not valid in cgroupv1 @@ -123,9 +122,8 @@ public void TestInvalidContainer() { using var tempFile = new TempFile(); tempFile.Write(testCase.Line); - Assert.Equal( - containerDetector.BuildResource(tempFile.FilePath, ParseMode.V1), - Resource.Empty); + Assert.Null( + containerDetector.ExtractContainerId(tempFile.FilePath, ParseMode.V1)); } // test invalid cases @@ -133,12 +131,12 @@ public void TestInvalidContainer() { using var tempFile = new TempFile(); tempFile.Write(testCase.Line); - Assert.Equal(containerDetector.BuildResource(tempFile.FilePath, testCase.CgroupVersion), Resource.Empty); + Assert.Null(containerDetector.ExtractContainerId(tempFile.FilePath, testCase.CgroupVersion)); } // test invalid file - Assert.Equal(containerDetector.BuildResource(Path.GetTempPath(), ParseMode.V1), Resource.Empty); - Assert.Equal(containerDetector.BuildResource(Path.GetTempPath(), ParseMode.V2), Resource.Empty); + Assert.Null(containerDetector.ExtractContainerId(Path.GetTempPath(), ParseMode.V1)); + Assert.Null(containerDetector.ExtractContainerId(Path.GetTempPath(), ParseMode.V2)); } [Fact] @@ -152,12 +150,6 @@ public async Task TestK8sContainerId() } } - private static string GetContainerId(Resource resource) - { - var resourceAttributes = resource.Attributes.ToDictionary(x => x.Key, x => x.Value); - return resourceAttributes[ContainerSemanticConventions.AttributeContainerId].ToString()!; - } - private sealed class TestCase { public TestCase(string name, string line, ParseMode cgroupVersion, string? expectedContainerId = null) diff --git a/test/OpenTelemetry.Resources.Container.Tests/OpenTelemetry.Resources.Container.Tests.csproj b/test/OpenTelemetry.Resources.Container.Tests/OpenTelemetry.Resources.Container.Tests.csproj index 0c9ba70cf8..3ff5eb23e3 100644 --- a/test/OpenTelemetry.Resources.Container.Tests/OpenTelemetry.Resources.Container.Tests.csproj +++ b/test/OpenTelemetry.Resources.Container.Tests/OpenTelemetry.Resources.Container.Tests.csproj @@ -7,7 +7,7 @@ enable - + PreserveNewest From 5442c3ff74576dc61e313178ffcd452726631122 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:56:51 +0000 Subject: [PATCH 23/30] Simplify deserialization --- .../ContainerDetector.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index 8955e85c4a..d7174e1b43 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -199,7 +199,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); - var pod = DeserializeK8sResponse(response); + var pod = ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.K8sPod); if (pod?.Status?.ContainerStatuses == null) { return null; @@ -221,14 +221,5 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex } return null; - - static K8sPod? DeserializeK8sResponse(string response) - { -#if NET6_0_OR_GREATER - return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.K8sPod); -#else - return ResourceDetectorUtils.DeserializeFromString(response); -#endif - } } } From 4dd428dce9d19e319dfad958364aaa9ef7cf33d0 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 6 Jun 2024 07:01:49 +0000 Subject: [PATCH 24/30] Remove unnecessary using --- src/OpenTelemetry.Resources.Container/ContainerDetector.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index d7174e1b43..14c6538b03 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; -using OpenTelemetry.Resources.Container.Models; using OpenTelemetry.Resources.Container.Utils; namespace OpenTelemetry.Resources.Container; From 72463c5a2f56548239f6d01247c88612387ccbee Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Fri, 28 Jun 2024 06:31:26 +0000 Subject: [PATCH 25/30] Remove unnecessary usings --- .../ContainerResourceEventSource.cs | 1 - src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs | 2 -- src/OpenTelemetry.Resources.Container/Models/K8sPodStatus.cs | 1 - 3 files changed, 4 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs index 446919dfcf..db553254cc 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System; using System.Diagnostics.Tracing; using OpenTelemetry.Internal; diff --git a/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs b/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs index 3007c112e8..2ff6c58d05 100644 --- a/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs +++ b/src/OpenTelemetry.Resources.Container/K8sMetadataFetcher.cs @@ -1,8 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System; -using System.IO; using System.Text; namespace OpenTelemetry.Resources.Container; diff --git a/src/OpenTelemetry.Resources.Container/Models/K8sPodStatus.cs b/src/OpenTelemetry.Resources.Container/Models/K8sPodStatus.cs index 66dc04fa29..7451635af3 100644 --- a/src/OpenTelemetry.Resources.Container/Models/K8sPodStatus.cs +++ b/src/OpenTelemetry.Resources.Container/Models/K8sPodStatus.cs @@ -1,7 +1,6 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -using System.Collections.Generic; using System.Text.Json.Serialization; namespace OpenTelemetry.Resources.Container.Models; From 0c3bad3363020fadcea982512d7cb3e59f64bb4a Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Fri, 28 Jun 2024 06:31:54 +0000 Subject: [PATCH 26/30] Move and use AsyncHelper --- opentelemetry-dotnet-contrib.sln | 1 + .../OpenTelemetry.Resources.AWS.csproj | 1 + src/OpenTelemetry.Resources.Container/ContainerDetector.cs | 2 +- .../OpenTelemetry.Resources.Container.csproj | 1 + src/{OpenTelemetry.Resources.AWS => Shared}/AsyncHelper.cs | 2 +- 5 files changed, 5 insertions(+), 2 deletions(-) rename src/{OpenTelemetry.Resources.AWS => Shared}/AsyncHelper.cs (97%) diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index dc47d5de4d..0a13cfdd87 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -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 diff --git a/src/OpenTelemetry.Resources.AWS/OpenTelemetry.Resources.AWS.csproj b/src/OpenTelemetry.Resources.AWS/OpenTelemetry.Resources.AWS.csproj index 010589d8d7..0c889615ed 100644 --- a/src/OpenTelemetry.Resources.AWS/OpenTelemetry.Resources.AWS.csproj +++ b/src/OpenTelemetry.Resources.AWS/OpenTelemetry.Resources.AWS.csproj @@ -27,6 +27,7 @@ + diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index d94e1ba1bf..4b44bef915 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -193,7 +193,7 @@ private static string RemovePrefixAndSuffixIfNeeded(string input, int startIndex } using var httpClientHandler = ServerCertificateValidationHandler.Create(K8sCertificatePath, ContainerResourceEventSource.Log); - var response = ResourceDetectorUtils.SendOutRequest(url, "GET", new KeyValuePair("Authorization", credentials), httpClientHandler).GetAwaiter().GetResult(); + var response = AsyncHelper.RunSync(() => ResourceDetectorUtils.SendOutRequestAsync(url, HttpMethod.Get, new KeyValuePair("Authorization", credentials), httpClientHandler)); var pod = ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.K8sPod); if (pod?.Status?.ContainerStatuses == null) { diff --git a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj index 588093080b..5af14b167e 100644 --- a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj +++ b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj @@ -17,6 +17,7 @@ + diff --git a/src/OpenTelemetry.Resources.AWS/AsyncHelper.cs b/src/Shared/AsyncHelper.cs similarity index 97% rename from src/OpenTelemetry.Resources.AWS/AsyncHelper.cs rename to src/Shared/AsyncHelper.cs index f999beff0e..3a34165ff5 100644 --- a/src/OpenTelemetry.Resources.AWS/AsyncHelper.cs +++ b/src/Shared/AsyncHelper.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -namespace OpenTelemetry.Resources.AWS; +namespace OpenTelemetry.Resources; /// /// A helper class for running asynchronous methods synchronously. From fed42832f28d514a97524ad652a433f0635996f4 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman2@users.noreply.github.com> Date: Tue, 6 Aug 2024 11:27:33 +0000 Subject: [PATCH 27/30] Update src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs Co-authored-by: Weihan Li --- .../ContainerResourceEventSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs index db553254cc..5283956384 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerResourceEventSource.cs @@ -7,7 +7,7 @@ namespace OpenTelemetry.Resources.Container; [EventSource(Name = "OpenTelemetry-Resources-Container")] -internal class ContainerResourceEventSource : EventSource, IServerCertificateValidationEventSource +internal sealed class ContainerResourceEventSource : EventSource, IServerCertificateValidationEventSource { public static ContainerResourceEventSource Log = new(); From 8f72ae3bdc89e9f79305feb3fe05c61f6cf410b2 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 5 Oct 2024 09:03:56 +0000 Subject: [PATCH 28/30] Few fixes following .NET Standard 2.0 --- .../ContainerDetector.cs | 4 ++-- .../OpenTelemetry.Resources.Container.csproj | 6 +++++ src/Shared/ResourceDetectorUtils.cs | 22 +++++++++---------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs index 2eaf3b584b..d86d621b6b 100644 --- a/src/OpenTelemetry.Resources.Container/ContainerDetector.cs +++ b/src/OpenTelemetry.Resources.Container/ContainerDetector.cs @@ -92,9 +92,9 @@ static Resource BuildResource(string containerId) containerId = GetIdFromLineV1(line); } #if NET - else if (cgroupVersion == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal)) + else if (parseMode == ParseMode.V2 && line.Contains(Hostname, StringComparison.Ordinal)) #else - else if (cgroupVersion == ParseMode.V2 && line.Contains(Hostname)) + else if (parseMode == ParseMode.V2 && line.Contains(Hostname)) #endif { containerId = GetIdFromLineV2(line); diff --git a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj index d1c096ebf5..d687f8b762 100644 --- a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj +++ b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj @@ -7,6 +7,11 @@ Resources.Container- + + + $(NoWarn);nullable + + @@ -15,6 +20,7 @@ + diff --git a/src/Shared/ResourceDetectorUtils.cs b/src/Shared/ResourceDetectorUtils.cs index 422f71f55a..5b89c51cfb 100644 --- a/src/Shared/ResourceDetectorUtils.cs +++ b/src/Shared/ResourceDetectorUtils.cs @@ -6,7 +6,7 @@ #endif using System.Text; using System.Text.Json; -#if NET +#if !NETFRAMEWORK using System.Text.Json.Serialization.Metadata; #endif @@ -17,7 +17,7 @@ namespace OpenTelemetry.Resources; /// internal static class ResourceDetectorUtils { -#if !NET +#if NETFRAMEWORK private static readonly JsonSerializerOptions JsonSerializerOptions = new(JsonSerializerDefaults.Web); #endif @@ -47,31 +47,31 @@ internal static async Task SendOutRequestAsync( } } -#if NET - internal static T? DeserializeFromFile(string filePath, JsonTypeInfo jsonTypeInfo) +#if NETFRAMEWORK + internal static T? DeserializeFromFile(string filePath) { using (var stream = GetStream(filePath)) { - return JsonSerializer.Deserialize(stream, jsonTypeInfo); + return JsonSerializer.Deserialize(stream, JsonSerializerOptions); } } - internal static T? DeserializeFromString(string json, JsonTypeInfo jsonTypeInfo) + internal static T? DeserializeFromString(string json) { - return JsonSerializer.Deserialize(json, jsonTypeInfo); + return JsonSerializer.Deserialize(json, JsonSerializerOptions); } #else - internal static T? DeserializeFromFile(string filePath) + internal static T? DeserializeFromFile(string filePath, JsonTypeInfo jsonTypeInfo) { using (var stream = GetStream(filePath)) { - return JsonSerializer.Deserialize(stream, JsonSerializerOptions); + return JsonSerializer.Deserialize(stream, jsonTypeInfo); } } - internal static T? DeserializeFromString(string json) + internal static T? DeserializeFromString(string json, JsonTypeInfo jsonTypeInfo) { - return JsonSerializer.Deserialize(json, JsonSerializerOptions); + return JsonSerializer.Deserialize(json, jsonTypeInfo); } #endif From c81dd58df66e9322103f8bed5940450f24c34211 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Sat, 5 Oct 2024 09:19:49 +0000 Subject: [PATCH 29/30] Additional fixes --- src/OpenTelemetry.Resources.AWS/AWSEBSDetector.cs | 6 +++--- src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs | 6 +++--- src/OpenTelemetry.Resources.AWS/SourceGenerationContext.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/OpenTelemetry.Resources.AWS/AWSEBSDetector.cs b/src/OpenTelemetry.Resources.AWS/AWSEBSDetector.cs index c17c291add..80acea1404 100644 --- a/src/OpenTelemetry.Resources.AWS/AWSEBSDetector.cs +++ b/src/OpenTelemetry.Resources.AWS/AWSEBSDetector.cs @@ -84,10 +84,10 @@ internal static List> ExtractResourceAttributes(AWS internal static AWSEBSMetadataModel? GetEBSMetadata(string filePath) { -#if NET - return ResourceDetectorUtils.DeserializeFromFile(filePath, SourceGenerationContext.Default.AWSEBSMetadataModel); -#else +#if NETFRAMEWORK return ResourceDetectorUtils.DeserializeFromFile(filePath); +#else + return ResourceDetectorUtils.DeserializeFromFile(filePath, SourceGenerationContext.Default.AWSEBSMetadataModel); #endif } } diff --git a/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs b/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs index 4d41c33338..076adbf48d 100644 --- a/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs +++ b/src/OpenTelemetry.Resources.AWS/AWSEC2Detector.cs @@ -83,10 +83,10 @@ internal static List> ExtractResourceAttributes(AWS internal static AWSEC2IdentityDocumentModel? DeserializeResponse(string response) { -#if NET - return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.AWSEC2IdentityDocumentModel); -#else +#if NETFRAMEWORK return ResourceDetectorUtils.DeserializeFromString(response); +#else + return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.AWSEC2IdentityDocumentModel); #endif } diff --git a/src/OpenTelemetry.Resources.AWS/SourceGenerationContext.cs b/src/OpenTelemetry.Resources.AWS/SourceGenerationContext.cs index 51f0b9a4d5..8857095023 100644 --- a/src/OpenTelemetry.Resources.AWS/SourceGenerationContext.cs +++ b/src/OpenTelemetry.Resources.AWS/SourceGenerationContext.cs @@ -1,7 +1,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if NET +#if !NETFRAMEWORK using System.Text.Json.Serialization; using OpenTelemetry.Resources.AWS.Models; From 1f7d8f5808bd007bf7e664ebb2a840fffb26ef20 Mon Sep 17 00:00:00 2001 From: joegoldman2 <147369450+joegoldman@users.noreply.github.com> Date: Thu, 17 Oct 2024 06:04:07 +0000 Subject: [PATCH 30/30] Fix STJ version --- .../OpenTelemetry.Resources.Container.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj index d687f8b762..1093920143 100644 --- a/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj +++ b/src/OpenTelemetry.Resources.Container/OpenTelemetry.Resources.Container.csproj @@ -20,7 +20,7 @@ - +