Skip to content

Create installation VM and run bootc install inside a VM using rootless podman #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
binary_name = podman-bootc
binary_proxy= vsock-proxy
output_dir = bin
build_tags = exclude_graphdriver_btrfs,btrfs_noversion,exclude_graphdriver_devicemapper,containers_image_openpgp,remote

registry = quay.io/containers
vm_image_name = bootc-vm
vm_image_tag = latest
vm_image = $(registry)/$(vm_image_name):$(vm_image_tag)

all: out_dir docs
go build -tags $(build_tags) $(GOOPTS) -o $(output_dir)/$(binary_name)

.PHONY: proxy
proxy: out_dir
go build -o ${output_dir}/$(binary_proxy) ./proxy

out_dir:
mkdir -p $(output_dir)

Expand All @@ -18,6 +28,10 @@ integration_tests:
e2e_test: all
ginkgo -tags $(build_tags) ./test/...

image: proxy
podman build -t $(vm_image) --device /dev/kvm \
-f containerfiles/vm/Containerfile .

.PHONY: docs
docs:
make -C docs
Expand Down
178 changes: 178 additions & 0 deletions cmd/install.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package cmd

import (
"context"
"fmt"
"os"
filepath "path/filepath"

"github.com/containers/podman-bootc/pkg/podman"
"github.com/containers/podman-bootc/pkg/vm"
"github.com/containers/podman-bootc/pkg/vm/domain"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/spf13/cobra"
log "github.com/sirupsen/logrus"
)

type installCmd struct {
image string
bootcCmdLine []string
artifactsDir string
diskPath string
ctx context.Context
socket string
podmanSocketDir string
libvirtDir string
outputImage string
containerStorage string
configPath string
outputPath string
installVM *vm.InstallVM
}

func filterCmdlineArgs(args []string) ([]string, error) {
sepIndex := -1
for i, arg := range args {
if arg == "--" {
sepIndex = i
break
}
}
if sepIndex == -1 {
return nil, fmt.Errorf("no command line specified")
}

return args[sepIndex+1:], nil
}

func NewInstallCommand() *cobra.Command {
c := installCmd{}
cmd := &cobra.Command{
Use: "install",
Short: "Install the OS Containers",
Long: "Run bootc install to build the OS Containers. Specify the bootc cmdline after the '--'",
RunE: c.doInstall,
}
cacheDir, err := os.UserCacheDir()
if err != nil {
cacheDir = ""
}
cacheDir = filepath.Join(cacheDir, "bootc")
cmd.PersistentFlags().StringVar(&c.image, "bootc-image", "", "bootc-vm container image")
cmd.PersistentFlags().StringVar(&c.artifactsDir, "dir", cacheDir, "directory where the artifacts are extracted")
cmd.PersistentFlags().StringVar(&c.outputPath, "output-dir", "", "directory to store the output results")
cmd.PersistentFlags().StringVar(&c.outputImage, "output-image", "", "path of the image to use for the installation")
cmd.PersistentFlags().StringVar(&c.configPath, "config-dir", "", "path where to find the config.toml")
cmd.PersistentFlags().StringVar(&c.containerStorage, "container-storage", podman.DefaultContainerStorage(), "Container storage to use")
cmd.PersistentFlags().StringVar(&c.socket, "podman-socket", podman.DefaultPodmanSocket(), "path to the podman socket")
if args, err := filterCmdlineArgs(os.Args); err == nil {
c.bootcCmdLine = args
}

return cmd
}

func init() {
RootCmd.AddCommand(NewInstallCommand())
}

func (c *installCmd) validateArgs() error {
if c.image == "" {
return fmt.Errorf("the bootc-image cannot be empty")
}
if c.artifactsDir == "" {
return fmt.Errorf("the artifacts directory path cannot be empty")
}
if c.outputImage == "" {
return fmt.Errorf("the output-image needs to be set")
}
if c.outputPath == "" {
return fmt.Errorf("the output-path needs to be set")
}
if c.configPath == "" {
return fmt.Errorf("the config-dir needs to be set")
}
if c.containerStorage == "" {
return fmt.Errorf("the container storage cannot be empty")
}
if c.socket == "" {
return fmt.Errorf("the socket for podman cannot be empty")
}
if len(c.bootcCmdLine) == 0 {
return fmt.Errorf("the bootc commandline needs to be specified after the '--'")
}
var err error
c.ctx, err = bindings.NewConnection(context.Background(), "unix://"+c.socket)
if err != nil {
return fmt.Errorf("failed to connect to podman at %s: %v", c.socket, err)
}

return nil
}

