Devin Flake
11/19/2025, 10:35 PMMarvin
11/19/2025, 10:35 PMMarvin
11/19/2025, 10:38 PM<http://prefect.io/flow-run-id|prefect.io/flow-run-id> and emits Prefect events (Added/Running/Succeeded/Failed, including evictions) so you can automate on K8s lifecycle signals.
- Crash detection: Watches corresponding Jobs; when backoff limits are hit or no active Jobs remain, it marks the flow run as Crashed.
- Runs inside the K8s worker: The worker starts a Kopf operator in a background thread and stops it on shutdown. It can run cluster-wide or be namespace-scoped, and supports multiple observers without duplicate events (via deterministic event IDs).
How it’s implemented
- Kopf handlers:
@kopf.on.startup()
def configure(settings, **_): settings.scanning.disabled = True
@kopf.on.event("pods", labels={"<http://prefect.io/flow-run-id|prefect.io/flow-run-id>": kopf.PRESENT})
async def _replicate_pod_event(...): ...
@kopf.on.event("jobs", labels={"<http://prefect.io/flow-run-id|prefect.io/flow-run-id>": kopf.PRESENT})
async def _mark_flow_run_as_crashed(...): ...
- The worker starts/stops the observer thread which runs kopf.operator(...) with either clusterwide=True or explicit namespaces=[...].
Configuration (env vars)
- PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED (default true)
- PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_REPLICATE_POD_EVENTS (default true)
- PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES (comma-separated)
- PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ADDITIONAL_LABEL_FILTERS
Logging
- Prefect ships a Kopf-specific JSON log formatter to avoid serialization issues when JSON logging is enabled.
Source and docs
- Observer implementation: prefect_kubernetes/observer.py
- Worker integration (starts/stops observer): prefect_kubernetes/worker.py
- Settings (env var mapping): prefect_kubernetes/settings.py
- Kopf JSON logging: prefect_kubernetes/_logging.py
- Prefect K8s guide: Kubernetes integration guide
- Kopf docs: Kopf documentation
If you’re on Prefect 2.x, Kopf isn’t used; these observer features are 3.x-only.Devin Flake
11/20/2025, 10:23 PMMarvin
11/20/2025, 10:23 PMMarvin
11/20/2025, 10:27 PMkubectl logs deploy/<your-worker-deployment> -n <namespace> -f
You should see something like “Started worker 'KubernetesWorker' for pool '<pool-name>'” and periodic “polled x runs” messages.
- If you run it manually, make sure you start it with the correct pool:
prefect worker start -p <your-pool-name>
2) Is the work pool active, of the right type, and being polled?
- List and inspect:
prefect work-pool ls
prefect work-pool inspect "<your-pool-name>"
Verify:
- Status: not paused (resume with prefect work-pool resume "<pool>" if needed)
- Type: Kubernetes
- Concurrency limit: not 0 or fully consumed
- Work queues: if you’re using queue filters, confirm they match your deployments’ tags/labels
3) Is your deployment assigned to that pool (and queue) you’re polling?
- Inspect the deployment:
prefect deployment inspect "<flow-name>/<deployment-name>"
Check “Work pool” and “Work queue” values. If they don’t match the worker, update/redeploy so they do.
4) Did your recent changes alter tags/queues/templates?
- If you added a work queue with filters or changed deployment tags, the worker may no longer match. Either adjust the filters or start the worker with the specific queue(s):
prefect worker start -p "<pool>" -q "<queue-1>" -q "<queue-2>"
5) Does the worker have the Kubernetes integration installed and permissions?
- The worker image/env must include the K8s extra:
pip install "prefect[kubernetes]"
If it’s missing, the worker typically fails to start as a Kubernetes worker — check logs.
- Ensure the worker’s ServiceAccount can create Jobs/Pods in the target namespace (RBAC). Note: missing RBAC usually causes runs to be claimed then fail; if you never get beyond “Scheduled,” it’s a worker/pool/queue issue.
6) API connectivity
- Ensure the worker has PREFECT_API_URL and (if using Cloud) PREFECT_API_KEY set, and it’s pointing to the same workspace as your deployments. If the worker can’t reach the API, it won’t claim runs.
If you can share a bit more, I can pinpoint it quickly:
- What changes did you make right before this started?
- Output of:
prefect work-pool ls
prefect work-pool inspect "<pool>"
prefect deployment inspect "<flow>/<deployment>"
- How are you running the worker (K8s Deployment/Helm/manual)? A short snippet of the worker logs at startup would help.
Docs for reference:
- Kubernetes guide: Kubernetes integration guideDevin Flake
11/20/2025, 10:32 PM$ kubectl logs deployment.apps/prefect-worker -n ns-team-arte-prefect -f
Found 2 pods, using pod/prefect-worker-8487f4fc79-nsj2n
/usr/local/lib/python3.11/site-packages/prefect/context.py:955: UserWarning: Failed to create the Prefect home directory at /home/prefect/.prefect
GLOBAL_SETTINGS_CONTEXT: SettingsContext = root_settings_context()
Loaded collection 'prefect_dask'.
Loaded collection 'prefect_redis'.
Loaded collection 'prefect_kubernetes'.
Worker 'KubernetesWorker 1b3db6e2-847d-41da-b42d-15f5b6a8a6a7' started!Marvin
11/20/2025, 10:32 PMMarvin
11/20/2025, 10:32 PMprefect worker start -p <your-pool-name> # optionally -q <queue>
- After “Worker 'KubernetesWorker ...' started!” you should see periodic polling lines. If you don’t, increase logging and re-check:
- Set env in the worker: PREFECT_LOGGING_LEVEL=DEBUG
- Then watch logs again:
kubectl logs deploy/prefect-worker -n ns-team-arte-prefect -f
2) Inspect your work pool and status
- From anywhere with CLI auth to the same workspace:
prefect work-pool ls
prefect work-pool inspect "<your-pool-name>"
Verify:
- Status is not paused (resume if needed):
prefect work-pool resume "<your-pool-name>"
- Type is Kubernetes
- Concurrency limit is not 0 and not fully used. If set to 0 by accident:
prefect work-pool clear-concurrency-limit "<your-pool-name>"
3) Ensure deployments target that pool (and queue)
- Check the deployment that produced the scheduled runs:
prefect deployment inspect "<flow-name>/<deployment-name>"
Confirm “Work pool” matches the one your worker is polling, and if you use work queues, that the names/filters match the worker’s -q flags (or just let the worker poll all queues by omitting -q).
4) Verify API connectivity and workspace alignment
- In the worker pod, confirm these env vars:
kubectl exec -it deploy/prefect-worker -n ns-team-arte-prefect -- env | grep PREFECT_API
You should see a valid PREFECT_API_URL (and PREFECT_API_KEY if using Cloud). If these are wrong/missing, the worker won’t claim runs from the right workspace.
About the warning in your logs
- Failed to create the Prefect home directory at /home/prefect/.prefect usually isn’t the root cause here, but it’s worth fixing to avoid oddities with local settings/logging:
- Either make /home/prefect writable for the container user, or set
PREFECT_HOME=/tmp/.prefect
in the worker env.
If you can, please share:
- The command/args your worker container uses to start (from the Deployment manifest)
- Output of:
prefect work-pool inspect "<your-pool-name>"
prefect deployment inspect "<flow>/<deployment>"
- A few lines of the worker logs after enabling PREFECT_LOGGING_LEVEL=DEBUG
Reference:
- Kubernetes worker guide: Kubernetes integration guideDevin Flake
11/21/2025, 7:04 PMMarvin
11/21/2025, 7:04 PMMarvin
11/21/2025, 7:05 PMkubectl get deploy prefect-worker -n ns-team-arte-prefect -o yaml | grep -A2 -n "prefect worker start"
You should see:
prefect worker start -p <your-pool-name> # optionally -q <queue-1> -q <queue-2>
If you’re passing any -q values, temporarily remove them so the worker polls all queues in the pool.
- Enable debug logs and look for polling activity:
- Add env var PREFECT_LOGGING_LEVEL=DEBUG to the worker Deployment and restart it.
- Then:
kubectl logs deploy/prefect-worker -n ns-team-arte-prefect -f
You should see periodic “polled” lines. If you don’t see any polling lines after startup, the worker likely isn’t connected to the right API or pool.
2) Inspect the work pool (status, type, concurrency)
- From a machine authenticated to the same workspace:
prefect work-pool ls
prefect work-pool inspect "<your-pool-name>"
Confirm:
- Status: not paused (resume if needed)
prefect work-pool resume "<your-pool-name>"
- Type: Kubernetes (must match your worker type)
- Concurrency limit: not 0 and not fully consumed
prefect work-pool clear-concurrency-limit "<your-pool-name>"
3) Verify deployments target that pool (and any queues)
- Check the deployment that produced the scheduled runs:
prefect deployment inspect "<flow-name>/<deployment-name>"
Ensure:
- Work pool matches the one your worker is polling
- If you use queues/filters, they match any -q you passed to the worker (or remove -q to poll all)
4) Confirm API connectivity and workspace alignment from the worker pod
- Check env:
kubectl exec -it deploy/prefect-worker -n ns-team-arte-prefect -- env | grep PREFECT_API
You should see a valid PREFECT_API_URL (and PREFECT_API_KEY if using Cloud).
- For Prefect Server: URL typically ends with /api (e.g., http://prefect-api.<ns>.svc.cluster.local:4200/api)
- For Prefect Cloud: full account/workspace URL
- Quick health check from the worker pod:
kubectl exec -it deploy/prefect-worker -n ns-team-arte-prefect -- sh -lc 'apk add --no-cache curl || true; \
curl -fsS $PREFECT_API_URL/health || curl -fsS $PREFECT_API_URL/api/health || true'
You should get a healthy response. If this fails, the worker can’t reach the API and will never claim runs.
5) Sanity checks
- Version alignment:
kubectl exec -it deploy/prefect-worker -n ns-team-arte-prefect -- prefect version
Confirm it’s 3.4.20 and matches what your server expects.
- UI Work Pools page: open your pool, check “Workers” tab and “Last polled” timestamp. If it never updates, the worker isn’t polling the pool you think it is.
Note on the log warning
- Failed to create the Prefect home directory at /home/prefect/.prefect is usually harmless but you can eliminate it by setting:
PREFECT_HOME=/tmp/.prefect
in the worker env or making /home/prefect writable. It won’t prevent claiming runs.
If you can share the following, I can pinpoint the issue quickly:
- The worker container command/args from the Deployment
- Output of:
prefect work-pool inspect "<your-pool-name>"
prefect deployment inspect "<flow-name>/<deployment-name>"
- A few lines of worker logs at DEBUG level after startup (especially any “polling” or “claim” messages)
Reference:Marvin
11/21/2025, 7:05 PMDevin Flake
11/21/2025, 7:17 PM$ prefect work-pool inspect arte-k8s
WorkPool(
id='73bd42d3-4175-4339-82f4-9cb33b56258d',
created=DateTime(2024, 12, 5, 23, 5, 30, 735310, tzinfo=Timezone('UTC')),
updated=DateTime(2025, 11, 20, 22, 50, 2, 188157, tzinfo=Timezone('UTC')),
name='arte-k8s',
type='kubernetes',
base_job_template={
'variables': {
'type': 'object',
'properties': {
'env': {
'type': 'object',
'title': 'Environment Variables',
'default': [
{'name': 'ARTE_REST_TOKEN', 'valueFrom': {'secretKeyRef': {'key': 'arte_rest_token', 'name': 'api-tokens'}}},
{'name': 'AUTOMATION_VAULT_TOKEN', 'valueFrom': {'secretKeyRef': {'key': 'arte_vault_token', 'name': 'api-tokens'}}},
{'name': 'ARTE__SLACK_BOT_TOKEN', 'valueFrom': {'secretKeyRef': {'key': 'slack_bot_token', 'name': 'api-tokens'}}},
{'name': 'ARTE__LOG_LEVEL', 'value': 'INFO'},
{'name': 'ARTE_BASE_URL', 'value': '<https://arte.int.ethos12-prod-or1.ethos.adobe.net>'},
{'name': 'ARTE__VAULT_URL', 'value': '<https://vault-int-amer.adobe.net>'},
{'name': 'ARTE__NAGIOS_USERNAME', 'valueFrom': {'secretKeyRef': {'key': 'nagios_username', 'name': 'nagios'}}},
{'name': 'ARTE__NAGIOS_PASSWORD', 'valueFrom': {'secretKeyRef': {'key': 'nagios_password', 'name': 'nagios'}}},
{'name': 'USE_SERVICE_TOKEN', 'value': 'true'},
{'name': 'ARTE__MYSQL_USERNAME', 'valueFrom': {'secretKeyRef': {'key': 'mysql_username', 'name': 'credentials'}}},
{'name': 'ARTE__MYSQL_PASSWORD', 'valueFrom': {'secretKeyRef': {'key': 'mysql_password', 'name': 'credentials'}}},
{'name': 'ARTE__FTP_USERNAME', 'valueFrom': {'secretKeyRef': {'key': 'ftp_username', 'name': 'credentials'}}},
{'name': 'ARTE__FTP_PASSWORD', 'valueFrom': {'secretKeyRef': {'key': 'ftp_password', 'name': 'credentials'}}},
{'name': 'PREFECT_KUBERNETES_CLUSTER_UID', 'value': 'prefect-worker-id-1'},
{'name': 'PREFECT_LOGGING_SETTINGS_PATH', 'value': '/etc/prefect/logging/logging.yml'}
],
'description': 'Environment variables to set when starting a flow run.',
'additionalProperties': {'anyOf': [{'type': 'string'}, {'type': 'null'}]}
},
'name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Name', 'default': 'prefect-job', 'description': 'Name given to infrastructure created by a worker.'},
'image': {
'anyOf': [{'type': 'string'}, {'type': 'null'}],
'title': 'Image',
'default': '<http://docker-arte-release.dr-uw2.adobeitc.com/arte-flows:latest|docker-arte-release.dr-uw2.adobeitc.com/arte-flows:latest>',
'examples': ['<http://docker.io/prefecthq/prefect:3-latest|docker.io/prefecthq/prefect:3-latest>'],
'description': 'The image reference of a container image to use for created jobs. If not set, the latest Prefect image will be used.'
},
'labels': {
'type': 'object',
'title': 'Labels',
'default': {'app': 'prefect', 'environment': 'production', 'use-default-egress-policy': 'true'},
'description': 'Labels applied to infrastructure created by a worker.',
'additionalProperties': {'type': 'string'}
},
'command': {
'anyOf': [{'type': 'string'}, {'type': 'null'}],
'title': 'Command',
'description': 'The command to use when starting a flow run. In most cases, this should be left blank and the command will be automatically generated by the worker.'
},
'namespace': {'type': 'string', 'title': 'Namespace', 'default': 'ns-team-arte-prefect', 'description': 'The Kubernetes namespace to create jobs within.'},
'stream_output': {'type': 'boolean', 'title': 'Stream Output', 'default': True, 'description': 'If set, output will be streamed from the job to local standard output.'},
'cluster_config': {'anyOf': [{'$ref': '#/definitions/KubernetesClusterConfig'}, {'type': 'null'}], 'description': 'The Kubernetes cluster config to use for job creation.'},
'finished_job_ttl': {
'anyOf': [{'type': 'integer'}, {'type': 'null'}],
'title': 'Finished Job TTL',
'default': 300,
'description': 'The number of seconds to retain jobs after completion. If set, finished jobs will be cleaned up by Kubernetes after the given delay. If not set, jobs will be retained indefinitely.'
},
'image_pull_policy': {'enum': ['IfNotPresent', 'Always', 'Never'], 'type': 'string', 'title': 'Image Pull Policy', 'default': 'Always', 'description': 'The Kubernetes image pull policy to use for job containers.'},
'service_account_name': {'anyOf': [{'type': 'string'}, {'type': 'null'}], 'title': 'Service Account Name', 'default': 'prefect-service-account', 'description': 'The Kubernetes service account to use for job creation.'},
'job_watch_timeout_seconds': {
'anyOf': [{'type': 'integer'}, {'type': 'null'}],
'title': 'Job Watch Timeout Seconds',
'description': 'Number of seconds to wait for each event emitted by a job before timing out. If not set, the worker will wait for each event indefinitely.'
},
'pod_watch_timeout_seconds': {'type': 'integer', 'title': 'Pod Watch Timeout Seconds', 'default': 60, 'description': 'Number of seconds to watch for pod creation before timing out.'}
},
'definitions': {
'KubernetesClusterConfig': {
'type': 'object',
'title': 'KubernetesClusterConfig',
'required': ['config', 'context_name'],
'properties': {
'config': {'type': 'object', 'title': 'Config', 'description': 'The entire contents of a kubectl config file.'},
'context_name': {'type': 'string', 'title': 'Context Name', 'description': 'The name of the kubectl context to use.'}
},
'description': 'Stores configuration for interaction with Kubernetes clusters.\n\nSee `from_file` for creation.',
'secret_fields': [],
'block_type_slug': 'kubernetes-cluster-config',
'block_schema_references': {}
}
},
'description': 'Default variables for the Kubernetes worker.\n\nThe schema for this class is used to populate the `variables` section of the default\nbase job template.'
},
'job_configuration': {
'env': '{{ env }}',
'name': '{{ name }}',
'labels': '{{ labels }}',
'command': '{{ command }}',
'namespace': '{{ namespace }}',
'job_manifest': {
'kind': 'Job',
'spec': {
'template': {
'spec': {
'volumes': [{'name': 'id-rsa', 'secret': {'secretName': 'ssh-key-arte-autobot'}}, {'name': 'config-volume', 'configMap': {'name': 'prefect-logging-config'}}],
'containers': [
{
'env': '{{ env }}',
'args': '{{ command }}',
'name': 'prefect-job',
'image': '{{ image }}',
'volumeMounts': [
{'name': 'id-rsa', 'subPath': 'ssh-privatekey', 'readOnly': True, 'mountPath': '/home/arte/.ssh/id_rsa', 'mountPropagation': 'None'},
{'name': 'config-volume', 'subPath': 'logging.yml', 'mountPath': '/etc/prefect/logging/logging.yml'}
],
'imagePullPolicy': '{{ image_pull_policy }}'
}
],
'completions': 1,
'parallelism': 1,
'restartPolicy': 'Never',
'imagePullSecrets': [{'name': 'artedocker'}],
'serviceAccountName': '{{ service_account_name }}'
},
'metadata': {'labels': '{{ labels }}'}
},
'backoffLimit': 0,
'ttlSecondsAfterFinished': '{{ finished_job_ttl }}'
},
'metadata': {'labels': '{{ labels }}', 'namespace': '{{ namespace }}', 'annotations': {'<http://arc.ethos.adobe.net/ignore|arc.ethos.adobe.net/ignore>': 'true', '<http://karpenter.sh/do-not-disrupt|karpenter.sh/do-not-disrupt>': 'true'}, 'generateName': '{{ name }}-'},
'apiVersion': 'batch/v1'
},
'stream_output': '{{ stream_output }}',
'cluster_config': '{{ cluster_config }}',
'job_watch_timeout_seconds': '{{ job_watch_timeout_seconds }}',
'pod_watch_timeout_seconds': '{{ pod_watch_timeout_seconds }}'
}
},
status=WorkPoolStatus.READY,
storage_configuration=WorkPoolStorageConfiguration(),
default_queue_id='b6aa8565-5598-45ae-85c1-60a7b6327552'
)Marvin
11/21/2025, 7:18 PMMarvin
11/21/2025, 7:18 PMDevin Flake
11/21/2025, 7:25 PMINFO: 172.16.62.228:12862 - "POST /api/logs/filter HTTP/1.1" 200 OK
INFO: 172.17.148.181:40400 - "GET /api/work_pools/arte-k8s HTTP/1.1" 200 OK
INFO: 172.17.148.181:40400 - "POST /api/work_pools/arte-k8s/workers/heartbeat HTTP/1.1" 204 No Content
INFO: 172.16.215.80:60248 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.17.148.181:40400 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "GET /api/flow_runs/8e798cac-ff86-4a28-a3fc-c3e7f39a171b HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "POST /api/logs/filter HTTP/1.1" 200 OK
INFO: 172.16.215.80:60256 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.17.148.181:48618 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "POST /api/flow_runs/paginate HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "GET /api/flow_runs/8e798cac-ff86-4a28-a3fc-c3e7f39a171b HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "POST /api/logs/filter HTTP/1.1" 200 OK
INFO: 172.16.215.80:39640 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.17.148.181:48618 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.16.215.80:39640 - "GET /api/work_pools/arte-k8s HTTP/1.1" 200 OK
INFO: 172.16.215.80:39640 - "POST /api/work_pools/arte-k8s/workers/heartbeat HTTP/1.1" 204 No Content
INFO: 172.16.62.228:12862 - "GET /api/flow_runs/8e798cac-ff86-4a28-a3fc-c3e7f39a171b HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "POST /api/logs/filter HTTP/1.1" 200 OK
INFO: 172.16.215.80:39640 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.17.148.181:48618 - "POST /api/work_pools/arte-k8s/get_scheduled_flow_runs HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "GET /api/flow_runs/8e798cac-ff86-4a28-a3fc-c3e7f39a171b HTTP/1.1" 200 OK
INFO: 172.16.62.228:12862 - "POST /api/logs/filter HTTP/1.1" 200 OKMarvin
11/21/2025, 7:25 PMMarvin
11/21/2025, 7:30 PMprefect deployment inspect "<flow-name>/<deployment-name>"
2) Check the pool and queue states
- Pool status and concurrency:
prefect work-pool inspect "arte-k8s"
Ensure it’s not paused and concurrency limit is None or > 0.
- List queues and see if any are paused:
prefect work-queue ls
In this output, (**) denotes paused queues. If your deployment’s queue is paused:
prefect work-queue resume "<queue-name>"
- Preview and artificially poll the exact queue your deployment uses:
prefect work-queue preview -p "arte-k8s" "<queue-name>"
prefect work-queue read-runs -p "arte-k8s" "<queue-name>"
If these return 0 runs while the UI shows Scheduled, it usually means filters/tags or concurrency are blocking eligibility.
3) Check deployment concurrency and scheduled time
- For a specific scheduled run ID from the UI:
prefect flow-run inspect <flow-run-id>
Look for:
- scheduled_time vs current time (from inside the worker pod, date -u). If scheduled_time is in the future due to TZ/time skew, it won’t be picked yet.
- deployment_concurrency_limit and current occupancy
4) Confirm the worker is polling the right pool/queues
- Show the worker start command/args from your Deployment:
kubectl get deploy prefect-worker -n ns-team-arte-prefect -o yaml | grep -nA2 "prefect worker start"
If you pass any -q flags, the worker will ignore other queues in the pool. Remove -q temporarily so it polls all queues in “arte-k8s”.
5) Turn on DEBUG logs for the worker and look for polling details
- Set env on worker: PREFECT_LOGGING_LEVEL=DEBUG
- Then:
kubectl logs deploy/prefect-worker -n ns-team-arte-prefect -f
You should see lines like “Polling work pool arte-k8s” followed by “Found 0 eligible runs” or “Claiming N runs…”. That message tells us if the server is returning empties.
If you paste back:
- The worker start command/args
- prefect work-pool inspect "arte-k8s"
- prefect work-queue ls and prefect work-queue preview/read-runs -p "arte-k8s" "<queue>"
- prefect deployment inspect "<flow>/<deployment>" (or the flow-run inspect for one scheduled run)
…I can pinpoint exactly why get_scheduled_flow_runs is returning nothing for the worker and get you unblocked fast.
Reference: Kubernetes integration guideDevin Flake
11/21/2025, 7:39 PMNone?
$ prefect deployment inspect 'UPGRADE_OS/upgrade_os' | grep -i work
'description': "Prefect flow/deployment to upgrade the OS of qualified devices.\nCan work by providing a list of hosts or by matching a project/reservation or based on the kernel of 'On Duty - Standby' servers.\nNote, the reservation will be removed
'work_queue_name': 'arte-concurrency-1',
'work_queue_id': None,
'work_pool_name': 'arte-k8s',Marvin
11/21/2025, 7:39 PMMarvin
11/21/2025, 7:43 PMprefect work-queue create -p "arte-k8s" "arte-concurrency-1"
2) Re-save the deployment so it binds to the queue ID
- If you manage deployments from code, redeploy it so Prefect resolves name → ID:
prefect deploy
or re-run your flow.from_source(...).deploy(...).
- Or update it directly:
prefect deployment inspect "UPGRADE_OS/upgrade_os" # confirm fields
# then update via your deployment config or re-run your deploy command
3) Verify the link is restored
- After redeploy/update, check:
prefect deployment inspect "UPGRADE_OS/upgrade_os" | grep -i work_queue
You should see both work_queue_name set and work_queue_id populated with a UUID.
4) Make sure your worker is polling that pool/queue
- Start worker without -q first (polls all queues in the pool):
prefect worker start -p "arte-k8s"
If you do use -q, ensure it includes “arte-concurrency-1”.
Optional sanity checks
- Preview and artificial poll:
prefect work-queue preview -p "arte-k8s" "arte-concurrency-1"
prefect work-queue read-runs -p "arte-k8s" "arte-concurrency-1"
- Confirm pool/queue not paused and concurrency > 0:
prefect work-pool inspect "arte-k8s"
prefect work-queue ls
If you like, paste the output of:
- prefect work-queue ls (look for “arte-concurrency-1” under pool “arte-k8s”)
- prefect deployment inspect "UPGRADE_OS/upgrade_os" after redeploy
…and I’ll confirm everything is aligned.Devin Flake
11/21/2025, 7:54 PM$ kubectl -n ns-team-arte-prefect logs pod/prefect-worker-6864b554f4-hftn2 -f
/usr/local/lib/python3.11/site-packages/prefect/context.py:955: UserWarning: Failed to create the Prefect home directory at /home/prefect/.prefect
GLOBAL_SETTINGS_CONTEXT: SettingsContext = root_settings_context()
Loaded collection 'prefect_dask'.
Loaded collection 'prefect_redis'.
Loaded collection 'prefect_kubernetes'.
Worker 'KubernetesWorker df328cff-5e0a-478e-999a-9a3beaceb0aa' started!
^CMarvin
11/21/2025, 7:55 PMMarvin
11/21/2025, 7:56 PM'work_queue_name': 'arte-concurrency-1',
'work_queue_id': None,
'work_pool_name': 'arte-k8s',
this is the blocker. Prefect matches runs to queues by ID (not by the name string). If work_queue_id is None, those runs will never be eligible for any worker.
Do this to fix quickly
1) Ensure the queue exists in the pool:
prefect work-queue create -p "arte-k8s" "arte-concurrency-1"
2) Re-save the deployment so Prefect resolves the queue name → ID:
- If you deploy from code, re-run your deploy step (e.g., flow.from_source(...).deploy(...) or prefect deploy).
- Then confirm the link:
prefect deployment inspect "UPGRADE_OS/upgrade_os" | grep -i work_queue
# Expect both name and a non-None work_queue_id
3) Sanity check the queue and pool:
- Not paused and with usable concurrency:
prefect work-queue ls
prefect work-pool inspect "arte-k8s"
- Optional: preview and artificially poll the exact queue:
prefect work-queue preview -p "arte-k8s" "arte-concurrency-1"
prefect work-queue read-runs -p "arte-k8s" "arte-concurrency-1"
4) Worker flags
- Start the worker without any -q filters first so it polls all queues in the pool:
prefect worker start -p "arte-k8s"
- If you do pass -q, make sure it includes exactly arte-concurrency-1.
About the missing polling logs
- On 3.4.20, the “Polling work pool … Found N eligible runs …” lines are DEBUG-level. Make sure PREFECT_LOGGING_LEVEL=DEBUG is set in the container env before the process starts. You can check from inside the pod:
prefect config view | grep LOGGING
- Also fix the home directory warning (it can interfere with settings/logging writes):
PREFECT_HOME=/tmp/.prefect
or make /home/prefect writable.
Once work_queue_id is populated and the worker is polling the right pool/queue, your Scheduled runs should be returned by the server and claimed by the worker. If you still don’t see claims after the steps above, share:
- prefect work-queue ls
- prefect work-pool inspect "arte-k8s"
- prefect deployment inspect "UPGRADE_OS/upgrade_os"
- The worker container command/args from the Deployment
…and I’ll zero in on the next issue.Devin Flake
11/21/2025, 8:26 PM20:25:57.113 | DEBUG | APILogWorkerThread | prefect._internal.concurrency - Running call get(timeout=1.9999785100808367) in thread 'APILogWorkerThread'
20:25:57.113 | DEBUG | APILogWorkerThread | prefect._internal.concurrency - <WatcherThreadCancelScope, name='get' RUNNING, runtime=0.00> entered
20:25:59.113 | DEBUG | APILogWorkerThread | prefect._internal.concurrency - <WatcherThreadCancelScope, name='get' COMPLETED, runtime=2.00> exited
20:25:59.114 | DEBUG | APILogWorkerThread | prefect._internal.concurrency - Encountered exception in call get(<dropped>)
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/prefect/_internal/concurrency/calls.py", line 399, in _run_sync
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/queue.py", line 179, in get
raise Empty
_queue.EmptyMarvin
11/21/2025, 8:26 PMMarvin
11/21/2025, 8:28 PMqueue.Empty). It does not indicate an error and doesn’t affect run polling or job submission.
Given your deployment shows work_queue_id: None, that’s the real blocker: Prefect matches eligible runs to workers by work_queue_id, not by the name string. With a null ID, the server will never return those runs to any worker, which explains why you don’t see “Found 0/Claiming N runs…” logs even at DEBUG.
Fix steps
1) Ensure the queue exists in your pool:
prefect work-queue create -p "arte-k8s" "arte-concurrency-1"
2) Re-save/redeploy so the deployment binds to the queue ID:
- If you deploy from code, run your deploy again (e.g., flow.from_source(...).deploy(...) or prefect deploy).
- Then confirm:
prefect deployment inspect "UPGRADE_OS/upgrade_os" | grep -i work_queue
# Expect both work_queue_name set and work_queue_id populated with a UUID
3) Sanity checks:
prefect work-queue ls # ensure "arte-concurrency-1" exists and isn’t paused
prefect work-queue preview -p "arte-k8s" "arte-concurrency-1"
prefect work-queue read-runs -p "arte-k8s" "arte-concurrency-1"
prefect work-pool inspect "arte-k8s" # pool not paused; concurrency OK
4) Worker start:
- Poll the pool without -q first:
prefect worker start -p "arte-k8s"
- If you do use -q, include exactly arte-concurrency-1.
Optional: clear the home warning to avoid any config/logging oddities:
PREFECT_HOME=/tmp/.prefect
If after this you still don’t see claims, please share:
- prefect work-queue ls
- prefect work-pool inspect "arte-k8s"
- prefect deployment inspect "UPGRADE_OS/upgrade_os"
- The worker container command/args from the Deployment
Kubernetes guide for reference: Kubernetes integration guideDevin Flake
11/21/2025, 8:51 PMMarvin
11/21/2025, 8:51 PMMarvin
11/21/2025, 8:53 PMprefect deploy ... --pool "arte-k8s" --work-queue "arte-concurrency-1"
- Programmatic API:
from prefect.client.orchestration import get_client
from prefect.client.schemas.actions import DeploymentUpdate
async with get_client() as client:
await client.update_deployment(
"<deployment-id>",
DeploymentUpdate(
work_pool_name="arte-k8s",
work_queue_name="arte-concurrency-1",
),
)
- After updating, verify the ID is populated:
prefect deployment inspect "UPGRADE_OS/upgrade_os" | grep -i work_queue
If the named queue doesn’t exist, create it first, then update:
prefect work-queue create -p "arte-k8s" "arte-concurrency-1"
Note: Trying to set work_queue_id directly isn’t supported in the client or REST schemas; the server-side logic always derives it from the provided names.Devin Flake
11/21/2025, 9:16 PMMarvin
11/21/2025, 9:16 PMMarvin
11/21/2025, 9:18 PMprefect work-pool inspect "arte-k8s"
- Queue must exist in that pool: arte-concurrency-1
- If you created the queue without a pool (legacy queue), Prefect won’t map it when you specify pool+queue.
- Create the queue in the pool explicitly:
prefect work-queue create -p "arte-k8s" "arte-concurrency-1"
2) Update the deployment using pool + queue names together
- This uses the server path that guarantees a pool-scoped queue and will auto-create the queue if missing in that pool.
- CLI redeploy:
prefect deploy ... --pool "arte-k8s" --work-queue "arte-concurrency-1"
- Or programmatic:
from prefect.client.orchestration import get_client
from prefect.client.schemas.actions import DeploymentUpdate
async with get_client() as client:
await client.update_deployment(
"<deployment-id>",
DeploymentUpdate(
work_pool_name="arte-k8s",
work_queue_name="arte-concurrency-1",
),
)
- Then verify:
prefect deployment inspect "UPGRADE_OS/upgrade_os" | grep -i work_queue
# Expect work_queue_id to be a UUID
3) If you only set work_queue_name (without pool), Prefect may create a legacy queue
- The legacy path uses ensure_work_queue_exists(name) which puts the queue in the “default-agent-pool” if present, or creates a standalone queue. This will not bind to arte-k8s.
- Always include both pool and queue for modern setups.
4) Double-check there aren’t duplicate queues with the same name across pools
- If you previously created a queue named arte-concurrency-1 outside arte-k8s, you could be inspecting that one instead. Confirm the queue under the target pool:
prefect work-queue ls | grep arte-concurrency-1
You should see Pool: arte-k8s for the one you intend to use.
5) Inspect your deployment after recreation
- Sometimes recreating via a file or recipe retains an old value. Confirm what actually got applied:
prefect deployment inspect "UPGRADE_OS/upgrade_os"
If work_pool_name is set but work_queue_id is still None, the queue likely didn’t resolve in that pool.
6) As a diagnostic, try pool-only to bind to the pool’s default queue
- This confirms that the pool is healthy and its default queue ID is resolvable:
prefect deploy ... --pool "arte-k8s"
After this, inspect the deployment to see if work_queue_id is set to the pool’s default_queue_id.
If this still doesn’t populate the UUID, please share:
- prefect work-pool inspect "arte-k8s" (look for default_queue_id)
- prefect work-queue ls (to confirm a queue named “arte-concurrency-1” exists under the arte-k8s pool)
- The exact deploy command or DeploymentUpdate call you’re using
- prefect deployment inspect "UPGRADE_OS/upgrade_os" after the update
I can then pinpoint whether it’s using the legacy queue path or failing the name→ID lookup.