Skip to content

Commit 02749b4

Browse files
authoredDec 13, 2021
feat(lambda): add cloudwatch lambda insights arm support (#17665)
Adding builtin support for the new ARM64 CloudWatch insights Lambda layers which were [announced](https://aws.amazon.com/about-aws/whats-new/2021/11/amazon-cloudwatch-lambda-insights-functions-graviton2/) yesterday. also fixes #17133 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4937cd0 commit 02749b4

17 files changed

+987
-109
lines changed
 

‎packages/@aws-cdk/aws-cloudfront/lib/experimental/edge-function.ts

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export class EdgeFunction extends Resource implements lambda.IVersion {
4646
public readonly permissionsNode: ConstructNode;
4747
public readonly role?: iam.IRole;
4848
public readonly version: string;
49+
public readonly architecture: lambda.Architecture;
4950

5051
private readonly _edgeFunction: lambda.Function;
5152

@@ -66,6 +67,7 @@ export class EdgeFunction extends Resource implements lambda.IVersion {
6667
this.grantPrincipal = this._edgeFunction.role!;
6768
this.permissionsNode = this._edgeFunction.permissionsNode;
6869
this.version = lambda.extractQualifierFromArn(this.functionArn);
70+
this.architecture = this._edgeFunction.architecture;
6971

7072
this.node.defaultChild = this._edgeFunction;
7173
}

‎packages/@aws-cdk/aws-lambda/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,19 @@ new lambda.Function(this, 'MyFunction', {
411411
});
412412
```
413413

414+
If you are deploying an ARM_64 Lambda Function, you must specify a
415+
Lambda Insights Version >= `1_0_119_0`.
416+
417+
```ts
418+
new lambda.Function(this, 'MyFunction', {
419+
runtime: lambda.Runtime.NODEJS_12_X,
420+
handler: 'index.handler',
421+
architecture: lambda.Architecture.ARM_64,
422+
code: lambda.Code.fromAsset(path.join(__dirname, 'lambda-handler')),
423+
insightsVersion: lambda.LambdaInsightsVersion.VERSION_1_0_119_0,
424+
});
425+
```
426+
414427
## Event Rule Target
415428

416429
You can use an AWS Lambda function as a target for an Amazon CloudWatch event

‎packages/@aws-cdk/aws-lambda/lib/alias.ts

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
33
import * as iam from '@aws-cdk/aws-iam';
44
import { ArnFormat } from '@aws-cdk/core';
55
import { Construct } from 'constructs';
6+
import { Architecture } from './architecture';
67
import { EventInvokeConfigOptions } from './event-invoke-config';
78
import { IFunction, QualifiedFunctionBase } from './function-base';
89
import { extractQualifierFromArn, IVersion } from './lambda-version';
@@ -97,6 +98,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
9798
public readonly functionName = `${attrs.aliasVersion.lambda.functionName}:${attrs.aliasName}`;
9899
public readonly grantPrincipal = attrs.aliasVersion.grantPrincipal;
99100
public readonly role = attrs.aliasVersion.role;
101+
public readonly architecture = attrs.aliasVersion.lambda.architecture;
100102

101103
protected readonly canCreatePermissions = this._isStackAccount();
102104
protected readonly qualifier = attrs.aliasName;
@@ -120,6 +122,8 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
120122

121123
public readonly lambda: IFunction;
122124

125+
public readonly architecture: Architecture;
126+
123127
public readonly version: IVersion;
124128

125129
/**
@@ -145,6 +149,7 @@ export class Alias extends QualifiedFunctionBase implements IAlias {
145149
this.lambda = props.version.lambda;
146150
this.aliasName = this.physicalName;
147151
this.version = props.version;
152+
this.architecture = this.lambda.architecture;
148153

149154
const alias = new CfnAlias(this, 'Resource', {
150155
name: this.aliasName,

‎packages/@aws-cdk/aws-lambda/lib/function-base.ts

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as ec2 from '@aws-cdk/aws-ec2';
33
import * as iam from '@aws-cdk/aws-iam';
44
import { ArnFormat, ConstructNode, IResource, Resource, Token } from '@aws-cdk/core';
55
import { AliasOptions } from './alias';
6+
import { Architecture } from './architecture';
67
import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config';
78
import { IEventSource } from './event-source';
89
import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping';
@@ -56,6 +57,11 @@ export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable {
5657
*/
5758
readonly permissionsNode: ConstructNode;
5859

60+
/**
61+
* The system architectures compatible with this lambda function.
62+
*/
63+
readonly architecture: Architecture;
64+
5965
/**
6066
* Adds an event source that maps to this AWS Lambda function.
6167
* @param id construct ID
@@ -173,6 +179,12 @@ export interface FunctionAttributes {
173179
* For environment-agnostic stacks this will default to `false`.
174180
*/
175181
readonly sameEnvironment?: boolean;
182+
183+
/**
184+
* The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64).
185+
* @default - Architecture.X86_64
186+
*/
187+
readonly architecture?: Architecture;
176188
}
177189

178190
export abstract class FunctionBase extends Resource implements IFunction, ec2.IClientVpnConnectionHandler {
@@ -203,6 +215,11 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC
203215
*/
204216
public abstract readonly permissionsNode: ConstructNode;
205217

218+
/**
219+
* The architecture of this Lambda Function.
220+
*/
221+
public abstract readonly architecture: Architecture;
222+
206223
/**
207224
* Whether the addPermission() call adds any permissions
208225
*
@@ -521,6 +538,10 @@ class LatestVersion extends FunctionBase implements IVersion {
521538
return `${this.lambda.functionName}:${this.version}`;
522539
}
523540

541+
public get architecture() {
542+
return this.lambda.architecture;
543+
}
544+
524545
public get grantPrincipal() {
525546
return this.lambda.grantPrincipal;
526547
}

‎packages/@aws-cdk/aws-lambda/lib/function.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ export class Function extends FunctionBase {
450450
public readonly grantPrincipal: iam.IPrincipal;
451451
public readonly role = role;
452452
public readonly permissionsNode = this.node;
453+
public readonly architecture = attrs.architecture ?? Architecture.X86_64;
453454

454455
protected readonly canCreatePermissions = attrs.sameEnvironment ?? this._isStackAccount();
455456

@@ -576,7 +577,7 @@ export class Function extends FunctionBase {
576577
/**
577578
* The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64).
578579
*/
579-
public readonly architecture?: Architecture;
580+
public readonly architecture: Architecture;
580581

581582
/**
582583
* The timeout configured for this lambda.
@@ -600,6 +601,8 @@ export class Function extends FunctionBase {
600601
private readonly currentVersionOptions?: VersionOptions;
601602
private _currentVersion?: Version;
602603

604+
private _architecture?: Architecture;
605+
603606
constructor(scope: Construct, id: string, props: FunctionProps) {
604607
super(scope, id, {
605608
physicalName: props.functionName,
@@ -683,7 +686,7 @@ export class Function extends FunctionBase {
683686
if (props.architectures && props.architectures.length > 1) {
684687
throw new Error('Only one architecture must be specified.');
685688
}
686-
const architecture = props.architecture ?? (props.architectures && props.architectures[0]);
689+
this._architecture = props.architecture ?? (props.architectures && props.architectures[0]);
687690

688691
const resource: CfnFunction = new CfnFunction(this, 'Resource', {
689692
functionName: this.physicalName,
@@ -717,7 +720,7 @@ export class Function extends FunctionBase {
717720
kmsKeyArn: props.environmentEncryption?.keyArn,
718721
fileSystemConfigs,
719722
codeSigningConfigArn: props.codeSigningConfig?.codeSigningConfigArn,
720-
architectures: architecture ? [architecture.name] : undefined,
723+
architectures: this._architecture ? [this._architecture.name] : undefined,
721724
});
722725

723726
resource.node.addDependency(this.role);
@@ -733,7 +736,7 @@ export class Function extends FunctionBase {
733736
this.runtime = props.runtime;
734737
this.timeout = props.timeout;
735738

736-
this.architecture = props.architecture;
739+
this.architecture = props.architecture ?? Architecture.X86_64;
737740

738741
if (props.layers) {
739742
if (props.runtime === Runtime.FROM_IMAGE) {
@@ -935,7 +938,7 @@ Environment variables can be marked for removal when used in Lambda@Edge by sett
935938
if (props.runtime !== Runtime.FROM_IMAGE) {
936939
// Layers cannot be added to Lambda container images. The image should have the insights agent installed.
937940
// See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Lambda-Insights-Getting-Started-docker.html
938-
this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', props.insightsVersion.layerVersionArn));
941+
this.addLayers(LayerVersion.fromLayerVersionArn(this, 'LambdaInsightsLayer', props.insightsVersion._bind(this, this).arn));
939942
}
940943
this.role?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy'));
941944
}

‎packages/@aws-cdk/aws-lambda/lib/lambda-insights.ts

+65-12
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import { Aws, CfnMapping, Fn, IResolveContext, Lazy, Stack, Token } from '@aws-cdk/core';
22
import { FactName, RegionInfo } from '@aws-cdk/region-info';
3+
import { Construct } from 'constructs';
4+
import { Architecture } from './architecture';
5+
import { IFunction } from './function-base';
6+
37

48
// This is the name of the mapping that will be added to the CloudFormation template, if a stack is region agnostic
59
const DEFAULT_MAPPING_PREFIX = 'LambdaInsightsVersions';
610

11+
/**
12+
* Config returned from {@link LambdaInsightsVersion._bind}
13+
*/
14+
interface InsightsBindConfig {
15+
/**
16+
* ARN of the Lambda Insights Layer Version
17+
*/
18+
readonly arn: string;
19+
}
20+
721
// To add new versions, update fact-tables.ts `CLOUDWATCH_LAMBDA_INSIGHTS_ARNS` and create a new `public static readonly VERSION_A_B_C_D`
822

923
/**
@@ -31,6 +45,11 @@ export abstract class LambdaInsightsVersion {
3145
*/
3246
public static readonly VERSION_1_0_98_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.98.0');
3347

48+
/**
49+
* Version 1.0.119.0
50+
*/
51+
public static readonly VERSION_1_0_119_0 = LambdaInsightsVersion.fromInsightsVersion('1.0.119.0');
52+
3453
/**
3554
* Use the insights extension associated with the provided ARN. Make sure the ARN is associated
3655
* with same region as your function
@@ -40,23 +59,35 @@ export abstract class LambdaInsightsVersion {
4059
public static fromInsightVersionArn(arn: string): LambdaInsightsVersion {
4160
class InsightsArn extends LambdaInsightsVersion {
4261
public readonly layerVersionArn = arn;
62+
public _bind(_scope: Construct, _function: IFunction): InsightsBindConfig {
63+
return { arn };
64+
}
4365
}
4466
return new InsightsArn();
4567
}
4668

4769
// Use the verison to build the object. Not meant to be called by the user -- user should use e.g. VERSION_1_0_54_0
4870
private static fromInsightsVersion(insightsVersion: string): LambdaInsightsVersion {
4971

50-
// Check if insights version is valid. This should only happen if one of the public static readonly versions are set incorrectly
51-
const versionExists = RegionInfo.regions.some(regionInfo => regionInfo.cloudwatchLambdaInsightsArn(insightsVersion));
52-
if (!versionExists) {
53-
throw new Error(`Insights version ${insightsVersion} does not exist.`);
54-
}
55-
5672
class InsightsVersion extends LambdaInsightsVersion {
5773
public readonly layerVersionArn = Lazy.uncachedString({
5874
produce: (context) => getVersionArn(context, insightsVersion),
5975
});
76+
77+
public _bind(_scope: Construct, _function: IFunction): InsightsBindConfig {
78+
const arch = _function.architecture?.name ?? Architecture.X86_64.name;
79+
// Check if insights version is valid. This should only happen if one of the public static readonly versions are set incorrectly
80+
// or if the version is not available for the Lambda Architecture
81+
const versionExists = RegionInfo.regions.some(regionInfo => regionInfo.cloudwatchLambdaInsightsArn(insightsVersion, arch));
82+
if (!versionExists) {
83+
throw new Error(`Insights version ${insightsVersion} does not exist.`);
84+
}
85+
return {
86+
arn: Lazy.uncachedString({
87+
produce: (context) => getVersionArn(context, insightsVersion, arch),
88+
}),
89+
};
90+
}
6091
}
6192
return new InsightsVersion();
6293
}
@@ -65,6 +96,13 @@ export abstract class LambdaInsightsVersion {
6596
* The arn of the Lambda Insights extension
6697
*/
6798
public readonly layerVersionArn: string = '';
99+
100+
/**
101+
* Returns the arn of the Lambda Insights extension based on the
102+
* Lambda architecture
103+
* @internal
104+
*/
105+
public abstract _bind(_scope: Construct, _function: IFunction): InsightsBindConfig;
68106
}
69107

70108
/**
@@ -73,14 +111,15 @@ export abstract class LambdaInsightsVersion {
73111
*
74112
* This function is run on CDK synthesis.
75113
*/
76-
function getVersionArn(context: IResolveContext, insightsVersion: string): string {
114+
function getVersionArn(context: IResolveContext, insightsVersion: string, architecture?: string): string {
77115

78116
const scopeStack = Stack.of(context.scope);
79117
const region = scopeStack.region;
118+
const arch = architecture ?? Architecture.X86_64.name;
80119

81120
// Region is defined, look up the arn, or throw an error if the version isn't supported by a region
82121
if (region !== undefined && !Token.isUnresolved(region)) {
83-
const arn = RegionInfo.get(region).cloudwatchLambdaInsightsArn(insightsVersion);
122+
const arn = RegionInfo.get(region).cloudwatchLambdaInsightsArn(insightsVersion, arch);
84123
if (arn === undefined) {
85124
throw new Error(`Insights version ${insightsVersion} is not supported in region ${region}`);
86125
}
@@ -116,19 +155,33 @@ function getVersionArn(context: IResolveContext, insightsVersion: string): strin
116155
* -- {'arn': 'arn3'},
117156
* - us-east-2
118157
* -- {'arn': 'arn4'}
158+
* LambdaInsightsVersions101190arm64 // a separate mapping version 1.0.119.0 arm64
159+
* - us-east-1
160+
* -- {'arn': 'arn3'},
161+
* - us-east-2
162+
* -- {'arn': 'arn4'}
119163
*/
120164

121-
const mapName = DEFAULT_MAPPING_PREFIX + insightsVersion.split('.').join('');
165+
let mapName = DEFAULT_MAPPING_PREFIX + insightsVersion.split('.').join('');
166+
// if the architecture is arm64 then append that to the end of the name
167+
// this is so that we can have a separate mapping for x86 vs arm in scenarios
168+
// where we have Lambda functions with both architectures in the same stack
169+
if (arch === Architecture.ARM_64.name) {
170+
mapName += arch;
171+
}
122172
const mapping: { [k1: string]: { [k2: string]: any } } = {};
123-
const region2arns = RegionInfo.regionMap(FactName.cloudwatchLambdaInsightsVersion(insightsVersion));
173+
const region2arns = RegionInfo.regionMap(FactName.cloudwatchLambdaInsightsVersion(insightsVersion, arch));
124174
for (const [reg, arn] of Object.entries(region2arns)) {
125175
mapping[reg] = { arn };
126176
}
127177

128178
// Only create a given mapping once. If another version of insights is used elsewhere, that mapping will also exist
129179
if (!scopeStack.node.tryFindChild(mapName)) {
130-
new CfnMapping(scopeStack, mapName, { mapping });
180+
// need to call findInMap here if we are going to set lazy=true, otherwise
181+
// we get the informLazyUse info message
182+
const map = new CfnMapping(scopeStack, mapName, { mapping, lazy: true });
183+
return map.findInMap(Aws.REGION, 'arn');
131184
}
132185
// The ARN will be looked up at deployment time from the mapping we created
133186
return Fn.findInMap(mapName, Aws.REGION, 'arn');
134-
}
187+
}

‎packages/@aws-cdk/aws-lambda/lib/lambda-version.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
22
import { Fn, Lazy, RemovalPolicy } from '@aws-cdk/core';
33
import { Construct } from 'constructs';
44
import { Alias, AliasOptions } from './alias';
5+
import { Architecture } from './architecture';
56
import { EventInvokeConfigOptions } from './event-invoke-config';
67
import { Function } from './function';
78
import { IFunction, QualifiedFunctionBase } from './function-base';
@@ -127,11 +128,12 @@ export class Version extends QualifiedFunctionBase implements IVersion {
127128
public readonly functionArn = versionArn;
128129
public readonly grantPrincipal = lambda.grantPrincipal;
129130
public readonly role = lambda.role;
131+
public readonly architecture = lambda.architecture;
130132

131133
protected readonly qualifier = version;
132134
protected readonly canCreatePermissions = this._isStackAccount();
133135

134-
public addAlias(name: string, opts: AliasOptions = { }): Alias {
136+
public addAlias(name: string, opts: AliasOptions = {}): Alias {
135137
return addAlias(this, this, name, opts);
136138
}
137139

@@ -153,11 +155,12 @@ export class Version extends QualifiedFunctionBase implements IVersion {
153155
public readonly functionArn = `${attrs.lambda.functionArn}:${attrs.version}`;
154156
public readonly grantPrincipal = attrs.lambda.grantPrincipal;
155157
public readonly role = attrs.lambda.role;
158+
public readonly architecture = attrs.lambda.architecture;
156159

157160
protected readonly qualifier = attrs.version;
158161
protected readonly canCreatePermissions = this._isStackAccount();
159162

160-
public addAlias(name: string, opts: AliasOptions = { }): Alias {
163+
public addAlias(name: string, opts: AliasOptions = {}): Alias {
161164
return addAlias(this, this, name, opts);
162165
}
163166

@@ -175,6 +178,7 @@ export class Version extends QualifiedFunctionBase implements IVersion {
175178
public readonly lambda: IFunction;
176179
public readonly functionArn: string;
177180
public readonly functionName: string;
181+
public readonly architecture: Architecture;
178182

179183
protected readonly qualifier: string;
180184
protected readonly canCreatePermissions = true;
@@ -183,6 +187,7 @@ export class Version extends QualifiedFunctionBase implements IVersion {
183187
super(scope, id);
184188

185189
this.lambda = props.lambda;
190+
this.architecture = props.lambda.architecture;
186191

187192
const version = new CfnVersion(this, 'Resource', {
188193
codeSha256: props.codeSha256,
@@ -239,7 +244,7 @@ export class Version extends QualifiedFunctionBase implements IVersion {
239244
* @param aliasName The name of the alias (e.g. "live")
240245
* @param options Alias options
241246
*/
242-
public addAlias(aliasName: string, options: AliasOptions = { }): Alias {
247+
public addAlias(aliasName: string, options: AliasOptions = {}): Alias {
243248
return addAlias(this, this, aliasName, options);
244249
}
245250

0 commit comments

Comments
 (0)
Please sign in to comment.