Skip to content

Commit

Permalink
k8s container id detection (#191)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brettmc authored Sep 15, 2023
1 parent e317b41 commit a973a41
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/ResourceDetectors/Container/.php-cs-fixer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
'blank_line_before_statement' => true,
'cast_spaces' => true,
'declare_strict_types' => true,
'function_typehint_space' => true,
'type_declaration_spaces' => true,
'include' => true,
'lowercase_cast' => true,
'new_with_braces' => true,
Expand All @@ -34,7 +34,7 @@
'phpdoc_scalar' => true,
'phpdoc_types' => true,
'short_scalar_cast' => true,
'single_blank_line_before_namespace' => true,
'blank_lines_before_namespace' => true,
'single_quote' => true,
'trailing_comma_in_multiline' => true,
])
Expand Down
1 change: 1 addition & 0 deletions src/ResourceDetectors/Container/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# OpenTelemetry Container Detector

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

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion src/ResourceDetectors/Container/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"prefer-stable": true,
"require": {
"php": "^7.0|^8.0",
"open-telemetry/sdk": ">= 1.0.0beta10"
"open-telemetry/sdk": ">= 1.0.0RC1 <= 2"
},
"autoload": {
"psr-4": {
Expand Down
30 changes: 23 additions & 7 deletions src/ResourceDetectors/Container/src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@

/**
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.18.0/specification/resource/semantic_conventions/container.md
* @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
*/
final class Container implements ResourceDetectorInterface
{
private string $dir;
private const CONTAINER_ID_LENGTH = 64;
private const CONTAINER_ID_REGEX = '/^[0-9a-f]{64}$/';
private const V1_CONTAINER_ID_REGEX = '/\.?[0-9a-f]{64}\-?/';
private const CGROUP_V1 = 'cgroup';
private const CGROUP_V2 = 'mountinfo';
private const HOSTNAME = 'hostname';
Expand All @@ -38,9 +40,14 @@ public function getResource(): ResourceInfo

private function getContainerId(): ?string
{
return $this->getContainerIdV2() ?? $this->getContainerIdV1();
return $this->getContainerIdV1() ?? $this->getContainerIdV2();
}

/**
* Each line of cgroup file looks like "14:name=systemd:/docker/.../... A hex string is expected
* inside the last section separated by '/' Each segment of the '/' can contain metadata separated
* by either '.' (at beginning) or '-' (at end)
*/
private function getContainerIdV1(): ?string
{
if (!file_exists(sprintf('%s/%s', $this->dir, self::CGROUP_V1))) {
Expand All @@ -50,11 +57,20 @@ private function getContainerIdV1(): ?string
if (!$data) {
return null;
}
$lines = explode('\n', $data);
$lines = explode(PHP_EOL, $data);
foreach ($lines as $line) {
if (strlen($line) >= self::CONTAINER_ID_LENGTH) {
//if string is longer than CONTAINER_ID_LENGTH, return the last CONTAINER_ID_LENGTH chars
return substr($line, strlen($line) - self::CONTAINER_ID_LENGTH);
if (strpos($line, '/') === false) {
continue;
}
$parts = explode('/', $line);
$section = end($parts);
$colon = strrpos($section, ':');
if ($colon !== false) {
return substr($section, $colon);
}
$matches = [];
if (preg_match(self::V1_CONTAINER_ID_REGEX, $section, $matches) === 1) {
return $matches[0];
}
}

Expand All @@ -75,7 +91,7 @@ private function getContainerIdV2(): ?string
if (strpos($line, self::HOSTNAME) !== false) {
$parts = explode('/', $line);
foreach ($parts as $part) {
if (strlen($part) === self::CONTAINER_ID_LENGTH) {
if (preg_match(self::CONTAINER_ID_REGEX, $part) === 1) {
return $part;
}
}
Expand Down
85 changes: 72 additions & 13 deletions src/ResourceDetectors/Container/tests/Unit/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,65 @@ public function setUp(): void
$this->detector = new Container($root->url());
}

public function test_valid_v1(): void
/**
* cgroup (v1) should take precedence over mountinfo (v2)
* @dataProvider cgroupMountinfoProvider
*/
public function test_with_cgroup_and_mountinfo(string $cgroup, string $mountinfo, string $expected): void
{
$valid = 'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d';
$this->cgroup->setContent($valid);
$cgroup && $this->cgroup->setContent($cgroup);
$mountinfo && $this->mountinfo->setContent($mountinfo);
$resource = $this->detector->getResource();

$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
}

public static function cgroupMountinfoProvider(): array
{
return [
'k8s' => [
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.k8s.txt'),
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.k8s.txt'),
'78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075',
],
'docker with invalid cgroup' => [
'no-container-ids-here',
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.docker.txt'),
'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d',
],
'podman' => [
'no-container-ids-here',
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.podman.txt'),
'2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6',
],
];
}

/**
* @dataProvider cgroupProvider
*/
public function test_valid_v1(string $data, string $expected): void
{
$this->cgroup->setContent($data);
$resource = $this->detector->getResource();

$this->assertSame(ResourceAttributes::SCHEMA_URL, $resource->getSchemaUrl());
$this->assertIsString($resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
$this->assertSame($valid, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
$this->assertSame($expected, $resource->getAttributes()->get(ResourceAttributes::CONTAINER_ID));
}

public static function cgroupProvider(): array
{
return [
'docker' => [
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.docker.txt'),
'7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605',
],
'k8s' => [
file_get_contents(__DIR__ . '/fixtures/v1.cgroup.k8s.txt'),
'78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075',
],
];
}

public function test_invalid_v1(): void
Expand All @@ -44,23 +94,32 @@ public function test_invalid_v1(): void
$this->assertEmpty($resource->getAttributes());
}

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

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

public static function mountinfoProvider(): array
{
return [
'docker' => [
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.docker.txt'),
'a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d',
],
'podman' => [
file_get_contents(__DIR__ . '/fixtures/v2.mountinfo.podman.txt'),
'2a33efc76e519c137fe6093179653788bed6162d4a15e5131c8e835c968afbe6',
],
];
}

public function test_invalid_v2(): void
{
$data = <<< EOS
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
14:name=systemd:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
13:pids:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
12:hugetlb:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
11:net_prio:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
10:perf_event:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
9:net_cls:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
8:freezer:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
7:devices:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
6:memory:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
5:blkio:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
4:cpuacct:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
3:cpu:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
2:cpuset:/docker/7be92808767a667f35c8505cbf40d14e931ef6db5b0210329cf193b15ba9d605
1:name=openrc:/docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
13:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
12:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
11:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
10:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
9:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
8:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
7:rdma:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
6:misc:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
5:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
4:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
3:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
2:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod542985b6_0726_4d82_bf85_36e327b167b3.slice/crio-78ea929aa43e7b71f7c36583d82038d92a76800bf5da9b8850e8bd7b514bc075.scope
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
1366 1365 0:30 / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw
1408 1362 0:107 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
1579 1362 0:112 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64
1581 1359 259:2 /var/lib/docker/containers/a8493b8a4f6f23b65c5db50be86619ca4da078da040aa3d5ccff26fe50de205d/hostname /etc/hostname rw,relatime - ext4 /dev/nvme0n1p2 rw,errors=remount-ro
1583 1359 259:3 /brett/docker/otel/opentelemetry-php /usr/src/myapp rw,relatime - ext4 /dev/nvme0n1p3 rw
Loading

0 comments on commit a973a41

Please sign in to comment.