Skip to content

Commit ce27a4d

Browse files
authored
k8s container id detection (#191)
update container detection to follow current best-practise, per bug report. we should check v1 then v2, and a few other updates I ripped off from java's implementation. adding tests for k8s and podman, and break inline content out into fixtures.
1 parent a6d26ad commit ce27a4d

10 files changed

Lines changed: 217 additions & 23 deletions

.php-cs-fixer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
'blank_line_before_statement' => true,
2121
'cast_spaces' => true,
2222
'declare_strict_types' => true,
23-
'function_typehint_space' => true,
23+
'type_declaration_spaces' => true,
2424
'include' => true,
2525
'lowercase_cast' => true,
2626
'new_with_braces' => true,
@@ -34,7 +34,7 @@
3434
'phpdoc_scalar' => true,
3535
'phpdoc_types' => true,
3636
'short_scalar_cast' => true,
37-
'single_blank_line_before_namespace' => true,
37+
'blank_lines_before_namespace' => true,
3838
'single_quote' => true,
3939
'trailing_comma_in_multiline' => true,
4040
])

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# OpenTelemetry Container Detector
22

33
This package provides an OpenTelemetry `ResourceDetector` which will detect docker container id at runtime, using either V1 (cgroup) or V2 (mountinfo).
4+
It should work with docker, kubernetes, and podman containers.
45

