cri: add enable_criu configuration option · containerd/containerd@7d41b25 · GitHub
Skip to content

Commit 7d41b25

Browse files
committed
cri: add enable_criu configuration option
Add a new `enable_criu` configuration option under CRI plugin runtime settings. When set to false, any checkpoint or restore request will fail fast with an error indicating that CRIU support is disabled by configuration. `enable_criu` currently defaults to true. Add an integration test script to verify that setting `enable_criu` to false in containerd configuration successfully disables checkpoint and restore operations and fails fast. Assisted-by: Antigravity Signed-off-by: Samuel Karp <samuelkarp@google.com> (cherry picked from commit 0649573) Signed-off-by: Samuel Karp <samuelkarp@google.com>
1 parent 48116fa commit 7d41b25

10 files changed

Lines changed: 468 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright The containerd Authors.
4+
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -eu -o pipefail
18+
19+
DIR="$(dirname "${0}")"
20+
cd "${DIR}"
21+
if ! command -v crictl >/dev/null 2>&1; then
22+
echo >&2 "ERROR: crictl binary not found"
23+
exit 1
24+
fi
25+
26+
crictl() {
27+
command crictl --timeout 60s "$@"
28+
}
29+
TESTDIR=$(mktemp -d -p "${PWD}")
30+
SUCCESS=0
31+
CRIU_PATH=$(command -v criu || true)
32+
CRIU_DIR=""
33+
if [ -n "${CRIU_PATH}" ]; then
34+
CRIU_DIR=$(dirname "${CRIU_PATH}")
35+
fi
36+
37+
function get_cleaned_path() {
38+
if [ -z "${CRIU_DIR}" ]; then
39+
echo "$PATH"
40+
return
41+
fi
42+
local real_criu_dir
43+
real_criu_dir=$(realpath "${CRIU_DIR}")
44+
local shadow_dir="${TESTDIR}/shadow_path"
45+
mkdir -p "${shadow_dir}"
46+
for file in "${real_criu_dir}"/*; do
47+
if [ -x "$file" ] && [ "$(basename "$file")" != "criu" ]; then
48+
ln -s "$file" "${shadow_dir}/" || true
49+
fi
50+
done
51+
local cleaned=""
52+
local IFS=':'
53+
for dir in $PATH; do
54+
if [ -d "$dir" ]; then
55+
local real_dir
56+
real_dir=$(realpath "$dir")
57+
if [ "${real_dir}" == "${real_criu_dir}" ]; then
58+
cleaned="${cleaned:+$cleaned:}${shadow_dir}"
59+
else
60+
cleaned="${cleaned:+$cleaned:}$dir"
61+
fi
62+
fi
63+
done
64+
echo "${cleaned}"
65+
}
66+
67+
function cleanup() {
68+
crictl ps -a || true
69+
stop_containerd
70+
if [ "${SUCCESS}" == "1" ]; then
71+
echo PASS
72+
else
73+
echo "--> containerd logs"
74+
if [ -f "${TESTDIR}/containerd.log" ]; then
75+
sed 's/^/----> \t/' <"${TESTDIR}/containerd.log"
76+
fi
77+
echo FAIL
78+
fi
79+
find "${TESTDIR}" -name shm -type d -exec umount {} + >/dev/null 2>&1 || true
80+
find "${TESTDIR}" -name rootfs -type d -exec umount {} + >/dev/null 2>&1 || true
81+
rm -rf "${TESTDIR}" || true
82+
}
83+
trap cleanup EXIT
84+
export CONTAINERD_ADDRESS="$TESTDIR/c.sock"
85+
export CONTAINER_RUNTIME_ENDPOINT="unix://${CONTAINERD_ADDRESS}"
86+
TEST_IMAGE=ghcr.io/containerd/alpine
87+
TESTDATA=testdata
88+
function start_containerd() {
89+
local enable_criu="$1"
90+
local use_path="$2"
91+
echo "--> Starting containerd with enable_criu=${enable_criu}"
92+
cat >"${TESTDIR}/config.toml" <<EOF
93+
version = 3
94+
[plugins."io.containerd.cri.v1.runtime"]
95+
EOF
96+
if [ "${enable_criu}" != "omit" ]; then
97+
cat >>"${TESTDIR}/config.toml" <<EOF
98+
enable_criu = ${enable_criu}
99+
EOF
100+
fi
101+
cat >>"${TESTDIR}/config.toml" <<EOF
102+
[plugins."io.containerd.cri.v1.runtime".containerd]
103+
default_runtime_name = "test-runtime"
104+
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.test-runtime]
105+
runtime_type = "${TEST_RUNTIME:-io.containerd.runc.v2}"
106+
EOF
107+
mkdir -p "${TESTDIR}"/{root,state}
108+
echo "=== STARTING CONTAINERD (enable_criu=${enable_criu}) ===" >> "${TESTDIR}/containerd.log"
109+
if [ -n "${use_path}" ]; then
110+
env PATH="${use_path}" ../../bin/containerd \
111+
--address "${TESTDIR}/c.sock" \
112+
--config "${TESTDIR}/config.toml" \
113+
--root "${TESTDIR}/root" \
114+
--state "${TESTDIR}/state" \
115+
--log-level trace >>"${TESTDIR}/containerd.log" 2>&1 &
116+
else
117+
../../bin/containerd \
118+
--address "${TESTDIR}/c.sock" \
119+
--config "${TESTDIR}/config.toml" \
120+
--root "${TESTDIR}/root" \
121+
--state "${TESTDIR}/state" \
122+
--log-level trace >>"${TESTDIR}/containerd.log" 2>&1 &
123+
fi
124+
echo $! > "${TESTDIR}/containerd.pid"
125+
retry_counter=0
126+
max_retries=10
127+
while true; do
128+
((retry_counter += 1))
129+
if crictl info >/dev/null 2>&1; then
130+
break
131+
fi
132+
sleep 1
133+
if [ "${retry_counter}" -gt "${max_retries}" ]; then
134+
echo "--> Failed to start containerd"
135+
exit 1
136+
fi
137+
done
138+
}
139+
function stop_containerd() {
140+
echo "--> Stopping containerd..."
141+
if [ -f "${TESTDIR}/containerd.pid" ]; then
142+
local pid
143+
pid=$(cat "${TESTDIR}/containerd.pid")
144+
echo "--> Killing containerd PID ${pid}..."
145+
kill -15 "${pid}" || true
146+
sleep 2
147+
if kill -0 "${pid}" 2>/dev/null; then
148+
echo "--> containerd PID ${pid} still running, force killing..."
149+
kill -9 "${pid}" || true
150+
fi
151+
rm -f "${TESTDIR}/containerd.pid"
152+
else
153+
echo "--> No containerd.pid found, using pkill..."
154+
pkill -x containerd || true
155+
sleep 2
156+
fi
157+
}
158+
function setup_container() {
159+
crictl pull "${TEST_IMAGE}" >/dev/null
160+
POD_JSON=$(mktemp)
161+
jq ".log_directory=\"${TESTDIR}\"" "$TESTDATA"/sandbox_config.json >"$POD_JSON"
162+
pod_id=$(crictl runp "$POD_JSON")
163+
ctr_id=$(crictl create "$pod_id" "$TESTDATA"/container_sleep.json "$POD_JSON")
164+
crictl start "$ctr_id" >/dev/null
165+
rm -f "$POD_JSON"
166+
echo "${pod_id}:${ctr_id}"
167+
}
168+
function cleanup_container() {
169+
local pod_id="$1"
170+
local ctr_id="$2"
171+
crictl rm -f "${ctr_id}" >/dev/null 2>&1 || true
172+
crictl rmp -f "${pod_id}" >/dev/null 2>&1 || true
173+
crictl rmi "${TEST_IMAGE}" >/dev/null 2>&1 || true
174+
}
175+
176+
# --- Test Executions ---
177+
rm -f "${TESTDIR}/containerd.log"
178+
179+
# ==============================================================================
180+
# Group 1 (Configuration: enable_criu = false, normal PATH)
181+
# ==============================================================================
182+
start_containerd "false" ""
183+
184+
# Test 1: Checkpoint fails fast when enable_criu = false, and the source container remains running.
185+
ids=$(setup_container)
186+
pod_id=$(echo "$ids" | cut -d: -f1)
187+
ctr_id=$(echo "$ids" | cut -d: -f2)
188+
set +e
189+
output=$(crictl checkpoint --export="$TESTDIR"/cp.tar "${ctr_id}" 2>&1)
190+
exit_code=$?
191+
set -e
192+
if [ $exit_code -eq 0 ] || [[ ! "$output" =~ "criu support is disabled by configuration" ]]; then
193+
echo "ERROR: Test 1 failed (checkpoint did not fail fast). Output: $output"
194+
exit 1
195+
fi
196+
state=$(crictl inspect "${ctr_id}" | jq -r '.status.state')
197+
if [ "$state" != "CONTAINER_RUNNING" ]; then
198+
echo "ERROR: Test 1 failed (source container state is not RUNNING). State: $state"
199+
exit 1
200+
fi
201+
echo "PASS: Test 1: Checkpoint fails fast and source container remains running"
202+
cleanup_container "$pod_id" "$ctr_id"
203+
204+
# Test 2: Restore fails fast when enable_criu = false.
205+
POD_JSON=$(mktemp)
206+
jq ".log_directory=\"${TESTDIR}\"" "$TESTDATA"/sandbox_config.json >"$POD_JSON"
207+
pod_id=$(crictl runp "$POD_JSON")
208+
touch "$TESTDIR/dummy-checkpoint.tar"
209+
RESTORE_JSON=$(mktemp)
210+
jq ".image.image=\"$TESTDIR/dummy-checkpoint.tar\"" "$TESTDATA"/container_sleep.json >"$RESTORE_JSON"
211+
set +e
212+
output=$(crictl create "$pod_id" "$RESTORE_JSON" "$POD_JSON" 2>&1)
213+
exit_code=$?
214+
set -e
215+
rm -f "$RESTORE_JSON" "$POD_JSON"
216+
if [ $exit_code -eq 0 ] || [[ ! "$output" =~ "criu support is disabled by configuration" ]]; then
217+
echo "ERROR: Test 2 failed (restore did not fail fast). Output: $output"
218+
exit 1
219+
fi
220+
echo "PASS: Test 2: Restore fails fast when enable_criu = false"
221+
crictl rmp -f "$pod_id" >/dev/null 2>&1 || true
222+
223+
stop_containerd
224+
225+
# ==============================================================================
226+
# Group 2 (Configuration: enable_criu = true, normal PATH)
227+
# ==============================================================================
228+
start_containerd "true" ""
229+
230+
# Test 3: Normal checkpoint and restore preserves container state.
231+
ids=$(setup_container)
232+
pod_id=$(echo "$ids" | cut -d: -f1)
233+
ctr_id=$(echo "$ids" | cut -d: -f2)
234+
crictl exec "$ctr_id" touch /root/state_file
235+
rm -f "$TESTDIR"/state_checkpoint.tar
236+
crictl checkpoint --export="$TESTDIR"/state_checkpoint.tar "${ctr_id}"
237+
cleanup_container "$pod_id" "$ctr_id"
238+
239+
POD_JSON=$(mktemp)
240+
jq ".log_directory=\"${TESTDIR}\"" "$TESTDATA"/sandbox_config.json >"$POD_JSON"
241+
pod_id=$(crictl runp "$POD_JSON")
242+
RESTORE_JSON=$(mktemp)
243+
jq ".image.image=\"$TESTDIR/state_checkpoint.tar\"" "$TESTDATA"/container_sleep.json >"$RESTORE_JSON"
244+
restored_ctr_id=$(crictl create "$pod_id" "$RESTORE_JSON" "$POD_JSON")
245+
rm -f "$RESTORE_JSON" "$POD_JSON"
246+
crictl start "$restored_ctr_id"
247+
set +e
248+
crictl exec "$restored_ctr_id" ls /root/state_file >/dev/null 2>&1
249+
exit_code=$?
250+
set -e
251+
if [ $exit_code -ne 0 ]; then
252+
echo "ERROR: Test 3 failed (state_file not found in restored container)."
253+
exit 1
254+
fi
255+
echo "PASS: Test 3: Normal checkpoint and restore preserves container state"
256+
cleanup_container "$pod_id" "$restored_ctr_id"
257+
258+
stop_containerd
259+
260+
# ==============================================================================
261+
# Group 3 (Configuration: enable_criu omitted/defaults, normal PATH)
262+
# ==============================================================================
263+
start_containerd "omit" ""
264+
265+
# Test 4: enable_criu omitted from configuration defaults to true (allowing checkpoint/restore).
266+
ids=$(setup_container)
267+
pod_id=$(echo "$ids" | cut -d: -f1)
268+
ctr_id=$(echo "$ids" | cut -d: -f2)
269+
rm -f "$TESTDIR"/omitted_checkpoint.tar
270+
crictl checkpoint --export="$TESTDIR"/omitted_checkpoint.tar "${ctr_id}"
271+
echo "PASS: Test 4: enable_criu omitted from configuration defaults to true"
272+
cleanup_container "$pod_id" "$ctr_id"
273+
274+
stop_containerd
275+
276+
# ==============================================================================
277+
# Group 4 (Configuration: enable_criu = true, cleaned PATH without CRIU)
278+
# ==============================================================================
279+
if [ -n "${CRIU_DIR}" ]; then
280+
CLEANED_PATH=$(get_cleaned_path)
281+
282+
start_containerd "true" "${CLEANED_PATH}"
283+
284+
# Test 5: CRIU missing from PATH, enable_criu = true.
285+
# Verifies that when CRIU is enabled but missing, it fails with the binary missing error.
286+
ids=$(setup_container)
287+
pod_id=$(echo "$ids" | cut -d: -f1)
288+
ctr_id=$(echo "$ids" | cut -d: -f2)
289+
set +e
290+
output=$(crictl checkpoint --export="$TESTDIR"/cp.tar "${ctr_id}" 2>&1)
291+
exit_code=$?
292+
set -e
293+
if [ $exit_code -eq 0 ] || [[ ! "$output" =~ "criu binary not found" ]]; then
294+
echo "ERROR: Test 5 failed. Output: $output"
295+
exit 1
296+
fi
297+
echo "PASS: Test 5: Fails with binary not found as expected when enable_criu = true"
298+
cleanup_container "$pod_id" "$ctr_id"
299+
300+
stop_containerd
301+
fi
302+
303+
SUCCESS=1

docs/cri/config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ version = 3
272272
cdi_spec_dirs = ['/etc/cdi', '/var/run/cdi']
273273
drain_exec_sync_io_timeout = '0s'
274274
ignore_deprecation_warnings = []
275+
enable_criu = true
275276
276277
[plugins.'io.containerd.cri.v1.runtime'.containerd]
277278
default_runtime_name = 'runc'

internal/cri/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,10 @@ type RuntimeConfig struct {
427427
// IgnoreDeprecationWarnings is the list of the deprecation IDs (such as "io.containerd.deprecation/pull-schema-1-image")
428428
// that should be ignored for checking "ContainerdHasNoDeprecationWarnings" condition.
429429
IgnoreDeprecationWarnings []string `toml:"ignore_deprecation_warnings" json:"ignoreDeprecationWarnings"`
430+
431+
// EnableCRIU enables CRIU (Checkpoint/Restore In Userspace) support.
432+
// When set to false, checkpoint/restore operations will be disabled.
433+
EnableCRIU *bool `toml:"enable_criu" json:"enableCRIU"`
430434
}
431435

432436
// X509KeyPairStreaming contains the x509 configuration for streaming

internal/cri/config/config_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,3 +508,12 @@ func TestCheckLocalImagePullConfigs(t *testing.T) {
508508
})
509509
}
510510
}
511+
512+
func TestDefaultConfigEnableCRIU(t *testing.T) {
513+
if runtime.GOOS != "linux" {
514+
t.Skip("only supported on Linux")
515+
}
516+
cfg := DefaultRuntimeConfig()
517+
assert.NotNil(t, cfg.EnableCRIU)
518+
assert.True(t, *cfg.EnableCRIU)
519+
}

internal/cri/config/config_unix.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,6 @@ func DefaultRuntimeConfig() RuntimeConfig {
109109
DrainExecSyncIOTimeout: "0s",
110110
EnableUnprivilegedPorts: true,
111111
EnableUnprivilegedICMP: true,
112+
EnableCRIU: func() *bool { v := true; return &v }(),
112113
}
113114
}

internal/cri/config/config_windows.go

Lines changed: 1 addition & 0 deletions

0 commit comments

Comments
 (0)