A cluster determines where workflow tasks can run. When you submit a job, its root task is submitted to a cluster, and only runners assigned to that cluster can claim it.
By default, subtasks are submitted to the same cluster as their parent task. Task code can submit individual subtasks to other clusters when a workflow needs to execute across different environments.
Workflow releases can be deployed to individual clusters. This lets release runners automatically load workflow code and execute compatible tasks from those releases.
Use Cases
Section titled “Use Cases”Use clusters to organize runners and workflow deployments into logical groups, which can help with:
- Targeting specific runners for a particular job
- Reserving a group of runners for specific purposes, such as running certain types of batch jobs
- Setting up different clusters for different environments (like development and production)
- Deploying different workflow releases to development, staging, or production clusters
Even within the same cluster, runners may have different capabilities. A direct runner advertises the tasks registered in its own process. A release runner advertises tasks from the workflow releases currently deployed to that cluster.
Cluster deployments
Section titled “Cluster deployments”A workflow release deployment maps one workflow release to one cluster. Deploying a release does not submit a job. It only makes the release available to release runners on that cluster.
A release runner can run multiple deployed releases for the same cluster. While it runs, it polls cluster deployment state and updates its task registrations when releases are deployed, updated, or removed.
Adding runners to a cluster
Section titled “Adding runners to a cluster”You can add direct runners to a cluster by specifying the cluster’s slug when starting the runner from your SDK code. You can add release runners to a cluster with tilebox runner start --cluster <cluster-slug>.
Each runner must always be assigned to a cluster. If no cluster is specified, Tilebox uses the default cluster.
Default Cluster
Section titled “Default Cluster”Each team has a default cluster that is automatically created for them. This cluster is used when no cluster is specified when starting a runner, deploying a release, or submitting a job. This is useful when you are just getting started and don’t need to create any custom clusters yet.
Managing Clusters
Section titled “Managing Clusters”Before starting a runner or submitting a job to a custom cluster, create the cluster. You can also list, fetch, and delete clusters as needed. The following sections explain how to do this.
To manage clusters, first instantiate a cluster client using the clusters method in the workflows client.
from tilebox.workflows import Client
client = Client()clusters = client.clusters()import "github.com/tilebox/tilebox-go/workflows/v1"
client := workflows.NewClient()clusterClient := client.ClustersCreating a Cluster
Section titled “Creating a Cluster”To create a cluster, use the create method on the cluster client and provide a name for the cluster.
cluster = clusters.create("testing")print(cluster)cluster := client.Clusters.Create("testing")fmt.Println(cluster)Cluster(slug='testing-CvufcSxcC9SKfe', display_name='testing')&{testing-CvufcSxcC9SKfe testing}Cluster Slug
Section titled “Cluster Slug”Each cluster has a unique identifier, combining the cluster’s name and an automatically generated identifier. Use this slug to reference the cluster for other operations, like submitting a job or subtasks.
Listing Clusters
Section titled “Listing Clusters”To list all available clusters, use the all method:
all_clusters = clusters.all()print(all_clusters)clusters, err := client.Clusters.List(ctx)if err != nil { slog.Error("failed to list clusters", slog.Any("error", err)) return}
for _, cluster := range clusters { fmt.Println(cluster)}[Cluster(slug='testing-CvufcSxcC9SKfe', display_name='testing'),Cluster(slug='production-EifhUozDpwAJDL', display_name='Production')]&{testing-CvufcSxcC9SKfe testing}&{production-EifhUozDpwAJDL Production}Fetching a Specific Cluster
Section titled “Fetching a Specific Cluster”To fetch a specific cluster, use the find method and pass the cluster’s slug:
cluster = clusters.find("testing-CvufcSxcC9SKfe")print(cluster)cluster, err := client.Clusters.Get(ctx, "testing-CvufcSxcC9SKfe")if err != nil { slog.Error("failed to get cluster", slog.Any("error", err)) return}fmt.Println(cluster)Cluster(slug='testing-CvufcSxcC9SKfe', display_name='testing')&{testing-CvufcSxcC9SKfe testing}Deleting a Cluster
Section titled “Deleting a Cluster”To delete a cluster, use the delete method and pass the cluster’s slug:
clusters.delete("testing-CvufcSxcC9SKfe")err := client.Clusters.Delete(ctx, "testing-CvufcSxcC9SKfe")Jobs Across Different Clusters
Section titled “Jobs Across Different Clusters”When submitting a job, you need to specify which cluster the job’s root task should be executed on. This allows you to direct the job to a specific set of runners. By default, all sub-tasks within a job are also submitted to the same cluster, but this can be overridden to submit sub-tasks to different clusters if needed. See the example below for a job that spans across multiple clusters.
from tilebox.workflows import Task, ExecutionContext, Client
class MultiCluster(Task): def execute(self, context: ExecutionContext) -> None: # this submits a task to the same cluster as the one currently executing this task same_cluster = context.submit_subtask(DummyTask()) other_cluster = context.submit_subtask( DummyTask(), # this task runs only on a runner in the "other-cluster" cluster cluster="other-cluster-As3dcSb3D9SAdK", # dependencies can be specified across clusters depends_on=[same_cluster], )
class DummyTask(Task): def execute(self, context: ExecutionContext) -> None: pass
# submit a job to the "testing" clusterclient = Client()job_client = client.jobs()job = job_client.submit( "my-job", MultiCluster(), cluster="testing-CvufcSxcC9SKfe",)package main
import ( "context"
"github.com/tilebox/tilebox-go/workflows/v1" "github.com/tilebox/tilebox-go/workflows/v1/subtask")
type MultiCluster struct{}
func (t *MultiCluster) Execute(ctx context.Context) error { // this submits a task to the same cluster as the one currently executing this task sameCluster, err := workflows.SubmitSubtask(ctx, &DummyTask{}) if err != nil { return err }
otherCluster, err := workflows.SubmitSubtask( ctx, &DummyTask{}, // this task runs only on a runner in the "other-cluster" cluster subtask.WithClusterSlug("other-cluster-As3dcSb3D9SAdK"), // dependencies can be specified across clusters subtask.WithDependencies(sameCluster), ) if err != nil { return err }
_ = otherCluster return nil}
type DummyTask struct{}
func main() { ctx := context.Background() client := workflows.NewClient()
// submit a job to the "testing" cluster _, _ = client.Jobs.Submit( ctx, "my-job", []workflows.Task{ &MultiCluster{}, }, job.WithClusterSlug("testing-CvufcSxcC9SKfe"), )}This workflow requires at least two runners to complete. One must be in the “testing” cluster, and the other must be in the “other-cluster” cluster.
If no runners are available in the “other-cluster,” the task submitted to that cluster will remain queued until a runner is available.
It won’t execute on a runner in the “testing” cluster, even if that runner has the DummyTask registered.