Skip to content

Deduplicate Kubernetes observer state checks and diagnosis logs for unchanged Pending pods - #22965

Open
devin-ai-integration[bot] wants to merge 11 commits into
mainfrom
devin1/oss-8181-kubernetes-observer-repeats-state-checks-and-logs-for
Open

Deduplicate Kubernetes observer state checks and diagnosis logs for unchanged Pending pods#22965
devin-ai-integration[bot] wants to merge 11 commits into
mainfrom
devin1/oss-8181-kubernetes-observer-repeats-state-checks-and-logs-for

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

closes #22964

This PR stops the Kubernetes observer from repeating flow run state checks and diagnosis logs when a pod stays in the Pending phase.

Kubernetes sends many watch events for a pod that does not change. Before this change, each event caused a new read_flow_run call, a new propose_state call, and a possible duplicate flow run log. This PR makes these operations occur one time for each unchanged condition:

  • The observer deduplicates diagnoses by normalized cause. For Unschedulable diagnoses, it normalizes only complete, recognized Kubernetes scheduler formats. Supported filter counts and reason ordering, the Kubernetes v1.35 NodeDeclaredFeatures requirement list, and default-preemption summaries are canonicalized while general post-filter text remains exact. Unknown or malformed scheduler output uses the exact original detail, so upstream format changes can produce an extra log instead of suppressing a meaningfully changed diagnosis.
  • One lifecycle-state owner retains each pod's completed state check and last diagnosis key together. Completed checks are cleared when a pod leaves Pending, diagnosis keys are cleared on recovery, and empty records are removed. Deletion and shutdown release the whole record. A periodic, paginated Kubernetes listing also releases state for pods whose deletion event was missed, but only after the full watched namespace and label scope is listed successfully. Live pods and UIDs observed concurrently with reconciliation remain retained.
  • The observer records a completed flow run state check for each pod while the pod stays in the Pending phase. Transient failures, timeouts, and rejected proposals are not retained, so the observer retries them on the next event.
  • The observer inspects the state that propose_state() returns. It records the check as completed only when the returned state is InfrastructurePending or is running, final, paused, or cancelling. Any other replacement state, such as a Scheduled state after a rejected proposal, is retried on the next event.
Implementation details
  • diagnostics.py: adds InfrastructureDiagnosis._dedupe_key(). Recognized Unschedulable* scheduler messages use a namespaced normalized key; unfamiliar or malformed messages use a namespaced exact key. The parser requires the expected scheduler scaffold, fully consumes counted reason histograms, and accepts only the Kubernetes reason shapes covered by the observer's scheduler-message corpus. It tags scheduler stages, preserves arbitrary post-filter text, validates nested default-preemption summaries independently, and sorts NodeDeclaredFeatures requirements only when that complete reason matches. All other diagnosis categories remain exact.
  • observer.py:
    • _PodLifecycleStates owns one record per relevant pod UID, containing the completed Pending-state check and last successfully handed-off diagnosis key.
    • Lifecycle observation clears completed checks when a pod leaves Pending and clears diagnosis keys on recovery. Deletion releases the whole pod record even if event handling fails, and observer shutdown clears the owner after reconciliation stops.
    • While pod event replication is enabled, a private reconciliation task lists pods every five minutes using the observer's exact namespaces and label filters. It follows every Kubernetes continuation token and mutates lifecycle state only after every page succeeds. Failed, partial, or invalid listings leave state unchanged.
    • Reconciliation snapshots eligible UIDs before Kubernetes I/O and tracks UIDs observed by concurrent pod events, so neither newly added state nor a refreshed existing UID can be removed by an older listing. Overlapping passes keep independent observation sets.
    • Observer shutdown cancels and awaits reconciliation before running the existing Prefect client cleanup path and releasing all lifecycle state. Kubernetes client-close errors cannot mask an in-flight cancellation.
    • Startup events reconcile phase and diagnosis recovery before the existing-event deduplication return.
    • _is_completed_flow_run_state() is used both for the state read before the proposal and for the state that propose_state() returns.
    • ObjectNotFound from read_flow_run completes the check because a missing flow run is a terminal observation for an immutable flow run ID.
    • propose_state() raises Pause instead of returning a paused replacement state, and Pause is a BaseException subclass. The observer catches it so the signal does not escape the event handler and records the check as completed.
    • A diagnosis key is retained only after the configured flow_run_logger call returns successfully. Durable API delivery remains owned by the existing Prefect logging pipeline rather than the observer.

