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

feat: container ID detector for cgroup v2 #1181

Merged
merged 26 commits into from
Oct 1, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ad89334
added another block to test for v2
abhee11 Sep 4, 2022
37d45cb
feat: changing export name to reflect generic docker
abhee11 Sep 4, 2022
d933955
feat: adding geenric naming and logic changes to support cgroup v2
abhee11 Sep 15, 2022
4ad530d
test: test file renamed
abhee11 Sep 15, 2022
1ebd445
feat: adding hostname check
abhee11 Sep 16, 2022
5cd6324
feat: adding hostname check- refactor
abhee11 Sep 16, 2022
132e06d
feat: fixed a test and fixed lint
abhee11 Sep 19, 2022
0a61ba2
feat: added another condition to check for length
abhee11 Sep 19, 2022
9ad9f52
test: added two more tests
abhee11 Sep 19, 2022
ffcebb8
Merge pull request #1 from abhee11/docker-v2-detector
abhee11 Sep 19, 2022
432759b
Merge branch 'open-telemetry:main' into main
abhee11 Sep 19, 2022
5e626e9
feat: renamed docker to container and addressed pr comments
abhee11 Sep 20, 2022
5406466
feat: renamed docker to container and addressed pr comments- 2
abhee11 Sep 20, 2022
2c775d0
feat: renamed docker to container and addressed pr comments-3
abhee11 Sep 20, 2022
541b84a
Merge branch 'main' into main
abhee11 Sep 20, 2022
7813abe
feat: addressed pr comments-2
abhee11 Sep 22, 2022
74c362f
Merge branch 'main' of https://github.com/abhee11/opentelemetry-js-co…
abhee11 Sep 22, 2022
c3e2359
Merge branch 'main' into main
abhee11 Sep 22, 2022
3e3a20b
feat: modified tests to take in multiple lines input
abhee11 Sep 30, 2022
6740303
Merge branch 'main' of https://github.com/abhee11/opentelemetry-js-co…
abhee11 Sep 30, 2022
fc0f31c
feat: merge resolution for manifest
abhee11 Sep 30, 2022
e4ca980
Merge branch 'main' into main
abhee11 Sep 30, 2022
9ed5aa5
feat: fixing release please manifest
abhee11 Sep 30, 2022
5dee09c
feat: merge resolution for manifest
abhee11 Sep 30, 2022
a72a56f
feat: changing please-config to rename docker to container
abhee11 Sep 30, 2022
2c4d709
feat: replaced >= check to == check
abhee11 Sep 30, 2022
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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"detectors/node/opentelemetry-resource-detector-alibaba-cloud":"0.27.2","detectors/node/opentelemetry-resource-detector-aws":"1.1.2","detectors/node/opentelemetry-resource-detector-gcp":"0.27.2","detectors/node/opentelemetry-resource-detector-github":"0.27.0","metapackages/auto-instrumentations-node":"0.32.1","metapackages/auto-instrumentations-web":"0.30.0","packages/opentelemetry-host-metrics":"0.30.0","packages/opentelemetry-id-generator-aws-xray":"1.1.0","packages/opentelemetry-test-utils":"0.32.0","plugins/node/instrumentation-amqplib":"0.31.0","plugins/node/instrumentation-fs":"0.5.0","plugins/node/instrumentation-tedious":"0.4.0","plugins/node/opentelemetry-instrumentation-aws-lambda":"0.33.0","plugins/node/opentelemetry-instrumentation-aws-sdk":"0.9.1","plugins/node/opentelemetry-instrumentation-bunyan":"0.30.0","plugins/node/opentelemetry-instrumentation-cassandra":"0.30.0","plugins/node/opentelemetry-instrumentation-connect":"0.30.0","plugins/node/opentelemetry-instrumentation-dns":"0.30.0","plugins/node/opentelemetry-instrumentation-express":"0.31.1","plugins/node/opentelemetry-instrumentation-generic-pool":"0.30.0","plugins/node/opentelemetry-instrumentation-graphql":"0.31.0","plugins/node/opentelemetry-instrumentation-hapi":"0.30.0","plugins/node/opentelemetry-instrumentation-ioredis":"0.32.1","plugins/node/opentelemetry-instrumentation-knex":"0.30.0","plugins/node/opentelemetry-instrumentation-koa":"0.32.0","plugins/node/instrumentation-lru-memoizer":"0.31.0","plugins/node/opentelemetry-instrumentation-memcached":"0.30.0","plugins/node/opentelemetry-instrumentation-mongodb":"0.32.0","plugins/node/opentelemetry-instrumentation-mysql":"0.31.1","plugins/node/opentelemetry-instrumentation-mysql2":"0.32.0","plugins/node/opentelemetry-instrumentation-nestjs-core":"0.31.0","plugins/node/opentelemetry-instrumentation-net":"0.30.1","plugins/node/opentelemetry-instrumentation-pg":"0.31.1","plugins/node/opentelemetry-instrumentation-pino":"0.32.0","plugins/node/opentelemetry-instrumentation-redis":"0.33.0","plugins/node/opentelemetry-instrumentation-redis-4":"0.33.0","plugins/node/opentelemetry-instrumentation-restify":"0.30.0","plugins/node/opentelemetry-instrumentation-router":"0.30.0","plugins/node/opentelemetry-instrumentation-winston":"0.30.0","plugins/web/opentelemetry-instrumentation-document-load":"0.30.0","plugins/web/opentelemetry-instrumentation-user-interaction":"0.31.0","plugins/web/opentelemetry-plugin-react-load":"0.28.0","propagators/opentelemetry-propagator-aws-xray":"1.1.0","propagators/opentelemetry-propagator-grpc-census-binary":"0.26.0","propagators/opentelemetry-propagator-instana":"0.2.0","propagators/opentelemetry-propagator-ot-trace":"0.26.1","plugins/node/opentelemetry-instrumentation-fastify":"0.29.0","packages/opentelemetry-propagation-utils":"0.28.0","plugins/web/opentelemetry-instrumentation-long-task":"0.31.0","detectors/node/opentelemetry-resource-detector-docker":"0.1.2","detectors/node/opentelemetry-resource-detector-instana":"0.3.0"}
{"detectors/node/opentelemetry-resource-detector-alibaba-cloud":"0.27.2","detectors/node/opentelemetry-resource-detector-aws":"1.1.2","detectors/node/opentelemetry-resource-detector-gcp":"0.27.2","detectors/node/opentelemetry-resource-detector-github":"0.27.0","metapackages/auto-instrumentations-node":"0.32.1","metapackages/auto-instrumentations-web":"0.30.0","packages/opentelemetry-host-metrics":"0.30.0","packages/opentelemetry-id-generator-aws-xray":"1.1.0","packages/opentelemetry-test-utils":"0.32.0","plugins/node/instrumentation-amqplib":"0.31.0","plugins/node/instrumentation-fs":"0.5.0","plugins/node/instrumentation-tedious":"0.4.0","plugins/node/opentelemetry-instrumentation-aws-lambda":"0.33.0","plugins/node/opentelemetry-instrumentation-aws-sdk":"0.9.1","plugins/node/opentelemetry-instrumentation-bunyan":"0.30.0","plugins/node/opentelemetry-instrumentation-cassandra":"0.30.0","plugins/node/opentelemetry-instrumentation-connect":"0.30.0","plugins/node/opentelemetry-instrumentation-dns":"0.30.0","plugins/node/opentelemetry-instrumentation-express":"0.31.1","plugins/node/opentelemetry-instrumentation-generic-pool":"0.30.0","plugins/node/opentelemetry-instrumentation-graphql":"0.31.0","plugins/node/opentelemetry-instrumentation-hapi":"0.30.0","plugins/node/opentelemetry-instrumentation-ioredis":"0.32.1","plugins/node/opentelemetry-instrumentation-knex":"0.30.0","plugins/node/opentelemetry-instrumentation-koa":"0.32.0","plugins/node/instrumentation-lru-memoizer":"0.31.0","plugins/node/opentelemetry-instrumentation-memcached":"0.30.0","plugins/node/opentelemetry-instrumentation-mongodb":"0.32.0","plugins/node/opentelemetry-instrumentation-mysql":"0.31.1","plugins/node/opentelemetry-instrumentation-mysql2":"0.32.0","plugins/node/opentelemetry-instrumentation-nestjs-core":"0.31.0","plugins/node/opentelemetry-instrumentation-net":"0.30.1","plugins/node/opentelemetry-instrumentation-pg":"0.31.1","plugins/node/opentelemetry-instrumentation-pino":"0.32.0","plugins/node/opentelemetry-instrumentation-redis":"0.33.0","plugins/node/opentelemetry-instrumentation-redis-4":"0.33.0","plugins/node/opentelemetry-instrumentation-restify":"0.30.0","plugins/node/opentelemetry-instrumentation-router":"0.30.0","plugins/node/opentelemetry-instrumentation-winston":"0.30.0","plugins/web/opentelemetry-instrumentation-document-load":"0.30.0","plugins/web/opentelemetry-instrumentation-user-interaction":"0.31.0","plugins/web/opentelemetry-plugin-react-load":"0.28.0","propagators/opentelemetry-propagator-aws-xray":"1.1.0","propagators/opentelemetry-propagator-grpc-census-binary":"0.26.0","propagators/opentelemetry-propagator-instana":"0.2.0","propagators/opentelemetry-propagator-ot-trace":"0.26.1","plugins/node/opentelemetry-instrumentation-fastify":"0.29.0","packages/opentelemetry-propagation-utils":"0.28.0","plugins/web/opentelemetry-instrumentation-long-task":"0.31.0","detectors/node/opentelemetry-resource-detector-container":"0.1.2","detectors/node/opentelemetry-resource-detector-instana":"0.3.0"}
Original file line number Diff line number Diff line change
@@ -1,37 +1,34 @@
# OpenTelemetry Resource Detector for Docker
# OpenTelemetry Resource Detector for Container

