Skip to content

Commit 7198733

Browse files
committed
Address AWS Lambda sample review feedback
1 parent 317ab5e commit 7198733

7 files changed

Lines changed: 144 additions & 53 deletions

File tree

lambda-worker/README.md

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,20 @@ It uses the same published Temporal Java SDK version as the other samples in thi
1515
- Java 17+
1616
- AWS CLI configured with permissions to create Lambda functions, IAM roles, and
1717
CloudFormation stacks
18-
- A Temporal Cloud namespace with Serverless Workers enabled, or a [self-hosted Temporal Service](https://docs.temporal.io/production-deployment/worker-deployments/serverless-workers/self-hosted-setup)
19-
configured for AWS Lambda Serverless Workers
20-
- A Temporal Cloud API key (if using Temporal Cloud). This walkthrough deploys it as a Lambda environment variable
21-
because these are development-only secrets.
18+
- An AWS-hosted Temporal Cloud namespace with Serverless Workers enabled, or a
19+
[self-hosted Temporal Service](https://docs.temporal.io/production-deployment/worker-deployments/serverless-workers/self-hosted-setup)
20+
version 1.31.0 or later with the AWS Lambda Worker Controller setup completed
21+
- A Temporal Cloud API key (if using Temporal Cloud). This walkthrough deploys it as a
22+
Lambda environment variable because these are development-only secrets.
2223

2324
## Layout
2425

2526
- `worker/` contains the Lambda handler, Workflow, Activity, and deployable Worker project.
2627
- `starter/` contains the local Workflow starter project.
2728
- `deploy/` contains the AWS deployment scripts and CloudFormation template.
28-
- `temporal.template.toml` and `otel-collector-config.template.yaml` are configuration
29-
templates for local and Lambda setup.
29+
- `temporal.template.toml` is a Temporal connection configuration template.
30+
- `otel-collector-config.template.yaml` configures the ADOT collector packaged with the
31+
Lambda Worker.
3032

3133
## Build
3234

@@ -35,6 +37,9 @@ It uses the same published Temporal Java SDK version as the other samples in thi
3537
./gradlew :lambda-worker:worker:shadowJar
3638
```
3739

40+
The `shadowJar` task packages `otel-collector-config.template.yaml` at the root of the Lambda
41+
artifact as `otel-collector-config.yaml`.
42+
3843
The Lambda handler string is:
3944

4045
```text
@@ -44,12 +49,15 @@ io.temporal.samples.lambdaworker.LambdaFunction::handleRequest
4449
## Configure Environment
4550

4651
Set AWS, Temporal, and sample names first. Use unique values if you share the account or
47-
namespace with other developers.
52+
namespace with other developers. The connection values below are for Temporal Cloud. For a
53+
self-hosted Service, use its frontend address, Namespace, and TLS or authentication settings;
54+
leave `TEMPORAL_API_KEY` unset if the Service does not require one.
4855

4956
```bash
5057
export AWS_PROFILE=<aws-profile>
5158
export AWS_REGION=us-west-2
5259
export AWS_DEFAULT_REGION="$AWS_REGION"
60+
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
5361

5462
export TEMPORAL_ADDRESS=<your-namespace>.<account>.tmprl.cloud:7233
5563
export TEMPORAL_NAMESPACE=<your-namespace>.<account>
@@ -78,6 +86,12 @@ TEMPORAL_LAMBDA_DEPLOYMENT_NAME
7886
TEMPORAL_LAMBDA_BUILD_ID
7987
```
8088

89+
The ADOT collector extension reads
90+
`OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml`. The Java Lambda Worker
91+
uses `OtelLambdaWorkerConfigurationHelper` to send Temporal traces and metrics to the collector
92+
over OTLP. The collector exports traces to AWS X-Ray and metrics to the
93+
`TemporalWorkerMetrics` CloudWatch namespace.
94+
8195
The local starter also reads `TEMPORAL_TASK_QUEUE` and
8296
`TEMPORAL_LAMBDA_WORKFLOW_ID_PREFIX`.
8397

@@ -140,40 +154,53 @@ aws lambda create-function \
140154
--handler io.temporal.samples.lambdaworker.LambdaFunction::handleRequest \
141155
--role "$EXECUTION_ROLE_ARN" \
142156
--zip-file fileb://lambda-worker/worker/build/libs/lambda-worker-1.0.0-all.jar \
143-
--environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_LAMBDA_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_LAMBDA_BUILD_ID=$BUILD_ID}" \
157+
--environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_LAMBDA_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_LAMBDA_BUILD_ID=$BUILD_ID,OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml}" \
144158
--timeout 90 \
145159
--memory-size 1024 \
146160
--query 'FunctionArn' \
147161
--output text
148162

149163
aws lambda wait function-active --function-name "$FUNCTION_NAME"
150164