Kopf multiplexes the watch stream into one queue for each object UID and processes each queue with one worker, so two events for the same pod cannot run in the handler at the same time. Reconciliation separately protects any UID touched while its Kubernetes listing is in flight.

Checklist

  • This pull request references any related issue by including "closes <link to issue>"
  • If this is a complex change, a maintainer has confirmed the proposed approach on the linked issue.
  • If this pull request adds or changes functionality, it includes tests or explains why tests are not needed.
  • If this pull request changes user-facing behavior, it updates documentation or explains why documentation is not needed.
    • No documentation change is needed: the change reduces duplicate logs and API calls and does not change any public interface.
  • If this pull request removes docs files, it includes redirect settings in mint.json.
  • If this pull request adds functions or classes, it includes helpful docstrings.

Link to Devin session: https://app.devin.ai/sessions/a3dd1abe570d4a97b6ab97350a8a06fb
Open in Devin Desktop: https://app.devin.ai/desktop/session/a3dd1abe570d4a97b6ab97350a8a06fb?variant=devin

…nchanged Pending pods

Deduplicate diagnoses by normalized causes so scheduler count changes do
not produce duplicate flow run logs, cache completed Pending state checks
per pod until the phase changes while retrying failures and timeouts, and
do not cache a proposal rejected with a Scheduled replacement.

OSS-8181

Co-authored-by: Alexander Streed <alex.s@prefect.io>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions github-actions Bot added the bug Something isn't working label Aug 27, 2026

@chuqCTC chuqCTC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found three correctness gaps compared with the issue's intended behavior.

Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/diagnostics.py Outdated
Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py Outdated
Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py Outdated
Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py Outdated
Co-authored-by: Alexander Streed <alex.s@prefect.io>

@chuqCTC chuqCTC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found two remaining edge cases in the scheduler-message normalization.

Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/diagnostics.py Outdated
Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/diagnostics.py Outdated

@chuqCTC chuqCTC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One additional state-transition edge case and one non-blocking simplification from a cleanup pass.

Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py
Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py Outdated
Co-authored-by: Alexander Streed <alex.s@prefect.io>
@desertaxle
desertaxle marked this pull request as ready for review August 29, 2026 19:45
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T21:14:59.507794Z 5522a21 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

Comment thread src/integrations/prefect-kubernetes/prefect_kubernetes/observer.py
Comment on lines +34 to +42
@pytest.fixture(autouse=True)
def clear_observer_lifecycle_caches() -> Iterator[None]:
_completed_state_check_cache.clear()
_last_diagnosis_cache.clear()
observer._pod_cache_reconciliation_touches.clear()
yield
_completed_state_check_cache.clear()
_last_diagnosis_cache.clear()
observer._pod_cache_reconciliation_touches.clear()

@devin-ai-integration devin-ai-integration Bot Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Tests expose private cache structure

Several tests mutate private caches and reconciliation registries directly. This conflicts with the contribution guide's preference for behavior-focused tests and increases refactoring cost.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment on lines +518 to +519
else:
_last_diagnosis_cache[uid] = key

@devin-ai-integration devin-ai-integration Bot Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Queued log failures suppress retries

When the background API request fails, _last_diagnosis_cache still marks its queued diagnosis as delivered. Unchanged pod events never retry that missing log.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kubernetes observer repeats state checks and logs for unchanged Pending pods

2 participants