[
https://issues.apache.org/jira/browse/FLINK-41050?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
ASF GitHub Bot updated FLINK-41050:
-----------------------------------
Labels: pull-request-available (was: )
> Canary health checks revert canary metadata changes and restart a healthy
> operator
> -----------------------------------------------------------------------------------
>
> Key: FLINK-41050
> URL: https://issues.apache.org/jira/browse/FLINK-41050
> Project: Flink
> Issue Type: Bug
> Components: Kubernetes Operator
> Reporter: Dennis-Mircea Ciupitu
> Priority: Major
> Labels: pull-request-available
>
> h1. Problem
> Canary resources let the operator health probe verify that spec changes are
> still picked up and reconciled. Every
> {{kubernetes.operator.health.canary.resource.timeout}} cycle (60s by
> default), the operator bumps the canary {{spec.restartNonce}} and checks that
> the previous bump was reconciled in time. If not, the probe reports the
> operator unhealthy and Kubernetes restarts it.
> The way the canary spec is updated has several problems. Users can lose their
> changes to the canary, the operator can modify resources that are no longer
> canaries, and the operator can be restarted although it works fine:
> # *Metadata changes on the canary are reverted* - Labels or annotations
> added to a canary (by a user, Argo CD, Kustomize, ...) are removed again on
> the next canary check. GitOps tools then keep fighting with the operator.
> # *Removing the canary label does not stop the canary checks* - The label is
> added back on the next check, so a resource cannot stop being a canary
> without being deleted.
> # *Any failed spec update makes the operator unhealthy* - A single failed
> update, for example a transient API server error (or a not found error = 404)
> or a concurrent write, makes the next check fail. The unhealthy state lasts
> until the check after that, longer than the default liveness probe budget, so
> the operator is restarted although it reconciles correctly.
> # *A canary that is gone or no longer watched restarts the operator* - If
> the canary is deleted without the operator cleanup (e.g. its finalizer was
> removed manually), or its namespace is removed from
> {{kubernetes.operator.watched.namespaces}} at runtime
> ({{{}kubernetes.operator.dynamic.namespaces.enabled: true{}}}), every
> following update fails and the operator is restarted.
> h1. Root cause
> h2. The canary is updated from a stale copy
> The spec update uses {{replace()}} on the canary copy cached at the last
> reconcile. Metadata-only changes do not change the generation, so they are
> not reconciled and the cached copy (labels, annotations, resource version)
> goes stale. The update then gets a {{{}409 Conflict{}}}, and the fabric8
> client retries {{replace()}} by refreshing only the resource version and
> sending the same stale copy again. The update succeeds and overwrites every
> metadata change made since the last reconcile, including the removal of the
> canary label.
> h2. The health check does not depend on the spec update
> A failed update is only logged ("Could not bump canary deployment, it may
> have been deleted"), and the health check is scheduled anyway. The check then
> compares the reconciled generation with a generation that was never written,
> so it always fails, whatever the cause of the failure:
> * a transient error fails one check, which is enough to restart the operator.
> * a permanent error ({{{}404{}}} for a canary deleted without cleanup,
> {{403}} for a namespace that is no longer watched and whose Role was removed)
> fails every check.
> h2. The canary is not dropped when the operator stops owning it
> The tracked canaries are only dropped by the canary deletion cleanup. Nothing
> drops a canary when the operator stops watching its namespace, or when the
> canary label is removed, so the operator keeps updating resources it no
> longer reconciles.
> h1. How to reproduce
> h2. Metadata changes reverted
> # Operator watching the {{default}} namespace, apply the example canary:
> {code:bash}
> kubectl apply -f examples/canary.yaml
> {code}
> # After a canary check (e.g. {{{}restartNonce: 2{}}}), add a label and an
> annotation:
> {code:bash}
> kubectl label flinkdeployment canary repro=409
> kubectl annotate flinkdeployment canary repro/note=keep-me
> {code}
> # After the next canary check ({{{}restartNonce: 3{}}}), the label {{repro}}
> and the annotation {{repro/note}} are gone:
> {code:bash}
> kubectl get flinkdeployment canary -o
> jsonpath='{.metadata.labels}{"\n"}{.metadata.annotations}{"\n"}{.spec}{"\n"}'
> {code}
> Removing the {{flink.apache.org/canary}} label instead of adding one behaves
> the same way, the label comes back on the next check.
> h2. Namespace removed from the watch list
> # Operator installed with Helm, namespace-scoped RBAC, {{watchNamespaces:
> [default, canary]}} and {{{}kubernetes.operator.dynamic.namespaces.enabled:
> true{}}}. Apply the canary in the {{canary}} namespace and wait until it is
> reconciled:
> {code:bash}
> kubectl create namespace canary
> kubectl apply -f examples/canary.yaml -n canary
> {code}
> # Run {{helm upgrade}} with {{{}watchNamespaces: [default]{}}}, without
> deleting the namespace. The operator stops watching {{canary}} and its Role
> there is removed.
> # The next canary update fails, the check is evaluated anyway, and the probe
> reports unhealthy until the operator is restarted:
> {code:java}
> WARN o.a.f.k.o.h.CanaryResourceManager Could not bump canary deployment, it
> may have been deleted
> io.fabric8.kubernetes.client.KubernetesClientException: Failure executing:
> PUT at: .../namespaces/canary/flinkdeployments/canary. Message:
> flinkdeployments.flink.apache.org "canary" is forbidden: User
> "system:serviceaccount:default:flink-operator" cannot update resource
> "flinkdeployments" in API group "flink.apache.org" in the namespace "canary".
> Received status: Status(apiVersion=v1, code=403, ...)
> ...
> ERROR o.a.f.k.o.h.CanaryResourceManager Canary deployment
> ResourceID{name='canary', namespace='canary'} latest spec not reconciled.
> Expected generation larger than 2, received 2
> ERROR o.a.f.k.operator.health.HealthProbe Unhealthy canary resources
> ERROR o.a.f.k.operator.health.HealthProbe Unhealthy canary resources
> ERROR o.a.f.k.operator.health.HealthProbe Unhealthy canary resources
> INFO i.javaoperatorsdk.operator.Operator Operator SDK 5.5.0 is shutting
> down...
> {code}
> {code:bash}
> kubectl get pods
> NAME READY STATUS RESTARTS
> AGE
> flink-kubernetes-operator-5bdbb9d45f-pvbsl 2/2 Running 1 (43s ago)
> 3m24s
> {code}
> h2. Canary deleted without cleanup
> # Apply the example canary and wait for the first canary check.
> # Remove its finalizer and delete it:
> {code:bash}
> kubectl patch flinkdeployment canary --type=json
> -p='[{"op":"remove","path":"/metadata/finalizers"}]'
> kubectl delete flinkdeployment canary
> {code}
> # Every following check fails and the operator is restarted.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)