[![NPM Published Version][npm-img]][npm-url]
[![dependencies][dependencies-image]][dependencies-url]
[![devDependencies][devDependencies-image]][devDependencies-url]
[![Apache License][license-image]][license-image]

[component owners](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/.github/component_owners.yml): @abhee11

Resource detector for docker.

Resource detector for container id.
Compatible with OpenTelemetry JS API and SDK `1.0+`.

## Installation

```bash
npm install --save @opentelemetry/resource-detector-docker
npm install --save @opentelemetry/resource-detector-container
```

## Usage

```typescript
import { detectResources } from '@opentelemetry/resources';
import { dockerCGroupV1Detector } from '@opentelemetry/resource-detector-docker'
import { containerDetector } from '@opentelemetry/resource-detector-container'
const resource = await detectResources({
detectors: [dockerCGroupV1Detector],
detectors: [containerDetector],
})

const tracerProvider = new NodeTracerProvider({ resource });
```

## Available detectors

- `dockerCGroupV1Detector`: Populates `container.id` for processes running on docker cgroup v1
- `containerDetector`: Populates `container.id` for processes running on contianers supporting : docker( cgroup v1 or v2 ) or with containerd

## Useful links

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@opentelemetry/resource-detector-docker",
"name": "@opentelemetry/resource-detector-container",
"version": "0.1.2",
"description": "Opentelemetry resource detector to get docker resource attributes",
"description": "Opentelemetry resource detector to get container resource attributes",
"main": "build/src/index.js",
"types": "build/src/index.d.ts",
"repository": "open-telemetry/opentelemetry-js-contrib",
Expand All @@ -11,7 +11,7 @@
"compile": "npm run version:update && tsc -p .",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"precompile": "tsc --version && lerna run version --scope @opentelemetry/resource-detector-docker --include-dependencies",
"precompile": "tsc --version && lerna run version --scope @opentelemetry/resource-detector-container --include-dependencies",
"prewatch": "npm run precompile",
"prepare": "npm run compile",
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.test.ts'",
Expand Down Expand Up @@ -57,5 +57,5 @@
"@opentelemetry/resources": "^1.0.0",
"@opentelemetry/semantic-conventions": "^1.0.0"
},
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/detectors/node/opentelemetry-resource-detector-docker#readme"
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/detectors/node/opentelemetry-resource-detector-container#readme"
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ import * as fs from 'fs';
import * as util from 'util';
import { diag } from '@opentelemetry/api';

