Skip to content

Latest commit

 

History

History
480 lines (333 loc) · 28.3 KB

README.md

File metadata and controls

480 lines (333 loc) · 28.3 KB

Lumigo CDK Integrations

View on Construct Hub

This repository provides means of adding Lumigo tracing as "infrastructure-as-code" to Lambda functions and ECS workloads deployed via the AWS Cloud Development Kit (CDK) v2.

If instead of the AWS CDK v2, you are using the Serverless Framework, refer to the serverless-lumigo plugin documentation.

With the Lumigo CDK Constructs, getting your Lambda and ECS workloads monitored by Lumigo across your entire CDK applciation is as easy as:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

// This will trace all Lambda functions and ECS workloads managed with supported constructs
new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app);

app.synth();

Installation

TypeScript / JavaScript

With yarn:

yarn install '@lumigo/cdk-constructs-v2'

With npm:

npm install '@lumigo/cdk-constructs-v2'

Supported Constructs

Supported AWS Lambda Constructs

The Lumigo CDK integration applies automated distributed tracing to the following constructs that manage AWS Lambda functions:

AWS CDK Package Constructs Notes
aws-cdk-lib/aws-lambda Function Lumigo tracing will be added to the supported nodejs and python3.x runtimes; provided runtimes will not be instrumented.
aws-cdk-lib/aws-lambda-nodejs NodejsFunction
@aws-cdk/aws-lambda-python-alpha PythonFunction The PythonFunction is not GA in AWS CDK 2, but is supported by the Lumigo CDK integration regardless

Supported Amazon ECS Constructs

The Lumigo CDK integration applies automated distributed tracing to Java, Node.js and Python applications run on Amazon ECS and managed by the following constructs:

AWS CDK Package Constructs Notes
aws-cdk-lib/aws-ecs Ec2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs Ec2TaskDefinition Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs FargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs FargateTaskDefinition Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ApplicationLoadBalancedEc2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ApplicationLoadBalancedFargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ApplicationMultipleTargetGroupsEc2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ApplicationMultipleTargetGroupsFargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns NetworkLoadBalancedEc2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns NetworkLoadBalancedFargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns NetworkMultipleTargetGroupsEc2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns NetworkMultipleTargetGroupsFargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns QueueProcessingEc2Service Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns QueueProcessingFargateService Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ScheduledEc2Task Java, Node.js and Python distributed tracing
aws-cdk-lib/aws-ecs-patterns ScheduledFargateTask Java, Node.js and Python distributed tracing

The automated distributed tracing will work for all Java, Node.js and Python processes dynamically linked against GNU C Library (which is used by virtually all container base images except Alpine Linux) or musl libc (for Alpine Linux-based containers).

Usage

The Lumigo CDK integration enables you to trace all the applicable constructs inside an CDK App or a Stack, as well on a function-by-function basis. The only requirement to use the Lumigo CDK integration is to have the Lumigo token stored in a way that can be accessed as a SecretValue, which supports, among other means of accessing secrets via the CDK:

Instrumenting the entire CDK application

The following code will apply Lumigo autotracing to all the supported AWS Lambda constructs:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app); // This will trace all Lambda functions and ECS workloads managed with supported constructs

app.synth();

Instrumenting a CDK stack

The following code will apply Lumigo autotracing to all the supported AWS Lambda constructs in the instrumented stack:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue, Stack, StackProps } from 'aws-cdk-lib';
import { Function } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class NodejsStack extends Stack {

    constructor(scope: Construct, id: string, props: StackProps = {}) {
        super(scope, id, props);

        new Function(this, 'MyLambda', {
            code: new InlineCode('foo'),
            handler: 'index.handler',
            runtime: Runtime.NODEJS_18_X,
        });

    }

}

const app = new App();

const stack = new NodejsStack(app, 'NodejsTestStack', {
    env: {
        region: 'eu-central-1',
    }
}); 

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(stack);

app.synth();

Instrumenting single functions

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue, Stack, StackProps } from 'aws-cdk-lib';
import { Function } from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

interface NodejsStackProps extends StackProps {
  readonly lumigo: Lumigo;
}

export class NodejsStack extends Stack {

    constructor(scope: Construct, id: string, props: NodejsStackProps = {}) {
        super(scope, id, props);

        const handler = new Function(this, 'MyLambda', {
            code: new InlineCode('foo'),
            handler: 'index.handler',
            runtime: Runtime.NODEJS_18_X,
        });

        props.lumigo.traceLambda(handler);
    }

}

const app = new App();

const lumigo = new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')});

const stack = new NodejsStack(app, 'NodejsTestStack', {
    env: {
        region: 'eu-central-1',
    },
    lumigo,
}); 

app.synth();

Instrumenting single ECS services

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue, Stack, StackProps } from 'aws-cdk-lib';
import { FargateService } from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';

interface NodejsStackProps extends StackProps {
  readonly lumigo: Lumigo;
}

export class NodejsStack extends Stack {

    constructor(scope: Construct, id: string, props: NodejsStackProps = {}) {
        super(scope, id, props);

        const service = new FargateService(this, 'MyFargateService', {
            ...
        });

        props.lumigo.traceEcsService(service);
    }

}