151-
export FUNCTION_ARN="$(
165+
./lambda-worker/deploy/enable-telemetry.sh \
166+
"$EXECUTION_ROLE_NAME" \
167+
"$FUNCTION_NAME" \
168+
"$AWS_REGION" \
169+
"$AWS_ACCOUNT_ID"
170+
171+
aws lambda wait function-updated --function-name "$FUNCTION_NAME"
172+
173+
export FUNCTION_BASE_ARN="$(
152174
aws lambda get-function \
153175
--function-name "$FUNCTION_NAME" \
154176
--query 'Configuration.FunctionArn' \
155177
--output text
156178
)"
157-
```
158-
159-
To update code after the function exists:
160179

161-
```bash
162-
./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME"
180+
export FUNCTION_VERSION_ARN="$(
181+
aws lambda publish-version \
182+
--function-name "$FUNCTION_NAME" \
183+
--description "Build ID $BUILD_ID" \
184+
--query 'FunctionArn' \
185+
--output text
186+
)"
163187
```
164188

165-
If direct upload is too large, set `LAMBDA_CODE_S3_BUCKET` and rerun:
166-
167-
```bash
168-
LAMBDA_CODE_S3_BUCKET=<code-bucket> ./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME"
169-
```
189+
`enable-telemetry.sh` grants the execution role permission to send traces and EMF logs, enables
190+
active tracing, and attaches AWS's collector-only ADOT Lambda layer. Its default layer ARN is for
191+
the sample's `x86_64` architecture in standard AWS regions. Set `ADOT_COLLECTOR_LAYER_ARN` before
192+
running the script to use another compatible regional layer.
170193

171194
## Configure Invocation
172195

173-
Create the IAM role that Temporal Cloud assumes to invoke the Lambda:
196+
For Temporal Cloud, create the IAM role that Temporal Cloud assumes to invoke the Lambda. The
197+
wildcard suffix authorizes invocation of every immutable version published for this function:
174198

175199
```bash
176-
./lambda-worker/deploy/mk-iam-role.sh "$STACK_NAME" "$EXTERNAL_ID" "$FUNCTION_ARN"
200+
./lambda-worker/deploy/mk-iam-role.sh \
201+
"$STACK_NAME" \
202+
"$EXTERNAL_ID" \
203+
"${FUNCTION_BASE_ARN}:*"
177204

178205
aws cloudformation wait stack-create-complete --stack-name "$STACK_NAME"
179206

@@ -185,15 +212,23 @@ export INVOCATION_ROLE_ARN="$(
185212
)"
186213
```
187214

188-
Create and route the Worker Deployment Version:
215+
The included CloudFormation template trusts Temporal Cloud's AWS identities and must not be
216+
used for a self-hosted Temporal Service. For self-hosted deployments, complete the
217+
[self-hosted Serverless Workers setup](https://docs.temporal.io/production-deployment/worker-deployments/serverless-workers/self-hosted-setup),
218+
then set `INVOCATION_ROLE_ARN` to the role created by that process.
219+
220+
## Create Worker Deployment Version
221+
222+
Create and route the Worker Deployment Version. The Temporal CLI can connect to either Temporal
223+
Cloud or a self-hosted Service using the connection configuration above.
189224

190225
```bash
191226
temporal worker deployment create --name "$DEPLOYMENT_NAME"
192227

193228
temporal worker deployment create-version \
194229
--deployment-name "$DEPLOYMENT_NAME" \
195230
--build-id "$BUILD_ID" \
196-
--aws-lambda-function-arn "$FUNCTION_ARN" \
231+
--aws-lambda-function-arn "$FUNCTION_VERSION_ARN" \
197232
--aws-lambda-assume-role-arn "$INVOCATION_ROLE_ARN" \
198233
--aws-lambda-assume-role-external-id "$EXTERNAL_ID"
199234

@@ -208,7 +243,7 @@ An async Lambda smoke test returns immediately and should produce worker startup
208243

