diff --git a/rootfs-builder/README.md b/rootfs-builder/README.md index 861a48007..1218db010 100644 --- a/rootfs-builder/README.md +++ b/rootfs-builder/README.md @@ -1,13 +1,17 @@ -* [Supported base OSs](#supported-base-oss) -* [Rootfs requirements](#rootfs-requirements) -* [Creating a rootfs](#creating-a-rootfs) -* [Creating a rootfs with kernel modules](#creating-a-rootfs-with-kernel-modules) -* [Build a rootfs using Docker](#build-a-rootfs-using-docker) -* [Adding support for a new guest OS](#adding-support-for-a-new-guest-os) - * [Create template files](#create-template-files) - * [Modify template files](#modify-template-files) - * [Expected rootfs directory content](#expected-rootfs-directory-content) - * [Optional - Customise the rootfs](#optional---customise-the-rootfs) +* [Building a Guest OS rootfs for Kata Containers](#building-a-guest-os-rootfs-for-kata-containers) + * [Supported base OSs](#supported-base-oss) + * [Extra features](#extra-features) + * [Supported distributions list](#supported-distributions-list) + * [Generate Kata specific files](#generate-kata-specific-files) + * [Rootfs requirements](#rootfs-requirements) + * [Creating a rootfs](#creating-a-rootfs) + * [Creating a rootfs with kernel modules](#creating-a-rootfs-with-kernel-modules) + * [Build a rootfs using Docker](#build-a-rootfs-using-docker) + * [Adding support for a new guest OS](#adding-support-for-a-new-guest-os) + * [Create template files](#create-template-files) + * [Modify template files](#modify-template-files) + * [Expected rootfs directory content](#expected-rootfs-directory-content) + * [Optional - Customise the rootfs](#optional---customise-the-rootfs) * [Adding extra packages](#adding-extra-packages) * [Arbitrary rootfs changes](#arbitrary-rootfs-changes) @@ -21,10 +25,25 @@ The `rootfs.sh` script builds a rootfs based on a particular Linux\* distribution. The script supports multiple distributions and can be extended to add further ones. -To list the supported distributions, run: - +### Extra features +#### Supported distributions list +Supported distributions can be listed with: ``` -$ ./rootfs.sh -h +$ ./rootfs.sh -l +``` + +#### Generate Kata specific files +`rootfs.sh` can be used to only populate a target directory with the set of Kata +specific files and components integrable into a generic Linux rootfs to generate +a Kata guest OS image. +This feature can be used when creating a rootfs with a distribution not officially +supported by osbuilder. +It is also used when building the rootfs using the 'dracut' build method. + +To obtain this, simply invoke `rootfs.sh` without specifying a target rootfs, e.g.: +``` +mkdir kata-overlay +./rootfs.sh -r `pwd`/kata-overlay ``` ## Rootfs requirements diff --git a/rootfs-builder/rootfs.sh b/rootfs-builder/rootfs.sh index 6f5d68450..7705e0d60 100755 --- a/rootfs-builder/rootfs.sh +++ b/rootfs-builder/rootfs.sh @@ -52,18 +52,31 @@ typeset -r CONFIG_ARCH_SH="config_${ARCH}.sh" # build_rootfs() function. typeset -r LIB_SH="rootfs_lib.sh" +# rootfs distro name specified by the user +typeset distro= + +# Absolute path to the rootfs root folder +typeset ROOTFS_DIR + +# Absolute path in the rootfs to the "init" executable / symlink. +# Typically something like "${ROOTFS_DIR}/init +typeset init= + #$1: Error code if want to exit different to 0 usage() { error="${1:-0}" cat < +Usage: ${script_name} [options] [DISTRO] -Build a rootfs based on OS, to be included in a Kata Containers -image. +Build and setup a rootfs directory based on DISTRO OS, used to create +Kata Containers images or initramfs. -Supported values: +When no DISTRO is provided, an existing base rootfs at ROOTFS_DIR is provisioned +with the Kata specific components and configuration. + +Supported DISTRO values: $(get_distros | tr "\n" " ") Options: @@ -75,7 +88,7 @@ Options: yaml description. -r Specify the rootfs base directory. Overrides the ROOTFS_DIR environment variable. - -t Print the test configuration for and exit + -t DISTRO Print the test configuration for DISTRO and exit immediately. Environment Variables: @@ -100,7 +113,7 @@ DISTRO_REPO Use host repositories to install guest packages. GO_AGENT_PKG URL of the Git repository hosting the agent package. Default value: ${GO_AGENT_PKG} -GRACEFUL_EXIT If set, and if the configuration specifies a +GRACEFUL_EXIT If set, and if the DISTRO configuration specifies a non-empty BUILD_CAN_FAIL variable, do not return with an error code in case any of the build step fails. This is used when running CI jobs, to tolerate failures for @@ -112,7 +125,7 @@ KERNEL_MODULES_DIR Path to a directory containing kernel modules to include in Default value: ROOTFS_DIR Path to the directory that is populated with the rootfs. - Default value: <${script_name} path>/rootfs- + Default value: <${script_name} path>/rootfs- USE_DOCKER If set, build the rootfs inside a container (requires Docker). @@ -137,7 +150,9 @@ get_distros() { } get_test_config() { - local distro="$1" + local -r distro="$1" + [ -z "$distro" ] && die "No distro name specified" + local config="${script_dir}/${distro}/config.sh" source ${config} @@ -330,229 +345,264 @@ compare_versions() true } -while getopts a:hlo:r:t: opt -do - case $opt in - a) AGENT_VERSION="${OPTARG}" ;; - h) usage ;; - l) get_distros | sort && exit 0;; - o) OSBUILDER_VERSION="${OPTARG}" ;; - r) ROOTFS_DIR="${OPTARG}" ;; - t) get_test_config "${OPTARG}" && exit 0;; - esac -done +check_env_variables() +{ + # Fetch the first element from GOPATH as working directory + # as go get only works against the first item in the GOPATH + [ -z "$GOPATH" ] && die "GOPATH not set" + GOPATH_LOCAL="${GOPATH%%:*}" -shift $(($OPTIND - 1)) + [ "$AGENT_INIT" == "yes" -o "$AGENT_INIT" == "no" ] || die "AGENT_INIT($AGENT_INIT) is invalid (must be yes or no)" -# Fetch the first element from GOPATH as working directory -# as go get only works against the first item in the GOPATH -[ -z "$GOPATH" ] && die "GOPATH not set" -GOPATH_LOCAL="${GOPATH%%:*}" + [ -n "${KERNEL_MODULES_DIR}" ] && [ ! -d "${KERNEL_MODULES_DIR}" ] && die "KERNEL_MODULES_DIR defined but is not an existing directory" -[ "$AGENT_INIT" == "yes" -o "$AGENT_INIT" == "no" ] || die "AGENT_INIT($AGENT_INIT) is invalid (must be yes or no)" + [ -n "${OSBUILDER_VERSION}" ] || die "need osbuilder version" +} -[ -n "${KERNEL_MODULES_DIR}" ] && [ ! -d "${KERNEL_MODULES_DIR}" ] && die "KERNEL_MODULES_DIR defined but is not an existing directory" +# Builds a rootfs based on the distro name provided as argument +build_rootfs_distro() +{ + [ -n "${distro}" ] || usage 1 + distro_config_dir="${script_dir}/${distro}" -[ -z "${OSBUILDER_VERSION}" ] && die "need osbuilder version" + # Source config.sh from distro + rootfs_config="${distro_config_dir}/${CONFIG_SH}" + source "${rootfs_config}" -distro="$1" + # Source arch-specific config file + rootfs_arch_config="${distro_config_dir}/${CONFIG_ARCH_SH}" + if [ -f "${rootfs_arch_config}" ]; then + source "${rootfs_arch_config}" + fi -[ -n "${distro}" ] || usage 1 -distro_config_dir="${script_dir}/${distro}" + [ -d "${distro_config_dir}" ] || die "Not found configuration directory ${distro_config_dir}" -# Source config.sh from distro -rootfs_config="${distro_config_dir}/${CONFIG_SH}" -source "${rootfs_config}" + if [ -z "$ROOTFS_DIR" ]; then + ROOTFS_DIR="${script_dir}/rootfs-${OS_NAME}" + fi -# Source arch-specific config file -rootfs_arch_config="${distro_config_dir}/${CONFIG_ARCH_SH}" -if [ -f "${rootfs_arch_config}" ]; then - source "${rootfs_arch_config}" -fi + if [ -e "${distro_config_dir}/${LIB_SH}" ];then + rootfs_lib="${distro_config_dir}/${LIB_SH}" + info "rootfs_lib.sh file found. Loading content" + source "${rootfs_lib}" + fi -[ -d "${distro_config_dir}" ] || die "Not found configuration directory ${distro_config_dir}" + CONFIG_DIR=${distro_config_dir} + check_function_exist "build_rootfs" -if [ -z "$ROOTFS_DIR" ]; then - ROOTFS_DIR="${script_dir}/rootfs-${OS_NAME}" -fi + if [ -z "$INSIDE_CONTAINER" ] ; then + # Capture errors, but only outside of the docker container + trap error_handler ERR + fi -init="${ROOTFS_DIR}/sbin/init" + mkdir -p ${ROOTFS_DIR} -if [ -e "${distro_config_dir}/${LIB_SH}" ];then - rootfs_lib="${distro_config_dir}/${LIB_SH}" - info "rootfs_lib.sh file found. Loading content" - source "${rootfs_lib}" -fi - -CONFIG_DIR=${distro_config_dir} -check_function_exist "build_rootfs" - -if [ -z "$INSIDE_CONTAINER" ] ; then - # Capture errors, but only outside of the docker container - trap error_handler ERR -fi - -mkdir -p ${ROOTFS_DIR} - -detect_go_version || + detect_go_version || die "Could not detect the required Go version for AGENT_VERSION='${AGENT_VERSION:-master}'." -echo "Required Go version: $GO_VERSION" + echo "Required Go version: $GO_VERSION" -if [ -z "${USE_DOCKER}" ] ; then - #Generate an error if the local Go version is too old - foundVersion=$(go version | sed -E "s/^.+([0-9]+\.[0-9]+\.[0-9]+).*$/\1/g") + if [ -z "${USE_DOCKER}" ] ; then + #Generate an error if the local Go version is too old + foundVersion=$(go version | sed -E "s/^.+([0-9]+\.[0-9]+\.[0-9]+).*$/\1/g") - compare_versions "$GO_VERSION" $foundVersion || \ - die "Your Go version $foundVersion is older than the minimum expected Go version $GO_VERSION" -else - image_name="${distro}-rootfs-osbuilder" - - generate_dockerfile "${distro_config_dir}" - docker build \ - --build-arg http_proxy="${http_proxy}" \ - --build-arg https_proxy="${https_proxy}" \ - -t "${image_name}" "${distro_config_dir}" - - # fake mapping if KERNEL_MODULES_DIR is unset - kernel_mod_dir=${KERNEL_MODULES_DIR:-${ROOTFS_DIR}} - - docker_run_args="" - docker_run_args+=" --rm" - docker_run_args+=" --runtime ${DOCKER_RUNTIME}" - - if [ -z "${AGENT_SOURCE_BIN}" ] ; then - docker_run_args+=" --env GO_AGENT_PKG=${GO_AGENT_PKG}" + compare_versions "$GO_VERSION" $foundVersion || \ + die "Your Go version $foundVersion is older than the minimum expected Go version $GO_VERSION" else - docker_run_args+=" --env AGENT_SOURCE_BIN=${AGENT_SOURCE_BIN}" - docker_run_args+=" -v ${AGENT_SOURCE_BIN}:${AGENT_SOURCE_BIN}" + image_name="${distro}-rootfs-osbuilder" + + generate_dockerfile "${distro_config_dir}" + docker build \ + --build-arg http_proxy="${http_proxy}" \ + --build-arg https_proxy="${https_proxy}" \ + -t "${image_name}" "${distro_config_dir}" + + # fake mapping if KERNEL_MODULES_DIR is unset + kernel_mod_dir=${KERNEL_MODULES_DIR:-${ROOTFS_DIR}} + + docker_run_args="" + docker_run_args+=" --rm" + docker_run_args+=" --runtime ${DOCKER_RUNTIME}" + + if [ -z "${AGENT_SOURCE_BIN}" ] ; then + docker_run_args+=" --env GO_AGENT_PKG=${GO_AGENT_PKG}" + else + docker_run_args+=" --env AGENT_SOURCE_BIN=${AGENT_SOURCE_BIN}" + docker_run_args+=" -v ${AGENT_SOURCE_BIN}:${AGENT_SOURCE_BIN}" + fi + + docker_run_args+=" $(docker_extra_args $distro)" + + # Relabel volumes so SELinux allows access (see docker-run(1)) + if command -v selinuxenabled > /dev/null && selinuxenabled ; then + for volume_dir in "${script_dir}" \ + "${ROOTFS_DIR}" \ + "${script_dir}/../scripts" \ + "${kernel_mod_dir}" \ + "${GOPATH_LOCAL}"; do + chcon -Rt svirt_sandbox_file_t "$volume_dir" + done + fi + + #Make sure we use a compatible runtime to build rootfs + # In case Clear Containers Runtime is installed we dont want to hit issue: + #https://github.com/clearcontainers/runtime/issues/828 + docker run \ + --env https_proxy="${https_proxy}" \ + --env http_proxy="${http_proxy}" \ + --env AGENT_VERSION="${AGENT_VERSION}" \ + --env ROOTFS_DIR="/rootfs" \ + --env AGENT_BIN="${AGENT_BIN}" \ + --env AGENT_INIT="${AGENT_INIT}" \ + --env GOPATH="${GOPATH_LOCAL}" \ + --env KERNEL_MODULES_DIR="${KERNEL_MODULES_DIR}" \ + --env EXTRA_PKGS="${EXTRA_PKGS}" \ + --env OSBUILDER_VERSION="${OSBUILDER_VERSION}" \ + --env INSIDE_CONTAINER=1 \ + --env SECCOMP="${SECCOMP}" \ + --env DEBUG="${DEBUG}" \ + -v "${script_dir}":"/osbuilder" \ + -v "${ROOTFS_DIR}":"/rootfs" \ + -v "${script_dir}/../scripts":"/scripts" \ + -v "${kernel_mod_dir}":"${kernel_mod_dir}" \ + -v "${GOPATH_LOCAL}":"${GOPATH_LOCAL}" \ + $docker_run_args \ + ${image_name} \ + bash /osbuilder/rootfs.sh "${distro}" + + exit $? fi - docker_run_args+=" $(docker_extra_args $distro)" + build_rootfs ${ROOTFS_DIR} +} - # Relabel volumes so SELinux allows access (see docker-run(1)) - if command -v selinuxenabled > /dev/null && selinuxenabled ; then - for volume_dir in "${script_dir}" \ - "${ROOTFS_DIR}" \ - "${script_dir}/../scripts" \ - "${kernel_mod_dir}" \ - "${GOPATH_LOCAL}"; do - chcon -Rt svirt_sandbox_file_t "$volume_dir" - done +# Used to create a minimal directory tree where the agent can be instaleld. +# This is used when a distro is not specified. +prepare_overlay() +{ + pushd "${ROOTFS_DIR}" >> /dev/null + mkdir -p ./etc ./lib/systemd ./sbin ./var + ln -sf ./usr/lib/systemd/systemd ./init + ln -sf ../../init ./lib/systemd/systemd + ln -sf ../init ./sbin/init + popd >> /dev/null +} + +# Setup an existing rootfs directory, based on the OPTIONAL distro name +# provided as argument +setup_rootfs() +{ + [ -z "$distro" ] && prepare_overlay + + info "Create symlink to /tmp in /var to create private temporal directories with systemd" + pushd "${ROOTFS_DIR}" >> /dev/null + if [ "$PWD" != "/" ] ; then + rm -rf ./var/cache/ ./var/lib ./var/log ./var/tmp + fi + ln -s ../tmp ./var/ + + # For some distros tmp.mount may not be installed by default in systemd paths + if ! [ -f "./etc/systemd/system/tmp.mount" ] && \ + ! [ -f "./usr/lib/systemd/system/tmp.mount" ] && + [ "$AGENT_INIT" != "yes" ]; then + info "Install tmp.mount in ./etc/systemd/system" + cp ./usr/share/systemd/tmp.mount ./etc/systemd/system/tmp.mount fi - #Make sure we use a compatible runtime to build rootfs - # In case Clear Containers Runtime is installed we dont want to hit issue: - #https://github.com/clearcontainers/runtime/issues/828 - docker run \ - --env https_proxy="${https_proxy}" \ - --env http_proxy="${http_proxy}" \ - --env AGENT_VERSION="${AGENT_VERSION}" \ - --env ROOTFS_DIR="/rootfs" \ - --env AGENT_BIN="${AGENT_BIN}" \ - --env AGENT_INIT="${AGENT_INIT}" \ - --env GOPATH="${GOPATH_LOCAL}" \ - --env KERNEL_MODULES_DIR="${KERNEL_MODULES_DIR}" \ - --env EXTRA_PKGS="${EXTRA_PKGS}" \ - --env OSBUILDER_VERSION="${OSBUILDER_VERSION}" \ - --env INSIDE_CONTAINER=1 \ - --env SECCOMP="${SECCOMP}" \ - --env DEBUG="${DEBUG}" \ - -v "${script_dir}":"/osbuilder" \ - -v "${ROOTFS_DIR}":"/rootfs" \ - -v "${script_dir}/../scripts":"/scripts" \ - -v "${kernel_mod_dir}":"${kernel_mod_dir}" \ - -v "${GOPATH_LOCAL}":"${GOPATH_LOCAL}" \ - $docker_run_args \ - ${image_name} \ - bash /osbuilder/rootfs.sh "${distro}" + popd >> /dev/null - exit $? -fi + [ -n "${KERNEL_MODULES_DIR}" ] && copy_kernel_modules ${KERNEL_MODULES_DIR} ${ROOTFS_DIR} -build_rootfs ${ROOTFS_DIR} -pushd "${ROOTFS_DIR}" >> /dev/null -if [ "$PWD" != "/" ] ; then - rm -rf ./var/cache/ ./var/lib ./var/log -fi + chrony_conf_file="${ROOTFS_DIR}/etc/chrony.conf" + if [ "${distro}" == "ubuntu" ] || [ "${distro}" == "debian" ] ; then + chrony_conf_file="${ROOTFS_DIR}/etc/chrony/chrony.conf" + fi -info "Create symlink to /tmp in /var to create private temporal directories with systemd" -rm -rf ./var/tmp -ln -s ../tmp ./var/ + info "Create ${ROOTFS_DIR}/etc" + mkdir -p "${ROOTFS_DIR}/etc" -# For some distros tmp.mount may not be installed by default in systemd paths -if ! [ -f "./etc/systemd/system/tmp.mount" ] && \ - ! [ -f "./usr/lib/systemd/system/tmp.mount" ] && - [ "$AGENT_INIT" != "yes" ]; then - info "Install tmp.mount in ./etc/systemd/system" - cp ./usr/share/systemd/tmp.mount ./etc/systemd/system/tmp.mount -fi - -popd >> /dev/null - -[ -n "${KERNEL_MODULES_DIR}" ] && copy_kernel_modules ${KERNEL_MODULES_DIR} ${ROOTFS_DIR} - -chrony_conf_file="${ROOTFS_DIR}/etc/chrony.conf" -if [ ${distro} == ubuntu ] || [ ${distro} == debian ] ; then - chrony_conf_file="${ROOTFS_DIR}/etc/chrony/chrony.conf" -fi - -info "Create ${ROOTFS_DIR}/etc" -mkdir -p "${ROOTFS_DIR}/etc" - -info "Configure chrony file ${chrony_conf_file}" -cat >> "${chrony_conf_file}" <> "${chrony_conf_file}" <