Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/workflows/kubernetes-workers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Kubernetes SDK worker guide

on:
pull_request:
paths:
- docs/deployment.md
- tests/kubernetes-workers/**
- .github/workflows/kubernetes-workers.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: kubernetes-worker-guide-${{ github.ref }}
cancel-in-progress: true

jobs:
published-workers:
name: Published SDK workers on Kubernetes
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- name: Install verified kind and kubectl binaries
shell: bash
run: |
install_dir="${RUNNER_TEMP}/worker-guide-tools"
mkdir -p "$install_dir"
curl --proto '=https' --proto-redir '=https' -fsSLo "$install_dir/kind" https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
curl --proto '=https' --proto-redir '=https' -fsSLo "$install_dir/kind.sha256" https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64.sha256sum
expected="$(cut -d ' ' -f 1 "$install_dir/kind.sha256")"
printf '%s %s\n' "$expected" "$install_dir/kind" | sha256sum --check
curl --proto '=https' --proto-redir '=https' -fsSLo "$install_dir/kubectl" https://dl.k8s.io/release/v1.35.0/bin/linux/amd64/kubectl
curl --proto '=https' --proto-redir '=https' -fsSLo "$install_dir/kubectl.sha256" https://dl.k8s.io/release/v1.35.0/bin/linux/amd64/kubectl.sha256
expected="$(cat "$install_dir/kubectl.sha256")"
printf '%s %s\n' "$expected" "$install_dir/kubectl" | sha256sum --check
chmod +x "$install_dir/kind" "$install_dir/kubectl"
echo "$install_dir" >> "$GITHUB_PATH"
- name: Exercise the documented Deployment with published artifacts
env:
QUALIFICATION_OUTPUT: ${{ runner.temp }}/worker-guide-evidence
run: python3 tests/kubernetes-workers/qualify.py
- name: Retain bounded qualification evidence
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: kubernetes-worker-guide-evidence
path: ${{ runner.temp }}/worker-guide-evidence
retention-days: 3
if-no-files-found: ignore
86 changes: 86 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,92 @@ support-led or tracked separately from the raw-manifest contract. Use the
separately versioned Helm path above when you want Helm-managed installation
and upgrades.

## Running SDK workers on Kubernetes {#sdk-workers-on-kubernetes}

Deploy your application workers separately from the Server's Laravel queue worker. Build an image containing your registered PHP, Python or Rust handlers and its locked SDK dependencies. Use an exec-form entrypoint so the worker process receives `SIGTERM`. Replace the image below with that build's immutable digest.

For workers in the same cluster, use the Server Service's actual name, namespace and port. The raw manifests expose `http://durable-workflow-server.durable-workflow.svc.cluster.local:8080`. A Helm release can use a different Service name. Workers outside the cluster need the reachable ingress or load balancer's HTTPS URL and normal certificate verification. Keep long-poll and transport budgets aligned with [the timeout guidance](#worker-long-poll-timeout-budgets).

Create a Secret in the worker Pod's Kubernetes namespace from your existing worker credential. Keep the token out of the manifest and image. The Server namespace is a separate product boundary from the Kubernetes namespace.

```bash
kubectl -n durable-workflow create secret generic orders-worker-auth --from-literal=token="$DW_WORKER_TOKEN"
```

Save this as `orders-worker.yaml`. These environment names are application configuration: your entrypoint must pass them to the SDK client and Worker. Give each live process its own worker ID and register the handlers for the configured queue.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-worker
namespace: durable-workflow
spec:
replicas: 2
selector:
matchLabels:
app: orders-worker
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: orders-worker
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 75
containers:
- name: worker
image: registry.example.com/acme/orders-worker:release-id
env:
- name: DW_SERVER_URL
value: http://durable-workflow-server.durable-workflow.svc.cluster.local:8080
- name: DW_NAMESPACE
value: default
- name: DW_TASK_QUEUE
value: orders
- name: DW_WORKER_ID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: DW_WORKER_TOKEN
valueFrom:
secretKeyRef:
name: orders-worker-auth
key: token
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
```

Apply the Deployment and wait for its rollout:

```bash
kubectl apply -f orders-worker.yaml
kubectl -n durable-workflow rollout status deployment/orders-worker
```

Replica and resource values are starting examples, not capacity guarantees. Tune them with actual task duration, memory use, downstream limits and [worker admission budgets](./polyglot/task-queue-admission.md). A Deployment becoming available does not prove SDK registration or useful work. Verify fresh worker registrations and the expected build/queue, then start and complete a representative workflow. An SDK worker does not acquire an HTTP health endpoint merely by running in Kubernetes. Use an application-owned probe when you have one, and do not use Server health as a worker liveness check.

### Delivering SIGTERM and waiting for cleanup {#sdk-worker-pod-shutdown}

| SDK | Worker process shutdown |
| --- | --- |
| PHP | Use the managed `Worker::run()` loop with `pcntl` installed. Its signal handler requests shutdown, then the active synchronous task reaches a boundary before managed cleanup and deregistration. A custom loop around `tick()` does not supply that lifecycle. See [PHP graceful shutdown](https://php.durable-workflow.com/operate/graceful-shutdown/). |
| Python | Wire `SIGTERM` to the same Worker's shutdown path and await both `Worker.stop()` and its run task. Set an explicit `shutdown_timeout`. Accepted work shares that drain deadline. A running synchronous thread cannot be forcibly stopped by cancelling its await, and an unconfirmed stop remains an error. Use the [separate-process example and signal wiring](https://python.durable-workflow.com/guides/separate-workers/) and [lifecycle contract](https://python.durable-workflow.com/reference/worker/). |
| Rust | Pass a future that resolves on Unix `SIGTERM` to `Worker::run_until(...)` and await its result. A Ctrl-C-only future does not handle the Pod's termination signal. Keep asynchronous handlers yielding and bound blocking work separately. See [Worker::run_until](https://rust.durable-workflow.com/durable_workflow/struct.Worker.html#method.run_until). |

Give `terminationGracePeriodSeconds` enough time for the installed SDK's poll, accepted-task shutdown and HTTP/session/deregistration cleanup. The example's 75 seconds must be checked against your application's actual bounds. Kubernetes counts any `preStop` hook within that same grace period and force-stops remaining processes after expiry. A sleep hook alone neither asks the SDK to drain nor extends its deadline. See the [Kubernetes Pod termination flow](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination-flow).

Rehearse termination while idle and during bounded work. Treat cleanup errors, a retained registration or a forced kill as incomplete shutdown. A replacement worker continues durable work according to its recorded leases, retries and deadlines. Stopping a Pod does not cancel a workflow or undo an external side effect. Before changing builds, follow [worker build-ID rollout](./polyglot/worker-build-id-rollout.md) and retain its compatibility, replay and rollback checks.

## Single-region high availability and failover

Single-region HA failover is currently an **unverified, support-led** contract
Expand Down
86 changes: 86 additions & 0 deletions i18n/de/docusaurus-plugin-content-docs/current/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,92 @@ curl -H "Authorization: Bearer $DW_ADMIN_TOKEN" http://localhost:8080/api/cluste

Einregionen-HA-Failover mit verwalteter Datenbank, verwaltetem Redis, API-Knotenverlust, Worker-Verlust und Schedulerneustart bleibt auf dem Rohmanifestweg ebenso wie im kleinen Cluster supportbegleitet, solange die Veröffentlichungsnachweisprüfung geschlossen ist. Der beabsichtigte Vertrag erfordert die Regeln für Bereitschaft, Singleton-Scheduler und gemeinsame Basissysteme unter [Hochverfügbarkeit und Failover in einer Region](#single-region-high-availability-and-failover). Providerspezifische Load Balancer, Speicherklassen, Netzwerkrichtlinien, Aktiv/Aktiv über mehrere Regionen und selbst gehostetes bedienungsfreies regionales Failover bleiben supportbegleitet oder werden separat vom Rohmanifestvertrag verfolgt. Verwenden Sie den separat versionierten Helm-Weg oben für Helm-verwaltete Installation und Upgrades.

## SDK-Worker auf Kubernetes ausführen {#sdk-workers-on-kubernetes}

Stellen Sie die Worker Ihrer Anwendung getrennt vom Laravel-Queue-Worker des Servers bereit. Bauen Sie ein Image mit Ihren registrierten PHP-, Python- oder Rust-Handlern und den im Lockfile festgelegten SDK-Abhängigkeiten. Verwenden Sie einen Entrypoint in Exec-Form, damit der Worker-Prozess `SIGTERM` erhält. Ersetzen Sie das folgende Image durch den unveränderlichen Digest dieses Builds.

Verwenden Sie für Worker im selben Cluster den tatsächlichen Namen, Namespace und Port des Server-Service. Die unbearbeiteten Manifeste stellen `http://durable-workflow-server.durable-workflow.svc.cluster.local:8080` bereit. Ein Helm-Release kann einen anderen Service-Namen verwenden. Worker außerhalb des Clusters benötigen die erreichbare HTTPS-URL des Ingress oder Load Balancers mit normaler Zertifikatsprüfung. Stimmen Sie Long-Poll- und Transportbudgets mit den [Timeout-Hinweisen](#worker-long-poll-timeout-budgets) ab.

Erstellen Sie im Kubernetes-Namespace des Worker-Pods ein Secret aus dessen vorhandenen Zugangsdaten. Halten Sie den Token aus Manifest und Image heraus. Der Server-Namespace ist eine eigene Produktgrenze und vom Kubernetes-Namespace getrennt.

```bash
kubectl -n durable-workflow create secret generic orders-worker-auth --from-literal=token="$DW_WORKER_TOKEN"
```

Speichern Sie dies als `orders-worker.yaml`. Diese Umgebungsvariablen sind Anwendungskonfiguration: Ihr Entrypoint muss sie an SDK-Client und Worker übergeben. Geben Sie jedem gleichzeitig laufenden Prozess eine eigene Worker-ID und registrieren Sie die Handler für die konfigurierte Queue.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-worker
namespace: durable-workflow
spec:
replicas: 2
selector:
matchLabels:
app: orders-worker
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: orders-worker
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 75
containers:
- name: worker
image: registry.example.com/acme/orders-worker:release-id
env:
- name: DW_SERVER_URL
value: http://durable-workflow-server.durable-workflow.svc.cluster.local:8080
- name: DW_NAMESPACE
value: default
- name: DW_TASK_QUEUE
value: orders
- name: DW_WORKER_ID
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: DW_WORKER_TOKEN
valueFrom:
secretKeyRef:
name: orders-worker-auth
key: token
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
```

Wenden Sie das Deployment an und warten Sie auf den Rollout:

```bash
kubectl apply -f orders-worker.yaml
kubectl -n durable-workflow rollout status deployment/orders-worker
```

Replikazahlen und Ressourcen sind Ausgangsbeispiele, keine Kapazitätsgarantien. Passen Sie sie an tatsächliche Task-Dauer, Speicherbedarf, Grenzen nachgelagerter Dienste und die [Worker-Zulassungsbudgets](./polyglot/task-queue-admission.md) an. Ein verfügbares Deployment belegt weder die SDK-Registrierung noch erfolgreiche Arbeit. Prüfen Sie aktuelle Worker-Registrierungen sowie erwarteten Build und Queue und starten und beenden Sie anschließend einen repräsentativen Workflow. Ein SDK-Worker erhält durch Kubernetes nicht automatisch einen HTTP-Health-Endpunkt. Verwenden Sie eine anwendungseigene Probe, falls vorhanden, und nicht die Server-Gesundheit als Liveness-Prüfung des Workers.

### SIGTERM zustellen und die Bereinigung abwarten {#sdk-worker-pod-shutdown}

| SDK | Beenden des Worker-Prozesses |
| --- | --- |
| PHP | Verwenden Sie die verwaltete Schleife `Worker::run()` mit installiertem `pcntl`. Ihr Signal-Handler fordert das Herunterfahren an. Der aktive synchrone Task erreicht danach eine Grenze vor der verwalteten Bereinigung und Deregistrierung. Eine eigene Schleife um `tick()` stellt diesen Lebenszyklus nicht bereit. Siehe [geordnetes Herunterfahren in PHP](https://php.durable-workflow.com/operate/graceful-shutdown/). |
| Python | Verbinden Sie `SIGTERM` mit dem Shutdown-Pfad desselben Workers und warten Sie sowohl auf `Worker.stop()` als auch auf dessen Run-Task. Setzen Sie `shutdown_timeout` ausdrücklich. Angenommene Arbeit teilt sich diese Frist zum Leeren. Das Abbrechen eines Await kann keinen laufenden synchronen Thread zwangsweise stoppen. Ein unbestätigter Stopp bleibt ein Fehler. Verwenden Sie das [Beispiel mit getrennten Prozessen und Signalbehandlung](https://python.durable-workflow.com/guides/separate-workers/) und den [Lebenszyklusvertrag](https://python.durable-workflow.com/reference/worker/). |
| Rust | Übergeben Sie `Worker::run_until(...)` ein Future, das bei Unix-`SIGTERM` abgeschlossen wird, und warten Sie auf dessen Ergebnis. Ein Future nur für Strg+C behandelt das Beendigungssignal des Pods nicht. Asynchrone Handler müssen Kontrolle abgeben; begrenzen Sie blockierende Arbeit gesondert. Siehe [Worker::run_until](https://rust.durable-workflow.com/durable_workflow/struct.Worker.html#method.run_until). |

Planen Sie in `terminationGracePeriodSeconds` genug Zeit für Polling, das Beenden angenommener Tasks und die HTTP-, Session- und Registrierungsbereinigung des installierten SDKs ein. Prüfen Sie die 75 Sekunden des Beispiels gegen die tatsächlichen Grenzen Ihrer Anwendung. Kubernetes rechnet jeden `preStop`-Hook auf dieselbe Frist an und beendet verbleibende Prozesse danach zwangsweise. Ein reiner Sleep-Hook fordert weder das Leeren laufender Arbeit an noch verlängert er die SDK-Frist. Siehe den [Ablauf der Pod-Beendigung in Kubernetes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination-flow).

Erproben Sie die Beendigung im Leerlauf und während zeitlich begrenzter Arbeit. Behandeln Sie Bereinigungsfehler, eine verbleibende Registrierung oder erzwungenes Beenden als unvollständiges Herunterfahren. Ein Ersatz-Worker setzt dauerhafte Arbeit gemäß den aufgezeichneten Leases, Wiederholungen und Fristen fort. Das Stoppen eines Pods bricht keinen Workflow ab und macht keinen externen Seiteneffekt rückgängig. Folgen Sie vor einem Build-Wechsel dem [Rollout nach Worker-Build-ID](./polyglot/worker-build-id-rollout.md) und behalten Sie dessen Kompatibilitäts-, Replay- und Rollback-Prüfungen bei.

## Hochverfügbarkeit und Failover in einer Region {#single-region-high-availability-and-failover}

Einregionen-HA-Failover ist derzeit ein **unverifizierter, supportbegleiteter** Vertragskandidat auf Basis des kleinen Clusters und der unveränderten Kubernetes-Form. Er zielt auf Fehlermodi, für deren Überstehen die Engine **innerhalb einer Region** ausgelegt ist: Failover verwalteter Datenbanken wie RDS Multi-AZ, Aurora-Cluster, Cloud SQL HA oder Patroni-Hochstufung; Failover verwalteten Redis wie Sentinel, Elasticache-Replikationsgruppen oder Memorystore HA; API-Knotenverlust, Worker-Verlust und Neustart des Schedulers/Wartungsprozesses. Regionsübergreifende Aktiv/Passiv-Wiederherstellung ist eine andere supportbegleitete Evaluierung. Siehe [Aktiv/Passiv über mehrere Regionen](#activepassive-multi-region).
Expand Down
2 changes: 1 addition & 1 deletion i18n/de/source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"defining-workflows/workflow-id.md": "91b94211e2f8f405895ad82cfe2ef9baadb7a2cbd06e9c1e7aa6362231e0d424",
"defining-workflows/workflow-status.md": "8e8cb27ff44c890d489d89bf166375acfdfaecabd990a2235e106fab1592d529",
"defining-workflows/workflows.md": "f467bc73de32aa5d722c61ce085065460382ae7dd44cea4625847d9fcdc4c97c",
"deployment.md": "aa76a132fd78a27428dd9b8d9ab6f81aa8661b3f302eb799877688b77f508596",
"deployment.md": "dd0b6bd184eda12e67922121a09c7f3c98346bde01be1d4c3217f168db5fd7ca",
"failures-and-recovery.md": "318f0cc183d26539c7d5b6bdf791429bb874b793b81cff4c6094ef1d11a796b2",
"features/activity-execution-model.md": "e8c37520ae46a7efb30438f4fc06bf893c1df430cb1f92c48963697adaf09082",
"features/cancel-and-terminate.md": "c678fc7a0c9b11f0e1bee295aff259de9803219c0aa6769fb801622799e7deb1",
Expand Down
Loading
Loading