about summary refs log tree commit diff
path: root/users/fcuny/exp/containerd-to-vm/cmd/c2vm
diff options
context:
space:
mode:
authorFranck Cuny <franck@fcuny.net>2021-04-22 14:03:04 -0700
committerFranck Cuny <franck@fcuny.net>2022-06-11 13:57:41 -0700
commit8686cd5698e42dd1a328f4fed6f77dc103c34275 (patch)
tree10e688f6080ce45ac115c31d3ff9d5f3372078b0 /users/fcuny/exp/containerd-to-vm/cmd/c2vm
parentadd a lease to the Go context (diff)
downloadworld-8686cd5698e42dd1a328f4fed6f77dc103c34275.tar.gz
extract layers to a mounted loop device
We create a loop device by pre-allocating space to a file on disk. This
file is then converted to an ext4 partition, which we mount to a
temporary path on the host.

Once this is done, we extract all the layers from the given container on
that mounted path. We also add a few extra files to the image
(`/etc/hosts` and `/etc/resolv.conf`).

When we're done extracting and writing, we run resize2fs in order to
resize the image to a more reasonable size.
Diffstat (limited to 'users/fcuny/exp/containerd-to-vm/cmd/c2vm')
-rw-r--r--users/fcuny/exp/containerd-to-vm/cmd/c2vm/main.go120
1 files changed, 119 insertions, 1 deletions
diff --git a/users/fcuny/exp/containerd-to-vm/cmd/c2vm/main.go b/users/fcuny/exp/containerd-to-vm/cmd/c2vm/main.go
index 5e9fbb0..c416e47 100644
--- a/users/fcuny/exp/containerd-to-vm/cmd/c2vm/main.go
+++ b/users/fcuny/exp/containerd-to-vm/cmd/c2vm/main.go
@@ -4,11 +4,18 @@ import (
 	"context"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"log"
+	"os/exec"
+	"path/filepath"
 
 	"github.com/containerd/containerd"
+	"github.com/containerd/containerd/content"
+	"github.com/containerd/containerd/images"
 	"github.com/containerd/containerd/namespaces"
 	"github.com/containerd/containerd/platforms"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/google/renameio"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -27,6 +34,7 @@ var (
 func main() {
 	var (
 		containerName = flag.String("container", "", "Name of the container")
+		outFile       = flag.String("out", "container.img", "firecracker output to create")
 	)
 
 	flag.Parse()
@@ -58,5 +66,115 @@ func main() {
 		log.Fatalf("failed to get the size of the image: %v", err)
 	}
 
-	fmt.Printf("pulled %s (%d bytes)\n", image.Name(), imageSize)
+	log.Printf("pulled %s (%d bytes)\n", image.Name(), imageSize)
+
+	mntDir, err := ioutil.TempDir("", "c2vm")
+	if err != nil {
+		log.Fatalf("Failed to create mount temp dir: %v\n", err)
+	}
+
+	if err := createLoopDevice(*outFile, mntDir); err != nil {
+		log.Fatalf("%v\n", err)
+	}
+
+	if err := extract(ctx, client, image, mntDir); err != nil {
+		log.Fatalf("failed to extract the container: %v\n", err)
+	}
+
+	if err = extraFiles(mntDir); err != nil {
+		log.Fatalf("failed to add extra files to the image: %v\n", err)
+	}
+
+	if err := detachLoopDevice(mntDir); err != nil {
+		log.Fatalf("failed to umount %s: %v\n", mntDir, err)
+	}
+
+	if err := resizeImage(*outFile); err != nil {
+		log.Fatalf("failed to resize the image %s: %s\n", *outFile, err)
+	}
+}
+
+func extract(ctx context.Context, client *containerd.Client, image containerd.Image, mntDir string) error {
+	manifest, err := images.Manifest(ctx, client.ContentStore(), image.Target(), platform)
+	if err != nil {
+		log.Fatalf("failed to get the manifest: %v\n", err)
+	}
+
+	for _, desc := range manifest.Layers {
+		log.Printf("extracting layer %s\n", desc.Digest.String())
+		layer, err := client.ContentStore().ReaderAt(ctx, desc)
+		if err != nil {
+			return err
+		}
+		if err := archive.Untar(content.NewReader(layer), mntDir, &archive.TarOptions{NoLchown: true}); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func createLoopDevice(rawFile, mntDir string) error {
+	f, err := renameio.TempFile("", rawFile)
+	if err != nil {
+		return err
+	}
+	defer f.Cleanup()
+
+	command := exec.Command("fallocate", "-l", "2G", f.Name())
+	if err := command.Run(); err != nil {
+		return fmt.Errorf("fallocate error: %s", err)
+	}
+
+	command = exec.Command("mkfs.ext4", "-F", f.Name())
+	if err := command.Run(); err != nil {
+		return fmt.Errorf("mkfs.ext4 error: %s", err)
+	}
+
+	f.CloseAtomicallyReplace()
+
+	command = exec.Command("mount", "-o", "loop", rawFile, mntDir)
+	if err := command.Run(); err != nil {
+		return fmt.Errorf("mount error: %s", err)
+	}
+	log.Printf("mounted %s on %s\n", rawFile, mntDir)
+	return nil
+}
+
+func detachLoopDevice(mntDir string) error {
+	log.Printf("umount %s\n", mntDir)
+	command := exec.Command("umount", mntDir)
+	return command.Run()
+}
+
+func resizeImage(rawFile string) error {
+	// let's bring the image to a more reasonable size. We do this by
+	// first running e2fsck on the image then we can resize the image.
+	command := exec.Command("/usr/bin/e2fsck", "-p", "-f", rawFile)
+	if err := command.Run(); err != nil {
+		return fmt.Errorf("e2fsck error: %s", err)
+	}
+
+	command = exec.Command("resize2fs", "-M", rawFile)
+	if err := command.Run(); err != nil {
+		return fmt.Errorf("resize2fs error: %s", err)
+	}
+	return nil
+}
+
+func extraFiles(mntDir string) error {
+	if err := writeToFile(filepath.Join(mntDir, "etc", "hosts"), "127.0.0.1\tlocalhost\n"); err != nil {
+		return err
+	}
+	if err := writeToFile(filepath.Join(mntDir, "etc", "resolv.conf"), "nameserver 192.168.0.1\n"); err != nil {
+		return err
+	}
+	return nil
+}
+
+func writeToFile(filepath string, content string) error {
+	if err := ioutil.WriteFile(filepath, []byte(content), 0644); err != nil {
+		return fmt.Errorf("writeToFile %s: %v", filepath, err)
+	}
+	return nil
 }