Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support cgroups v1 and v2 for Docker containers #2229

Merged
merged 4 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# New Relic Ruby Agent Release Notes

## dev

Version <dev> brings support for gleaning a Docker container id from cgroups v2 based containers.

- **Feature: Enhance Docker container id reporting**

Previously, the agent was only capable of determining a host Docker container's ID if the container was based on cgroups v1. Now, containers based on cgroups v2 will also have their container IDs reported to New Relic. [PR#2229](https://github.com/newrelic/newrelic-ruby-agent/issues/2229).

## v9.5.0

Version 9.5.0 introduces Stripe instrumentation, allows the agent to record additional response information on a transaction when middleware instrumentation is disabled, introduces new `:'sidekiq.args.include'` and `:'sidekiq.args.exclude:` configuration options to permit capturing only certain Sidekiq job arguments, updates Elasticsearch datastore instance metrics, and fixes a bug in `NewRelic::Rack::AgentHooks.needed?`.
Expand Down
24 changes: 24 additions & 0 deletions lib/new_relic/agent/system_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,39 @@ def self.os_version
proc_try_read('/proc/version')
end

# When operating within a Docker container, attempt to obtain the
# container id.
#
# First look for `/proc/self/mountinfo` to exist on disk to signify
# cgroups v2. If that file exists, read it and expect it to contain one
# or more "/docker/containers/<container_id>/" lines from which the
# container id can be gleaned.
#
# Next look for `/proc/self/cgroup` to exist on disk to signify cgroup v1.
# If that file exists, read it and parse the "cpu" group info in the hope
# of finding a 64 character container id value.
#
# For non-cgroups based containers, use a `nil` value for the container
# id without generating any warnings or errors.
def self.docker_container_id
return unless ruby_os_identifier.include?('linux')

cgroupsv2_based_id = docker_container_id_for_cgroupsv2
return cgroupsv2_based_id if cgroupsv2_based_id

cgroup_info = proc_try_read('/proc/self/cgroup')
return unless cgroup_info

parse_docker_container_id(cgroup_info)
end

def self.docker_container_id_for_cgroupsv2
mountinfo = proc_try_read('/proc/self/mountinfo')
return unless mountinfo

Regexp.last_match(1) if mountinfo =~ %r{/docker/containers/([^/]+)/}
end

def self.parse_docker_container_id(cgroup_info)
cpu_cgroup = parse_cgroup_ids(cgroup_info)['cpu']
return unless cpu_cgroup
Expand Down
17 changes: 17 additions & 0 deletions test/new_relic/agent/system_info_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def setup
end
end

# BEGIN cgroups v1
container_id_test_dir = File.join(cross_agent_tests_dir, 'docker_container_id')
container_id_test_cases = load_cross_agent_test(File.join('docker_container_id', 'cases'))

Expand All @@ -86,6 +87,21 @@ def setup
end
end
end
# END cgroups v1

# BEGIN cgroups v2
def test_docker_container_id_is_gleaned_from_mountinfo_for_cgroups_v2
skip_unless_minitest5_or_above

container_id = "And Autumn leaves lie thick and still o'er land that is lost now"
mountinfo = "line1\nline2\n/docker/containers/#{container_id}/other/content\nline4\nline5"
NewRelic::Agent::SystemInfo.stub :ruby_os_identifier, 'linux' do
NewRelic::Agent::SystemInfo.stub :proc_try_read, mountinfo, %w[/proc/self/mountinfo] do
assert_equal container_id, NewRelic::Agent::SystemInfo.docker_container_id
end
end
end
# END cgroups v2

each_cross_agent_test :dir => 'proc_meminfo', :pattern => '*.txt' do |file|
if File.basename(file) =~ /^meminfo_(\d+)MB.txt$/
Expand Down Expand Up @@ -297,6 +313,7 @@ def test_system_info_bsd_predicate
def test_supportability_metric_recorded_when_docker_id_unavailable
NewRelic::Agent::SystemInfo.stubs(:ruby_os_identifier).returns('linux')
cgroup_info = File.read(File.join(cross_agent_tests_dir, 'docker_container_id', 'invalid-length.txt'))
NewRelic::Agent::SystemInfo.expects(:proc_try_read).with('/proc/self/mountinfo').returns(cgroup_info)
NewRelic::Agent::SystemInfo.expects(:proc_try_read).with('/proc/self/cgroup').returns(cgroup_info)

in_transaction('txn') do
Expand Down
Loading