diff --git a/pkg/server/container_create.go b/pkg/server/container_create.go index c757d0afa..dd5d4dd25 100644 --- a/pkg/server/container_create.go +++ b/pkg/server/container_create.go @@ -20,9 +20,10 @@ import ( "fmt" "time" + rootfsapi "github.com/containerd/containerd/api/services/rootfs" "github.com/golang/glog" + imagedigest "github.com/opencontainers/go-digest" "golang.org/x/net/context" - runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata" @@ -68,9 +69,26 @@ func (c *criContainerdService) CreateContainer(ctx context.Context, r *runtime.C Config: config, } - // TODO(random-liu): [P0] Prepare container rootfs. - - // TODO(random-liu): [P0] Set ImageRef in ContainerMetadata with image id. + // Prepare container image snapshot. For container, the image should have + // been pulled before creating the container, so do not ensure the image. + image := config.GetImage().GetImage() + imageMeta, err := c.localResolve(ctx, image) + if err != nil { + return nil, fmt.Errorf("failed to resolve image %q: %v", image, err) + } + if imageMeta == nil { + return nil, fmt.Errorf("image %q not found", image) + } + if _, err := c.rootfsService.Prepare(ctx, &rootfsapi.PrepareRequest{ + Name: id, + // We are sure that ChainID must be a digest. + ChainID: imagedigest.Digest(imageMeta.ChainID), + Readonly: config.GetLinux().GetSecurityContext().GetReadonlyRootfs(), + }); err != nil { + return nil, fmt.Errorf("failed to prepare container rootfs %q: %v", imageMeta.ChainID, err) + } + // TODO(random-liu): [P0] Cleanup snapshot on failure after switching to new rootfs api. + meta.ImageRef = imageMeta.ID // Create container root directory. containerRootDir := getContainerRootDir(c.rootDir, id) diff --git a/pkg/server/container_remove.go b/pkg/server/container_remove.go index 3171608c6..5e39dcf3a 100644 --- a/pkg/server/container_remove.go +++ b/pkg/server/container_remove.go @@ -69,7 +69,7 @@ func (c *criContainerdService) RemoveContainer(ctx context.Context, r *runtime.R // kubelet implementation, we'll never start a container once we decide to remove it, // so we don't need the "Dead" state for now. - // TODO(random-liu): [P0] Cleanup container rootfs. + // TODO(random-liu): [P0] Cleanup snapshot after switching to new snapshot api. // Cleanup container root directory. containerRootDir := getContainerRootDir(c.rootDir, id) diff --git a/pkg/server/container_start.go b/pkg/server/container_start.go index 6269fddc6..9a38fb8b7 100644 --- a/pkg/server/container_start.go +++ b/pkg/server/container_start.go @@ -23,16 +23,15 @@ import ( "os" "time" + "github.com/containerd/containerd/api/services/execution" + rootfsapi "github.com/containerd/containerd/api/services/rootfs" + "github.com/containerd/containerd/api/types/container" prototypes "github.com/gogo/protobuf/types" "github.com/golang/glog" + imagespec "github.com/opencontainers/image-spec/specs-go/v1" runtimespec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "golang.org/x/net/context" - - "github.com/containerd/containerd/api/services/execution" - "github.com/containerd/containerd/api/types/container" - "github.com/containerd/containerd/api/types/mount" - runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata" @@ -114,12 +113,11 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me glog.V(2).Infof("Sandbox container %q is running with pid %d", sandboxID, sandboxPid) // Generate containerd container create options. - // TODO(random-liu): [P0] Create container rootfs with image ref. - // TODO(random-liu): [P0] Apply default image config. - // Use fixed rootfs path for now. - const rootPath = "/" - - spec, err := c.generateContainerSpec(id, sandboxPid, config, sandboxConfig) + imageMeta, err := c.imageMetadataStore.Get(meta.ImageRef) + if err != nil { + return fmt.Errorf("failed to get container image %q: %v", meta.ImageRef, err) + } + spec, err := c.generateContainerSpec(id, sandboxPid, config, sandboxConfig, imageMeta.Config) if err != nil { return fmt.Errorf("failed to generate container %q spec: %v", id, err) } @@ -169,6 +167,12 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me }(stderrPipe) } + // Get rootfs mounts. + mountsResp, err := c.rootfsService.Mounts(ctx, &rootfsapi.MountsRequest{Name: id}) + if err != nil { + return fmt.Errorf("failed to get rootfs mounts %q: %v", id, err) + } + // Create containerd container. createOpts := &execution.CreateRequest{ ID: id, @@ -176,24 +180,14 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me TypeUrl: runtimespec.Version, Value: rawSpec, }, - // TODO(random-liu): [P0] Get rootfs mount from containerd. - Rootfs: []*mount.Mount{ - { - Type: "bind", - Source: rootPath, - Options: []string{ - "rw", - "rbind", - }, - }, - }, + Rootfs: mountsResp.Mounts, Runtime: defaultRuntime, Stdin: stdin, Stdout: stdout, Stderr: stderr, Terminal: config.GetTty(), } - glog.V(2).Infof("Create containerd container (id=%q, name=%q) with options %+v.", + glog.V(5).Infof("Create containerd container (id=%q, name=%q) with options %+v.", id, meta.Name, createOpts) createResp, err := c.containerService.Create(ctx, createOpts) if err != nil { @@ -219,7 +213,8 @@ func (c *criContainerdService) startContainer(ctx context.Context, id string, me return nil } -func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig) (*runtimespec.Spec, error) { +func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint32, config *runtime.ContainerConfig, + sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig) (*runtimespec.Spec, error) { // Creates a spec Generator with the default spec. // TODO(random-liu): [P2] Move container runtime spec generation into a helper function. g := generate.New() @@ -228,14 +223,21 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3 // pre-defined directory. g.SetRootPath(relativeRootfsPath) - if len(config.GetCommand()) != 0 || len(config.GetArgs()) != 0 { - g.SetProcessArgs(append(config.GetCommand(), config.GetArgs()...)) + if err := setOCIProcessArgs(&g, config, imageConfig); err != nil { + return nil, err } if config.GetWorkingDir() != "" { g.SetProcessCwd(config.GetWorkingDir()) + } else if imageConfig.WorkingDir != "" { + g.SetProcessCwd(imageConfig.WorkingDir) } + // Apply envs from image config first, so that envs from container config + // can override them. + if err := addImageEnvs(&g, imageConfig.Env); err != nil { + return nil, err + } for _, e := range config.GetEnvs() { g.AddProcessEnv(e.GetKey(), e.GetValue()) } @@ -288,6 +290,27 @@ func (c *criContainerdService) generateContainerSpec(id string, sandboxPid uint3 return g.Spec(), nil } +// setOCIProcessArgs sets process args. It returns error if the final arg list +// is empty. +func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) error { + command, args := config.GetCommand(), config.GetArgs() + // The following logic is migrated from https://github.com/moby/moby/blob/master/daemon/commit.go + // TODO(random-liu): Clearly define the commands overwrite behavior. + if len(command) == 0 { + if len(args) == 0 { + args = imageConfig.Cmd + } + if command == nil { + command = imageConfig.Entrypoint + } + } + if len(command) == 0 && len(args) == 0 { + return fmt.Errorf("no command specified") + } + g.SetProcessArgs(append(command, args...)) + return nil +} + // addOCIBindMounts adds bind mounts. func addOCIBindMounts(g *generate.Generator, mounts []*runtime.Mount) { for _, mount := range mounts { diff --git a/pkg/server/sandbox_run.go b/pkg/server/sandbox_run.go index 64d03e744..9d329169f 100644 --- a/pkg/server/sandbox_run.go +++ b/pkg/server/sandbox_run.go @@ -219,12 +219,8 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r g := generate.New() // Apply default config from image config. - for _, e := range imageConfig.Env { - kv := strings.Split(e, "=") - if len(kv) != 2 { - return nil, fmt.Errorf("invalid environment variable in image config %+v", imageConfig) - } - g.AddProcessEnv(kv[0], kv[1]) + if err := addImageEnvs(&g, imageConfig.Env); err != nil { + return nil, err } if imageConfig.WorkingDir != "" { @@ -300,3 +296,16 @@ func (c *criContainerdService) generateSandboxContainerSpec(id string, config *r return g.Spec(), nil } + +// addImageEnvs adds environment variables from image config. It returns error if +// an invalid environment variable is encountered. +func addImageEnvs(g *generate.Generator, imageEnvs []string) error { + for _, e := range imageEnvs { + kv := strings.Split(e, "=") + if len(kv) != 2 { + return fmt.Errorf("invalid environment variable %q", e) + } + g.AddProcessEnv(kv[0], kv[1]) + } + return nil +}