Skip to main content

Overview

Control Plane Task Runner is a self-hosted task queue and scheduler service similar to Google Cloud Tasks. It provides HTTP-based task enqueuing with automatic retry, delayed and scheduled execution, per-client rate limiting, and multi-queue support with priority levels. The admin API key comes from a secret you create before installing, and the API is not exposed to the internet by default.
/v1/enqueue has no authentication of any kind, and this is the reason api.public.enabled defaults to false. The endpoint does not merely accept registered clients: an unknown client_id is accepted and auto-registered rather than rejected — measured against the running image, where posting a never-seen ID returned status: enqueued and created that client. So a client ID is not a credential in any sense.With public access on, anyone who finds the endpoint can make a worker issue arbitrary outbound HTTP requests with a method, headers, and body of their choosing. No setting closes this — the application has no client authentication. If you need to accept task submissions from outside the GVC, front the API with an authenticating proxy of your own.
Upgrading an install created with 1.2.x is a breaking change. api.env.adminApiKey — which disabled admin authentication entirely when left at its shipped empty default — is replaced by api.admin.apiKeySecretName, a required prerequisite secret. api.public.enabled also flips from true to false. See Upgrading From 1.2.x.

What Gets Created

  • API Workload — HTTP endpoint for enqueuing tasks, managing clients, and health checks. Scales between 1 and 3 replicas by default. Internal-only unless you turn public access on.
  • Worker Workload — Background processor that picks tasks off the queue and executes them. Scales between 1 and 5 replicas by default. Always internal-only.
  • Redis with Sentinel — A Redis instance with Redis Sentinel for high-availability task persistence and coordination. Sentinel monitors the Redis primary and handles automatic failover.
  • Secret (optional, on by default) — A dictionary secret holding the bundled Redis and Sentinel passwords. The admin API key is not created here — it lives in the secret you create.
  • Identity & Policy — An identity bound to the workloads with reveal on exactly the secrets they read: the Redis secret and your admin key secret.

Architecture

The API workload receives task submissions over HTTP and pushes them into Redis. The Worker workload polls Redis and executes tasks by making outbound HTTP requests to the configured target URLs. Both workloads connect to Redis via Sentinel for failover resilience. A fresh install reached all four workloads ready in about 58 seconds, and enqueue-to-delivery latency was under 5 seconds.
This template does not create a GVC. You must deploy it into an existing GVC.

Prerequisites

