Skip to content

feat(operator): pod management#345

Open
bmcquilkin-sentry wants to merge 2 commits into
mainfrom
bmcquilkin/operator/pods
Open

feat(operator): pod management#345
bmcquilkin-sentry wants to merge 2 commits into
mainfrom
bmcquilkin/operator/pods

Conversation

@bmcquilkin-sentry

@bmcquilkin-sentry bmcquilkin-sentry commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Adds Pod management abilities to the operator. Instead of generating a Deployment, the operator will generate and manage Pods. Uses a ConfigMap per CR to track the Pod generations. Watches Pod events and uses a health classifier to determine how to respond to them and reconcile.

This is a different implementation than the original test we did and is based on Kopf's official suggestion for how to reconcile: https://docs.kopf.dev/en/latest/reconciliation/#level-based-triggering

Our old implementation runs reconcile on every event. This is really bad especially when there are lots of events and many CRs and can easily overwhelm the k8s API. We instead use a level-based reconcile loop (one per CR) via a Kopf daemon. CR changes and relevant k8s events trigger the loop. This also ensures that reconciles are one at a time and there aren't any race conditions from multiple reconciles.

@bmcquilkin-sentry
bmcquilkin-sentry requested a review from a team as a code owner July 20, 2026 21:21
)
core.delete_namespaced_config_map(
name=configmap.metadata.name, namespace=workload_namespace
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Orphaned Deployments after upgrade

High Severity

The operator no longer deletes previously created Deployment workloads, and deployment RBAC was removed. On upgrade, old Deployments keep running their Pods while the new path also creates managed Pods for the same pipeline, so consumers can run twice and double-process traffic.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 50b3b68. Configure here.

namespace=namespace,
field_manager=FIELD_MANAGER,
force_conflicts=True,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing workload ownership checks

Medium Severity

apply_pod and save_generations apply with force_conflicts and never verify streams.sentry.io/owner-uid. Unlike _apply for ConfigMaps, a second StreamingPipeline that renders the same base name can overwrite another pipeline’s Pods or generation ledger instead of failing permanently.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 50b3b68. Configure here.

Comment thread sentry_streams_k8s/sentry_streams_k8s/operator/operator.py Outdated
Comment thread sentry_streams_k8s/sentry_streams_k8s/operator/generations.py
@linear-code

linear-code Bot commented Jul 20, 2026

Copy link
Copy Markdown

STREAM-1609

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d2ee769. Configure here.

@bmcquilkin-sentry
bmcquilkin-sentry force-pushed the bmcquilkin/operator/pods branch from d2ee769 to 50b3b68 Compare July 20, 2026 23:06
@getsentry getsentry deleted a comment from sentry Bot Jul 20, 2026
@getsentry getsentry deleted a comment from cursor Bot Jul 20, 2026
Comment thread sentry_streams_k8s/sentry_streams_k8s/operator/reconcile.py
Comment thread sentry_streams_k8s/sentry_streams_k8s/operator/operator.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 399b877. Configure here.

)


def _prune_stale_resources(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale canary status after removal

Medium Severity

Status is written with a JSON merge patch, and pods.sets is a nested object. When a workload set such as canary is removed, that key is omitted rather than cleared, so the old sets entry can remain in status even though the Pods were deleted.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 399b877. Configure here.

Comment thread sentry_streams_k8s/sentry_streams_k8s/operator/pod_resources.py
Comment on lines +165 to 199
@kopf.daemon(GROUP, VERSION, PLURAL)
async def reconcile_pipeline_daemon(
stopped: kopf.DaemonStopped,
spec: kopf.Spec,
name: str,
namespace: str | None,
uid: str,
patch: kopf.Patch,
memo: kopf.Memo,
logger: Logger,
**_: Any,
) -> None:
assert namespace is not None
workload_namespace = _workload_namespace()
if namespace is None:
raise kopf.PermanentError("Missing namespace!")

consumer = from_crd_spec(dict(spec), name=name)
scheduler = _scheduler(memo)
event = scheduler.register(uid)
try:
validate(consumer)
result = render(consumer)
except Exception as e:
patch.status["conditions"] = [_condition("Rendered", False, type(e).__name__, str(e))]
raise kopf.PermanentError(f"StreamingPipeline {namespace}/{name} failed to render: {e}")

manifests = [result["configmap"], result["deployment"]]
if "canary_deployment" in result:
manifests.append(result["canary_deployment"])

dyn = dynamic.DynamicClient(client.ApiClient())
for manifest in manifests:
_prepare_manifest(
manifest,
workload_namespace=workload_namespace,
owner_uid=uid,
owner_name=name,
owner_namespace=namespace,
)
_apply(
dyn,
manifest,
workload_namespace=workload_namespace,
owner_uid=uid,
)
while not stopped:
event.clear()
timeout = await _reconcile_once(
spec=spec,
name=name,
namespace=namespace,
uid=uid,
logger=logger,
scheduler=scheduler,
stopped=stopped,
)
if stopped:
break
await _wait_for_reconcile(event, stopped, timeout)
finally:
scheduler.unregister(uid, event)

_prune_stale_resources(
workload_namespace=workload_namespace,
owner_uid=uid,
desired_deployments={
manifest["metadata"]["name"]
for manifest in manifests
if manifest["kind"] == "Deployment"
},
desired_configmaps={
manifest["metadata"]["name"]
for manifest in manifests
if manifest["kind"] == "ConfigMap"
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pod and generation-ledger applies omit cross-pipeline ownership checks

The new reconcile path applies Pods and generation-ledger ConfigMaps in the shared workload namespace without the owner-uid guard used for other resources, so a StreamingPipeline whose rendered base name collides can overwrite another pipeline's ledger (and race on Pod names). Reuse the existing owner-uid check from _apply before apply_pod/save_generations, or fail closed on collision.

Evidence
  • reconcile_pipeline_daemon (this hunk) calls _reconcile_oncereconcile_pipeline, which creates Pods via apply_pod and generation ConfigMaps via save_generations.
  • _apply in reconcile.py refuses to mutate an existing object when streams.sentry.io/owner-uid belongs to another pipeline; apply_pod and save_generations perform server_side_apply(..., force_conflicts=True) with no equivalent check.
  • Workload object names are derived from CR-controlled service_name / pipeline_name / segment_id (and emergency_patch can override the deployment name), and all pipelines share WORKLOAD_NAMESPACE.
  • A colliding CR can therefore overwrite another pipeline's {base_name}-generations ConfigMap data and owner labels in the workload namespace; Pod name collisions are also unguarded.
Also found at 2 additional locations
  • sentry_streams_k8s/sentry_streams_k8s/operator/pod_resources.py:55-62
  • sentry_streams_k8s/sentry_streams_k8s/operator/reconcile.py:261-350

Identified by Warden security-review · 66Z-LS4

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