Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/checkpoint-restore/criu.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSaied Kazemi <saied@google.com>2014-09-03 23:22:10 +0400
committerPavel Emelyanov <xemul@parallels.com>2014-09-09 16:08:22 +0400
commitbd8b5dd53c4742f637fed73d4f9076e4fb0b804c (patch)
treeacd7b72007ae4d745f687e36e093e5c9ac14cb74 /contrib
parent5fcebf2d7ec37e39446b4682aad73a2b26139e14 (diff)
Add a convenience shell script for Docker container C/R
Since the command line for checkpointing and restoring Docker containers is very long and there are some manual steps involved before restoring a container, it's much easier to use a shell script to automate the work. One would simply do: $ sudo docker_cr.sh -c $ sudo docker_cr.sh -r Signed-off-by: Saied Kazemi <saied@google.com> Acked-by: Filipe Brandenburger <filbranden@google.com> Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/docker_cr.sh367
1 files changed, 367 insertions, 0 deletions
diff --git a/contrib/docker_cr.sh b/contrib/docker_cr.sh
new file mode 100755
index 000000000..fd9566673
--- /dev/null
+++ b/contrib/docker_cr.sh
@@ -0,0 +1,367 @@
+#!/bin/bash
+
+#
+# A convenience shell script to call criu for checkpointing and restoring
+# a Docker container.
+#
+# This script saves the user from having to remember all the command
+# line options, some of which are very long. Note that once Docker
+# has native support for checkpoint and restore, there will no longer
+# be a need for this particular shell script.
+#
+
+set -o errexit
+set -o nounset
+set -o pipefail
+
+#
+# These can be set in the environment to override their defaults.
+# Note that while the default value of CRIU_IMG_DIR in this script
+# is a directory in DOCKER_HOME, it doesn't have to be tied to
+# DOCKER_HOME. For example, it can be /var/spool/criu_img.
+#
+: ${DOCKER_HOME=/var/lib/docker}
+: ${DOCKER_BINARY=docker}
+: ${CRIU_IMG_DIR=${DOCKER_HOME}/criu_img}
+: ${CRIU_BINARY=criu}
+
+declare -A BIND_MOUNT
+BIND_MOUNT[/etc/resolv.conf]=.ResolvConfPath
+BIND_MOUNT[/etc/hosts]=.HostsPath
+BIND_MOUNT[/etc/hostname]=.HostnamePath
+# since older versions of docker bind mount /.dockerinit but
+# the path cannot be queried by docker inspect, we have to
+# hard code it here
+BIND_MOUNT[/.dockerinit]="${DOCKER_HOME}/init/dockerinit-1.0.0"
+MOUNT_MAP_ARGS=()
+
+#
+# The default mode is non-verbose, printing only a short message
+# saying if the comand succeeded or failed. For the verbose mode,
+# we could have used set -o xtrace but this option would have
+# generated excessive output suitable for debugging, not normal
+# usage. So we set ${ECHO} to echo in the verbose mode to print
+# selected messages.
+#
+VERBOSE=""
+ECHO=":"
+CMD=""
+PGNAME=$(basename "$0")
+
+usage() {
+ local rv=0
+
+ if [[ -n "${1-}" ]]; then
+ rv=1
+ echo -e "${PGNAME}: $1\n" >&2
+ fi
+
+ cat <<EOF
+Usage:
+ ${PGNAME} -c|-r [-hv] [<container_id>]
+ -c, --checkpoint checkpoint container
+ -h, --help print help message
+ -r, --restore restore container
+ -v, --verbose enable verbose mode
+
+Environment:
+ DOCKER_HOME (default ${DOCKER_HOME})
+ CRIU_IMG_DIR (default ${CRIU_IMG_DIR})
+ DOCKER_BINARY (default ${DOCKER_BINARY})
+ CRIU_BINARY (default ${CRIU_BINARY})
+EOF
+ exit ${rv}
+}
+
+parse_args() {
+ local args
+ local flags
+
+ args=$(getopt --options 'chrv' \
+ --longoptions 'checkpoint help restore verbose' -- "$@")
+ [[ $? == 0 ]] || usage
+ eval set -- "${args}"
+
+ while :; do
+ arg="${1}"
+ shift
+ case "${arg}" in
+ -c|--checkpoint) CMD="dump" ;;
+ -h|--help) usage ;;
+ -r|--restore) CMD="restore" ;;
+ -v|--verbose) VERBOSE="-v"; ECHO="echo" ;;
+ --) break ;;
+ *) usage "internal error parsing arguments!" ;;
+ esac
+ done
+
+ [[ "${CMD}" == "" ]] && usage "need either -c or -r"
+ [[ $# -gt 1 ]] && usage "$# too many arguments"
+
+ # if no container id in args, prompt the user
+ if [[ $# -eq 1 ]]; then
+ CID="$1"
+ else
+ if [[ "${CMD}" == "dump" ]]; then
+ flags=""
+ else
+ # we need -a only for restore
+ flags="-a"
+ fi
+ "${DOCKER_BINARY}" ps ${flags}
+ read -rp $'\nContainer ID: ' CID
+ fi
+}
+
+execute() {
+ # since commands are pretty long and can wrap around
+ # several lines, print a blank line to make it visually
+ # easier to see
+ ${ECHO} -e "\n$*"
+ "$@"
+}
+
+init_container_vars() {
+ local d
+
+ CID=$(get_container_conf .Id)
+
+ d=$("${DOCKER_BINARY}" info 2> /dev/null | awk '/Storage Driver:/ { print $3 }')
+ if [[ "${d}" == "vfs" ]]; then
+ CONTAINER_ROOT_DIR="${DOCKER_HOME}/${d}/dir/${CID}"
+ else
+ CONTAINER_ROOT_DIR="${DOCKER_HOME}/${d}/mnt/${CID}"
+ fi
+ CONTAINER_IMG_DIR="${CRIU_IMG_DIR}/${CID}"
+}
+
+get_container_conf() {
+ local val
+
+ val=$("${DOCKER_BINARY}" inspect --format "{{$1}}" "${CID}")
+ [[ "${val}" == "" ]] && exit 1
+ echo "${val/<no value>/}"
+}
+
+setup_mount_map() {
+ local key
+
+ if [[ "$1" == "dump" ]]; then
+ for key in "${!BIND_MOUNT[@]}"; do
+ MOUNT_MAP_ARGS+=(--ext-mount-map "${key}:${key}")
+ done
+ else
+ for key in "${!BIND_MOUNT[@]}"; do
+ if [[ "${key}" == "/.dockerinit" ]]; then
+ MOUNT_MAP_ARGS+=("--ext-mount-map" "${key}:${BIND_MOUNT[$key]}")
+ else
+ MOUNT_MAP_ARGS+=("--ext-mount-map" "${key}:$(get_container_conf "${BIND_MOUNT[$key]}")")
+ fi
+ done
+ fi
+}
+
+fs_mounted() {
+ grep -wq "$1" /proc/mounts
+}
+
+
+#
+# Pretty print the mount command in verbose mode by putting each branch
+# pathname on a single line for easier visual inspection.
+#
+pp_mount() {
+ ${ECHO} -e "\nmount -t $1 -o"
+ ${ECHO} "${2}" | tr ':' '\n'
+ ${ECHO} none
+ ${ECHO} "${3}"
+}
+
+#
+# Reconstruct the AUFS filesystem from information in CRIU's dump log.
+# The dump log has a series of branch entries for each process in the
+# entire process tree in the following form:
+#
+# (00.014075) /sys/fs/aufs/si_f598876b0855b883/br0 : /var/lib/docker/aufs/diff/<ID>
+#
+# Note that this script assumes that all processes in the process
+# tree have the same AUFS filesystem. This assumption is fairly
+# safe for typical Docker containers.
+#
+setup_aufs() {
+ local logf="${CONTAINER_IMG_DIR}/dump.log"
+ local tmpf="${CONTAINER_IMG_DIR}/aufs.br"
+ local br
+ local branches
+
+ # create a temporary file with branches listed in
+ # ascending order (line 1 is branch 0)
+ awk '/aufs.si_/ { print $2, $4 }' "${logf}" | sort | uniq | \
+ awk '{ print $2 }' > "${tmpf}"
+
+ # nothing to do if filesystem already mounted
+ fs_mounted "${CONTAINER_ROOT_DIR}" && return
+
+ # construct the mount option string from branches
+ branches=""
+ while read br; do
+ branches+="${branches:+:}${br}"
+ done < "${tmpf}"
+
+ # mount the container's filesystem
+ pp_mount "aufs" "${branches}" "${CONTAINER_ROOT_DIR}"
+ mount -t aufs -o br="${branches}" none "${CONTAINER_ROOT_DIR}"
+ rm -f "${tmpf}"
+}
+
+#
+# Reconstruct the UnionFS filesystem from information in CRIU's dump log.
+# The dump log has the mountinfo root entry for the filesystem. The
+# options field contains the list of directories that make up the UnionFS.
+#
+# Note that this script assumes that all processes in the process
+# tree have the same UnionFS filesystem. This assumption is fairly
+# safe for typical Docker containers.
+#
+# XXX If /dev/null was manually created by Docker (i.e., it's not in
+# a branch), create it. Although this has worked so far, it needs
+# a deeper look as I am not sure if /dev/null should be created as
+# a regular file to be the target of a bind mount or created as a
+# device file by mknod.
+#
+setup_unionfs() {
+ local logf="${CONTAINER_IMG_DIR}/dump.log"
+ local dirs
+
+ # nothing to do if filesystem already mounted
+ fs_mounted "${CONTAINER_ROOT_DIR}" && return
+
+ dirs=$(sed -n -e 's/.*type.*dirs=/dirs=/p' "${logf}")
+ [[ "${dirs}" = "" ]] && echo "do not have branch information" && exit 1
+
+ # mount the container's filesystem
+ pp_mount "unionfs" "${dirs}" "${CONTAINER_ROOT_DIR}"
+ mount -t unionfs -o "${dirs}" none "${CONTAINER_ROOT_DIR}"
+
+ # see comment at the beginning of the function
+ if [[ ! -e "${CONTAINER_ROOT_DIR}/dev/null" ]]; then
+ execute touch "${CONTAINER_ROOT_DIR}/dev/null"
+ fi
+}
+
+prep_dump() {
+ local pid
+
+ pid=$(get_container_conf .State.Pid)
+
+ # docker returns 0 for containers it thinks have exited
+ # (i.e., dumping a restored container again)
+ if [[ ${pid} -eq 0 ]]; then
+ read -p "Process ID: " pid
+ fi
+
+ # remove files previously created by criu but not others files (if any)
+ mkdir -p "${CONTAINER_IMG_DIR}"
+ rm -f "${CONTAINER_IMG_DIR}"/*.{img,log,pid} "${CONTAINER_IMG_DIR}"/stats-restore
+
+ CMD_ARGS=("-t" "${pid}")
+
+ # we need --root only for aufs to compensate for the
+ # erroneous information in /proc/<pid>/map_files
+ if [[ "${CONTAINER_ROOT_DIR}" == *aufs* ]]; then
+ CMD_ARGS+=("--root" "${CONTAINER_ROOT_DIR}")
+ fi
+}
+
+prep_restore() {
+ local aufs_pattern='/sys/fs/aufs/si_'
+ local unionfs_pattern='type.*source.*options.*dirs='
+
+ # set up aufs and unionfs mounts if they're not already set up
+ if grep -q "${aufs_pattern}" "${CONTAINER_IMG_DIR}/dump.log"; then
+ setup_aufs
+ elif grep -q "${unionfs_pattern}" "${CONTAINER_IMG_DIR}/dump.log"; then
+ setup_unionfs
+ fi
+
+ # criu requires this (due to container using pivot_root)
+ if ! grep -q "${CONTAINER_ROOT_DIR}" /proc/mounts; then
+ execute mount --rbind "${CONTAINER_ROOT_DIR}" "${CONTAINER_ROOT_DIR}"
+ MOUNTED=1
+ else
+ MOUNTED=0
+ fi
+
+ CMD_ARGS=("-d" "--root" "${CONTAINER_ROOT_DIR}" "--pidfile" "${CONTAINER_IMG_DIR}/restore.pid")
+}
+
+#
+# Since this function produces output string (either in the
+# verbose mode or from ${CRIU_BINARY}), we set the return value
+# in parameter 1.
+#
+run_criu() {
+ local -a common_args=("-v4" "-D" "${CONTAINER_IMG_DIR}" \
+ "-o" "${CMD}.log" \
+ "--manage-cgroups" \
+ "--evasive-devices")
+
+ setup_mount_map "${CMD}"
+ common_args+=("${MOUNT_MAP_ARGS[@]}")
+
+ # we do not want to exit if there's an error
+ execute "${CRIU_BINARY}" "${CMD}" "${common_args[@]}" "${CMD_ARGS[@]}"
+}
+
+wrap_up() {
+ local logf="${CONTAINER_IMG_DIR}/${CMD}.log"
+ local pidf="${CONTAINER_IMG_DIR}/restore.pid"
+
+ if [[ $1 -eq 0 ]]; then
+ ${ECHO} -e "\n"
+ echo "${CMD} successful"
+ else
+ ${ECHO} -e "\n"
+ echo "${CMD} failed"
+ fi
+
+ if [[ "${VERBOSE}" == "-v" && -e "${logf}" ]]; then
+ if ! grep "finished successfully" "${logf}"; then
+ grep Error "${logf}"
+ fi
+ fi
+
+ if [[ "${CMD}" == "restore" ]]; then
+ if [[ ${MOUNTED} -eq 1 ]]; then
+ execute umount "${CONTAINER_ROOT_DIR}"
+ fi
+
+ if [[ -e "${pidf}" ]]; then
+ ${ECHO} -e "\n$(ps -f -p "$(cat "${pidf}")" --no-headers)"
+ fi
+ fi
+}
+
+main() {
+ local rv=0
+
+ parse_args "$@"
+ init_container_vars
+
+ ${ECHO} "docker binary: ${DOCKER_BINARY}"
+ ${ECHO} "criu binary: ${CRIU_BINARY}"
+ ${ECHO} "image directory: ${CONTAINER_IMG_DIR}"
+ ${ECHO} "container root directory: ${CONTAINER_ROOT_DIR}"
+
+ if [[ "${CMD}" == "dump" ]]; then
+ prep_dump
+ else
+ prep_restore
+ fi
+
+ run_criu || rv=$?
+ wrap_up ${rv}
+ exit ${rv}
+}
+
+main "$@"