From 801a23e64f84c8eca76ed93328f7e90b81959f7f Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Thu, 22 Apr 2021 14:03:04 -0700 Subject: 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. --- cmd/c2vm/main.go | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) (limited to 'cmd') diff --git a/cmd/c2vm/main.go b/cmd/c2vm/main.go index 5e9fbb0..c416e47 100644 --- a/cmd/c2vm/main.go +++ b/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 } -- cgit 1.4.1