De-dup container root filesystems

With the mount framework we can de-dup containers that share the same image.

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack 2017-08-22 17:27:08 +01:00
parent 6b98aff58b
commit cfa5d273b7
2 changed files with 39 additions and 13 deletions

View File

@ -120,7 +120,7 @@ func enforceContentTrust(fullImageName string, config *TrustConfig) bool {
return false return false
} }
func outputImage(image Image, section string, prefix string, m Moby, idMap map[string]uint32, pull bool, iw *tar.Writer) error { func outputImage(image Image, section string, prefix string, m Moby, idMap map[string]uint32, dupMap map[string]string, pull bool, iw *tar.Writer) error {
log.Infof(" Create OCI config for %s", image.Image) log.Infof(" Create OCI config for %s", image.Image)
useTrust := enforceContentTrust(image.Image, &m.Trust) useTrust := enforceContentTrust(image.Image, &m.Trust)
oci, runtime, err := ConfigToOCI(image, useTrust, idMap) oci, runtime, err := ConfigToOCI(image, useTrust, idMap)
@ -133,7 +133,7 @@ func outputImage(image Image, section string, prefix string, m Moby, idMap map[s
} }
path := path.Join("containers", section, prefix+image.Name) path := path.Join("containers", section, prefix+image.Name)
readonly := oci.Root.Readonly readonly := oci.Root.Readonly
err = ImageBundle(path, image.Image, config, runtime, iw, useTrust, pull, readonly) err = ImageBundle(path, image.Image, config, runtime, iw, useTrust, pull, readonly, dupMap)
if err != nil { if err != nil {
return fmt.Errorf("Failed to extract root filesystem for %s: %v", image.Image, err) return fmt.Errorf("Failed to extract root filesystem for %s: %v", image.Image, err)
} }
@ -167,6 +167,9 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
id++ id++
} }
// deduplicate containers with the same image
dupMap := map[string]string{}
if m.Kernel.Image != "" { if m.Kernel.Image != "" {
// get kernel and initrd tarball from container // get kernel and initrd tarball from container
log.Infof("Extract kernel image: %s", m.Kernel.Image) log.Infof("Extract kernel image: %s", m.Kernel.Image)
@ -198,7 +201,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
} }
for i, image := range m.Onboot { for i, image := range m.Onboot {
so := fmt.Sprintf("%03d", i) so := fmt.Sprintf("%03d", i)
if err := outputImage(image, "onboot", so+"-", m, idMap, pull, iw); err != nil { if err := outputImage(image, "onboot", so+"-", m, idMap, dupMap, pull, iw); err != nil {
return err return err
} }
} }
@ -208,7 +211,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
} }
for i, image := range m.Onshutdown { for i, image := range m.Onshutdown {
so := fmt.Sprintf("%03d", i) so := fmt.Sprintf("%03d", i)
if err := outputImage(image, "onshutdown", so+"-", m, idMap, pull, iw); err != nil { if err := outputImage(image, "onshutdown", so+"-", m, idMap, dupMap, pull, iw); err != nil {
return err return err
} }
} }
@ -217,7 +220,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
log.Infof("Add service containers:") log.Infof("Add service containers:")
} }
for _, image := range m.Services { for _, image := range m.Services {
if err := outputImage(image, "services", "", m, idMap, pull, iw); err != nil { if err := outputImage(image, "services", "", m, idMap, dupMap, pull, iw); err != nil {
return err return err
} }
} }

View File

@ -191,16 +191,28 @@ func ImageTar(image, prefix string, tw tarWriter, trust bool, pull bool, resolv
} }
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json // ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
func ImageBundle(prefix string, image string, config []byte, runtime Runtime, tw tarWriter, trust bool, pull bool, readonly bool) error { func ImageBundle(prefix string, image string, config []byte, runtime Runtime, tw tarWriter, trust bool, pull bool, readonly bool, dupMap map[string]string) error {
// if read only, just unpack in rootfs/ but otherwise set up for overlay // if read only, just unpack in rootfs/ but otherwise set up for overlay
rootfs := "rootfs" rootExtract := "rootfs"
if !readonly { if !readonly {
rootfs = "lower" rootExtract = "lower"
} }
if err := ImageTar(image, path.Join(prefix, rootfs)+"/", tw, trust, pull, ""); err != nil { // See if we have extracted this image previously
root := path.Join(prefix, rootExtract)
var foundElsewhere = dupMap[image] != ""
if !foundElsewhere {
if err := ImageTar(image, root+"/", tw, trust, pull, ""); err != nil {
return err return err
} }
dupMap[image] = root
} else {
if err := tarPrefix(prefix+"/", tw); err != nil {
return err
}
root = dupMap[image]
}
hdr := &tar.Header{ hdr := &tar.Header{
Name: path.Join(prefix, "config.json"), Name: path.Join(prefix, "config.json"),
Mode: 0644, Mode: 0644,
@ -237,11 +249,22 @@ func ImageBundle(prefix string, image string, config []byte, runtime Runtime, tw
runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "tmpfs", Type: "tmpfs", Destination: "/" + tmp}) runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "tmpfs", Type: "tmpfs", Destination: "/" + tmp})
// remount private as nothing else should see the temporary layers // remount private as nothing else should see the temporary layers
runtime.Mounts = append(runtime.Mounts, specs.Mount{Destination: "/" + tmp, Options: []string{"remount", "private"}}) runtime.Mounts = append(runtime.Mounts, specs.Mount{Destination: "/" + tmp, Options: []string{"remount", "private"}})
overlayOptions := []string{"lowerdir=/" + path.Join(prefix, "lower"), "upperdir=/" + path.Join(tmp, "upper"), "workdir=/" + path.Join(tmp, "work")} overlayOptions := []string{"lowerdir=/" + root, "upperdir=/" + path.Join(tmp, "upper"), "workdir=/" + path.Join(tmp, "work")}
runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "overlay", Type: "overlay", Destination: "/" + path.Join(prefix, "rootfs"), Options: overlayOptions}) runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "overlay", Type: "overlay", Destination: "/" + path.Join(prefix, "rootfs"), Options: overlayOptions})
} else { } else {
// make rootfs a mountpoint as runc can be picky about this if foundElsewhere {
runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: path.Join(prefix, rootfs), Destination: "/" + path.Join(prefix, "rootfs"), Options: []string{"bind"}}) // we need to make the mountpoint at rootfs
hdr = &tar.Header{
Name: path.Join(prefix, "rootfs"),
Mode: 0755,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
}
// either bind from another location, or bind from self to make sure it is a mountpoint as runc prefers this
runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "/" + root, Destination: "/" + path.Join(prefix, "rootfs"), Options: []string{"bind"}})
} }
// write the runtime config // write the runtime config