const app = new App();

const lumigo = new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')});

const stack = new NodejsStack(app, 'NodejsTestStack', {
    env: {
        region: 'eu-central-1',
    },
    lumigo,
}); 

app.synth();

Instrumenting single ECS scheduled task

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue, Stack, StackProps } from 'aws-cdk-lib';
import { ScheduledFargateTask } from 'aws-cdk-lib/aws-ecs-patterns';
import { Construct } from 'constructs';

interface NodejsStackProps extends StackProps {
  readonly lumigo: Lumigo;
}

export class NodejsStack extends Stack {

    constructor(scope: Construct, id: string, props: NodejsStackProps = {}) {
        super(scope, id, props);

        const scheduledTask = new ScheduledFargateTask(this, 'MyFargateScheduledTask', {
            ...
        });

        props.lumigo.traceEcsScheduledTask(scheduledTask);
    }

}

const app = new App();

const lumigo = new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')});

const stack = new NodejsStack(app, 'NodejsTestStack', {
    env: {
        region: 'eu-central-1',
    },
    lumigo,
}); 

app.synth();

Instrumenting single ECS task definitions

Instrumenting at the level of the Amazon ECS task definition enables you to share the instrumented task definition across multiple ECS services:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue, Stack, StackProps } from 'aws-cdk-lib';
import { FargateService, FargateTaskDefinition } from 'aws-cdk-lib/aws-ecs';
import { Construct } from 'constructs';

interface NodejsStackProps extends StackProps {
  readonly lumigo: Lumigo;
}

export class NodejsStack extends Stack {

    constructor(scope: Construct, id: string, props: NodejsStackProps = {}) {
        super(scope, id, props);

        const taskDefinition = new FargateTaskDefinition(, this 'MyFargateTaskDefinition', {
            ...
        })
        new FargateService(this, 'MyFargateService1', {
            taskDefinition: taskDefinition,
            ...
        });
        new FargateService(this, 'MyFargateService2', {
            taskDefinition: taskDefinition,
            ...
        });

        props.lumigo.traceEcsTaskDefinition(taskDefinition);
    }

}

const app = new App();

const lumigo = new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')});

const stack = new NodejsStack(app, 'NodejsTestStack', {
    env: {
        region: 'eu-central-1',
    },
    lumigo,
}); 

app.synth();

Lambda Layer Version Pinning

Unless specified otherwise, when instrumenting a Lambda function, the Lumigo CDK integration will use the latest known Lambda layer at the moment of publishing the adopted version of the @lumigo/cdk-constructs-v2 package. (It is considered bad practice in CDK Construct designs to have API calls take place inside the synth phase, so new versions of the @lumigo/cdk-constructs-v2 are regularly released, pointing at the latest layers.)

The pinning of specific layer versions can be performed at the level of the entire application or stack:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app, {
    lambdaNodejsLayerVersion: 42,  // All Lambda functions with a supported Node.js runtime will use the layer v42
    lambdaPythonLayerVersion: 1,  // All Lambda functions with a supported Python runtime will use the layer v1
});

app.synth();

Layer-version pinning can also be done function-by-function:

export class MyNodejsLambdaStack extends Stack {
  constructor(scope: Construct, id: string, props: LumigoStackProps) {
    super(scope, id, props);

    const handler = new Function(this, 'MyLambda', {
      code: new InlineCode('foo'),
      handler: 'index.handler',
      runtime: Runtime.NODEJS_14_X,
    });

    props.lumigo.traceLambda(handler, {
      layerVersion: 42,  // Which layer this is about (Node.js? Python?) is contextual to the `runtime` of the function
    });
  }
}

ECS tracer version pinning

The tracing on Amazon ECS relies on the lumigo/lumigo-autotrace container image available on Amazon ECR Public Gallery. By default, the Lumigo CDK integration will use the latest image tag at the moment of publishing the adopted version of the @lumigo/cdk-constructs-v2 package. (It is considered bad practice in CDK Construct designs to have API calls take place inside the synth phase, so new versions of the @lumigo/cdk-constructs-v2 are regularly released, pointing at the latest container image tag.)

The pinning of specific layer versions can be performed at the level of the entire application or stack:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app, {
    lumigoAutoTraceImage: 'public.ecr.aws/lumigo/lumigo-autotrace:v14',  // See https://gallery.ecr.aws/lumigo/lumigo-autotrace for the list of currently available tags
});

app.synth();

Image-version pinning can also be done for single Amazon ECS task definitions and services.

Setting Lumigo tags

Lumigo tags add dimension to your Lambda functions so that they can be identified, managed, organized, searched for, and filtered in Lumigo. They can be utilized to filter resources for projects, alerts and functions, helping you simplify the complexity of monitoring distributed applications.

Every trace* method that the Lumigo object offers to trace functions, ECS workloads, entire stacks or applications, supports also setting Lumigo tags on the traced resource:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app, {
    lumigoTag: 'MY_TAG',
});

app.synth();