func (c *installCmd) installBuildVM(kernel, initrd string) error {
image := filepath.Join(c.outputPath, c.outputImage)
outputImageFormat, err := domain.GetDiskInfo(image)
if err != nil {
return err
}
c.installVM = vm.NewInstallVM(filepath.Join(c.libvirtDir, "virtqemud-sock"), vm.InstallOptions{
OutputFormat: outputImageFormat,
OutputImage: filepath.Join(vm.OutputDir, c.outputImage), // Path relative to the container filesystem
Root: false,
Kernel: kernel,
Initrd: initrd,
})
if err := c.installVM.Run(); err != nil {
return err
}

return nil
}

func (c *installCmd) doInstall(_ *cobra.Command, _ []string) error {
if err := c.validateArgs(); err != nil {
return err
}
c.libvirtDir = filepath.Join(c.artifactsDir, "libvirt")
if _, err := os.Stat(c.libvirtDir); os.IsNotExist(err) {
if err := os.Mkdir(c.libvirtDir, 0755); err != nil {
return err
}
}
c.podmanSocketDir = filepath.Join(c.artifactsDir, "podman")
if _, err := os.Stat(c.podmanSocketDir); os.IsNotExist(err) {
if err := os.Mkdir(c.podmanSocketDir, 0755); err != nil {
return err
}
}
remoteSocket := filepath.Join(c.podmanSocketDir, "podman-vm.sock")
vmCont := podman.NewVMContainer(c.image, c.socket, &podman.RunVMContainerOptions{
ContainerStoragePath: c.containerStorage,
ConfigDir: c.configPath,
OutputDir: c.outputPath,
SocketDir: c.podmanSocketDir,
LibvirtSocketDir: c.libvirtDir,
})
if err := vmCont.Run(); err != nil {
return err
}
defer vmCont.Stop()

kernel, initrd, err := vmCont.GetBootArtifacts()
if err != nil {
return err
}
log.Debugf("Boot artifacts kernel: %s and initrd: %s", kernel, initrd)

if err := c.installBuildVM(kernel, initrd); err != nil {
return err
}
defer c.installVM.Stop()

if err := podman.RunPodmanCmd(remoteSocket, c.image, c.bootcCmdLine); err != nil {
return err
}

return nil
}
28 changes: 28 additions & 0 deletions containerfiles/vm/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM quay.io/fedora/fedora:42

RUN dnf install -y \
libvirt-client \
libvirt-daemon \
libvirt-daemon-driver-qemu \
libvirt-daemon-driver-storage-core \
qemu-kvm \
socat \
virt-install \
virtiofsd \
&& dnf clean all

RUN mkdir -p /home/qemu && chown -R qemu:qemu /home/qemu
RUN mkdir -p /etc/libvirt /vm_files

COPY containerfiles/vm/entrypoint.sh /entrypoint.sh
COPY ./bin/vsock-proxy /usr/local/bin/vsock-proxy
COPY containerfiles/vm/files /vm_files
COPY containerfiles/vm/qemu.conf /etc/libvirt/qemu.conf
COPY containerfiles/vm/virtqemud.conf /etc/libvirt/virtqemud.conf
COPY containerfiles/vm/virtiofsd-wrapper /usr/local/bin/virtiofsd-wrapper

EXPOSE 5959

RUN dnf install -y socat

ENTRYPOINT ["/entrypoint.sh"]
34 changes: 34 additions & 0 deletions containerfiles/vm/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/bash

set -xe

BOOTC_ROOT=/bootc-data

