Skip to content

Commit 7e036aa

Browse files
author
Mrunal Patel
authored
Merge pull request #1541 from adrianreber/lazy
checkpoint: support lazy migration
2 parents 5274430 + ec26065 commit 7e036aa

File tree

5 files changed

+175
-3
lines changed

5 files changed

+175
-3
lines changed

checkpoint.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ checkpointed.`,
3030
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
3131
cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
3232
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
33+
cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
34+
cli.StringFlag{Name: "status-fd", Value: "", Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
3335
cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
3436
cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
3537
cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},

libcontainer/container_linux.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -621,9 +621,24 @@ func (c *linuxContainer) checkCriuFeatures(criuOpts *CriuOpts, rpcOpts *criurpc.
621621
logrus.Debugf("Feature check says: %s", criuFeatures)
622622
missingFeatures := false
623623

624-
if *criuFeat.MemTrack && !*criuFeatures.MemTrack {
625-
missingFeatures = true
626-
logrus.Debugf("CRIU does not support MemTrack")
624+
// The outer if checks if the fields actually exist
625+
if (criuFeat.MemTrack != nil) &&
626+
(criuFeatures.MemTrack != nil) {
627+
// The inner if checks if they are set to true
628+
if *criuFeat.MemTrack && !*criuFeatures.MemTrack {
629+
missingFeatures = true
630+
logrus.Debugf("CRIU does not support MemTrack")
631+
}
632+
}
633+
634+
// This needs to be repeated for every new feature check.
635+
// Is there a way to put this in a function. Reflection?
636+
if (criuFeat.LazyPages != nil) &&
637+
(criuFeatures.LazyPages != nil) {
638+
if *criuFeat.LazyPages && !*criuFeatures.LazyPages {
639+
missingFeatures = true
640+
logrus.Debugf("CRIU does not support LazyPages")
641+
}
627642
}
628643

629644
if missingFeatures {
@@ -779,6 +794,25 @@ func (c *linuxContainer) addMaskPaths(req *criurpc.CriuReq) error {
779794
}
780795
req.Opts.ExtMnt = append(req.Opts.ExtMnt, extMnt)
781796
}
797+
return nil
798+
}
799+
800+
func waitForCriuLazyServer(r *os.File, status string) error {
801+
802+
data := make([]byte, 1)
803+
_, err := r.Read(data)
804+
if err != nil {
805+
return err
806+
}
807+
fd, err := os.OpenFile(status, os.O_TRUNC|os.O_WRONLY, os.ModeAppend)
808+
if err != nil {
809+
return err
810+
}
811+
_, err = fd.Write(data)
812+
if err != nil {
813+
return err
814+
}
815+
fd.Close()
782816

783817
return nil
784818
}
@@ -846,6 +880,7 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
846880
EmptyNs: proto.Uint32(criuOpts.EmptyNs),
847881
OrphanPtsMaster: proto.Bool(true),
848882
AutoDedup: proto.Bool(criuOpts.AutoDedup),
883+
LazyPages: proto.Bool(criuOpts.LazyPages),
849884
}
850885

851886
fcg := c.cgroupManager.GetPaths()["freezer"]
@@ -896,6 +931,24 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
896931
Opts: &rpcOpts,
897932
}
898933

934+
if criuOpts.LazyPages {
935+
// lazy migration requested; check if criu supports it
936+
feat := criurpc.CriuFeatures{
937+
LazyPages: proto.Bool(true),
938+
}
939+
940+
if err := c.checkCriuFeatures(criuOpts, &rpcOpts, &feat); err != nil {
941+
return err
942+
}
943+
944+
statusRead, statusWrite, err := os.Pipe()
945+
if err != nil {
946+
return err
947+
}
948+
rpcOpts.StatusFd = proto.Int32(int32(statusWrite.Fd()))
949+
go waitForCriuLazyServer(statusRead, criuOpts.StatusFd)
950+
}
951+
899952
//no need to dump these information in pre-dump
900953
if !criuOpts.PreDump {
901954
for _, m := range c.config.Mounts {
@@ -1048,6 +1101,7 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
10481101
EmptyNs: proto.Uint32(criuOpts.EmptyNs),
10491102
OrphanPtsMaster: proto.Bool(true),
10501103
AutoDedup: proto.Bool(criuOpts.AutoDedup),
1104+
LazyPages: proto.Bool(criuOpts.LazyPages),
10511105
},
10521106
}
10531107

libcontainer/criu_opts_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,6 @@ type CriuOpts struct {
3535
ManageCgroupsMode cgMode // dump or restore cgroup mode
3636
EmptyNs uint32 // don't c/r properties for namespace from this mask
3737
AutoDedup bool // auto deduplication for incremental dumps
38+
LazyPages bool // restore memory pages lazily using userfaultfd
39+
StatusFd string // fd for feedback when lazy server is ready
3840
}

restore.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ using the runc checkpoint command.`,
8686
Name: "auto-dedup",
8787
Usage: "enable auto deduplication of memory images",
8888
},
89+
cli.BoolFlag{
90+
Name: "lazy-pages",
91+
Usage: "use userfaultfd to lazily restore memory pages",
92+
},
8993
},
9094
Action: func(context *cli.Context) error {
9195
if err := checkArgs(context, 1, exactArgs); err != nil {
@@ -128,5 +132,7 @@ func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
128132
FileLocks: context.Bool("file-locks"),
129133
PreDump: context.Bool("pre-dump"),
130134
AutoDedup: context.Bool("auto-dedup"),
135+
LazyPages: context.Bool("lazy-pages"),
136+
StatusFd: context.String("status-fd"),
131137
}
132138
}

