Skip to main content

Overview

Control Plane Task Runner is a self-hosted HTTP task queue and scheduler, similar to Google Cloud Tasks. You enqueue a task over HTTP and the workers deliver it to its target URL, with automatic retries, delayed and scheduled execution, per-client rate limiting and a circuit breaker. This template deploys the API, the workers, and the Redis Sentinel cluster they persist to.
This template deploys into an existing GVC that you already have. It does not create, provision or manage a GVC.
/v1/enqueue has no authentication, which is why api.public.enabled defaults to false. An unknown client_id is accepted and auto-registered rather than rejected, so a client ID is not a credential. With public access on, anyone who finds the endpoint can make a worker send outbound HTTP requests with a method, headers and body of their choosing. See Public Access and the Open Queue.

What Gets Created

The policies grant only the reveal permission on secrets. The template’s identities have no cloud access and no permission on any other Control Plane resource.

Prerequisites

Three secrets must exist before you install: the Redis password, the Sentinel password and the admin API key. Secrets are org-level, so no GVC flag is involved.
1

Create the Redis and Sentinel credentials secrets

Two dictionary secrets, each with the key password: one holds the bundled Redis password, the other the Sentinel password. Redis, Sentinel, the API and the worker read both. The names below are the chart defaults for redis.redis.auth.fromSecret.name and redis.sentinel.auth.fromSecret.name:
Upgrading from 1.3.0? Do not generate new passwords; see Upgrading From 1.3.0.
2

Create the admin key secret

