kube icon indicating copy to clipboard operation
kube copied to clipboard

Controller continue to reconcile when object is deleted

Open dmolokanov opened this issue 3 years ago • 4 comments

Current and expected behavior

I use Controller to make an k8s operator. Inside my reconcile function it creates (via just PATCH) a number of resources (StatefulSets, Services, Deployments). I make all my resources to have OwnerReference to the CRD. Reconcile happens every 60s.

Sometimes when I delete CRD right before reset/refresh of the watcher happens I have my controller to stuck in reconcile loop for about 2.5 min. It receives old CRD object (from cache?) despite the fact it was deleted from k8s resources. During this time there are concurrent 2 processes happen:

  1. k8s tries to cleanup objects because of deleted CRD+owner reference
  2. my operator patches missing resources because it things CRD is in place.

Expected behavior: Controller should notice CRD object deleted and not call reconcile function.

As a side note I have questions:

  1. Is it okay to rely only on PATCH operations inside reconcile function?
  2. I don't see any option for Controller to receive Deleted(CRD) event in the separate callback/inside reconcile function. Can I rely on ObjectNotFound error?

Possible solution

No response

Additional context

No response

Environment

Client Version: v1.22.2 Server Version: v1.20.9

Configuration and features

k8s-openapi = { version = "0.12.0", features = ["v1_20"], default-features = false }
kube = { version = "0.58.1", features = ["derive"] }
kube-derive = "0.61.0"
kube-runtime = "0.58.1"

Affected crates

No response

Would you like to work on fixing this bug?

No response

dmolokanov avatar Nov 16 '21 19:11 dmolokanov

To be clear, this is about the object being controlled, not the CustomResourceDefinition itself, right? Anyway...

Sometimes when I delete CRD right before reset/refresh of the watcher happens I have my controller to stuck in reconcile loop for about 2.5 min. It receives old CRD object (from cache?) despite the fact it was deleted from k8s resources. During this time there are concurrent 2 processes happen:

k8s tries to cleanup objects because of deleted CRD+owner reference my operator patches missing resources because it things CRD is in place.

That should be fine, K8s should GC those created objects soon enough. Ultimately the race here is unavoidable, there will always be a period where clients are working with an object that has already been deleted. We might be able to reduce it, but there's no way of avoiding it completely. We could abort reconcilers that are already running, but that's already 1) best-effort only, and 2) might cause issues if we cancel in the middle of a non-atomic action. Then again, all reconcilers are subject to the ultimate cancellation anyway, so we might end up saying that this is an OK tradeoff and the reconciler should just deal with it.

Sometimes when I delete CRD right before reset/refresh of the watcher happens I have my controller to stuck in reconcile loop for about 2.5 min. It receives old CRD object (from cache?) despite the fact it was deleted from k8s resources.

Is this one reconciliation that gets stuck, or does it stop and create new reconciliations for the same object? If the latter then that's definitely a bug in kube-runtime.

Is it okay to rely only on PATCH operations inside reconcile function?

I assume you're talking about server-side apply patches here. That should be fine (and I'd generally consider that best-practice as long as you're targetting a K8s version new enough to support it). Other patch types generally only allow you to update existing objects, not create new ones.

I don't see any option for Controller to receive Deleted(CRD) event in the separate callback/inside reconcile function.

Use a finalizer (such as https://docs.rs/kube-runtime/0.63.2/kube_runtime/finalizer/fn.finalizer.html) if you want reliable deletion events.

Can I rely on ObjectNotFound error?

No.

nightkr avatar Nov 16 '21 19:11 nightkr

To be clear, this is about the object being controlled, not the CustomResourceDefinition itself, right? Anyway...

yes, the part which generates CRD definition works OK.

Then again, all reconcilers are subject to the ultimate cancellation anyway, so we might end up saying that this is an OK tradeoff and the reconciler should just deal with it.

Do you mean inside the reconcile function I should query k8s API and check that there is still an object with given name/namespace? If yes, does it mean that Watcher produces unreliable results and we cannot rely only on it?

Is this one reconciliation that gets stuck, or does it stop and create new reconciliations for the same object? If the latter then that's definitely a bug in kube-runtime.

Controller produces multiple reconciliations for the object that has already been deleted. I'd expect Controller will finish the running reconciliation if any, and then will not produce any new reconciliations for the deleted object.

I assume you're talking about server-side apply patches here.

// the pattern I follow so far
fn reconcile() {
   service_api.patch(...).await?;
   deployment_api.patch(...).await?;
}

// vs something like this
fn reconcile() {
  if service_api.get("blah-svc").await.is_some() {
     service_api.update(...).await?;
  }

  if deployment_api.get("blah-dp").await.is_some() {
     deployment_api.update(...).await?;
  }
}

dmolokanov avatar Nov 16 '21 19:11 dmolokanov

Do you mean inside the reconcile function I should query k8s API and check that there is still an object with given name/namespace?

No. Generally you should not need to care about this in your reconciler.

If yes, does it mean that Watcher produces unreliable results and we cannot rely only on it?

Are you getting ObjectNotFound errors for the deleted objects? If yes then watcher is fine and something funky is going on with Controller. If no then there might be a bug in there.

Either way, the intention is that watcher is reliable (but not always completely up-to-date).

Controller produces multiple reconciliations for the object that has already been deleted.

Does this happen reliably for you no matter what you do, or are you able to post a minimal example where this happens?

I'd expect Controller will finish the running reconciliation if any, and then will not produce any new reconciliations for the deleted object.

Yeah, that's the intention. If that's not the case then there is a bug somewhere that we need to find.

[snipped code snippet about patching]

The patch mode is decided by which arm of the Patch enum you pass to Api::patch (Patch::Apply, Patch::Merge, Patch::Strategic, or Patch::Json).

nightkr avatar Nov 18 '21 14:11 nightkr

Does this happen reliably for you no matter what you do, or are you able to post a minimal example where this happens?

I will try to repro this. It looks it happens if I delete CRD object right before (or during?) watcher restarts list+watch operation. I'm referring to this event https://docs.rs/kube-runtime/0.64.0/kube_runtime/watcher/enum.Event.html#variant.Restarted

The patch mode is decided by which arm of the Patch enum you pass to Api::patch (Patch::Apply, Patch::Merge, Patch::Strategic, or Patch::Json).

Ah, that's right! Patch::Apply is what I use and it is Server-side apply

Are you getting ObjectNotFound errors for the deleted objects? If yes then watcher is fine and something funky is going on with Controller. If no then there might be a bug in there.

Yes, usually I receive ObjectNotFound error when I deleted CRD object.

dmolokanov avatar Nov 19 '21 20:11 dmolokanov

Going to close this as it's more of a discussion issue.

For more context about ObjectNotFound on deleted owned resources see https://github.com/kube-rs/kube/issues/1167#issuecomment-1636773541 for a bigger explanation.

clux avatar Sep 13 '23 08:09 clux