209244
```bash
210245
aws lambda invoke \
211-
--function-name "$FUNCTION_NAME" \
246+
--function-name "$FUNCTION_VERSION_ARN" \
212247
--invocation-type Event \
213248
--cli-binary-format raw-in-base64-out \
214249
--payload '{}' \
@@ -221,6 +256,47 @@ A synchronous invoke can run until the Lambda worker exits near the function tim
221256
you want to wait for that path, set the AWS CLI read timeout higher than the function
222257
timeout.
223258

259+
## Deploy an Updated Version
260+
261+
Each Temporal Build ID should point to an immutable Lambda function version. To deploy an
262+
update, choose a new Build ID, update the Lambda environment, upload the new code, and publish a
263+
new Lambda version:
264+
265+
```bash
266+
export BUILD_ID=build-2
267+
268+
aws lambda update-function-configuration \
269+
--function-name "$FUNCTION_NAME" \
270+
--environment "Variables={TEMPORAL_ADDRESS=$TEMPORAL_ADDRESS,TEMPORAL_NAMESPACE=$TEMPORAL_NAMESPACE,TEMPORAL_API_KEY=$TEMPORAL_API_KEY,TEMPORAL_TASK_QUEUE=$TASK_QUEUE,TEMPORAL_LAMBDA_DEPLOYMENT_NAME=$DEPLOYMENT_NAME,TEMPORAL_LAMBDA_BUILD_ID=$BUILD_ID,OPENTELEMETRY_COLLECTOR_CONFIG_URI=/var/task/otel-collector-config.yaml}" \
271+
--query 'FunctionArn' \
272+
--output text
273+
274+
aws lambda wait function-updated --function-name "$FUNCTION_NAME"
275+
276+
./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME"
277+
278+
aws lambda wait function-updated --function-name "$FUNCTION_NAME"
279+
280+
export FUNCTION_VERSION_ARN="$(
281+
aws lambda publish-version \
282+
--function-name "$FUNCTION_NAME" \
283+
--description "Build ID $BUILD_ID" \
284+
--query 'FunctionArn' \
285+
--output text
286+
)"
287+
```
288+
289+
If direct upload is too large, set `LAMBDA_CODE_S3_BUCKET` when running `deploy-lambda.sh`:
290+
291+
```bash
292+
LAMBDA_CODE_S3_BUCKET=<code-bucket> ./lambda-worker/deploy/deploy-lambda.sh "$FUNCTION_NAME"
293+
```
294+
295+
Create the new Worker Deployment Version and make it current using the commands in
296+
[Create Worker Deployment Version](#create-worker-deployment-version), omitting the
297+
`temporal worker deployment create` command because the deployment already exists. Existing
298+
Worker Deployment Versions continue to reference their original Lambda versions.
299+
224300
## Start Workflow
225301

226302
After the Worker Deployment Version is current, start the sample Workflow:
@@ -236,6 +312,26 @@ The starter only creates a Workflow Execution. It does not start a local Worker.
236312
important value is `TEMPORAL_TASK_QUEUE`; it must match the task queue configured on the
237313
Lambda function.
238314

315+
## Verify OpenTelemetry
316+
317+
After invoking the Lambda or completing a Workflow, inspect the function logs for ADOT collector
318+
startup and export messages:
319+
320+
```bash
321+
aws logs tail "/aws/lambda/$FUNCTION_NAME" --since 10m
322+
```
323+
324+
Temporal SDK metrics should also appear in the `TemporalWorkerMetrics` CloudWatch namespace:
325+
326+
```bash
327+
aws cloudwatch list-metrics \
328+
--namespace TemporalWorkerMetrics \
329+
--query 'Metrics[].MetricName' \
330+
--output text
331+
```
332+
333+
Temporal tracing spans are exported to AWS X-Ray and can be inspected in the X-Ray trace view.
334+
239335
## Local SDK Development
240336

241337
For local development of the Workflow and Activity logic, run the unit tests. They use

lambda-worker/deploy/enable-telemetry.sh

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ ROLE_NAME="${1:?Usage: enable-telemetry.sh <role-name> <function-name> <region>
55
FUNCTION_NAME="${2:?Usage: enable-telemetry.sh <role-name> <function-name> <region> <account-id>}"
66
REGION="${3:?Usage: enable-telemetry.sh <role-name> <function-name> <region> <account-id>}"
77
ACCOUNT_ID="${4:?Usage: enable-telemetry.sh <role-name> <function-name> <region> <account-id>}"
8+
ADOT_COLLECTOR_LAYER_ARN="${ADOT_COLLECTOR_LAYER_ARN:-arn:aws:lambda:${REGION}:901920570463:layer:aws-otel-collector-amd64-ver-0-117-0:1}"
89

910
aws iam put-role-policy \
1011
--role-name "$ROLE_NAME" \
@@ -28,17 +29,13 @@ aws iam put-role-policy \
2829
\"xray:PutTelemetryRecords\"
2930
],
3031
\"Resource\": \"*\"
31-
},
32-
{
33-
\"Effect\": \"Allow\",
34-
\"Action\": [
35-
\"cloudwatch:PutMetricData\"
36-
],
37-
\"Resource\": \"*\"
3832
}
3933
]
4034
}"
4135

4236
aws lambda update-function-configuration \
4337
--function-name "$FUNCTION_NAME" \
44-
--tracing-config Mode=Active
38+
--layers "$ADOT_COLLECTOR_LAYER_ARN" \
39+
--tracing-config Mode=Active \
40+
--query 'FunctionArn' \
41+
--output text
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#!/bin/bash
22
set -euo pipefail
33

4-
STACK_NAME="${1:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
5-
EXTERNAL_ID="${2:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
6-
LAMBDA_ARN="${3:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
4+
STACK_NAME="${1:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn-pattern>}"
5+
EXTERNAL_ID="${2:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn-pattern>}"
6+
LAMBDA_ARN_PATTERN="${3:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn-pattern>}"
77
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
88

99
aws cloudformation create-stack \
1010
--stack-name "$STACK_NAME" \
11-
--template-body "file://${SCRIPT_DIR}/iam-role-for-temporal-lambda-invoke-test.yaml" \
11+
--template-body "file://${SCRIPT_DIR}/temporal-cloud-lambda-invoke-role.yaml" \
1212
--parameters \
1313
ParameterKey=AssumeRoleExternalId,ParameterValue="$EXTERNAL_ID" \
14-
ParameterKey=LambdaFunctionARNs,ParameterValue="\"$LAMBDA_ARN\"" \
15-
--capabilities CAPABILITY_NAMED_IAM
14+
ParameterKey=LambdaFunctionARNs,ParameterValue="\"$LAMBDA_ARN_PATTERN\"" \
15+
--capabilities CAPABILITY_IAM

lambda-worker/deploy/iam-role-for-temporal-lambda-invoke-test.yaml renamed to lambda-worker/deploy/temporal-cloud-lambda-invoke-role.yaml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# This trust policy is for Temporal Cloud. Self-hosted Temporal Services must trust their
2+
# own AWS identity; see the self-hosted Serverless Workers setup documentation.
13
AWSTemplateFormatVersion: "2010-09-09"
24
Description: Creates an IAM role that Temporal Cloud can assume to invoke Lambda functions for Serverless Workers.
35

@@ -13,10 +15,6 @@ Parameters:
1315
Type: CommaDelimitedList
1416
Description: Comma-separated list of Lambda function ARNs to invoke.
1517

16-
RoleName:
17-
Type: String
18-
Default: "Temporal-Cloud-Serverless-Worker"
19-
2018
Metadata:
2119
AWS::CloudFormation::Interface:
2220
ParameterGroups:
@@ -28,20 +26,15 @@ Metadata:
2826
default: "Lambda Configuration"
2927
Parameters:
3028
- LambdaFunctionARNs
31-
- RoleName
3229
ParameterLabels:
3330
AssumeRoleExternalId:
3431
default: "External ID"
3532
LambdaFunctionARNs:
3633
default: "Lambda Function ARNs"
37-
RoleName:
38-
default: "IAM Role Name"
39-
4034
Resources:
4135
TemporalCloudServerlessWorker:
4236
Type: AWS::IAM::Role
4337
Properties:
44-
RoleName: !Sub "${RoleName}-${AWS::StackName}"
4538
AssumeRolePolicyDocument:
4639
Version: "2012-10-17"
4740
Statement:
@@ -76,7 +69,7 @@ Resources:
7669
- lambda:GetFunction
7770
Resource: !Ref LambdaFunctionARNs
7871
Roles:
79-
- !Sub "${RoleName}-${AWS::StackName}"
72+
- !Ref TemporalCloudServerlessWorker
8073

8174
Outputs:
8275
RoleARN:
@@ -87,7 +80,7 @@ Outputs:
8780

8881
RoleName:
8982
Description: The name of the IAM role.
90-
Value: !Ref RoleName
83+
Value: !Ref TemporalCloudServerlessWorker
9184

9285
LambdaFunctionARNs:
9386
Description: The Lambda function ARNs that can be invoked.

lambda-worker/otel-collector-config.template.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ receivers:
99
exporters:
1010
debug:
1111
awsxray:
12-
region: us-west-2
12+
region: ${env:AWS_REGION}
1313
awsemf:
1414
namespace: TemporalWorkerMetrics
15-
log_group_name: /aws/lambda/<your-function-name>
16-
region: us-west-2
15+
log_group_name: /aws/lambda/${env:AWS_LAMBDA_FUNCTION_NAME}
16+
region: ${env:AWS_REGION}
1717
dimension_rollup_option: NoDimensionRollup
1818
resource_to_telemetry_conversion:
1919
enabled: true
@@ -28,6 +28,6 @@ service:
2828
exporters: [awsemf]
2929
telemetry:
3030
logs:
31-
level: debug
31+
level: info
3232
metrics:
3333
address: localhost:8888

lambda-worker/worker/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ tasks.register('shadowJar', Jar) {
2424
def mergedServicesDir = layout.buildDirectory.dir('generated/mergedServices')
2525

2626
from sourceSets.main.output
27+
from(file('../otel-collector-config.template.yaml')) {
28+
rename 'otel-collector-config.template.yaml', 'otel-collector-config.yaml'
29+
}
2730
from({
2831
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
2932
}) {

0 commit comments

Comments
 (0)