mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 09:16:29 +00:00
Use Go code to extract rootfs from system containers
- this removes the use of riddler to extract the rootfs, use code we were using for rootfs. riddler now just geenrates the config, next stage is to generate this ourselves - change the naming of the daemons so no longer include number as we do not guarantee ordering as they start up simultaneously Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
parent
cc5f42dc9d
commit
2be31831d8
@ -4,6 +4,7 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -108,7 +109,7 @@ func build(name string, args []string) {
|
||||
containers = append(containers, ktar)
|
||||
|
||||
// convert init image to tarball
|
||||
init, err := imageExtract(m.Init)
|
||||
init, err := ImageExtract(m.Init, "")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to build init tarball: %v", err)
|
||||
}
|
||||
@ -116,20 +117,29 @@ func build(name string, args []string) {
|
||||
containers = append(containers, buffer)
|
||||
|
||||
for i, image := range m.System {
|
||||
args := ConfigToRun(i, "system", &image)
|
||||
out, err := dockerRun(args...)
|
||||
config, err := ConfigToOCI(&image)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to build container tarball: %v", err)
|
||||
log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
so := fmt.Sprintf("%03d", i)
|
||||
path := "containers/system/" + so + "-" + image.Name
|
||||
out, err := ImageBundle(path, image.Image, config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err)
|
||||
}
|
||||
buffer := bytes.NewBuffer(out)
|
||||
containers = append(containers, buffer)
|
||||
}
|
||||
|
||||
for i, image := range m.Daemon {
|
||||
args := ConfigToRun(i, "daemon", &image)
|
||||
out, err := dockerRun(args...)
|
||||
for _, image := range m.Daemon {
|
||||
config, err := ConfigToOCI(&image)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to build container tarball: %v", err)
|
||||
log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
path := "containers/daemon/" + image.Name
|
||||
out, err := ImageBundle(path, image.Image, config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err)
|
||||
}
|
||||
buffer := bytes.NewBuffer(out)
|
||||
containers = append(containers, buffer)
|
||||
|
@ -50,7 +50,7 @@ type MobyImage struct {
|
||||
ReadOnly bool `yaml:"read_only"`
|
||||
}
|
||||
|
||||
const riddler = "mobylinux/riddler:c23ab4b6e2a2a4ebd4dd51a059cef7f270da72cb@sha256:7e7744b2f554518411633200db98e599782b120e323348495f43f540de26f7b6"
|
||||
const riddler = "mobylinux/riddler:2b4051422b155f659019f9e3fef8cca04e153f5c@sha256:f4bb0c39f1e5c636ed52ebd3ed8ec447ca6c0dc554ffb5784cbeff423ac70d34"
|
||||
|
||||
// NewConfig parses a config file
|
||||
func NewConfig(config []byte) (*Moby, error) {
|
||||
@ -64,11 +64,10 @@ func NewConfig(config []byte) (*Moby, error) {
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// ConfigToRun converts a config to a series of arguments for docker run
|
||||
func ConfigToRun(order int, path string, image *MobyImage) []string {
|
||||
// ConfigToOCI converts a config specification to an OCI config file
|
||||
func ConfigToOCI(image *MobyImage) (string, error) {
|
||||
// riddler arguments
|
||||
so := fmt.Sprintf("%03d", order)
|
||||
args := []string{"-v", "/var/run/docker.sock:/var/run/docker.sock", riddler, image.Image, "/containers/" + path + "/" + so + "-" + image.Name}
|
||||
args := []string{"-v", "/var/run/docker.sock:/var/run/docker.sock", riddler, image.Image}
|
||||
// docker arguments
|
||||
args = append(args, "--cap-drop", "all")
|
||||
for _, cap := range image.Capabilities {
|
||||
@ -107,7 +106,12 @@ func ConfigToRun(order int, path string, image *MobyImage) []string {
|
||||
// command
|
||||
args = append(args, image.Command...)
|
||||
|
||||
return args
|
||||
config, err := dockerRun(args...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to run riddler to get config.json: %v", err)
|
||||
}
|
||||
|
||||
return string(config), nil
|
||||
}
|
||||
|
||||
func filesystem(m *Moby) (*bytes.Buffer, error) {
|
||||
|
@ -164,23 +164,69 @@ nameserver 2001:4860:4860::8844
|
||||
"etc/hostname": "moby",
|
||||
}
|
||||
|
||||
func imageExtract(image string) ([]byte, error) {
|
||||
// ImageExtract extracts the filesystem from an image and returns a tarball with the files prefixed by the given path
|
||||
func ImageExtract(image, prefix string) ([]byte, error) {
|
||||
out := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(out)
|
||||
err := tarPrefix(prefix, tw)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
err = imageTar(image, prefix, tw)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
err = tw.Close()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
// tarPrefix creates the leading directories for a path
|
||||
func tarPrefix(path string, tw *tar.Writer) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
if path[len(path)-1] != byte('/') {
|
||||
return fmt.Errorf("path does not end with /: %s", path)
|
||||
}
|
||||
path = path[:len(path)-1]
|
||||
if path[0] == byte('/') {
|
||||
return fmt.Errorf("path should be relative: %s", path)
|
||||
}
|
||||
mkdir := ""
|
||||
for _, dir := range strings.Split(path, "/") {
|
||||
mkdir = mkdir + dir
|
||||
hdr := &tar.Header{
|
||||
Name: mkdir,
|
||||
Mode: 0755,
|
||||
Typeflag: tar.TypeDir,
|
||||
}
|
||||
tw.WriteHeader(hdr)
|
||||
mkdir = mkdir + "/"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageTar(image, prefix string, tw *tar.Writer) error {
|
||||
if prefix != "" && prefix[len(prefix)-1] != byte('/') {
|
||||
return fmt.Errorf("prefix does not end with /: %s", prefix)
|
||||
}
|
||||
container, err := dockerCreate(image)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("Failed to docker create image %s: %v", image, err)
|
||||
return fmt.Errorf("Failed to docker create image %s: %v", image, err)
|
||||
}
|
||||
contents, err := dockerExport(container)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("Failed to docker export container from container %s: %v", container, err)
|
||||
return fmt.Errorf("Failed to docker export container from container %s: %v", container, err)
|
||||
}
|
||||
err = dockerRm(container)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("Failed to docker rm container %s: %v", container, err)
|
||||
return fmt.Errorf("Failed to docker rm container %s: %v", container, err)
|
||||
}
|
||||
|
||||
// now we need to filter out some files from the resulting tar archive
|
||||
out := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(out)
|
||||
|
||||
r := bytes.NewReader(contents)
|
||||
tr := tar.NewReader(r)
|
||||
@ -191,21 +237,58 @@ func imageExtract(image string) ([]byte, error) {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
return err
|
||||
}
|
||||
if exclude[hdr.Name] {
|
||||
io.Copy(ioutil.Discard, tr)
|
||||
} else if replace[hdr.Name] != "" {
|
||||
hdr.Size = int64(len(replace[hdr.Name]))
|
||||
contents := replace[hdr.Name]
|
||||
hdr.Size = int64(len(contents))
|
||||
hdr.Name = prefix + hdr.Name
|
||||
tw.WriteHeader(hdr)
|
||||
buf := bytes.NewBufferString(replace[hdr.Name])
|
||||
buf := bytes.NewBufferString(contents)
|
||||
io.Copy(tw, buf)
|
||||
io.Copy(ioutil.Discard, tr)
|
||||
} else {
|
||||
hdr.Name = prefix + hdr.Name
|
||||
tw.WriteHeader(hdr)
|
||||
io.Copy(tw, tr)
|
||||
}
|
||||
}
|
||||
err = tw.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
|
||||
func ImageBundle(path, image, config string) ([]byte, error) {
|
||||
out := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(out)
|
||||
err := tarPrefix(path+"/rootfs/", tw)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
hdr := &tar.Header{
|
||||
Name: path + "/" + "config.json",
|
||||
Mode: 0644,
|
||||
Size: int64(len(config)),
|
||||
}
|
||||
err = tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
buf := bytes.NewBufferString(config)
|
||||
_, err = io.Copy(tw, buf)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
err = imageTar(image, path+"/rootfs/", tw)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
err = tw.Close()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
@ -3,15 +3,12 @@
|
||||
set -e
|
||||
|
||||
# arguments are image name, prefix, then arguments passed to Docker
|
||||
# eg ./riddler.sh alpine:3.4 / --read-only alpine:3.4 ls
|
||||
# This script will output a tarball under prefix/ with rootfs and config.json
|
||||
# eg ./riddler.sh alpine:3.4 --read-only alpine:3.4 ls
|
||||
# This script will output config.json
|
||||
|
||||
IMAGE="$1"; shift
|
||||
PREFIX="$1"; shift
|
||||
|
||||
cd /tmp
|
||||
mkdir -p /tmp/$PREFIX
|
||||
cd /tmp/$PREFIX
|
||||
|
||||
# riddler always adds the apparmor options if this is not present
|
||||
EXTRA_OPTIONS="--security-opt apparmor=unconfined"
|
||||
@ -22,7 +19,7 @@ riddler $CONTAINER > /dev/null
|
||||
docker rm $CONTAINER > /dev/null
|
||||
|
||||
# unfixed known issues
|
||||
# noNewPrivileges is always set by riddler, but that is fine for our use cases
|
||||
# noNewPrivileges is always set by riddler
|
||||
|
||||
# These fixes should be removed when riddler is fixed
|
||||
# process.rlimits, just a constant at present, not useful
|
||||
@ -47,16 +44,5 @@ cat config.json.orig | \
|
||||
jq 'if .root.readonly==true then .mounts = (.mounts|map(if .destination=="/dev" then .options |= .+ ["ro"] else . end)) else . end' | \
|
||||
jq '.mounts = if .process.capabilities | length != 38 then (.mounts|map(if .destination=="/sys" then .options |= .+ ["ro"] else . end)) else . end' \
|
||||
> config.json
|
||||
rm config.json.orig
|
||||
|
||||
# extract rootfs
|
||||
EXCLUDE="--exclude .dockerenv --exclude Dockerfile \
|
||||
--exclude dev/console --exclude dev/pts --exclude dev/shm \
|
||||
--exclude etc/hostname --exclude etc/hosts --exclude etc/resolv.conf"
|
||||
mkdir -p rootfs
|
||||
CONTAINER="$(docker create $IMAGE /dev/null)"
|
||||
docker export "$CONTAINER" | tar -xf - -C rootfs $EXCLUDE
|
||||
docker rm "$CONTAINER" > /dev/null
|
||||
|
||||
cd /tmp
|
||||
tar cf - .
|
||||
cat config.json
|
||||
|
Loading…
Reference in New Issue
Block a user