@ashishpandeyone/cdk-lambda-web-function 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,7 +59,7 @@ Requests carry `lambda-web-request-id` and `lambda-web-endpoint-name` headers.
59
59
 
60
60
  ## Regions and endpoints
61
61
 
62
- Each `WebFunction` has one endpoint, `fn.endpoint`:
62
+ Each `WebFunction` has a default endpoint, `fn.endpoint`, configured with the `endpoint` property. [Add more](#more-endpoints) with `fn.addEndpoint()`.
63
63
 
64
64
  | `endpointType` | URL | Routing |
65
65
  |---|---|---|
@@ -84,6 +84,34 @@ new CfnOutput(this, 'UsEast1Url', { value: perRegion.endpoint.regionalUrl('us-ea
84
84
 
85
85
  - `MULTI_REGION` and `PER_REGION` need at least one region besides the home region; `HOME_REGION` takes none.
86
86
  - A MultiRegion endpoint takes about a minute to create.
87
+ - Lambda routes a `MULTI_REGION` endpoint by latency, not by health: it doesn't fail over when a region has problems.
88
+
89
+ ### More endpoints
90
+
91
+ A function can have up to 10 endpoints, each with its own URL, authentication, regions and limits. For example, a public endpoint next to an IAM-only global one:
92
+
93
+ ```ts
94
+ const api = new WebFunction(this, 'Api', {
95
+ runtime: lambda.Runtime.NODEJS_24_X,
96
+ code: lambda.Code.fromAsset('dist'),
97
+ authType: WebFunctionAuthType.NONE, // the default endpoint is public
98
+ endpoint: { rateLimit: 1000 }, // requests per second
99
+ });
100
+
101
+ const internal = api.addEndpoint('Internal', {
102
+ authType: WebFunctionAuthType.AWS_IAM,
103
+ endpointType: WebFunctionEndpointType.MULTI_REGION,
104
+ regions: ['us-east-1', 'eu-west-1'],
105
+ description: 'Service-to-service traffic',
106
+ maxEnvironments: 50,
107
+ });
108
+ internal.grantInvoke(consumerRole);
109
+ new CfnOutput(this, 'InternalUrl', { value: internal.url });
110
+ ```
111
+
112
+ - `rateLimit` takes 0 (blocks all traffic), 100–1,000 in steps of 100, or 2,000–10,000 in steps of 1,000; excess requests get HTTP 429. `maxEnvironments` takes 2–10,000.
113
+ - **Both limits apply separately in each region** an endpoint serves from: a 3-region endpoint with `rateLimit: 1000` accepts up to 1,000 requests per second in each region.
114
+ - Endpoints can serve the same regions. The construct owns one `.local` log group per region, whichever endpoints serve it.
87
115
 
88
116
  ### Changing regions or the endpoint type
89
117
 
@@ -91,7 +119,7 @@ An endpoint's type and regions can't change in place: a change creates a **new e
91
119
 
92
120
  | `endpointName` | Physical name | Changing `regions` or `endpointType` |
93
121
  |---|---|---|
94
- | omitted (recommended) | `default-<hash of type and regions>` | works: new endpoint, new URL |
122
+ | omitted (recommended) | `default-<hash>` for `fn.endpoint`, `<id>-<hash>` for added endpoints; the hash covers the type and regions | works: new endpoint, new URL |
95
123
  | set, e.g. `'live'` | `live` | also change the name in the same deployment, or it fails with *The endpoint live already exists* (the stack rolls back and the old endpoint keeps serving) |
96
124
 
97
125
  The construct warns at synth time when `endpointName` is set.
@@ -107,7 +135,7 @@ The construct warns at synth time when `endpointName` is set.
107
135
  fn.grants.invoke(consumerRole); // or fn.grantInvoke(consumerRole), as on lambda.Function
108
136
  ```
109
137
 
110
- The grant is on the endpoint's home-region ARN, which IAM evaluates in every region the endpoint serves from, including the global URL.
138
+ The grant is on the endpoint's home-region ARN, which IAM evaluates in every region the endpoint serves from, including the global URL. `fn.grants` covers the default endpoint only; grant other endpoints with their own `grantInvoke()` or `grants.invoke()`.
111
139
 
112
140
  | Endpoint | How to sign |
113
141
  |---|---|
@@ -181,9 +209,126 @@ new NodejsWebFunction(this, 'Api', {
181
209
 
182
210
  Lambda Web Functions download code as the `lambda.amazonaws.com` service principal, from a **versioned** bucket, and the CDK bootstrap bucket allows neither. So each stack gets a small code bucket, and the code is copied into it on deploy.
183
211
 
184
- ### Updates
212
+ ## Revisions
213
+
214
+ Any change to code or configuration creates a new **revision** (`fn.currentRevision`), and endpoints switch to it once it's ready. A failed deployment rolls back to the previous revision. If nothing changed, nothing is deployed.
215
+
216
+ Earlier revisions are kept, so you can roll back or route traffic to them. After each deployment, the construct deletes the oldest beyond `revisionRetention` (default 20, up to 50). It never deletes a revision an endpoint routes to, or one the previous deployment used, which a rollback returns to.
217
+
218
+ ```ts
219
+ new WebFunction(this, 'Api', {
220
+ runtime: lambda.Runtime.NODEJS_24_X,
221
+ code: lambda.Code.fromAsset('dist'),
222
+ authType: WebFunctionAuthType.AWS_IAM,
223
+ revisionRetention: 5,
224
+ });
225
+ ```
226
+
227
+ ### Routing to earlier revisions
228
+
229
+ Reference an earlier revision by its ID (for example from a `CfnOutput` of `fn.currentRevision.revisionId`, or `aws lambda-web list-web-function-revisions`). Then pin an endpoint to it, or split traffic between two revisions:
230
+
231
+ ```ts
232
+ const previous = WebFunctionRevision.fromRevisionAttributes(this, 'Previous', {
233
+ webFunction: fn,
234
+ revisionId: 'rev-a1b2c',
235
+ });
236
+
237
+ // Keep the default endpoint on the previous revision, and send 10% to the new one:
238
+ fn.endpoint.routeTraffic(previous, [{ revision: fn.currentRevision, weight: 0.1 }]);
239
+
240
+ // Or a second endpoint that stays on the previous revision while fn.endpoint moves on:
241
+ fn.addEndpoint('Stable', { authType: WebFunctionAuthType.AWS_IAM, revision: previous });
242
+ ```
243
+
244
+ - An endpoint routes to at most two revisions. The additional revision's `weight` is a whole percentage, from 0.01 to 0.99.
245
+ - To promote, remove `routeTraffic` (or `revision`) and deploy: the endpoint serves the current revision again.
246
+ - Lambda Web Function metrics have no revision dimension. To compare a split's revisions, log a version you control (for example an environment variable) from your application.
247
+
248
+ ### Canary and linear deployments
249
+
250
+ By default an endpoint moves all its traffic to a new revision at once. With `deployment`, it moves in steps instead, watching CloudWatch alarms: if one goes into ALARM, the endpoint goes back to the revision it served before and the deployment fails, so CloudFormation rolls the stack back.
251
+
252
+ ```ts
253
+ const api = new WebFunction(this, 'Api', {
254
+ runtime: lambda.Runtime.NODEJS_24_X,
255
+ code: lambda.Code.fromAsset('dist'),
256
+ authType: WebFunctionAuthType.AWS_IAM,
257
+ endpoint: {
258
+ // 10% of the traffic for 10 minutes, then all of it; back if more than 2% of requests get a 5XX.
259
+ deployment: WebFunctionDeployment.canary({ percent: 10, bake: Duration.minutes(10), rollbackOn: { serverErrorRate: 2 } }),
260
+ },
261
+ });
262
+
263
+ // 25% more every 5 minutes, watching an alarm of your own. Each endpoint has its own deployment.
264
+ // errorRate: any cloudwatch.IAlarm, e.g. on a metric your application publishes.
265
+ api.addEndpoint('Public', {
266
+ authType: WebFunctionAuthType.NONE,
267
+ deployment: WebFunctionDeployment.linear({ percent: 25, interval: Duration.minutes(5), alarms: [errorRate] }),
268
+ });
269
+ ```
270
+
271
+ | | Steps | Limits |
272
+ |---|---|---|
273
+ | `allAtOnce()` (default) | 100% | |
274
+ | `canary({ percent, bake })` | `percent`, then 100% after `bake` | `percent` 1–99, `bake` 1–45 minutes |
275
+ | `linear({ percent, interval })` | `percent` more every `interval` | `percent` 5–50, `interval` from 1 minute, at most 45 minutes in all |
276
+
277
+ How it works:
278
+
279
+ - Each deployment that creates a revision (code or configuration) moves the traffic from the revision the endpoint serves at that moment, which is also what it goes back to. If someone switched the endpoint outside CloudFormation, the deployment starts from there.
280
+ - Each step waits until every region of a `MULTI_REGION` or `PER_REGION` endpoint has applied it (usually about 15 seconds), and if a region fails to update, the endpoint goes back as for an alarm. The bake or interval counts from the start of the step.
281
+ - Alarms are checked before the first step (an alarm already in ALARM stops the deployment before any traffic moves), throughout every step, and once the new revision has all the traffic. They can be in any region, and composite alarms work. `INSUFFICIENT_DATA` doesn't stop a deployment.
282
+ - A rollback of the stack, for any reason, moves the traffic back at once, without waiting or checking alarms.
283
+ - A stack's first deployment, and a new endpoint (including one renamed by a region change), serve the revision at once: there's nothing to move from.
284
+ - A deployment can take up to an hour: CloudFormation waits for it before it updates the endpoint. Deployments of different endpoints run in parallel.
285
+ - A canary or linear endpoint always moves to the current revision, so it can't also use `revision`, `additionalRevisions` or `routeTraffic()`. Use `allAtOnce()` to deploy a fix immediately, for example while an alarm is in ALARM.
286
+
287
+ #### Rollback alarms
288
+
289
+ `rollbackOn` makes the endpoint create alarms on its own error rates, and the deployment watches them alongside any `alarms` you pass:
290
+
291
+ | `rollbackOn` | Default | |
292
+ |---|---|---|
293
+ | `serverErrorRate` | none | roll back when more than this % of requests get a 5XX |
294
+ | `clientErrorRate` | none | the same for 4XX (including 429s from `rateLimit` and `maxEnvironments`) |
295
+ | `minimumRequests` | 10 | periods with fewer requests don't count, so one failed request out of two doesn't roll back |
296
+ | `period`, `evaluationPeriods` | 1 minute, 2 | consecutive periods over the threshold before rolling back |
297
+
298
+ - **One alarm per threshold in every region the endpoint serves from.** Metrics are published in the region that served the request, and an alarm can only watch metrics in its own region. The home region's alarm is a normal CloudFormation alarm; the others are created and deleted with the endpoint by a custom resource, and named `LambdaWeb-<function>-<endpoint>-5xx-rate` (or `-4xx-rate`). `endpoint.rollbackAlarms` lists them all, e.g. to add notifications to the home region's.
299
+ - **The alarms see the whole endpoint.** Lambda Web Function metrics have no revision dimension, so at 10% a new revision's errors are about a tenth of the endpoint's: set the threshold low enough to notice that, or alarm on metrics your application publishes with its version.
300
+ - **5XX includes the platform's 503s** while the endpoint scales up for a sudden burst, not only your application's errors.
301
+ - **A step should outlast the alarm.** CloudWatch needs `period` × `evaluationPeriods`, plus a couple of minutes of delay, to fire; the construct warns when a canary's bake or a linear interval is shorter.
302
+
303
+ The deployment runs in a few resources per stack, created on first use: two Lambda functions, a Step Functions state machine that calls them every 15 seconds, and a log group.
304
+
305
+ ## Metrics
306
+
307
+ Endpoints and functions have helpers for their CloudWatch metrics (namespace `AWS/Lambda/Web`), like `lambda.Function`'s:
308
+
309
+ ```ts
310
+ fn.endpoint.metricServerErrorRate().createAlarm(this, 'ErrorRate', { threshold: 1, evaluationPeriods: 3 });
311
+ fn.endpoint.metricRequestThrottles().createAlarm(this, 'Throttled', { threshold: 10, evaluationPeriods: 3 });
312
+ fn.metricConcurrentRequests().createAlarm(this, 'Saturated', { threshold: 60, evaluationPeriods: 3 });
313
+ ```
314
+
315
+ | Endpoint | Metric |
316
+ |---|---|
317
+ | `metricRequests()` | `ExecutedRequests` (there's no separate 2XX count) |
318
+ | `metricClientErrors()`, `metricServerErrors()` | `HTTPResponseStatusClass4XX`, `HTTPResponseStatusClass5XX` |
319
+ | `metricClientErrorRate()`, `metricServerErrorRate()` | 4XX or 5XX as a % of requests (math expression) |
320
+ | `metricActiveEnvironments()` | `EndpointActiveEnvironments`, to compare with `maxEnvironments` |
321
+ | `metricRequestThrottles()`, `metricEnvironmentThrottles()` | 429s from `rateLimit` and from `maxEnvironments` |
322
+
323
+ | Function | Metric |
324
+ |---|---|
325
+ | `metricConcurrentRequests()` | `EnvironmentConcurrentRequests` (maximum), to compare with `maxConcurrencyPerEnvironment` |
326
+ | `metricMaxMemoryUsage()` | `EnvironmentMaxMemoryUsage` (maximum) |
327
+ | `metricActiveTime()`, `metricCpuTime()`, `metricResponseBytes()` | `EnvironmentActiveTime`, `EnvironmentCPUTime`, `ResponseBytes` |
185
328
 
186
- Any change to code or configuration creates a new **revision**. The endpoint switches to it once it's ready, and the old revision is deleted after the deployment succeeds; a failed deployment rolls back to it. If nothing changed, nothing is deployed.
329
+ - **Metrics stay in the region that served the request**; unlike logs, they aren't copied to the home region. Every helper takes `region`. Dashboards can show any region, but an alarm can only use metrics from its own stack's region.
330
+ - 4XX, 5XX and the throttle metrics only exist once they're non-zero: alarm on them with `treatMissingData: NOT_BREACHING`. The rate helpers count missing errors as 0.
331
+ - There's no latency metric. Publish one from your application (e.g. with the CloudWatch embedded metric format).
187
332
 
188
333
  ## Logs in every region
189
334
 
@@ -219,10 +364,9 @@ new WebFunction(this, 'Api', {
219
364
 
220
365
  ## Limitations
221
366
 
222
- - One endpoint per function; no traffic splitting between revisions.
223
367
  - Not yet supported: importing existing functions, VPC access (network connectors), resource-policy permissions, KMS encryption, metrics helpers, tracing, and a CloudFront origin.
224
368
  - `cdk deploy --hotswap` and `cdk watch` don't support Web Functions.
225
- - Quotas per account and region: 50 revisions and 10 endpoints per function, 200 Arm vCPUs, 10,000 requests per second.
369
+ - Quotas: 10 endpoints and about 50 revisions per function (Lambda cleans up older revisions automatically), and 10,000 requests per second per endpoint. Per account and region: 100,000 requests per second and 2,000 Arm64 vCPUs (each execution environment uses 2). The account quotas are adjustable, and new accounts start lower. See [Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html).
226
370
  - The stack's custom resource provider Lambda keeps its own log group after the stack is deleted, as with other CDK custom resources.
227
371
 
228
372
  ## Development