export class DockerCGroupV1Detector implements Detector {
export class ContainerDetector implements Detector {
readonly CONTAINER_ID_LENGTH = 64;
readonly DEFAULT_CGROUP_PATH = '/proc/self/cgroup';
readonly DEFAULT_CGROUP_V1_PATH = '/proc/self/cgroup';
readonly DEFAULT_CGROUP_V2_PATH = '/proc/self/mountinfo';
readonly UTF8_UNICODE = 'utf8';
readonly HOSTNAME = 'hostname';

private static readFileAsync = util.promisify(fs.readFile);

Expand All @@ -42,30 +44,59 @@ export class DockerCGroupV1Detector implements Detector {
});
} catch (e) {
diag.info(
'Docker CGROUP V1 Detector did not identify running inside a supported docker container, no docker attributes will be added to resource: ',
'Container Detector did not identify running inside a supported container, no container attributes will be added to resource: ',
e
);
return Resource.empty();
}
}

private async _getContainerIdV1() {
const rawData = await ContainerDetector.readFileAsync(
this.DEFAULT_CGROUP_V1_PATH,
this.UTF8_UNICODE
);
const splitData = rawData.trim().split('\n');
for (const str of splitData) {
if (str.length >= this.CONTAINER_ID_LENGTH) {
return str.substring(str.length - this.CONTAINER_ID_LENGTH);
}
}
return undefined;
}

private async _getContainerIdV2() {
const rawData = await ContainerDetector.readFileAsync(
this.DEFAULT_CGROUP_V2_PATH,
this.UTF8_UNICODE
);
const str = rawData
.trim()
.split('\n')
.find(s => s.includes(this.HOSTNAME));
const containerIdStr = str
?.split('/')
.find(s => s.length >= this.CONTAINER_ID_LENGTH);
rauno56 marked this conversation as resolved.
Show resolved Hide resolved
return containerIdStr?.substring(
containerIdStr.length - this.CONTAINER_ID_LENGTH
);
}

