From 807483fe45091b2fe19968c841adc035d969be68 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 26 Jan 2024 15:21:11 -0500 Subject: [PATCH] install: Optionally use host mounted `/var/lib/containers` I just keep hitting the host skopeo requirement in corner cases; it's annoying because *otherwise* the container is self-sufficient. Change our installation instructions to add a `/var/lib/containers` bind mount. For the time being of course we continue to support forking off `skopeo` on the host. One thing I still want to investigate is dropping some requirements here and switch to *dynamically* setting up the mount points inside the container as is mentioned in https://brauner.io/2023/02/28/mounting-into-mount-namespaces.html but this currently requires relatively new host kernels. As far as test coverage, this changes the Github Action that uses ubuntu and needed to build a newer skopeo to stop doing that, and in fact we explicitly *remove* skopeo to verify it's not being used in the install process. I didn't change the other install tests to verify they keep working. Closes: https://github.com/containers/bootc/issues/81 Signed-off-by: Colin Walters --- .github/workflows/ci.yml | 34 ++++------------------------------ docs/install.md | 15 +++++++++------ lib/src/install.rs | 39 +++++++++++++++++++++++++++++++++------ lib/src/podman.rs | 4 ++++ 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e2ebc0bb..c39913f16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,39 +116,13 @@ jobs: run: sudo tar -C / -xvf bootc.tar.zst - name: Integration tests run: bootc internal-tests run-container-integration - build-skopeo-ubuntu: - name: "Build skopeo git main for ubuntu" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - repository: containers/skopeo - path: skopeo - - name: Install build deps - run: | - sudo sed -ie s,'# deb-src,deb-src,' /etc/apt/sources.list - sudo apt update - sudo apt build-dep -y skopeo - - uses: actions/setup-go@v4 - with: - go-version: '>=1.20' - - name: Build skopeo - run: cd skopeo && make bin/skopeo PREFIX=/usr - - name: Upload binary - uses: actions/upload-artifact@v4 - with: - name: skopeo-ubuntu - path: skopeo/bin/skopeo privtest-alongside: name: "Test install-alongside" - needs: [build-fedora, build-skopeo-ubuntu] + needs: [build-fedora] runs-on: ubuntu-latest steps: - - name: Download - uses: actions/download-artifact@v4 - with: - name: skopeo-ubuntu - - run: chmod a+x skopeo && sudo mv skopeo /usr/bin + - name: Ensure host skopeo is disabled + run: sudo rm -f /bin/skopeo /usr/bin/skopeo - name: Download uses: actions/download-artifact@v3 with: @@ -158,7 +132,7 @@ jobs: - name: Integration tests run: | set -xeuo pipefail - sudo podman run --rm -ti --privileged -v /:/target -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \ + sudo podman run --rm -ti --privileged --env RUST_LOG=debug -v /:/target -v /var/lib/containers:/var/lib/containers -v ./usr/bin/bootc:/usr/bin/bootc --pid=host --security-opt label=disable \ quay.io/centos-bootc/fedora-bootc-dev:eln bootc install to-filesystem \ --karg=foo=bar --disable-selinux --replace=alongside /target ls -al /boot/loader/ diff --git a/docs/install.md b/docs/install.md index ef347f189..1e710bc9e 100644 --- a/docs/install.md +++ b/docs/install.md @@ -27,9 +27,8 @@ inside the container. There are two sub-commands: `bootc install to-disk` and `boot install to-filesystem`. However, nothing *else* (external) is required to perform a basic installation -to disk. (The one exception to host requirements today is that the host must -have `skopeo` installed. This is a bug; more information in -[this issue](https://github.com/containers/bootc/issues/81).) +to disk - the container image itself comes with a baseline self-sufficient installer +that sets things up ready to boot. This is motivated by experience gained from the Fedora CoreOS project where today the expectation is that one boots from a pre-existing disk @@ -58,7 +57,7 @@ to an existing system and install your container image. Failure to run Here's an example of using `bootc install` (root/elevated permission required): ```bash -podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t bootc install to-disk /path/to/disk +podman run --rm --privileged --pid=host -v /var/lib/containers:/var/lib/containers --security-opt label=type:unconfined_t bootc install to-disk /path/to/disk ``` Note that while `--privileged` is used, this command will not perform any @@ -70,6 +69,10 @@ The `--pid=host --security-opt label=type:unconfined_t` today make it more convenient for bootc to perform some privileged operations; in the future these requirement may be dropped. +The `-v /var/lib/containers:/var/lib/containers` option is required in order +for the container to access its own underlying image, which is used by +the installation process. + Jump to the section for [`install to-filesystem`](#more-advanced-installation) later in this document for additional information about that method. @@ -219,7 +222,7 @@ via e.g.: ```bash truncate -s 10G exampleos.raw -podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t -v .:/output bootc install to-disk --generic-image --via-loopback /output/myimage.raw +podman run --rm --privileged --pid=host --security-opt label=type:unconfined_t -v /var/lib/containers:/var/lib/containers -v .:/output bootc install to-disk --generic-image --via-loopback /output/myimage.raw ``` Notice that we use `--generic-image` for this use case. @@ -237,7 +240,7 @@ support the root storage setup already initialized. The core command should look like this (root/elevated permission required): ```bash -podman run --rm --privileged -v /:/target \ +podman run --rm --privileged -v /var/lib/containers:/var/lib/containers -v /:/target \ --pid=host --security-opt label=type:unconfined_t \ \ bootc install to-filesystem --replace=alongside /target diff --git a/lib/src/install.rs b/lib/src/install.rs index fa97452c4..54abad561 100644 --- a/lib/src/install.rs +++ b/lib/src/install.rs @@ -245,6 +245,8 @@ pub(crate) struct SourceInfo { pub(crate) selinux: bool, /// Whether the source is available in the host mount namespace pub(crate) in_host_mountns: bool, + /// Whether we were invoked with -v /var/lib/containers:/var/lib/containers + pub(crate) have_host_container_storage: bool, } // Shared read-only global state @@ -387,16 +389,33 @@ impl SourceInfo { }; let digest = crate::podman::imageid_to_digest(&container_info.imageid)?; + let root = Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + let have_host_container_storage = Utf8Path::new(crate::podman::CONTAINER_STORAGE) + .try_exists()? + && ostree_ext::mountutil::is_mountpoint( + &root, + crate::podman::CONTAINER_STORAGE.trim_start_matches('/'), + )? + .unwrap_or_default(); + // Verify up front we can do the fetch - require_skopeo_with_containers_storage()?; + if have_host_container_storage { + tracing::debug!("Host container storage found"); + } else { + tracing::debug!( + "No {} mount available, checking skopeo", + crate::podman::CONTAINER_STORAGE + ); + require_skopeo_with_containers_storage()?; + } - Self::new(imageref, Some(digest), true) + Self::new(imageref, Some(digest), true, have_host_container_storage) } #[context("Creating source info from a given imageref")] pub(crate) fn from_imageref(imageref: &str) -> Result { let imageref = ostree_container::ImageReference::try_from(imageref)?; - Self::new(imageref, None, false) + Self::new(imageref, None, false, false) } /// Construct a new source information structure @@ -404,6 +423,7 @@ impl SourceInfo { imageref: ostree_container::ImageReference, digest: Option, in_host_mountns: bool, + have_host_container_storage: bool, ) -> Result { let cancellable = ostree::gio::Cancellable::NONE; let commit = Task::new("Reading ostree commit", "ostree") @@ -424,6 +444,7 @@ impl SourceInfo { digest, selinux, in_host_mountns, + have_host_container_storage, }) } } @@ -630,10 +651,16 @@ async fn initialize_ostree_root_from_self( } }; - // We need to fetch the container image from the root mount namespace - let skopeo_cmd = run_in_host_mountns("skopeo"); + // We need to fetch the container image from the root mount namespace. If + // we don't have /var/lib/containers mounted in this image, fork off skopeo + // in the host mountnfs. + let skopeo_cmd = if !state.source.have_host_container_storage { + Some(run_in_host_mountns("skopeo")) + } else { + None + }; let proxy_cfg = ostree_container::store::ImageProxyConfig { - skopeo_cmd: Some(skopeo_cmd), + skopeo_cmd, ..Default::default() }; diff --git a/lib/src/podman.rs b/lib/src/podman.rs index f4cf748c0..f5f7fd966 100644 --- a/lib/src/podman.rs +++ b/lib/src/podman.rs @@ -4,6 +4,10 @@ use serde::Deserialize; use crate::install::run_in_host_mountns; use crate::task::Task; +/// Where we look inside our container to find our own image +/// for use with `bootc install`. +pub(crate) const CONTAINER_STORAGE: &str = "/var/lib/containers"; + #[derive(Deserialize)] #[serde(rename_all = "PascalCase")] pub(crate) struct Inspect {