Devin Flake
11/19/2025, 8:07 PMkopf._cogs.clients.errors.APIForbiddenError: ('pods is forbidden: User "system:serviceaccount:ns-team-arte-prefect:prefect-service-account" cannot list resource "pods" in API group "" at the cluster scope', {'kind': 'Status', 'apiVersion': 'v1', 'metadata': {}, 'status': 'Failure', 'message': 'pods is forbidden: User "system:serviceaccount:ns-team-arte-prefect:prefect-service-account" cannot list resource "pods" in API group "" at the cluster scope', 'reason': 'Forbidden', 'details': {'kind': 'pods'}, 'code': 403})
was there a change with how Prefect interacts with Kubernetes?Marvin
11/19/2025, 8:07 PMMarvin
11/19/2025, 8:15 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES=ns-team-arte-prefect
This makes Kopf watch only that namespace. With that, a namespaced Role with list/watch permissions is sufficient.
2) Keep cluster-wide observation and grant cluster‑scope RBAC
If you want to watch all namespaces, give the worker’s service account cluster‑wide list/watch:
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: ClusterRole
metadata:
name: prefect-worker
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get","create","patch","delete","list","watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: ClusterRoleBinding
metadata:
name: prefect-worker
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: ClusterRole
name: prefect-worker
subjects:
- kind: ServiceAccount
name: prefect-service-account
namespace: ns-team-arte-prefect
3) Disable the observer entirely (if you don’t need pod/job event watching or event replication)
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
# or, to keep the observer but skip sending Prefect events:
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_REPLICATE_POD_EVENTS=false
Notes
- Kubernetes watch semantics require list + watch. Even if you only “watch,” the API starts with a list to get a resourceVersion.
- If you scope the observer with PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES, ensure your namespaced Role includes list/watch on pods and jobs, e.g.:
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: Role
metadata:
name: prefect-worker
namespace: ns-team-arte-prefect
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get","create","patch","delete","list","watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: RoleBinding
metadata:
name: prefect-worker
namespace: ns-team-arte-prefect
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: Role
name: prefect-worker
subjects:
- kind: ServiceAccount
name: prefect-service-account
namespace: ns-team-arte-prefect
Docs and references
- Kubernetes deployment guide: Prefect on Kubernetes
- Prefect Kubernetes integration: prefect-kubernetes docs
- API reference: prefect_kubernetes reference
- Helm chart (RBAC examples): Prefect Helm repoMarvin
11/19/2025, 8:15 PMDevin Flake
11/19/2025, 8:15 PMDevin Flake
11/19/2025, 8:15 PMMarvin
11/19/2025, 8:15 PMMarvin
11/19/2025, 8:16 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES=ns-team-arte-prefect
Without this, the observer defaults to cluster-wide and Kubernetes will perform a cluster-scope “list” when establishing watches, which your SA is blocking.
2) Ensure your namespace Role includes list/watch
If you haven’t already, add list/watch on pods and jobs for the worker’s service account in your namespace:
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: Role
metadata:
name: prefect-worker
namespace: ns-team-arte-prefect
rules:
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get","create","patch","delete","list","watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: RoleBinding
metadata:
name: prefect-worker
namespace: ns-team-arte-prefect
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: Role
name: prefect-worker
subjects:
- kind: ServiceAccount
name: prefect-service-account
namespace: ns-team-arte-prefect
3) Optional: disable the observer if you don’t need it
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
(or keep it on but disable event replication)
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_REPLICATE_POD_EVENTS=false
Why this is happening
- The Kubernetes observer (enabled by default) uses Kopf to watch Pods/Jobs. Establishing a watch starts with a list. If not namespace-scoped, that list is cluster-wide and will be forbidden by a namespace-only service account.
Docs and references
- Kubernetes deployment guide: Prefect on Kubernetes
- Prefect Kubernetes integration: prefect-kubernetes docs
- API reference: prefect_kubernetes reference
- Helm chart (RBAC examples): Prefect Helm repo
If you can share your current Role/RoleBinding and how you’re injecting env vars into the worker Deployment, I can tailor a minimal patch for your setup.Devin Flake
11/19/2025, 8:17 PMapiVersion: v1
items:
- apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: Role
metadata:
labels:
<http://app.kubernetes.io/component|app.kubernetes.io/component>: role
<http://app.kubernetes.io/name|app.kubernetes.io/name>: prefect-role
<http://app.kubernetes.io/version|app.kubernetes.io/version>: 3.6.2
prefect-version: 3.6.2
name: prefect-role
rules:
- apiGroups:
- ""
resources:
- events
- pods
- pods/log
- pods/status
verbs:
- get
- watch
- list
- apiGroups:
- batch
resources:
- jobs
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
kind: List
metadata:
resourceVersion: ""
---
apiVersion: v1
items:
- apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: RoleBinding
metadata:
labels:
<http://app.kubernetes.io/component|app.kubernetes.io/component>: rolebinding
<http://app.kubernetes.io/name|app.kubernetes.io/name>: prefect-role-binding
<http://app.kubernetes.io/version|app.kubernetes.io/version>: 3.6.2
prefect-version: 3.6.2
name: prefect-role-binding
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: Role
name: prefect-role
subjects:
- kind: ServiceAccount
name: prefect-service-account
kind: List
metadata:
resourceVersion: ""Marvin
11/19/2025, 8:17 PMMarvin
11/19/2025, 8:17 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES=ns-team-arte-prefect
- With that set, your existing Role is sufficient because it already includes:
- pods: get, list, watch
- jobs: get, list, watch, create, update, patch, delete
- pods/log: get
- Double-check your RoleBinding subject includes the namespace of the ServiceAccount to avoid any ambiguity:
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: RoleBinding
metadata:
name: prefect-role-binding
namespace: ns-team-arte-prefect
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: Role
name: prefect-role
subjects:
- kind: ServiceAccount
name: prefect-service-account
namespace: ns-team-arte-prefect
Optional: If you don’t need the observer at all, disable it:
PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
(Note: disabling just event replication won’t stop the watch; to avoid watch/list entirely, disable the observer.)
Docs
- Prefect on Kubernetes
- prefect-kubernetes docs
- prefect_kubernetes reference
If you tell me how you’re deploying the worker (Helm vs. a Deployment manifest), I can share an exact patch snippet to add the env var.Devin Flake
11/19/2025, 8:49 PMMarvin
11/19/2025, 8:49 PMMarvin
11/19/2025, 8:49 PMkubectl -n ns-team-arte-prefect set env deploy/<your-deployment-name> PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES=ns-team-arte-prefect
kubectl -n ns-team-arte-prefect rollout status deploy/<your-deployment-name>
Option B: edit your Deployment YAML
Add this under the worker container’s env: and re-apply:
spec:
template:
spec:
containers:
- name: <your-worker-container-name>
env:
- name: PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES
value: ns-team-arte-prefect
Then:
kubectl apply -f <your-deployment.yaml>
kubectl -n ns-team-arte-prefect rollout status deploy/<your-deployment-name>
Optional: disable the observer entirely
kubectl -n ns-team-arte-prefect set env deploy/<your-deployment-name> PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false
Verify
- Check logs after rollout: the 403 “cannot list pods at the cluster scope” should disappear.
- The worker should still run jobs and stream logs normally with your existing Role.
RBAC sanity check
Make sure your RoleBinding is namespaced and binds to the SA in the same namespace (add explicit namespace fields if missing):
apiVersion: <http://rbac.authorization.k8s.io/v1|rbac.authorization.k8s.io/v1>
kind: RoleBinding
metadata:
name: prefect-role-binding
namespace: ns-team-arte-prefect
roleRef:
apiGroup: <http://rbac.authorization.k8s.io|rbac.authorization.k8s.io>
kind: Role
name: prefect-role
subjects:
- kind: ServiceAccount
name: prefect-service-account
namespace: ns-team-arte-prefect
References
- Prefect on Kubernetes
- prefect-kubernetes docs
- prefect_kubernetes reference
If you share your Deployment name and container name, I can give you an exact kubectl command tailored to your manifest.Devin Flake
11/19/2025, 9:04 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES need to be set on the prefect server or the prefect jobs config?Marvin
11/19/2025, 9:04 PMMarvin
11/19/2025, 9:04 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES from the worker’s environment at startup.
- Not needed on Prefect Server/Cloud.
- Not needed in your flow-run job pods (those don’t run the observer).
Quick patch to your Deployment manifest:
spec:
template:
spec:
containers:
- name: <your-worker-container-name>
env:
- name: PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES
value: ns-team-arte-prefect
Then re-apply and roll out:
kubectl apply -f <your-deployment.yaml>
kubectl -n ns-team-arte-prefect rollout status deploy/<your-deployment-name>
Optional alternative: disable the observer entirely on the worker with PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false if you don’t need the watch.Devin Flake
11/19/2025, 9:21 PMPREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_NAMESPACES=ns-team-arte-prefect and PREFECT_INTEGRATIONS_KUBERNETES_OBSERVER_ENABLED=false , I don't see any errors in the logs but none of my prefect jobs will run. They all just hang with this message:
kubectl -n ns-team-arte-prefect logs job.batch/prefect-job-tw5q8
20:59:02.033 | INFO | prefect.flow_runs.runner - Opening process...Marvin
11/19/2025, 9:21 PMDevin Flake
11/19/2025, 9:23 PMMarvin
11/19/2025, 9:25 PMkubectl -n ns-team-arte-prefect exec job/<job-name> -- ps aux
If you only see one Python process, the subprocess didn’t start.
2) Confirm Prefect version inside the job image
kubectl -n ns-team-arte-prefect exec job/<job-name> -- python -c "import prefect; print(prefect.__version__)"
Please share the output. Ideally this matches your worker’s Prefect version (3.6.2).
3) Check the job’s image and command
kubectl -n ns-team-arte-prefect get job <job-name> -o yaml
- Which image is used?
- What’s the container command/args? (Prefect should be starting the engine subprocess.)
4) Verify the job has API connectivity
- Does the pod have PREFECT_API_URL (and PREFECT_API_KEY if using Cloud)?
kubectl -n ns-team-arte-prefect exec job/<job-name> -- printenv | grep PREFECT_API
- Basic DNS/egress check (replace host as needed):
kubectl -n ns-team-arte-prefect exec job/<job-name> -- sh -c 'apk add --no-cache curl >/dev/null 2>&1 || true; curl -sS -m 5 https://<your-prefect-api-host>/'
If you’re using Prefect Server, try hitting its /api/health endpoint; if Cloud, just confirm DNS resolves.
5) Resources
Insufficient CPU/memory can stall process creation. Check container events and usage:
kubectl -n ns-team-arte-prefect describe pod <pod-name>
Look for OOMKilled, throttling, or probe failures. Ensure requests/limits give the pod enough headroom (e.g., requests: cpu 250m/mem 512Mi; limits: cpu 1/mem 2Gi).
Turn on more logging for the job subprocess
Add this env var to the job so we can see more:
- If you control deployments, add to the deployment’s job variables/env:
PREFECT_LOGGING_LEVEL=DEBUG
- Or, if you want to test quickly, add it to your work pool’s base job template so it propagates to new jobs.
Two quick hypotheses to validate
- Version/image mismatch: Worker upgraded to 3.6.2 but the job image is still on an older Prefect or missing dependencies, and the subprocess never starts.
- Network/DNS: The child process starts but immediately blocks trying to reach the Prefect API or your code source (e.g., Git). With DEBUG logs or “ps aux”, we’ll see whether the child PID exists.
Next actions
- Please share:
- The output of steps 1–4
- The image name used by the job
- Whether your flow code is baked into the image or pulled from source (e.g., Git) at runtime
- If you’d like, I can also propose a minimal tweak to your work pool/deployment to inject DEBUG logging and a small startup echo to confirm the child starts.
References
- Kubernetes guide: Prefect on Kubernetes
- Kubernetes integration: prefect-kubernetes docs
- API reference: prefect_kubernetes referenceNate
11/19/2025, 9:32 PMNate
11/19/2025, 9:33 PM