Skip to content

Commit

Permalink
feat: container ID detector for cgroup v2 (open-telemetry#1181)
Browse files Browse the repository at this point in the history
* added another block to test for v2

* feat: changing export name to reflect generic docker

* feat: adding geenric naming and logic changes to support cgroup v2

* test: test file renamed

* feat: adding hostname check

* feat: adding hostname check- refactor

* feat: fixed a test and fixed lint

* feat: added another condition to check for length

* test: added two more tests

* feat: renamed docker to container and addressed pr comments

* feat: renamed docker to container and addressed pr comments- 2

* feat: renamed docker to container and addressed pr comments-3

* feat: addressed pr comments-2

* feat: modified tests to take in multiple lines input

* feat: merge resolution for manifest

* feat: fixing release please manifest

* feat: changing please-config to rename docker to container

* feat: replaced >= check to == check
  • Loading branch information
abhee11 authored Oct 1, 2022
1 parent 60a92c7 commit 502caae
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 121 deletions.
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
{"detectors/node/opentelemetry-resource-detector-alibaba-cloud":"0.27.2","detectors/node/opentelemetry-resource-detector-aws":"1.1.2","detectors/node/opentelemetry-resource-detector-docker":"0.1.2","detectors/node/opentelemetry-resource-detector-gcp":"0.27.2","detectors/node/opentelemetry-resource-detector-github":"0.27.0","detectors/node/opentelemetry-resource-detector-instana":"0.3.0","metapackages/auto-instrumentations-node":"0.33.1","metapackages/auto-instrumentations-web":"0.30.0","packages/opentelemetry-host-metrics":"0.30.1","packages/opentelemetry-id-generator-aws-xray":"1.1.0","packages/opentelemetry-propagation-utils":"0.29.0","packages/opentelemetry-test-utils":"0.32.0","plugins/node/instrumentation-amqplib":"0.31.0","plugins/node/instrumentation-dataloader":"0.2.0","plugins/node/instrumentation-fs":"0.5.0","plugins/node/instrumentation-lru-memoizer":"0.31.0","plugins/node/instrumentation-mongoose":"0.31.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.2","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.2","plugins/node/opentelemetry-instrumentation-fastify":"0.30.0","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/opentelemetry-instrumentation-memcached":"0.30.0","plugins/node/opentelemetry-instrumentation-mongodb":"0.32.1","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-long-task":"0.31.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"}
{"detectors/node/opentelemetry-resource-detector-alibaba-cloud":"0.27.2","detectors/node/opentelemetry-resource-detector-aws":"1.1.2","detectors/node/opentelemetry-resource-detector-container":"0.1.2","detectors/node/opentelemetry-resource-detector-gcp":"0.27.2","detectors/node/opentelemetry-resource-detector-github":"0.27.0","detectors/node/opentelemetry-resource-detector-instana":"0.3.0","metapackages/auto-instrumentations-node":"0.33.1","metapackages/auto-instrumentations-web":"0.30.0","packages/opentelemetry-host-metrics":"0.30.1","packages/opentelemetry-id-generator-aws-xray":"1.1.0","packages/opentelemetry-propagation-utils":"0.29.0","packages/opentelemetry-test-utils":"0.32.0","plugins/node/instrumentation-amqplib":"0.31.0","plugins/node/instrumentation-dataloader":"0.2.0","plugins/node/instrumentation-fs":"0.5.0","plugins/node/instrumentation-lru-memoizer":"0.31.0","plugins/node/instrumentation-mongoose":"0.31.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.2","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.2","plugins/node/opentelemetry-instrumentation-fastify":"0.30.0","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/opentelemetry-instrumentation-memcached":"0.30.0","plugins/node/opentelemetry-instrumentation-mongodb":"0.32.1","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-long-task":"0.31.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"}

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,57 @@ 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);
return containerIdStr || '';
}

/*
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 +103,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,140 @@
/*
* 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 =
'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm';
const correctCgroupV2Data = `tmhdefghijklmnopqrstuvwxyzafgrefghiugkmnopqrstuvwxyzabcdefghijkl/hostname
fhkjdshgfhsdfjhdsfkjhfkdshkjhfd/host
sahfhfjkhjhfhjdhfjkdhfkjdhfjkhhdsjfhdfhjdhfkj/somethingelse`;

const wrongCgroupV2Data =
'bcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklm/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: 'tmhdefghijklmnopqrstuvwxyzafgrefghiugkmnopqrstuvwxyzabcdefghijkl',
});
});

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

0 comments on commit 502caae

Please sign in to comment.