Merge pull request #145 from justincormack/runtime-mount

Add support for mount in runtime config
This commit is contained in:
Justin Cormack 2017-08-23 16:44:13 +01:00 committed by GitHub
commit 69596e17dd
5 changed files with 76 additions and 37 deletions

View File

@ -183,6 +183,8 @@ permissions issues in use.
In addition to the parts of the specification above used to generate the OCI spec, there is a `runtime` section in the image specification
which specifies some actions to take place when the container is being started.
- `mounts` takes a list of mount specifications (`source`, `destination`, `type`, `options`) and mounts them in the root namespace before the container is created. It will
try to make any missing destination directories.
- `mkdir` takes a list of directories to create at runtime, in the root mount namespace. These are created before the container is started, so they can be used to create
directories for bind mounts, for example in `/tmp` or `/run` which would otherwise be empty.
- `interface` defines a list of actions to perform on a network interface:

View File

@ -120,7 +120,7 @@ func enforceContentTrust(fullImageName string, config *TrustConfig) bool {
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)
useTrust := enforceContentTrust(image.Image, &m.Trust)
oci, runtime, err := ConfigToOCI(image, useTrust, idMap)
@ -131,13 +131,9 @@ func outputImage(image Image, section string, prefix string, m Moby, idMap map[s
if err != nil {
return fmt.Errorf("Failed to create config for %s: %v", image.Image, err)
}
runtimeConfig, err := json.MarshalIndent(runtime, "", " ")
if err != nil {
return fmt.Errorf("Failed to create runtime config for %s: %v", image.Image, err)
}
path := path.Join("containers", section, prefix+image.Name)
readonly := oci.Root.Readonly
err = ImageBundle(path, image.Image, config, runtimeConfig, iw, useTrust, pull, readonly)
err = ImageBundle(path, image.Image, config, runtime, iw, useTrust, pull, readonly, dupMap)
if err != nil {
return fmt.Errorf("Failed to extract root filesystem for %s: %v", image.Image, err)
}
@ -171,6 +167,9 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
id++
}
// deduplicate containers with the same image
dupMap := map[string]string{}
if m.Kernel.Image != "" {
// get kernel and initrd tarball from container
log.Infof("Extract kernel image: %s", m.Kernel.Image)
@ -202,7 +201,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
}
for i, image := range m.Onboot {
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
}
}
@ -212,7 +211,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
}
for i, image := range m.Onshutdown {
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
}
}
@ -221,7 +220,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
log.Infof("Add service containers:")
}
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
}
}

View File

@ -93,9 +93,10 @@ type Image struct {
// Runtime is the type of config processed at runtime, not used to build the OCI spec
type Runtime struct {
Mounts []specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
Mkdir []string `yaml:"mkdir" json:"mkdir,omitempty"`
Interfaces []Interface `yaml:"interfaces" json:"interfaces,omitempty"`
BindNS *Namespaces `yaml:"bindNS" json:"bindNS,omitempty"`
BindNS Namespaces `yaml:"bindNS" json:"bindNS,omitempty"`
}
// Namespaces is the type for configuring paths to bind namespaces
@ -727,7 +728,6 @@ func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect, idMap map[string
sort.Sort(mountList)
namespaces := []specs.LinuxNamespace{}
// to attach to an existing namespace, easiest to bind mount with nsfs in a system container
// net, ipc, and uts namespaces: default to not creating a new namespace (usually host namespace)
netNS := assignStringEmpty3("root", label.Net, yaml.Net)

View File

@ -3,6 +3,7 @@ package moby
import (
"archive/tar"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@ -10,6 +11,7 @@ import (
"strings"
log "github.com/Sirupsen/logrus"
"github.com/opencontainers/runtime-spec/specs-go"
)
type tarWriter interface {
@ -189,18 +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
func ImageBundle(prefix string, image string, config []byte, runtimeConfig []byte, tw tarWriter, trust bool, pull bool, readonly bool) error {
log.Debugf("image bundle: %s %s cfg: %s runtime: %s", prefix, image, string(config), string(runtimeConfig))
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
rootfs := "rootfs"
rootExtract := "rootfs"
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
}
dupMap[image] = root
} else {
if err := tarPrefix(prefix+"/", tw); err != nil {
return err
}
root = dupMap[image]
}
hdr := &tar.Header{
Name: path.Join(prefix, "config.json"),
Mode: 0644,
@ -214,26 +226,11 @@ func ImageBundle(prefix string, image string, config []byte, runtimeConfig []byt
return err
}
// do not write an empty runtime config
if string(runtimeConfig) != "{}" {
hdr = &tar.Header{
Name: path.Join(prefix, "runtime.json"),
Mode: 0644,
Size: int64(len(runtimeConfig)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
buf = bytes.NewBuffer(runtimeConfig)
if _, err := io.Copy(tw, buf); err != nil {
return err
}
}
if !readonly {
// add a tmp directory to be used as a mount point for tmpfs for upper, work
tmp := path.Join(prefix, "tmp")
hdr = &tar.Header{
Name: path.Join(prefix, "tmp"),
Name: tmp,
Mode: 0755,
Typeflag: tar.TypeDir,
}
@ -249,7 +246,47 @@ func ImageBundle(prefix string, image string, config []byte, runtimeConfig []byt
if err := tw.WriteHeader(hdr); err != nil {
return err
}
runtime.Mounts = append(runtime.Mounts, specs.Mount{Source: "tmpfs", Type: "tmpfs", Destination: "/" + tmp})
// remount private as nothing else should see the temporary layers
runtime.Mounts = append(runtime.Mounts, specs.Mount{Destination: "/" + tmp, Options: []string{"remount", "private"}})
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})
} else {
if foundElsewhere {
// 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
runtimeConfig, err := json.MarshalIndent(runtime, "", " ")
if err != nil {
return fmt.Errorf("Failed to create runtime config for %s: %v", image, err)
}
hdr = &tar.Header{
Name: path.Join(prefix, "runtime.json"),
Mode: 0644,
Size: int64(len(runtimeConfig)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
buf = bytes.NewBuffer(runtimeConfig)
if _, err := io.Copy(tw, buf); err != nil {
return err
}
log.Debugf("image bundle: %s %s cfg: %s runtime: %s", prefix, image, string(config), string(runtimeConfig))
return nil
}

View File

@ -239,6 +239,7 @@ var schema = string(`
"type": "object",
"additionalProperties": false,
"properties": {
"mounts": {"$ref": "#/definitions/mounts"},
"mkdir": {"$ref": "#/definitions/strings"},
"interfaces": {"$ref": "#/definitions/interfaces"},
"bindNS": {"$ref": "#/definitions/namespaces"}