Skip to content

Commit b4e2fd7

Browse files
committed
Add lambda worker sample
1 parent ce5d8dd commit b4e2fd7

16 files changed

+537
-143
lines changed

.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: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
### 1. Configure Temporal connection
39+
40+
Edit `temporal.toml` with your Temporal Cloud namespace address and credentials. In production,
41+
we'd recommend reading your credentials from a secret store, but to keep this example simple
42+
the toml file defaults to reading them from keys bundled along with the Lambda code.
43+
44+
### 2. Create the IAM role
45+
46+
This creates the IAM role that Temporal Cloud assumes to invoke your Lambda function:
47+
48+
```bash
49+
./mk-iam-role.sh <stack-name> <external-id> <lambda-arn>
50+
```
51+
52+
The External ID is provided by Temporal Cloud in your namespace's serverless worker
53+
configuration.
54+
55+
### 3. (Optional) Enable OpenTelemetry
56+
57+
If you want traces, metrics, and logs, you'll have to attach the ADOT layet to your Lambda function.
58+
You will need to add the appropriate layer for your runtime and region. See [this page
59+
](https://aws-otel.github.io/docs/getting-started/lambda#getting-started-with-aws-lambda-layers)
60+
for more info.
61+
62+
Then run the extra setup to grant the Lambda role the necessary permissions:
63+
64+
```bash
65+
./extra-setup-steps <role-name> <function-name> <region> <account-id>
66+
```
67+
68+
Update `otel-collector-config.yaml` with your function name and region as needed.
69+
70+
### 4. Deploy the Lambda function
71+
72+
```bash
73+
./deploy-lambda.sh <function-name>
74+
```
75+
76+
This installs Python dependencies, bundles them with your code and configuration files,
77+
and uploads to AWS Lambda.
78+
79+
### 5. Start a Workflow
80+
81+
Use the starter program to execute a Workflow on the Lambda worker, using
82+
the same config file the Lambda uses for connecting to the server:
83+
84+
```bash
85+
TEMPORAL_CONFIG_FILE=temporal.toml uv run python lambda_worker/starter.py
86+
```

lambda_worker/__init__.py

Whitespace-only changes.

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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
SDK_DIR="$SCRIPT_DIR/../../sdk-python"
7+
8+
# Install the published temporalio package (Linux wheels) and OTel dependencies
9+
# TODO: Remove explicit OTel deps once lambda-worker-otel extra is published
10+
uv pip install --target "$SCRIPT_DIR/package" --python-platform x86_64-unknown-linux-gnu --no-build \
11+
temporalio \
12+
"opentelemetry-api>=1.11.1,<2" \
13+
"opentelemetry-sdk>=1.11.1,<2" \
14+
"opentelemetry-exporter-otlp-proto-grpc>=1.11.1,<2" \
15+
"opentelemetry-semantic-conventions>=0.40b0,<1" \
16+
"opentelemetry-sdk-extension-aws>=2.0.0,<3"
17+
18+
# Overlay the local SDK's pure-Python source (for unpublished contrib code)
19+
# TODO: Remove this step once the contrib package is published
20+
cp -r "$SDK_DIR/temporalio/contrib" "$SCRIPT_DIR/package/temporalio"
21+
22+
# Copy application code into the package directory (all at zip root)
23+
cp "$SCRIPT_DIR/lambda_function.py" "$SCRIPT_DIR/workflows.py" \
24+
"$SCRIPT_DIR/activities.py" "$SCRIPT_DIR/package/"
25+
26+
# Bundle with configuration files
27+
cd "$SCRIPT_DIR/package"
28+
zip -r "$SCRIPT_DIR/function.zip" .
29+
cd "$SCRIPT_DIR"
30+
zip function.zip client.pem client.key temporal.toml otel-collector-config.yaml
31+
32+
aws lambda update-function-code --function-name "$FUNCTION_NAME" --zip-file fileb://function.zip
33+
34+
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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 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+
LambdaFunctionARN:
14+
Type: String
15+
Description: >-
16+
The ARN of the Lambda function to invoke
17+
(e.g., arn:aws:lambda:us-west-2:123456789012:function:worker-1)
18+
19+
Metadata:
20+
AWS::CloudFormation::Interface:
21+
ParameterGroups:
22+
- Label:
23+
default: "Temporal Cloud Configuration"
24+
Parameters:
25+
- AssumeRoleExternalId
26+
- Label:
27+
default: "Lambda Configuration"
28+
Parameters:
29+
- LambdaFunctionARN
30+
ParameterLabels:
31+
AssumeRoleExternalId:
32+
default: "External ID (provided by Temporal Cloud)"
33+
LambdaFunctionARN:
34+
default: "Lambda Function ARN"
35+
36+
Resources:
37+
TemporalCloudServerlessWorker:
38+
Type: AWS::IAM::Role
39+
Properties:
40+
RoleName: !Sub
41+
- "Temporal-Cloud-Serverless-Worker-${LambdaName}"
42+
- LambdaName: !Select [6, !Split [":", !Ref LambdaFunctionARN]]
43+
AssumeRolePolicyDocument:
44+
Version: "2012-10-17"
45+
Statement:
46+
- Effect: Allow
47+
Principal:
48+
AWS: [arn:aws:iam::031568301006:role/wci-lambda-invoke]
49+
Action: sts:AssumeRole
50+
Condition:
51+
StringEquals:
52+
"sts:ExternalId": [!Ref AssumeRoleExternalId]
53+
Description: "The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers"
54+
MaxSessionDuration: 3600 # 1 hour
55+
56+
TemporalCloudLambdaInvokePermissions:
57+
Type: AWS::IAM::Policy
58+
DependsOn: TemporalCloudServerlessWorker
59+
Properties:
60+
PolicyName: "Temporal-Cloud-Lambda-Invoke-Permissions"
61+
PolicyDocument:
62+
Version: "2012-10-17"
63+
Statement:
64+
- Effect: Allow
65+
Action:
66+
- lambda:InvokeFunction
67+
- lambda:GetFunction
68+
Resource: [!Ref LambdaFunctionARN]
69+
Roles:
70+
- !Ref TemporalCloudServerlessWorker
71+
72+
Outputs:
73+
RoleARN:
74+
Description: The ARN of the IAM role created for Temporal Cloud
75+
Value: !GetAtt TemporalCloudServerlessWorker.Arn
76+
Export:
77+
Name: !Sub "${AWS::StackName}-RoleARN"
78+
79+
RoleName:
80+
Description: The name of the IAM role
81+
Value: !Ref TemporalCloudServerlessWorker
82+
83+
LambdaFunctionARN:
84+
Description: The Lambda function ARN that can be invoked
85+
Value: !Ref LambdaFunctionARN

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=LambdaFunctionARN,ParameterValue="$LAMBDA_ARN" \
17+
--capabilities CAPABILITY_NAMED_IAM

0 commit comments

Comments
 (0)