diff --git a/CHANGELOG.md b/CHANGELOG.md index 73f65e4c06..d1f4fee461 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # New Relic Ruby Agent Release Notes +## dev + +Version 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?`. diff --git a/lib/new_relic/agent/system_info.rb b/lib/new_relic/agent/system_info.rb index 5d72674445..062157e6d7 100644 --- a/lib/new_relic/agent/system_info.rb +++ b/lib/new_relic/agent/system_info.rb @@ -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//" 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 diff --git a/test/new_relic/agent/system_info_test.rb b/test/new_relic/agent/system_info_test.rb index a44e8b5f30..33fc104493 100644 --- a/test/new_relic/agent/system_info_test.rb +++ b/test/new_relic/agent/system_info_test.rb @@ -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')) @@ -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$/ @@ -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