The /admin/* endpoints create, edit and delete clients and rate-limit tiers, and they are guarded by the X-Admin-Key header. The key is an opaque secret with encoding plain whose payload is the key itself:
Use printf, not echo. A trailing newline from echo becomes part of the key and then has to be sent in every admin request. Set api.admin.apiKeySecretName to this name.
3

Read the key back later

Pass -o yaml. A bare cpln secret reveal prints only a summary table, not the payload:
Create the secrets before installing, or the deployment wedges silently. An install that names a secret which does not exist still reports success, and every workload that reads it never starts. The container never runs, so cpln logs returns zero lines. The one place the reason appears is status.versions[].message:
Use get-deployments. Plain cpln workload get has no versions key. Run the same command for RELEASE_NAME-redis, RELEASE_NAME-sentinel and RELEASE_NAME-task-runner-worker when a credentials secret is missing. Once the secret exists the workload recovers on its own after several minutes, or run cpln workload force-redeployment RELEASE_NAME-task-runner-api --gvc GVC_NAME to skip the wait.

Installation

Create the three secrets first, then install with the admin key secret’s name. If you named the credentials secrets differently from the defaults, also set redis.redis.auth.fromSecret.name and redis.sentinel.auth.fromSecret.name:
For the other install methods, follow the instructions for your preferred tool:

UI

Browse, install, and manage templates visually

CLI

Manage templates from your terminal

Terraform

Declare templates in your Terraform configurations

Pulumi

Declare templates in your Pulumi programs

Configuration

Image

The same image runs both the API and the worker:

API Workload

Setting api.admin.apiKeySecretName to "" turns admin authentication off entirely. The chart permits that only while api.public.enabled is false.

Worker Workload

Workers fetch whatever URL a task carries. allowPrivateUrls: false is what keeps an enqueued task off internal addresses.

Secrets and Redis

Redis and Sentinel read their passwords from the prerequisite credentials secrets:
The admin API key is never in these secrets. It is always its own opaque secret, named by api.admin.apiKeySecretName.

Connecting

Read the public endpoint, when enabled:
To reach the private API from your machine, open a tunnel and check readiness. This uses the same /health/ready path as the workload readiness probe:
To see what happened to a delivered task, read the worker logs. The second form filters on the server, so put any text you expect in the line between the quotes:

Enqueuing Tasks

Post a task to /v1/enqueue. With the default private API, send it from a workload in the same GVC to http://RELEASE_NAME-task-runner-api.GVC_NAME.cpln.local:8080, or through the tunnel above to http://127.0.0.1:8080:
This request carries no credential, and none exists.

Admin Endpoints

Every /admin/* request needs the X-Admin-Key header, whose value is the payload of your api.admin.apiKeySecretName secret:

Rate Limiting

Rate limits apply per client, and a client tier is assigned through the admin API:

OpenTelemetry

Set otelEndpoint under api.env and worker.env to export traces. In the GVC settings, set the Tracing Provider to Control Plane, then use the built-in HTTP collector endpoint:

Operations

Backing Up

The Redis volume set RELEASE_NAME-redis-vs takes a snapshot every day and keeps each one for 7 days. List the snapshots:

Restoring a Backup

A restore procedure for this template has not been verified. Do not rely on the snapshots as a tested recovery path for queued tasks.

Rotating the Admin Key

Apply the whole secret with a new payload, confirm it, then force a redeployment of the API. A running replica keeps the old key until it is redeployed:
Every admin caller must then send the new key.

Rotating the Redis and Sentinel Passwords

Redis reads its password from the secret at every start, but Sentinel keeps the passwords it first started with on its volume, so editing the secrets alone locks the API and worker out. Apply the new passwords live on every node first, then restart the API and worker. rx and sx run a command on one Redis node or Sentinel (LOCATION is your GVC’s location; nodes and Sentinels are numbered from 0 up to redis.redis.replicas and redis.sentinel.replicas minus one).
  1. Accept the new passwords alongside the old ones on every Redis node and Sentinel:
  1. Put the new passwords in the two secrets (key password). For each secret, export it, change only password, apply it and read it back:
  1. Restart the API and worker, which keep the old passwords until they restart:
  1. Once get-deployments shows both at a higher version, alone and ready, retire the old passwords and check that the Sentinels can still fail over:
If both fromSecret.name values point at one secret, edit that one secret in step 2, each password under its own key.

Upgrading From 1.2.x

Two defaults change, and both break an existing workflow that relied on the old behaviour. An upgrade that still carries api.env.adminApiKey or redis.admin.fromSecret, or an empty apiKeySecretName while public access is on, is rejected at render before anything is applied.
1

Create the admin key secret

Follow Prerequisites. Put the key you use today into it so existing admin callers keep working.
2

Drop the removed keys from your values

Remove api.env.adminApiKey and redis.admin.fromSecret, and set api.admin.apiKeySecretName instead.
3

Decide about public access

api.public.enabled now defaults to false. Read Public Access and the Open Queue before setting it back to true.
4

Upgrade

The first upgrade after an install re-applies the bundled Redis even with identical values, which restarts it, and the API returns errors briefly while Redis comes back. Later upgrades are clean.
Then follow Upgrading From 1.3.0: the Redis and Sentinel passwords your 1.2.x release runs with (mypassword if never changed) go into the two credentials secrets.

Upgrading From 1.3.0

1.4.0 no longer takes the Redis and Sentinel passwords as values, and no longer creates the task-runner-secrets secret. Use the CURRENT passwords: Redis and Sentinel keep the ones they started with, so new ones would lock the API and worker out. To change them, upgrade first, then follow Rotating the Redis and Sentinel Passwords. With createSecret: true (the default), the upgrade deletes task-runner-secrets (or the secret named by your secretName, if you changed it), so copy the passwords into two new secrets before upgrading:
1

Read the current passwords

redis-password is the Redis password, redis-sentinel-password the Sentinel password. Use the name you set in secretName if you changed it:
2

Create the two credentials secrets with those values

3

Drop the removed keys from your values

Remove createSecret, secretName, redis.redisPassword and redis.sentinelPassword, and point redis.redis.auth.fromSecret.name and redis.sentinel.auth.fromSecret.name at the new secrets (the defaults above). The chart refuses to render while any of the removed keys is still set, and refuses the name task-runner-secrets; do not reuse a custom secretName either, because the upgrade deletes that secret too.
4

Upgrade

With createSecret: false and a secret of your own, keep it: remove the same four keys, keep both fromSecret.name values pointing at your secret, and set both passwordKey values to its key names explicitly (redis-password and redis-sentinel-password if you kept the 1.3.0 defaults; 1.4.0 defaults both to password). Nothing is deleted.

Public Access and the Open Queue

api.public.enabled is false by default. Turning it on exposes the following:
  • /v1/enqueue requires no credential. There is no header, token or signature to supply.
  • An unknown client_id is auto-registered, not rejected. A client ID is therefore not a secret and not a control.
  • The API becomes a request relay. Anyone reaching it can make a worker send outbound HTTP with the method, headers and body they choose. worker.env.allowPrivateUrls: false keeps those requests off internal addresses.
  • /metrics is unauthenticated and its labels list every client_id.
No setting closes this, because the application has no client authentication. If you need submissions from outside the GVC, put an authenticating proxy of your own in front of the API and leave api.public.enabled at false. Access changes can take several minutes to take effect, so re-test over that window.

Scaling and Availability

The API and worker are serverless workloads that autoscale on concurrent requests within api.replicas and worker.replicas. Raise worker.replicas.max or worker.env.concurrency for more delivery throughput. Redis runs as three replicas with three Sentinels, which elect a new primary if the current one fails.

Troubleshooting

Cause: a prerequisite secret does not exist: the one named by api.admin.apiKeySecretName, or a Redis or Sentinel credentials secret. get-deployments shows The secret SECRET_NAME no longer exists. Workload updates are paused until the secret is added or the reference to the secret removed.Fix: create the secret as in Prerequisites. The workload recovers on its own, or run cpln workload force-redeployment RELEASE_NAME-task-runner-api --gvc GVC_NAME.
Cause: api.admin.apiKeySecretName is "" and public access is on. The chart refuses to publish unauthenticated admin endpoints.Fix: set api.admin.apiKeySecretName to your admin key secret, or set api.public.enabled: false.
Cause: your values still carry the 1.2.x admin key (api.env.adminApiKey or redis.admin.fromSecret).Fix: follow Upgrading From 1.2.x.
Cause: your values still carry createSecret, secretName, redis.redisPassword or redis.sentinelPassword.Fix: follow Upgrading From 1.3.0.
Cause: the X-Admin-Key header is missing or does not match the secret payload, for example because the key was created with echo and carries a trailing newline, or the secret was rotated without a redeployment.Fix: read the key with cpln secret reveal SECRET_NAME -o yaml, and redeploy the API after any rotation.
Cause: worker.env.allowPrivateUrls is false, so workers refuse private and internal targets.Fix: set it to true only if you accept that any enqueued task can then reach anything the worker can route to.

Important Notes

  • Create the admin key secret and the Redis and Sentinel credentials secrets before installing. A missing secret wedges the workloads that read it with no log output; the reason is in status.versions[].message from cpln workload get-deployments.
  • Leave api.public.enabled at false unless an authenticating proxy fronts the API. /v1/enqueue is unauthenticated and auto-registers unknown client IDs.
  • Never change the Redis or Sentinel password by editing the secrets alone. Sentinel keeps the passwords it first started with on its volume, so that locks the API and worker out. Follow Rotating the Redis and Sentinel Passwords.
  • Force a redeployment after rotating the admin key. A running replica keeps the old key.

External References

CPLN Task Runner Template

View the source files, default values, and chart definition

Task Runner Image

The container image used by the API and worker

Control Plane Secrets

Secret types and how workloads reference them

OpenTelemetry Documentation

OpenTelemetry distributed tracing documentation