/*
cgroupv1 path would still exist in case of container running on v2
but the cgroupv1 path would no longer have the container id and would
fallback on the cgroupv2 implementation.
*/
private async _getContainerId(): Promise<string | undefined> {
try {
const rawData = await DockerCGroupV1Detector.readFileAsync(
this.DEFAULT_CGROUP_PATH,
this.UTF8_UNICODE
return (
(await this._getContainerIdV1()) ?? (await this._getContainerIdV2())
);
const splitData = rawData.trim().split('\n');
for (const str of splitData) {
if (str.length >= this.CONTAINER_ID_LENGTH) {
return str.substring(str.length - this.CONTAINER_ID_LENGTH);
}
}
} catch (e) {
if (e instanceof Error) {
const errorMessage = e.message;
diag.info(
'Docker CGROUP V1 Detector failed to read the Container ID: ',
'Container Detector failed to read the Container ID: ',
errorMessage
);
}
Expand All @@ -74,4 +105,4 @@ export class DockerCGroupV1Detector implements Detector {
}
}

export const dockerCGroupV1Detector = new DockerCGroupV1Detector();
export const containerDetector = new ContainerDetector();
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './DockerCGroupV1Detector';
export * from './ContainerDetector';
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as sinon from 'sinon';
import * as assert from 'assert';
import { Resource } from '@opentelemetry/resources';
import { containerDetector } from '../src';
import {
assertContainerResource,
assertEmptyResource,
} from '@opentelemetry/contrib-test-utils';

import { ContainerDetector } from '../src';

describe('ContainerDetector', () => {
let readStub;
const correctCgroupV1Data =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm';
const correctCgroupV2Data =
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm/hostname';
rauno56 marked this conversation as resolved.
Show resolved Hide resolved

const wrongCgroupV2Data =
blumamir marked this conversation as resolved.
Show resolved Hide resolved
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm/wrongkeyword';

afterEach(() => {
sinon.restore();
});

describe('Supported container - Container ID ', () => {
it('should return a resource attributes without container id - docker cgroup v1 detector', async () => {
readStub = sinon
.stub(ContainerDetector, 'readFileAsync' as any)
.resolves(undefined);

const resource: Resource = await containerDetector.detect();

assert.deepStrictEqual(resource.attributes, {});
assert.ok(resource);
});

it('should return a resource with container ID with a valid container ID present', async () => {
readStub = sinon
.stub(ContainerDetector, 'readFileAsync' as any)
.resolves(correctCgroupV1Data);

const resource: Resource = await containerDetector.detect();

sinon.assert.calledOnce(readStub);

assert.ok(resource);
assertContainerResource(resource, {
id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm',
});
});

it('should return a resource with container ID with a valid container ID present for v2', async () => {
readStub = sinon.stub(ContainerDetector, 'readFileAsync' as any);

readStub.onFirstCall().resolves('');
readStub.onSecondCall().resolves(correctCgroupV2Data);

const resource: Resource = await containerDetector.detect();
sinon.assert.calledTwice(readStub);

assert.ok(resource);
assertContainerResource(resource, {
id: 'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm',
});
});

it('should return a empty resource with failed hostname check for v2', async () => {
readStub = sinon.stub(ContainerDetector, 'readFileAsync' as any);

readStub.onFirstCall().resolves('');
readStub.onSecondCall().resolves(wrongCgroupV2Data);

const resource: Resource = await containerDetector.detect();
sinon.assert.calledTwice(readStub);

assert.ok(resource);
});

it('should return a resource without attribute container.id when cgroup file does not contain valid Container ID', async () => {
readStub = sinon
.stub(ContainerDetector, 'readFileAsync' as any)
.resolves('');

const resource: Resource = await containerDetector.detect();
assert.deepStrictEqual(resource.attributes, {});

sinon.assert.calledTwice(readStub);
assert.ok(resource);
});

it('should return an empty resource when containerId is not valid', async () => {
const errorMsg = {
fileNotFoundError: new Error('cannot find file in path'),
};

readStub = sinon
.stub(ContainerDetector, 'readFileAsync' as any)
.rejects(errorMsg.fileNotFoundError);

const resource: Resource = await containerDetector.detect();

sinon.assert.calledOnce(readStub);
assertEmptyResource(resource);
});

//cgroup v2 and containerd test

it('should return an empty resource when containerId is not valid', async () => {
const errorMsg = {
fileNotFoundError: new Error('cannot find file in path'),
};

readStub = sinon
.stub(ContainerDetector, 'readFileAsync' as any)
.rejects(errorMsg.fileNotFoundError);

const resource: Resource = await containerDetector.detect();
sinon.assert.calledOnce(readStub);
assertEmptyResource(resource);
});
});
});
Loading