56
## Requirements
67

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"prefer-stable": true,
1111
"require": {
1212
"php": "^7.0|^8.0",
13-
"open-telemetry/sdk": ">= 1.0.0beta10"
13+
"open-telemetry/sdk": ">= 1.0.0RC1 <= 2"
1414
},
1515
"autoload": {
1616
"psr-4": {

src/Container.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
/**
1313
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.18.0/specification/resource/semantic_conventions/container.md
14+
* @see https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v1.29.0/instrumentation/resources/library/src/main/java/io/opentelemetry/instrumentation/resources/ContainerResource.java
1415
*/
1516
final class Container implements ResourceDetectorInterface
1617
{
1718
private string $dir;
18-
private const CONTAINER_ID_LENGTH = 64;
19+
private const CONTAINER_ID_REGEX = '/^[0-9a-f]{64}$/';
20+
private const V1_CONTAINER_ID_REGEX = '/\.?[0-9a-f]{64}\-?/';
1921
private const CGROUP_V1 = 'cgroup';
2022
private const CGROUP_V2 = 'mountinfo';
2123
private const HOSTNAME = 'hostname';
@@ -38,9 +40,14 @@ public function getResource(): ResourceInfo
3840

3941
private function getContainerId(): ?string
4042
{
41-
return $this->getContainerIdV2() ?? $this->getContainerIdV1();
43+
return $this->getContainerIdV1() ?? $this->getContainerIdV2();
4244
}
4345

46+
/**
47+
* Each line of cgroup file looks like "14:name=systemd:/docker/.../... A hex string is expected
48+
* inside the last section separated by '/' Each segment of the '/' can contain metadata separated
49+
* by either '.' (at beginning) or '-' (at end)
50+
*/
4451
private function getContainerIdV1(): ?string
4552
{
4653
if (!file_exists(sprintf('%s/%s', $this->dir, self::CGROUP_V1))) {
@@ -50,11 +57,20 @@ private function getContainerIdV1(): ?string
5057
if (!$data) {
5158
return null;
5259
}
53-
$lines = explode('\n', $data);
60+
$lines = explode(PHP_EOL, $data);
5461
foreach ($lines as $line) {
55-
if (strlen($line) >= self::CONTAINER_ID_LENGTH) {
56-
//if string is longer than CONTAINER_ID_LENGTH, return the last CONTAINER_ID_LENGTH chars
57-
return substr($line, strlen($line) - self::CONTAINER_ID_LENGTH);
62+
if (strpos($line, '/') === false) {
63+
continue;
64+
}
65+
$parts = explode('/', $line);
66+
$section = end($parts);
67+
$colon = strrpos($section, ':');
68+
if ($colon !== false) {
69+
return substr($section, $colon);
70+
}
71+
$matches = [];
72+
if (preg_match(self::V1_CONTAINER_ID_REGEX, $section, $matches) === 1) {
73+
return $matches[0];
5874
}
5975
}
6076

@@ -75,7 +91,7 @@ private function getContainerIdV2(): ?string
7591
if (strpos($line, self::HOSTNAME) !== false) {
7692
$parts = explode('/', $line);
7793
foreach ($parts as $part) {
78-
if (strlen($part) === self::CONTAINER_ID_LENGTH) {
94+
if (preg_match(self::CONTAINER_ID_REGEX, $part) === 1) {
7995
return $part;
8096
}
8197
}

tests/Unit/ContainerTest.php

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,65 @@ public function setUp(): void
2525
$this->detector = new Container($root->url());
2626
}
2727

28-
public function test_valid_v1(): void
28+
/**
29+
* cgroup (v1) should take precedence over mountinfo (v2)
30+
* @dataProvider cgroupMountinfoProvider
31+
*/
32+
public function test_with_cgroup_and_mountinfo(string $cgroup, string $mountinfo, string $expected): void
2933
{
30-
$valid = 'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d';
31-
$this->cgroup->setContent($valid);
34+
$cgroup && $this->cgroup->setContent($cgroup);
35+
$mountinfo && $this->mountinfo->setContent($mountinfo);
36+
$resource = $this->detector->getResource();
37+
38+
$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
39+
}
40+
41+
public static function cgroupMountinfoProvider(): array
42+
{
43+
return [
44+
'k8s' => [
45+
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.k8s.txt'),
46+
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.k8s.txt'),
47+
'78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075',
48+
],
49+
'docker with invalid cgroup' => [
50+
'no-container-ids-here',
51+
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.docker.txt'),
52+
'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d',
53+
],
54+
'podman' => [
55+
'no-container-ids-here',
56+
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.podman.txt'),
57+
'2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6',
58+
],
59+
];
60+
}
61+
62+
/**
63+
* @dataProvider cgroupProvider
64+
*/
65+
public function test_valid_v1(string $data, string $expected): void
66+
{
67+
$this->cgroup->setContent($data);
3268
$resource = $this->detector->getResource();
3369

3470
$this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
3571
$this->assertIsString($resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
36-
$this->assertSame($valid, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
72+
$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
73+
}
74+
75+
public static function cgroupProvider(): array
76+
{
77+
return [
78+
'docker' => [
79+
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.docker.txt'),
80+
'7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605',
81+
],
82+
'k8s' => [
83+
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.k8s.txt'),
84+
'78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075',
85+
],
86+
];
3787
}
3888

3989
public function test_invalid_v1(): void
@@ -44,23 +94,32 @@ public function test_invalid_v1(): void
4494
$this->assertEmpty($resource->getAttributes());
4595
}
4696

47-
public function test_valid_v2(): void
97+
/**
98+
* @dataProvider mountinfoProvider
99+
*/
100+
public function test_valid_v2(string $data, string $expected): void
48101
{
49-
$expected = 'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d';
50-
$data = <<< EOS
51-
1366 1365 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
52-
1408 1362 0:107 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
53-
1579 1362 0:112 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64
54-
1581 1359 259:2 /var/lib/docker/containers/a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d/hostname /etc/hostname rw,relatime - ext4 /dev/nvme0n1p2 rw,errors=remount-ro
55-
1583 1359 259:3 /brett/docker/otel/opentelemetry-php /usr/src/myapp rw,relatime - ext4 /dev/nvme0n1p3 rw
56-
EOS;
57102
$this->mountinfo->withContent($data);
58103
$resource = $this->detector->getResource();
59104

60105
$this->assertCount(1, $resource->getAttributes());
61106
$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
62107
}
63108

109+
public static function mountinfoProvider(): array
110+
{
111+
return [
112+
'docker' => [
113+
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.docker.txt'),
114+
'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d',
115+
],
116+
'podman' => [
117+
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.podman.txt'),
118+
'2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6',
119+
],
120+
];
121+
}
122+
64123
public function test_invalid_v2(): void
65124
{
66125
$data = <<< EOS
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
14:name=systemd:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
2+
13:pids:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
3+
12:hugetlb:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
4+
11:net_prio:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
5+
10:perf_event:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
6+
9:net_cls:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
7+
8:freezer:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
8+
7:devices:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
9+
6:memory:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
10+
5:blkio:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
11+
4:cpuacct:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
12+
3:cpu:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
13+
2:cpuset:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
14+
1:name=openrc:/docker
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
13:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
2+
12:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
3+
11:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
4+
10:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
5+
9:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
6+
8:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
7+
7:rdma:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
8+
6:misc:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
9+
5:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
10+
4:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
11+
3:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
12+
2:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
13+
1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
1366 1365 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
2+
1408 1362 0:107 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
3+
1579 1362 0:112 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64
4+
1581 1359 259:2 /var/lib/docker/containers/a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d/hostname /etc/hostname rw,relatime - ext4 /dev/nvme0n1p2 rw,errors=remount-ro
5+
1583 1359 259:3 /brett/docker/otel/opentelemetry-php /usr/src/myapp rw,relatime - ext4 /dev/nvme0n1p3 rw

0 commit comments

Comments
 (0)