|
| 1 | +# Procedure to fix a faulty disk in a kubernetes node |
| 2 | + |
| 3 | +This guide is written while handling a corrupted NVMe disks in one of our managed kubernetes bare-metal node. |
| 4 | + |
| 5 | +ssh into the node and check: |
| 6 | + |
| 7 | +```shell |
| 8 | +root@<server-name> ~ # zpool status |
| 9 | + pool: primary |
| 10 | + state: DEGRADED |
| 11 | +status: One or more devices has been removed by the administrator. |
| 12 | + Sufficient replicas exist for the pool to continue functioning in a |
| 13 | + degraded state. |
| 14 | +action: Online the device using zpool online' or replace the device with |
| 15 | + 'zpool replace'. |
| 16 | + scan: scrub repaired 0B in 00:05:33 with 0 errors on Sun Jun 14 00:29:34 2026 |
| 17 | +config: |
| 18 | +
|
| 19 | + NAME STATE READ WRITE CKSUM |
| 20 | + primary DEGRADED 0 0 0 |
| 21 | + mirror-0 DEGRADED 0 0 0 |
| 22 | + nvme-SAMSUNG_MZVL2512HCJQ-00B00_SERIAL-NUMBER ONLINE 0 0 0 |
| 23 | + nvme-SAMSUNG_MZVL2512HCJQ-00B00_SERIAL-NUMBER REMOVED 0 0 0 |
| 24 | +``` |
| 25 | +
|
| 26 | +Procedure: |
| 27 | +
|
| 28 | +1. Cordon and drain the node, check etcd health, and manage rook ceph if it's running on your node |
| 29 | +2. Fix/Request cloud provider for disk change |
| 30 | +3. After fixes/changes, replace the disk in zpool with newer one or fixed one and unset ceph settings and recheck etcd health |
| 31 | + |
| 32 | +## Phase 1: Before taking the node down |
| 33 | + |
| 34 | +### 1. Check etcd health (all members) |
| 35 | + |
| 36 | +```sh |
| 37 | +kubectl -n kube-system get pods -l component=etcd -o wide |
| 38 | +
|
| 39 | +kubectl -n kube-system exec etcd-<healthy-node> -- etcdctl \ |
| 40 | + --cacert=/etc/kubernetes/pki/etcd/ca.crt \ |
| 41 | + --cert=/etc/kubernetes/pki/etcd/server.crt \ |
| 42 | + --key=/etc/kubernetes/pki/etcd/server.key \ |
| 43 | + endpoint health --cluster |
| 44 | +``` |
| 45 | + |
| 46 | +Please note: With one control plane node down in a 3 control plane node cluster, etcd runs 2/3, quorum holds but |
| 47 | +there is zero fault tolerance, so all members must be healthy before you start. |
| 48 | + |
| 49 | +### 2. Check Ceph health and OSD layout (If installed & running on the node) |
| 50 | + |
| 51 | +```sh |
| 52 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph status |
| 53 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph health detail |
| 54 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd tree |
| 55 | +``` |
| 56 | + |
| 57 | +You need HEALTH_OK (or an understood warning), and you need to know which OSDs / mons / MDS live on the node, |
| 58 | +pools must be able to tolerate losing that host for some time |
| 59 | + |
| 60 | +### 3. Set the Ceph noout flag (If installed & running on the node) |
| 61 | + |
| 62 | +Ref - <https://docs.ceph.com/en/latest/rados/troubleshooting/troubleshooting-osd/#stopping-without-rebalancing> |
| 63 | + |
| 64 | +```sh |
| 65 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd set noout |
| 66 | +``` |
| 67 | + |
| 68 | +Ceph usually tries to rebalance OSDs if it detects some OSDs are down and since this entire work is intentional, |
| 69 | +we should disable rebalancing temporarily |
| 70 | + |
| 71 | +### 4. Confirm rook won't auto-remove the OSDs (If installed & running on the node) |
| 72 | + |
| 73 | +This step is a safety measure, so ceph do not go rogue while the node is down |
| 74 | + |
| 75 | +```sh |
| 76 | +kubectl -n rook-ceph get cephcluster -o jsonpath='{.items[0].spec.removeOSDsIfOutAndSafeToRemove}' |
| 77 | +# must be false or empty |
| 78 | +``` |
| 79 | + |
| 80 | +### 5. dry-run drain for verification |
| 81 | + |
| 82 | +```sh |
| 83 | +kubectl drain node-name \ |
| 84 | + --ignore-daemonsets \ |
| 85 | + --delete-emptydir-data \ |
| 86 | + --grace-period=120 \ |
| 87 | + --timeout=10m \ |
| 88 | + --dry-run=client |
| 89 | +``` |
| 90 | + |
| 91 | +Please also take care of any ```PodDisruptionBudget``` related blocks. |
| 92 | + |
| 93 | +### 6. Cordon and Drain after verification |
| 94 | + |
| 95 | +```sh |
| 96 | +kubectl drain node-name \ |
| 97 | + --ignore-daemonsets \ |
| 98 | + --delete-emptydir-data \ |
| 99 | + --grace-period=120 \ |
| 100 | + --timeout=10m |
| 101 | +``` |
| 102 | + |
| 103 | +It evicts all regular workloads so they reschedule elsewhere; DaemonSets and static pods (etcd, apiserver) remain, |
| 104 | +which is expected. |
| 105 | + |
| 106 | +### 7. Confirm the planned degraded state |
| 107 | + |
| 108 | +```sh |
| 109 | +kubectl get nodes # NODE = NotReady,SchedulingDisabled |
| 110 | +# etcd: 2/3 healthy (step 1 command) |
| 111 | +# ceph: 2 osds down, noout set, PGs degraded but all active |
| 112 | +``` |
| 113 | + |
| 114 | +This is the expected state for the whole window, so alerts are expected to come up. |
| 115 | + |
| 116 | +### Rules during the maintenance window |
| 117 | + |
| 118 | +- Do NOT touch the other control plane nodes (etcd has zero fault tolerance). |
| 119 | +- Do NOT restart other OSDs or change Ceph config (PGs have no spare copies). |
| 120 | +- Silence Ceph/node alerts for the window. |
| 121 | + |
| 122 | +--- |
| 123 | + |
| 124 | +## Phase 2: Get the disk changed via cloud provider |
| 125 | + |
| 126 | +### 1. Identify the disks by serial, never by device name |
| 127 | + |
| 128 | +```sh |
| 129 | +lsblk -o NAME,MODEL,SERIAL |
| 130 | +``` |
| 131 | + |
| 132 | +`/dev/nvme0n1` vs `nvme1n1` can swap between boots, serials never lie. The surviving disk has ZFS partitions; |
| 133 | +the new one is blank. |
| 134 | + |
| 135 | +### 2. Wipe the new disk to make sure no residue are present |
| 136 | + |
| 137 | +```sh |
| 138 | +wipefs -a /dev/<new-disk> |
| 139 | +blkdiscard -f /dev/<new-disk> |
| 140 | +``` |
| 141 | + |
| 142 | +It clears any leftover signatures from burn-in/refurb and TRIMs the SSD to a factory-clean state, double-check |
| 143 | +the serial before running. |
| 144 | + |
| 145 | +### 3. Reboot into the normal OS and Replace the dead ZFS mirror member (on the node) |
| 146 | + |
| 147 | +```sh |
| 148 | +zpool status <pool> |
| 149 | +ls -l /dev/disk/by-id/ | grep <new-serial> |
| 150 | +
|
| 151 | +zpool replace <pool> \ |
| 152 | + <old-disk-by-id-name> \ |
| 153 | + /dev/disk/by-id/<new-disk-by-id-name> |
| 154 | +
|
| 155 | +zpool status -v <pool> # use this command to keep monitoring the progress |
| 156 | +``` |
| 157 | + |
| 158 | +### 2. Verify etcd is back to 3/3 |
| 159 | + |
| 160 | +```sh |
| 161 | +# same endpoint health --cluster command as Phase 1 step 1 |
| 162 | +``` |
| 163 | + |
| 164 | +### 3. Uncordon the node |
| 165 | + |
| 166 | +```sh |
| 167 | +kubectl uncordon $NODE |
| 168 | +``` |
| 169 | + |
| 170 | +### 4. Watch Ceph recover (if ceph installed on the node) |
| 171 | + |
| 172 | +```sh |
| 173 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph status |
| 174 | +# wait for: 8/8 osds up, all PGs active+clean, 0% degraded |
| 175 | +``` |
| 176 | + |
| 177 | +### 5. Unset noout, only after all PGs are active+clean (if ceph installed on the node) |
| 178 | + |
| 179 | +Please make sure ```ceph status``` goes back to 0% degradation. it may take 3 to 4 hours sometimes so you need to wait |
| 180 | + |
| 181 | +```sh |
| 182 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph osd unset noout |
| 183 | +``` |
| 184 | + |
| 185 | +### 6. Final verification |
| 186 | + |
| 187 | +Confirm every layer. nodes, Ceph, ZFS, workloads is fully back before you close the ticket. |
| 188 | + |
| 189 | +```sh |
| 190 | +kubectl get nodes # all Ready, none SchedulingDisabled |
| 191 | +kubectl -n rook-ceph exec deploy/rook-ceph-tools -- ceph status # HEALTH_OK |
| 192 | +zpool status <pool> # ONLINE, 0 errors |
| 193 | +kubectl get pods -A | grep -vE 'Running|Completed' # nothing stuck |
| 194 | +``` |
0 commit comments