Skip to content

Add standalone function-worker component (split the worker out of the broker)#698

Merged
lhotari merged 5 commits into
apache:masterfrom
lhotari:lh-function-worker-separation
Jun 26, 2026
Merged

Add standalone function-worker component (split the worker out of the broker)#698
lhotari merged 5 commits into
apache:masterfrom
lhotari:lh-function-worker-separation

Conversation

@lhotari

@lhotari lhotari commented Jun 25, 2026

Copy link
Copy Markdown
Member

Motivation

By default the Pulsar Functions worker runs embedded in the broker. Running it as a separate
component
separates the function worker workload from the brokers, which is useful for:

  • Operational reasons — scale, schedule and manage the function worker independently of the brokers.
  • Security reasons — a reduced attack surface. Functions run user code, so isolating the worker means a
    compromise of the function worker (for example a remote code execution) does not directly impact the
    brokers.

Modifications

Add a standalone function-worker component, enabled with components.function_worker. When enabled, the
broker's embedded function worker is disabled completely (functionsWorkerEnabled=false) and a dedicated
function-worker StatefulSet runs bin/pulsar functions-worker.

  • New component templates modeled on the broker templates: function-worker statefulset, configmap (generates
    functions_worker.yml via the PF_ env mechanism), service + headless service, service-account, RBAC,
    podmonitor, pdb, and the _function_worker.tpl helpers.
  • New values: components.function_worker, images.function_worker, tls.function_worker, and the
    function_worker config block.
  • The worker is broker-attached (it connects to the broker over pulsarServiceUrl/pulsarWebServiceUrl
    and keeps function metadata in system topics). Function instances run with the Kubernetes runtime (one pod
    per instance), so the worker has its own RBAC.
  • broker-configmap forces functionsWorkerEnabled=false and broker-statefulset uses the Parallel
    podManagementPolicy when the standalone component is enabled.
  • proxy-configmap routes the Functions REST API to the function-worker service
    (functionWorkerWebServiceURL[TLS]); tls-certs-internal issues its server certificate.
  • Docs + example (examples/values-function-worker.yaml); the render-all templates-all-values-patch1
    overlay exercises the new templates (with TLS).

This change is independent of package storage. (For uploaded function packages on Oxia, see the separate
broker FileSystemPackagesStorage change, #697.)

Verifying this change

  • helm lint and helm template pass locally for: defaults (output unchanged), components.function_worker=true
    (the component renders, the broker's embedded worker is disabled, and the proxy routes to the new service),
    and the render-all templates-all-values + patch1 overlay (function-worker on, with TLS).
  • Make sure that the change passes the CI checks.

lhotari and others added 5 commits June 25, 2026 00:29
… broker)

Run the Pulsar Functions worker as a separate component instead of embedded in
the broker, enabled with components.function_worker. When enabled, the broker's
embedded function worker is disabled completely.

Running the function worker separately separates its workload from the brokers,
which is useful for operational reasons (scale and manage it independently of
the brokers) and security reasons (a reduced attack surface: functions run user
code, so a compromise of the function worker -- for example a remote code
execution -- does not directly impact the brokers).

The worker is broker-attached (it connects to the broker and keeps function
metadata in system topics). Function instances run with the Kubernetes runtime
(one pod per instance), so the worker has its own RBAC.

- New component templates modeled on the broker: function-worker statefulset,
  configmap (functions_worker.yml via the PF_ env mechanism), service + headless
  service, service-account, rbac, podmonitor, pdb, and the _function_worker.tpl
  helpers. New values: components.function_worker, images.function_worker,
  tls.function_worker, and the function_worker block.
- broker-configmap forces functionsWorkerEnabled=false and broker-statefulset
  uses the Parallel podManagementPolicy when the standalone component is enabled.
- proxy-configmap routes the Functions REST API to the function-worker service
  (functionWorkerWebServiceURL[TLS]); tls-certs-internal issues its cert.
- Docs + example (values-function-worker.yaml); render-all patch1 exercises the
  new templates.
Add a CI install scenario that runs the Pulsar Functions worker as a
separate component (components.function_worker) instead of embedded in the
broker, and exercises it end to end with the existing function smoke test
(components.functions=true gates ci::test_pulsar_function).

The new .ci/clusters/values-function-worker.yaml sets toolset.useProxy=true:
once the broker's embedded worker is disabled, the broker returns HTTP 409 for
Functions admin REST calls and Pulsar has no broker-side redirect, so admin
calls must reach the standalone worker through the proxy (which the chart
already wires via functionWorkerWebServiceURL). The CI default is
toolset.useProxy=false (toolset -> broker direct), hence the override here.

Function packages are stored in BookKeeper via DistributedLog (default
ZooKeeper+BookKeeper); the worker fetches the metadata/bookkeeper service
URIs from the broker's internal-configuration endpoint, so no
FileSystemPackagesStorage is required.

Also:
- disable the function-worker PodMonitor in .ci/values-common.yaml (CI omits
  the Prometheus Operator CRD, mirroring every other component), otherwise
  helm install --wait fails on the missing PodMonitor kind;
- pin the spawned function-instance pods to small resource requests so they
  schedule on the single kind node (mirrors the embedded-worker scenarios);
- fix the misleading function_worker.rbac.limit_to_namespace default comments
  (the default is namespace-scoped) and document in the example that function
  admin clients must reach the worker via the proxy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZUbdHbb856wdmKxBU4V48
…separation

# Conflicts:
#	.github/workflows/pulsar-helm-chart-ci.yaml
…rker, not the broker

With the standalone function worker, the broker's embedded worker is disabled,
so the broker returns HTTP 409 ("Pulsar Function Worker is not enabled") for any
Functions admin REST call. The Kubernetes-runtime function instance pod runs
`pulsar-admin --admin-url <pulsarAdminUrl> functions download ...` before exec'ing
the instance, and pulsarAdminUrl was pointed at the broker — so every function
instance crashed in a loop on the download step (HTTP 409) and never reached
numRunning, as seen in the new Separate Function Worker CI scenario.

Point the instance's functionRuntimeFactoryConfigs/kubernetesContainerFactory
pulsarAdminUrl at the standalone worker's web service (which serves the Functions
REST API, including download). pulsarServiceUrl stays on the broker for the
instance's input/output/state topics. Adds a worker TLS variant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RZUbdHbb856wdmKxBU4V48
@lhotari lhotari merged commit d5624a8 into apache:master Jun 26, 2026
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant