From 970043c1f05d9cab84a8e017f57955a7041d0907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Fr=C3=A9con?= Date: Fri, 5 Apr 2024 14:16:09 +0200 Subject: [PATCH] Add generic microvm API lib and use it Add a generic API to create microVMs in an "agnostic" way, and migrate to the new API in the orchestrator.sh and runner.sh scripts. While the API has code for podman, this update only focuses on migrating the existing krunvm-based code to the new API. The new API is implemented in lib/microvm.sh, and provides a set of docker/podman lookalike commands to manage microVMs. The function microvm_runtime can be used to pinpoint or auto-detect the runtime to use depending on commands available at the host. The API is **not** able to handle several krunvm-based microVMs at the same time. --- lib/common.sh | 28 +++-- lib/microvm.sh | 269 ++++++++++++++++++++++++++++++++++++++++++++++++ orchestrator.sh | 28 +++-- runner.sh | 157 ++++++++++++++-------------- 4 files changed, 389 insertions(+), 93 deletions(-) create mode 100644 lib/microvm.sh diff --git a/lib/common.sh b/lib/common.sh index 9fbab23..c382d9f 100644 --- a/lib/common.sh +++ b/lib/common.sh @@ -51,9 +51,30 @@ usage() { } check_command() { + OPTIND=1 + _hard=1 + _warn=0 + while getopts "sw-" _opt; do + case "$_opt" in + s) # Soft check, return an error code instead of exiting + _hard=0;; + w) # Print a warning when soft checking + _warn=1;; + -) # End of options, everything after is the command + break;; + ?) + error "$_opt is an unrecognised option";; + esac + done + shift $((OPTIND-1)) trace "Checking $1 is an accessible command" if ! command -v "$1" >/dev/null 2>&1; then - error "Command not found: $1" + if is_true "$_hard"; then + error "Command not found: $1" + elif is_true "$_warn"; then + warn "Command not found: $1" + fi + return 1 fi } @@ -70,11 +91,6 @@ get_env() ( fi ) -run_krunvm() { - debug "Running krunvm $*" - buildah unshare krunvm "$@" -} - tac() { awk '{ buffer[NR] = $0; } END { for(i=NR; i>0; i--) { print buffer[i] } }' } diff --git a/lib/microvm.sh b/lib/microvm.sh new file mode 100644 index 0000000..5262210 --- /dev/null +++ b/lib/microvm.sh @@ -0,0 +1,269 @@ +#!/bin/sh + +# This implements an API to manage microVMs using krunvm and podman. + + +# Runtime to use for microVMs: podman+krun, krunvm. When empty, it will be +# automatically selected. +: "${KRUNVM_RUNNER_RUNTIME:=""}" + +# Run krunvm with the provided arguments, behind a buildah unshare. +run_krunvm() { + debug "Running krunvm $*" + buildah unshare krunvm "$@" +} + + +# Automatically select a microVM runtime based on the available commands. Set +# the KRUNVM_RUNNER_RUNTIME variable. +_microvm_runtime_auto() { + if check_command -s -- krun; then + check_command podman + KRUNVM_RUNNER_RUNTIME="podman+krun" + elif check_command -s -- krunvm; then + check_command buildah + KRUNVM_RUNNER_RUNTIME="krunvm" + fi + info "Automatically selected $KRUNVM_RUNNER_RUNTIME to handle microVMs" +} + + +# Set the microVM runtime to use. When no argument is provided, it will try to +# automatically detect it based on the available commands. +# shellcheck disable=SC2120 +microvm_runtime() { + # Pick runtime provided as an argument, when available. + [ "$#" -gt 0 ] && KRUNVM_RUNNER_RUNTIME="$1" + + # When no runtime is provided, try to auto-detect it. + [ -z "${KRUNVM_RUNNER_RUNTIME:-""}" ] && _microvm_runtime_auto + + # Enforce podman+krun as soon as anything starting podman is provided. + [ "${KRUNVM_RUNNER_RUNTIME#podman}" != "$KRUNVM_RUNNER_RUNTIME" ] && KRUNVM_RUNNER_RUNTIME="podman+krun" + + # Check if the runtime is valid. + case "$KRUNVM_RUNNER_RUNTIME" in + podman*) + check_command podman + check_command krun + ;; + krunvm) + check_command krunvm + check_command buildah + ;; + *) + error "Unknown microVM runtime: $KRUNVM_RUNNER_RUNTIME" + ;; + esac +} + + +# List all microVMs. +microvm_list() { + [ -z "$KRUNVM_RUNNER_RUNTIME" ] && microvm_runtime + case "$KRUNVM_RUNNER_RUNTIME" in + podman*) + podman ps -a --format "{{.Names}}" + ;; + krunvm) + run_krunvm list + ;; + esac +} + + +_krunvm_create() { + KRUNVM_RUNNER_IMAGE=$1 + verbose "Creating microVM '${KRUNVM_RUNNER_NAME}', $KRUNVM_RUNNER_CPUS vCPUs, ${KRUNVM_RUNNER_MEM}M memory" + # Note: reset arguments! + set -- \ + --cpus "$KRUNVM_RUNNER_CPUS" \ + --mem "$KRUNVM_RUNNER_MEM" \ + --dns "$KRUNVM_RUNNER_DNS" \ + --name "$KRUNVM_RUNNER_NAME" + if [ -n "$KRUNVM_RUNNER_VOLS" ]; then + while IFS= read -r mount || [ -n "$mount" ]; do + if [ -n "$mount" ]; then + set -- "$@" --volume "$mount" + fi + done < "${RUNNER_ENVIRONMENT}/${RUNNER_ID}.trm" - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - fi - if [ "$RUNNER_PID" ]; then - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) - else - warning "No PID to wait for" - fi - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) - fi - elif [ -n "$RUNNER_PID" ]; then - kill_tree "$RUNNER_PID" - # shellcheck disable=SC2046 # We want to wait for all children - waitpid $(ps_tree "$RUNNER_PID"|tac) + # Request for termination through .trm file, whenever possible. Otherwise, + # just stop the VM. + if [ -n "$RUNNER_ENVIRONMENT" ] \ + && [ -f "${RUNNER_ENVIRONMENT}/${1}.tkn" ] \ + && [ -n "${RUNNER_SECRET:-}" ]; then + verbose "Requesting termination via ${RUNNER_ENVIRONMENT}/${1}.trm" + printf %s\\n "$RUNNER_SECRET" > "${RUNNER_ENVIRONMENT}/${1}.trm" + microvm_wait "${RUNNER_PREFIX}-$1" + else + microvm_stop "${RUNNER_PREFIX}-$1" fi } + cleanup() { trap '' EXIT - if [ -n "${RUNNER_PID:-}" ]; then - vm_terminate - fi + if [ -n "${RUNNER_ID:-}" ]; then + vm_terminate "$RUNNER_ID" vm_delete "$RUNNER_ID" fi } @@ -339,9 +332,19 @@ trap cleanup EXIT iteration=0 while true; do + # Prefetch, since this might take time and we want to be ready to count away + # download time from the termination setting. + microvm_pull "$RUNNER_IMAGE" + + # Terminate in xx seconds. This is mostly used for demo purposes, but might + # help keeping the machines "warm" and actualised (as per the pull above). + if [ -n "$RUNNER_TERMINATE" ]; then + verbose "Terminating runner in $RUNNER_TERMINATE seconds" + sleep "$RUNNER_TERMINATE" && cleanup & + fi + RUNNER_ID="${loop}-$(random_string)" - vm_create "${RUNNER_ID}" - vm_start "${RUNNER_ID}" + vm_run "${RUNNER_ID}" vm_delete "${RUNNER_ID}" RUNNER_ID=