@ashishpandeyone/cdk-lambda-web-function 0.1.0 → 0.1.2
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/.jsii +1457 -135
- package/.jsii.tabl.json +1 -1
- package/API.md +2349 -238
- package/README.md +133 -7
- package/assets/web-function-deployment/index.js +58 -0
- package/assets/web-function-provider/index.js +23 -23
- package/lib/index.d.ts +3 -0
- package/lib/index.js +4 -1
- package/lib/nodejs-web-function.d.ts +153 -0
- package/lib/nodejs-web-function.js +113 -0
- package/lib/private/bundling.d.ts +68 -0
- package/lib/private/bundling.js +192 -0
- package/lib/private/provider-protocol.d.ts +40 -0
- package/lib/private/provider-protocol.js +9 -2
- package/lib/private/provider.js +2 -2
- package/lib/private/revision-cleanup.d.ts +19 -0
- package/lib/private/revision-cleanup.js +38 -0
- package/lib/private/traffic-shift.d.ts +20 -0
- package/lib/private/traffic-shift.js +143 -0
- package/lib/web-function-deployment.d.ts +89 -0
- package/lib/web-function-deployment.js +90 -0
- package/lib/web-function-endpoint.d.ts +122 -19
- package/lib/web-function-endpoint.js +139 -18
- package/lib/web-function-grants.d.ts +2 -2
- package/lib/web-function-grants.js +4 -4
- package/lib/web-function-revision.d.ts +45 -0
- package/lib/web-function-revision.js +40 -0
- package/lib/web-function.d.ts +60 -25
- package/lib/web-function.js +80 -26
- package/package.json +7 -2
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
|
|
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
|
|
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
|
|---|---|
|
|
@@ -151,15 +179,114 @@ table.grantReadWriteData(api); // the default role trusts lambda.amazo
|
|
|
151
179
|
- Environment variable names starting with `AWS_` are reserved.
|
|
152
180
|
- **Renaming the function replaces it and changes its URL.** A generated name depends on the construct path, so moving the construct renames it too.
|
|
153
181
|
|
|
182
|
+
### Bundling TypeScript with `NodejsWebFunction`
|
|
183
|
+
|
|
184
|
+
`NodejsWebFunction` is a `WebFunction` that bundles your server from a TypeScript or JavaScript entry file with esbuild, like `NodejsFunction` does for Lambda functions:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
new NodejsWebFunction(this, 'Api', {
|
|
188
|
+
entry: 'src/server.ts',
|
|
189
|
+
authType: WebFunctionAuthType.AWS_IAM,
|
|
190
|
+
bundling: {
|
|
191
|
+
minify: true, // default true
|
|
192
|
+
sourceMap: true, // default true
|
|
193
|
+
format: lambdaNodejs.OutputFormat.ESM, // default ESM (index.mjs); CJS gives index.js
|
|
194
|
+
externalModules: [], // default: bundle everything you import
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- **Defaults:** `runtime` is `NODEJS_24_X`, and the esbuild target follows the runtime (`node24`). The project root is the directory of the nearest lock file above `entry` (override it with `depsLockFilePath` or `projectRoot`).
|
|
200
|
+
- **It also sets what a `lambda.Code` can't:** the entry point that matches the bundle, and `NODE_OPTIONS=--enable-source-maps` when source maps are on, appended to any `NODE_OPTIONS` you set, so stack traces point at your `.ts` files (e.g. `/var/task/src/server.ts:22:12`). Source map paths are relative to the project root, so no build-machine paths are deployed.
|
|
201
|
+
- **ESM bundles include a `require` shim**, so CommonJS dependencies that load Node.js built-ins (Express, for example) work.
|
|
202
|
+
- **The AWS SDK is bundled like any other dependency**, since the runtime has none. Only the clients you import are included.
|
|
203
|
+
- **Bundling runs locally** when esbuild is installed in your project, and otherwise in Docker (an arm64 AWS SAM build image; set `forceDockerBundling` to always use Docker).
|
|
204
|
+
- **Not yet supported:** installing native modules next to the bundle (`nodeModules`) and command hooks. For native dependencies, use `WebFunction` with `Code.fromAsset(..., { bundling: { image, command } })`.
|
|
205
|
+
|
|
154
206
|
### Code
|
|
155
207
|
|
|
156
208
|
`code` takes any `lambda.Code` that produces a zip in S3: `fromAsset`, `fromBucket`/`fromBucketV2`, `fromCustomCommand`, `fromDockerBuild`. Inline code and container images fail at synth time.
|
|
157
209
|
|
|
158
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.
|
|
159
211
|
|
|
160
|
-
|
|
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
|
+
// errorRate: a cloudwatch.IAlarm, for example on the share of 5xx responses
|
|
254
|
+
const api = new WebFunction(this, 'Api', {
|
|
255
|
+
runtime: lambda.Runtime.NODEJS_24_X,
|
|
256
|
+
code: lambda.Code.fromAsset('dist'),
|
|
257
|
+
authType: WebFunctionAuthType.AWS_IAM,
|
|
258
|
+
endpoint: {
|
|
259
|
+
// 10% of the traffic for 10 minutes, then all of it.
|
|
260
|
+
deployment: WebFunctionDeployment.canary({ percent: 10, bake: Duration.minutes(10), alarms: [errorRate] }),
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// 25% more every 5 minutes. Each endpoint has its own deployment.
|
|
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
|
+
Lambda Web Function metrics have no revision dimension, so an alarm sees the whole endpoint: at 10%, a new revision's errors are a tenth of the endpoint's. Alarm on a rate, with a threshold low enough to notice that, or publish metrics from your application that carry its version. A bake should be longer than an alarm needs to fire (its period times its evaluation periods).
|
|
161
288
|
|
|
162
|
-
|
|
289
|
+
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.
|
|
163
290
|
|
|
164
291
|
## Logs in every region
|
|
165
292
|
|
|
@@ -195,10 +322,9 @@ new WebFunction(this, 'Api', {
|
|
|
195
322
|
|
|
196
323
|
## Limitations
|
|
197
324
|
|
|
198
|
-
- One endpoint per function; no traffic splitting between revisions.
|
|
199
325
|
- Not yet supported: importing existing functions, VPC access (network connectors), resource-policy permissions, KMS encryption, metrics helpers, tracing, and a CloudFront origin.
|
|
200
326
|
- `cdk deploy --hotswap` and `cdk watch` don't support Web Functions.
|
|
201
|
-
- Quotas
|
|
327
|
+
- 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).
|
|
202
328
|
- The stack's custom resource provider Lambda keeps its own log group after the stack is deleted, as with other CDK custom resources.
|
|
203
329
|
|
|
204
330
|
## Development
|