mirror of
				https://github.com/kata-containers/kata-containers.git
				synced 2025-10-31 01:13:02 +00:00 
			
		
		
		
	Don't add the container ID as a log fields as it is already a field (added on #453). Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
		
			
				
	
	
		
			400 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) 2014,2015,2016 Docker, Inc.
 | |
| // Copyright (c) 2017 Intel Corporation
 | |
| //
 | |
| // SPDX-License-Identifier: Apache-2.0
 | |
| //
 | |
| 
 | |
| package main
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	vc "github.com/kata-containers/runtime/virtcontainers"
 | |
| 	"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
 | |
| 	"github.com/urfave/cli"
 | |
| )
 | |
| 
 | |
| var createCLICommand = cli.Command{
 | |
| 	Name:  "create",
 | |
| 	Usage: "Create a container",
 | |
| 	ArgsUsage: `<container-id>
 | |
| 
 | |
|    <container-id> is your name for the instance of the container that you
 | |
|    are starting. The name you provide for the container instance must be unique
 | |
|    on your host.`,
 | |
| 	Description: `The create command creates an instance of a container for a bundle. The
 | |
|    bundle is a directory with a specification file named "` + specConfig + `" and a
 | |
|    root filesystem.
 | |
|    The specification file includes an args parameter. The args parameter is
 | |
|    used to specify command(s) that get run when the container is started.
 | |
|    To change the command(s) that get executed on start, edit the args
 | |
|    parameter of the spec.`,
 | |
| 	Flags: []cli.Flag{
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "bundle, b",
 | |
| 			Value: "",
 | |
| 			Usage: `path to the root of the bundle directory, defaults to the current directory`,
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "console",
 | |
| 			Value: "",
 | |
| 			Usage: "path to a pseudo terminal",
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "console-socket",
 | |
| 			Value: "",
 | |
| 			Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
 | |
| 		},
 | |
| 		cli.StringFlag{
 | |
| 			Name:  "pid-file",
 | |
| 			Value: "",
 | |
| 			Usage: "specify the file to write the process id to",
 | |
| 		},
 | |
| 		cli.BoolFlag{
 | |
| 			Name:  "no-pivot",
 | |
| 			Usage: "warning: this flag is meaningless to kata-runtime, just defined in order to be compatible with docker in ramdisk",
 | |
| 		},
 | |
| 	},
 | |
| 	Action: func(context *cli.Context) error {
 | |
| 		runtimeConfig, ok := context.App.Metadata["runtimeConfig"].(oci.RuntimeConfig)
 | |
| 		if !ok {
 | |
| 			return errors.New("invalid runtime config")
 | |
| 		}
 | |
| 
 | |
| 		console, err := setupConsole(context.String("console"), context.String("console-socket"))
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		return create(context.Args().First(),
 | |
| 			context.String("bundle"),
 | |
| 			console,
 | |
| 			context.String("pid-file"),
 | |
| 			true,
 | |
| 			runtimeConfig,
 | |
| 		)
 | |
| 	},
 | |
| }
 | |
| 
 | |
| // Use a variable to allow tests to modify its value
 | |
| var getKernelParamsFunc = getKernelParams
 | |
| 
 | |
