Noa Ramon created FLINK-41404:
---------------------------------
Summary: Blue/Green: after an aborted transition the retry
restores the aborted attempt's state instead of the serving deployment's fresh
savepoint
Key: FLINK-41404
URL: https://issues.apache.org/jira/browse/FLINK-41404
Project: Flink
Issue Type: Bug
Affects Versions: kubernetes-operator-1.15.0
Reporter: Noa Ramon
When a two-deployment Blue/Green transition aborts,
\{{BlueGreenDeploymentService#abortDeployment}} suspends the new child
(\{{BlueGreenDeploymentService.java:685}} on \{{main}} at 43719c98) and rolls
the parent back to the previous \{{ACTIVE_*}} state. The next transition
re-creates the child under the same name with \{{createOrReplace}}
(\{{BlueGreenKubernetesService.java:59}}). Because the existing resource keeps
its status, the \{{FlinkDeployment}} controller treats this as a *resume of a
suspended deployment*, not as a first deployment.
\{{spec.job.initialSavepointPath}}, which the Blue/Green controller sets to the
serving deployment's fresh savepoint, is only honoured on a first deployment.
The retry therefore restores the aborted attempt's state in both stateful modes:
# *\{{upgradeMode: last-state}}.* The suspend keeps the Kubernetes HA metadata
(\{{SuspendMode#deleteHaMeta}} is \{{false}} only for \{{LAST_STATE}}). On
resume the JobManager recovers the aborted attempt's latest checkpoint from the
HA ConfigMaps before \{{upgradeMode}} or \{{initialSavepointPath}} take effect.
Switching the spec to \{{stateless}} does not help, because the HA restore
happens first.
# *\{{upgradeMode: savepoint}}.* The suspend takes a stop-savepoint of the
aborted child and records it in \{{status.jobStatus.upgradeSavepointPath}}. On
resume the job restores from that savepoint.
\{{AbstractJobReconciler#restoringFromInitialSavepoint}} does not apply,
because the new \{{initialSavepointPath}} differs from the recorded
\{{upgradeSavepointPath}}.
*Impact:* if an attempt aborts because of its state (for example a
\{{StateMigrationException}} after an incompatible serializer change), every
later attempt on that colour fails the same way. The serving deployment's good
savepoint is never used. The only way out is deleting the child
\{{FlinkDeployment}} by hand.
h4. Observed on 1.15.0
* \{{last-state}}: a transition to a new build restored the previous savepoint,
failed with \{{StateMigrationException}} and aborted (expected). A follow-up
transition with \{{upgradeMode: stateless}} and no savepoint path still logged
\{{Restoring SplitEnumerator ... from checkpoint}} with the checkpoint taken
from the leftover HA ConfigMaps, and failed with the same exception.
* \{{savepoint}}: after a forced abort and a retry, the parent's child spec and
the JobManager disagreed:
** \{{spec.job.initialSavepointPath = .../savepoint-e28232-...}} (the serving
deployment's fresh savepoint)
** JobManager \{{execution.savepoint.path = .../savepoint-8b5a03-...}} (the
aborted child's own stop-savepoint)
h4. Steps to reproduce
# Deploy a \{{FlinkBlueGreenDeployment}} with \{{upgradeMode: savepoint}} (or
\{{last-state}}) and wait for \{{ACTIVE_BLUE}}.
# Set \{{kubernetes.operator.bluegreen.abort.grace-period}} lower than the time
the job needs to become ready (a few seconds), then make any spec change. The
transition aborts, \{{<name>-green}} is suspended, and the parent returns to
\{{ACTIVE_BLUE}}.
# Restore a normal grace period and make another valid spec change.
\{{<name>-green}} is re-created under the same name.
# Expected: \{{<name>-green}} starts from blue's fresh savepoint
(\{{spec.job.initialSavepointPath}}). Actual: it resumes from its own suspend
savepoint (\{{savepoint}} mode) or from its last checkpoint via HA
(\{{last-state}} mode).
h4. Proposed fix
In a two-deployment transition, delete the aborted child instead of suspending
it, the same way a successful cutover deletes the outgoing deployment. Deletion
goes through \{{ApplicationReconciler#cleanupInternal}}: a running job is
cancelled with \{{SuspendMode.STATELESS}} (or \{{SAVEPOINT}} when
savepoint-on-deletion is enabled), and both delete the HA metadata. The child's
status is removed together with the resource, so the next transition creates a
fresh resource that honours \{{initialSavepointPath}}. An abort of the *first*
deployment, where there is no other deployment to fall back to, keeps the
current suspend behaviour.
Trade-off: the aborted deployment's pods are no longer kept around for
inspection. The parent still records the reason in \{{status.error}} and the
operator logs the abort.
h4. Verification
* New regression test
\{{verifyRetryAfterAbortRestoresTheServingColoursFreshSavepoint}}: aborts a
\{{savepoint}}-mode transition on the grace period, then retries. It requires
that only the previous deployment remains after the abort, and that the retried
child is a new resource (different \{{metadata.uid}}) whose
\{{initialSavepointPath}} is the retry's fresh savepoint. With the current
suspend behaviour the retried child is the aborted resource updated in place
(same \{{uid}}), so the test fails.
* Two existing tests (\{{verifyFailureDuringTransition}},
\{{verifyReadyTimestampResetOnAbortPreventsPrematureBlueDelete}}) asserted the
suspended child; their assertions now require that only the previous deployment
remains and keeps running.
* All Blue/Green test suites pass (133 tests).
* The change has been running on our clusters; retries after an abort start
from the serving deployment's savepoint.
A patch against \{{main}} is ready. I will open the PR once this ticket exists.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)