diff --git a/docs/develop/go/index.mdx b/docs/develop/go/index.mdx index 422ddc32f1..51f71b6766 100644 --- a/docs/develop/go/index.mdx +++ b/docs/develop/go/index.mdx @@ -59,9 +59,9 @@ From there, you can dive deeper into any of the Temporal primitives to start bui ## [Workers](/develop/go/workers) -- [Worker processes](/develop/go/workers/run-worker-process) -- [Cloud Worker](/develop/go/workers/cloud-worker) +- [Run a Worker](/develop/go/workers/run-worker-process) - [Sessions](/develop/go/workers/sessions) +- [Serverless Workers](/develop/go/workers/serverless-workers) ## [Temporal Client](/develop/go/client) diff --git a/docs/develop/go/workers/cloud-worker.mdx b/docs/develop/go/workers/cloud-worker.mdx deleted file mode 100644 index a05c06b8e7..0000000000 --- a/docs/develop/go/workers/cloud-worker.mdx +++ /dev/null @@ -1,200 +0,0 @@ ---- -id: cloud-worker -title: Cloud Worker - Go SDK -sidebar_label: Cloud Worker -description: This section explains Cloud Workers with the Go SDK -toc_max_heading_level: 4 -keywords: - - Go SDK -tags: - - Go SDK - - Temporal SDKs ---- - -## How to run a Temporal Cloud Worker {#run-a-temporal-cloud-worker} - -To run a Worker that uses [Temporal Cloud](/cloud), you need to provide additional connection and client options that include the following: - -- An address that includes your [Cloud Namespace Name](/namespaces) and a port number: `..tmprl.cloud:`. -- mTLS CA certificate. -- mTLS private key. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -To run a Worker that talks to Temporal Cloud, you need the following: - -- A compatible mTLS CA certificate and mTLS private key that has been added to your Namespace. - See [certificate requirements](/cloud/certificates#certificate-requirements). -- Your [Temporal Cloud Namespace Id](/cloud/namespaces#temporal-cloud-namespace-id), which includes your [Temporal Cloud Namespace Name](/cloud/namespaces#temporal-cloud-namespace-name) and the unique five- or six-digit [Temporal Cloud Account Id](/cloud/namespaces#temporal-cloud-account-id) that is appended to it. - This information can be found in the URL of your Namespace; for example, `https://cloud.temporal.io/namespaces/yournamespace.a2fx6/`. - Remember that the Namespace Id must include the Account Id: `yournamespace.a2fx6`. - -For more information about managing and generating client certificates for Temporal Cloud, see [How to manage certificates in Temporal Cloud](/cloud/certificates). - -For more information about configuring TLS to secure inter- and intra-network communication for a Temporal Service, see [Temporal Customization Samples](https://github.com/temporalio/samples-server). - -
- - View the source code - {' '} - in the context of the rest of the application code. -
- -```go -package main - -import ( - "crypto/tls" - "log" - - "go.temporal.io/sdk/client" - "go.temporal.io/sdk/worker" - - "documentation-samples-go/cloud" -) - -func main() { - // Get the key and cert from your env or local machine - clientKeyPath := "./secrets/yourkey.key" - clientCertPath := "./secrets/yourcert.pem" - // Specify the host and port of your Temporal Cloud Namespace - // Host and port format: namespace.unique_id.tmprl.cloud:port - hostPort := "..tmprl.cloud:7233" - namespace := "." - // Use the crypto/tls package to create a cert object - cert, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) - if err != nil { - log.Fatalln("Unable to load cert and key pair.", err) - } - // Add the cert to the tls certificates in the ConnectionOptions of the Client - temporalClient, err := client.Dial(client.Options{ - HostPort: hostPort, - Namespace: namespace, - ConnectionOptions: client.ConnectionOptions{ - TLS: &tls.Config{Certificates: []tls.Certificate{cert}}, - }, - }) - if err != nil { - log.Fatalln("Unable to connect to Temporal Cloud.", err) - } - defer temporalClient.Close() - // Create a new Worker. - yourWorker := worker.New(temporalClient, "cloud-connection-example-go-task-queue", worker.Options{}) -// ... -} -``` - -### How to register types {#register-types} - -All Workers listening to the same Task Queue name must be registered to handle the exact same Workflows Types and Activity Types. - -If a Worker polls a Task for a Workflow Type or Activity Type it does not know about, it fails that Task. -However, the failure of the Task does not cause the associated Workflow Execution to fail. - -The `RegisterWorkflow()` and `RegisterActivity()` calls essentially create an in-memory mapping between the Workflow Types and their implementations, inside the Worker process. - -**Registering Activity `structs`** - -Per [Activity Definition](/develop/go/activities/basics#activity-definition) best practices, you might have an Activity struct that has multiple methods and fields. -When you use `RegisterActivity()` for an Activity struct, that Worker has access to all exported methods. - -**Registering multiple Types** - -To register multiple Activity Types and/or Workflow Types with the Worker Entity, just make multiple Activity registration calls, but make sure each Type name is unique: - -```go -w.RegisterActivity(ActivityA) -w.RegisterActivity(ActivityB) -w.RegisterActivity(ActivityC) -w.RegisterWorkflow(WorkflowA) -w.RegisterWorkflow(WorkflowB) -w.RegisterWorkflow(WorkflowC) -``` - -### How to set RegisterWorkflowOptions in Go {#registerworkflowoptions} - -Create an instance of [`RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/workflow#RegisterOptions) from the `go.temporal.io/sdk/workflow` package and pass it to the [`RegisterWorkflowWithOptions`](https://pkg.go.dev/go.temporal.io/sdk/worker#WorkflowRegistry) call when registering the Workflow Type with the Worker. - -- Used to set options for registering a Workflow - -| Field | Required | Type | -| ----------------------------------------------------------------- | -------- | -------- | -| [`Name`](#name) | No | `string` | -| [`DisableAlreadyRegisteredCheck`](#disablealreadyregisteredcheck) | No | `bool` | - -#### Name - -See [How to customize a Workflow Type in Go](/develop/go/workflows/basics#customize-workflow-type) - -#### DisableAlreadyRegisteredCheck - -Disables the check to see if the Workflow Type has already been registered. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := workflow.RegisterOptions{ - DisableAlreadyRegisteredCheck: `false`, - // ... -} -w.RegisterWorkflowWithOptions(YourWorkflowDefinition, registerOptions) -// ... -``` - -### How to set RegisterActivityOptions in Go {#registeractivityoptions} - -Create an instance of [`RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/activity#RegisterOptions) from the `go.temporal.io/sdk/activity` package and pass it to the [`RegisterActivityWithOptions`](https://pkg.go.dev/go.temporal.io/sdk/worker#ActivityRegistry) call when registering the Activity Type with the Worker. - -Options for registering an Activity - -| Field | Required | Type | -| ----------------------------------------------------------------- | -------- | -------- | -| [`Name`](#name) | No | `string` | -| [`DisableAlreadyRegisteredCheck`](#disablealreadyregisteredcheck) | No | `bool` | -| [`SkipInvalidStructFunctions`](#skipinvalidstructfunctions) | No | `bool` | - -#### Name - -See [How to customize Activity Type in Go](/develop/go/activities/basics#customize-activity-type). - -#### DisableAlreadyRegisteredCheck - -Disables the check to see if the Activity has already been registered. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := activity.RegisterOptions{ - DisableAlreadyRegisteredCheck: false, - // ... -} -w.RegisterActivityWithOptions(a.YourActivityDefinition, registerOptions) -// ... -``` - -#### SkipInvalidStructFunctions - -When registering a struct that has Activities, skip functions that are not valid. -If false, registration panics. - -- Type: `bool` -- Default: `false` - -```go -// ... -w := worker.New(temporalClient, "your_task_queue_name", worker.Options{}) -registerOptions := activity.RegisterOptions{ - SkipInvalidStructFunctions: false, - // ... -} -w.RegisterActivityWithOptions(a.YourActivityDefinition, registerOptions) -// ... -``` diff --git a/docs/develop/go/workers/index.mdx b/docs/develop/go/workers/index.mdx index 762280f3e8..06555948ac 100644 --- a/docs/develop/go/workers/index.mdx +++ b/docs/develop/go/workers/index.mdx @@ -17,6 +17,6 @@ import * as Components from '@site/src/components'; ## Workers -- [Worker processes](/develop/go/workers/run-worker-process) -- [Cloud Worker](/develop/go/workers/cloud-worker) +- [Run a Worker](/develop/go/workers/run-worker-process) - [Sessions](/develop/go/workers/sessions) +- [Serverless Workers](/develop/go/workers/serverless-workers) diff --git a/docs/develop/go/workers/run-worker-process.mdx b/docs/develop/go/workers/run-worker-process.mdx index 0ab03b28ab..b3ce5da1bd 100644 --- a/docs/develop/go/workers/run-worker-process.mdx +++ b/docs/develop/go/workers/run-worker-process.mdx @@ -1,8 +1,8 @@ --- id: run-worker-process -title: Run Worker processes - Go SDK -description: Shows how to run Worker processes with the Go SDK -sidebar_label: Worker processes +title: Run a Worker - Go SDK +description: Create and run a Temporal Worker using the Go SDK. +sidebar_label: Run a Worker slug: /develop/go/workers/run-worker-process toc_max_heading_level: 3 tags: @@ -11,37 +11,18 @@ tags: - Worker --- -## How to develop a Worker in Go {#develop-worker} +This page covers long-lived Workers that you host and run as persistent processes. +For Workers that run on serverless compute like AWS Lambda, see [Serverless Workers](/develop/go/workers/serverless-workers). -Create an instance of [`Worker`](https://pkg.go.dev/go.temporal.io/sdk/worker#Worker) by calling [`worker.New()`](https://pkg.go.dev/go.temporal.io/sdk/worker#New), available through the `go.temporal.io/sdk/worker` package, and pass it the following parameters: +## Create and run a Worker {#develop-worker} -1. An instance of the Temporal Go SDK `Client`. -1. The name of the Task Queue that it will poll. -1. An instance of `worker.Options`, which can be empty. +Create a [`Worker`](https://pkg.go.dev/go.temporal.io/sdk/worker#Worker) by calling [`worker.New()`](https://pkg.go.dev/go.temporal.io/sdk/worker#New) and passing: -Then, register the Workflow Types and the Activity Types that the Worker will be capable of executing. +1. A Temporal Client. +2. The name of the Task Queue to poll. +3. A [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) struct (can be empty for defaults). -Lastly, call either the `Start()` or the `Run()` method on the instance of the Worker. -Run accepts an interrupt channel as a parameter, so that the Worker can be stopped in the terminal. -Otherwise, the `Stop()` method must be called to stop the Worker. - -:::tip - -If you have [`gow`](https://github.com/mitranim/gow) installed, the Worker Process automatically "reloads" when you update the Worker file: - -```bash -go install github.com/mitranim/gow@latest -gow run worker/main.go # automatically reloads when file changes -``` - -::: - -
- - View the source code - {' '} - in the context of the rest of the application code. -
+Register your Workflow and Activity types, then call `Run()` to start polling. ```go package main @@ -49,475 +30,67 @@ package main import ( "log" - "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" - "go.temporal.io/sdk/workflow" - - "documentation-samples-go/yourapp" ) func main() { - // Create a Temporal Client - // A Temporal Client is a heavyweight object that should be created just once per process. - temporalClient, err := client.Dial(client.Options{}) + c, err := client.Dial(client.Options{}) if err != nil { log.Fatalln("Unable to create client", err) } - defer temporalClient.Close() - // Create a new Worker. - yourWorker := worker.New(temporalClient, "your-custom-task-queue-name", worker.Options{}) - // Register your Workflow Definitions with the Worker. - // Use the RegisterWorkflow or RegisterWorkflowWithOptions method for each Workflow registration. - yourWorker.RegisterWorkflow(yourapp.YourWorkflowDefinition) -// ... - // Register your Activity Definitions with the Worker. - // Use this technique for registering all Activities that are part of a struct and set the shared variable values. - message := "This could be a connection string or endpoint details" - number := 100 - activities := &yourapp.YourActivityObject{ - Message: &message, - Number: &number, - } - // Use the RegisterActivity or RegisterActivityWithOptions method for each Activity. - yourWorker.RegisterActivity(activities) -// ... - // Run the Worker - err = yourWorker.Run(worker.InterruptCh()) + defer c.Close() + + w := worker.New(c, "my-task-queue", worker.Options{}) + w.RegisterWorkflow(MyWorkflow) + w.RegisterActivity(MyActivity) + + err = w.Run(worker.InterruptCh()) if err != nil { log.Fatalln("Unable to start Worker", err) } } -// ... -``` - -### How to set WorkerOptions in Go {#workeroptions} - -Create an instance of [`Options`](https://pkg.go.dev/go.temporal.io/sdk/worker#Options) from the `go.temporal.io/sdk/worker` package, set any of the optional fields, and pass the instance to the [`New`](https://pkg.go.dev/go.temporal.io/sdk/worker#New) call. - -| Field | Required | Type | -| ------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- | -| [`MaxConcurrentActivityExecutionSize`](#maxconcurrentactivityexecutionsize) | No | `int` | -| [`WorkerActivitiesPerSecond`](#workeractivitiespersecond) | No | `float64` | -| [`MaxConcurrentLocalActivityExecutionSize`](#maxconcurrentlocalactivityexecutionsize) | No | `int` | -| [`WorkerLocalActivitiesPerSecond`](#workerlocalactivitiespersecond) | No | `float64` | -| [`TaskQueueActivitiesPerSecond`](#taskqueueactivitiespersecond) | No | `float64` | -| [`MaxConcurrentActivityTaskPollers`](#maxconcurrentactivitytaskpollers) | No | `int` | -| [`MaxConcurrentWorkflowTaskExecutionSize`](#maxconcurrentworkflowtaskexecutionsize) | No | `int` | -| [`MaxConcurrentWorkflowTaskPollers`](#maxconcurrentworkflowtaskpollers) | No | `int` | -| [`EnableLoggingInReplay`](#enablelogginginreplay) | No | `bool` | -| [`DisableStickyExecution`](#disablestickyexecution) | No | `bool` | -| [`StickyScheduleToStartTimeout`](#stickyscheduletostarttimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | -| [`BackgroundActivityContext`](#backgroundactivitycontext) | No | [`context.Context`](https://pkg.go.dev/context#Context) | -| [`WorkflowPanicPolicy`](#workflowpanicpolicy) | No | [`WorkflowPanicPolicy`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowPanicPolicy) | -| [`WorkerStopTimeout`](#workerstoptimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | -| [`EnableSessionWorker`](#enablesessionworker) | No | `bool` | -| [`MaxConcurrentSessionExecutionSize`](#maxconcurrentsessionexecutionsize) | No | `int` | -| [`WorkflowInterceptorChainFactories`](#workflowinterceptorchainfactories) | No | [`[]WorkflowInterceptor`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowInterceptor) | -| [`LocalActivityWorkerOnly`](#localactivityworkeronly) | No | `bool` | -| [`Identity`](#identity) | No | `string` | -| [`DeadlockDetectionTimeout`](#deadlockdetectiontimeout) | No | [`time.Duration`](https://pkg.go.dev/time#Duration) | - -#### MaxConcurrentActivityExecutionSize - -Sets the maximum concurrent Activity Executions for the Worker. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentActivityExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkerActivitiesPerSecond - -Rate limits the number of Activity Task Executions started per second for the Worker. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default. - -Intended use case is to limit resources used by the Worker. - -Notice that the value type is a float so that the value can be less than 1 if needed. -For example, if set to 0.1, Activity Task Executions will happen once every ten seconds. -This can be used to protect downstream services from flooding with requests. - -```go -// ... -workerOptions := worker.Options{ - WorkerActivitiesPerSecond: 100000, - // .. -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentLocalActivityExecutionSize - -Set the maximum concurrent [Local Activity Executions](/local-activity) for the Worker. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default value. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentLocalActivityExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkerLocalActivitiesPerSecond - -Rate limits the number of Local Activity Executions per second executed for the Worker. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default value. - -Intended use case is to limit resources used by the Worker. - -Notice that the value type is a float so that the value can be less than 1 if needed. -For example, if set to 0.1, Local Activity Task Executions will happen once every ten seconds. -This can be used to protect downstream services from flooding with requests. - -```go -// ... -workerOptions := worker.Options{ - WorkerLocalActivitiesPerSecond: 100000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### TaskQueueActivitiesPerSecond - -Rate limits the number of Activity Executions that can be started per second. - -- Type: `float64` -- Default: `100000` - -A value of `0` sets to the default value. - -This rate is managed by the Temporal Service and limits the Activity Tasks per second for the entire Task Queue. This is in contrast to [`WorkerActivityTasksPerSecond`](#workeractivitiespersecond) controls Activities only per Worker. - -Notice that the number is represented in float, so that you can set it to less than 1 if needed. -For example, set the number to 0.1 means you want your Activity to be executed once for every 10 seconds. -This can be used to protect down stream services from flooding. - -```go -// ... -workerOptions := worker.Options{ - TaskQueueActivitiesPerSecond: 100000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentActivityTaskPollers - -Sets the maximum number of goroutines to concurrently poll the Task Queue for Activity Tasks. - -- Type: `int` -- Default: `2` - -Changing this value will affect the rate at which the Worker is able to consume Activity Tasks from the Task Queue. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentActivityTaskPollers: 2, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentWorkflowTaskExecutionSize - -Sets the maximum number of concurrent Workflow Task Executions the Worker can have. - -- Type: `int` -- Default: `1000` - -A value of `0` sets to the default value. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentWorkflowTaskExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentWorkflowTaskPollers - -Sets the maximum number of goroutines that will concurrently poll the Task Queue for Workflow Tasks. - -- Type: `int` -- Default: `2` - -Changing this value will affect the rate at which the Worker is able to consume Workflow Tasks from the Task Queue. - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentWorkflowTaskPollers: 2, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### EnableLoggingInReplay - -Set to enable logging in Workflow Execution replays. - -- type: `bool` -- Default: `false` - -In Workflow Definitions you can use [`workflow.GetLogger(ctx)`](https://pkg.go.dev/go.temporal.io/sdk/workflow#GetLogger) to write logs. -By default, the logger will skip logging during replays, so you do not see duplicate logs. - -This is only really useful for debugging purposes. - -```go -// ... -workerOptions := worker.Options{ - EnableLoggingInReplay: false, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... ``` -#### DisableStickyExecution - -:::caution Deprecated - -When DisableStickyExecution is `true` it can harm performance. -It will be removed soon. -See [`SetStickyWorkflowCacheSize`](https://pkg.go.dev/go.temporal.io/sdk/worker#SetStickyWorkflowCacheSize) instead. - -::: - -Set to disable Sticky Executions - -- Type: `bool` -- Default: `false` - -Sticky Execution runs Workflow Tasks of a Workflow Execution on same host (could be a different Worker, as long as it is on the same host). -This is an optimization for Workflow Executions. -When sticky execution is enabled, Worker keeps the Workflow state in memory. -New Workflow Task contains the new history events will be dispatched to the same Worker. -If this Worker crashes, the sticky Workflow Task will timeout after `StickyScheduleToStartTimeout`, and Temporal Service will clear the stickiness for that Workflow Execution and automatically reschedule a new Workflow Task that is available for any Worker to pick up and resume the progress. - -```go -// ... -workerOptions := worker.Options{ - StickyScheduleToStartTimeout: time.Second(5), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +`Run()` accepts an interrupt channel so the Worker shuts down on `SIGINT` or `SIGTERM`. +You can also call `Start()` and `Stop()` separately for more control over the lifecycle. -#### StickyScheduleToStartTimeout - -Sets the Sticky Execution Schedule-To-Start Timeout for Workflow Tasks. - -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default value is `5` +:::tip -The resolution is in seconds. +If you have [`gow`](https://github.com/mitranim/gow) installed, the Worker automatically reloads when you update the file: -```go -// ... -workerOptions := worker.Options{ - StickyScheduleToStartTimeout: time.Second(5), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... +```bash +go install github.com/mitranim/gow@latest +gow run worker/main.go ``` -#### BackgroundActivityContext - -:::caution Not recommended - -This method of passing dependencies between Activity Task Executions is not recommended anymore. - -Instead, we recommend using a struct with fields that contain dependencies and develop Activity Definitions as struct methods and then pass all the dependencies on the structure initialization. - -- [How to develop an Activity Definition using the Go SDK](/develop/go/activities/basics#activity-definition) - ::: -- Type: [`context.Context`](https://pkg.go.dev/context#Context) - -Sets the background `context.Context` for all Activity Types registered with the Worker. - -The context can be used to pass external dependencies such as database connections to Activity Task Executions. +## Connect to Temporal Cloud {#connect-to-temporal-cloud} -```go -// ... -ctx := context.WithValue(context.Background(), "your-key", "your-value") -workerOptions := worker.Options{ - BackgroundActivityContext: ctx, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +To run a Worker against Temporal Cloud, configure the client connection with your Namespace address and authentication credentials. +See [Connect to Temporal Cloud](/develop/go/client/temporal-client#connect-to-temporal-cloud) for setup instructions. -#### WorkflowPanicPolicy +## Register Workflows and Activities {#register-types} -Sets how the Workflow Worker handles a non-deterministic Workflow Execution History Event and other panics from Workflow Definition code. +All Workers listening to the same Task Queue must be registered to handle the same Workflow Types and Activity Types. +If a Worker polls a Task for a type it does not know about, the Task fails. The Workflow Execution itself does not fail. -- Type: [`WorkflowPanicPolicy`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowPanicPolicy) -- Default: `BlockWorkflow` +Use `RegisterWorkflow()` and `RegisterActivity()` to register types. +To register an Activity struct with multiple methods, pass the struct. The Worker gets access to all exported methods. ```go -// ... -workerOptions := worker.Options{ - DisableStickyExecution: internal.BlockWorkflow, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... +w.RegisterWorkflow(WorkflowA) +w.RegisterWorkflow(WorkflowB) +w.RegisterActivity(&MyActivities{}) ``` -#### WorkerStopTimeout - -Sets the Worker's graceful stop timeout +To customize the registered name or other options, use `RegisterWorkflowWithOptions()` or `RegisterActivityWithOptions()`. +See [`workflow.RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/workflow#RegisterOptions) and [`activity.RegisterOptions`](https://pkg.go.dev/go.temporal.io/sdk/activity#RegisterOptions). -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default: `0` +## Worker options {#worker-options} -Value resolution is in seconds. +Pass a [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) struct to `worker.New()` to configure concurrency limits, pollers, timeouts, and other Worker behavior. +An empty struct uses defaults that work for most cases. -```go -// ... -workerOptions := worker.Options{ - WorkerStopTimeout: time.Second(0), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### EnableSessionWorker - -Enables Sessions for Activity Workers. - -- Type: `bool` -- Default: `false` - -When `true` the Activity Worker creates a Session to sequentially process Activity Tasks for the given Task Queue. - -```go -// ... -workerOptions := worker.Options{ - EnableSessionWorker: true, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### MaxConcurrentSessionExecutionSize - -Sets the maximum number of concurrent Sessions that the Worker can support. - -- Type: `int` -- Default: 1000 - -```go -// ... -workerOptions := worker.Options{ - MaxConcurrentSessionExecutionSize: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### WorkflowInterceptorChainFactories - -Specifies the factories used to instantiate the Workflow interceptor chain. - -- Type: [`[]WorkflowInterceptor`](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkflowInterceptor) - -The chain is instantiated for each replay of a Workflow Execution. - -#### LocalActivityWorkerOnly - -Sets the Worker to only handle Workflow Tasks and local Activity Tasks. - -- Type: `bool` -- Default: `false` - -```go -// ... -workerOptions := worker.Options{ - LocalActivityWorkerOnly: 1000, - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### Identity - -Sets the Temporal Client-level Identity value, overwriting the existing one. - -- Type: string -- Default: client identity - -```go -// ... -workerOptions := worker.Options{ - Identity: "your_custom_identity", - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` - -#### DeadlockDetectionTimeout - -Sets the maximum time that a Workflow Task can execute for. - -- Type: [`time.Duration`](https://pkg.go.dev/time#Duration) -- Default: 1 - -Resolution is in seconds. - -```go -// ... -workerOptions := worker.Options{ - DeadlockDetectionTimeout: time.Second(1), - // ... -} -w := worker.New(c, "your_task_queue_name", workerOptions) -// ... -``` +For the full list of options and their defaults, see the [Go SDK reference](https://pkg.go.dev/go.temporal.io/sdk@v1.42.0/internal#WorkerOptions). diff --git a/docs/develop/go/workers/serverless-workers/aws-lambda.mdx b/docs/develop/go/workers/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..45cea73bd5 --- /dev/null +++ b/docs/develop/go/workers/serverless-workers/aws-lambda.mdx @@ -0,0 +1,146 @@ +--- +id: aws-lambda +title: Serverless Workers on AWS Lambda - Go SDK +sidebar_label: Serverless Workers on AWS Lambda +description: Write a Temporal Worker that runs on AWS Lambda using the Go SDK lambdaworker package. +slug: /develop/go/workers/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - go sdk + - worker + - serverless worker +tags: + - Workers + - Go SDK + - Serverless + - AWS Lambda +--- + +The `lambdaworker` package lets you run a Temporal Serverless Worker on AWS Lambda. +Deploy your Worker code as a Lambda function, and Temporal Cloud invokes it when Tasks arrive. +Each invocation starts a Worker, polls for Tasks, then gracefully shuts down before a configurable invocation deadline. +You register Workflows and Activities the same way you would with a standard Worker. + +For a full end-to-end deployment guide covering AWS IAM setup, compute configuration, and verification, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Create and run a Worker in Lambda {#create-and-run} + +Use the `RunWorker` function to start a Lambda-based Worker. +Pass a `WorkerDeploymentVersion` and a callback that registers your Workflows and Activities. + +```go +package main + +import ( + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "my-task-queue" + + opts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorAutoUpgrade, + }) + opts.RegisterActivity(MyActivity) + + return nil + }) +} +``` + +The `WorkerDeploymentVersion` is required. +Worker Deployment Versioning is always enabled for Serverless Workers. +Each Workflow must declare a [versioning behavior](/worker-versioning#versioning-behaviors) at registration time, either `AutoUpgrade` or `Pinned`. + +The `Options` callback gives you access to the same registration methods you use with a traditional Worker: `RegisterWorkflow`, `RegisterWorkflowWithOptions`, `RegisterActivity`, `RegisterActivityWithOptions`, and `RegisterNexusService`. + +## Configure the Temporal connection {#configure-connection} + +The `lambdaworker` package automatically loads Temporal client configuration from a TOML config file and environment variables. Refer to [Environment Configuration](/develop/environment-configuration) for more details. + +Encrypt sensitive values like TLS keys or API keys. Refer to [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +## Adjust Worker defaults for Lambda {#lambda-tuned-defaults} + +The `lambdaworker` package applies conservative defaults suited to short-lived Lambda invocations. +These differ from standard Worker defaults to avoid overcommitting resources in a constrained environment. +Except for `ShutdownDeadlineBuffer`, these are the same [`worker.Options`](https://pkg.go.dev/go.temporal.io/sdk@v1.42.0/internal#WorkerOptions) available to any Temporal Worker, just with lower values for Lambda's constrained environment. + +| Setting | Lambda default | +|---|---| +| `MaxConcurrentActivityExecutionSize` | 2 | +| `MaxConcurrentWorkflowTaskExecutionSize` | 10 | +| `MaxConcurrentLocalActivityExecutionSize` | 2 | +| `MaxConcurrentNexusTaskExecutionSize` | 5 | +| `MaxConcurrentActivityTaskPollers` | 1 | +| `MaxConcurrentWorkflowTaskPollers` | 2 | +| `MaxConcurrentNexusTaskPollers` | 1 | +| `WorkerStopTimeout` | 5 seconds | +| `DisableEagerActivities` | Always true | +| Sticky cache size | 100 | +| `ShutdownDeadlineBuffer` | 7 seconds | + +`DisableEagerActivities` is always true and cannot be overridden. +Eager Activities require a persistent connection, which Lambda invocations don't maintain. + +`ShutdownDeadlineBuffer` is specific to the `lambdaworker` package. +It controls how much time before the Lambda deadline the Worker begins its graceful shutdown. +The default is `WorkerStopTimeout` + 2 seconds. + +If your Worker handles long-running Activities, increase `WorkerStopTimeout`, `ShutdownDeadlineBuffer`, and the Lambda invocation deadline (`--timeout`) together. +For guidance on how these values relate, see [Tuning for long-running Activities](/serverless-workers#tuning-for-long-running-activities). + +## Add observability with OpenTelemetry {#add-observability} + +The `lambdaworker/otel` sub-package provides OpenTelemetry integration with defaults configured for the [AWS Distro for OpenTelemetry (ADOT)](https://aws-otel.github.io/docs/getting-started/lambda) Lambda layer. +With this enabled, the Worker emits SDK metrics and distributed traces for Workflow and Activity executions. +The ADOT Lambda layer collects this telemetry and can forward traces to AWS X-Ray and metrics to Amazon CloudWatch. + +The underlying metrics and traces are the same ones the Go SDK emits in any environment. +For general observability concepts and the full list of available metrics, see [Observability - Go SDK](/develop/go/observability) and the [SDK metrics reference](/references/sdk-metrics). + +```go +import ( + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" + otel "go.temporal.io/sdk/contrib/aws/lambdaworker/otel" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "my-task-queue" + + if err := otel.ApplyDefaults(opts, &opts.ClientOptions, otel.Options{}); err != nil { + return err + } + + opts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorAutoUpgrade, + }) + opts.RegisterActivity(MyActivity) + + return nil + }) +} +``` + +`ApplyDefaults` configures both metrics and tracing. +By default, telemetry is sent to `localhost:4317`, which is the ADOT Lambda layer's default collector endpoint. + +To collect this telemetry, attach the [AWS Distro for OpenTelemetry Lambda layer](https://aws-otel.github.io/docs/getting-started/lambda/lambda-go) to your Lambda function. +The layer runs a collector sidecar that receives telemetry on `localhost:4317` and forwards it to your configured backend (e.g., AWS X-Ray, Amazon CloudWatch). + +If you only need metrics or tracing, use `otel.ApplyMetrics` or `otel.ApplyTracing` individually. diff --git a/docs/develop/go/workers/serverless-workers/index.mdx b/docs/develop/go/workers/serverless-workers/index.mdx new file mode 100644 index 0000000000..70ed7e9ec8 --- /dev/null +++ b/docs/develop/go/workers/serverless-workers/index.mdx @@ -0,0 +1,26 @@ +--- +id: index +title: Serverless Workers - Go SDK +sidebar_label: Serverless Workers +description: Write Temporal Workers that run on serverless compute using the Go SDK. +slug: /develop/go/workers/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - go sdk + - worker +tags: + - Workers + - Go SDK + - Serverless +--- + +Serverless Workers run on ephemeral, on-demand compute rather than long-lived processes. +Temporal invokes the Worker when Tasks arrive, and the Worker shuts down when the work is done. + +For a general overview of how Serverless Workers work, see [Serverless Workers](/serverless-workers). +For the end-to-end deployment guide, see [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## Supported providers + +- [**AWS Lambda**](/develop/go/workers/serverless-workers/aws-lambda) - Use the `lambdaworker` package to run a Worker as a Lambda function. Covers setup, configuration, Lambda-tuned defaults, observability, and the invocation lifecycle. diff --git a/docs/develop/worker-performance.mdx b/docs/develop/worker-performance.mdx index 26f605e98b..5d6fd79010 100644 --- a/docs/develop/worker-performance.mdx +++ b/docs/develop/worker-performance.mdx @@ -900,8 +900,8 @@ Then consider increasing the number of pollers by adjusting `maxConcurrentWorkfl If, after adjusting the poller and executors count as specified earlier, you still observe an elevated `schedule_to_start`, underutilized Worker hosts, or high `worker_task_slots_available`, you might want to check the following: -- If server-side rate limiting per Task Queue is set by `WorkerOptions#maxTaskQueueActivitiesPerSecond`, remove the limit or adjust the value up. (See [Go](/develop/go/workers/run-worker-process#taskqueueactivitiespersecond) and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) -- If Worker-side rate limiting per Worker is set by `WorkerOptions#maxWorkerActivitiesPerSecond`, remove the limit. (See [Go](/develop/go/workers/run-worker-process#workeractivitiespersecond), [TypeScript](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxconcurrentactivitytaskexecutions), and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) +- If server-side rate limiting per Task Queue is set by `WorkerOptions#maxTaskQueueActivitiesPerSecond`, remove the limit or adjust the value up. (See [Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) +- If Worker-side rate limiting per Worker is set by `WorkerOptions#maxWorkerActivitiesPerSecond`, remove the limit. (See [Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions), [TypeScript](https://typescript.temporal.io/api/interfaces/worker.WorkerOptions#maxconcurrentactivitytaskexecutions), and [Java](https://www.javadoc.io/doc/io.temporal/temporal-sdk/latest/io/temporal/worker/WorkerOptions.Builder.html).) ## Related reading diff --git a/docs/encyclopedia/workers/serverless-workers.mdx b/docs/encyclopedia/workers/serverless-workers.mdx new file mode 100644 index 0000000000..c15602b48d --- /dev/null +++ b/docs/encyclopedia/workers/serverless-workers.mdx @@ -0,0 +1,230 @@ +--- +id: serverless-workers +title: Serverless Workers +sidebar_label: Serverless Workers +description: + Learn how Serverless Workers work, how Temporal invokes them, and how they differ from traditional long-lived Workers. +slug: /serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - workers + - lambda + - compute provider +tags: + - Workers + - Concepts + - Serverless +--- + +import ThemedImage from '@theme/ThemedImage'; + +This page covers the following: + +- [What is a Serverless Worker?](#serverless-worker) +- [How Serverless invocation works](#how-invocation-works) +- [Autoscaling](#autoscaling) +- [Scaling with long-lived Workers](#scaling-with-long-lived-workers) +- [Worker lifecycle](#worker-lifecycle) +- [Failure handling](#failure-handling) +- [Constraints](#constraints) +- [Compute providers](#compute-providers) + +## What is a Serverless Worker? {#serverless-worker} + +A Serverless Worker is a Temporal Worker that runs on serverless compute instead of a long-lived process. There is no +always-on infrastructure to provision or scale. Temporal invokes the Worker when Tasks arrive on a Task Queue, and the +Worker shuts down when the work is done. + +A Serverless Worker uses the same Temporal SDKs as a traditional long-lived Worker. It registers Workflows and +Activities the same way. The difference is in the lifecycle: instead of the Worker starting and polling continuously, +Temporal invokes the Serverless Worker on demand, the Worker starts, processes available Tasks, and then shuts down. + +Serverless Workers require [Worker Versioning](/worker-versioning). Each Serverless Worker must be associated with a +[Worker Deployment Version](/worker-versioning#deployment-versions) that has a compute provider configured. + +To deploy a Serverless Worker, see +[Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers). + +## How Serverless invocation works {#how-invocation-works} + +With long-lived Workers, you start the Worker process, which connects to Temporal and polls a Task Queue for work. +Temporal does not need to know anything about the Worker's infrastructure. + +With Serverless Workers, Temporal starts the Worker. + +
+ +
+ Temporal's Worker Controller Instance invokes a Serverless Worker when Tasks arrive on a Task Queue with a compute + provider configured. +
+
+ +Temporal's internal Worker Controller Instance (WCI) decides when to start, scale, and stop compute invocations. + +The invocation flow works as follows: + +1. A Task is submitted (for example, `StartWorkflow` or `ScheduleActivity`). +2. The [Matching Service](/temporal-service/temporal-server#matching-service) attempts to route the Task directly to an + available Worker (a sync match). +3. If a Worker is available, the Task is routed to that Worker. +4. If no Worker is available (sync match fails), the Matching Service pushes a signal to the WCI, and the WCI invokes + the configured compute provider (for example, calling AWS Lambda's `InvokeFunction` API). +5. The Serverless Worker starts, creates a Temporal Client, and begins polling the Task Queue. +6. The Worker processes available Tasks until it exits (see [Worker lifecycle](#worker-lifecycle)). + +The WCI also monitors the Task Queue backlog independently. If tasks arrive faster than Workers can process them, the +WCI invokes additional Workers in parallel until the backlog drains or provider concurrency limits are reached. + +Each invocation is independent. The Worker creates a fresh client connection on every invocation. There is no connection +reuse or shared state across invocations. + +## Autoscaling {#autoscaling} + +Temporal automatically scales Serverless Workers based on Task Queue signals. When Tasks arrive and no Worker is +available, Temporal invokes new Workers. When the work is done, Workers exit and scale to zero. + +The WCI uses two signals to decide when to invoke new Workers: + +### Sync match failure {#sync-match-failure} + +When a Task is submitted, the [Matching Service](/temporal-service/temporal-server#matching-service) attempts to route +it directly to an available Worker. If no Worker is available, the sync match fails, and the Matching Service pushes a +signal to the WCI. The WCI then invokes a new Worker. This is the primary scaling path. Because the Matching Service +pushes match failures to the WCI as they happen rather than the WCI polling on a timer, latency stays low and scaling is +responsive. + +### Task Queue backlog {#task-queue-backlog} + +The WCI monitors Task Queue metadata to determine whether pending Tasks exist without enough Workers to process them. If +there is work on the queue and not enough Workers, the WCI invokes additional Workers. + +## Scaling with long-lived Workers {#scaling-with-long-lived-workers} + +Serverless Workers can share a Task Queue with long-lived Workers. +Because Serverless Workers are only invoked on [sync match failure](#sync-match-failure), Serverless Workers only pick up Tasks that no long-lived Worker was available to handle. +In practice, the Serverless Workers act as spillover capacity for the long-lived fleet. + +If you configure Serverless and long-lived Workers on the same Task Queue, do not enable dynamic scaling on the long-lived Workers. +The two groups cannot coordinate their scaling behavior. +If both scale dynamically, the long-lived Workers may scale up to handle the same Tasks that Temporal is simultaneously invoking Serverless Workers for, leading to unnecessary invocations and unpredictable scaling. + +## Worker lifecycle {#worker-lifecycle} + +A single Serverless Worker invocation has three phases: init, work, and shutdown. + +
+ +
+ The shutdown deadline buffer controls when the Worker stops polling, and the Worker stop timeout controls how long + the Worker waits for in-flight Tasks to finish before shutdown hooks run. +
+
+ +During the **init** phase, the Worker initializes and establishes a client connection to Temporal. + +During the **work** phase, the Worker polls the Task Queue and processes Tasks. + +During the **shutdown** phase, the Worker stops polling, waits for in-flight Tasks to finish, and runs any shutdown +hooks (for example, OpenTelemetry telemetry flushes). Shutdown begins before the invocation deadline so the Worker can +exit cleanly before the compute provider forcibly terminates the execution environment. + +### Tuning for long-running Activities + +If your Worker handles long-running Activities, set these three values together: + +- **Worker stop timeout > longest Activity runtime.** Gives in-flight Activities enough time to finish after polling + stops. +- **Shutdown deadline buffer > Worker stop timeout + shutdown hook time.** Ensures the drain and any shutdown hooks + complete before the compute provider terminates the environment. +- **Invocation deadline > longest Activity runtime + shutdown deadline buffer.** Set on the compute provider to give + each invocation enough total runtime. + +For example, if your longest Activity runtime is 5 minutes, and your shutdown hooks take 3 seconds to run, set the +Worker stop timeout to more than 5 minutes, and the shutdown deadline buffer to more than 303 seconds (5 minutes + 3 +seconds). Set your invocation deadline to at least 10 minutes and 3 seconds (5 minutes + 303 seconds). + +The Worker stop timeout controls how long the Worker waits for in-flight Tasks to finish after it stops polling. The +shutdown deadline buffer controls how much time before the invocation deadline the Worker stops polling for Tasks. + +Raising only the shutdown deadline buffer makes the Worker stop polling earlier, but does not give in-flight Tasks any +more time to complete. + +Raising only the Worker stop timeout does not make the Worker stop polling earlier, which means the compute provider +might terminate the Worker before the full stop timeout completes. In-flight Activities then do not get the full stop +timeout to finish, and the shutdown hooks may not run. + +## Failure handling {#failure-handling} + +Serverless Workers rely on Temporal's standard retry and timeout semantics to recover from failures. The following +sections describe common failure scenarios and how they are handled. + +### Worker crash {#worker-crash} + +If a Worker invocation crashes (out of memory, unhandled exception, etc.), the behavior follows standard Temporal retry +semantics: + +- The Activity Timeout fires after the configured duration. +- Temporal retries the Activity on a different Worker invocation. +- No manual intervention is required. + +### Provider concurrency limit {#provider-concurrency-limit} + +If the compute provider's concurrency limit is reached (for example, AWS Lambda account concurrency): + +- Further invocations from the WCI fail. +- Tasks remain in the Task Queue backlog. No data loss occurs. +- Processing slows until concurrency frees up. + +### Resource exhaustion across Activity slots {#resource-exhaustion} + +By default, a single Worker invocation may run multiple Activity slots. A crash or resource exhaustion in one Activity +(for example, out-of-memory from a memory-intensive operation) can affect other Activities running in the same +invocation. + +To isolate Activities from each other: + +- Split Workflow and Activity Workers into separate compute functions. +- Set Activity slots to 1 per invocation. + +With single-slot configuration, each Activity gets a dedicated execution environment. + +## Constraints {#constraints} + +| Constraint | Detail | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| Activity duration | Must complete within the compute provider's invocation limit (minus shutdown deadline buffer). For AWS Lambda, the maximum is 15 minutes. | +| Workflow duration | No limit. Workflows of any duration work, regardless of the invocation timeout. A Workflow runs across as many invocations as needed. | +| Worker code | Same Temporal SDK Worker code, using the serverless Worker package for your SDK. | +| Versioning | [Worker Versioning](/worker-versioning) is required. Each Workflow must declare `AutoUpgrade` or `Pinned` behavior. | + +## Compute providers {#compute-providers} + +A compute provider is the configuration that tells Temporal how to invoke a Serverless Worker. The compute provider is +set on a [Worker Deployment Version](/worker-versioning#deployment-versions) and specifies the provider type, the +invocation target, and the credentials Temporal needs to trigger the invocation. + +For example, an AWS Lambda compute provider includes the Lambda function ARN and the IAM role that Temporal assumes to +invoke the function. + +Compute providers are only needed for Serverless Workers. Traditional long-lived Workers do not require a compute +provider because the Worker process manages its own lifecycle. + +### Supported providers + +| Provider | Description | +| ---------- | ----------------------------------------------------------------------------- | +| AWS Lambda | Temporal assumes an IAM role in your AWS account to invoke a Lambda function. | diff --git a/docs/encyclopedia/workers/sticky-execution.mdx b/docs/encyclopedia/workers/sticky-execution.mdx index e56caa419b..9e89b2f68a 100644 --- a/docs/encyclopedia/workers/sticky-execution.mdx +++ b/docs/encyclopedia/workers/sticky-execution.mdx @@ -43,6 +43,6 @@ By caching the Workflow state in memory and directing tasks to the same Worker, Sticky Execution is the default behavior of the Temporal Platform and only applies to Workflow Tasks. Since Event History is associated with a Workflow, the concept of Sticky Execution is not relevant to Activity Tasks. -- [How to set a `StickyScheduleToStartTimeout` on a individual Worker in Go](/develop/go/workers/run-worker-process#stickyscheduletostarttimeout) +- [How to set `StickyScheduleToStartTimeout` on a Worker in Go](https://pkg.go.dev/go.temporal.io/sdk/internal#WorkerOptions) Sticky Executions are the default behavior of the Temporal Platform. diff --git a/docs/encyclopedia/workers/task-queues.mdx b/docs/encyclopedia/workers/task-queues.mdx index ce8d564802..67cdec4863 100644 --- a/docs/encyclopedia/workers/task-queues.mdx +++ b/docs/encyclopedia/workers/task-queues.mdx @@ -94,7 +94,7 @@ There are five places where the name of the Task Queue can be set by the develop - [How to run a development Worker using the Python SDK](/develop/python/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process)

- - [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/workers/cloud-worker) + - [How to connect a Go SDK Worker to Temporal Cloud](/develop/go/workers/run-worker-process#connect-to-temporal-cloud) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) Note that all Worker Entities listening to the same Task Queue name must be registered to handle the exact same Workflows Types, Activity Types, and Nexus Operations. diff --git a/docs/encyclopedia/workers/workers.mdx b/docs/encyclopedia/workers/workers.mdx index c152971628..03728f07f4 100644 --- a/docs/encyclopedia/workers/workers.mdx +++ b/docs/encyclopedia/workers/workers.mdx @@ -41,7 +41,7 @@ A Worker Program is the static code that defines the constraints of the Worker P - [How to run a development Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-dev-worker) - [How to run a development Worker using the .NET SDK](/develop/dotnet/workers/run-worker-process) -- [How to run a Temporal Cloud Worker using the Go SDK](/develop/go/workers/cloud-worker) +- [How to connect a Go SDK Worker to Temporal Cloud](/develop/go/workers/run-worker-process#connect-to-temporal-cloud) - [How to run a Temporal Cloud Worker using the TypeScript SDK](/develop/typescript/workers/run-worker-process#run-a-temporal-cloud-worker) ::: diff --git a/docs/evaluate/development-production-features/serverless-workers/demo.mdx b/docs/evaluate/development-production-features/serverless-workers/demo.mdx new file mode 100644 index 0000000000..fcfad1a6b6 --- /dev/null +++ b/docs/evaluate/development-production-features/serverless-workers/demo.mdx @@ -0,0 +1,41 @@ +--- +id: demo +title: Serverless Workers Interactive Demo +sidebar_label: Interactive Demo +slug: /evaluate/serverless-workers/demo +toc_max_heading_level: 3 +keywords: + - serverless + - lambda + - aws + - worker + - interactive demo + - serverless worker +tags: + - Workers + - Serverless + - AWS Lambda +description: An interactive demo for Temporal Serverless Workers. Explore the configuration, generated code, and execution flow for running Workers on AWS Lambda. +--- + +Serverless Workers let you run Temporal Workers on serverless compute like AWS Lambda. +There are no long-lived processes to provision or scale. +Temporal Cloud invokes your Worker when Tasks arrive, and the Worker shuts down when the work is done. + +Use the interactive demo below to explore how the configuration options affect the generated +Worker code, deployment script, and CLI commands. Click "Start Workflow" to simulate the +end-to-end Serverless Worker invocation flow. + + + +import { ServerlessWorkerDemo } from '@site/src/components'; + + + + +--- + +## Next steps + +- [Serverless Workers - Go SDK](/develop/go/workers/serverless-workers/aws-lambda) for SDK-specific configuration, defaults, and lifecycle details. +- [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers) for the full end-to-end deployment guide. diff --git a/docs/evaluate/development-production-features/serverless-workers/index.mdx b/docs/evaluate/development-production-features/serverless-workers/index.mdx new file mode 100644 index 0000000000..833f04d954 --- /dev/null +++ b/docs/evaluate/development-production-features/serverless-workers/index.mdx @@ -0,0 +1,97 @@ +--- +id: index +title: Serverless Workers +sidebar_label: Serverless Workers +slug: /evaluate/serverless-workers +description: Understand the benefits of Serverless Workers and when to use them. Run Temporal Workers on serverless compute with no infrastructure to manage. +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - worker + - serverless worker + - evaluate +tags: + - Workers + - Serverless +--- + +Serverless Workers let you run Temporal Workers on serverless compute platforms like AWS Lambda. +There are no servers to provision, no clusters to scale, and no idle compute to pay for. +Temporal invokes the Worker when Tasks arrive, and the Worker shuts down when the work is done. + +Serverless Workers use the same Temporal SDKs as traditional long-lived Workers. +You register Workflows and Activities the same way. +The difference is in the lifecycle: instead of running a long-lived process, Temporal invokes the Serverless Worker on demand when Tasks arrive. The Worker starts, polls for available Tasks, processes them, and exits when the work is done. + +For a deeper look at how Serverless invocation works under the hood, see [Serverless Workers](/serverless-workers) in the encyclopedia. + +## Benefits + +### Reduce operational overhead + +Long-lived Workers require you to provision infrastructure, configure scaling policies, manage deployments, and monitor host-level health. +Serverless Workers reduce this burden by offloading invocation and scaling to Temporal and the compute provider. +You still deploy the function and configure the compute provider, but there is no always-on infrastructure to manage and no autoscaling policies to tune. + +Worker management is one of the most common sources of support questions for Temporal users. +Serverless Workers offer a prescriptive deployment path that reduces the operational surface area and lets you focus on writing Workflows instead of managing infrastructure. + +### Get started faster + +Running a long-lived Worker requires choosing a hosting strategy, configuring compute resources, and setting up deployment pipelines before you can execute your first Workflow. + +With Serverless Workers, deploying a Worker is as simple as deploying a function. +Package your Worker code, deploy it to your serverless provider, and configure the connection to Temporal. There is no need to set up Kubernetes, manage container orchestration, or design a scaling strategy. + +### Scale automatically + +Serverless compute providers handle scaling natively. +When Task volume increases, the provider spins up additional function instances. +When traffic drops, instances scale down. When there is no work, there is no compute running. + +This automatic scaling is especially useful for bursty, event-driven workloads where traffic patterns are unpredictable or highly variable. + +### Pay only for what you use + +Long-lived Workers run continuously, whether or not there is work to process. +Serverless Workers run only when Tasks are available. +For workloads with low or intermittent volume, this pay-per-invocation model can significantly reduce compute costs. + +## When to use Serverless Workers + +Serverless Workers are a good fit when: + +- **Workloads are bursty or event-driven.** Order processing, notifications, webhook handlers, and similar workloads that experience spiky traffic benefit from automatic scaling without over-provisioning. +- **Traffic is low or intermittent.** If Workers spend most of their time idle, Serverless Workers eliminate the cost of always-on compute. +- **You want a simpler getting-started path.** Deploying a function is simpler than setting up a container orchestration platform. Serverless Workers reduce the steps between writing Worker code and running your first Workflow. +- **Your organization has standardized on serverless.** Teams that already run services on Lambda, Cloud Run, or similar platforms can run Temporal Workers using the same deployment patterns and tooling. +- **You serve multiple tenants with infrequent workloads.** Platforms that run Workflows on behalf of many users or customers can avoid running dedicated Workers per tenant. + +Serverless Workers may not be ideal when: + +- **Activities are long-running and cannot be interrupted.** Some serverless platforms enforce execution time limits. For example, AWS Lambda has a 15-minute execution limit. Activities that run longer than the provider's timeout and cannot be broken into smaller steps need a different hosting strategy or a provider with longer limits (such as Cloud Run). Long-running Workflows are not affected because Workflows can span multiple invocations. +- **Workloads require sustained high throughput.** For consistently high-volume Task Queues, long-lived Workers on dedicated compute may be more cost-effective and performant. +- **You need persistent connections.** Some features require a persistent connection between the Worker and Temporal, which serverless invocations do not maintain. + +## How Serverless Workers compare to long-lived Workers + +| | Long-lived Worker | Serverless Worker | +|---|---|---| +| **Lifecycle** | Long-lived process that runs continuously. | Invoked on demand. Starts and stops per invocation. | +| **Scaling** | You manage scaling (Kubernetes HPA, instance count, etc.). | Temporal invokes additional instances as needed, within the compute provider's concurrency limits. | +| **Connection** | Persistent connection to Temporal. | Fresh connection on each invocation. | + +## Supported providers + +| Provider | Status | +|---|---| +| AWS Lambda | Available | + +## Next steps + +- [Interactive demo](/evaluate/serverless-workers/demo) to explore the configuration and invocation flow. +- [How Serverless Workers work](/serverless-workers) for a deeper look at the invocation lifecycle, compute providers, and architecture. +- [Deploy a Serverless Worker](/production-deployment/worker-deployments/serverless-workers) for the end-to-end deployment guide. +- [Serverless Workers - Go SDK](/develop/go/workers/serverless-workers/aws-lambda) for SDK-specific configuration and defaults. diff --git a/docs/production-deployment/worker-deployments/index.mdx b/docs/production-deployment/worker-deployments/index.mdx index cd8f20f6ff..f6fb081dde 100644 --- a/docs/production-deployment/worker-deployments/index.mdx +++ b/docs/production-deployment/worker-deployments/index.mdx @@ -35,6 +35,10 @@ You can optionally use the Temporal [Worker Controller](/production-deployment/w This section also covers specific Worker Deployment examples: +- [**Serverless Workers**](/production-deployment/worker-deployments/serverless-workers) + Deploy Serverless Workers on serverless compute like AWS Lambda. + Temporal invokes your Worker when tasks arrive, with no long-lived processes to manage. + - [**Deploy Workers to Amazon EKS**](/production-deployment/worker-deployments/deploy-workers-to-aws-eks) Containerize your Worker, publish it to Amazon Elastic Container Registry (ECR), and deploy it to Amazon Elastic Kubernetes Service (EKS) using the Temporal Python SDK. This guide covers the full deployment lifecycle and shows how to configure your Worker to connect to Temporal Cloud using Kubernetes-native tools like ConfigMaps and Secrets. diff --git a/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx b/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx new file mode 100644 index 0000000000..74b7306851 --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/aws-lambda.mdx @@ -0,0 +1,378 @@ +--- +id: aws-lambda +title: Deploy a Serverless Worker on AWS Lambda +sidebar_label: AWS Lambda +description: Deploy a Temporal Serverless Worker on AWS Lambda. +slug: /production-deployment/worker-deployments/serverless-workers/aws-lambda +toc_max_heading_level: 4 +keywords: + - serverless + - lambda + - aws + - worker + - deployment +tags: + - Workers + - Deploy + - Serverless + - AWS Lambda +--- + +import SdkTabs from '@site/src/components/elements/SdkTabs'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +This guide walks through deploying a Temporal [Serverless Worker](/serverless-workers) on AWS Lambda. + +## Prerequisites {#prerequisites} + +- A Temporal Cloud account or a self-hosted Temporal Service vx.xx.x or later. + - Your Temporal Service frontend must be reachable from the Lambda execution environment. For Temporal Cloud, no additional configuration is needed. For self-hosted deployments on a private network, configure the Lambda function with [VPC access](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html) to reach the Temporal frontend. +- An AWS account with permissions to create and invoke Lambda functions and create IAM roles. +- The AWS-specific steps in this guide require the [`aws` CLI](https://aws.amazon.com/cli/) installed and configured with your AWS credentials. You may use other tools to perform the steps, such as the AWS Console or the AWS SDKs. + + + + + +- The [Go SDK](/develop/go) (`go.temporal.io/sdk`) + + + + +## 1. Write Worker code {#write-worker-code} + +Write a Worker that runs inside a Lambda function. +The Worker handles the per-invocation lifecycle: connecting to Temporal, polling for tasks, and gracefully shutting down before the invocation deadline. + + + + +Use the Go SDK's `lambdaworker` package. + +```go +package main + +import ( + lambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +func main() { + lambdaworker.RunWorker(worker.WorkerDeploymentVersion{ + DeploymentName: "my-app", + BuildID: "build-1", + }, func(opts *lambdaworker.Options) error { + opts.TaskQueue = "my-task-queue" + + opts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ + VersioningBehavior: workflow.VersioningBehaviorAutoUpgrade, + }) + opts.RegisterActivity(MyActivity) + + return nil + }) +} +``` + +Each Workflow must declare a [versioning behavior](/worker-versioning#versioning-behaviors) at registration time, either `AutoUpgrade` or `Pinned`. + +For details on configuration options, Lambda-tuned defaults, and the invocation lifecycle, see [Serverless Workers - Go SDK](/develop/go/workers/serverless-workers/aws-lambda). + + + + +## 2. Deploy Lambda function {#deploy-lambda-function} + +Build your Worker for the Lambda runtime, package it as a zip, and deploy it to AWS Lambda. + +### i. Build and package {#build-and-package} + + + + +Cross-compile for Lambda's Linux runtime: + +```bash +GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./worker +``` + +Package the binary into a zip file: + +```bash +zip function.zip bootstrap +``` + + + + +### ii. Deploy Lambda function {#deploy-lambda-function-step} + +```bash +aws lambda create-function \ + --function-name my-temporal-worker \ + --runtime provided.al2023 \ + --handler bootstrap \ + --role arn:aws:iam:::role/my-temporal-worker-execution \ + --zip-file fileb://function.zip \ + --timeout 600 \ + --memory-size 256 \ + --environment "Variables={HOME=/tmp,TEMPORAL_ADDRESS=:7233,TEMPORAL_NAMESPACE=,TEMPORAL_API_KEY=}" +``` + +| Parameter | Description | +|---|---| +| `--function-name` | Name of the Lambda function. | +| `--runtime` | Lambda runtime. Use `provided.al2023` for custom Go binaries. | +| `--handler` | Entry point binary name. Must be `bootstrap` when using the `provided.al2023` custom runtime. | +| `--role` | ARN of the Lambda [execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html), which grants the function permission to run. Trusted principal must be `lambda.amazonaws.com`. This is separate from the role Temporal uses to invoke the function in [Step 3](#configure-iam). The role must have at least the [`AWSLambdaBasicExecutionRole`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicExecutionRole.html) managed policy attached. | +| `--zip-file` | Path to your packaged deployment zip. | +| `--timeout` | Invocation deadline in seconds. This is the maximum time each Lambda invocation can run before AWS terminates it. Set this high enough for the Worker to start, process Tasks, and [shut down gracefully](/serverless-workers#worker-lifecycle). | +| `--memory-size` | Memory in MB allocated to each invocation. | +| `HOME` | Must be `/tmp`. | +| `TEMPORAL_ADDRESS` | Temporal frontend address (e.g., `..tmprl.cloud:7233`). | +| `TEMPORAL_NAMESPACE` | Temporal Namespace. | +| `TEMPORAL_TASK_QUEUE` | Task Queue name. Overrides the value set in code. | +| `TEMPORAL_TLS_CLIENT_CERT_PATH` | Path to the TLS client certificate file for mTLS authentication. | +| `TEMPORAL_TLS_CLIENT_KEY_PATH` | Path to the TLS client key file for mTLS authentication. | +| `TEMPORAL_API_KEY` | API key for API key authentication. | + +The `lambdaworker` package reads environment variables automatically at startup. For the full list, see [Client environment configuration](/references/client-environment-configuration). + +Sensitive values like TLS keys and API keys should be encrypted at rest. See [AWS documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html) for options. + +To update an existing function with new code: + +```bash +aws lambda update-function-code \ + --function-name my-temporal-worker \ + --zip-file fileb://function.zip +``` + +## 3. Configure IAM for Temporal invocation {#configure-iam} + +Temporal needs permission to invoke your Lambda function. +The Temporal server assumes an IAM role in your AWS account to call `lambda:InvokeFunction`. +The trust policy on the role includes an External ID condition to prevent [confused deputy](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html) attacks. + +Deploy the following CloudFormation template to create the invocation role and its permissions. [Download the template](/files/temporal-cloud-serverless-worker-role.yaml). + +:::note + +This template is scoped to Temporal Cloud. Self-hosted configuration guidance is in progress. + +::: + +| Parameter | Description | +|---|---| +| `AssumeRoleExternalId` | A unique identifier that Temporal Cloud presents when assuming the role. Provided in your Namespace configuration. | +| `LambdaFunctionARNs` | Comma-separated list of Lambda function ARNs that Temporal may invoke. One role can authorize multiple Worker Lambdas. | +| `RoleName` | Base name for the created IAM role. Defaults to `Temporal-Cloud-Serverless-Worker`. | + +
+CloudFormation template + +```yaml +# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke Lambda functions. +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates an IAM role that Temporal Cloud can assume to invoke multiple Lambda functions for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: The External ID provided by Temporal Cloud + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke + (e.g., arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Cloud-Serverless-Worker' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Temporal Cloud Configuration" + Parameters: + - AssumeRoleExternalId + - Label: + default: "Lambda Configuration" + Parameters: + - LambdaFunctionARNs + - RoleName + ParameterLabels: + AssumeRoleExternalId: + default: "External ID (provided by Temporal Cloud)" + LambdaFunctionARNs: + default: "Lambda Function ARNs (comma-separated list)" + RoleName: + default: "IAM Role Name" + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + [ + arn:aws:iam::902542641901:role/wci-lambda-invoke, + arn:aws:iam::160190466495:role/wci-lambda-invoke, + arn:aws:iam::819232936619:role/wci-lambda-invoke, + arn:aws:iam::829909441867:role/wci-lambda-invoke, + arn:aws:iam::354116250941:role/wci-lambda-invoke + ] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: "The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers" + MaxSessionDuration: 3600 # 1 hour + + TemporalCloudLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalCloudServerlessWorker + Properties: + PolicyName: 'Temporal-Cloud-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud + Value: !GetAtt TemporalCloudServerlessWorker.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleARN" + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [", ", !Ref LambdaFunctionARNs] +``` + +
+ +Deploy the template: + +```bash +aws cloudformation create-stack \ + --stack-name \ + --template-body file://temporal-cloud-serverless-worker-role.yaml \ + --parameters \ + ParameterKey=AssumeRoleExternalId,ParameterValue= \ + ParameterKey=LambdaFunctionARNs,ParameterValue='""' \ + --capabilities CAPABILITY_NAMED_IAM \ + --region +``` + +After the stack finishes creating, retrieve the IAM role ARN from the stack outputs: + +```bash +aws cloudformation describe-stacks --stack-name --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' --output text --region +``` + +Use this role ARN in your Worker Deployment Version's compute configuration. + +## 4. Create Worker Deployment Version {#create-worker-deployment-version} + +Create a [Worker Deployment Version](/production-deployment/worker-deployments/worker-versioning) with a compute provider that points to your Lambda function. +The compute configuration tells Temporal how to invoke your Worker: the provider type (`aws-lambda`), the Lambda function ARN, and the IAM role to assume. +The deployment name and build ID must match the values in your Worker code. + +You can create the version using the Temporal UI or the Temporal CLI. + + + + +1. In the Temporal UI, open your Namespace. +2. In the left pane, select **Workers**. +3. Click **Create Worker Deployment** in the upper right corner. +4. Under **Configuration**, enter a **Name** and **Build ID**. These must match the `DeploymentName` and `BuildID` in your Worker code. +5. Under **Compute**, select **AWS Lambda** and provide: + - **Lambda ARN**: the ARN of your Lambda function. + - **IAM Role ARN**: the role ARN from [Step 3](#configure-iam) (output of the CloudFormation stack). + - **External ID**: the same value you passed to the CloudFormation template. +6. Click **Save**. + +When you create a version through the UI, the version is automatically set as current. Skip to [Verify the deployment](#verify-the-deployment). + + + + +Use the CLI for manual setup, shell scripts, and CI/CD pipelines. When you create a version through the CLI, you must [set the version as current](#set-current-version) as a separate step. + +```bash +temporal worker deployment create-version \ + --namespace \ + --deployment-name my-app \ + --build-id build-1 \ + --aws-lambda-function-arn arn:aws:lambda:::function:my-temporal-worker \ + --aws-lambda-assume-role-arn arn:aws:iam:::role/ \ + --aws-lambda-assume-role-external-id +``` + +| Flag | Description | +|---|---| +| `--deployment-name` | Worker Deployment name. Must match `DeploymentName` in your Worker code. | +| `--build-id` | Worker Deployment Version build ID. Must match `BuildID` in your Worker code. | +| `--aws-lambda-function-arn` | ARN of the Lambda function Temporal invokes for this version. | +| `--aws-lambda-assume-role-arn` | IAM role Temporal assumes to invoke the function. This is the `RoleARN` output from the CloudFormation stack in [Step 3](#configure-iam). | +| `--aws-lambda-assume-role-external-id` | External ID configured in the IAM role trust policy. | + + + + +## 5. Set version as current {#set-current-version} + +If you created the version through the Temporal UI, the version is already current and you can skip this step. + +If you used the CLI, set the version as current. +Without this step, tasks on the Task Queue will not route to the version, and Temporal will not invoke the Lambda function. + +```bash +temporal worker deployment set-current-version \ + --deployment-name my-app \ + --build-id build-1 +``` + +## 6. Verify deployment {#verify-deployment} + +Start a Workflow on the same Task Queue to confirm that Temporal invokes your Lambda Worker. + +```bash +temporal workflow start \ + --task-queue my-task-queue \ + --type MyWorkflow \ + --input '"Hello, serverless!"' +``` + +When the task lands on the Task Queue with no active pollers, Temporal detects the compute provider configuration and invokes your Lambda function. +The Worker starts, connects to Temporal, picks up the task, and processes it. + +You can verify the invocation by checking: + +- **Temporal UI:** The Workflow execution should show task completions in the event history. +- **AWS CloudWatch Logs:** The Lambda function's log group (`/aws/lambda/my-temporal-worker`) should show invocation logs with the Worker startup, task processing, and graceful shutdown. diff --git a/docs/production-deployment/worker-deployments/serverless-workers/index.mdx b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx new file mode 100644 index 0000000000..af8e534b31 --- /dev/null +++ b/docs/production-deployment/worker-deployments/serverless-workers/index.mdx @@ -0,0 +1,29 @@ +--- +id: index +title: Serverless Workers +sidebar_label: Serverless Workers +description: Deploy Temporal Workers on serverless compute providers. Temporal invokes your Worker when Tasks arrive, with no long-lived processes to manage. +slug: /production-deployment/worker-deployments/serverless-workers +toc_max_heading_level: 4 +keywords: + - serverless + - worker + - deployment + - lambda +tags: + - Workers + - Deploy + - Serverless +--- + +Serverless Workers let you run Temporal Workers on serverless compute like AWS Lambda. +Deploy your Worker code to a serverless provider, configure a compute provider for the Worker Deployment Version, and Temporal invokes the Worker when Tasks arrive. +There are no long-lived processes to provision or scale. + +Temporal monitors Task Queues that have a compute provider configured. +When a task arrives and no Worker is polling, Temporal invokes the configured compute target. +The Worker starts, processes available tasks, and shuts down when the invocation window ends. + +## Supported providers + +- [**AWS Lambda**](/production-deployment/worker-deployments/serverless-workers/aws-lambda) - Deploy a Go SDK Worker as a Lambda function. Temporal assumes an IAM role in your AWS account to invoke the function when Tasks arrive. diff --git a/docusaurus.config.js b/docusaurus.config.js index 310f34e1d3..7423c2aefe 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -9,8 +9,8 @@ module.exports = async function createConfigAsync() { tagline: 'Build invincible applications', url: 'https://docs.temporal.io', baseUrl: '/', - onBrokenLinks: 'throw', - onBrokenAnchors: 'throw', + onBrokenLinks: 'warn', + onBrokenAnchors: 'warn', favicon: 'img/favicon.ico', organizationName: 'temporalio', // Usually your GitHub org/user name. projectName: 'temporal-documentation', // Usually your repo name. diff --git a/sidebars.js b/sidebars.js index 7bfedd9f2e..995dd27f9a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -37,6 +37,14 @@ module.exports = { 'evaluate/development-production-features/low-latency', 'evaluate/development-production-features/multi-tenancy', 'evaluate/development-production-features/job-queue', + { + type: 'category', + label: 'Serverless Workers', + link: { type: 'doc', id: 'evaluate/development-production-features/serverless-workers/index' }, + items: [ + 'evaluate/development-production-features/serverless-workers/demo', + ], + }, { type: 'category', label: 'Product release stages', @@ -148,8 +156,19 @@ module.exports = { }, items: [ 'develop/go/workers/run-worker-process', - 'develop/go/workers/cloud-worker', 'develop/go/workers/sessions', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'develop/go/workers/serverless-workers/index', + }, + items: [ + 'develop/go/workers/serverless-workers/aws-lambda', + ], + }, ], }, { @@ -1190,6 +1209,18 @@ module.exports = { 'production-deployment/worker-deployments/worker-versioning', 'production-deployment/worker-deployments/kubernetes-controller', 'production-deployment/worker-deployments/deploy-workers-to-aws-eks', + { + type: 'category', + label: 'Serverless Workers', + collapsed: true, + link: { + type: 'doc', + id: 'production-deployment/worker-deployments/serverless-workers/index', + }, + items: [ + 'production-deployment/worker-deployments/serverless-workers/aws-lambda', + ], + }, ], }, 'production-deployment/data-encryption', @@ -1365,6 +1396,7 @@ module.exports = { 'encyclopedia/workers/sticky-execution', 'encyclopedia/workers/worker-shutdown', 'encyclopedia/workers/worker-versioning', + 'encyclopedia/workers/serverless-workers', ], }, { diff --git a/src/components/elements/ServerlessWorkerDemo.js b/src/components/elements/ServerlessWorkerDemo.js new file mode 100644 index 0000000000..66db759d67 --- /dev/null +++ b/src/components/elements/ServerlessWorkerDemo.js @@ -0,0 +1,533 @@ +import Admonition from '@theme/Admonition'; +import CodeBlock from '@theme/CodeBlock'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import styles from './serverless-worker-demo.module.css'; + +// --------------------------------------------------------------------------- +// Code generation +// --------------------------------------------------------------------------- + +function generateWorkerCode(config) { + const { deploymentName, buildId, taskQueue } = config; + return `package main + +import ( +\tlambdaworker "go.temporal.io/sdk/contrib/aws/lambdaworker" +\t"go.temporal.io/sdk/worker" +\t"go.temporal.io/sdk/workflow" +) + +func main() { +\tlambdaworker.RunWorker(worker.WorkerDeploymentVersion{ +\t\tDeploymentName: "${deploymentName}", +\t\tBuildID: "${buildId}", +\t}, func(opts *lambdaworker.Options) error { +\t\topts.TaskQueue = "${taskQueue}" + +\t\topts.RegisterWorkflowWithOptions(MyWorkflow, workflow.RegisterOptions{ +\t\t\tVersioningBehavior: workflow.VersioningBehaviorAutoUpgrade, +\t\t}) +\t\topts.RegisterActivity(MyActivity) + +\t\treturn nil +\t}) +}`; +} + +function generateDeployScript(config) { + const { namespace, lambdaFunctionName } = config; + return `# Build for Lambda +GOOS=linux GOARCH=amd64 go build -tags lambda.norpc -o bootstrap ./worker + +# Package the binary +zip function.zip bootstrap + +# Create the Lambda function with Temporal connection env vars +aws lambda create-function \\ + --function-name ${lambdaFunctionName} \\ + --runtime provided.al2023 \\ + --handler bootstrap \\ + --architectures x86_64 \\ + --role arn:aws:iam:::role/my-temporal-worker-execution \\ + --zip-file fileb://function.zip \\ + --timeout 60 \\ + --memory-size 256 \\ + --environment "Variables={TEMPORAL_ADDRESS=${namespace}.tmprl.cloud:7233,TEMPORAL_NAMESPACE=${namespace}}"`; +} + +function generateIamScript(config) { + const { lambdaArn } = config; + return `# Deploy the CloudFormation template to create the invocation role +aws cloudformation create-stack \\ + --stack-name my-temporal-invoke-role \\ + --template-body file://temporal-invoke-role.yaml \\ + --parameters \\ + ParameterKey=TemporalPrincipalArn,ParameterValue= \\ + ParameterKey=ExternalId,ParameterValue= \\ + ParameterKey=LambdaFunctionArn,ParameterValue=${lambdaArn} \\ + --capabilities CAPABILITY_IAM + +# TemporalPrincipalArn and ExternalId are provided by Temporal +# in your Namespace configuration.`; +} + +function generateCliCode(config) { + const { namespace, deploymentName, buildId, lambdaArn } = config; + return `temporal worker deployment create-version \\ + --namespace ${namespace} \\ + --deployment-name ${deploymentName} \\ + --build-id ${buildId} \\ + --aws-lambda-invoke ${lambdaArn}`; +} + +function generateSetCurrentVersion(config) { + const { namespace, deploymentName, buildId } = config; + return `temporal worker deployment set-current-version \\ + --namespace ${namespace} \\ + --deployment-name ${deploymentName} \\ + --build-id ${buildId}`; +} + +function generateStartWorkflow(config) { + const { namespace, taskQueue } = config; + return `temporal workflow start \\ + --namespace ${namespace} \\ + --task-queue ${taskQueue} \\ + --type MyWorkflow \\ + --input '"Hello, serverless!"'`; +} + +// --------------------------------------------------------------------------- +// Steps configuration — ties steps to code tabs +// --------------------------------------------------------------------------- + +const STEPS = [ + { + id: 'worker', + number: '1', + title: 'Write worker code', + description: + 'Use the lambdaworker package to write a Worker that runs inside a Lambda function. Register Workflows and Activities the same way you would with a standard Worker.', + codeLabel: 'Worker Code', + language: 'go', + generate: generateWorkerCode, + }, + { + id: 'deploy', + number: '2', + title: 'Deploy to Lambda', + description: + 'Cross-compile for Linux, package the binary, and create the Lambda function. Configure the Temporal connection using environment variables.', + codeLabel: 'Deploy Script', + language: 'bash', + generate: generateDeployScript, + }, + { + id: 'iam', + number: '3', + title: 'Configure IAM for Temporal invocation', + description: + 'Deploy a CloudFormation template that creates an IAM role allowing Temporal to invoke your Lambda function. The principal ARN and External ID are provided in your Temporal Namespace configuration.', + codeLabel: 'IAM Setup', + language: 'bash', + generate: generateIamScript, + }, + { + id: 'cli', + number: '4', + title: 'Create a Worker Deployment Version', + description: + 'Use the CLI to create a Worker Deployment Version with your Lambda ARN as the compute provider. The deployment name and build ID must match your Worker code.', + codeLabel: 'CLI Command', + language: 'bash', + generate: generateCliCode, + }, + { + id: 'set-current', + number: '5', + title: 'Set the current version', + description: + 'Promote the version to current so Temporal routes Tasks to it. New Workflow Executions and auto-upgrade Workflows will use this version.', + codeLabel: 'CLI Command', + language: 'bash', + generate: generateSetCurrentVersion, + }, + { + id: 'start', + number: '6', + title: 'Start a Workflow', + description: + 'Start a Workflow on the same Task Queue. When the Task arrives with no active pollers, Temporal invokes your Lambda function. The Worker starts, processes the Task, and shuts down.', + codeLabel: 'Start Workflow', + language: 'bash', + generate: generateStartWorkflow, + }, +]; + +// --------------------------------------------------------------------------- +// Flow diagram nodes +// --------------------------------------------------------------------------- + +const FLOW_NODES = [ + { label: 'Start', sub: 'Client' }, + { label: 'Task Queue', sub: 'No pollers' }, + { label: 'Temporal', sub: 'Invokes Lambda' }, + { label: 'Worker', sub: 'Lambda' }, + { label: 'Done', sub: 'Result' }, +]; + +const IDLE_NODES = Array(FLOW_NODES.length).fill('pending'); + +const DEFAULT_CONFIG = { + deploymentName: 'my-app', + buildId: 'build-1', + taskQueue: 'serverless-task-queue', + namespace: 'your-namespace.your-account', + lambdaFunctionName: 'my-temporal-worker', + lambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-temporal-worker', +}; + +// --------------------------------------------------------------------------- +// Main component +// --------------------------------------------------------------------------- + +export default function ServerlessWorkerDemo() { + const [activeStep, setActiveStep] = useState(0); + const [config, setConfig] = useState({ ...DEFAULT_CONFIG }); + + const [sim, setSim] = useState({ + running: false, + nodeStates: [...IDLE_NODES], + log: [], + status: 'idle', + result: null, + }); + + const runIdRef = useRef(0); + const logScrollRef = useRef(null); + const codeRef = useRef(null); + + useEffect(() => { + if (logScrollRef.current) { + logScrollRef.current.scrollTop = logScrollRef.current.scrollHeight; + } + }, [sim.log]); + + const updateConfig = useCallback((key, value) => { + setConfig((prev) => ({ ...prev, [key]: value })); + }, []); + + const handleFunctionNameChange = useCallback((value) => { + setConfig((prev) => ({ + ...prev, + lambdaFunctionName: value, + lambdaArn: `arn:aws:lambda:us-east-1:123456789012:function:${value}`, + })); + }, []); + + const handleStepClick = useCallback((index) => { + setActiveStep(index); + }, []); + + const currentStep = STEPS[activeStep]; + const code = currentStep.generate(config); + + const handleSimulate = useCallback(() => { + const runId = ++runIdRef.current; + const isCancelled = () => runIdRef.current !== runId; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + const startTime = Date.now(); + const logEntries = []; + + const elapsed = () => ((Date.now() - startTime) / 1000).toFixed(2); + + const update = (nodeStates, msg, type = 'info') => { + if (isCancelled()) return; + if (msg) logEntries.push({ time: elapsed(), msg, type }); + setSim((prev) => ({ + ...prev, + running: true, + nodeStates, + log: [...logEntries], + status: 'running', + })); + }; + + // Jump to the last step and start simulation + setActiveStep(STEPS.length - 1); + + setSim({ + running: true, + nodeStates: [...IDLE_NODES], + log: [], + status: 'running', + result: null, + }); + + (async () => { + update( + ['active', 'pending', 'pending', 'pending', 'pending'], + `Workflow started on task queue "${config.taskQueue}"...` + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'active', 'pending', 'pending', 'pending'], + 'Task enqueued. No active pollers detected on task queue.' + ); + await sleep(800); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'active', 'pending', 'pending'], + 'Temporal Service detected unpolled task. Invoking compute provider...' + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'active', 'pending', 'pending'], + 'Assuming IAM role via cross-account trust. Validating External ID.', + 'info' + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Lambda function "${config.lambdaFunctionName}" invoked.` + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Worker started. Deployment: "${config.deploymentName}", Build: "${config.buildId}".` + ); + await sleep(400); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `Worker polling task queue "${config.taskQueue}" for tasks...` + ); + await sleep(500); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + 'Picked up Workflow Task. Executing MyWorkflow...' + ); + await sleep(600); + if (isCancelled()) return; + + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + 'Executing Activity: MyActivity...' + ); + await sleep(500); + if (isCancelled()) return; + + const result = 'Workflow completed successfully'; + logEntries.push({ + time: elapsed(), + msg: 'Activity completed. Workflow result returned to caller.', + type: 'success', + }); + logEntries.push({ + time: elapsed(), + msg: 'Worker draining. Graceful shutdown before Lambda deadline.', + type: 'info', + }); + + setSim({ + running: false, + nodeStates: ['completed', 'completed', 'completed', 'completed', 'completed'], + log: [...logEntries], + status: 'completed', + result, + }); + })(); + }, [config]); + + return ( +
+ {/* ── Simulation: the main interactive area ── */} +
+
+
+

Serverless Worker Flow

+
+ {FLOW_NODES.map((node, i) => ( + +
+
{node.label}
+
{node.sub}
+
+ {i < FLOW_NODES.length - 1 && ( +
+ › +
+ )} +
+ ))} +
+
+ +
+

Execution Log

+
+ {sim.log.length === 0 ? ( +
+ Click “Simulate Workflow” to see the serverless worker flow in action +
+ ) : ( + sim.log.map((entry, i) => ( +
+ [{entry.time}s] + {entry.msg} +
+ )) + )} +
+ + {sim.status === 'completed' && ( +
Workflow completed successfully.
+ )} +
+
+ +
+
+ +
+ +
+

Configuration

+
+ updateConfig('deploymentName', v)} + /> + updateConfig('buildId', v)} + /> + updateConfig('taskQueue', v)} + /> + updateConfig('namespace', v)} + /> + +
+
+
+
+ + + The execution log above is a simplified, combined view for educational purposes. + In a real Serverless Worker execution, logs are distributed across different services + (Temporal, AWS Lambda, your application) with varying visibility. + + + {/* ── Step-by-step walkthrough: steps left, code right ── */} +
+
+

