[ 
https://issues.apache.org/jira/browse/FLINK-41157?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated FLINK-41157:
-----------------------------------
    Labels: pull-request-available  (was: )

> Stateless jobs with HA enabled cannot be recovered or restarted when HA 
> metadata is missing
> -------------------------------------------------------------------------------------------
>
>                 Key: FLINK-41157
>                 URL: https://issues.apache.org/jira/browse/FLINK-41157
>             Project: Flink
>          Issue Type: Bug
>          Components: Kubernetes Operator
>            Reporter: Dennis-Mircea Ciupitu
>            Priority: Major
>              Labels: pull-request-available
>
> h1. Summary
> For a FlinkDeployment in application mode with {{job.upgradeMode: stateless}} 
> and Kubernetes HA enabled in {{{}spec.flinkConfiguration{}}}, the JobManager 
> deployment recovery path and the cluster health-check restart path require 
> the per-job HA ConfigMap ({{{}<cluster-id>--config-map{}}}) to exist. When it 
> does not, the operator fails the restart with {{RestoreFailed}} ("HA metadata 
> is not available to restore from last state") instead of redeploying the 
> stateless job from empty state. The eligibility checks of both paths 
> explicitly treat a stateless job as always restartable without HA metadata, 
> so the gating and the execution disagree.
> Depending on the trigger, the deployment either stays {{FAILED}} and retries 
> the same failing restore on every reconcile, or ends up with no running 
> cluster at all while reporting lifecycle state {{STABLE}} and no error.
> h1. Root cause
> h2. Eligibility accepts stateless jobs without HA metadata
>  * {{{}AbstractFlinkResourceReconciler.shouldRecoverDeployment{}}}: a lost 
> JobManager deployment is recovered when the job is stateless or HA is enabled.
>  * {{{}ApplicationReconciler.shouldRestartJobBecauseUnhealthy{}}}: a 
> stateless job is always restarted, other upgrade modes only when 
> {{isHaMetadataAvailable}} is true.
> h2. Execution requires HA metadata whenever HA is enabled
>  * {{ApplicationReconciler.reconcileOtherChanges}} calls {{resubmitJob(ctx, 
> HighAvailabilityMode.isHighAvailabilityModeActivated(observeConfig))}} - The 
> upgrade mode is not considered.
>  * {{AbstractJobReconciler.resubmitJob}} switches the spec to {{LAST_STATE}} 
> when {{requireHaMetadata}} is true and keeps the job id.
>  * {{AbstractFlinkService.submitApplicationCluster}} then calls 
> {{{}validateHaMetadataExists{}}}, which throws 
> {{{}UpgradeFailureException("HA metadata is not available to restore from 
> last state. ...", "RestoreFailed"){}}}.
> h2. History
> FLINK-31277 (operator 1.5.0) replaced the earlier call site 
> {{requireHaMetadata = upgradeMode != STATELESS}} with the HA-only condition 
> and at the same time widened {{shouldRecoverDeployment}} to "stateless or HA".
> The combination: stateless + HA enabled + missing metadata has never been 
> covered.
> h2. The availability check is existence-only
>  * {{FlinkUtils.isValidHaConfigMap}} accepts any ConfigMap whose name ends in 
> {{-config-map}} except {{{}-cluster-config-map{}}}, ignoring both content and 
> job id.
>  * {{FlinkUtils.checkpointExists}} is only used to decide whether the dummy 
> last-state savepoint path is set in {{{}deploy{}}}.
> h2. When the per-job ConfigMap is missing
> Flink creates it when the JobMaster builds its checkpoint store and deletes 
> it itself on global job cleanup. It is missing when a human or a cleanup 
> script removed the HA ConfigMaps, when the JobManager never reached job 
> submission, or after the job reached a terminal state (the FLINK-37392 
> flavour).
> h1. Reproduction
> The reproduction can be done on {{examples/basic-checkpoint-ha.yaml}} with a 
> single change: {{{}upgradeMode: stateless{}}}. On minikube the hostPath 
> additionally needs {{type: DirectoryOrCreate}} and a writable {{/tmp/flink}} 
> on the node.
> Common steps:
>  * Apply the deployment and wait for {{jobStatus.state=RUNNING}} and 
> {{lifecycleState=STABLE}} + ConfigMaps {{{}-{}}}{{{}cluster-config-map{}}} 
> and {{-config-map}} exist.
>  * Delete the per-job ConfigMap: {{{}kubectl delete cm --config-map{}}}. The 
> running job is not affected (no checkpointing configured in the example).
> h2. Trigger A: JobManager deployment recovery
> Delete the deployment:
> {code:bash}
> kubectl delete deployment basic-checkpoint-ha-example
> {code}
> Observed on the next reconcile:
> {code:java}
> Observing JobManager deployment. Previous status: READY
> Exception while getting job status
> java.util.concurrent.TimeoutException
>         at java.base/java.util.concurrent.CompletableFuture.timedGet(Unknown 
> Source)
>         at java.base/java.util.concurrent.CompletableFuture.get(Unknown 
> Source)
>         at 
> org.apache.flink.kubernetes.operator.service.AbstractFlinkService.getJobStatus(AbstractFlinkService.java:314)
>         at 
> org.apache.flink.kubernetes.operator.observer.JobStatusObserver.observe(JobStatusObserver.java:89)
>         at 
> org.apache.flink.kubernetes.operator.observer.deployment.ApplicationObserver.observeFlinkCluster(ApplicationObserver.java:49)
>         at 
> org.apache.flink.kubernetes.operator.observer.deployment.AbstractFlinkDeploymentObserver.observeInternal(AbstractFlinkDeploymentObserver.java:70)
>         at 
> org.apache.flink.kubernetes.operator.observer.AbstractFlinkResourceObserver.observe(AbstractFlinkResourceObserver.java:49)
>         at 
> org.apache.flink.kubernetes.operator.controller.FlinkDeploymentController.reconcile(FlinkDeploymentController.java:145)
>         at 
> org.apache.flink.kubernetes.operator.controller.FlinkDeploymentController.reconcile(FlinkDeploymentController.java:59)
>         at 
> io.javaoperatorsdk.operator.processing.Controller$1.execute(Controller.java:191)
>         at 
> io.javaoperatorsdk.operator.processing.Controller$1.execute(Controller.java:147)
>         at 
> org.apache.flink.kubernetes.operator.metrics.OperatorJosdkMetrics.timeControllerExecution(OperatorJosdkMetrics.java:82)
>         at 
> io.javaoperatorsdk.operator.processing.Controller.reconcile(Controller.java:146)
>         at 
> io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.reconcileExecution(ReconciliationDispatcher.java:164)
>         at 
> io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.handleReconcile(ReconciliationDispatcher.java:145)
>         at 
> io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.handleDispatch(ReconciliationDispatcher.java:111)
>         at 
> io.javaoperatorsdk.operator.processing.event.ReconciliationDispatcher.handleExecution(ReconciliationDispatcher.java:76)
>         at 
> io.javaoperatorsdk.operator.processing.event.EventProcessor$ReconcilerExecutor.run(EventProcessor.java:578)
>         at 
> java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
>         at 
> java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
>         at java.base/java.lang.Thread.run(Unknown Source)
> Missing JobManager deployment
> >>> Event[Job]       | Warning | MISSING         | Missing JobManager 
> >>> deployment
> Config uses deprecated configuration key 'state.savepoints.dir' instead of 
> proper key 'execution.checkpointing.savepoint-dir'
> >>> Event[Job]       | Warning | RECOVERDEPLOYMENT | Recovering lost 
> >>> deployment
> Resubmitting Flink job...
> >>> Event[Job]       | Info    | SUBMIT          | Starting deployment
> Deploying application cluster requiring last-state from HA metadata
> Job graph in ConfigMap basic-checkpoint-ha-example-cluster-config-map is 
> deleted
> >>> Event[Job]       | Warning | RESTOREFAILED   | HA metadata is not 
> >>> available to restore from last state.It is possible that the job has 
> >>> finished or terminally failed, or the configmaps have been deleted.
> >>> Status[Job]      | Error   | FAILED          | {...}
> {code}
> Status: {{{}jobStatus.state=RECONCILING{}}}, 
> {{{}jobManagerDeploymentStatus=MISSING{}}}, {{{}lifecycleState=FAILED{}}}, 
> {{error}} set to the {{{}UpgradeFailureException{}}}. The 
> {{RecoverDeployment}} / {{Submit}} / {{RestoreFailed}} sequence repeated on 
> every reconcile (event count 5 after 3.5 minutes). No pods running.
> h2. Trigger B: cluster health-check restart
> Additional configuration on the deployment (within 
> {{{}spec.flinkConfiguration{}}}):
> {code:java}
> kubernetes.operator.cluster.health-check.enabled: "true"
> kubernetes.operator.cluster.health-check.restarts.threshold: "1"
> kubernetes.operator.cluster.health-check.restarts.window: 10 min
> kubernetes.operator.cluster.health-check.checkpoint-progress.enabled: "false" 
> {code}
> Delete the TaskManager pod twice, waiting for the new TaskManager to register 
> in between ({{{}numRestarts{}}} goes from 1 to 3). The following command was 
> used:
> {code:java}
> kubectl delete pod -l app=basic-checkpoint-ha-example,component=taskmanager
> {code}
> Observed within a single reconcile (operator log):
> {code:java}
> >>> Event[Job]       | Info    | JOBSTATUSCHANGED | Job status changed from 
> >>> RUNNING to RESTARTING
> >>> Event[Job]       | Warning | JOBEXCEPTION    | 
> >>> org.apache.flink.util.FlinkExpectedException: The TaskExecutor is 
> >>> shutting down.
>         at 
> org.apache.flink.runtime.taskexecutor.TaskExecutor.onStop(TaskExecutor.java:523)
>         at 
> org.apache.flink.runtime.rpc.RpcEndpoint.internalCallOnStop(RpcEndpoint.java:255)
>         at 
> org.apache.flink.runtime.rpc.pekko.PekkoRpcActor$StartedState.lambda$terminate$0(PekkoRpcActor.java:583)
>         at 
> org.apache.flink.runtime.concurrent.ClassLoadingUtils.runWithContextClassLoader(ClassLoadingUtils.java:83)
> ... (22 more lines)
> >>> Event[Job]       | Warning | JOBEXCEPTION    | 
> >>> org.apache.flink.util.FlinkExpectedException: The TaskExecutor is 
> >>> shutting down.
>         at 
> org.apache.flink.runtime.taskexecutor.TaskExecutor.onStop(TaskExecutor.java:523)
>         at 
> org.apache.flink.runtime.rpc.RpcEndpoint.internalCallOnStop(RpcEndpoint.java:255)
>         at 
> org.apache.flink.runtime.rpc.pekko.PekkoRpcActor$StartedState.lambda$terminate$0(PekkoRpcActor.java:583)
>         at 
> org.apache.flink.runtime.concurrent.ClassLoadingUtils.runWithContextClassLoader(ClassLoadingUtils.java:83)
> ... (22 more lines)
> ...
> Restart count hit threshold: 1
> >>> Event[Job]       | Warning | RESTARTUNHEALTHYJOB | Restart count hit 
> >>> threshold: 1
> Deleting cluster with Foreground propagation
> Scaling JobManager Deployment to zero with 60 seconds timeout...
> Completed Scaling JobManager Deployment to zero
> Deleting JobManager Deployment with 298 seconds timeout...
> Completed Deleting JobManager Deployment
> Keeping HA metadata for last-state restore
> Resubmitting Flink job...
> >>> Event[Job]       | Info    | SUBMIT          | Starting deployment
> Deploying application cluster requiring last-state from HA metadata
> Job graph in ConfigMap basic-checkpoint-ha-example-cluster-config-map is 
> deleted
> >>> Event[Job]       | Warning | RESTOREFAILED   | HA metadata is not 
> >>> available to restore from last state.It is possible that the job has 
> >>> finished or terminally failed, or the configmaps have been deleted.
> >>> Status[Job]      | Error   | FAILED          | {...}
> {code}
> End state: {{{}jobStatus.state=FINISHED{}}}, 
> {{{}jobManagerDeploymentStatus=MISSING{}}}, {{{}lifecycleState=STABLE{}}}, 
> {{error}} empty, no pods, {{RestoreFailed}} event count still 1 after 6 
> minutes.
> In both triggers the attempted submit also removed the {{executionPlan-*}} 
> entry from {{-cluster-config-map}} (5 keys to 0) before the validation threw.
> h1. Impact
> The impact is different per each trigger:
>  * Trigger A: a stateless job with HA enabled cannot recover from a lost 
> JobManager deployment without manual intervention, and the operator repeats a 
> failing deploy attempt on every reconcile.
>  * Trigger B: a stateless job with HA enabled and the health check turned on 
> is terminated by the operator and never restarted, with no error recorded and 
> lifecycle {{{}STABLE{}}}. Alerting on {{lifecycleState}} or {{status.error}} 
> does not catch it.
> The documented semantics of {{stateless}} ("upgrades from empty state") are 
> not honoured by the recovery paths.
> h1. Workaround
> The workaroung for the described issue is represented by any spec change, for 
> example bumping {{{}restartNonce{}}}.
>  * In the trigger A state (job {{{}RECONCILING{}}}) this cancels in stateless 
> mode, deletes the HA ConfigMaps and redeploys with a new job id (verified, 
> {{RUNNING}} about 30 s after the patch).
>  * In the trigger B state (job {{{}FINISHED{}}}) the suspend step is a no-op, 
> the cluster is redeployed with a new job id and the HA ConfigMaps are left 
> behind.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to