tests/integration/checkpoint.bats

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,111 @@ function teardown() {
122122
[ "$status" -eq 0 ]
123123
[[ "${output}" == *"ponG Ping"* ]]
124124
}
125+
126+
@test "checkpoint --lazy-pages and restore" {
127+
# XXX: currently criu require root containers.
128+
requires criu root
129+
130+
# check if lazy-pages is supported
131+
run ${CRIU} check --feature lazy_pages
132+
if [ "$status" -eq 1 ]; then
133+
# this criu does not support lazy migration; skip the test
134+
skip "this criu does not support lazy migration"
135+
fi
136+
137+
sed -i 's;"terminal": true;"terminal": false;' config.json
138+
sed -i 's;"readonly": true;"readonly": false;' config.json
139+
sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
140+
141+
# The following code creates pipes for stdin and stdout.
142+
# CRIU can't handle fifo-s, so we need all these tricks.
143+
fifo=`mktemp -u /tmp/runc-fifo-XXXXXX`
144+
mkfifo $fifo
145+
146+
# For lazy migration we need to know when CRIU is ready to serve
147+
# the memory pages via TCP.
148+
lazy_pipe=`mktemp -u /tmp/lazy-pipe-XXXXXX`
149+
mkfifo $lazy_pipe
150+
151+
# TCP port for lazy migration
152+
port=27277
153+
154+
# stdout
155+
cat $fifo | cat $fifo &
156+
pid=$!
157+
exec 50</proc/$pid/fd/0
158+
exec 51>/proc/$pid/fd/0
159+
160+
# stdin
161+
cat $fifo | cat $fifo &
162+
pid=$!
163+
exec 60</proc/$pid/fd/0
164+
exec 61>/proc/$pid/fd/0
165+
166+
echo -n > $fifo
167+
unlink $fifo
168+
169+
# run busybox
170+
__runc run -d test_busybox <&60 >&51 2>&51
171+
[ $? -eq 0 ]
172+
173+
testcontainer test_busybox running
174+
175+
# checkpoint the running container
176+
mkdir image-dir
177+
mkdir work-dir
178+
# Double fork taken from helpers.bats
179+
# We need to start 'runc checkpoint --lazy-pages' in the background,
180+
# so we double fork in the shell.
181+
(runc --criu "$CRIU" checkpoint --lazy-pages --page-server 0.0.0.0:${port} --status-fd ${lazy_pipe} --work-path ./work-dir --image-path ./image-dir test_busybox & ) &
182+
# Sleeping here. This is ugly, but not sure how else to handle it.
183+
# The return code of the in the background running runc is needed, if
184+
# there is some basic error. If the lazy migration is ready can
185+
# be handled by $lazy_pipe. Which probably will always be ready
186+
# after sleeping two seconds.
187+
sleep 2
188+
# Check if inventory.img was written
189+
[ -e image-dir/inventory.img ]
190+
# If the inventory.img exists criu checkpointed some things, let's see
191+
# if there were other errors in the log file.
192+
run grep -B 5 Error ./work-dir/dump.log -q
193+
[ "$status" -eq 1 ]
194+
195+
# This will block until CRIU is ready to serve memory pages
196+
cat $lazy_pipe
197+
[ "$status" -eq 1 ]
198+
199+
unlink $lazy_pipe
200+
201+
# Double fork taken from helpers.bats
202+
# We need to start 'criu lazy-pages' in the background,
203+
# so we double fork in the shell.
204+
# Start CRIU in lazy-daemon mode
205+
$(${CRIU} lazy-pages --page-server --address 127.0.0.1 --port ${port} -D image-dir &) &
206+
207+
# Restore lazily from checkpoint.
208+
# The restored container needs a different name as the checkpointed
209+
# container is not yet destroyed. It is only destroyed at that point
210+
# in time when the last page is lazily transferred to the destination.
211+
# Killing the CRIU on the checkpoint side will let the container
212+
# continue to run if the migration failed at some point.
213+
__runc --criu "$CRIU" restore -d --work-path ./image-dir --image-path ./image-dir --lazy-pages test_busybox_restore <&60 >&51 2>&51
214+
ret=$?
215+
[ $ret -eq 0 ]
216+
run grep -B 5 Error ./work-dir/dump.log -q
217+
[ "$status" -eq 1 ]
218+
219+
# busybox should be back up and running
220+
testcontainer test_busybox_restore running
221+
222+
runc exec --cwd /bin test_busybox_restore echo ok
223+
[ "$status" -eq 0 ]
224+
[[ ${output} == "ok" ]]
225+
226+
echo Ping >&61
227+
exec 61>&-
228+
exec 51>&-
229+
run cat <&50
230+
[ "$status" -eq 0 ]
231+
[[ "${output}" == *"ponG Ping"* ]]
232+
}

0 commit comments

Comments
 (0)