Module: Datadog::Core::Environment::Container

Defined in:
lib/datadog/core/environment/container.rb

Overview

For container environments

Defined Under Namespace

Classes: Entry

Constant Summary collapse

UUID_PATTERN =
'[0-9a-f]{8}[-_]?[0-9a-f]{4}[-_]?[0-9a-f]{4}[-_]?[0-9a-f]{4}[-_]?[0-9a-f]{12}'
CONTAINER_PATTERN =
'[0-9a-f]{64}'
PLATFORM_REGEX =
/(?<platform>.*?)(?:.slice)?$/.freeze
POD_REGEX =
/(?<pod>(pod)?#{UUID_PATTERN})(?:.slice)?$/.freeze
CONTAINER_REGEX =
/(?<container>#{UUID_PATTERN}|#{CONTAINER_PATTERN})(?:.scope)?$/.freeze
FARGATE_14_CONTAINER_REGEX =
/(?<container>[0-9a-f]{32}-[0-9]{1,10})/.freeze
HOST_CGROUP_NAMESPACE_INODE =

From github.com/torvalds/linux/blob/5859a2b1991101d6b978f3feb5325dad39421f29/include/linux/proc_ns.h#L41-L49 Currently, the host namespace inode number is hardcoded. We use it to determine if we’re running in the host namespace. This detection approach does not work when running in [“Docker-in-Docker”](www.docker.com/resources/docker-in-docker-containerized-ci-workflows-dockercon-2023/).

0xEFFFFFFB

Class Method Summary collapse

Class Method Details

.container_idString?

The unique identifier of the current container in the container environment.

Returns:

  • (String, nil)

    The container ID, or nil if not running in a containerized environment



73
74
75
# File 'lib/datadog/core/environment/container.rb', line 73

def container_id
  entry.container_id
end

.entity_idObject

Container ID, prefixed with “ci-” or Inode, prefixed with “in-”.



47
48
49
50
51
52
53
# File 'lib/datadog/core/environment/container.rb', line 47

def entity_id
  if container_id
    "ci-#{container_id}"
  elsif inode
    "in-#{inode}"
  end
end

.entryObject

All cgroup entries have the same container identity. The first valid one is sufficient. v2 entries are preferred over v1.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/datadog/core/environment/container.rb', line 120

def entry
  return @entry if defined?(@entry)

  # Scan all v2 entries first, only then falling back to v1 entries.
  #
  # To do this, we {Enumerable#partition} the list between v1 and v2,
  # with a `true` predicate for v2 entries, making v2 first
  # partition returned.
  #
  # All v2 entries have the `hierarchy` set to zero.
  # v1 entries have a non-zero `hierarchy`.
  entries = Cgroup.entries.partition { |d| d.hierarchy == '0' }.flatten(1)
  entries.each do |entry_obj|
    path = entry_obj.path
    next unless path

    # To ease handling, remove the emtpy leading "",
    # as `path` starts with a "/".
    path.delete_prefix!('/')
    parts = path.split('/')

    # With not path information, we can still use the inode
    if parts.empty? && entry_obj.inode && !running_on_host?
      return @entry = Entry.new(nil, nil, nil, entry_obj.inode)
    end

    platform = parts[0][PLATFORM_REGEX, :platform]

    # Extract container_id and task_uid based on path structure
    container_id = task_uid = nil
    if parts.length >= 2
      # Try standard container regex first
      if (container_id = parts[-1][CONTAINER_REGEX, :container])
        # For 3+ parts, also extract task_uid
        if parts.length > 2
          task_uid = parts[-2][POD_REGEX, :pod] || parts[1][POD_REGEX, :pod]
        end
      else
        # Fall back to Fargate regex
        container_id = parts[-1][FARGATE_14_CONTAINER_REGEX, :container]
      end
    end

    # container_id is a better container identifier than inode.
    # We MUST only populate one of them, to avoid container identification ambiguity.
    if container_id
      return @entry = Entry.new(platform, task_uid, container_id)
    elsif entry_obj.inode && !running_on_host?
      return @entry = Entry.new(platform, task_uid, nil, entry_obj.inode)
    end
  end

  @entry = Entry.new # Empty entry if no valid cgroup entry is found
rescue => e
  Datadog.logger.debug(
    "Error while reading container entry. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
  )
  @entry = Entry.new unless defined?(@entry)
  @entry
end

.external_envObject

External data supplied by the Datadog Cluster Agent Admission Controller.

See Also:

  • for more details.


57
58
59
# File 'lib/datadog/core/environment/container.rb', line 57

def external_env
  Datadog.configuration.container.external_env
end

.inodeInteger?

A unique identifier for the execution context (container or host namespace).

Used as a fallback identifier when #container_id is unavailable.

Returns:

  • (Integer, nil)

    The namespace inode, or nil if unavailable



93
94
95
# File 'lib/datadog/core/environment/container.rb', line 93

def inode
  entry.inode
end

.platformString?

The container orchestration platform or runtime environment.

Examples: Docker, Kubernetes, AWS Fargate, LXC, etc.

Returns:

  • (String, nil)

    The platform name (e.g., “docker”, “kubepods”, “fargate”), or nil if not containerized



66
67
68
# File 'lib/datadog/core/environment/container.rb', line 66

def platform
  entry.platform
end

.running_on_host?Boolean

Checks if the current process is running on the host cgroup namespace. This indicates that the process is not running inside a container. When unsure, we return false (not running on host).

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/datadog/core/environment/container.rb', line 100

def running_on_host?
  return @running_on_host if defined?(@running_on_host)

  @running_on_host = begin
    if File.exist?('/proc/self/ns/cgroup')
      File.stat('/proc/self/ns/cgroup').ino == HOST_CGROUP_NAMESPACE_INODE
    else
      false
    end
  rescue => e
    Datadog.logger.debug(
      "Error while checking cgroup namespace. Cause: #{e.class.name} #{e.message} Location: #{Array(e.backtrace).first}"
    )
    false
  end
end

.task_uidString?

The unique identifier of the task or pod containing this container.

In Kubernetes, this is the Pod UID; in AWS ECS/Fargate, the task ID. Used to identify higher-level workloads beyond this container, enabling correlation across container restarts and multi-container applications.

Returns:

  • (String, nil)

    The task/pod UID, or nil if not available in the current environment



84
85
86
# File 'lib/datadog/core/environment/container.rb', line 84

def task_uid
  entry.task_uid
end

.to_headersObject

Returns HTTP headers representing container information. These can used in any Datadog request that requires origin detection. This is the recommended method to call to get container information.



38
39
40
41
42
43
44
# File 'lib/datadog/core/environment/container.rb', line 38

def to_headers
  headers = {}
  headers["Datadog-Container-ID"] = container_id if container_id
  headers["Datadog-Entity-ID"] = entity_id if entity_id
  headers["Datadog-External-Env"] = external_env if external_env
  headers
end