The admin API key secret must exist before you install. The /admin/* endpoints create, edit, and delete clients and rate-limit tiers, and they are guarded by the X-Admin-Key header. Secrets are org-level, so no GVC flag is involved.
1

Create the admin key secret

An opaque secret with encoding plain whose payload is the key:
Set api.admin.apiKeySecretName to the name you used. Use printf, not echoecho appends a newline, which becomes part of the key and then has to be sent in every admin request.
2

Read it back later

-o yaml is required; without it the command prints the secret’s metadata table rather than its payload:
Admin authentication is enforced, and it is enforced on the internal path too. Verified on a live deployment: /admin/clients returned 401 with no header and 401 with a wrong key, and 200 with the correct one — both over the public endpoint and over in-GVC service DNS.
Setting api.admin.apiKeySecretName to "" disables admin authentication completely, and the chart therefore refuses to render while api.public.enabled is true. An empty name is permitted only for an internal-only deployment, and it is a deliberate act: measured through a port-forward with the name empty, /admin/clients returned the full client list with no header at all, and again with a nonsense key.
A missing prerequisite secret wedges the install rather than failing it. cpln helm install still exits 0 and reports success, the resources are created, and the API workload then never starts — while the other three come up normally. Because the container never ran, cpln logs returns zero lines, which reads as a broken platform rather than a missing prerequisite.The only diagnostic is status.versions[].message, which names the missing secret:
It is get-deployments — plain cpln workload get has no versions key at all. Creating the missing secret clears the wedge on its own, but slowly: recovery here was measured at 8 minutes 43 seconds, inside the 5.5–10.5 minute range seen across the catalog, so poll rather than time-boxing it. A forced redeployment shortcuts it to roughly 90 seconds.
Nothing else is required. The bundled Redis and Sentinel passwords are ordinary values — they are internal plumbing between the workloads and their own datastore, and nobody types them — but they are used exactly as written, so change them from their change-me-… defaults before installing.

Installation

Once the admin key secret exists, install the template using your preferred method:

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

The default values.yaml for this template:

API Workload

  • api.enabled — Enable or disable the API workload.
  • api.replicas — Min/max replica count for autoscaling (default: 1–3).
  • api.port — Container port (default 8080).
  • api.public.enabled — Expose the API to the public internet. Defaults to false; read Public Access and the Open Queue before turning it on.
  • api.public.pathPrefix — Optional path prefix for the public endpoint. Leave empty to serve from the root.
  • api.admin.apiKeySecretName — Name of your pre-created opaque secret whose payload is the admin API key, sent as the X-Admin-Key header. Required; "" disables admin authentication and is rejected while public access is on. See Prerequisites.
  • api.resources — CPU and memory allocated to each API replica.
  • api.env.logLevel — Log verbosity: debug, info, warn, or error.
  • api.env.connectRetries / api.env.retryIntervalSec — Redis connection retry behavior on startup.

Worker Workload

  • worker.enabled — Enable or disable the Worker workload.
  • worker.replicas — Min/max replica count for autoscaling (default: 1–5).
  • worker.resources — CPU and memory allocated to each Worker replica.
  • worker.env.concurrency — Number of tasks a single Worker replica can execute concurrently.
  • worker.env.taskTimeoutSec — Maximum duration in seconds before a task execution is considered failed (default 1800).
  • worker.env.maxRetry — Maximum number of retry attempts for a failed task.
  • worker.env.allowPrivateUrls — When true, allows tasks to target internal/private URLs. Workers fetch whatever URL a task carries, so leaving this false is the only bound on where an enqueued task can reach.
  • worker.env.cbFailureThreshold / worker.env.cbTimeoutSec — Circuit breaker settings to stop hammering failing endpoints.

Secrets and Redis

When createSecret is true, the template creates a dictionary secret named by secretName holding the Redis and Sentinel passwords. The redis.*.fromSecret fields are pre-wired to reference it. The admin API key is not in this secret — it is always your own opaque secret, independent of createSecret. When bringing your own secret (createSecret: false), create a dictionary secret holding the keys named by the passwordKey fields and point both fromSecret.name values at it. This path was verified end to end alongside the admin secret, including non-default key names.
Change redis.redisPassword and redis.sentinelPassword before the first install — they are used exactly as written. They stay ordinary values deliberately: they are internal plumbing for a datastore bundled with this one app, which nobody connects to by hand. Once the volumes are initialised, changing them requires uninstalling (which deletes the volume sets) and reinstalling.

Enqueuing Tasks

Submit tasks to the API. With the default api.public.enabled: false the endpoint below is the in-GVC address RELEASE_NAME-task-runner-api.GVC_NAME.cpln.local:8080; it is a public *.cpln.app hostname only if you turned public access on.
This request carries no credential, and none exists — see Public Access and the Open Queue.

Admin Endpoints

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

Rate Limiting

Rate limits are applied per client and configured via the admin API. Available tiers:

OpenTelemetry

To enable distributed tracing, set otelEndpoint in both api.env and worker.env. In your GVC configuration, ensure the Tracing Provider is set to Control Plane, then use the default HTTP collector endpoint:

Connecting

Find the public endpoint under status.canonicalEndpoint of cpln workload get RELEASE_NAME-task-runner-api --gvc GVC_NAME -o yaml.

Upgrading From 1.2.x

Two behaviours change, and both will break an existing workflow if you relied on the old defaults.
Admin authentication is now enforced. 1.2.x shipped api.env.adminApiKey: "", which left /admin/* unauthenticated on a public API — anyone who found the endpoint could create clients and change rate-limit tiers. Create the secret with the same key you were using and your admin scripts keep working; create a new one and every caller must be updated.
A helm upgrade that still carries either removed key is rejected before anything is applied, and so is an empty apiKeySecretName while public access is on. A real cpln helm upgrade carrying the old keys failed at render, created no Helm revision, and left the running release healthy and untouched:
To upgrade an existing install:
1

Create the admin key secret

Follow Prerequisites, putting 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. Leaving it at the new default closes the unauthenticated /v1/enqueue endpoint to the internet — read Public Access and the Open Queue before setting it back to true.
4

Upgrade

The first upgrade after an install also re-applies the bundled Redis even with identical values, restarting it; the API returns errors for a minute or two while Redis comes back. Later upgrades are clean.
The bundled Redis and Sentinel passwords keep whatever you already set. Only their defaults changed, which affects fresh installs.

Public Access and the Open Queue

api.public.enabled is false by default. Understand exactly what turning it on exposes:
  • /v1/enqueue requires no credential whatsoever. There is no header, token, or signature to supply.
  • An unknown client_id is auto-registered, not rejected. Posting a never-before-seen ID returns status: enqueued and creates the client with the default tier. A client ID is therefore not a secret and not a control.
  • The consequence is a request relay. Anyone reaching the endpoint can make a worker issue outbound HTTP with the method, headers, and body they choose. worker.env.allowPrivateUrls: false keeps those requests off internal addresses, which is the only bound.
  • /metrics is served on the same port, unauthenticated, and its labels enumerate every client_id.
No setting fixes this; the application has no client authentication. If you need submissions from outside the GVC, put an authenticating proxy in front of the API and leave api.public.enabled at false.
Disabling public access surfaces as 421 Misdirected Request, not 403. After the change the public hostname stops routing entirely — a 421 here means the knob worked, not that something is broken. Access changes take roughly 30 seconds to a few minutes to propagate, so re-test over that window before concluding otherwise.

Important Notes

  • Create the admin key secret before installing. A missing prerequisite secret wedges the deployment with no log output at all; Prerequisites gives the one command that diagnoses it.
  • /v1/enqueue is unauthenticated and auto-registers unknown client IDs — this is why public access is off by default. See Public Access and the Open Queue.
  • /metrics is unauthenticated and its labels enumerate every client_id.
  • Change the change-me-… Redis and Sentinel passwords before the first install. Changing them later requires uninstalling and reinstalling.
  • The first Helm upgrade after an install re-applies the bundled Redis even with identical values, which restarts it; the API returns errors for a minute or two while Redis comes back. Later upgrades are clean.
  • Access changes take roughly 30 seconds to a few minutes to propagate, so a freshly toggled api.public.enabled looks unchanged at first — and once disabled, the public hostname returns 421, not 403.

External References

OpenTelemetry Documentation

OpenTelemetry distributed tracing documentation

CPLN Task Runner Template

View the source files, default values, and chart definition