|
| 1 | +--- |
| 2 | +title: Support cluster failover for stateful workloads |
| 3 | +authors: |
| 4 | + - "@XiShanYongYe-Chang" |
| 5 | +reviewers: |
| 6 | + - "@mszacillo" |
| 7 | + - "@Dyex719" |
| 8 | + - "@RainbowMango" |
| 9 | +approvers: |
| 10 | + - "@kevin-wangzefeng" |
| 11 | + - "@RainbowMango" |
| 12 | +creation-date: 2025-06-26 |
| 13 | +--- |
| 14 | + |
| 15 | +# Support cluster failover for stateful workloads |
| 16 | + |
| 17 | +This proposal is modified base on the design of https://github.com/karmada-io/karmada/pull/5116. |
| 18 | + |
| 19 | +## Summary |
| 20 | + |
| 21 | +The advantage of stateless workloads is their higher fault tolerance, as the loss of workloads does not affect user sessions. However, for stateful workloads (such as Flink), the loss of workloads may result in the loss of session data, preventing the workload from recovering correctly. |
| 22 | + |
| 23 | +For workloads, failover scenarios include two cases: the first is when the workload itself fails and needs to be migrated to another cluster; the second is when the cluster encounter a failure, such as a network partition, requiring some workloads within the cluster to be migrated. |
| 24 | + |
| 25 | +The Karmada community has completed the design and development for the first scenario in version v1.12: https://github.com/karmada-io/karmada/issues/5788. This proposal will build upon the previous design experience to support the second scenario, which involves stateful workload failover caused by cluster failures. The API design will unify the handling of common aspects in both scenarios to avoid redundant fields and improve the user experience. |
| 26 | + |
| 27 | +## Motivation |
| 28 | + |
| 29 | +Karmada currently supports propagating various types of resources, including Kubernetes objects and CRDs, which is particularly useful for ensuring the resilience of stateful workloads in multi-cluster environments. However, the Karmada system's processing logic is based on the assumption that the propagated resources are stateless. In failover scenarios, users may want to preserve a certain state before failure so that workloads can resume from the point where they stopped in the previous cluster. |
| 30 | + |
| 31 | +For CRDs that handle data processing (such as Flink or Spark), restarting the application from a previous checkpoint can be particularly useful. This allows the application to seamlessly resume processing from the point of interruption while avoiding duplicate processing. Karmada v1.12 supports stateful workload migration after application failures, and the next step is to extend support for stateful workload migration following cluster failures. |
| 32 | + |
| 33 | +### Goals |
| 34 | + |
| 35 | +Support stateful application cluster failover, allowing users to customize the state information that needs to be preserved for workloads in the failed cluster within Karmada. This information is then passed to the migrated workloads, enabling the newly started workloads to recover from the state where the previous cluster stopped. |
| 36 | + |
| 37 | +### No-Goals |
| 38 | + |
| 39 | +- No optimization will be made for cluster failover functionality. |
| 40 | +- No optimization will be made for application failover functionality. |
| 41 | + |
| 42 | +## Proposal |
| 43 | + |
| 44 | +### Flink use case |
| 45 | + |
| 46 | +This section helps to provide a basic understanding of how Flink applications work. |
| 47 | + |
| 48 | +In Flink applications, checkpoints are snapshots of the application's state at a specific point in time. They contain information about all records processed up to that moment. This information is continuously persisted to durable storage at regular intervals. To retrieve the latest Flink application state from persistent storage, the `jobId` of the job being recovered and the path to the persisted state are required. Users can then use this information to modify the Flink CRD accordingly to restore from the latest state. |
| 49 | + |
| 50 | +```yaml |
| 51 | +jobStatus: |
| 52 | + checkpointInfo: |
| 53 | + formatType: FULL |
| 54 | + lastCheckpoint: |
| 55 | + formatType: FULL |
| 56 | + timeStamp: 1734462491693 |
| 57 | + triggerType: PERIODIC |
| 58 | + lastPeriodicCheckpointTimestamp: 1734462491693 |
| 59 | + triggerId: 447b790bb9c88da9ae753f00f45acb0e |
| 60 | + triggerTimestamp: 1734462506836 |
| 61 | + triggerType: PERIODIC |
| 62 | + jobId: e6fdb5c0997c11b0c62d796b3df25e86 |
| 63 | +``` |
| 64 | +
|
| 65 | +The recovery process for Flink workloads can be summarized as follows: |
| 66 | +
|
| 67 | +1. Karmada obtains the `{ .jobStatus.jobId }` from the FlinkDeployment in the failed cluster. |
| 68 | +2. Karmada synchronizes the `jobId` to the migrated FlinkDeployment using a label with a `<user-defined key>:<jobId>`. |
| 69 | +3. The user retrieves checkpoint data using the `jobId` and generates the initialSavepointPath: `/<shared-path>/<job-namespace>/<jobID>/checkpoints/<checkpoint>`. |
| 70 | +4. The user injects the initialSavepointPath information into the migrated FlinkDeployment. |
| 71 | +5. The Flink Operator restarts Flink from the savepoint. |
| 72 | + |
| 73 | +### User Stories |
| 74 | + |
| 75 | +#### Story 1 |
| 76 | + |
| 77 | +As an administrator or operator, I can define the state preservation strategy for Flink applications deployed on Karmada after a cluster failure. This includes preserving the `jobID` of the Flink application, allowing the application to resume processing from the point of failure using the `jobID` after migration. |
| 78 | + |
| 79 | +## Design Details |
| 80 | + |
| 81 | +This proposal provides users with a method to persist the necessary state of workloads during cluster failover migration. By configuring propagation policies, users can leverage the state information preserved by the Karmada system to restore workloads once they are migrated due to cluster failure. Since stateful workloads may have different ways to recover from the latest state, configurability of this feature is crucial for users. |
| 82 | + |
| 83 | +To support this feature, we need at least: |
| 84 | +- A mechanism for stateful workloads to preserve state during cluster failover migration and to reload the preserved state upon workload recovery; |
| 85 | +- The ability for users to customize which state information to preserve during the state preservation phase; |
| 86 | +- The ability for users to customize how the preserved state information is injected into the migrated workload during the state recovery phase. |
| 87 | + |
| 88 | +> Note: An important detail is that if all replicas of a stateful workload are not migrated together, the timing for state recovery becomes uncertain for the stateful workload. Therefore, this proposal focuses on scenarios where all replicas of a stateful workload are migrated together. |
| 89 | + |
| 90 | +### State Preservation |
| 91 | + |
| 92 | +Key design points for state preservation are as follows: |
| 93 | + |
| 94 | +1. The state information to be preserved is parsed from the workload's `.status` field; state information not present in `.status` cannot be preserved. |
| 95 | +2. Users can customize one or more pieces of state information to be preserved for the workload. |
| 96 | +3. The state preservation method can be unified for workload migration caused by both cluster failure and application failure. |
| 97 | +4. The preserved state information is stored as key-value pairs in the `gracefulEvictionTask` of the `ResourceBinding` resource related to the workload. |
| 98 | + |
| 99 | +Modification of PropagationPolicy API |
| 100 | + |
| 101 | +```go |
| 102 | +// FailoverBehavior indicates failover behaviors in case of an application or |
| 103 | +// cluster failure. |
| 104 | +type FailoverBehavior struct { |
| 105 | + // Application indicates failover behaviors in case of application failure. |
| 106 | + // If this value is nil, failover is disabled. |
| 107 | + // If set, the PropagateDeps should be true so that the dependencies could |
| 108 | + // be migrated along with the application. |
| 109 | + // +optional |
| 110 | + Application *ApplicationFailoverBehavior `json:"application,omitempty"` |
| 111 | + |
| 112 | + // Cluster indicates failover behaviors in case of cluster failure. |
| 113 | + // If this value is nil, failover is disabled. |
| 114 | + // +optional |
| 115 | + // Cluster *ClusterFailoverBehavior `json:"cluster,omitempty"` |
| 116 | + |
| 117 | + // StatePreservation defines the policy for preserving and restoring state data |
| 118 | + // during failover events for stateful applications. |
| 119 | + // |
| 120 | + // When an application fails over from one cluster to another, this policy enables |
| 121 | + // the extraction of critical data from the original resource configuration. |
| 122 | + // Upon successful migration, the extracted data is then re-injected into the new |
| 123 | + // resource, ensuring that the application can resume operation with its previous |
| 124 | + // state intact. |
| 125 | + // This is particularly useful for stateful applications where maintaining data |
| 126 | + // consistency across failover events is crucial. |
| 127 | + // If not specified, means no state data will be preserved. |
| 128 | + // |
| 129 | + // Note: This requires the StatefulFailoverInjection feature gate to be enabled, |
| 130 | + // which is alpha. |
| 131 | + // +optional |
| 132 | + StatePreservation *StatePreservation `json:"statePreservation,omitempty"` |
| 133 | +} |
| 134 | + |
| 135 | +// StatePreservation defines the policy for preserving state during failover events. |
| 136 | +type StatePreservation struct { |
| 137 | + // Rules contains a list of StatePreservationRule configurations. |
| 138 | + // Each rule specifies a JSONPath expression targeting specific pieces of |
| 139 | + // state data to be preserved during failover events. An AliasLabelName is associated |
| 140 | + // with each rule, serving as a label key when the preserved data is passed |
| 141 | + // to the new cluster. |
| 142 | + // +required |
| 143 | + Rules []StatePreservationRule `json:"rules"` |
| 144 | +} |
| 145 | + |
| 146 | +// StatePreservationRule defines a single rule for state preservation. |
| 147 | +// It includes a JSONPath expression and an alias name that will be used |
| 148 | +// as a label key when passing state information to the new cluster. |
| 149 | +type StatePreservationRule struct { |
| 150 | + // AliasLabelName is the name that will be used as a label key when the preserved |
| 151 | + // data is passed to the new cluster. This facilitates the injection of the |
| 152 | + // preserved state back into the application resources during recovery. |
| 153 | + // +required |
| 154 | + AliasLabelName string `json:"aliasLabelName"` |
| 155 | + |
| 156 | + // JSONPath is the JSONPath template used to identify the state data |
| 157 | + // to be preserved from the original resource configuration. |
| 158 | + // The JSONPath syntax follows the Kubernetes specification: |
| 159 | + // https://kubernetes.io/docs/reference/kubectl/jsonpath/ |
| 160 | + // |
| 161 | + // Note: The JSONPath expression will start searching from the "status" field of |
| 162 | + // the API resource object by default. For example, to extract the "availableReplicas" |
| 163 | + // from a Deployment, the JSONPath expression should be "{.availableReplicas}", not |
| 164 | + // "{.status.availableReplicas}". |
| 165 | + // |
| 166 | + // +required |
| 167 | + JSONPath string `json:"jsonPath"` |
| 168 | +} |
| 169 | +``` |
| 170 | + |
| 171 | +In this proposal, the `StatePreservation` field is promoted from the `ApplicationFailoverBehavior` struct to the `FailoverBehavior` struct. Therefore, whether it is a cluster failure or an application failure, the `StatePreservation` configuration remains consistent for users. |
| 172 | + |
| 173 | +The `ResourceBinding` API already meets the design goals and can directly reuse the `PreservedLabelState` and `ClustersBeforeFailover` fields in the `gracefulEvictionTask` without introducing new modifications. |
| 174 | + |
| 175 | +### State Recovery |
| 176 | + |
| 177 | +Key design points for state recovery are as follows: |
| 178 | + |
| 179 | +1. State recovery is achieved by injecting the preserved state information as labels into the migrated new workload; |
| 180 | +2. The timing of label injection is when the workload is migrated to a new cluster (to confirm the cluster is the result of new scheduling, the previous cluster scheduling information needs to be stored in `gracefulEvictionTask`). During the propagation of the workload, the controller injects the preserved state information into the manifest of the work object. |
| 181 | + |
| 182 | +> Note: The controller logic for state recovery can fully reuse the modifications made previously for application failover to support stateful workloads. No new changes are required in this proposal. |
| 183 | +
|
| 184 | +### Feature Gate |
| 185 | + |
| 186 | +In Karmada v1.12, the `StatefulFailoverInjection` FeatureGate was introduced to control whether state recovery is performed during the recovery process of stateful applications after application failure. In this proposal, we extend the control of this feature gate to cover stateful application recovery scenarios after cluster failure, thereby unifying the control of stateful application failover recovery. |
| 187 | + |
| 188 | +## System Failure Impact and Mitigation |
| 189 | + |
| 190 | +What situations may cause the feature to fail, and will it cause system crashes? |
| 191 | + |
| 192 | +As mentioned above, this proposal focuses on scenarios where all replicas of a stateful application are migrated together. If users split the replicas of a stateful application and propagate them across different clusters, and the system migrates only some replicas, the stateful application failover recovery feature may not work effectively. |
| 193 | + |
| 194 | +This feature will not cause system crashes. |
| 195 | + |
| 196 | +## Graduation Criteria |
| 197 | + |
| 198 | +### Alpha to Beta |
| 199 | + |
| 200 | +- The `StatePreservation` definition in the `PropagationPolicy` API will transition to Beta; |
| 201 | +- The `PreservedLabelState` and `ClustersBeforeFailover` fields in the `ResourceBinding` API will transition to Beta; |
| 202 | +- End-to-end (E2E) testing has been completed. |
| 203 | + |
| 204 | +### GA Graduation |
| 205 | + |
| 206 | +- Scalability and performance testing; |
| 207 | +- The `StatePreservation` definition in the `PropagationPolicy` API reaches GA; |
| 208 | +- The `PreservedLabelState` and `ClustersBeforeFailover` fields in the `ResourceBinding` API reach GA. |
| 209 | + |
| 210 | +## Notes/Constraints/Caveats |
| 211 | + |
| 212 | +None |
| 213 | + |
| 214 | +## Risks and Mitigations |
| 215 | + |
| 216 | +None |
| 217 | + |
| 218 | +## Impact Analysis on Related Features |
| 219 | + |
| 220 | +No related features are affected at this time. |
| 221 | + |
| 222 | +## Upgrade Impact Analysis |
| 223 | + |
| 224 | +In this proposal, the `StatePreservation` field is promoted from the `ApplicationFailoverBehavior` struct to the `FailoverBehavior` struct. To ensure a smooth upgrade, the following steps can be taken: |
| 225 | + |
| 226 | +- Mark the `StatePreservation` field in the `ApplicationFailoverBehavior` struct as deprecated, and remove it after the `PropagationPolicy` API is upgraded; |
| 227 | +- Maintain compatibility with both old and new API definitions, ensuring functionality remains effective when users still use the old API definition; |
| 228 | +- Remove support for the old API definition in the feature release, meaning that `StatePreservation` defined in `ApplicationFailoverBehavior` will no longer be effective. |
| 229 | + |
| 230 | +## Test Planning |
| 231 | + |
| 232 | +UT: Add unit tests for the newly added functions |
| 233 | +E2E: Add end-to-end test cases for stateful workload recovery after cluster failover. |
0 commit comments