Skip to content

Commit c0b97bd

Browse files
authored
Add lambda worker sample (#286)
1 parent 601f5d4 commit c0b97bd

16 files changed

Lines changed: 1100 additions & 0 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ __pycache__
44
.vscode
55
.DS_Store
66
.claude
7+
.mypy_cache/
8+
**/client.key
9+
**/client.pem

lambda_worker/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package/
2+
temporal-sdk.toml

lambda_worker/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Lambda Worker
2+
3+
This sample demonstrates how to run a Temporal Worker inside an AWS Lambda function using
4+
the [`lambda_worker`](https://python.temporal.io/temporalio.contrib.aws.lambda_worker.html)
5+
contrib package. It includes optional OpenTelemetry instrumentation that exports traces
6+
and metrics through AWS Distro for OpenTelemetry (ADOT).
7+
8+
The sample registers a simple greeting Workflow and Activity, but the pattern applies to
9+
any Workflow/Activity definitions.
10+
11+
## Prerequisites
12+
13+
- A [Temporal Cloud](https://temporal.io/cloud) namespace (or a self-hosted Temporal
14+
cluster accessible from your Lambda)
15+
- AWS CLI configured with permissions to create Lambda functions, IAM roles, and
16+
CloudFormation stacks
17+
- mTLS client certificate and key for your Temporal namespace (place as `client.pem` and
18+
`client.key` in this directory)
19+
- Python 3.10+
20+
21+
## Files
22+
23+
| File | Description |
24+
|------|-------------|
25+
| `lambda_function.py` | Lambda entry point -- configures the worker, registers Workflows/Activities, and exports the handler |
26+
| `workflows.py` | Sample Workflow that executes a greeting Activity |
27+
| `activities.py` | Sample Activity that returns a greeting string |
28+
| `starter.py` | Helper program to start a Workflow execution from a local machine |
29+
| `temporal.toml` | Temporal client connection configuration (update with your namespace) |
30+
| `otel-collector-config.yaml` | OpenTelemetry Collector sidecar configuration for ADOT |
31+
| `deploy-lambda.sh` | Packages and deploys the Lambda function |
32+
| `mk-iam-role.sh` | Creates the IAM role that allows Temporal Cloud to invoke the Lambda |
33+
| `iam-role-for-temporal-lambda-invoke-test.yaml` | CloudFormation template for the IAM role |
34+
| `extra-setup-steps` | Additional IAM and Lambda configuration for OpenTelemetry support |
35+
36+
## Setup
37+
38+
The instructions here are a slimmed down version of the more complete getting started guide which
39+
you can find [here](https://docs.temporal.io/production-deployment/worker-deployments/serverless-workers/aws-lambda).
40+
41+
### 1. Create a lambda function for your Python worker
42+
43+
Use either the AWS web UI or CLI to create a Python runtime Lambda function. Ex:
44+
45+
```bash
46+
aws lambda create-function \
47+
--function-name my-temporal-worker \
48+
--runtime python3.13 \
49+
--handler lambda_function.lambda_handler \
50+
--role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/my-temporal-worker-execution \
51+
--timeout 600 \
52+
--memory-size 256
53+
```
54+
55+
### 2. Configure Temporal connection
56+
57+
Edit `temporal.toml` with your Temporal Cloud namespace address and credentials. In production,
58+
we'd recommend reading your credentials from a secret store, but to keep this example simple
59+
the toml file defaults to reading them from keys bundled along with the Lambda code.
60+
61+
### 3. Create the IAM role
62+
63+
This creates the IAM role that Temporal Cloud assumes to invoke your Lambda function:
64+
65+
```bash
66+
./mk-iam-role.sh <stack-name> <external-id> <lambda-arn>
67+
```
68+
69+
The External ID is provided by Temporal Cloud in your namespace's serverless worker
70+
configuration.
71+
72+
### 4. (Optional) Enable OpenTelemetry
73+
74+
If you want traces, metrics, and logs, you'll have to attach the ADOT layet to your Lambda function.
75+
You will need to add the appropriate layer for your runtime and region. See [this page
76+
](https://aws-otel.github.io/docs/getting-started/lambda#getting-started-with-aws-lambda-layers)
77+
for more info.
78+
79+
Then run the extra setup to grant the Lambda role the necessary permissions:
80+
81+
```bash
82+
./extra-setup-steps <role-name> <function-name> <region> <account-id>
83+
```
84+
85+
Update `otel-collector-config.yaml` with your function name and region as needed.
86+
87+
### 5. Deploy the Lambda function
88+
89+
```bash
90+
./deploy-lambda.sh <function-name>
91+
```
92+
93+
This installs Python dependencies, bundles them with your code and configuration files,
94+
and uploads to AWS Lambda.
95+
96+
### 6. Configure Temporal to be able to invoke your lambda function
97+
98+
Refer to the docs [here](https://docs.temporal.io/production-deployment/worker-deployments/serverless-workers/aws-lambda#create-worker-deployment-version).
99+
100+
### 7. Start a Workflow
101+
102+
Use the starter program to execute a Workflow on the Lambda worker, using
103+
the same config file the Lambda uses for connecting to the server:
104+
105+
From inside this directory:
106+
107+
```bash
108+
TEMPORAL_CONFIG_FILE=temporal.toml uv run python starter.py
109+
```

lambda_worker/activities.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from temporalio import activity
2+
3+
4+
@activity.defn
5+
async def hello_activity(name: str) -> str:
6+
activity.logger.info("HelloActivity started with name: %s", name)
7+
return f"Hello, {name}!"

lambda_worker/deploy-lambda.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
FUNCTION_NAME="${1:?Usage: deploy-lambda.sh <function-name>}"
5+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
6+
7+
# Install the published temporalio package (Linux wheels) with the OTel extras
8+
# needed by the lambda_worker contrib module
9+
uv pip install --target "$SCRIPT_DIR/package" --python-platform x86_64-unknown-linux-gnu \
10+
--only-binary=:all: "temporalio[lambda-worker-otel]"
11+
12+
# Copy application code into the package directory (all at zip root)
13+
cp "$SCRIPT_DIR/lambda_function.py" "$SCRIPT_DIR/workflows.py" \
14+
"$SCRIPT_DIR/activities.py" "$SCRIPT_DIR/package/"
15+
16+
# Bundle with configuration files
17+
cd "$SCRIPT_DIR/package"
18+
zip -r "$SCRIPT_DIR/function.zip" .
19+
cd "$SCRIPT_DIR"
20+
zip function.zip client.pem client.key temporal.toml otel-collector-config.yaml
21+
22+
aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file fileb://function.zip
23+
24+
rm -rf "$SCRIPT_DIR/package" "$SCRIPT_DIR/function.zip"

lambda_worker/extra-setup-steps

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Additional setup steps for OpenTelemetry support.
5+
# These are needed if you want metrics, logs, and traces from your Lambda worker.
6+
7+
ROLE_NAME="${1:?Usage: extra-setup-steps <role-name> <function-name> <region> <account-id>}"
8+
FUNCTION_NAME="${2:?Usage: extra-setup-steps <role-name> <function-name> <region> <account-id>}"
9+
REGION="${3:?Usage: extra-setup-steps <role-name> <function-name> <region> <account-id>}"
10+
ACCOUNT_ID="${4:?Usage: extra-setup-steps <role-name> <function-name> <region> <account-id>}"
11+
12+
# Needed to allow metrics/logs/traces to be published
13+
aws iam put-role-policy \
14+
--role-name "$ROLE_NAME" \
15+
--policy-name ADOT-Telemetry-Permissions \
16+
--policy-document "{
17+
\"Version\": \"2012-10-17\",
18+
\"Statement\": [
19+
{
20+
\"Effect\": \"Allow\",
21+
\"Action\": [
22+
\"logs:CreateLogGroup\",
23+
\"logs:CreateLogStream\",
24+
\"logs:PutLogEvents\"
25+
],
26+
\"Resource\": \"arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/lambda/${FUNCTION_NAME}:*\"
27+
},
28+
{
29+
\"Effect\": \"Allow\",
30+
\"Action\": [
31+
\"xray:PutTraceSegments\",
32+
\"xray:PutTelemetryRecords\"
33+
],
34+
\"Resource\": \"*\"
35+
},
36+
{
37+
\"Effect\": \"Allow\",
38+
\"Action\": [
39+
\"cloudwatch:PutMetricData\"
40+
],
41+
\"Resource\": \"*\"
42+
}
43+
]
44+
}"
45+
46+
# Needed to make traces show up with type: `"AWS::Lambda::Function"` filter
47+
aws lambda update-function-configuration \
48+
--function-name "$FUNCTION_NAME" --tracing-config Mode=Active
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke Lambda functions.
2+
AWSTemplateFormatVersion: "2010-09-09"
3+
Description: Creates an IAM role that Temporal Cloud can assume to invoke multiple Lambda functions for Serverless Workers.
4+
5+
Parameters:
6+
AssumeRoleExternalId:
7+
Type: String
8+
Description: The External ID provided by Temporal Cloud
9+
AllowedPattern: "[a-zA-Z0-9_+=,.@-]*"
10+
MinLength: 5
11+
MaxLength: 45
12+
13+
LambdaFunctionARNs:
14+
Type: CommaDelimitedList
15+
Description: >-
16+
Comma-separated list of Lambda function ARNs to invoke (e.g.,
17+
arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2)
18+
19+
RoleName:
20+
Type: String
21+
Default: "Temporal-Cloud-Serverless-Worker"
22+
23+
Metadata:
24+
AWS::CloudFormation::Interface:
25+
ParameterGroups:
26+
- Label:
27+
default: "Temporal Cloud Configuration"
28+
Parameters:
29+
- AssumeRoleExternalId
30+
- Label:
31+
default: "Lambda Configuration"
32+
Parameters:
33+
- LambdaFunctionARNs
34+
- RoleName
35+
ParameterLabels:
36+
AssumeRoleExternalId:
37+
default: "External ID (provided by Temporal Cloud)"
38+
LambdaFunctionARNs:
39+
default: "Lambda Function ARNs (comma-separated list)"
40+
RoleName:
41+
default: "IAM Role Name"
42+
43+
Resources:
44+
TemporalCloudServerlessWorker:
45+
Type: AWS::IAM::Role
46+
Properties:
47+
RoleName: !Sub "${RoleName}-${AWS::StackName}"
48+
AssumeRolePolicyDocument:
49+
Version: "2012-10-17"
50+
Statement:
51+
- Effect: Allow
52+
Principal:
53+
AWS:
54+
[
55+
arn:aws:iam::902542641901:role/wci-lambda-invoke,
56+
arn:aws:iam::160190466495:role/wci-lambda-invoke,
57+
arn:aws:iam::819232936619:role/wci-lambda-invoke,
58+
arn:aws:iam::829909441867:role/wci-lambda-invoke,
59+
arn:aws:iam::354116250941:role/wci-lambda-invoke,
60+
]
61+
Action: sts:AssumeRole
62+
Condition:
63+
StringEquals:
64+
"sts:ExternalId": [!Ref AssumeRoleExternalId]
65+
Description: "The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers"
66+
MaxSessionDuration: 3600 # 1 hour
67+
68+
TemporalCloudLambdaInvokePermissions:
69+
Type: AWS::IAM::Policy
70+
DependsOn: TemporalCloudServerlessWorker
71+
Properties:
72+
PolicyName: "Temporal-Cloud-Lambda-Invoke-Permissions"
73+
PolicyDocument:
74+
Version: "2012-10-17"
75+
Statement:
76+
- Effect: Allow
77+
Action:
78+
- lambda:InvokeFunction
79+
- lambda:GetFunction
80+
Resource: !Ref LambdaFunctionARNs
81+
Roles:
82+
- !Sub "${RoleName}-${AWS::StackName}"
83+
84+
Outputs:
85+
RoleARN:
86+
Description: The ARN of the IAM role created for Temporal Cloud
87+
Value: !GetAtt TemporalCloudServerlessWorker.Arn
88+
Export:
89+
Name: !Sub "${AWS::StackName}-RoleARN"
90+
91+
RoleName:
92+
Description: The name of the IAM role
93+
Value: !Ref RoleName
94+
95+
LambdaFunctionARNs:
96+
Description: The Lambda function ARNs that can be invoked
97+
Value: !Join [", ", !Ref LambdaFunctionARNs]

