Files
kata-containers/cli/create.go
James O. D. Hunt a3ce12179f logging: Add containerID and sandboxID to all log calls
Adding cid+sid fields to the log entries generated by most of the CLI
commands will make debugging across the system easier.

Fixes #452.

Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
2018-06-28 10:37:51 +01:00

405 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/sirupsen/logrus"
"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 {
fields := logrus.Fields{
"container": containerID,
"pid": pid,
}
kataLog.WithFields(fields).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
}