| func create(containerID, bundlePath, console, pidFilePath string, detach bool,
 | |
| 	runtimeConfig oci.RuntimeConfig) error {
 | |
| 	var err error
 | |
| 
 | |
| 	kataLog = kataLog.WithField("container", containerID)
 | |
| 
 | |
| 	// Checks the MUST and MUST NOT from OCI runtime specification
 | |
| 	if bundlePath, err = validCreateParams(containerID, bundlePath); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	ociSpec, err := oci.ParseConfigJSON(bundlePath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	containerType, err := ociSpec.ContainerType()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	disableOutput := noNeedForOutput(detach, ociSpec.Process.Terminal)
 | |
| 
 | |
| 	var process vc.Process
 | |
| 
 | |
| 	switch containerType {
 | |
| 	case vc.PodSandbox:
 | |
| 		process, err = createSandbox(ociSpec, runtimeConfig, containerID, bundlePath, console, disableOutput)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	case vc.PodContainer:
 | |
| 		process, err = createContainer(ociSpec, containerID, bundlePath, console, disableOutput)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// config.json provides a cgroups path that has to be used to create "tasks"
 | |
| 	// and "cgroups.procs" files. Those files have to be filled with a PID, which
 | |
| 	// is shim's in our case. This is mandatory to make sure there is no one
 | |
| 	// else (like Docker) trying to create those files on our behalf. We want to
 | |
| 	// know those files location so that we can remove them when delete is called.
 | |
| 	cgroupsPathList, err := processCgroupsPath(ociSpec, containerType.IsSandbox())
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// cgroupsDirPath is CgroupsPath fetch from OCI spec
 | |
| 	var cgroupsDirPath string
 | |
| 	if ociSpec.Linux != nil {
 | |
| 		cgroupsDirPath = ociSpec.Linux.CgroupsPath
 | |
| 	}
 | |
| 
 | |
| 	if err := createCgroupsFiles(containerID, cgroupsDirPath, cgroupsPathList, process.Pid); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Creation of PID file has to be the last thing done in the create
 | |
| 	// because containerd considers the create complete after this file
 | |
| 	// is created.
 | |
| 	return createPIDFile(pidFilePath, process.Pid)
 | |
| }
 | |
| 
 | |
| var systemdKernelParam = []vc.Param{
 | |
| 	{
 | |
| 		Key:   "init",
 | |
| 		Value: "/usr/lib/systemd/systemd",
 | |
| 	},
 | |
| 	{
 | |
| 		Key:   "systemd.unit",
 | |
| 		Value: systemdUnitName,
 | |
| 	},
 | |
| 	{
 | |
| 		Key:   "systemd.mask",
 | |
| 		Value: "systemd-networkd.service",
 | |
| 	},
 | |
| 	{
 | |
| 		Key:   "systemd.mask",
 | |
| 		Value: "systemd-networkd.socket",
 | |
| 	},
 | |
| }
 | |
| 
 | |
| func getKernelParams(containerID string, needSystemd bool) []vc.Param {
 | |
| 	p := []vc.Param{
 | |
| 		{
 | |
| 			Key:   "ip",
 | |
| 			Value: fmt.Sprintf("::::::%s::off::", containerID),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	if needSystemd {
 | |
| 		p = append(p, systemdKernelParam...)
 | |
| 	}
 | |
| 
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func needSystemd(config vc.HypervisorConfig) bool {
 | |
| 	return config.ImagePath != ""
 | |
| }
 | |
| 
 | |
| // setKernelParams adds the user-specified kernel parameters (from the
 | |
| // configuration file) to the defaults so that the former take priority.
 | |
| func setKernelParams(containerID string, runtimeConfig *oci.RuntimeConfig) error {
 | |
| 	defaultKernelParams := getKernelParamsFunc(containerID, needSystemd(runtimeConfig.HypervisorConfig))
 | |
| 
 | |
| 	if runtimeConfig.HypervisorConfig.Debug {
 | |
| 		strParams := vc.SerializeParams(defaultKernelParams, "=")
 | |
| 		formatted := strings.Join(strParams, " ")
 | |
| 
 | |
| 		kataLog.WithField("default-kernel-parameters", formatted).Debug()
 | |
| 	}
 | |
| 
 | |
| 	// retrieve the parameters specified in the config file
 | |
| 	userKernelParams := runtimeConfig.HypervisorConfig.KernelParams
 | |
| 
 | |
| 	// reset
 | |
| 	runtimeConfig.HypervisorConfig.KernelParams = []vc.Param{}
 | |
| 
 | |
| 	// first, add default values
 | |
| 	for _, p := range defaultKernelParams {
 | |
| 		if err := (runtimeConfig).AddKernelParam(p); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// now re-add the user-specified values so that they take priority.
 | |
| 	for _, p := range userKernelParams {
 | |
| 		if err := (runtimeConfig).AddKernelParam(p); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createSandbox(ociSpec oci.CompatOCISpec, runtimeConfig oci.RuntimeConfig,
 | |
| 	containerID, bundlePath, console string, disableOutput bool) (vc.Process, error) {
 | |
| 
 | |
| 	err := setKernelParams(containerID, &runtimeConfig)
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	sandboxConfig, err := oci.SandboxConfig(ociSpec, runtimeConfig, bundlePath, containerID, console, disableOutput)
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	sandbox, err := vci.CreateSandbox(sandboxConfig)
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	kataLog = kataLog.WithField("sandbox", sandbox.ID())
 | |
| 
 | |
| 	containers := sandbox.GetAllContainers()
 | |
| 	if len(containers) != 1 {
 | |
| 		return vc.Process{}, fmt.Errorf("BUG: Container list from sandbox is wrong, expecting only one container, found %d containers", len(containers))
 | |
| 	}
 | |
| 
 | |
| 	if err := addContainerIDMapping(containerID, sandbox.ID()); err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	return containers[0].Process(), nil
 | |
| }
 | |
| 
 | |
| func createContainer(ociSpec oci.CompatOCISpec, containerID, bundlePath,
 | |
| 	console string, disableOutput bool) (vc.Process, error) {
 | |
| 
 | |
| 	contConfig, err := oci.ContainerConfig(ociSpec, bundlePath, containerID, console, disableOutput)
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	sandboxID, err := ociSpec.SandboxID()
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	kataLog = kataLog.WithField("sandbox", sandboxID)
 | |
| 
 | |
| 	_, c, err := vci.CreateContainer(sandboxID, contConfig)
 | |
| 	if err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	if err := addContainerIDMapping(containerID, sandboxID); err != nil {
 | |
| 		return vc.Process{}, err
 | |
| 	}
 | |
| 
 | |
| 	return c.Process(), nil
 | |
| }
 | |
| 
 | |
| func createCgroupsFiles(containerID string, cgroupsDirPath string, cgroupsPathList []string, pid int) error {
 | |
| 	if len(cgroupsPathList) == 0 {
 | |
| 		kataLog.WithField("pid", pid).Info("Cgroups files not created because cgroupsPath was empty")
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	for _, cgroupsPath := range cgroupsPathList {
 | |
| 		if err := os.MkdirAll(cgroupsPath, cgroupsDirMode); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		if strings.Contains(cgroupsPath, "cpu") && cgroupsDirPath != "" {
 | |
| 			parent := strings.TrimSuffix(cgroupsPath, cgroupsDirPath)
 | |
| 			copyParentCPUSet(cgroupsPath, parent)
 | |
| 		}
 | |
| 
 | |
| 		tasksFilePath := filepath.Join(cgroupsPath, cgroupsTasksFile)
 | |
| 		procsFilePath := filepath.Join(cgroupsPath, cgroupsProcsFile)
 | |
| 
 | |
| 		pidStr := fmt.Sprintf("%d", pid)
 | |
| 
 | |
| 		for _, path := range []string{tasksFilePath, procsFilePath} {
 | |
| 			f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, cgroupsFileMode)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			defer f.Close()
 | |
| 
 | |
| 			n, err := f.WriteString(pidStr)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			if n < len(pidStr) {
 | |
| 				return fmt.Errorf("Could not write pid to %q: only %d bytes written out of %d",
 | |
| 					path, n, len(pidStr))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createPIDFile(pidFilePath string, pid int) error {
 | |
| 	if pidFilePath == "" {
 | |
| 		// runtime should not fail since pid file is optional
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err := os.RemoveAll(pidFilePath); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	f, err := os.Create(pidFilePath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	defer f.Close()
 | |
| 
 | |
| 	pidStr := fmt.Sprintf("%d", pid)
 | |
| 
 | |
| 	n, err := f.WriteString(pidStr)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if n < len(pidStr) {
 | |
| 		return fmt.Errorf("Could not write pid to '%s': only %d bytes written out of %d", pidFilePath, n, len(pidStr))
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // copyParentCPUSet copies the cpuset.cpus and cpuset.mems from the parent
 | |
| // directory to the current directory if the file's contents are 0
 | |
| func copyParentCPUSet(current, parent string) error {
 | |
| 	currentCpus, currentMems, err := getCPUSet(current)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	parentCpus, parentMems, err := getCPUSet(parent)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if len(parentCpus) < 1 || len(parentMems) < 1 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var cgroupsFileMode = os.FileMode(0600)
 | |
| 	if isEmptyString(currentCpus) {
 | |
| 		if err := writeFile(filepath.Join(current, "cpuset.cpus"), string(parentCpus), cgroupsFileMode); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if isEmptyString(currentMems) {
 | |
| 		if err := writeFile(filepath.Join(current, "cpuset.mems"), string(parentMems), cgroupsFileMode); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func getCPUSet(parent string) (cpus []byte, mems []byte, err error) {
 | |
| 	if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	return cpus, mems, nil
 | |
| }
 |