lambda_worker/lambda_function.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from activities import hello_activity
2+
from temporalio.common import WorkerDeploymentVersion
3+
from temporalio.contrib.aws.lambda_worker import LambdaWorkerConfig, run_worker
4+
from temporalio.contrib.aws.lambda_worker.otel import apply_defaults
5+
from workflows import TASK_QUEUE, SampleWorkflow
6+
7+
8+
def configure(config: LambdaWorkerConfig) -> None:
9+
config.worker_config["task_queue"] = TASK_QUEUE
10+
config.worker_config["workflows"] = [SampleWorkflow]
11+
config.worker_config["activities"] = [hello_activity]
12+
apply_defaults(config)
13+
14+
15+
lambda_handler = run_worker(
16+
WorkerDeploymentVersion(deployment_name="my-app", build_id="build-1"),
17+
configure,
18+
)

lambda_worker/mk-iam-role.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
# Creates the IAM role that allows Temporal Cloud to invoke your Lambda function.
5+
# You can find the External ID in your Temporal Cloud namespace settings.
6+
7+
STACK_NAME="${1:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
8+
EXTERNAL_ID="${2:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
9+
LAMBDA_ARN="${3:?Usage: mk-iam-role.sh <stack-name> <external-id> <lambda-arn>}"
10+
11+
aws cloudformation create-stack \
12+
--stack-name "$STACK_NAME" \
13+
--template-body file://iam-role-for-temporal-lambda-invoke-test.yaml \
14+
--parameters \
15+
ParameterKey=AssumeRoleExternalId,ParameterValue="$EXTERNAL_ID" \
16+
ParameterKey=LambdaFunctionARNs,ParameterValue="$LAMBDA_ARN" \
17+
--capabilities CAPABILITY_NAMED_IAM
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
grpc:
5+
endpoint: "localhost:4317"
6+
http:
7+
endpoint: "localhost:4318"
8+
9+
exporters:
10+
debug:
11+
awsxray:
12+
region: us-west-2
13+
awsemf:
14+
# AWS EMF exporter for metrics
15+
# These are example configurations
16+
namespace: TemporalWorkerMetrics
17+
log_group_name: /aws/lambda/<your-function-name>
18+
region: us-west-2
19+
dimension_rollup_option: NoDimensionRollup
20+
resource_to_telemetry_conversion:
21+
enabled: true
22+
23+
service:
24+
pipelines:
25+
traces:
26+
receivers: [otlp]
27+
exporters: [awsxray, debug]
28+
metrics:
29+
receivers: [otlp]
30+
exporters: [awsemf]
31+
telemetry:
32+
logs:
33+
level: debug
34+
metrics:
35+
address: localhost:8888

0 commit comments

Comments
 (0)