# Inject the binaries, systemd and configuration files in the bootc image
mkdir -p ${BOOTC_ROOT}/etc/sysusers.d
mkdir -p ${BOOTC_ROOT}/usr/lib/containers/storage
cp /vm_files/bootc.conf ${BOOTC_ROOT}/etc/sysusers.d/bootc.conf
cp /vm_files/podman-vsock-proxy.service ${BOOTC_ROOT}/etc/systemd/system/podman-vsock-proxy.service
cp /vm_files/mount-vfsd-targets.service ${BOOTC_ROOT}/etc/systemd/system/mount-vfsd-targets.service
cp /vm_files/mount-vfsd-targets.sh ${BOOTC_ROOT}/usr/local/bin/mount-vfsd-targets.sh
cp /vm_files/container-storage.conf ${BOOTC_ROOT}/etc/containers/storage.conf
cp /vm_files/selinux-config ${BOOTC_ROOT}/etc/selinux/config
cp /vm_files/sudoers-bootc ${BOOTC_ROOT}/etc/sudoers.d/bootc
cp /usr/local/bin/vsock-proxy ${BOOTC_ROOT}/usr/local/bin/vsock-proxy

# Enable systemd services
chroot ${BOOTC_ROOT} systemctl enable mount-vfsd-targets
chroot ${BOOTC_ROOT} systemctl enable podman.socket
chroot ${BOOTC_ROOT} systemctl enable podman-vsock-proxy.service
# Create an empty password for the bootc user
entry='bootc::20266::::::'
echo $entry >> ${BOOTC_ROOT}/etc/shadow

# Start proxy the VM port 1234 to unix socket
vsock-proxy --log-level debug -s /run/podman/podman-vm.sock -p 1234 --cid 3 \
--listen-mode unixToVsock &> /var/log/vsock-proxy.log &

# Finally, start libvirt
/usr/sbin/virtlogd &
/usr/bin/virtstoraged &
/usr/sbin/virtqemud -v -t 0
1 change: 1 addition & 0 deletions containerfiles/vm/files/bootc.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
u bootc - "Bootc User" /home/bootc /bin/bash
14 changes: 14 additions & 0 deletions containerfiles/vm/files/container-storage.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[storage]

driver = "overlay"
runroot = "/run/containers/storage"
graphroot = "/var/lib/containers/storage"

[storage.options]
additionalimagestores = [
"/usr/lib/containers/storage",
"/usr/lib/bootc/container_storage",
]
pull_options = {enable_partial_images = "true", use_hard_links = "false", ostree_repos=""}
[storage.options.overlay]
mountopt = "nodev,metacopy=on"
12 changes: 12 additions & 0 deletions containerfiles/vm/files/mount-vfsd-targets.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Unit]
Description=Mount all virtiofs targets
After=local-fs.target
ConditionPathExists=/sys/fs/virtiofs

[Service]
Type=oneshot
ExecStart=/usr/local/bin/mount-vfsd-targets.sh
RemainAfterExit=true

[Install]
WantedBy=multi-user.target
9 changes: 9 additions & 0 deletions containerfiles/vm/files/mount-vfsd-targets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

set -xe
mkdir -p /usr/lib/bootc/config
mkdir -p /usr/lib/bootc/container_storage
mkdir -p /usr/lib/bootc/output
mount -t virtiofs config /usr/lib/bootc/config
mount -t virtiofs storage /usr/lib/bootc/container_storage
mount -t virtiofs output /usr/lib/bootc/output
15 changes: 15 additions & 0 deletions containerfiles/vm/files/podman-vsock-proxy.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=Proxy vsock (PORT: 1234) to Unix podman socket
After=network.target
Requires=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/vsock-proxy --log-level debug --cid 3 --port 1234 \
--socket /var/run/podman/podman.sock --listen-mode vsockToUnix
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

1 change: 1 addition & 0 deletions containerfiles/vm/files/selinux-config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SELINUX=disabled
1 change: 1 addition & 0 deletions containerfiles/vm/files/sudoers-bootc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bootc ALL=(ALL) NOPASSWD: ALL
10 changes: 10 additions & 0 deletions containerfiles/vm/qemu.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
stdio_handler = "logd"
vnc_listen = "0.0.0.0"
vnc_tls = 0
vnc_sasl = 0
user = "qemu"
group = "qemu"
dynamic_ownership = 1
remember_owner = 0
namespaces = [ ]
cgroup_controllers = [ ]
6 changes: 6 additions & 0 deletions containerfiles/vm/virtiofsd-wrapper
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
exec /usr/libexec/virtiofsd \
--sandbox=none \
--cache=auto --modcaps=-mknod \
--log-level debug \
"$@"
3 changes: 3 additions & 0 deletions containerfiles/vm/virtqemud.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
listen_tls = 0
listen_tcp = 0
log_outputs = "1:stderr"
Loading