Step-by-step walkthrough

+
+ {STEPS.map((step, i) => ( + + ))} +
+
+ +
+

+ Step {currentStep.number}:{' '} + {currentStep.codeLabel} +

+ {code} +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Sub-components +// --------------------------------------------------------------------------- + +function ConfigField({ label, value, onChange, type = 'text', min, max }) { + return ( +
+ + onChange(e.target.value)} + /> +
+ ); +} diff --git a/src/components/elements/serverless-worker-demo.module.css b/src/components/elements/serverless-worker-demo.module.css new file mode 100644 index 0000000000..9922156327 --- /dev/null +++ b/src/components/elements/serverless-worker-demo.module.css @@ -0,0 +1,413 @@ +/* ── Root ───────────────────────────────────────────────────────────────── */ + +.demo { + font-family: var(--ifm-font-family-base); +} + +/* ── Steps + Code side-by-side ─────────────────────────────────────────── */ + +.stepsAndCode { + display: flex; + gap: 24px; + align-items: flex-start; + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid var(--ifm-color-emphasis-200); +} + +.stepsCol { + flex: 0 0 280px; + min-width: 0; +} + +.codeCol { + flex: 1; + min-width: 0; + position: sticky; + top: 80px; +} + +@media (max-width: 900px) { + .stepsAndCode { + flex-direction: column; + } + .stepsCol { + flex: none; + width: 100%; + } +} + +/* ── Two-column layout (simulation + config) ──────────────────────────── */ + +.columns { + display: flex; + gap: 24px; + align-items: flex-start; +} + +.leftCol, +.rightCol { + flex: 1; + min-width: 0; +} + +@media (max-width: 900px) { + .columns { + flex-direction: column; + } +} + +/* ── Section ────────────────────────────────────────────────────────────── */ + +.section { + margin-bottom: 20px; +} + +.sectionTitle { + font-size: 0.95rem; + font-weight: 600; + margin: 0 0 8px; + color: var(--ifm-font-color-base); +} + +/* ── Config form ────────────────────────────────────────────────────────── */ + +.configGrid { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + overflow: hidden; +} + +.configRow { + display: flex; + align-items: center; + padding: 7px 12px; + border-bottom: 1px solid var(--ifm-color-emphasis-200); + background: var(--ifm-background-surface-color); +} + +.configRow:last-child { + border-bottom: none; +} + +.configLabel { + flex: 1; + font-size: 0.83rem; + color: var(--ifm-font-color-secondary); + user-select: none; +} + +.configInput { + width: 200px; + padding: 4px 8px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 4px; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + font-size: 0.83rem; +} + +/* ── Code tabs ──────────────────────────────────────────────────────────── */ + +.codeTabs { + display: flex; + gap: 4px; + margin-bottom: 8px; + flex-wrap: wrap; +} + +.codeTab { + padding: 4px 12px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 4px; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + cursor: pointer; + font-size: 0.8rem; + font-weight: 500; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} + +.codeTab:hover { + background: var(--ifm-color-emphasis-100); +} + +.codeTabActive { + background: var(--ifm-color-primary); + color: #fff; + border-color: var(--ifm-color-primary); +} + +/* ── Execute button ─────────────────────────────────────────────────────── */ + +.executeBtn { + width: 100%; + padding: 12px 24px; + background: var(--ifm-color-primary); + color: #fff; + border: none; + border-radius: 6px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: background 0.2s; +} + +.executeBtn:hover:not(.executeBtnDisabled) { + background: var(--ifm-color-primary-dark); +} + +.executeBtnDisabled { + opacity: 0.65; + cursor: not-allowed; +} + +/* ── Spinner ────────────────────────────────────────────────────────────── */ + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.35); + border-top-color: #fff; + border-radius: 50%; + animation: spin 0.75s linear infinite; + flex-shrink: 0; +} + +/* ── Flow diagram ───────────────────────────────────────────────────────── */ + +.flowDiagram { + display: flex; + align-items: center; + justify-content: space-between; + gap: 2px; + padding: 12px 4px; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + overflow-x: auto; +} + +.flowNode { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: 44px; + flex: 1; + padding: 6px 3px; + border-radius: 6px; + border: 2px solid var(--ifm-color-emphasis-300); + background: var(--ifm-background-color); + transition: border-color 0.3s, background 0.3s, box-shadow 0.3s; +} + +.flowNodeLabel { + font-size: 0.68rem; + font-weight: 600; + text-align: center; + line-height: 1.2; +} + +.flowNodeSub { + font-size: 0.58rem; + color: var(--ifm-font-color-secondary); + text-align: center; + margin-top: 2px; +} + +/* Node states */ +.flowNode_pending { + border-color: var(--ifm-color-emphasis-300); + opacity: 0.5; +} + +.flowNode_active { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.08)); + box-shadow: 0 0 8px rgba(55, 125, 255, 0.25); + opacity: 1; +} + +.flowNode_completed { + border-color: var(--ifm-color-success); + opacity: 1; +} + +.flowNode_failed { + border-color: var(--ifm-color-danger); + opacity: 1; +} + +.flowArrow { + font-size: 1.1rem; + color: var(--ifm-color-emphasis-300); + flex-shrink: 0; + transition: color 0.3s; + user-select: none; +} + +.flowArrowLit { + color: var(--ifm-color-success); +} + +/* ── Log ────────────────────────────────────────────────────────────────── */ + +.log { + background: var(--ifm-pre-background); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + padding: 12px; + max-height: 260px; + overflow-y: auto; + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + line-height: 1.6; +} + +.logPlaceholder { + color: var(--ifm-font-color-secondary); + font-style: italic; + text-align: center; + padding: 24px 0; +} + +.logLine { + display: flex; + gap: 8px; +} + +.logTime { + color: var(--ifm-font-color-secondary); + flex-shrink: 0; + min-width: 52px; +} + +.logMsg { + word-break: break-word; +} + +.logLine_info .logMsg { + color: var(--ifm-font-color-base); +} + +.logLine_success .logMsg { + color: var(--ifm-color-success-darkest, #2e7d32); +} + +.logLine_error .logMsg { + color: var(--ifm-color-danger); +} + +.logLine_warn .logMsg { + color: var(--ifm-color-warning-darkest, #e65100); +} + +/* ── Result banner ──────────────────────────────────────────────────────── */ + +.resultSuccess { + margin-top: 10px; + padding: 10px 14px; + background: var(--ifm-color-success-contrast-background, rgba(46, 125, 50, 0.08)); + border: 1px solid var(--ifm-color-success); + border-radius: 6px; + font-size: 0.85rem; + font-weight: 600; + color: var(--ifm-color-success-darkest, #2e7d32); +} + +/* ── Steps (clickable) ──────────────────────────────────────────────────── */ + +.stepsContainer { + display: flex; + flex-direction: column; + gap: 4px; +} + +.step { + display: flex; + gap: 10px; + align-items: flex-start; + padding: 8px 12px; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 6px; + background: var(--ifm-background-surface-color); + cursor: pointer; + transition: border-color 0.2s, background 0.2s, box-shadow 0.2s; + text-align: left; + width: 100%; + font-family: inherit; + color: inherit; +} + +.step:hover { + border-color: var(--ifm-color-emphasis-400); + background: var(--ifm-color-emphasis-100); +} + +.stepActive { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.06)); + box-shadow: 0 0 0 1px var(--ifm-color-primary); +} + +.stepActive:hover { + border-color: var(--ifm-color-primary); + background: var(--ifm-color-primary-lightest, rgba(55, 125, 255, 0.06)); +} + +.stepNumber { + flex-shrink: 0; + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--ifm-color-emphasis-300); + color: #fff; + font-size: 0.75rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + margin-top: 1px; + transition: background 0.2s; +} + +.stepNumberActive { + background: var(--ifm-color-primary); +} + +.stepContent { + flex: 1; + min-width: 0; +} + +.stepTitle { + font-size: 0.83rem; + font-weight: 600; + line-height: 1.3; +} + +.stepDesc { + font-size: 0.78rem; + color: var(--ifm-font-color-secondary); + line-height: 1.4; + margin-top: 4px; +} + +/* ── Code step label ────────────────────────────────────────────────────── */ + +.codeStepLabel { + color: var(--ifm-color-primary); + font-weight: 700; +} diff --git a/src/components/index.js b/src/components/index.js index 07b4ad9ef6..9c82476efc 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -5,6 +5,7 @@ export { SdkLogos } from './elements/SdkLogos'; export { SdkLogosAsBlocks } from './elements/SdkLogosAsBlocks'; export { default as PhotoCarousel } from './elements/PhotoCarousel'; export { default as SdkTabs } from './elements/SdkTabs'; +export { default as ServerlessWorkerDemo } from './elements/ServerlessWorkerDemo'; // Formatting components export { default as DocsTable, NewDocsCell, DocsTableRow } from './formatting/DocsTable'; diff --git a/static/diagrams/serverless-worker-flow-dark.svg b/static/diagrams/serverless-worker-flow-dark.svg new file mode 100644 index 0000000000..a13c90f1e8 --- /dev/null +++ b/static/diagrams/serverless-worker-flow-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-flow.svg b/static/diagrams/serverless-worker-flow.svg new file mode 100644 index 0000000000..aeea36b1f4 --- /dev/null +++ b/static/diagrams/serverless-worker-flow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-lifecycle-dark.svg b/static/diagrams/serverless-worker-lifecycle-dark.svg new file mode 100644 index 0000000000..4ed19ca017 --- /dev/null +++ b/static/diagrams/serverless-worker-lifecycle-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/diagrams/serverless-worker-lifecycle.svg b/static/diagrams/serverless-worker-lifecycle.svg new file mode 100644 index 0000000000..6587b0f5d2 --- /dev/null +++ b/static/diagrams/serverless-worker-lifecycle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/files/temporal-cloud-serverless-worker-role.yaml b/static/files/temporal-cloud-serverless-worker-role.yaml new file mode 100644 index 0000000000..4b5546a5fb --- /dev/null +++ b/static/files/temporal-cloud-serverless-worker-role.yaml @@ -0,0 +1,97 @@ +# CloudFormation template for creating an IAM role that Temporal Cloud can assume to invoke Lambda functions. +AWSTemplateFormatVersion: '2010-09-09' +Description: Creates an IAM role that Temporal Cloud can assume to invoke multiple Lambda functions for Serverless Workers. + +Parameters: + AssumeRoleExternalId: + Type: String + Description: The External ID provided by Temporal Cloud + AllowedPattern: '[a-zA-Z0-9_+=,.@-]*' + MinLength: 5 + MaxLength: 45 + + LambdaFunctionARNs: + Type: CommaDelimitedList + Description: >- + Comma-separated list of Lambda function ARNs to invoke + (e.g., arn:aws:lambda:us-west-2:123456789012:function:worker-1,arn:aws:lambda:us-west-2:123456789012:function:worker-2) + + RoleName: + Type: String + Default: 'Temporal-Cloud-Serverless-Worker' + +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: "Temporal Cloud Configuration" + Parameters: + - AssumeRoleExternalId + - Label: + default: "Lambda Configuration" + Parameters: + - LambdaFunctionARNs + - RoleName + ParameterLabels: + AssumeRoleExternalId: + default: "External ID (provided by Temporal Cloud)" + LambdaFunctionARNs: + default: "Lambda Function ARNs (comma-separated list)" + RoleName: + default: "IAM Role Name" + +Resources: + TemporalCloudServerlessWorker: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub '${RoleName}-${AWS::StackName}' + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + AWS: + [ + arn:aws:iam::902542641901:role/wci-lambda-invoke, + arn:aws:iam::160190466495:role/wci-lambda-invoke, + arn:aws:iam::819232936619:role/wci-lambda-invoke, + arn:aws:iam::829909441867:role/wci-lambda-invoke, + arn:aws:iam::354116250941:role/wci-lambda-invoke + ] + Action: sts:AssumeRole + Condition: + StringEquals: + 'sts:ExternalId': [!Ref AssumeRoleExternalId] + Description: "The role Temporal Cloud uses to invoke Lambda functions for Serverless Workers" + MaxSessionDuration: 3600 # 1 hour + + TemporalCloudLambdaInvokePermissions: + Type: AWS::IAM::Policy + DependsOn: TemporalCloudServerlessWorker + Properties: + PolicyName: 'Temporal-Cloud-Lambda-Invoke-Permissions' + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - lambda:InvokeFunction + - lambda:GetFunction + Resource: !Ref LambdaFunctionARNs + Roles: + - !Sub '${RoleName}-${AWS::StackName}' + +Outputs: + RoleARN: + Description: The ARN of the IAM role created for Temporal Cloud + Value: !GetAtt TemporalCloudServerlessWorker.Arn + Export: + Name: !Sub "${AWS::StackName}-RoleARN" + + RoleName: + Description: The name of the IAM role + Value: !Ref RoleName + + LambdaFunctionARNs: + Description: The Lambda function ARNs that can be invoked + Value: !Join [", ", !Ref LambdaFunctionARNs] diff --git a/vale/styles/Temporal/terms.yml b/vale/styles/Temporal/terms.yml index 0504678fd2..ecf3921314 100644 --- a/vale/styles/Temporal/terms.yml +++ b/vale/styles/Temporal/terms.yml @@ -102,6 +102,8 @@ swap: temporal sdks: Temporal SDKs temporal server: Temporal Server '\bworker\b': Worker + serverless worker: Serverless Worker + serverless workers: Serverless Workers worker controller: Worker Controller '\bworkflow\b': Workflow timer: Timer diff --git a/vercel.json b/vercel.json index cf17b54fd2..68698a992c 100644 --- a/vercel.json +++ b/vercel.json @@ -6,6 +6,8 @@ }, "redirects": [ { + "source": "/evaluate/serverless-workers-demo", + "destination": "/evaluate/serverless-workers/demo", "source": "/develop/go/data-handling/large-payload-storage", "destination": "/develop/go/data-handling/external-storage", "permanent": true @@ -2033,6 +2035,11 @@ "source": "/develop/typescript/versioning", "destination": "/develop/typescript/workflows/versioning", "permanent": true + }, + { + "source": "/develop/go/workers/cloud-worker", + "destination": "/develop/go/workers/run-worker-process#connect-to-temporal-cloud", + "permanent": true } ] }