Behind the scenes, the Lumigo CDK integration sets the AWS Tag LUMIGO_TAG with the value you specify on the Lambda functions, ECS services and task definitions, etc.

W3C TraceContext propagation in AWS Lambda

To be able to trace scenarios in which a Lambda function sends HTTP requests to an application instrumented with OpenTelemetry, like those using the Lumigo OpenTelemetry Distro for Java, Lumigo OpenTelemetry Distro for JS and Lumigo OpenTelemetry Distro for Python or other OpenTelemetry SDKs, the Lumigo Node.js and Python tracers can add W3C TraceContext HTTP headers to outgoing requests.

At the tracer level, the support of W3C TraceContext is regulated via the LUMIGO_PROPAGATE_W3C environment variable. The Lumigo CDK integration will turn on the W3C TraceContext for all your Lambda functions by default. If your Lambda functions are using some request-signing mechanism, and you wish to turn off W3C TraceContext propagation, set the lambdaEnableW3CTraceContext to false as follows:

import { Lumigo } from '@lumigo/cdk-constructs-v2';
import { App, SecretValue } from 'aws-cdk-lib';

const app = new App();

// Add here stacks and constructs

new Lumigo({lumigoToken:SecretValue.secretsManager('LumigoToken')}).traceEverything(app, {
    lambdaEnableW3CTraceContext: false, // <--- This parameter set to false dectivates the W3C TraceContext propagation for all supported Lambda functions
});

app.synth();

Deactivating W3C TraceContext support is also supported on a function-by-function basis:

export class MyNodejsLambdaStack extends Stack {
  constructor(scope: Construct, id: string, props: LumigoStackProps) {
    super(scope, id, props);

    const handler = new Function(this, 'MyLambda', {
      code: new InlineCode('foo'),
      handler: 'index.handler',
      runtime: Runtime.NODEJS_20_X,
    });

    props.lumigo.traceLambda(handler, {
      enableW3CTraceContext: false,  // <--- This parameter set to false deactivates the W3C TraceCntext propagation for this Lambda function.
    });
  }
}

How does it work?

Like any other CDK construct, the Lumigo CDK integration contributes changes to the CloudFormation templates that CDK generates for you. The changes that the Lumigo CDK integration applies are focused on enabling out-of-the-box distributed tracing for the AWS Lambda and Amazon ECS workloads you manage via AWS CDK.

Instrumentation of AWS Lambda functions

When encountering a supported AWS Lambda-related construct, the Lumigo CDK integration:

  1. adds a Lambda layer containing the tracer for the Lambda function runtime; the layer to be used depends on the runtime, and the version to be used (Node.js, Python) is the latest at the time of the release of the version of the @lumigo/cdk-constructs-v2 package. Using a different version of the layer is supported via version pinning.
  2. sets the LUMIGO_TRACER_TOKEN environment variable with, as value, the plain-text value of the SecretValue passed on instantiation of the Lumigo object. The value should be a valid Lumigo Token.
  3. adds runtime-dependent environment variables as documented in the Lumigo AWS Lambda manual tracing documentation.

Instrumentation of Amazon ECS workloads

When encountering a supported Amazon ECS-related construct, the Lumigo CDK integration:

  1. Adds an ephemeral volume called lumigo-injector to the task definition
  2. Adds a container called lumigo-injector that uses a public Amazon ECR image; the image contains the latest copies of the Lumigo OpenTelemetry Distro for Java, Lumigo OpenTelemetry Distro for Node.js and Lumigo OpenTelemetry Distro for Python, alongside an LD_PRELOAD injector that is not unlike this OpenTelemetry Injector.
  3. The lumigo-injector volume is mounted to all containers in the task definition, including the lumigo-injector container.
  4. All containers other than lumigo-injector get added:
  5. A container dependency on the completion of the lumigo-injector container.
  6. The LUMIGO_TRACER_TOKEN environment variable with, as value, the plain-text value of the SecretValue passed on instantiation of the Lumigo object.
  7. The LD_PRELOAD environment variable pointing to the Lumigo Injector's lumigo_injector.so file, which performs as needed the activation of the Python or Node.js tracers delivered by the Lumigo OpenTelemetry Distros for Node.js and Python as needed.

The automatic instrumentation of Amazon ECS task definitions works based on the principle that the runtimes (CPython, Node.js, etc.) inside your Amazon ECS tasks can be traced with OpenTelemetry Distros like Lumigo's that have no-code activation capabilities (see Node.js, Python) if:

  1. Tracer delivery: The files of the tracers are available on the filesystem of your containers in a way that the application process has read-access to them
  2. Tracer activation: The tracer is activated therough the application's process environment

The tracer delivery is accomplished by adding the shared lumigo-injector volume, and copy into it the Lumigo OpenTelemetry Distros before all other containers can start (thanks to the container dependencies). The tracer activation is based on manipulating the process environment of your applications inside the containers via the Lumigo Injector. The Lumigo Injector is an LD_PRELOAD object that ensure that, when your application looks up environment variables like NODE_OPTIONS or AUTOWRAPT_BOOTSTRAP (used for the activation of the Lumigo OpenTelemetry Distros for Node.js and Python, respectively), the right value is returned.