master
rst 361 lines 19.1 KB
Raw
1 .. SPDX-License-Identifier: GPL-2.0-or-later
2
3 Qemu COLO Fault Tolerance
4 =========================
5
6 This document gives an overview of COLO's design and how to use it.
7
8 Background
9 ----------
10 Virtual machine (VM) replication is a well known technique for providing
11 application-agnostic software-implemented hardware fault tolerance,
12 also known as "non-stop service".
13
14 COLO (COarse-grained LOck-stepping) is a high availability solution.
15 Both primary VM (PVM) and secondary VM (SVM) run in parallel. They receive the
16 same request from client, and generate response in parallel too.
17 If the response packets from PVM and SVM are identical, they are released
18 immediately. Otherwise, a VM checkpoint (on demand) is conducted.
19
20 Architecture
21 ------------
22 The architecture of COLO is shown in the diagram below.
23 It consists of a pair of networked physical nodes:
24 The primary node running the PVM, and the secondary node running the SVM
25 to maintain a valid replica of the PVM.
26 PVM and SVM execute in parallel and generate output of response packets for
27 client requests according to the application semantics.
28
29 The incoming packets from the client or external network are received by the
30 primary node, and then forwarded to the secondary node, so that both the PVM
31 and the SVM are stimulated with the same requests.
32
33 COLO receives the outbound packets from both the PVM and SVM and compares them
34 before allowing the output to be sent to clients.
35
36 The SVM is qualified as a valid replica of the PVM, as long as it generates
37 identical responses to all client requests. Once the differences in the outputs
38 are detected between the PVM and SVM, COLO withholds transmission of the
39 outbound packets until it has successfully synchronized the PVM state to the SVM.
40
41 Overview::
42
43 Primary Node Secondary Node
44 +------------+ +-----------------------+ +------------------------+ +------------+
45 | | | HeartBeat +<----->+ HeartBeat | | |
46 | Primary VM | +-----------+-----------+ +-----------+------------+ |Secondary VM|
47 | | | | | |
48 | | +-----------|-----------+ +-----------|------------+ | |
49 | | |QEMU +---v----+ | |QEMU +----v---+ | | |
50 | | | |Failover| | | |Failover| | | |
51 | | | +--------+ | | +--------+ | | |
52 | | | +---------------+ | | +---------------+ | | |
53 | | | | VM Checkpoint +-------------->+ VM Checkpoint | | | |
54 | | | +---------------+ | | +---------------+ | | |
55 |Requests<--------------------------\ /-----------------\ /--------------------->Requests|
56 | | | ^ ^ | | | | | | |
57 |Responses+---------------------\ /-|-|------------\ /-------------------------+Responses|
58 | | | | | | | | | | | | | | | |
59 | | | +-----------+ | | | | | | | | | | +----------+ | | |
60 | | | | COLO disk | | | | | | | | | | | | COLO disk| | | |
61 | | | | Manager +---------------------------->| Manager | | | |
62 | | | ++----------+ v v | | | | | v v | +---------++ | | |
63 | | | |+-----------+-+-+-++| | ++-+--+-+---------+ | | | |
64 | | | || COLO Proxy || | | COLO Proxy | | | | |
65 | | | || (compare packet || | |(adjust sequence | | | | |
66 | | | ||and mirror packet)|| | | and ACK) | | | | |
67 | | | |+------------+---+-+| | +-----------------+ | | | |
68 +------------+ +-----------------------+ +------------------------+ +------------+
69 +------------+ | | | | +------------+
70 | VM Monitor | | | | | | VM Monitor |
71 +------------+ | | | | +------------+
72 +---------------------------------------+ +----------------------------------------+
73 | Kernel | | | | | Kernel | |
74 +---------------------------------------+ +----------------------------------------+
75 | | | |
76 +--------------v+ +---------v---+--+ +------------------+ +v-------------+
77 | Storage | |External Network| | External Network | | Storage |
78 +---------------+ +----------------+ +------------------+ +--------------+
79
80 Components
81 ^^^^^^^^^^
82 You can see there are several components in COLO's diagram of architecture.
83 Their functions are described below.
84
85 HeartBeat
86 ~~~~~~~~~
87 Runs on both the primary and secondary nodes, to periodically check platform
88 availability. When the primary node suffers a hardware fail-stop failure,
89 the heartbeat stops responding, the secondary node will trigger a failover
90 as soon as it determines the absence.
91
92 COLO disk Manager
93 ~~~~~~~~~~~~~~~~~
94 When primary VM writes data into image, the colo disk manager captures this data
95 and sends it to secondary VM's which makes sure the context of secondary VM's
96 image is consistent with the context of primary VM 's image.
97 For more details, please refer to docs/block-replication.txt.
98
99 Checkpoint/Failover Controller
100 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
101 Modifications of save/restore flow to realize continuous migration,
102 to make sure the state of VM in Secondary side is always consistent with VM in
103 Primary side.
104
105 COLO Proxy
106 ~~~~~~~~~~
107 Delivers packets to Primary and Secondary, and then compare the responses from
108 both side. Then decide whether to start a checkpoint according to some rules.
109 Please refer to docs/colo-proxy.txt for more information.
110
111 Note:
112 HeartBeat has not been implemented yet, so you need to trigger failover process
113 by using 'x-colo-lost-heartbeat' command.
114
115 COLO operation status
116 ^^^^^^^^^^^^^^^^^^^^^
117
118 Overview::
119
120 +-----------------+
121 | |
122 | Start COLO |
123 | |
124 +--------+--------+
125 |
126 | Main qmp command:
127 | migrate-set-capabilities with x-colo
128 | migrate
129 |
130 v
131 +--------+--------+
132 | |
133 | COLO running |
134 | |
135 +--------+--------+
136 |
137 | Main qmp command:
138 | x-colo-lost-heartbeat
139 | or
140 | some error happened
141 v
142 +--------+--------+
143 | | send qmp event:
144 | COLO failover | COLO_EXIT
145 | |
146 +-----------------+
147
148
149 COLO use the qmp command to switch and report operation status.
150 The diagram just shows the main qmp command, you can get the detail
151 in test procedure.
152
153 Test procedure
154 --------------
155
156 Setup
157 ^^^^^
158
159 Here we are running both instances on the same host for testing,
160 change the IP Addresses if you want to run it on two hosts. Initially
161 ``127.0.0.1`` is the Primary Host and ``127.0.0.2`` is the Secondary Host.
162
163 COLO uses double the guest ram size on the secondary side. The Qemu version
164 should be the same on both hosts.
165
166 Startup qemu
167 ^^^^^^^^^^^^
168 **1. Primary**:
169 Initially, ``$imagefolder/primary.qcow2`` needs to be copied to all hosts.
170 You don't need to change any IP's here, because ``0.0.0.0`` listens on any
171 interface. The chardev's with ``127.0.0.1`` IP's loopback to the local qemu
172 instance::
173
174 # imagefolder="/mnt/vms/colo-test-primary"
175
176 # qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \
177 -device piix3-usb-uhci -device usb-tablet -name primary \
178 -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \
179 -device rtl8139,id=e0,netdev=hn0 \
180 -chardev socket,id=mirror0,host=0.0.0.0,port=9003,server=on,wait=off \
181 -chardev socket,id=compare1,host=0.0.0.0,port=9004,server=on,wait=on \
182 -chardev socket,id=compare0,host=127.0.0.1,port=9001,server=on,wait=off \
183 -chardev socket,id=compare0-0,host=127.0.0.1,port=9001 \
184 -chardev socket,id=compare_out,host=127.0.0.1,port=9005,server=on,wait=off \
185 -chardev socket,id=compare_out0,host=127.0.0.1,port=9005 \
186 -object filter-mirror,id=m0,netdev=hn0,queue=tx,outdev=mirror0 \
187 -object filter-redirector,netdev=hn0,id=redire0,queue=rx,indev=compare_out \
188 -object filter-redirector,netdev=hn0,id=redire1,queue=rx,outdev=compare0 \
189 -object iothread,id=iothread1 \
190 -object colo-compare,id=comp0,primary_in=compare0-0,secondary_in=compare1,\
191 outdev=compare_out0,iothread=iothread1 \
192 -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\
193 children.0.file.filename=$imagefolder/primary.qcow2,children.0.driver=qcow2 -S
194
195
196 **2. Secondary**:
197 Active and hidden images need to be created only once and the
198 size should be the same as ``primary.qcow2``. Again, you don't need to change
199 any IP's here, except for the ``$primary_ip`` variable::
200
201 # imagefolder="/mnt/vms/colo-test-secondary"
202 # primary_ip=127.0.0.1
203
204 # qemu-img create -f qcow2 $imagefolder/secondary-active.qcow2 10G
205
206 # qemu-img create -f qcow2 $imagefolder/secondary-hidden.qcow2 10G
207
208 # qemu-system-x86_64 -enable-kvm -cpu qemu64,kvmclock=on -m 512 -smp 1 -qmp stdio \
209 -device piix3-usb-uhci -device usb-tablet -name secondary \
210 -netdev tap,id=hn0,vhost=off,helper=/usr/lib/qemu/qemu-bridge-helper \
211 -device rtl8139,id=e0,netdev=hn0 \
212 -chardev socket,id=red0,host=$primary_ip,port=9003,reconnect-ms=1000 \
213 -chardev socket,id=red1,host=$primary_ip,port=9004,reconnect-ms=1000 \
214 -object filter-redirector,id=f1,netdev=hn0,queue=tx,indev=red0 \
215 -object filter-redirector,id=f2,netdev=hn0,queue=rx,outdev=red1 \
216 -object filter-rewriter,id=rew0,netdev=hn0,queue=all \
217 -drive if=none,id=parent0,file.filename=$imagefolder/primary.qcow2,driver=qcow2 \
218 -drive if=none,id=childs0,driver=replication,mode=secondary,file.driver=qcow2,\
219 top-id=colo-disk0,file.file.filename=$imagefolder/secondary-active.qcow2,\
220 file.backing.driver=qcow2,file.backing.file.filename=$imagefolder/secondary-hidden.qcow2,\
221 file.backing.backing=parent0 \
222 -drive if=ide,id=colo-disk0,driver=quorum,read-pattern=fifo,vote-threshold=1,\
223 children.0=childs0 \
224 -incoming tcp:0.0.0.0:9998
225
226
227 **3.** On Secondary VM's QEMU monitor, issue command::
228
229 {"execute":"qmp_capabilities"}
230 {"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "return-path", "state": true }, {"capability": "x-colo", "state": true } ] } }
231 {"execute": "nbd-server-start", "arguments": {"addr": {"type": "inet", "data": {"host": "0.0.0.0", "port": "9999"} } } }
232 {"execute": "nbd-server-add", "arguments": {"device": "parent0", "writable": true } }
233
234 Note:
235 a. The qmp command ``nbd-server-start`` and ``nbd-server-add`` must be run
236 before running the qmp command migrate on primary QEMU
237 b. Active disk, hidden disk and nbd target's length should be the
238 same.
239 c. It is better to put active disk and hidden disk in ramdisk. They
240 will be merged into the parent disk on failover.
241
242 **4.** On Primary VM's QEMU monitor, issue command::
243
244 {"execute":"qmp_capabilities"}
245 {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.2", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} }
246 {"execute": "x-blockdev-change", "arguments":{"parent": "colo-disk0", "node": "nbd0" } }
247 {"execute": "migrate-set-capabilities", "arguments": {"capabilities": [ {"capability": "return-path", "state": true }, {"capability": "x-colo", "state": true } ] } }
248 {"execute": "migrate", "arguments": {"uri": "tcp:127.0.0.2:9998" } }
249
250 Note:
251 a. There should be only one NBD Client for each primary disk.
252 b. The qmp command line must be run after running qmp command line in
253 secondary qemu.
254
255 **5.** After the above steps, you will see, whenever you make changes to PVM, SVM will be synced.
256 You can issue command ``{ "execute": "migrate-set-parameters" , "arguments":{ "x-checkpoint-delay": 2000 } }``
257 to change the idle checkpoint period time
258
259 Failover test
260 ^^^^^^^^^^^^^
261 You can kill one of the VMs and Failover on the surviving VM:
262
263 If you killed the Secondary, then follow "Primary Failover".
264 After that, if you want to resume the replication, follow "Primary resume replication"
265
266 If you killed the Primary, then follow "Secondary Failover".
267 After that, if you want to resume the replication, follow "Secondary resume replication"
268
269 Primary Failover
270 ~~~~~~~~~~~~~~~~
271 The Secondary died, resume on the Primary::
272
273 {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "child": "children.1"} }
274 {"execute": "blockdev-del", "arguments": {"node-name": "nbd0"} }
275 {"execute": "object-del", "arguments":{ "id": "comp0" } }
276 {"execute": "object-del", "arguments":{ "id": "iothread1" } }
277 {"execute": "object-del", "arguments":{ "id": "m0" } }
278 {"execute": "object-del", "arguments":{ "id": "redire0" } }
279 {"execute": "object-del", "arguments":{ "id": "redire1" } }
280 {"execute": "x-colo-lost-heartbeat" }
281
282 Secondary Failover
283 ~~~~~~~~~~~~~~~~~~
284 The Primary died, resume on the Secondary and prepare to become the new Primary::
285
286 {"execute": "nbd-server-stop"}
287 {"execute": "x-colo-lost-heartbeat"}
288
289 {"execute": "object-del", "arguments":{ "id": "f2" } }
290 {"execute": "object-del", "arguments":{ "id": "f1" } }
291 {"execute": "chardev-remove", "arguments":{ "id": "red1" } }
292 {"execute": "chardev-remove", "arguments":{ "id": "red0" } }
293
294 {"execute": "chardev-add", "arguments":{ "id": "mirror0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9003" } }, "server": true } } } }
295 {"execute": "chardev-add", "arguments":{ "id": "compare1", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "0.0.0.0", "port": "9004" } }, "server": true } } } }
296 {"execute": "chardev-add", "arguments":{ "id": "compare0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": true } } } }
297 {"execute": "chardev-add", "arguments":{ "id": "compare0-0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9001" } }, "server": false } } } }
298 {"execute": "chardev-add", "arguments":{ "id": "compare_out", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": true } } } }
299 {"execute": "chardev-add", "arguments":{ "id": "compare_out0", "backend": {"type": "socket", "data": {"addr": { "type": "inet", "data": { "host": "127.0.0.1", "port": "9005" } }, "server": false } } } }
300
301 Primary resume replication
302 ~~~~~~~~~~~~~~~~~~~~~~~~~~
303 Resume replication after new Secondary is up.
304
305 Start the new Secondary (Steps 2 and 3 above), then on the Primary::
306
307 {"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.2:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} }
308
309 Wait until disk is synced, then::
310
311 {"execute": "stop"}
312 {"execute": "block-job-cancel", "arguments":{ "device": "resync"} }
313
314 {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.2", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} }
315 {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "nbd0" } }
316
317 {"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } }
318 {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } }
319 {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } }
320 {"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } }
321 {"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } }
322
323 {"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } }
324 {"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.2:9998" } }
325
326 Note:
327 If this Primary previously was a Secondary, then we need to insert the
328 filters before the filter-rewriter by using the
329 ""insert": "before", "position": "id=rew0"" Options. See below.
330
331 Secondary resume replication
332 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
333 Become Primary and resume replication after new Secondary is up. Note
334 that now 127.0.0.1 is the Secondary and 127.0.0.2 is the Primary.
335
336 Start the new Secondary (Steps 2 and 3 above, but with primary_ip=127.0.0.2),
337 then on the old Secondary::
338
339 {"execute": "drive-mirror", "arguments":{ "device": "colo-disk0", "job-id": "resync", "target": "nbd://127.0.0.1:9999/parent0", "mode": "existing", "format": "raw", "sync": "full"} }
340
341 Wait until disk is synced, then::
342
343 {"execute": "stop"}
344 {"execute": "block-job-cancel", "arguments":{ "device": "resync" } }
345
346 {"execute": "blockdev-add", "arguments": {"driver": "nbd", "node-name": "nbd0", "server": {"type": "inet", "host": "127.0.0.1", "port": "9999"}, "export": "parent0", "detect-zeroes": "on"} }
347 {"execute": "x-blockdev-change", "arguments":{ "parent": "colo-disk0", "node": "nbd0" } }
348
349 {"execute": "object-add", "arguments":{ "qom-type": "filter-mirror", "id": "m0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "tx", "outdev": "mirror0" } }
350 {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire0", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "indev": "compare_out" } }
351 {"execute": "object-add", "arguments":{ "qom-type": "filter-redirector", "id": "redire1", "insert": "before", "position": "id=rew0", "netdev": "hn0", "queue": "rx", "outdev": "compare0" } }
352 {"execute": "object-add", "arguments":{ "qom-type": "iothread", "id": "iothread1" } }
353 {"execute": "object-add", "arguments":{ "qom-type": "colo-compare", "id": "comp0", "primary_in": "compare0-0", "secondary_in": "compare1", "outdev": "compare_out0", "iothread": "iothread1" } }
354
355 {"execute": "migrate-set-capabilities", "arguments":{ "capabilities": [ {"capability": "x-colo", "state": true } ] } }
356 {"execute": "migrate", "arguments":{ "uri": "tcp:127.0.0.1:9998" } }
357
358 | Copyright (c) 2016 Intel Corporation
359 | Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
360 | Copyright (c) 2016 Fujitsu, Corp.
361 | Copyright (c) 2026 Lukas Straub <lukasstraub2@web.de>