Crash Recovery¶
What does the full cluster crash mean?¶
A full cluster crash is a situation when all database instances where shut down in random order. Being rebooted after such situation, Pod is continuously restarting, and generates the following errors in the log:
It may not be safe to bootstrap the cluster from this node. It was not the last one to leave the cluster and may not contain all the updates.
To force cluster bootstrap with this node, edit the grastate.dat file manually and set safe_to_bootstrap to 1
Note
To avoid this, shutdown your cluster correctly as it is written in Pause/resume Percona XtraDB Cluster.
The Percona Operator for MySQL based on Percona XtraDB Cluster provides two ways of recovery after a full cluster crash.
The Operator is providing automatic crash recovery (by default) and semi-automatic
recovery starting from the version 1.7. For the previous Operator versions,
crash recovery can be done
manually.
Automatic Crash Recovery¶
Crash recovery can be done automatically. This behavior is controlled by the
pxc.autoRecovery option in the deploy/cr.yaml configuration file.
The default value for this option is true, which means that automatic
recovery is turned on.
If this option is set to false, automatic crash recovery is not done,
but semi-automatic recovery is still possible.
In this case you need to get the log from pxc container from all Pods using the following command:
for i in $(seq 0 $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do echo "###############cluster1-pxc-$i##############"; kubectl logs cluster1-pxc-$i -c pxc | grep '(seqno):' ; done
The output of this command should be similar to the following one:
###############cluster1-pxc-0##############
It is cluster1-pxc-0.cluster1-pxc.default.svc.cluster.local node with sequence number (seqno): 18
###############cluster1-pxc-1##############
It is cluster1-pxc-1.cluster1-pxc.default.svc.cluster.local node with sequence number (seqno): 18
###############cluster1-pxc-2##############
It is cluster1-pxc-2.cluster1-pxc.default.svc.cluster.local node with sequence number (seqno): 19
Now find the Pod with the largest seqno (it is cluster1-pxc-2 in the
above example).
Now execute the following commands to start this instance:
kubectl exec cluster1-pxc-2 -c pxc -- sh -c 'kill -s USR1 1'
When the Operator refuses automatic recovery¶
Every time the Operator recovers the cluster automatically, it records the result in the status.recovery field of the Custom Resource:
clusterUUID- the Galera cluster UUID of the node it bootstrapped from,lastRecoveryPod- the Pod it bootstrapped from,lastRecoverySeqNo- the sequence number (seqno) of that Pod,lastRecoveryTime- when the recovery started.
You can check it with the following command:
kubectl get pxc cluster1 -o jsonpath='{.status.recovery}'
On the next full cluster crash, the Operator uses this record as a guardrail against data loss. It relies on the fact that within one cluster UUID, the seqno only grows. The Operator doesn’t change the cluster UUID when it recovers the cluster. The UUID changes only when a new Galera cluster is created, such as during a restore. If the node the Operator would bootstrap from reports a different cluster UUID, or a seqno lower than lastRecoverySeqNo, bootstrapping from it could discard committed transactions. In this case, the Operator doesn’t recover the cluster. It emits the AutomaticRecoveryRefused event with both the recorded and the current values, and the Pods keep waiting:
kubectl get events --field-selector reason=AutomaticRecoveryRefused
The event message looks like this:
automatic recovery refused: cluster UUID or seqno regressed since last recovery (last uuid=f2bf3215-d296-11f1-bfc4-ba01f4b3c974, last seqno=51; current uuid=2278238c-d29b-11f1-8044-f2ed9b3d176b, current seqno=3). Follow the documented manual bootstrap/recovery procedure
The UUID or the seqno changes when a new Galera cluster starts without a recovery by the Operator. For example:
- you restored a backup into the cluster. A restore removes the Galera state files, so the restored cluster starts with a new UUID. The Operator doesn’t reset
status.recoverywhen the restore finishes. If it had recovered the cluster automatically before the restore, the first full cluster crash after the restore is refused. See Restore to the same cluster, - you bootstrapped a new cluster manually, after removing or editing the
grastate.datfiles, - you copied a data directory from another cluster into the PVCs,
- you bootstrapped manually from a node that wasn’t the one with the highest
seqno.
If you did one of these on purpose, the record in status.recovery is outdated, and you need to clean it up or update it before automatic recovery can work again:
-
Make sure the data on the nodes is what you expect. After a restore, a new UUID and a lower
seqnoare expected. If you are not sure why the UUID or theseqnochanged, don’t continue. Recover the cluster manually from the node you trust. -
Remove the outdated record. The
statusis a subresource, so you needkubectl1.24 or newer:kubectl patch pxc cluster1 --subresource=status --type=merge -p '{"status":{"recovery":null}}' -
Check that the record is gone:
kubectl get pxc cluster1 -o jsonpath='{.status.recovery}'A reconcile that was already running when you patched the status can write the old record back. If the output is not empty, run the patch again.
Without the record, the Operator treats the next recovery as the first one: it bootstraps from the node with the highest seqno and records the new values. Instead of removing the record, you can also update clusterUUID and lastRecoverySeqNo to the current values with the same kubectl patch command.
Manual Crash Recovery¶
Warning
This method includes a lot of operations, and therefore, it is intended for advanced users only!
This method involves the following steps:
-
swap the original Percona XtraDB Cluster image with the debug image, which does not reboot after the crash, and force all Pods to run it,
-
find the Pod with the most recent Percona XtraDB Cluster data, run recovery on it, start
mysqld, and allow the cluster to be restarted, -
revert all temporary substitutions.
Let’s assume that a full crash did occur for the cluster named cluster1,
which is based on three Percona XtraDB Cluster Pods.
Note
The following commands are written for Percona XtraDB Cluster 8.0. The same steps are also for Percona XtraDB Cluster 5.7 unless specifically indicated otherwise.
-
Check the current Update Strategy with the following command to make sure Smart Updates are turned off during the recovery:
kubectl get pxc cluster1 -o jsonpath='{.spec.updateStrategy}'If the returned value is
SmartUpdate, please change it toonDeletewith the following command:kubectl patch pxc cluster1 --type=merge --patch '{"spec": {"updateStrategy": "OnDelete" }}' -
Change the normal PXC image inside the cluster object to the debug image:
Note
Please make sure the Percona XtraDB Cluster version for the debug image matches the version currently in use in the cluster. You can run the following command to find out which Percona XtraDB Cluster image is in use:
kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.image}'kubectl patch pxc cluster1 --type="merge" -p '{"spec":{"pxc":{"image":"percona/percona-xtradb-cluster:8.0.45-36.1-debug"}}}'
Note
For Percona XtraDB Cluster 5.7 this command should be as follows:
kubectl patch pxc cluster1 --type="merge" -p '{"spec":{"pxc":{"image":"percona/percona-xtradb-cluster:5.7.44-31.65-debug"}}}'
-
Restart all Pods:
for i in $(seq 0 $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do kubectl delete pod cluster1-pxc-$i --force --grace-period=0; done -
Wait until the Pod
0is ready, and execute the following code (it is required for the Pod liveness check):for i in $(seq 0 $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do until [[ $(kubectl get pod cluster1-pxc-$i -o jsonpath='{.status.phase}') == 'Running' ]]; do sleep 10; done; kubectl exec cluster1-pxc-$i -- touch /var/lib/mysql/sst_in_progress; done -
Wait for all Percona XtraDB Cluster Pods to start, and execute the following code to make sure no mysqld processes are running:
for i in $(seq $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do pid=$(kubectl exec cluster1-pxc-$i -- ps -C mysqld-ps -o pid=); if [[ -n "$pid" ]]; then kubectl exec cluster1-pxc-$i -- kill -9 $pid; fi; done -
Wait for all Percona XtraDB Cluster Pods to start, then find the Percona XtraDB Cluster instance with the most recent data - i.e. the one with the highest sequence number (seqno) :
for i in $(seq 0 $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do echo "###############cluster1-pxc-$i##############"; kubectl exec cluster1-pxc-$i -- cat /var/lib/mysql/grastate.dat; doneThe output of this command should be similar to the following one:
###############cluster1-pxc-0############## # GALERA saved state version: 2.1 uuid: 7e037079-6517-11ea-a558-8e77af893c93 seqno: 18 safe_to_bootstrap: 0 ###############cluster1-pxc-1############## # GALERA saved state version: 2.1 uuid: 7e037079-6517-11ea-a558-8e77af893c93 seqno: 18 safe_to_bootstrap: 0 ###############cluster1-pxc-2############## # GALERA saved state version: 2.1 uuid: 7e037079-6517-11ea-a558-8e77af893c93 seqno: 19 safe_to_bootstrap: 0Now find the Pod with the largest
seqno(it iscluster1-pxc-2in the above example). -
Now execute the following commands in a separate shell to start this instance:
kubectl exec cluster1-pxc-2 -- mysqld --wsrep_recover kubectl exec cluster1-pxc-2 -- sed -i 's/safe_to_bootstrap: 0/safe_to_bootstrap: 1/g' /var/lib/mysql/grastate.dat kubectl exec cluster1-pxc-2 -- sed -i 's/wsrep_cluster_address=.*/wsrep_cluster_address=gcomm:\/\//g' /etc/mysql/node.cnf kubectl exec cluster1-pxc-2 -- mysqldThe
mysqldprocess will initialize the database once again, and it will be available for the incoming connections. -
Go back to the previous shell and return the original Percona XtraDB Cluster image because the debug image is no longer needed:
Note
Please make sure the Percona XtraDB Cluster version for the debug image matches the version currently in use in the cluster.
kubectl patch pxc cluster1 --type="merge" -p '{"spec":{"pxc":{"image":"percona/percona-xtradb-cluster:8.0.45-36.1"}}}'
Note
For Percona XtraDB Cluster 5.7 this command should be as follows:
kubectl patch pxc cluster1 --type="merge" -p '{"spec":{"pxc":{"image":"percona/percona-xtradb-cluster:5.7.44-31.65"}}}'
-
Restart all Pods besides the
cluster1-pxc-2Pod (the recovery donor).for i in $(seq 0 $(($(kubectl get pxc cluster1 -o jsonpath='{.spec.pxc.size}')-1))); do until [[ $(kubectl get pod cluster1-pxc-$i -o jsonpath='{.status.phase}') == 'Running' ]]; do sleep 10; done; kubectl exec cluster1-pxc-$i -- rm /var/lib/mysql/sst_in_progress; done kubectl delete pods --force --grace-period=0 cluster1-pxc-0 cluster1-pxc-1 -
Wait for the successful startup of the Pods which were deleted during the previous step, and finally remove the
cluster1-pxc-2Pod:kubectl delete pods --force --grace-period=0 cluster1-pxc-2 -
After the Pod startup, the cluster is fully recovered.
Note
If you have changed the update strategy on the 1st step, don’t
forget to revert it back to SmartUpdate with the following command:
kubectl patch pxc cluster1 --type=merge --patch '{"spec": {"updateStrategy": "SmartUpdate" }}'
Created: September 19, 2022