Move unix specific logic into _unix.go
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
7b606375ae
commit
50c73e6dc5
16
Makefile
16
Makefile
@ -15,8 +15,12 @@
|
|||||||
GO := go
|
GO := go
|
||||||
GOOS := $(shell $(GO) env GOOS)
|
GOOS := $(shell $(GO) env GOOS)
|
||||||
GOARCH := $(shell $(GO) env GOARCH)
|
GOARCH := $(shell $(GO) env GOARCH)
|
||||||
WHALE = "🇩"
|
WHALE := "🇩"
|
||||||
ONI = "👹"
|
ONI := "👹"
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
WHALE = "+"
|
||||||
|
ONI = "-"
|
||||||
|
endif
|
||||||
EPOCH_TEST_COMMIT := f9e02affccd51702191e5312665a16045ffef8ab
|
EPOCH_TEST_COMMIT := f9e02affccd51702191e5312665a16045ffef8ab
|
||||||
PROJECT := github.com/containerd/cri
|
PROJECT := github.com/containerd/cri
|
||||||
BINDIR := ${DESTDIR}/usr/local/bin
|
BINDIR := ${DESTDIR}/usr/local/bin
|
||||||
@ -26,13 +30,19 @@ BUILD_DIR := _output
|
|||||||
VERSION := $(shell git rev-parse --short HEAD)
|
VERSION := $(shell git rev-parse --short HEAD)
|
||||||
TARBALL_PREFIX := cri-containerd
|
TARBALL_PREFIX := cri-containerd
|
||||||
TARBALL := $(TARBALL_PREFIX)-$(VERSION).$(GOOS)-$(GOARCH).tar.gz
|
TARBALL := $(TARBALL_PREFIX)-$(VERSION).$(GOOS)-$(GOARCH).tar.gz
|
||||||
|
ifneq ($(GOOS),windows)
|
||||||
BUILD_TAGS := seccomp apparmor
|
BUILD_TAGS := seccomp apparmor
|
||||||
|
endif
|
||||||
# Add `-TEST` suffix to indicate that all binaries built from this repo are for test.
|
# Add `-TEST` suffix to indicate that all binaries built from this repo are for test.
|
||||||
GO_LDFLAGS := -X $(PROJECT)/vendor/github.com/containerd/containerd/version.Version=$(VERSION)-TEST
|
GO_LDFLAGS := -X $(PROJECT)/vendor/github.com/containerd/containerd/version.Version=$(VERSION)-TEST
|
||||||
SOURCES := $(shell find cmd/ pkg/ vendor/ -name '*.go')
|
SOURCES := $(shell find cmd/ pkg/ vendor/ -name '*.go')
|
||||||
PLUGIN_SOURCES := $(shell ls *.go)
|
PLUGIN_SOURCES := $(shell ls *.go)
|
||||||
INTEGRATION_SOURCES := $(shell find integration/ -name '*.go')
|
INTEGRATION_SOURCES := $(shell find integration/ -name '*.go')
|
||||||
|
|
||||||
|
ifeq ($(GOOS),windows)
|
||||||
|
BIN_EXT := .exe
|
||||||
|
endif
|
||||||
|
|
||||||
all: binaries
|
all: binaries
|
||||||
|
|
||||||
help: ## this help
|
help: ## this help
|
||||||
@ -74,7 +84,7 @@ update-vendor: sync-vendor sort-vendor ## Syncs containerd/vendor.conf -> vendor
|
|||||||
|
|
||||||
$(BUILD_DIR)/containerd: $(SOURCES) $(PLUGIN_SOURCES)
|
$(BUILD_DIR)/containerd: $(SOURCES) $(PLUGIN_SOURCES)
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
$(GO) build -o $@ \
|
$(GO) build -o $@$(BIN_EXT) \
|
||||||
-tags '$(BUILD_TAGS)' \
|
-tags '$(BUILD_TAGS)' \
|
||||||
-ldflags '$(GO_LDFLAGS)' \
|
-ldflags '$(GO_LDFLAGS)' \
|
||||||
-gcflags '$(GO_GCFLAGS)' \
|
-gcflags '$(GO_GCFLAGS)' \
|
||||||
|
25
cmd/containerd/builtins_unix.go
Normal file
25
cmd/containerd/builtins_unix.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/containerd/containerd/metrics/cgroups"
|
||||||
|
_ "github.com/containerd/containerd/runtime/v1/linux"
|
||||||
|
_ "github.com/containerd/containerd/snapshots/overlay"
|
||||||
|
)
|
25
cmd/containerd/builtins_windows.go
Normal file
25
cmd/containerd/builtins_windows.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/containerd/containerd/diff/lcow"
|
||||||
|
_ "github.com/containerd/containerd/diff/windows"
|
||||||
|
_ "github.com/containerd/containerd/snapshots/windows"
|
||||||
|
)
|
@ -24,8 +24,6 @@ import (
|
|||||||
|
|
||||||
_ "github.com/containerd/containerd/diff/walking/plugin"
|
_ "github.com/containerd/containerd/diff/walking/plugin"
|
||||||
_ "github.com/containerd/containerd/gc/scheduler"
|
_ "github.com/containerd/containerd/gc/scheduler"
|
||||||
_ "github.com/containerd/containerd/metrics/cgroups"
|
|
||||||
_ "github.com/containerd/containerd/runtime/v1/linux"
|
|
||||||
_ "github.com/containerd/containerd/runtime/v2"
|
_ "github.com/containerd/containerd/runtime/v2"
|
||||||
_ "github.com/containerd/containerd/services/containers"
|
_ "github.com/containerd/containerd/services/containers"
|
||||||
_ "github.com/containerd/containerd/services/content"
|
_ "github.com/containerd/containerd/services/content"
|
||||||
@ -39,7 +37,6 @@ import (
|
|||||||
_ "github.com/containerd/containerd/services/snapshots"
|
_ "github.com/containerd/containerd/services/snapshots"
|
||||||
_ "github.com/containerd/containerd/services/tasks"
|
_ "github.com/containerd/containerd/services/tasks"
|
||||||
_ "github.com/containerd/containerd/services/version"
|
_ "github.com/containerd/containerd/services/version"
|
||||||
_ "github.com/containerd/containerd/snapshots/overlay"
|
|
||||||
_ "github.com/containerd/cri"
|
_ "github.com/containerd/cri"
|
||||||
|
|
||||||
"github.com/containerd/containerd/cmd/containerd/command"
|
"github.com/containerd/containerd/cmd/containerd/command"
|
||||||
|
@ -21,11 +21,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/containerd/containerd"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runtime struct to contain the type(ID), engine, and root variables for a default runtime
|
// Runtime struct to contain the type(ID), engine, and root variables for a default runtime
|
||||||
@ -227,51 +225,6 @@ type Config struct {
|
|||||||
StateDir string `json:"stateDir"`
|
StateDir string `json:"stateDir"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns default configurations of cri plugin.
|
|
||||||
func DefaultConfig() PluginConfig {
|
|
||||||
return PluginConfig{
|
|
||||||
CniConfig: CniConfig{
|
|
||||||
NetworkPluginBinDir: "/opt/cni/bin",
|
|
||||||
NetworkPluginConfDir: "/etc/cni/net.d",
|
|
||||||
NetworkPluginMaxConfNum: 1, // only one CNI plugin config file will be loaded
|
|
||||||
NetworkPluginConfTemplate: "",
|
|
||||||
},
|
|
||||||
ContainerdConfig: ContainerdConfig{
|
|
||||||
Snapshotter: containerd.DefaultSnapshotter,
|
|
||||||
DefaultRuntimeName: "runc",
|
|
||||||
NoPivot: false,
|
|
||||||
Runtimes: map[string]Runtime{
|
|
||||||
"runc": {
|
|
||||||
Type: "io.containerd.runc.v1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
DisableTCPService: true,
|
|
||||||
StreamServerAddress: "127.0.0.1",
|
|
||||||
StreamServerPort: "0",
|
|
||||||
StreamIdleTimeout: streaming.DefaultConfig.StreamIdleTimeout.String(), // 4 hour
|
|
||||||
EnableSelinux: false,
|
|
||||||
EnableTLSStreaming: false,
|
|
||||||
X509KeyPairStreaming: X509KeyPairStreaming{
|
|
||||||
TLSKeyFile: "",
|
|
||||||
TLSCertFile: "",
|
|
||||||
},
|
|
||||||
SandboxImage: "k8s.gcr.io/pause:3.1",
|
|
||||||
StatsCollectPeriod: 10,
|
|
||||||
SystemdCgroup: false,
|
|
||||||
MaxContainerLogLineSize: 16 * 1024,
|
|
||||||
Registry: Registry{
|
|
||||||
Mirrors: map[string]Mirror{
|
|
||||||
"docker.io": {
|
|
||||||
Endpoints: []string{"https://registry-1.docker.io"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
MaxConcurrentDownloads: 3,
|
|
||||||
DisableProcMount: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RuntimeUntrusted is the implicit runtime defined for ContainerdConfig.UntrustedWorkloadRuntime
|
// RuntimeUntrusted is the implicit runtime defined for ContainerdConfig.UntrustedWorkloadRuntime
|
||||||
RuntimeUntrusted = "untrusted"
|
RuntimeUntrusted = "untrusted"
|
||||||
|
69
pkg/config/config_unix.go
Normal file
69
pkg/config/config_unix.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultConfig returns default configurations of cri plugin.
|
||||||
|
func DefaultConfig() PluginConfig {
|
||||||
|
return PluginConfig{
|
||||||
|
CniConfig: CniConfig{
|
||||||
|
NetworkPluginBinDir: "/opt/cni/bin",
|
||||||
|
NetworkPluginConfDir: "/etc/cni/net.d",
|
||||||
|
NetworkPluginMaxConfNum: 1, // only one CNI plugin config file will be loaded
|
||||||
|
NetworkPluginConfTemplate: "",
|
||||||
|
},
|
||||||
|
ContainerdConfig: ContainerdConfig{
|
||||||
|
Snapshotter: containerd.DefaultSnapshotter,
|
||||||
|
DefaultRuntimeName: "runc",
|
||||||
|
NoPivot: false,
|
||||||
|
Runtimes: map[string]Runtime{
|
||||||
|
"runc": {
|
||||||
|
Type: "io.containerd.runc.v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DisableTCPService: true,
|
||||||
|
StreamServerAddress: "127.0.0.1",
|
||||||
|
StreamServerPort: "0",
|
||||||
|
StreamIdleTimeout: streaming.DefaultConfig.StreamIdleTimeout.String(), // 4 hour
|
||||||
|
EnableSelinux: false,
|
||||||
|
EnableTLSStreaming: false,
|
||||||
|
X509KeyPairStreaming: X509KeyPairStreaming{
|
||||||
|
TLSKeyFile: "",
|
||||||
|
TLSCertFile: "",
|
||||||
|
},
|
||||||
|
SandboxImage: "k8s.gcr.io/pause:3.1",
|
||||||
|
StatsCollectPeriod: 10,
|
||||||
|
SystemdCgroup: false,
|
||||||
|
MaxContainerLogLineSize: 16 * 1024,
|
||||||
|
Registry: Registry{
|
||||||
|
Mirrors: map[string]Mirror{
|
||||||
|
"docker.io": {
|
||||||
|
Endpoints: []string{"https://registry-1.docker.io"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxConcurrentDownloads: 3,
|
||||||
|
DisableProcMount: false,
|
||||||
|
}
|
||||||
|
}
|
24
pkg/config/config_windows.go
Normal file
24
pkg/config/config_windows.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
// DefaultConfig returns default configurations of cri plugin.
|
||||||
|
func DefaultConfig() PluginConfig {
|
||||||
|
return PluginConfig{}
|
||||||
|
}
|
@ -18,100 +18,19 @@ package opts
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/log"
|
|
||||||
"github.com/containerd/containerd/mount"
|
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
|
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/opencontainers/runc/libcontainer/devices"
|
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
osinterface "github.com/containerd/cri/pkg/os"
|
|
||||||
"github.com/containerd/cri/pkg/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSandboxCPUshares is default cpu shares for sandbox container.
|
|
||||||
DefaultSandboxCPUshares = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithAdditionalGIDs adds any additional groups listed for a particular user in the
|
|
||||||
// /etc/groups file of the image's root filesystem to the OCI spec's additionalGids array.
|
|
||||||
func WithAdditionalGIDs(userstr string) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
gids := s.Process.User.AdditionalGids
|
|
||||||
if err := oci.WithAdditionalGIDs(userstr)(ctx, client, c, s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Merge existing gids and new gids.
|
|
||||||
s.Process.User.AdditionalGids = mergeGids(s.Process.User.AdditionalGids, gids)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeGids(gids1, gids2 []uint32) []uint32 {
|
|
||||||
gidsMap := make(map[uint32]struct{})
|
|
||||||
for _, gid1 := range gids1 {
|
|
||||||
gidsMap[gid1] = struct{}{}
|
|
||||||
}
|
|
||||||
for _, gid2 := range gids2 {
|
|
||||||
gidsMap[gid2] = struct{}{}
|
|
||||||
}
|
|
||||||
var gids []uint32
|
|
||||||
for gid := range gidsMap {
|
|
||||||
gids = append(gids, gid)
|
|
||||||
}
|
|
||||||
sort.Slice(gids, func(i, j int) bool { return gids[i] < gids[j] })
|
|
||||||
return gids
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithoutRunMount removes the `/run` inside the spec
|
|
||||||
func WithoutRunMount(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
var (
|
|
||||||
mounts []runtimespec.Mount
|
|
||||||
current = s.Mounts
|
|
||||||
)
|
|
||||||
for _, m := range current {
|
|
||||||
if filepath.Clean(m.Destination) == "/run" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
mounts = append(mounts, m)
|
|
||||||
}
|
|
||||||
s.Mounts = mounts
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithoutDefaultSecuritySettings removes the default security settings generated on a spec
|
|
||||||
func WithoutDefaultSecuritySettings(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
// Make sure no default seccomp/apparmor is specified
|
|
||||||
s.Process.ApparmorProfile = ""
|
|
||||||
if s.Linux != nil {
|
|
||||||
s.Linux.Seccomp = nil
|
|
||||||
}
|
|
||||||
// Remove default rlimits (See issue #515)
|
|
||||||
s.Process.Rlimits = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRelativeRoot sets the root for the container
|
// WithRelativeRoot sets the root for the container
|
||||||
func WithRelativeRoot(root string) oci.SpecOpts {
|
func WithRelativeRoot(root string) oci.SpecOpts {
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
@ -145,141 +64,6 @@ func WithProcessArgs(config *runtime.ContainerConfig, image *imagespec.ImageConf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithMounts sorts and adds runtime and CRI mounts to the spec
|
|
||||||
func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) (err error) {
|
|
||||||
// mergeMounts merge CRI mounts with extra mounts. If a mount destination
|
|
||||||
// is mounted by both a CRI mount and an extra mount, the CRI mount will
|
|
||||||
// be kept.
|
|
||||||
var (
|
|
||||||
criMounts = config.GetMounts()
|
|
||||||
mounts = append([]*runtime.Mount{}, criMounts...)
|
|
||||||
)
|
|
||||||
// Copy all mounts from extra mounts, except for mounts overriden by CRI.
|
|
||||||
for _, e := range extra {
|
|
||||||
found := false
|
|
||||||
for _, c := range criMounts {
|
|
||||||
if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
mounts = append(mounts, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ---
|
|
||||||
|
|
||||||
// Sort mounts in number of parts. This ensures that high level mounts don't
|
|
||||||
// shadow other mounts.
|
|
||||||
sort.Sort(orderedMounts(mounts))
|
|
||||||
|
|
||||||
// Mount cgroup into the container as readonly, which inherits docker's behavior.
|
|
||||||
s.Mounts = append(s.Mounts, runtimespec.Mount{
|
|
||||||
Source: "cgroup",
|
|
||||||
Destination: "/sys/fs/cgroup",
|
|
||||||
Type: "cgroup",
|
|
||||||
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Copy all mounts from default mounts, except for
|
|
||||||
// - mounts overriden by supplied mount;
|
|
||||||
// - all mounts under /dev if a supplied /dev is present.
|
|
||||||
mountSet := make(map[string]struct{})
|
|
||||||
for _, m := range mounts {
|
|
||||||
mountSet[filepath.Clean(m.ContainerPath)] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultMounts := s.Mounts
|
|
||||||
s.Mounts = nil
|
|
||||||
|
|
||||||
for _, m := range defaultMounts {
|
|
||||||
dst := filepath.Clean(m.Destination)
|
|
||||||
if _, ok := mountSet[dst]; ok {
|
|
||||||
// filter out mount overridden by a supplied mount
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, mountDev := mountSet["/dev"]; mountDev && strings.HasPrefix(dst, "/dev/") {
|
|
||||||
// filter out everything under /dev if /dev is a supplied mount
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.Mounts = append(s.Mounts, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mount := range mounts {
|
|
||||||
var (
|
|
||||||
dst = mount.GetContainerPath()
|
|
||||||
src = mount.GetHostPath()
|
|
||||||
)
|
|
||||||
// Create the host path if it doesn't exist.
|
|
||||||
// TODO(random-liu): Add CRI validation test for this case.
|
|
||||||
if _, err := osi.Stat(src); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return errors.Wrapf(err, "failed to stat %q", src)
|
|
||||||
}
|
|
||||||
if err := osi.MkdirAll(src, 0755); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to mkdir %q", src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO(random-liu): Add cri-containerd integration test or cri validation test
|
|
||||||
// for this.
|
|
||||||
src, err := osi.ResolveSymbolicLink(src)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to resolve symlink %q", src)
|
|
||||||
}
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
options := []string{"rbind"}
|
|
||||||
switch mount.GetPropagation() {
|
|
||||||
case runtime.MountPropagation_PROPAGATION_PRIVATE:
|
|
||||||
options = append(options, "rprivate")
|
|
||||||
// Since default root propogation in runc is rprivate ignore
|
|
||||||
// setting the root propagation
|
|
||||||
case runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL:
|
|
||||||
if err := ensureShared(src, osi.LookupMount); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options = append(options, "rshared")
|
|
||||||
s.Linux.RootfsPropagation = "rshared"
|
|
||||||
case runtime.MountPropagation_PROPAGATION_HOST_TO_CONTAINER:
|
|
||||||
if err := ensureSharedOrSlave(src, osi.LookupMount); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options = append(options, "rslave")
|
|
||||||
if s.Linux.RootfsPropagation != "rshared" &&
|
|
||||||
s.Linux.RootfsPropagation != "rslave" {
|
|
||||||
s.Linux.RootfsPropagation = "rslave"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.G(ctx).Warnf("Unknown propagation mode for hostPath %q", mount.HostPath)
|
|
||||||
options = append(options, "rprivate")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
|
|
||||||
// is readonly. This is different from docker's behavior, but make more sense.
|
|
||||||
if mount.GetReadonly() {
|
|
||||||
options = append(options, "ro")
|
|
||||||
} else {
|
|
||||||
options = append(options, "rw")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.GetSelinuxRelabel() {
|
|
||||||
if err := label.Relabel(src, mountLabel, true); err != nil && err != unix.ENOTSUP {
|
|
||||||
return errors.Wrapf(err, "relabel %q with %q failed", src, mountLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Mounts = append(s.Mounts, runtimespec.Mount{
|
|
||||||
Source: src,
|
|
||||||
Destination: dst,
|
|
||||||
Type: "bind",
|
|
||||||
Options: options,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mounts defines how to sort runtime.Mount.
|
// mounts defines how to sort runtime.Mount.
|
||||||
// This is the same with the Docker implementation:
|
// This is the same with the Docker implementation:
|
||||||
// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26
|
// https://github.com/moby/moby/blob/17.05.x/daemon/volumes.go#L26
|
||||||
@ -307,325 +91,6 @@ func (m orderedMounts) parts(i int) int {
|
|||||||
return strings.Count(filepath.Clean(m[i].ContainerPath), string(os.PathSeparator))
|
return strings.Count(filepath.Clean(m[i].ContainerPath), string(os.PathSeparator))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure mount point on which path is mounted, is shared.
|
|
||||||
func ensureShared(path string, lookupMount func(string) (mount.Info, error)) error {
|
|
||||||
mountInfo, err := lookupMount(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure source mount point is shared.
|
|
||||||
optsSplit := strings.Split(mountInfo.Optional, " ")
|
|
||||||
for _, opt := range optsSplit {
|
|
||||||
if strings.HasPrefix(opt, "shared:") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.Errorf("path %q is mounted on %q but it is not a shared mount", path, mountInfo.Mountpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure mount point on which path is mounted, is either shared or slave.
|
|
||||||
func ensureSharedOrSlave(path string, lookupMount func(string) (mount.Info, error)) error {
|
|
||||||
mountInfo, err := lookupMount(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Make sure source mount point is shared.
|
|
||||||
optsSplit := strings.Split(mountInfo.Optional, " ")
|
|
||||||
for _, opt := range optsSplit {
|
|
||||||
if strings.HasPrefix(opt, "shared:") {
|
|
||||||
return nil
|
|
||||||
} else if strings.HasPrefix(opt, "master:") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Errorf("path %q is mounted on %q but it is not a shared or slave mount", path, mountInfo.Mountpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPrivilegedDevices allows all host devices inside the container
|
|
||||||
func WithPrivilegedDevices(_ context.Context, _ oci.Client, _ *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &runtimespec.LinuxResources{}
|
|
||||||
}
|
|
||||||
hostDevices, err := devices.HostDevices()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, hostDevice := range hostDevices {
|
|
||||||
rd := runtimespec.LinuxDevice{
|
|
||||||
Path: hostDevice.Path,
|
|
||||||
Type: string(hostDevice.Type),
|
|
||||||
Major: hostDevice.Major,
|
|
||||||
Minor: hostDevice.Minor,
|
|
||||||
UID: &hostDevice.Uid,
|
|
||||||
GID: &hostDevice.Gid,
|
|
||||||
}
|
|
||||||
if hostDevice.Major == 0 && hostDevice.Minor == 0 {
|
|
||||||
// Invalid device, most likely a symbolic link, skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addDevice(s, rd)
|
|
||||||
}
|
|
||||||
s.Linux.Resources.Devices = []runtimespec.LinuxDeviceCgroup{
|
|
||||||
{
|
|
||||||
Allow: true,
|
|
||||||
Access: "rwm",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addDevice(s *runtimespec.Spec, rd runtimespec.LinuxDevice) {
|
|
||||||
for i, dev := range s.Linux.Devices {
|
|
||||||
if dev.Path == rd.Path {
|
|
||||||
s.Linux.Devices[i] = rd
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Linux.Devices = append(s.Linux.Devices, rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDevices sets the provided devices onto the container spec
|
|
||||||
func WithDevices(osi osinterface.OS, config *runtime.ContainerConfig) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &runtimespec.LinuxResources{}
|
|
||||||
}
|
|
||||||
for _, device := range config.GetDevices() {
|
|
||||||
path, err := osi.ResolveSymbolicLink(device.HostPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dev, err := devices.DeviceFromPath(path, device.Permissions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rd := runtimespec.LinuxDevice{
|
|
||||||
Path: device.ContainerPath,
|
|
||||||
Type: string(dev.Type),
|
|
||||||
Major: dev.Major,
|
|
||||||
Minor: dev.Minor,
|
|
||||||
UID: &dev.Uid,
|
|
||||||
GID: &dev.Gid,
|
|
||||||
}
|
|
||||||
|
|
||||||
addDevice(s, rd)
|
|
||||||
|
|
||||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, runtimespec.LinuxDeviceCgroup{
|
|
||||||
Allow: true,
|
|
||||||
Type: string(dev.Type),
|
|
||||||
Major: &dev.Major,
|
|
||||||
Minor: &dev.Minor,
|
|
||||||
Access: dev.Permissions,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCapabilities sets the provided capabilties from the security context
|
|
||||||
func WithCapabilities(sc *runtime.LinuxContainerSecurityContext) oci.SpecOpts {
|
|
||||||
capabilities := sc.GetCapabilities()
|
|
||||||
if capabilities == nil {
|
|
||||||
return nullOpt
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts []oci.SpecOpts
|
|
||||||
// Add/drop all capabilities if "all" is specified, so that
|
|
||||||
// following individual add/drop could still work. E.g.
|
|
||||||
// AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"}
|
|
||||||
// will be all capabilities without `CAP_CHOWN`.
|
|
||||||
if util.InStringSlice(capabilities.GetAddCapabilities(), "ALL") {
|
|
||||||
opts = append(opts, oci.WithAllCapabilities)
|
|
||||||
}
|
|
||||||
if util.InStringSlice(capabilities.GetDropCapabilities(), "ALL") {
|
|
||||||
opts = append(opts, oci.WithCapabilities(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
var caps []string
|
|
||||||
for _, c := range capabilities.GetAddCapabilities() {
|
|
||||||
if strings.ToUpper(c) == "ALL" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Capabilities in CRI doesn't have `CAP_` prefix, so add it.
|
|
||||||
caps = append(caps, "CAP_"+strings.ToUpper(c))
|
|
||||||
}
|
|
||||||
opts = append(opts, oci.WithAddedCapabilities(caps))
|
|
||||||
|
|
||||||
caps = []string{}
|
|
||||||
for _, c := range capabilities.GetDropCapabilities() {
|
|
||||||
if strings.ToUpper(c) == "ALL" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
caps = append(caps, "CAP_"+strings.ToUpper(c))
|
|
||||||
}
|
|
||||||
opts = append(opts, oci.WithDroppedCapabilities(caps))
|
|
||||||
return oci.Compose(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithoutAmbientCaps removes the ambient caps from the spec
|
|
||||||
func WithoutAmbientCaps(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
if s.Process.Capabilities == nil {
|
|
||||||
s.Process.Capabilities = &runtimespec.LinuxCapabilities{}
|
|
||||||
}
|
|
||||||
s.Process.Capabilities.Ambient = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDisabledCgroups clears the Cgroups Path from the spec
|
|
||||||
func WithDisabledCgroups(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
s.Linux.CgroupsPath = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSelinuxLabels sets the mount and process labels
|
|
||||||
func WithSelinuxLabels(process, mount string) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
s.Linux.MountLabel = mount
|
|
||||||
s.Process.SelinuxLabel = process
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithResources sets the provided resource restrictions
|
|
||||||
func WithResources(resources *runtime.LinuxContainerResources) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
|
||||||
if resources == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &runtimespec.LinuxResources{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources.CPU == nil {
|
|
||||||
s.Linux.Resources.CPU = &runtimespec.LinuxCPU{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources.Memory == nil {
|
|
||||||
s.Linux.Resources.Memory = &runtimespec.LinuxMemory{}
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
p = uint64(resources.GetCpuPeriod())
|
|
||||||
q = resources.GetCpuQuota()
|
|
||||||
shares = uint64(resources.GetCpuShares())
|
|
||||||
limit = resources.GetMemoryLimitInBytes()
|
|
||||||
)
|
|
||||||
|
|
||||||
if p != 0 {
|
|
||||||
s.Linux.Resources.CPU.Period = &p
|
|
||||||
}
|
|
||||||
if q != 0 {
|
|
||||||
s.Linux.Resources.CPU.Quota = &q
|
|
||||||
}
|
|
||||||
if shares != 0 {
|
|
||||||
s.Linux.Resources.CPU.Shares = &shares
|
|
||||||
}
|
|
||||||
if cpus := resources.GetCpusetCpus(); cpus != "" {
|
|
||||||
s.Linux.Resources.CPU.Cpus = cpus
|
|
||||||
}
|
|
||||||
if mems := resources.GetCpusetMems(); mems != "" {
|
|
||||||
s.Linux.Resources.CPU.Mems = resources.GetCpusetMems()
|
|
||||||
}
|
|
||||||
if limit != 0 {
|
|
||||||
s.Linux.Resources.Memory.Limit = &limit
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithOOMScoreAdj sets the oom score
|
|
||||||
func WithOOMScoreAdj(config *runtime.ContainerConfig, restrict bool) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := config.GetLinux().GetResources()
|
|
||||||
if resources == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
adj := int(resources.GetOomScoreAdj())
|
|
||||||
if restrict {
|
|
||||||
var err error
|
|
||||||
adj, err = restrictOOMScoreAdj(adj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Process.OOMScoreAdj = &adj
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSysctls sets the provided sysctls onto the spec
|
|
||||||
func WithSysctls(sysctls map[string]string) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Linux.Sysctl == nil {
|
|
||||||
s.Linux.Sysctl = make(map[string]string)
|
|
||||||
}
|
|
||||||
for k, v := range sysctls {
|
|
||||||
s.Linux.Sysctl[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPodOOMScoreAdj sets the oom score for the pod sandbox
|
|
||||||
func WithPodOOMScoreAdj(adj int, restrict bool) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
if restrict {
|
|
||||||
var err error
|
|
||||||
adj, err = restrictOOMScoreAdj(adj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Process.OOMScoreAdj = &adj
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSupplementalGroups sets the supplemental groups for the process
|
|
||||||
func WithSupplementalGroups(groups []int64) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Process == nil {
|
|
||||||
s.Process = &runtimespec.Process{}
|
|
||||||
}
|
|
||||||
var guids []uint32
|
|
||||||
for _, g := range groups {
|
|
||||||
guids = append(guids, uint32(g))
|
|
||||||
}
|
|
||||||
s.Process.User.AdditionalGids = mergeGids(s.Process.User.AdditionalGids, guids)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAnnotation sets the provided annotation
|
// WithAnnotation sets the provided annotation
|
||||||
func WithAnnotation(k, v string) oci.SpecOpts {
|
func WithAnnotation(k, v string) oci.SpecOpts {
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
@ -636,110 +101,3 @@ func WithAnnotation(k, v string) oci.SpecOpts {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithPodNamespaces sets the pod namespaces for the container
|
|
||||||
func WithPodNamespaces(config *runtime.LinuxContainerSecurityContext, pid uint32) oci.SpecOpts {
|
|
||||||
namespaces := config.GetNamespaceOptions()
|
|
||||||
|
|
||||||
opts := []oci.SpecOpts{
|
|
||||||
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.NetworkNamespace, Path: GetNetworkNamespace(pid)}),
|
|
||||||
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.IPCNamespace, Path: GetIPCNamespace(pid)}),
|
|
||||||
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.UTSNamespace, Path: GetUTSNamespace(pid)}),
|
|
||||||
}
|
|
||||||
if namespaces.GetPid() != runtime.NamespaceMode_CONTAINER {
|
|
||||||
opts = append(opts, oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.PIDNamespace, Path: GetPIDNamespace(pid)}))
|
|
||||||
}
|
|
||||||
return oci.Compose(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaultSandboxShares sets the default sandbox CPU shares
|
|
||||||
func WithDefaultSandboxShares(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Linux == nil {
|
|
||||||
s.Linux = &runtimespec.Linux{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources == nil {
|
|
||||||
s.Linux.Resources = &runtimespec.LinuxResources{}
|
|
||||||
}
|
|
||||||
if s.Linux.Resources.CPU == nil {
|
|
||||||
s.Linux.Resources.CPU = &runtimespec.LinuxCPU{}
|
|
||||||
}
|
|
||||||
i := uint64(DefaultSandboxCPUshares)
|
|
||||||
s.Linux.Resources.CPU.Shares = &i
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithoutNamespace removes the provided namespace
|
|
||||||
func WithoutNamespace(t runtimespec.LinuxNamespaceType) oci.SpecOpts {
|
|
||||||
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
|
||||||
if s.Linux == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var namespaces []runtimespec.LinuxNamespace
|
|
||||||
for i, ns := range s.Linux.Namespaces {
|
|
||||||
if ns.Type != t {
|
|
||||||
namespaces = append(namespaces, s.Linux.Namespaces[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Linux.Namespaces = namespaces
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullOpt(_ context.Context, _ oci.Client, _ *containers.Container, _ *runtimespec.Spec) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCurrentOOMScoreAdj() (int, error) {
|
|
||||||
b, err := ioutil.ReadFile("/proc/self/oom_score_adj")
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrap(err, "could not get the daemon oom_score_adj")
|
|
||||||
}
|
|
||||||
s := strings.TrimSpace(string(b))
|
|
||||||
i, err := strconv.Atoi(s)
|
|
||||||
if err != nil {
|
|
||||||
return 0, errors.Wrap(err, "could not get the daemon oom_score_adj")
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restrictOOMScoreAdj(preferredOOMScoreAdj int) (int, error) {
|
|
||||||
currentOOMScoreAdj, err := getCurrentOOMScoreAdj()
|
|
||||||
if err != nil {
|
|
||||||
return preferredOOMScoreAdj, err
|
|
||||||
}
|
|
||||||
if preferredOOMScoreAdj < currentOOMScoreAdj {
|
|
||||||
return currentOOMScoreAdj, nil
|
|
||||||
}
|
|
||||||
return preferredOOMScoreAdj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// netNSFormat is the format of network namespace of a process.
|
|
||||||
netNSFormat = "/proc/%v/ns/net"
|
|
||||||
// ipcNSFormat is the format of ipc namespace of a process.
|
|
||||||
ipcNSFormat = "/proc/%v/ns/ipc"
|
|
||||||
// utsNSFormat is the format of uts namespace of a process.
|
|
||||||
utsNSFormat = "/proc/%v/ns/uts"
|
|
||||||
// pidNSFormat is the format of pid namespace of a process.
|
|
||||||
pidNSFormat = "/proc/%v/ns/pid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetNetworkNamespace returns the network namespace of a process.
|
|
||||||
func GetNetworkNamespace(pid uint32) string {
|
|
||||||
return fmt.Sprintf(netNSFormat, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIPCNamespace returns the ipc namespace of a process.
|
|
||||||
func GetIPCNamespace(pid uint32) string {
|
|
||||||
return fmt.Sprintf(ipcNSFormat, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUTSNamespace returns the uts namespace of a process.
|
|
||||||
func GetUTSNamespace(pid uint32) string {
|
|
||||||
return fmt.Sprintf(utsNSFormat, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPIDNamespace returns the pid namespace of a process.
|
|
||||||
func GetPIDNamespace(pid uint32) string {
|
|
||||||
return fmt.Sprintf(pidNSFormat, pid)
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2018 The containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -21,16 +21,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergeGids(t *testing.T) {
|
|
||||||
gids1 := []uint32{3, 2, 1}
|
|
||||||
gids2 := []uint32{2, 3, 4}
|
|
||||||
assert.Equal(t, []uint32{1, 2, 3, 4}, mergeGids(gids1, gids2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrderedMounts(t *testing.T) {
|
func TestOrderedMounts(t *testing.T) {
|
||||||
mounts := []*runtime.Mount{
|
mounts := []*runtime.Mount{
|
||||||
{ContainerPath: "/a/b/c"},
|
{ContainerPath: "/a/b/c"},
|
||||||
@ -51,20 +44,3 @@ func TestOrderedMounts(t *testing.T) {
|
|||||||
sort.Stable(orderedMounts(mounts))
|
sort.Stable(orderedMounts(mounts))
|
||||||
assert.Equal(t, expected, mounts)
|
assert.Equal(t, expected, mounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRestrictOOMScoreAdj(t *testing.T) {
|
|
||||||
current, err := getCurrentOOMScoreAdj()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
got, err := restrictOOMScoreAdj(current - 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, got, current)
|
|
||||||
|
|
||||||
got, err = restrictOOMScoreAdj(current)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, got, current)
|
|
||||||
|
|
||||||
got, err = restrictOOMScoreAdj(current + 1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Equal(t, got, current+1)
|
|
||||||
}
|
|
||||||
|
676
pkg/containerd/opts/spec_unix.go
Normal file
676
pkg/containerd/opts/spec_unix.go
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package opts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/devices"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
osinterface "github.com/containerd/cri/pkg/os"
|
||||||
|
"github.com/containerd/cri/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultSandboxCPUshares is default cpu shares for sandbox container.
|
||||||
|
// TODO(windows): Evaluate whether this can be used for windows sandbox
|
||||||
|
// container cpu shares.
|
||||||
|
DefaultSandboxCPUshares = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithAdditionalGIDs adds any additional groups listed for a particular user in the
|
||||||
|
// /etc/groups file of the image's root filesystem to the OCI spec's additionalGids array.
|
||||||
|
func WithAdditionalGIDs(userstr string) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
gids := s.Process.User.AdditionalGids
|
||||||
|
if err := oci.WithAdditionalGIDs(userstr)(ctx, client, c, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Merge existing gids and new gids.
|
||||||
|
s.Process.User.AdditionalGids = mergeGids(s.Process.User.AdditionalGids, gids)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeGids(gids1, gids2 []uint32) []uint32 {
|
||||||
|
gidsMap := make(map[uint32]struct{})
|
||||||
|
for _, gid1 := range gids1 {
|
||||||
|
gidsMap[gid1] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, gid2 := range gids2 {
|
||||||
|
gidsMap[gid2] = struct{}{}
|
||||||
|
}
|
||||||
|
var gids []uint32
|
||||||
|
for gid := range gidsMap {
|
||||||
|
gids = append(gids, gid)
|
||||||
|
}
|
||||||
|
sort.Slice(gids, func(i, j int) bool { return gids[i] < gids[j] })
|
||||||
|
return gids
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutRunMount removes the `/run` inside the spec
|
||||||
|
func WithoutRunMount(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
var (
|
||||||
|
mounts []runtimespec.Mount
|
||||||
|
current = s.Mounts
|
||||||
|
)
|
||||||
|
for _, m := range current {
|
||||||
|
if filepath.Clean(m.Destination) == "/run" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mounts = append(mounts, m)
|
||||||
|
}
|
||||||
|
s.Mounts = mounts
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutDefaultSecuritySettings removes the default security settings generated on a spec
|
||||||
|
func WithoutDefaultSecuritySettings(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
// Make sure no default seccomp/apparmor is specified
|
||||||
|
s.Process.ApparmorProfile = ""
|
||||||
|
if s.Linux != nil {
|
||||||
|
s.Linux.Seccomp = nil
|
||||||
|
}
|
||||||
|
// Remove default rlimits (See issue #515)
|
||||||
|
s.Process.Rlimits = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMounts sorts and adds runtime and CRI mounts to the spec
|
||||||
|
func WithMounts(osi osinterface.OS, config *runtime.ContainerConfig, extra []*runtime.Mount, mountLabel string) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, _ *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
|
// mergeMounts merge CRI mounts with extra mounts. If a mount destination
|
||||||
|
// is mounted by both a CRI mount and an extra mount, the CRI mount will
|
||||||
|
// be kept.
|
||||||
|
var (
|
||||||
|
criMounts = config.GetMounts()
|
||||||
|
mounts = append([]*runtime.Mount{}, criMounts...)
|
||||||
|
)
|
||||||
|
// Copy all mounts from extra mounts, except for mounts overriden by CRI.
|
||||||
|
for _, e := range extra {
|
||||||
|
found := false
|
||||||
|
for _, c := range criMounts {
|
||||||
|
if filepath.Clean(e.ContainerPath) == filepath.Clean(c.ContainerPath) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
mounts = append(mounts, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ---
|
||||||
|
|
||||||
|
// Sort mounts in number of parts. This ensures that high level mounts don't
|
||||||
|
// shadow other mounts.
|
||||||
|
sort.Sort(orderedMounts(mounts))
|
||||||
|
|
||||||
|
// Mount cgroup into the container as readonly, which inherits docker's behavior.
|
||||||
|
s.Mounts = append(s.Mounts, runtimespec.Mount{
|
||||||
|
Source: "cgroup",
|
||||||
|
Destination: "/sys/fs/cgroup",
|
||||||
|
Type: "cgroup",
|
||||||
|
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Copy all mounts from default mounts, except for
|
||||||
|
// - mounts overriden by supplied mount;
|
||||||
|
// - all mounts under /dev if a supplied /dev is present.
|
||||||
|
mountSet := make(map[string]struct{})
|
||||||
|
for _, m := range mounts {
|
||||||
|
mountSet[filepath.Clean(m.ContainerPath)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMounts := s.Mounts
|
||||||
|
s.Mounts = nil
|
||||||
|
|
||||||
|
for _, m := range defaultMounts {
|
||||||
|
dst := filepath.Clean(m.Destination)
|
||||||
|
if _, ok := mountSet[dst]; ok {
|
||||||
|
// filter out mount overridden by a supplied mount
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, mountDev := mountSet["/dev"]; mountDev && strings.HasPrefix(dst, "/dev/") {
|
||||||
|
// filter out everything under /dev if /dev is a supplied mount
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.Mounts = append(s.Mounts, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mount := range mounts {
|
||||||
|
var (
|
||||||
|
dst = mount.GetContainerPath()
|
||||||
|
src = mount.GetHostPath()
|
||||||
|
)
|
||||||
|
// Create the host path if it doesn't exist.
|
||||||
|
// TODO(random-liu): Add CRI validation test for this case.
|
||||||
|
if _, err := osi.Stat(src); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return errors.Wrapf(err, "failed to stat %q", src)
|
||||||
|
}
|
||||||
|
if err := osi.MkdirAll(src, 0755); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to mkdir %q", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO(random-liu): Add cri-containerd integration test or cri validation test
|
||||||
|
// for this.
|
||||||
|
src, err := osi.ResolveSymbolicLink(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to resolve symlink %q", src)
|
||||||
|
}
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
options := []string{"rbind"}
|
||||||
|
switch mount.GetPropagation() {
|
||||||
|
case runtime.MountPropagation_PROPAGATION_PRIVATE:
|
||||||
|
options = append(options, "rprivate")
|
||||||
|
// Since default root propogation in runc is rprivate ignore
|
||||||
|
// setting the root propagation
|
||||||
|
case runtime.MountPropagation_PROPAGATION_BIDIRECTIONAL:
|
||||||
|
if err := ensureShared(src, osi.(osinterface.UNIX).LookupMount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
options = append(options, "rshared")
|
||||||
|
s.Linux.RootfsPropagation = "rshared"
|
||||||
|
case runtime.MountPropagation_PROPAGATION_HOST_TO_CONTAINER:
|
||||||
|
if err := ensureSharedOrSlave(src, osi.(osinterface.UNIX).LookupMount); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
options = append(options, "rslave")
|
||||||
|
if s.Linux.RootfsPropagation != "rshared" &&
|
||||||
|
s.Linux.RootfsPropagation != "rslave" {
|
||||||
|
s.Linux.RootfsPropagation = "rslave"
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.G(ctx).Warnf("Unknown propagation mode for hostPath %q", mount.HostPath)
|
||||||
|
options = append(options, "rprivate")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(random-liu): we don't change all mounts to `ro` when root filesystem
|
||||||
|
// is readonly. This is different from docker's behavior, but make more sense.
|
||||||
|
if mount.GetReadonly() {
|
||||||
|
options = append(options, "ro")
|
||||||
|
} else {
|
||||||
|
options = append(options, "rw")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mount.GetSelinuxRelabel() {
|
||||||
|
if err := label.Relabel(src, mountLabel, true); err != nil && err != unix.ENOTSUP {
|
||||||
|
return errors.Wrapf(err, "relabel %q with %q failed", src, mountLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Mounts = append(s.Mounts, runtimespec.Mount{
|
||||||
|
Source: src,
|
||||||
|
Destination: dst,
|
||||||
|
Type: "bind",
|
||||||
|
Options: options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure mount point on which path is mounted, is shared.
|
||||||
|
func ensureShared(path string, lookupMount func(string) (mount.Info, error)) error {
|
||||||
|
mountInfo, err := lookupMount(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure source mount point is shared.
|
||||||
|
optsSplit := strings.Split(mountInfo.Optional, " ")
|
||||||
|
for _, opt := range optsSplit {
|
||||||
|
if strings.HasPrefix(opt, "shared:") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Errorf("path %q is mounted on %q but it is not a shared mount", path, mountInfo.Mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure mount point on which path is mounted, is either shared or slave.
|
||||||
|
func ensureSharedOrSlave(path string, lookupMount func(string) (mount.Info, error)) error {
|
||||||
|
mountInfo, err := lookupMount(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Make sure source mount point is shared.
|
||||||
|
optsSplit := strings.Split(mountInfo.Optional, " ")
|
||||||
|
for _, opt := range optsSplit {
|
||||||
|
if strings.HasPrefix(opt, "shared:") {
|
||||||
|
return nil
|
||||||
|
} else if strings.HasPrefix(opt, "master:") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Errorf("path %q is mounted on %q but it is not a shared or slave mount", path, mountInfo.Mountpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPrivilegedDevices allows all host devices inside the container
|
||||||
|
func WithPrivilegedDevices(_ context.Context, _ oci.Client, _ *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &runtimespec.LinuxResources{}
|
||||||
|
}
|
||||||
|
hostDevices, err := devices.HostDevices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, hostDevice := range hostDevices {
|
||||||
|
rd := runtimespec.LinuxDevice{
|
||||||
|
Path: hostDevice.Path,
|
||||||
|
Type: string(hostDevice.Type),
|
||||||
|
Major: hostDevice.Major,
|
||||||
|
Minor: hostDevice.Minor,
|
||||||
|
UID: &hostDevice.Uid,
|
||||||
|
GID: &hostDevice.Gid,
|
||||||
|
}
|
||||||
|
if hostDevice.Major == 0 && hostDevice.Minor == 0 {
|
||||||
|
// Invalid device, most likely a symbolic link, skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addDevice(s, rd)
|
||||||
|
}
|
||||||
|
s.Linux.Resources.Devices = []runtimespec.LinuxDeviceCgroup{
|
||||||
|
{
|
||||||
|
Allow: true,
|
||||||
|
Access: "rwm",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDevice(s *runtimespec.Spec, rd runtimespec.LinuxDevice) {
|
||||||
|
for i, dev := range s.Linux.Devices {
|
||||||
|
if dev.Path == rd.Path {
|
||||||
|
s.Linux.Devices[i] = rd
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Linux.Devices = append(s.Linux.Devices, rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDevices sets the provided devices onto the container spec
|
||||||
|
func WithDevices(osi osinterface.OS, config *runtime.ContainerConfig) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &runtimespec.LinuxResources{}
|
||||||
|
}
|
||||||
|
for _, device := range config.GetDevices() {
|
||||||
|
path, err := osi.ResolveSymbolicLink(device.HostPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dev, err := devices.DeviceFromPath(path, device.Permissions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rd := runtimespec.LinuxDevice{
|
||||||
|
Path: device.ContainerPath,
|
||||||
|
Type: string(dev.Type),
|
||||||
|
Major: dev.Major,
|
||||||
|
Minor: dev.Minor,
|
||||||
|
UID: &dev.Uid,
|
||||||
|
GID: &dev.Gid,
|
||||||
|
}
|
||||||
|
|
||||||
|
addDevice(s, rd)
|
||||||
|
|
||||||
|
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, runtimespec.LinuxDeviceCgroup{
|
||||||
|
Allow: true,
|
||||||
|
Type: string(dev.Type),
|
||||||
|
Major: &dev.Major,
|
||||||
|
Minor: &dev.Minor,
|
||||||
|
Access: dev.Permissions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithCapabilities sets the provided capabilties from the security context
|
||||||
|
func WithCapabilities(sc *runtime.LinuxContainerSecurityContext) oci.SpecOpts {
|
||||||
|
capabilities := sc.GetCapabilities()
|
||||||
|
if capabilities == nil {
|
||||||
|
return nullOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []oci.SpecOpts
|
||||||
|
// Add/drop all capabilities if "all" is specified, so that
|
||||||
|
// following individual add/drop could still work. E.g.
|
||||||
|
// AddCapabilities: []string{"ALL"}, DropCapabilities: []string{"CHOWN"}
|
||||||
|
// will be all capabilities without `CAP_CHOWN`.
|
||||||
|
if util.InStringSlice(capabilities.GetAddCapabilities(), "ALL") {
|
||||||
|
opts = append(opts, oci.WithAllCapabilities)
|
||||||
|
}
|
||||||
|
if util.InStringSlice(capabilities.GetDropCapabilities(), "ALL") {
|
||||||
|
opts = append(opts, oci.WithCapabilities(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var caps []string
|
||||||
|
for _, c := range capabilities.GetAddCapabilities() {
|
||||||
|
if strings.ToUpper(c) == "ALL" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Capabilities in CRI doesn't have `CAP_` prefix, so add it.
|
||||||
|
caps = append(caps, "CAP_"+strings.ToUpper(c))
|
||||||
|
}
|
||||||
|
opts = append(opts, oci.WithAddedCapabilities(caps))
|
||||||
|
|
||||||
|
caps = []string{}
|
||||||
|
for _, c := range capabilities.GetDropCapabilities() {
|
||||||
|
if strings.ToUpper(c) == "ALL" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
caps = append(caps, "CAP_"+strings.ToUpper(c))
|
||||||
|
}
|
||||||
|
opts = append(opts, oci.WithDroppedCapabilities(caps))
|
||||||
|
return oci.Compose(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutAmbientCaps removes the ambient caps from the spec
|
||||||
|
func WithoutAmbientCaps(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
if s.Process.Capabilities == nil {
|
||||||
|
s.Process.Capabilities = &runtimespec.LinuxCapabilities{}
|
||||||
|
}
|
||||||
|
s.Process.Capabilities.Ambient = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDisabledCgroups clears the Cgroups Path from the spec
|
||||||
|
func WithDisabledCgroups(_ context.Context, _ oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
s.Linux.CgroupsPath = ""
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSelinuxLabels sets the mount and process labels
|
||||||
|
func WithSelinuxLabels(process, mount string) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
s.Linux.MountLabel = mount
|
||||||
|
s.Process.SelinuxLabel = process
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResources sets the provided resource restrictions
|
||||||
|
func WithResources(resources *runtime.LinuxContainerResources) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) (err error) {
|
||||||
|
if resources == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &runtimespec.LinuxResources{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources.CPU == nil {
|
||||||
|
s.Linux.Resources.CPU = &runtimespec.LinuxCPU{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources.Memory == nil {
|
||||||
|
s.Linux.Resources.Memory = &runtimespec.LinuxMemory{}
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
p = uint64(resources.GetCpuPeriod())
|
||||||
|
q = resources.GetCpuQuota()
|
||||||
|
shares = uint64(resources.GetCpuShares())
|
||||||
|
limit = resources.GetMemoryLimitInBytes()
|
||||||
|
)
|
||||||
|
|
||||||
|
if p != 0 {
|
||||||
|
s.Linux.Resources.CPU.Period = &p
|
||||||
|
}
|
||||||
|
if q != 0 {
|
||||||
|
s.Linux.Resources.CPU.Quota = &q
|
||||||
|
}
|
||||||
|
if shares != 0 {
|
||||||
|
s.Linux.Resources.CPU.Shares = &shares
|
||||||
|
}
|
||||||
|
if cpus := resources.GetCpusetCpus(); cpus != "" {
|
||||||
|
s.Linux.Resources.CPU.Cpus = cpus
|
||||||
|
}
|
||||||
|
if mems := resources.GetCpusetMems(); mems != "" {
|
||||||
|
s.Linux.Resources.CPU.Mems = resources.GetCpusetMems()
|
||||||
|
}
|
||||||
|
if limit != 0 {
|
||||||
|
s.Linux.Resources.Memory.Limit = &limit
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOOMScoreAdj sets the oom score
|
||||||
|
func WithOOMScoreAdj(config *runtime.ContainerConfig, restrict bool) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
|
||||||
|
resources := config.GetLinux().GetResources()
|
||||||
|
if resources == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
adj := int(resources.GetOomScoreAdj())
|
||||||
|
if restrict {
|
||||||
|
var err error
|
||||||
|
adj, err = restrictOOMScoreAdj(adj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Process.OOMScoreAdj = &adj
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSysctls sets the provided sysctls onto the spec
|
||||||
|
func WithSysctls(sysctls map[string]string) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Linux.Sysctl == nil {
|
||||||
|
s.Linux.Sysctl = make(map[string]string)
|
||||||
|
}
|
||||||
|
for k, v := range sysctls {
|
||||||
|
s.Linux.Sysctl[k] = v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPodOOMScoreAdj sets the oom score for the pod sandbox
|
||||||
|
func WithPodOOMScoreAdj(adj int, restrict bool) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
if restrict {
|
||||||
|
var err error
|
||||||
|
adj, err = restrictOOMScoreAdj(adj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Process.OOMScoreAdj = &adj
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSupplementalGroups sets the supplemental groups for the process
|
||||||
|
func WithSupplementalGroups(groups []int64) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Process == nil {
|
||||||
|
s.Process = &runtimespec.Process{}
|
||||||
|
}
|
||||||
|
var guids []uint32
|
||||||
|
for _, g := range groups {
|
||||||
|
guids = append(guids, uint32(g))
|
||||||
|
}
|
||||||
|
s.Process.User.AdditionalGids = mergeGids(s.Process.User.AdditionalGids, guids)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithPodNamespaces sets the pod namespaces for the container
|
||||||
|
func WithPodNamespaces(config *runtime.LinuxContainerSecurityContext, pid uint32) oci.SpecOpts {
|
||||||
|
namespaces := config.GetNamespaceOptions()
|
||||||
|
|
||||||
|
opts := []oci.SpecOpts{
|
||||||
|
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.NetworkNamespace, Path: GetNetworkNamespace(pid)}),
|
||||||
|
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.IPCNamespace, Path: GetIPCNamespace(pid)}),
|
||||||
|
oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.UTSNamespace, Path: GetUTSNamespace(pid)}),
|
||||||
|
}
|
||||||
|
if namespaces.GetPid() != runtime.NamespaceMode_CONTAINER {
|
||||||
|
opts = append(opts, oci.WithLinuxNamespace(runtimespec.LinuxNamespace{Type: runtimespec.PIDNamespace, Path: GetPIDNamespace(pid)}))
|
||||||
|
}
|
||||||
|
return oci.Compose(opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultSandboxShares sets the default sandbox CPU shares
|
||||||
|
func WithDefaultSandboxShares(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Linux == nil {
|
||||||
|
s.Linux = &runtimespec.Linux{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources == nil {
|
||||||
|
s.Linux.Resources = &runtimespec.LinuxResources{}
|
||||||
|
}
|
||||||
|
if s.Linux.Resources.CPU == nil {
|
||||||
|
s.Linux.Resources.CPU = &runtimespec.LinuxCPU{}
|
||||||
|
}
|
||||||
|
i := uint64(DefaultSandboxCPUshares)
|
||||||
|
s.Linux.Resources.CPU.Shares = &i
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithoutNamespace removes the provided namespace
|
||||||
|
func WithoutNamespace(t runtimespec.LinuxNamespaceType) oci.SpecOpts {
|
||||||
|
return func(ctx context.Context, client oci.Client, c *containers.Container, s *runtimespec.Spec) error {
|
||||||
|
if s.Linux == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var namespaces []runtimespec.LinuxNamespace
|
||||||
|
for i, ns := range s.Linux.Namespaces {
|
||||||
|
if ns.Type != t {
|
||||||
|
namespaces = append(namespaces, s.Linux.Namespaces[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.Linux.Namespaces = namespaces
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nullOpt(_ context.Context, _ oci.Client, _ *containers.Container, _ *runtimespec.Spec) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentOOMScoreAdj() (int, error) {
|
||||||
|
b, err := ioutil.ReadFile("/proc/self/oom_score_adj")
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not get the daemon oom_score_adj")
|
||||||
|
}
|
||||||
|
s := strings.TrimSpace(string(b))
|
||||||
|
i, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.Wrap(err, "could not get the daemon oom_score_adj")
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restrictOOMScoreAdj(preferredOOMScoreAdj int) (int, error) {
|
||||||
|
currentOOMScoreAdj, err := getCurrentOOMScoreAdj()
|
||||||
|
if err != nil {
|
||||||
|
return preferredOOMScoreAdj, err
|
||||||
|
}
|
||||||
|
if preferredOOMScoreAdj < currentOOMScoreAdj {
|
||||||
|
return currentOOMScoreAdj, nil
|
||||||
|
}
|
||||||
|
return preferredOOMScoreAdj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// netNSFormat is the format of network namespace of a process.
|
||||||
|
netNSFormat = "/proc/%v/ns/net"
|
||||||
|
// ipcNSFormat is the format of ipc namespace of a process.
|
||||||
|
ipcNSFormat = "/proc/%v/ns/ipc"
|
||||||
|
// utsNSFormat is the format of uts namespace of a process.
|
||||||
|
utsNSFormat = "/proc/%v/ns/uts"
|
||||||
|
// pidNSFormat is the format of pid namespace of a process.
|
||||||
|
pidNSFormat = "/proc/%v/ns/pid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetNetworkNamespace returns the network namespace of a process.
|
||||||
|
func GetNetworkNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(netNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIPCNamespace returns the ipc namespace of a process.
|
||||||
|
func GetIPCNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(ipcNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUTSNamespace returns the uts namespace of a process.
|
||||||
|
func GetUTSNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(utsNSFormat, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPIDNamespace returns the pid namespace of a process.
|
||||||
|
func GetPIDNamespace(pid uint32) string {
|
||||||
|
return fmt.Sprintf(pidNSFormat, pid)
|
||||||
|
}
|
49
pkg/containerd/opts/spec_unix_test.go
Normal file
49
pkg/containerd/opts/spec_unix_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package opts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeGids(t *testing.T) {
|
||||||
|
gids1 := []uint32{3, 2, 1}
|
||||||
|
gids2 := []uint32{2, 3, 4}
|
||||||
|
assert.Equal(t, []uint32{1, 2, 3, 4}, mergeGids(gids1, gids2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestrictOOMScoreAdj(t *testing.T) {
|
||||||
|
current, err := getCurrentOOMScoreAdj()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got, err := restrictOOMScoreAdj(current - 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, got, current)
|
||||||
|
|
||||||
|
got, err = restrictOOMScoreAdj(current)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, got, current)
|
||||||
|
|
||||||
|
got, err = restrictOOMScoreAdj(current + 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, got, current+1)
|
||||||
|
}
|
@ -69,7 +69,7 @@ func TestSerialWriteCloser(t *testing.T) {
|
|||||||
testData[i] = []byte(repeatNumber(i, dataLen) + "\n")
|
testData[i] = []byte(repeatNumber(i, dataLen) + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := ioutil.TempFile("/tmp", "serial-write-closer")
|
f, err := ioutil.TempFile("", "serial-write-closer")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(f.Name())
|
defer os.RemoveAll(f.Name())
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2018 The Containerd Authors.
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
50
pkg/netns/netns_windows.go
Normal file
50
pkg/netns/netns_windows.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package netns
|
||||||
|
|
||||||
|
// TODO(windows): Implement netns for windows.
|
||||||
|
// NetNS holds network namespace.
|
||||||
|
type NetNS struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNetNS creates a network namespace.
|
||||||
|
func NewNetNS() (*NetNS, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadNetNS loads existing network namespace.
|
||||||
|
func LoadNetNS(path string) *NetNS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes network namepace. Remove is idempotent, meaning it might
|
||||||
|
// be invoked multiple times and provides consistent result.
|
||||||
|
func (n *NetNS) Remove() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closed checks whether the network namespace has been closed.
|
||||||
|
func (n *NetNS) Closed() (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPath returns network namespace path for sandbox container
|
||||||
|
func (n *NetNS) GetPath() string {
|
||||||
|
return ""
|
||||||
|
}
|
40
pkg/os/os.go
40
pkg/os/os.go
@ -22,11 +22,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/containerd/containerd/mount"
|
|
||||||
"github.com/containerd/fifo"
|
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"golang.org/x/net/context"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OS collects system level operations that need to be mocked out
|
// OS collects system level operations that need to be mocked out
|
||||||
@ -34,15 +30,11 @@ import (
|
|||||||
type OS interface {
|
type OS interface {
|
||||||
MkdirAll(path string, perm os.FileMode) error
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
RemoveAll(path string) error
|
RemoveAll(path string) error
|
||||||
OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
|
|
||||||
Stat(name string) (os.FileInfo, error)
|
Stat(name string) (os.FileInfo, error)
|
||||||
ResolveSymbolicLink(name string) (string, error)
|
ResolveSymbolicLink(name string) (string, error)
|
||||||
FollowSymlinkInScope(path, scope string) (string, error)
|
FollowSymlinkInScope(path, scope string) (string, error)
|
||||||
CopyFile(src, dest string, perm os.FileMode) error
|
CopyFile(src, dest string, perm os.FileMode) error
|
||||||
WriteFile(filename string, data []byte, perm os.FileMode) error
|
WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||||
Mount(source string, target string, fstype string, flags uintptr, data string) error
|
|
||||||
Unmount(target string) error
|
|
||||||
LookupMount(path string) (mount.Info, error)
|
|
||||||
Hostname() (string, error)
|
Hostname() (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,11 +51,6 @@ func (RealOS) RemoveAll(path string) error {
|
|||||||
return os.RemoveAll(path)
|
return os.RemoveAll(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFifo will call fifo.OpenFifo to open a fifo.
|
|
||||||
func (RealOS) OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
|
||||||
return fifo.OpenFifo(ctx, fn, flag, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat will call os.Stat to get the status of the given file.
|
// Stat will call os.Stat to get the status of the given file.
|
||||||
func (RealOS) Stat(name string) (os.FileInfo, error) {
|
func (RealOS) Stat(name string) (os.FileInfo, error) {
|
||||||
return os.Stat(name)
|
return os.Stat(name)
|
||||||
@ -109,33 +96,6 @@ func (RealOS) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
|||||||
return ioutil.WriteFile(filename, data, perm)
|
return ioutil.WriteFile(filename, data, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount will call unix.Mount to mount the file.
|
|
||||||
func (RealOS) Mount(source string, target string, fstype string, flags uintptr, data string) error {
|
|
||||||
return unix.Mount(source, target, fstype, flags, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmount will call Unmount to unmount the file.
|
|
||||||
func (RealOS) Unmount(target string) error {
|
|
||||||
return Unmount(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupMount gets mount info of a given path.
|
|
||||||
func (RealOS) LookupMount(path string) (mount.Info, error) {
|
|
||||||
return mount.Lookup(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmount unmounts the target. It does not return an error in case the target is not mounted.
|
|
||||||
// In case the target does not exist, the appropriate error is returned.
|
|
||||||
func Unmount(target string) error {
|
|
||||||
err := unix.Unmount(target, unix.MNT_DETACH)
|
|
||||||
if err == unix.EINVAL {
|
|
||||||
// ignore "not mounted" error
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hostname will call os.Hostname to get the hostname of the host.
|
// Hostname will call os.Hostname to get the hostname of the host.
|
||||||
func (RealOS) Hostname() (string, error) {
|
func (RealOS) Hostname() (string, error) {
|
||||||
return os.Hostname()
|
return os.Hostname()
|
||||||
|
59
pkg/os/os_unix.go
Normal file
59
pkg/os/os_unix.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package os
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/mount"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UNIX collects unix system level operations that need to be
|
||||||
|
// mocked out during tests.
|
||||||
|
type UNIX interface {
|
||||||
|
Mount(source string, target string, fstype string, flags uintptr, data string) error
|
||||||
|
Unmount(target string) error
|
||||||
|
LookupMount(path string) (mount.Info, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount will call unix.Mount to mount the file.
|
||||||
|
func (RealOS) Mount(source string, target string, fstype string, flags uintptr, data string) error {
|
||||||
|
return unix.Mount(source, target, fstype, flags, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount will call Unmount to unmount the file.
|
||||||
|
func (RealOS) Unmount(target string) error {
|
||||||
|
return Unmount(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupMount gets mount info of a given path.
|
||||||
|
func (RealOS) LookupMount(path string) (mount.Info, error) {
|
||||||
|
return mount.Lookup(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmount unmounts the target. It does not return an error in case the target is not mounted.
|
||||||
|
// In case the target does not exist, the appropriate error is returned.
|
||||||
|
func Unmount(target string) error {
|
||||||
|
err := unix.Unmount(target, unix.MNT_DETACH)
|
||||||
|
if err == unix.EINVAL {
|
||||||
|
// ignore "not mounted" error
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
@ -17,12 +17,10 @@ limitations under the License.
|
|||||||
package testing
|
package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
containerdmount "github.com/containerd/containerd/mount"
|
containerdmount "github.com/containerd/containerd/mount"
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
osInterface "github.com/containerd/cri/pkg/os"
|
osInterface "github.com/containerd/cri/pkg/os"
|
||||||
)
|
)
|
||||||
@ -42,7 +40,6 @@ type FakeOS struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
MkdirAllFn func(string, os.FileMode) error
|
MkdirAllFn func(string, os.FileMode) error
|
||||||
RemoveAllFn func(string) error
|
RemoveAllFn func(string) error
|
||||||
OpenFifoFn func(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error)
|
|
||||||
StatFn func(string) (os.FileInfo, error)
|
StatFn func(string) (os.FileInfo, error)
|
||||||
ResolveSymbolicLinkFn func(string) (string, error)
|
ResolveSymbolicLinkFn func(string) (string, error)
|
||||||
FollowSymlinkInScopeFn func(string, string) (string, error)
|
FollowSymlinkInScopeFn func(string, string) (string, error)
|
||||||
@ -139,19 +136,6 @@ func (f *FakeOS) RemoveAll(path string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFifo is a fake call that invokes OpenFifoFn or just returns nil.
|
|
||||||
func (f *FakeOS) OpenFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
|
||||||
f.appendCalls("OpenFifo", ctx, fn, flag, perm)
|
|
||||||
if err := f.getError("OpenFifo"); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.OpenFifoFn != nil {
|
|
||||||
return f.OpenFifoFn(ctx, fn, flag, perm)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat is a fake call that invokes StatFn or just return nil.
|
// Stat is a fake call that invokes StatFn or just return nil.
|
||||||
func (f *FakeOS) Stat(name string) (os.FileInfo, error) {
|
func (f *FakeOS) Stat(name string) (os.FileInfo, error) {
|
||||||
f.appendCalls("Stat", name)
|
f.appendCalls("Stat", name)
|
||||||
|
23
pkg/os/testing/fake_os_unix.go
Normal file
23
pkg/os/testing/fake_os_unix.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package testing
|
||||||
|
|
||||||
|
import osInterface "github.com/containerd/cri/pkg/os"
|
||||||
|
|
||||||
|
var _ osInterface.UNIX = &FakeOS{}
|
@ -18,23 +18,12 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/contrib/apparmor"
|
|
||||||
"github.com/containerd/containerd/contrib/seccomp"
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/containerd/cri/pkg/annotations"
|
|
||||||
"github.com/containerd/cri/pkg/config"
|
|
||||||
customopts "github.com/containerd/cri/pkg/containerd/opts"
|
|
||||||
ctrdutil "github.com/containerd/cri/pkg/containerd/util"
|
|
||||||
cio "github.com/containerd/cri/pkg/server/io"
|
|
||||||
containerstore "github.com/containerd/cri/pkg/store/container"
|
|
||||||
"github.com/containerd/cri/pkg/util"
|
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@ -42,21 +31,12 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
customopts "github.com/containerd/cri/pkg/containerd/opts"
|
||||||
// profileNamePrefix is the prefix for loading profiles on a localhost. Eg. AppArmor localhost/profileName.
|
ctrdutil "github.com/containerd/cri/pkg/containerd/util"
|
||||||
profileNamePrefix = "localhost/" // TODO (mikebrow): get localhost/ & runtime/default from CRI kubernetes/kubernetes#51747
|
cio "github.com/containerd/cri/pkg/server/io"
|
||||||
// runtimeDefault indicates that we should use or create a runtime default profile.
|
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||||
runtimeDefault = "runtime/default"
|
"github.com/containerd/cri/pkg/util"
|
||||||
// dockerDefault indicates that we should use or create a docker default profile.
|
|
||||||
dockerDefault = "docker/default"
|
|
||||||
// appArmorDefaultProfileName is name to use when creating a default apparmor profile.
|
|
||||||
appArmorDefaultProfileName = "cri-containerd.apparmor.d"
|
|
||||||
// unconfinedProfile is a string indicating one should run a pod/containerd without a security profile
|
|
||||||
unconfinedProfile = "unconfined"
|
|
||||||
// seccompDefaultProfile is the default seccomp profile.
|
|
||||||
seccompDefaultProfile = dockerDefault
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -156,10 +136,10 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Create container volumes mounts.
|
// Create container volumes mounts.
|
||||||
volumeMounts := c.generateVolumeMounts(containerRootDir, config.GetMounts(), &image.ImageSpec.Config)
|
volumeMounts := c.volumeMounts(containerRootDir, config.GetMounts(), &image.ImageSpec.Config)
|
||||||
|
|
||||||
// Generate container runtime spec.
|
// Generate container mounts.
|
||||||
mounts := c.generateContainerMounts(sandboxID, config)
|
mounts := c.containerMounts(sandboxID, config)
|
||||||
|
|
||||||
ociRuntime, err := c.getSandboxRuntime(sandboxConfig, sandbox.Metadata.RuntimeHandler)
|
ociRuntime, err := c.getSandboxRuntime(sandboxConfig, sandbox.Metadata.RuntimeHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -167,7 +147,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
}
|
}
|
||||||
log.G(ctx).Debugf("Use OCI runtime %+v for sandbox %q and container %q", ociRuntime, sandboxID, id)
|
log.G(ctx).Debugf("Use OCI runtime %+v for sandbox %q and container %q", ociRuntime, sandboxID, id)
|
||||||
|
|
||||||
spec, err := c.generateContainerSpec(id, sandboxID, sandboxPid, config, sandboxConfig,
|
spec, err := c.containerSpec(id, sandboxID, sandboxPid, sandbox.NetNSPath, config, sandboxConfig,
|
||||||
&image.ImageSpec.Config, append(mounts, volumeMounts...), ociRuntime)
|
&image.ImageSpec.Config, append(mounts, volumeMounts...), ociRuntime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to generate container %q spec", id)
|
return nil, errors.Wrapf(err, "failed to generate container %q spec", id)
|
||||||
@ -185,7 +165,6 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
// rootfs readonly (requested by spec.Root.Readonly).
|
// rootfs readonly (requested by spec.Root.Readonly).
|
||||||
customopts.WithNewSnapshot(id, containerdImage),
|
customopts.WithNewSnapshot(id, containerdImage),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(volumeMounts) > 0 {
|
if len(volumeMounts) > 0 {
|
||||||
mountMap := make(map[string]string)
|
mountMap := make(map[string]string)
|
||||||
for _, v := range volumeMounts {
|
for _, v := range volumeMounts {
|
||||||
@ -219,58 +198,11 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var specOpts []oci.SpecOpts
|
specOpts, err := c.containerSpecOpts(config, &image.ImageSpec.Config)
|
||||||
securityContext := config.GetLinux().GetSecurityContext()
|
|
||||||
// Set container username. This could only be done by containerd, because it needs
|
|
||||||
// access to the container rootfs. Pass user name to containerd, and let it overwrite
|
|
||||||
// the spec for us.
|
|
||||||
userstr, err := generateUserString(
|
|
||||||
securityContext.GetRunAsUsername(),
|
|
||||||
securityContext.GetRunAsUser(),
|
|
||||||
securityContext.GetRunAsGroup())
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to generate user string")
|
return nil, errors.Wrap(err, "")
|
||||||
}
|
|
||||||
if userstr == "" {
|
|
||||||
// Lastly, since no user override was passed via CRI try to set via OCI
|
|
||||||
// Image
|
|
||||||
userstr = image.ImageSpec.Config.User
|
|
||||||
}
|
|
||||||
if userstr != "" {
|
|
||||||
specOpts = append(specOpts, oci.WithUser(userstr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if securityContext.GetRunAsUsername() != "" {
|
|
||||||
userstr = securityContext.GetRunAsUsername()
|
|
||||||
} else {
|
|
||||||
// Even if RunAsUser is not set, we still call `GetValue` to get uid 0.
|
|
||||||
// Because it is still useful to get additional gids for uid 0.
|
|
||||||
userstr = strconv.FormatInt(securityContext.GetRunAsUser().GetValue(), 10)
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, customopts.WithAdditionalGIDs(userstr))
|
|
||||||
|
|
||||||
apparmorSpecOpts, err := generateApparmorSpecOpts(
|
|
||||||
securityContext.GetApparmorProfile(),
|
|
||||||
securityContext.GetPrivileged(),
|
|
||||||
c.apparmorEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to generate apparmor spec opts")
|
|
||||||
}
|
|
||||||
if apparmorSpecOpts != nil {
|
|
||||||
specOpts = append(specOpts, apparmorSpecOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
seccompSpecOpts, err := generateSeccompSpecOpts(
|
|
||||||
securityContext.GetSeccompProfilePath(),
|
|
||||||
securityContext.GetPrivileged(),
|
|
||||||
c.seccompEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to generate seccomp spec opts")
|
|
||||||
}
|
|
||||||
if seccompSpecOpts != nil {
|
|
||||||
specOpts = append(specOpts, seccompSpecOpts)
|
|
||||||
}
|
|
||||||
containerLabels := buildLabels(config.Labels, containerKindContainer)
|
containerLabels := buildLabels(config.Labels, containerKindContainer)
|
||||||
|
|
||||||
runtimeOptions, err := getRuntimeOptions(sandboxInfo)
|
runtimeOptions, err := getRuntimeOptions(sandboxInfo)
|
||||||
@ -322,128 +254,10 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
return &runtime.CreateContainerResponse{ContainerId: id}, nil
|
return &runtime.CreateContainerResponse{ContainerId: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *criService) generateContainerSpec(id string, sandboxID string, sandboxPid uint32, config *runtime.ContainerConfig,
|
// volumeMounts sets up image volumes for container. Rely on the removal of container
|
||||||
sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig, extraMounts []*runtime.Mount,
|
|
||||||
ociRuntime config.Runtime) (*runtimespec.Spec, error) {
|
|
||||||
|
|
||||||
specOpts := []oci.SpecOpts{
|
|
||||||
customopts.WithoutRunMount,
|
|
||||||
customopts.WithoutDefaultSecuritySettings,
|
|
||||||
customopts.WithRelativeRoot(relativeRootfsPath),
|
|
||||||
customopts.WithProcessArgs(config, imageConfig),
|
|
||||||
// this will be set based on the security context below
|
|
||||||
oci.WithNewPrivileges,
|
|
||||||
}
|
|
||||||
if config.GetWorkingDir() != "" {
|
|
||||||
specOpts = append(specOpts, oci.WithProcessCwd(config.GetWorkingDir()))
|
|
||||||
} else if imageConfig.WorkingDir != "" {
|
|
||||||
specOpts = append(specOpts, oci.WithProcessCwd(imageConfig.WorkingDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.GetTty() {
|
|
||||||
specOpts = append(specOpts, oci.WithTTY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add HOSTNAME env.
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
hostname = sandboxConfig.GetHostname()
|
|
||||||
)
|
|
||||||
if hostname == "" {
|
|
||||||
if hostname, err = c.os.Hostname(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, oci.WithEnv([]string{hostnameEnv + "=" + hostname}))
|
|
||||||
|
|
||||||
// Apply envs from image config first, so that envs from container config
|
|
||||||
// can override them.
|
|
||||||
env := imageConfig.Env
|
|
||||||
for _, e := range config.GetEnvs() {
|
|
||||||
env = append(env, e.GetKey()+"="+e.GetValue())
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, oci.WithEnv(env))
|
|
||||||
|
|
||||||
securityContext := config.GetLinux().GetSecurityContext()
|
|
||||||
selinuxOpt := securityContext.GetSelinuxOptions()
|
|
||||||
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
|
|
||||||
|
|
||||||
if !c.config.DisableProcMount {
|
|
||||||
// Apply masked paths if specified.
|
|
||||||
// If the container is privileged, this will be cleared later on.
|
|
||||||
specOpts = append(specOpts, oci.WithMaskedPaths(securityContext.GetMaskedPaths()))
|
|
||||||
|
|
||||||
// Apply readonly paths if specified.
|
|
||||||
// If the container is privileged, this will be cleared later on.
|
|
||||||
specOpts = append(specOpts, oci.WithReadonlyPaths(securityContext.GetReadonlyPaths()))
|
|
||||||
}
|
|
||||||
|
|
||||||
if securityContext.GetPrivileged() {
|
|
||||||
if !sandboxConfig.GetLinux().GetSecurityContext().GetPrivileged() {
|
|
||||||
return nil, errors.New("no privileged container allowed in sandbox")
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, oci.WithPrivileged)
|
|
||||||
if !ociRuntime.PrivilegedWithoutHostDevices {
|
|
||||||
specOpts = append(specOpts, customopts.WithPrivilegedDevices)
|
|
||||||
}
|
|
||||||
} else { // not privileged
|
|
||||||
specOpts = append(specOpts, customopts.WithDevices(c.os, config), customopts.WithCapabilities(securityContext))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all ambient capabilities. The implication of non-root + caps
|
|
||||||
// is not clearly defined in Kubernetes.
|
|
||||||
// See https://github.com/kubernetes/kubernetes/issues/56374
|
|
||||||
// Keep docker's behavior for now.
|
|
||||||
specOpts = append(specOpts,
|
|
||||||
customopts.WithoutAmbientCaps,
|
|
||||||
customopts.WithSelinuxLabels(processLabel, mountLabel),
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Figure out whether we should set no new privilege for sandbox container by default
|
|
||||||
if securityContext.GetNoNewPrivs() {
|
|
||||||
specOpts = append(specOpts, oci.WithNoNewPrivileges)
|
|
||||||
}
|
|
||||||
// TODO(random-liu): [P1] Set selinux options (privileged or not).
|
|
||||||
if securityContext.GetReadonlyRootfs() {
|
|
||||||
specOpts = append(specOpts, oci.WithRootFSReadonly())
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.config.DisableCgroup {
|
|
||||||
specOpts = append(specOpts, customopts.WithDisabledCgroups)
|
|
||||||
} else {
|
|
||||||
specOpts = append(specOpts, customopts.WithResources(config.GetLinux().GetResources()))
|
|
||||||
if sandboxConfig.GetLinux().GetCgroupParent() != "" {
|
|
||||||
cgroupsPath := getCgroupsPath(sandboxConfig.GetLinux().GetCgroupParent(), id)
|
|
||||||
specOpts = append(specOpts, oci.WithCgroup(cgroupsPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supplementalGroups := securityContext.GetSupplementalGroups()
|
|
||||||
|
|
||||||
for pKey, pValue := range getPassthroughAnnotations(sandboxConfig.Annotations,
|
|
||||||
ociRuntime.PodAnnotations) {
|
|
||||||
specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
specOpts = append(specOpts,
|
|
||||||
customopts.WithOOMScoreAdj(config, c.config.RestrictOOMScoreAdj),
|
|
||||||
customopts.WithPodNamespaces(securityContext, sandboxPid),
|
|
||||||
customopts.WithSupplementalGroups(supplementalGroups),
|
|
||||||
customopts.WithAnnotation(annotations.ContainerType, annotations.ContainerTypeContainer),
|
|
||||||
customopts.WithAnnotation(annotations.SandboxID, sandboxID),
|
|
||||||
)
|
|
||||||
|
|
||||||
return runtimeSpec(id, specOpts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateVolumeMounts sets up image volumes for container. Rely on the removal of container
|
|
||||||
// root directory to do cleanup. Note that image volume will be skipped, if there is criMounts
|
// root directory to do cleanup. Note that image volume will be skipped, if there is criMounts
|
||||||
// specified with the same destination.
|
// specified with the same destination.
|
||||||
func (c *criService) generateVolumeMounts(containerRootDir string, criMounts []*runtime.Mount, config *imagespec.ImageConfig) []*runtime.Mount {
|
func (c *criService) volumeMounts(containerRootDir string, criMounts []*runtime.Mount, config *imagespec.ImageConfig) []*runtime.Mount {
|
||||||
if len(config.Volumes) == 0 {
|
if len(config.Volumes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -469,61 +283,9 @@ func (c *criService) generateVolumeMounts(containerRootDir string, criMounts []*
|
|||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateContainerMounts sets up necessary container mounts including /dev/shm, /etc/hosts
|
|
||||||
// and /etc/resolv.conf.
|
|
||||||
func (c *criService) generateContainerMounts(sandboxID string, config *runtime.ContainerConfig) []*runtime.Mount {
|
|
||||||
var mounts []*runtime.Mount
|
|
||||||
securityContext := config.GetLinux().GetSecurityContext()
|
|
||||||
if !isInCRIMounts(etcHostname, config.GetMounts()) {
|
|
||||||
// /etc/hostname is added since 1.1.6, 1.2.4 and 1.3.
|
|
||||||
// For in-place upgrade, the old sandbox doesn't have the hostname file,
|
|
||||||
// do not mount this in that case.
|
|
||||||
// TODO(random-liu): Remove the check and always mount this when
|
|
||||||
// containerd 1.1 and 1.2 are deprecated.
|
|
||||||
hostpath := c.getSandboxHostname(sandboxID)
|
|
||||||
if _, err := c.os.Stat(hostpath); err == nil {
|
|
||||||
mounts = append(mounts, &runtime.Mount{
|
|
||||||
ContainerPath: etcHostname,
|
|
||||||
HostPath: hostpath,
|
|
||||||
Readonly: securityContext.GetReadonlyRootfs(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isInCRIMounts(etcHosts, config.GetMounts()) {
|
|
||||||
mounts = append(mounts, &runtime.Mount{
|
|
||||||
ContainerPath: etcHosts,
|
|
||||||
HostPath: c.getSandboxHosts(sandboxID),
|
|
||||||
Readonly: securityContext.GetReadonlyRootfs(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount sandbox resolv.config.
|
|
||||||
// TODO: Need to figure out whether we should always mount it as read-only
|
|
||||||
if !isInCRIMounts(resolvConfPath, config.GetMounts()) {
|
|
||||||
mounts = append(mounts, &runtime.Mount{
|
|
||||||
ContainerPath: resolvConfPath,
|
|
||||||
HostPath: c.getResolvPath(sandboxID),
|
|
||||||
Readonly: securityContext.GetReadonlyRootfs(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isInCRIMounts(devShm, config.GetMounts()) {
|
|
||||||
sandboxDevShm := c.getSandboxDevShm(sandboxID)
|
|
||||||
if securityContext.GetNamespaceOptions().GetIpc() == runtime.NamespaceMode_NODE {
|
|
||||||
sandboxDevShm = devShm
|
|
||||||
}
|
|
||||||
mounts = append(mounts, &runtime.Mount{
|
|
||||||
ContainerPath: devShm,
|
|
||||||
HostPath: sandboxDevShm,
|
|
||||||
Readonly: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return mounts
|
|
||||||
}
|
|
||||||
|
|
||||||
// runtimeSpec returns a default runtime spec used in cri-containerd.
|
// runtimeSpec returns a default runtime spec used in cri-containerd.
|
||||||
func runtimeSpec(id string, opts ...oci.SpecOpts) (*runtimespec.Spec, error) {
|
// TODO(windows): Remove nolint after windows starts using this helper.
|
||||||
|
func runtimeSpec(id string, opts ...oci.SpecOpts) (*runtimespec.Spec, error) { // nolint: deadcode, unused
|
||||||
// GenerateSpec needs namespace.
|
// GenerateSpec needs namespace.
|
||||||
ctx := ctrdutil.NamespacedContext()
|
ctx := ctrdutil.NamespacedContext()
|
||||||
spec, err := oci.GenerateSpec(ctx, nil, &containers.Container{ID: id}, opts...)
|
spec, err := oci.GenerateSpec(ctx, nil, &containers.Container{ID: id}, opts...)
|
||||||
@ -532,105 +294,3 @@ func runtimeSpec(id string, opts ...oci.SpecOpts) (*runtimespec.Spec, error) {
|
|||||||
}
|
}
|
||||||
return spec, nil
|
return spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateSeccompSpecOpts generates containerd SpecOpts for seccomp.
|
|
||||||
func generateSeccompSpecOpts(seccompProf string, privileged, seccompEnabled bool) (oci.SpecOpts, error) {
|
|
||||||
if privileged {
|
|
||||||
// Do not set seccomp profile when container is privileged
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Set seccomp profile
|
|
||||||
if seccompProf == runtimeDefault || seccompProf == dockerDefault {
|
|
||||||
// use correct default profile (Eg. if not configured otherwise, the default is docker/default)
|
|
||||||
seccompProf = seccompDefaultProfile
|
|
||||||
}
|
|
||||||
if !seccompEnabled {
|
|
||||||
if seccompProf != "" && seccompProf != unconfinedProfile {
|
|
||||||
return nil, errors.New("seccomp is not supported")
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
switch seccompProf {
|
|
||||||
case "", unconfinedProfile:
|
|
||||||
// Do not set seccomp profile.
|
|
||||||
return nil, nil
|
|
||||||
case dockerDefault:
|
|
||||||
// Note: WithDefaultProfile specOpts must be added after capabilities
|
|
||||||
return seccomp.WithDefaultProfile(), nil
|
|
||||||
default:
|
|
||||||
// Require and Trim default profile name prefix
|
|
||||||
if !strings.HasPrefix(seccompProf, profileNamePrefix) {
|
|
||||||
return nil, errors.Errorf("invalid seccomp profile %q", seccompProf)
|
|
||||||
}
|
|
||||||
return seccomp.WithProfile(strings.TrimPrefix(seccompProf, profileNamePrefix)), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateApparmorSpecOpts generates containerd SpecOpts for apparmor.
|
|
||||||
func generateApparmorSpecOpts(apparmorProf string, privileged, apparmorEnabled bool) (oci.SpecOpts, error) {
|
|
||||||
if !apparmorEnabled {
|
|
||||||
// Should fail loudly if user try to specify apparmor profile
|
|
||||||
// but we don't support it.
|
|
||||||
if apparmorProf != "" && apparmorProf != unconfinedProfile {
|
|
||||||
return nil, errors.New("apparmor is not supported")
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
switch apparmorProf {
|
|
||||||
// Based on kubernetes#51746, default apparmor profile should be applied
|
|
||||||
// for when apparmor is not specified.
|
|
||||||
case runtimeDefault, "":
|
|
||||||
if privileged {
|
|
||||||
// Do not set apparmor profile when container is privileged
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// TODO (mikebrow): delete created apparmor default profile
|
|
||||||
return apparmor.WithDefaultProfile(appArmorDefaultProfileName), nil
|
|
||||||
case unconfinedProfile:
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
// Require and Trim default profile name prefix
|
|
||||||
if !strings.HasPrefix(apparmorProf, profileNamePrefix) {
|
|
||||||
return nil, errors.Errorf("invalid apparmor profile %q", apparmorProf)
|
|
||||||
}
|
|
||||||
return apparmor.WithProfile(strings.TrimPrefix(apparmorProf, profileNamePrefix)), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateUserString generates valid user string based on OCI Image Spec
|
|
||||||
// v1.0.0.
|
|
||||||
//
|
|
||||||
// CRI defines that the following combinations are valid:
|
|
||||||
//
|
|
||||||
// (none) -> ""
|
|
||||||
// username -> username
|
|
||||||
// username, uid -> username
|
|
||||||
// username, uid, gid -> username:gid
|
|
||||||
// username, gid -> username:gid
|
|
||||||
// uid -> uid
|
|
||||||
// uid, gid -> uid:gid
|
|
||||||
// gid -> error
|
|
||||||
//
|
|
||||||
// TODO(random-liu): Add group name support in CRI.
|
|
||||||
func generateUserString(username string, uid, gid *runtime.Int64Value) (string, error) {
|
|
||||||
var userstr, groupstr string
|
|
||||||
if uid != nil {
|
|
||||||
userstr = strconv.FormatInt(uid.GetValue(), 10)
|
|
||||||
}
|
|
||||||
if username != "" {
|
|
||||||
userstr = username
|
|
||||||
}
|
|
||||||
if gid != nil {
|
|
||||||
groupstr = strconv.FormatInt(gid.GetValue(), 10)
|
|
||||||
}
|
|
||||||
if userstr == "" {
|
|
||||||
if groupstr != "" {
|
|
||||||
return "", errors.Errorf("user group %q is specified without user", groupstr)
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if groupstr != "" {
|
|
||||||
userstr = userstr + ":" + groupstr
|
|
||||||
}
|
|
||||||
return userstr, nil
|
|
||||||
}
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
379
pkg/server/container_create_unix.go
Normal file
379
pkg/server/container_create_unix.go
Normal file
@ -0,0 +1,379 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/contrib/apparmor"
|
||||||
|
"github.com/containerd/containerd/contrib/seccomp"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/annotations"
|
||||||
|
"github.com/containerd/cri/pkg/config"
|
||||||
|
customopts "github.com/containerd/cri/pkg/containerd/opts"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// profileNamePrefix is the prefix for loading profiles on a localhost. Eg. AppArmor localhost/profileName.
|
||||||
|
profileNamePrefix = "localhost/" // TODO (mikebrow): get localhost/ & runtime/default from CRI kubernetes/kubernetes#51747
|
||||||
|
// runtimeDefault indicates that we should use or create a runtime default profile.
|
||||||
|
runtimeDefault = "runtime/default"
|
||||||
|
// dockerDefault indicates that we should use or create a docker default profile.
|
||||||
|
dockerDefault = "docker/default"
|
||||||
|
// appArmorDefaultProfileName is name to use when creating a default apparmor profile.
|
||||||
|
appArmorDefaultProfileName = "cri-containerd.apparmor.d"
|
||||||
|
// unconfinedProfile is a string indicating one should run a pod/containerd without a security profile
|
||||||
|
unconfinedProfile = "unconfined"
|
||||||
|
// seccompDefaultProfile is the default seccomp profile.
|
||||||
|
seccompDefaultProfile = dockerDefault
|
||||||
|
)
|
||||||
|
|
||||||
|
// containerMounts sets up necessary container system file mounts
|
||||||
|
// including /dev/shm, /etc/hosts and /etc/resolv.conf.
|
||||||
|
func (c *criService) containerMounts(sandboxID string, config *runtime.ContainerConfig) []*runtime.Mount {
|
||||||
|
var mounts []*runtime.Mount
|
||||||
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
if !isInCRIMounts(etcHostname, config.GetMounts()) {
|
||||||
|
// /etc/hostname is added since 1.1.6, 1.2.4 and 1.3.
|
||||||
|
// For in-place upgrade, the old sandbox doesn't have the hostname file,
|
||||||
|
// do not mount this in that case.
|
||||||
|
// TODO(random-liu): Remove the check and always mount this when
|
||||||
|
// containerd 1.1 and 1.2 are deprecated.
|
||||||
|
hostpath := c.getSandboxHostname(sandboxID)
|
||||||
|
if _, err := c.os.Stat(hostpath); err == nil {
|
||||||
|
mounts = append(mounts, &runtime.Mount{
|
||||||
|
ContainerPath: etcHostname,
|
||||||
|
HostPath: hostpath,
|
||||||
|
Readonly: securityContext.GetReadonlyRootfs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isInCRIMounts(etcHosts, config.GetMounts()) {
|
||||||
|
mounts = append(mounts, &runtime.Mount{
|
||||||
|
ContainerPath: etcHosts,
|
||||||
|
HostPath: c.getSandboxHosts(sandboxID),
|
||||||
|
Readonly: securityContext.GetReadonlyRootfs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount sandbox resolv.config.
|
||||||
|
// TODO: Need to figure out whether we should always mount it as read-only
|
||||||
|
if !isInCRIMounts(resolvConfPath, config.GetMounts()) {
|
||||||
|
mounts = append(mounts, &runtime.Mount{
|
||||||
|
ContainerPath: resolvConfPath,
|
||||||
|
HostPath: c.getResolvPath(sandboxID),
|
||||||
|
Readonly: securityContext.GetReadonlyRootfs(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isInCRIMounts(devShm, config.GetMounts()) {
|
||||||
|
sandboxDevShm := c.getSandboxDevShm(sandboxID)
|
||||||
|
if securityContext.GetNamespaceOptions().GetIpc() == runtime.NamespaceMode_NODE {
|
||||||
|
sandboxDevShm = devShm
|
||||||
|
}
|
||||||
|
mounts = append(mounts, &runtime.Mount{
|
||||||
|
ContainerPath: devShm,
|
||||||
|
HostPath: sandboxDevShm,
|
||||||
|
Readonly: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return mounts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint32, netNSPath string,
|
||||||
|
config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig,
|
||||||
|
extraMounts []*runtime.Mount, ociRuntime config.Runtime) (*runtimespec.Spec, error) {
|
||||||
|
|
||||||
|
specOpts := []oci.SpecOpts{
|
||||||
|
customopts.WithoutRunMount,
|
||||||
|
customopts.WithoutDefaultSecuritySettings,
|
||||||
|
customopts.WithRelativeRoot(relativeRootfsPath),
|
||||||
|
customopts.WithProcessArgs(config, imageConfig),
|
||||||
|
// this will be set based on the security context below
|
||||||
|
oci.WithNewPrivileges,
|
||||||
|
}
|
||||||
|
if config.GetWorkingDir() != "" {
|
||||||
|
specOpts = append(specOpts, oci.WithProcessCwd(config.GetWorkingDir()))
|
||||||
|
} else if imageConfig.WorkingDir != "" {
|
||||||
|
specOpts = append(specOpts, oci.WithProcessCwd(imageConfig.WorkingDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.GetTty() {
|
||||||
|
specOpts = append(specOpts, oci.WithTTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add HOSTNAME env.
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
hostname = sandboxConfig.GetHostname()
|
||||||
|
)
|
||||||
|
if hostname == "" {
|
||||||
|
if hostname, err = c.os.Hostname(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, oci.WithEnv([]string{hostnameEnv + "=" + hostname}))
|
||||||
|
|
||||||
|
// Apply envs from image config first, so that envs from container config
|
||||||
|
// can override them.
|
||||||
|
env := imageConfig.Env
|
||||||
|
for _, e := range config.GetEnvs() {
|
||||||
|
env = append(env, e.GetKey()+"="+e.GetValue())
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, oci.WithEnv(env))
|
||||||
|
|
||||||
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
selinuxOpt := securityContext.GetSelinuxOptions()
|
||||||
|
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, customopts.WithMounts(c.os, config, extraMounts, mountLabel))
|
||||||
|
|
||||||
|
if !c.config.DisableProcMount {
|
||||||
|
// Apply masked paths if specified.
|
||||||
|
// If the container is privileged, this will be cleared later on.
|
||||||
|
specOpts = append(specOpts, oci.WithMaskedPaths(securityContext.GetMaskedPaths()))
|
||||||
|
|
||||||
|
// Apply readonly paths if specified.
|
||||||
|
// If the container is privileged, this will be cleared later on.
|
||||||
|
specOpts = append(specOpts, oci.WithReadonlyPaths(securityContext.GetReadonlyPaths()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if securityContext.GetPrivileged() {
|
||||||
|
if !sandboxConfig.GetLinux().GetSecurityContext().GetPrivileged() {
|
||||||
|
return nil, errors.New("no privileged container allowed in sandbox")
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, oci.WithPrivileged)
|
||||||
|
if !ociRuntime.PrivilegedWithoutHostDevices {
|
||||||
|
specOpts = append(specOpts, customopts.WithPrivilegedDevices)
|
||||||
|
}
|
||||||
|
} else { // not privileged
|
||||||
|
specOpts = append(specOpts, customopts.WithDevices(c.os, config), customopts.WithCapabilities(securityContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all ambient capabilities. The implication of non-root + caps
|
||||||
|
// is not clearly defined in Kubernetes.
|
||||||
|
// See https://github.com/kubernetes/kubernetes/issues/56374
|
||||||
|
// Keep docker's behavior for now.
|
||||||
|
specOpts = append(specOpts,
|
||||||
|
customopts.WithoutAmbientCaps,
|
||||||
|
customopts.WithSelinuxLabels(processLabel, mountLabel),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Figure out whether we should set no new privilege for sandbox container by default
|
||||||
|
if securityContext.GetNoNewPrivs() {
|
||||||
|
specOpts = append(specOpts, oci.WithNoNewPrivileges)
|
||||||
|
}
|
||||||
|
// TODO(random-liu): [P1] Set selinux options (privileged or not).
|
||||||
|
if securityContext.GetReadonlyRootfs() {
|
||||||
|
specOpts = append(specOpts, oci.WithRootFSReadonly())
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.DisableCgroup {
|
||||||
|
specOpts = append(specOpts, customopts.WithDisabledCgroups)
|
||||||
|
} else {
|
||||||
|
specOpts = append(specOpts, customopts.WithResources(config.GetLinux().GetResources()))
|
||||||
|
if sandboxConfig.GetLinux().GetCgroupParent() != "" {
|
||||||
|
cgroupsPath := getCgroupsPath(sandboxConfig.GetLinux().GetCgroupParent(), id)
|
||||||
|
specOpts = append(specOpts, oci.WithCgroup(cgroupsPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
supplementalGroups := securityContext.GetSupplementalGroups()
|
||||||
|
|
||||||
|
for pKey, pValue := range getPassthroughAnnotations(sandboxConfig.Annotations,
|
||||||
|
ociRuntime.PodAnnotations) {
|
||||||
|
specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
specOpts = append(specOpts,
|
||||||
|
customopts.WithOOMScoreAdj(config, c.config.RestrictOOMScoreAdj),
|
||||||
|
customopts.WithPodNamespaces(securityContext, sandboxPid),
|
||||||
|
customopts.WithSupplementalGroups(supplementalGroups),
|
||||||
|
customopts.WithAnnotation(annotations.ContainerType, annotations.ContainerTypeContainer),
|
||||||
|
customopts.WithAnnotation(annotations.SandboxID, sandboxID),
|
||||||
|
)
|
||||||
|
|
||||||
|
return runtimeSpec(id, specOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criService) containerSpecOpts(config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) ([]oci.SpecOpts, error) {
|
||||||
|
var specOpts []oci.SpecOpts
|
||||||
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
// Set container username. This could only be done by containerd, because it needs
|
||||||
|
// access to the container rootfs. Pass user name to containerd, and let it overwrite
|
||||||
|
// the spec for us.
|
||||||
|
userstr, err := generateUserString(
|
||||||
|
securityContext.GetRunAsUsername(),
|
||||||
|
securityContext.GetRunAsUser(),
|
||||||
|
securityContext.GetRunAsGroup())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate user string")
|
||||||
|
}
|
||||||
|
if userstr == "" {
|
||||||
|
// Lastly, since no user override was passed via CRI try to set via OCI
|
||||||
|
// Image
|
||||||
|
userstr = imageConfig.User
|
||||||
|
}
|
||||||
|
if userstr != "" {
|
||||||
|
specOpts = append(specOpts, oci.WithUser(userstr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if securityContext.GetRunAsUsername() != "" {
|
||||||
|
userstr = securityContext.GetRunAsUsername()
|
||||||
|
} else {
|
||||||
|
// Even if RunAsUser is not set, we still call `GetValue` to get uid 0.
|
||||||
|
// Because it is still useful to get additional gids for uid 0.
|
||||||
|
userstr = strconv.FormatInt(securityContext.GetRunAsUser().GetValue(), 10)
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, customopts.WithAdditionalGIDs(userstr))
|
||||||
|
|
||||||
|
apparmorSpecOpts, err := generateApparmorSpecOpts(
|
||||||
|
securityContext.GetApparmorProfile(),
|
||||||
|
securityContext.GetPrivileged(),
|
||||||
|
c.apparmorEnabled())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate apparmor spec opts")
|
||||||
|
}
|
||||||
|
if apparmorSpecOpts != nil {
|
||||||
|
specOpts = append(specOpts, apparmorSpecOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
seccompSpecOpts, err := generateSeccompSpecOpts(
|
||||||
|
securityContext.GetSeccompProfilePath(),
|
||||||
|
securityContext.GetPrivileged(),
|
||||||
|
c.seccompEnabled())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate seccomp spec opts")
|
||||||
|
}
|
||||||
|
if seccompSpecOpts != nil {
|
||||||
|
specOpts = append(specOpts, seccompSpecOpts)
|
||||||
|
}
|
||||||
|
return specOpts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateSeccompSpecOpts generates containerd SpecOpts for seccomp.
|
||||||
|
func generateSeccompSpecOpts(seccompProf string, privileged, seccompEnabled bool) (oci.SpecOpts, error) {
|
||||||
|
if privileged {
|
||||||
|
// Do not set seccomp profile when container is privileged
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// Set seccomp profile
|
||||||
|
if seccompProf == runtimeDefault || seccompProf == dockerDefault {
|
||||||
|
// use correct default profile (Eg. if not configured otherwise, the default is docker/default)
|
||||||
|
seccompProf = seccompDefaultProfile
|
||||||
|
}
|
||||||
|
if !seccompEnabled {
|
||||||
|
if seccompProf != "" && seccompProf != unconfinedProfile {
|
||||||
|
return nil, errors.New("seccomp is not supported")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
switch seccompProf {
|
||||||
|
case "", unconfinedProfile:
|
||||||
|
// Do not set seccomp profile.
|
||||||
|
return nil, nil
|
||||||
|
case dockerDefault:
|
||||||
|
// Note: WithDefaultProfile specOpts must be added after capabilities
|
||||||
|
return seccomp.WithDefaultProfile(), nil
|
||||||
|
default:
|
||||||
|
// Require and Trim default profile name prefix
|
||||||
|
if !strings.HasPrefix(seccompProf, profileNamePrefix) {
|
||||||
|
return nil, errors.Errorf("invalid seccomp profile %q", seccompProf)
|
||||||
|
}
|
||||||
|
return seccomp.WithProfile(strings.TrimPrefix(seccompProf, profileNamePrefix)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateApparmorSpecOpts generates containerd SpecOpts for apparmor.
|
||||||
|
func generateApparmorSpecOpts(apparmorProf string, privileged, apparmorEnabled bool) (oci.SpecOpts, error) {
|
||||||
|
if !apparmorEnabled {
|
||||||
|
// Should fail loudly if user try to specify apparmor profile
|
||||||
|
// but we don't support it.
|
||||||
|
if apparmorProf != "" && apparmorProf != unconfinedProfile {
|
||||||
|
return nil, errors.New("apparmor is not supported")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
switch apparmorProf {
|
||||||
|
// Based on kubernetes#51746, default apparmor profile should be applied
|
||||||
|
// for when apparmor is not specified.
|
||||||
|
case runtimeDefault, "":
|
||||||
|
if privileged {
|
||||||
|
// Do not set apparmor profile when container is privileged
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// TODO (mikebrow): delete created apparmor default profile
|
||||||
|
return apparmor.WithDefaultProfile(appArmorDefaultProfileName), nil
|
||||||
|
case unconfinedProfile:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
// Require and Trim default profile name prefix
|
||||||
|
if !strings.HasPrefix(apparmorProf, profileNamePrefix) {
|
||||||
|
return nil, errors.Errorf("invalid apparmor profile %q", apparmorProf)
|
||||||
|
}
|
||||||
|
return apparmor.WithProfile(strings.TrimPrefix(apparmorProf, profileNamePrefix)), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateUserString generates valid user string based on OCI Image Spec
|
||||||
|
// v1.0.0.
|
||||||
|
//
|
||||||
|
// CRI defines that the following combinations are valid:
|
||||||
|
//
|
||||||
|
// (none) -> ""
|
||||||
|
// username -> username
|
||||||
|
// username, uid -> username
|
||||||
|
// username, uid, gid -> username:gid
|
||||||
|
// username, gid -> username:gid
|
||||||
|
// uid -> uid
|
||||||
|
// uid, gid -> uid:gid
|
||||||
|
// gid -> error
|
||||||
|
//
|
||||||
|
// TODO(random-liu): Add group name support in CRI.
|
||||||
|
func generateUserString(username string, uid, gid *runtime.Int64Value) (string, error) {
|
||||||
|
var userstr, groupstr string
|
||||||
|
if uid != nil {
|
||||||
|
userstr = strconv.FormatInt(uid.GetValue(), 10)
|
||||||
|
}
|
||||||
|
if username != "" {
|
||||||
|
userstr = username
|
||||||
|
}
|
||||||
|
if gid != nil {
|
||||||
|
groupstr = strconv.FormatInt(gid.GetValue(), 10)
|
||||||
|
}
|
||||||
|
if userstr == "" {
|
||||||
|
if groupstr != "" {
|
||||||
|
return "", errors.Errorf("user group %q is specified without user", groupstr)
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if groupstr != "" {
|
||||||
|
userstr = userstr + ":" + groupstr
|
||||||
|
}
|
||||||
|
return userstr, nil
|
||||||
|
}
|
1314
pkg/server/container_create_unix_test.go
Normal file
1314
pkg/server/container_create_unix_test.go
Normal file
File diff suppressed because it is too large
Load Diff
46
pkg/server/container_create_windows.go
Normal file
46
pkg/server/container_create_windows.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// No container mounts for windows.
|
||||||
|
func (c *criService) containerMounts(sandboxID string, config *runtime.ContainerConfig) []*runtime.Mount {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(windows): Add windows container spec.
|
||||||
|
func (c *criService) containerSpec(id string, sandboxID string, sandboxPid uint32, netNSPath string,
|
||||||
|
config *runtime.ContainerConfig, sandboxConfig *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig,
|
||||||
|
extraMounts []*runtime.Mount, ociRuntime config.Runtime) (*runtimespec.Spec, error) {
|
||||||
|
return nil, errdefs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// No extra spec options needed for windows.
|
||||||
|
func (c *criService) containerSpecOpts(config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) ([]oci.SpecOpts, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -25,7 +25,6 @@ import (
|
|||||||
containerdio "github.com/containerd/containerd/cio"
|
containerdio "github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/plugin"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
@ -99,11 +98,7 @@ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContain
|
|||||||
return nil, errors.Wrap(err, "failed to get container info")
|
return nil, errors.Wrap(err, "failed to get container info")
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskOpts []containerd.NewTaskOpts
|
taskOpts := c.taskOpts(ctrInfo.Runtime.Name)
|
||||||
// TODO(random-liu): Remove this after shim v1 is deprecated.
|
|
||||||
if c.config.NoPivot && ctrInfo.Runtime.Name == plugin.RuntimeLinuxV1 {
|
|
||||||
taskOpts = append(taskOpts, containerd.WithNoPivotRoot)
|
|
||||||
}
|
|
||||||
task, err := container.NewTask(ctx, ioCreation, taskOpts...)
|
task, err := container.NewTask(ctx, ioCreation, taskOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create containerd task")
|
return nil, errors.Wrap(err, "failed to create containerd task")
|
||||||
|
@ -25,6 +25,8 @@ import (
|
|||||||
|
|
||||||
// ContainerStats returns stats of the container. If the container does not
|
// ContainerStats returns stats of the container. If the container does not
|
||||||
// exist, the call returns an error.
|
// exist, the call returns an error.
|
||||||
|
// TODO(windows): hcsshim Stats is not implemented, add windows support after
|
||||||
|
// that is implemented.
|
||||||
func (c *criService) ContainerStats(ctx context.Context, in *runtime.ContainerStatsRequest) (*runtime.ContainerStatsResponse, error) {
|
func (c *criService) ContainerStats(ctx context.Context, in *runtime.ContainerStatsRequest) (*runtime.ContainerStatsResponse, error) {
|
||||||
cntr, err := c.containerStore.Get(in.GetContainerId())
|
cntr, err := c.containerStore.Get(in.GetContainerId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,7 +41,7 @@ func (c *criService) ContainerStats(ctx context.Context, in *runtime.ContainerSt
|
|||||||
return nil, errors.Errorf("unexpected metrics response: %+v", resp.Metrics)
|
return nil, errors.Errorf("unexpected metrics response: %+v", resp.Metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
cs, err := c.getContainerMetrics(cntr.Metadata, resp.Metrics[0])
|
cs, err := c.containerMetrics(cntr.Metadata, resp.Metrics[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to decode container metrics")
|
return nil, errors.Wrap(err, "failed to decode container metrics")
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,8 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containerd/cgroups"
|
|
||||||
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
tasks "github.com/containerd/containerd/api/services/tasks/v1"
|
||||||
"github.com/containerd/containerd/api/types"
|
"github.com/containerd/containerd/api/types"
|
||||||
"github.com/containerd/typeurl"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
@ -58,7 +56,7 @@ func (c *criService) toCRIContainerStats(
|
|||||||
}
|
}
|
||||||
containerStats := new(runtime.ListContainerStatsResponse)
|
containerStats := new(runtime.ListContainerStatsResponse)
|
||||||
for _, cntr := range containers {
|
for _, cntr := range containers {
|
||||||
cs, err := c.getContainerMetrics(cntr.Metadata, statsMap[cntr.ID])
|
cs, err := c.containerMetrics(cntr.Metadata, statsMap[cntr.ID])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to decode container metrics for %q", cntr.ID)
|
return nil, errors.Wrapf(err, "failed to decode container metrics for %q", cntr.ID)
|
||||||
}
|
}
|
||||||
@ -67,59 +65,6 @@ func (c *criService) toCRIContainerStats(
|
|||||||
return containerStats, nil
|
return containerStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *criService) getContainerMetrics(
|
|
||||||
meta containerstore.Metadata,
|
|
||||||
stats *types.Metric,
|
|
||||||
) (*runtime.ContainerStats, error) {
|
|
||||||
var cs runtime.ContainerStats
|
|
||||||
var usedBytes, inodesUsed uint64
|
|
||||||
sn, err := c.snapshotStore.Get(meta.ID)
|
|
||||||
// If snapshotstore doesn't have cached snapshot information
|
|
||||||
// set WritableLayer usage to zero
|
|
||||||
if err == nil {
|
|
||||||
usedBytes = sn.Size
|
|
||||||
inodesUsed = sn.Inodes
|
|
||||||
}
|
|
||||||
cs.WritableLayer = &runtime.FilesystemUsage{
|
|
||||||
Timestamp: sn.Timestamp,
|
|
||||||
FsId: &runtime.FilesystemIdentifier{
|
|
||||||
Mountpoint: c.imageFSPath,
|
|
||||||
},
|
|
||||||
UsedBytes: &runtime.UInt64Value{Value: usedBytes},
|
|
||||||
InodesUsed: &runtime.UInt64Value{Value: inodesUsed},
|
|
||||||
}
|
|
||||||
cs.Attributes = &runtime.ContainerAttributes{
|
|
||||||
Id: meta.ID,
|
|
||||||
Metadata: meta.Config.GetMetadata(),
|
|
||||||
Labels: meta.Config.GetLabels(),
|
|
||||||
Annotations: meta.Config.GetAnnotations(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if stats != nil {
|
|
||||||
s, err := typeurl.UnmarshalAny(stats.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to extract container metrics")
|
|
||||||
}
|
|
||||||
metrics := s.(*cgroups.Metrics)
|
|
||||||
if metrics.CPU != nil && metrics.CPU.Usage != nil {
|
|
||||||
cs.Cpu = &runtime.CpuUsage{
|
|
||||||
Timestamp: stats.Timestamp.UnixNano(),
|
|
||||||
UsageCoreNanoSeconds: &runtime.UInt64Value{Value: metrics.CPU.Usage.Total},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if metrics.Memory != nil && metrics.Memory.Usage != nil {
|
|
||||||
cs.Memory = &runtime.MemoryUsage{
|
|
||||||
Timestamp: stats.Timestamp.UnixNano(),
|
|
||||||
WorkingSetBytes: &runtime.UInt64Value{
|
|
||||||
Value: getWorkingSet(metrics.Memory),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *criService) normalizeContainerStatsFilter(filter *runtime.ContainerStatsFilter) {
|
func (c *criService) normalizeContainerStatsFilter(filter *runtime.ContainerStatsFilter) {
|
||||||
if cntr, err := c.containerStore.Get(filter.GetId()); err == nil {
|
if cntr, err := c.containerStore.Get(filter.GetId()); err == nil {
|
||||||
filter.Id = cntr.ID
|
filter.Id = cntr.ID
|
||||||
@ -169,17 +114,3 @@ func matchLabelSelector(selector, labels map[string]string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// getWorkingSet calculates workingset memory from cgroup memory stats.
|
|
||||||
// The caller should make sure memory is not nil.
|
|
||||||
// workingset = usage - total_inactive_file
|
|
||||||
func getWorkingSet(memory *cgroups.MemoryStat) uint64 {
|
|
||||||
if memory.Usage == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
var workingSet uint64
|
|
||||||
if memory.TotalInactiveFile < memory.Usage.Usage {
|
|
||||||
workingSet = memory.Usage.Usage - memory.TotalInactiveFile
|
|
||||||
}
|
|
||||||
return workingSet
|
|
||||||
}
|
|
||||||
|
96
pkg/server/container_stats_list_unix.go
Normal file
96
pkg/server/container_stats_list_unix.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/cgroups"
|
||||||
|
"github.com/containerd/containerd/api/types"
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *criService) containerMetrics(
|
||||||
|
meta containerstore.Metadata,
|
||||||
|
stats *types.Metric,
|
||||||
|
) (*runtime.ContainerStats, error) {
|
||||||
|
var cs runtime.ContainerStats
|
||||||
|
var usedBytes, inodesUsed uint64
|
||||||
|
sn, err := c.snapshotStore.Get(meta.ID)
|
||||||
|
// If snapshotstore doesn't have cached snapshot information
|
||||||
|
// set WritableLayer usage to zero
|
||||||
|
if err == nil {
|
||||||
|
usedBytes = sn.Size
|
||||||
|
inodesUsed = sn.Inodes
|
||||||
|
}
|
||||||
|
cs.WritableLayer = &runtime.FilesystemUsage{
|
||||||
|
Timestamp: sn.Timestamp,
|
||||||
|
FsId: &runtime.FilesystemIdentifier{
|
||||||
|
Mountpoint: c.imageFSPath,
|
||||||
|
},
|
||||||
|
UsedBytes: &runtime.UInt64Value{Value: usedBytes},
|
||||||
|
InodesUsed: &runtime.UInt64Value{Value: inodesUsed},
|
||||||
|
}
|
||||||
|
cs.Attributes = &runtime.ContainerAttributes{
|
||||||
|
Id: meta.ID,
|
||||||
|
Metadata: meta.Config.GetMetadata(),
|
||||||
|
Labels: meta.Config.GetLabels(),
|
||||||
|
Annotations: meta.Config.GetAnnotations(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if stats != nil {
|
||||||
|
s, err := typeurl.UnmarshalAny(stats.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to extract container metrics")
|
||||||
|
}
|
||||||
|
metrics := s.(*cgroups.Metrics)
|
||||||
|
if metrics.CPU != nil && metrics.CPU.Usage != nil {
|
||||||
|
cs.Cpu = &runtime.CpuUsage{
|
||||||
|
Timestamp: stats.Timestamp.UnixNano(),
|
||||||
|
UsageCoreNanoSeconds: &runtime.UInt64Value{Value: metrics.CPU.Usage.Total},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metrics.Memory != nil && metrics.Memory.Usage != nil {
|
||||||
|
cs.Memory = &runtime.MemoryUsage{
|
||||||
|
Timestamp: stats.Timestamp.UnixNano(),
|
||||||
|
WorkingSetBytes: &runtime.UInt64Value{
|
||||||
|
Value: getWorkingSet(metrics.Memory),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWorkingSet calculates workingset memory from cgroup memory stats.
|
||||||
|
// The caller should make sure memory is not nil.
|
||||||
|
// workingset = usage - total_inactive_file
|
||||||
|
func getWorkingSet(memory *cgroups.MemoryStat) uint64 {
|
||||||
|
if memory.Usage == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var workingSet uint64
|
||||||
|
if memory.TotalInactiveFile < memory.Usage.Usage {
|
||||||
|
workingSet = memory.Usage.Usage - memory.TotalInactiveFile
|
||||||
|
}
|
||||||
|
return workingSet
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2018 The containerd Authors.
|
Copyright 2018 The containerd Authors.
|
||||||
|
|
35
pkg/server/container_stats_list_windows.go
Normal file
35
pkg/server/container_stats_list_windows.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/api/types"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
containerstore "github.com/containerd/cri/pkg/store/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(windows): Implement a dummy version of this, and actually support this
|
||||||
|
// when stats is supported by the hcs containerd shim.
|
||||||
|
func (c *criService) containerMetrics(
|
||||||
|
meta containerstore.Metadata,
|
||||||
|
stats *types.Metric,
|
||||||
|
) (*runtime.ContainerStats, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2017 The Kubernetes Authors.
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2017 The Kubernetes Authors.
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
31
pkg/server/container_update_resources_windows.go
Normal file
31
pkg/server/container_update_resources_windows.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateContainerResources updates ContainerConfig of the container.
|
||||||
|
// TODO(windows): Figure out whether windows support this.
|
||||||
|
func (c *criService) UpdateContainerResources(ctx context.Context, r *runtime.UpdateContainerResourcesRequest) (*runtime.UpdateContainerResourcesResponse, error) {
|
||||||
|
return nil, errdefs.ErrNotImplemented
|
||||||
|
}
|
@ -20,7 +20,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -33,7 +32,6 @@ import (
|
|||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
imagedigest "github.com/opencontainers/go-digest"
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
@ -51,6 +49,7 @@ const (
|
|||||||
errorStartReason = "StartError"
|
errorStartReason = "StartError"
|
||||||
// errorStartExitCode is the exit code when fails to start container.
|
// errorStartExitCode is the exit code when fails to start container.
|
||||||
// 128 is the same with Docker's behavior.
|
// 128 is the same with Docker's behavior.
|
||||||
|
// TODO(windows): Figure out what should be used for windows.
|
||||||
errorStartExitCode = 128
|
errorStartExitCode = 128
|
||||||
// completeExitReason is the exit reason when container exits with code 0.
|
// completeExitReason is the exit reason when container exits with code 0.
|
||||||
completeExitReason = "Completed"
|
completeExitReason = "Completed"
|
||||||
@ -58,39 +57,16 @@ const (
|
|||||||
errorExitReason = "Error"
|
errorExitReason = "Error"
|
||||||
// oomExitReason is the exit reason when process in container is oom killed.
|
// oomExitReason is the exit reason when process in container is oom killed.
|
||||||
oomExitReason = "OOMKilled"
|
oomExitReason = "OOMKilled"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// defaultSandboxOOMAdj is default omm adj for sandbox container. (kubernetes#47938).
|
|
||||||
defaultSandboxOOMAdj = -998
|
|
||||||
// defaultShmSize is the default size of the sandbox shm.
|
|
||||||
defaultShmSize = int64(1024 * 1024 * 64)
|
|
||||||
// relativeRootfsPath is the rootfs path relative to bundle path.
|
|
||||||
relativeRootfsPath = "rootfs"
|
|
||||||
// sandboxesDir contains all sandbox root. A sandbox root is the running
|
// sandboxesDir contains all sandbox root. A sandbox root is the running
|
||||||
// directory of the sandbox, all files created for the sandbox will be
|
// directory of the sandbox, all files created for the sandbox will be
|
||||||
// placed under this directory.
|
// placed under this directory.
|
||||||
sandboxesDir = "sandboxes"
|
sandboxesDir = "sandboxes"
|
||||||
// containersDir contains all container root.
|
// containersDir contains all container root.
|
||||||
containersDir = "containers"
|
containersDir = "containers"
|
||||||
// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
|
|
||||||
// "The search list is currently limited to six domains with a total of 256 characters."
|
|
||||||
maxDNSSearches = 6
|
|
||||||
// Delimiter used to construct container/sandbox names.
|
// Delimiter used to construct container/sandbox names.
|
||||||
nameDelimiter = "_"
|
nameDelimiter = "_"
|
||||||
// devShm is the default path of /dev/shm.
|
|
||||||
devShm = "/dev/shm"
|
|
||||||
// etcHosts is the default path of /etc/hosts file.
|
|
||||||
etcHosts = "/etc/hosts"
|
|
||||||
// etcHostname is the default path of /etc/hostname file.
|
|
||||||
etcHostname = "/etc/hostname"
|
|
||||||
// resolvConfPath is the abs path of resolv.conf on host or container.
|
|
||||||
resolvConfPath = "/etc/resolv.conf"
|
|
||||||
// hostnameEnv is the key for HOSTNAME env.
|
|
||||||
hostnameEnv = "HOSTNAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// criContainerdPrefix is common prefix for cri-containerd
|
// criContainerdPrefix is common prefix for cri-containerd
|
||||||
criContainerdPrefix = "io.cri-containerd"
|
criContainerdPrefix = "io.cri-containerd"
|
||||||
// containerKindLabel is a label key indicating container is sandbox container or application container
|
// containerKindLabel is a label key indicating container is sandbox container or application container
|
||||||
@ -107,14 +83,9 @@ const (
|
|||||||
sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
|
sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
|
||||||
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest
|
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest
|
||||||
containerMetadataExtension = criContainerdPrefix + ".container.metadata"
|
containerMetadataExtension = criContainerdPrefix + ".container.metadata"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// defaultIfName is the default network interface for the pods
|
// defaultIfName is the default network interface for the pods
|
||||||
defaultIfName = "eth0"
|
defaultIfName = "eth0"
|
||||||
// networkAttachCount is the minimum number of networks the PodSandbox
|
|
||||||
// attaches to
|
|
||||||
networkAttachCount = 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeSandboxName generates sandbox name from sandbox metadata. The name
|
// makeSandboxName generates sandbox name from sandbox metadata. The name
|
||||||
@ -141,17 +112,6 @@ func makeContainerName(c *runtime.ContainerMetadata, s *runtime.PodSandboxMetada
|
|||||||
}, nameDelimiter)
|
}, nameDelimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCgroupsPath generates container cgroups path.
|
|
||||||
func getCgroupsPath(cgroupsParent, id string) string {
|
|
||||||
base := path.Base(cgroupsParent)
|
|
||||||
if strings.HasSuffix(base, ".slice") {
|
|
||||||
// For a.slice/b.slice/c.slice, base is c.slice.
|
|
||||||
// runc systemd cgroup path format is "slice:prefix:name".
|
|
||||||
return strings.Join([]string{base, "cri-containerd", id}, ":")
|
|
||||||
}
|
|
||||||
return filepath.Join(cgroupsParent, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSandboxRootDir returns the root directory for managing sandbox files,
|
// getSandboxRootDir returns the root directory for managing sandbox files,
|
||||||
// e.g. hosts files.
|
// e.g. hosts files.
|
||||||
func (c *criService) getSandboxRootDir(id string) string {
|
func (c *criService) getSandboxRootDir(id string) string {
|
||||||
@ -176,26 +136,6 @@ func (c *criService) getVolatileContainerRootDir(id string) string {
|
|||||||
return filepath.Join(c.config.StateDir, containersDir, id)
|
return filepath.Join(c.config.StateDir, containersDir, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSandboxHostname returns the hostname file path inside the sandbox root directory.
|
|
||||||
func (c *criService) getSandboxHostname(id string) string {
|
|
||||||
return filepath.Join(c.getSandboxRootDir(id), "hostname")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSandboxHosts returns the hosts file path inside the sandbox root directory.
|
|
||||||
func (c *criService) getSandboxHosts(id string) string {
|
|
||||||
return filepath.Join(c.getSandboxRootDir(id), "hosts")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getResolvPath returns resolv.conf filepath for specified sandbox.
|
|
||||||
func (c *criService) getResolvPath(id string) string {
|
|
||||||
return filepath.Join(c.getSandboxRootDir(id), "resolv.conf")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSandboxDevShm returns the shm file path inside the sandbox root directory.
|
|
||||||
func (c *criService) getSandboxDevShm(id string) string {
|
|
||||||
return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
|
|
||||||
}
|
|
||||||
|
|
||||||
// criContainerStateToString formats CRI container state to string.
|
// criContainerStateToString formats CRI container state to string.
|
||||||
func criContainerStateToString(state runtime.ContainerState) string {
|
func criContainerStateToString(state runtime.ContainerState) string {
|
||||||
return runtime.ContainerState_name[int32(state)]
|
return runtime.ContainerState_name[int32(state)]
|
||||||
@ -298,49 +238,6 @@ func (c *criService) ensureImageExists(ctx context.Context, ref string, config *
|
|||||||
return &newImage, nil
|
return &newImage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) {
|
|
||||||
if selinuxOpt == nil {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should ignored selinuxOpts if they are incomplete.
|
|
||||||
if selinuxOpt.GetUser() == "" ||
|
|
||||||
selinuxOpt.GetRole() == "" ||
|
|
||||||
selinuxOpt.GetType() == "" {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the format of "level" is correct.
|
|
||||||
ok, err := checkSelinuxLevel(selinuxOpt.GetLevel())
|
|
||||||
if err != nil || !ok {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
labelOpts := fmt.Sprintf("%s:%s:%s:%s",
|
|
||||||
selinuxOpt.GetUser(),
|
|
||||||
selinuxOpt.GetRole(),
|
|
||||||
selinuxOpt.GetType(),
|
|
||||||
selinuxOpt.GetLevel())
|
|
||||||
|
|
||||||
options, err := label.DupSecOpt(labelOpts)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return label.InitLabels(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSelinuxLevel(level string) (bool, error) {
|
|
||||||
if len(level) == 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}((.c\d{1,4})?,c\d{1,4})*(.c\d{1,4})?(,c\d{1,4}(.c\d{1,4})?)*)?$`, level)
|
|
||||||
if err != nil || !matched {
|
|
||||||
return false, errors.Wrapf(err, "the format of 'level' %q is not correct", level)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isInCRIMounts checks whether a destination is in CRI mount list.
|
// isInCRIMounts checks whether a destination is in CRI mount list.
|
||||||
func isInCRIMounts(dst string, mounts []*runtime.Mount) bool {
|
func isInCRIMounts(dst string, mounts []*runtime.Mount) bool {
|
||||||
for _, m := range mounts {
|
for _, m := range mounts {
|
||||||
|
@ -114,39 +114,6 @@ func TestGetRepoDigestAndTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCgroupsPath(t *testing.T) {
|
|
||||||
testID := "test-id"
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
cgroupsParent string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
"should support regular cgroup path": {
|
|
||||||
cgroupsParent: "/a/b",
|
|
||||||
expected: "/a/b/test-id",
|
|
||||||
},
|
|
||||||
"should support systemd cgroup path": {
|
|
||||||
cgroupsParent: "/a.slice/b.slice",
|
|
||||||
expected: "b.slice:cri-containerd:test-id",
|
|
||||||
},
|
|
||||||
"should support tailing slash for regular cgroup path": {
|
|
||||||
cgroupsParent: "/a/b/",
|
|
||||||
expected: "/a/b/test-id",
|
|
||||||
},
|
|
||||||
"should support tailing slash for systemd cgroup path": {
|
|
||||||
cgroupsParent: "/a.slice/b.slice/",
|
|
||||||
expected: "b.slice:cri-containerd:test-id",
|
|
||||||
},
|
|
||||||
"should treat root cgroup as regular cgroup path": {
|
|
||||||
cgroupsParent: "/",
|
|
||||||
expected: "/test-id",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Logf("TestCase %q", desc)
|
|
||||||
got := getCgroupsPath(test.cgroupsParent, testID)
|
|
||||||
assert.Equal(t, test.expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildLabels(t *testing.T) {
|
func TestBuildLabels(t *testing.T) {
|
||||||
configLabels := map[string]string{
|
configLabels := map[string]string{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
|
137
pkg/server/helpers_unix.go
Normal file
137
pkg/server/helpers_unix.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
runcapparmor "github.com/opencontainers/runc/libcontainer/apparmor"
|
||||||
|
runcseccomp "github.com/opencontainers/runc/libcontainer/seccomp"
|
||||||
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defaultSandboxOOMAdj is default omm adj for sandbox container. (kubernetes#47938).
|
||||||
|
defaultSandboxOOMAdj = -998
|
||||||
|
// defaultShmSize is the default size of the sandbox shm.
|
||||||
|
defaultShmSize = int64(1024 * 1024 * 64)
|
||||||
|
// relativeRootfsPath is the rootfs path relative to bundle path.
|
||||||
|
relativeRootfsPath = "rootfs"
|
||||||
|
// According to http://man7.org/linux/man-pages/man5/resolv.conf.5.html:
|
||||||
|
// "The search list is currently limited to six domains with a total of 256 characters."
|
||||||
|
maxDNSSearches = 6
|
||||||
|
// devShm is the default path of /dev/shm.
|
||||||
|
devShm = "/dev/shm"
|
||||||
|
// etcHosts is the default path of /etc/hosts file.
|
||||||
|
etcHosts = "/etc/hosts"
|
||||||
|
// etcHostname is the default path of /etc/hostname file.
|
||||||
|
etcHostname = "/etc/hostname"
|
||||||
|
// resolvConfPath is the abs path of resolv.conf on host or container.
|
||||||
|
resolvConfPath = "/etc/resolv.conf"
|
||||||
|
// hostnameEnv is the key for HOSTNAME env.
|
||||||
|
hostnameEnv = "HOSTNAME"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getCgroupsPath generates container cgroups path.
|
||||||
|
func getCgroupsPath(cgroupsParent, id string) string {
|
||||||
|
base := path.Base(cgroupsParent)
|
||||||
|
if strings.HasSuffix(base, ".slice") {
|
||||||
|
// For a.slice/b.slice/c.slice, base is c.slice.
|
||||||
|
// runc systemd cgroup path format is "slice:prefix:name".
|
||||||
|
return strings.Join([]string{base, "cri-containerd", id}, ":")
|
||||||
|
}
|
||||||
|
return filepath.Join(cgroupsParent, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSandboxHostname returns the hostname file path inside the sandbox root directory.
|
||||||
|
func (c *criService) getSandboxHostname(id string) string {
|
||||||
|
return filepath.Join(c.getSandboxRootDir(id), "hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSandboxHosts returns the hosts file path inside the sandbox root directory.
|
||||||
|
func (c *criService) getSandboxHosts(id string) string {
|
||||||
|
return filepath.Join(c.getSandboxRootDir(id), "hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getResolvPath returns resolv.conf filepath for specified sandbox.
|
||||||
|
func (c *criService) getResolvPath(id string) string {
|
||||||
|
return filepath.Join(c.getSandboxRootDir(id), "resolv.conf")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSandboxDevShm returns the shm file path inside the sandbox root directory.
|
||||||
|
func (c *criService) getSandboxDevShm(id string) string {
|
||||||
|
return filepath.Join(c.getVolatileSandboxRootDir(id), "shm")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) {
|
||||||
|
if selinuxOpt == nil {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should ignored selinuxOpts if they are incomplete.
|
||||||
|
if selinuxOpt.GetUser() == "" ||
|
||||||
|
selinuxOpt.GetRole() == "" ||
|
||||||
|
selinuxOpt.GetType() == "" {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the format of "level" is correct.
|
||||||
|
ok, err := checkSelinuxLevel(selinuxOpt.GetLevel())
|
||||||
|
if err != nil || !ok {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
labelOpts := fmt.Sprintf("%s:%s:%s:%s",
|
||||||
|
selinuxOpt.GetUser(),
|
||||||
|
selinuxOpt.GetRole(),
|
||||||
|
selinuxOpt.GetType(),
|
||||||
|
selinuxOpt.GetLevel())
|
||||||
|
|
||||||
|
options, err := label.DupSecOpt(labelOpts)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return label.InitLabels(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSelinuxLevel(level string) (bool, error) {
|
||||||
|
if len(level) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matched, err := regexp.MatchString(`^s\d(-s\d)??(:c\d{1,4}((.c\d{1,4})?,c\d{1,4})*(.c\d{1,4})?(,c\d{1,4}(.c\d{1,4})?)*)?$`, level)
|
||||||
|
if err != nil || !matched {
|
||||||
|
return false, errors.Wrapf(err, "the format of 'level' %q is not correct", level)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criService) apparmorEnabled() bool {
|
||||||
|
return runcapparmor.IsEnabled() && !c.config.DisableApparmor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criService) seccompEnabled() bool {
|
||||||
|
return runcseccomp.IsEnabled()
|
||||||
|
}
|
58
pkg/server/helpers_unix_test.go
Normal file
58
pkg/server/helpers_unix_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCgroupsPath(t *testing.T) {
|
||||||
|
testID := "test-id"
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
cgroupsParent string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
"should support regular cgroup path": {
|
||||||
|
cgroupsParent: "/a/b",
|
||||||
|
expected: "/a/b/test-id",
|
||||||
|
},
|
||||||
|
"should support systemd cgroup path": {
|
||||||
|
cgroupsParent: "/a.slice/b.slice",
|
||||||
|
expected: "b.slice:cri-containerd:test-id",
|
||||||
|
},
|
||||||
|
"should support tailing slash for regular cgroup path": {
|
||||||
|
cgroupsParent: "/a/b/",
|
||||||
|
expected: "/a/b/test-id",
|
||||||
|
},
|
||||||
|
"should support tailing slash for systemd cgroup path": {
|
||||||
|
cgroupsParent: "/a.slice/b.slice/",
|
||||||
|
expected: "b.slice:cri-containerd:test-id",
|
||||||
|
},
|
||||||
|
"should treat root cgroup as regular cgroup path": {
|
||||||
|
cgroupsParent: "/",
|
||||||
|
expected: "/test-id",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", desc)
|
||||||
|
got := getCgroupsPath(test.cgroupsParent, testID)
|
||||||
|
assert.Equal(t, test.expected, got)
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ImageFsInfo returns information of the filesystem that is used to store images.
|
// ImageFsInfo returns information of the filesystem that is used to store images.
|
||||||
|
// TODO(windows): Usage for windows is always 0 right now. Support this for windows.
|
||||||
func (c *criService) ImageFsInfo(ctx context.Context, r *runtime.ImageFsInfoRequest) (*runtime.ImageFsInfoResponse, error) {
|
func (c *criService) ImageFsInfo(ctx context.Context, r *runtime.ImageFsInfoRequest) (*runtime.ImageFsInfoResponse, error) {
|
||||||
snapshots := c.snapshotStore.List()
|
snapshots := c.snapshotStore.List()
|
||||||
timestamp := time.Now().UnixNano()
|
timestamp := time.Now().UnixNano()
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/containerd/containerd/cio"
|
"github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/fifo"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
)
|
)
|
||||||
@ -113,7 +112,7 @@ func newStdioPipes(fifos *cio.FIFOSet) (_ *stdioPipes, _ *wgCloser, err error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if fifos.Stdin != "" {
|
if fifos.Stdin != "" {
|
||||||
if f, err = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
if f, err = openFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
p.stdin = f
|
p.stdin = f
|
||||||
@ -121,7 +120,7 @@ func newStdioPipes(fifos *cio.FIFOSet) (_ *stdioPipes, _ *wgCloser, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fifos.Stdout != "" {
|
if fifos.Stdout != "" {
|
||||||
if f, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
if f, err = openFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
p.stdout = f
|
p.stdout = f
|
||||||
@ -129,7 +128,7 @@ func newStdioPipes(fifos *cio.FIFOSet) (_ *stdioPipes, _ *wgCloser, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fifos.Stderr != "" {
|
if fifos.Stderr != "" {
|
||||||
if f, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
if f, err = openFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
p.stderr = f
|
p.stderr = f
|
||||||
|
31
pkg/server/io/helpers_unix.go
Normal file
31
pkg/server/io/helpers_unix.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/fifo"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
return fifo.OpenFifo(ctx, fn, flag, perm)
|
||||||
|
}
|
31
pkg/server/io/helpers_windows.go
Normal file
31
pkg/server/io/helpers_windows.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(windows): Add windows FIFO support.
|
||||||
|
func openFifo(ctx context.Context, fn string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -20,6 +20,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
goruntime "runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
@ -407,7 +408,8 @@ func (c *criService) loadSandbox(ctx context.Context, cntr containerd.Container)
|
|||||||
sandbox.Container = cntr
|
sandbox.Container = cntr
|
||||||
|
|
||||||
// Load network namespace.
|
// Load network namespace.
|
||||||
if meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
|
if goruntime.GOOS != "windows" &&
|
||||||
|
meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
|
||||||
// Don't need to load netns for host network sandbox.
|
// Don't need to load netns for host network sandbox.
|
||||||
return sandbox, nil
|
return sandbox, nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2017 The Kubernetes Authors.
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
37
pkg/server/sandbox_portforward_windows.go
Normal file
37
pkg/server/sandbox_portforward_windows.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
|
||||||
|
// TODO(windows): Implement this for windows.
|
||||||
|
func (c *criService) PortForward(ctx context.Context, r *runtime.PortForwardRequest) (*runtime.PortForwardResponse, error) {
|
||||||
|
return nil, errdefs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *criService) portForward(ctx context.Context, id string, port int32, stream io.ReadWriter) error {
|
||||||
|
return errdefs.ErrNotImplemented
|
||||||
|
}
|
@ -18,26 +18,20 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"os"
|
goruntime "runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
containerdio "github.com/containerd/containerd/cio"
|
containerdio "github.com/containerd/containerd/cio"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/oci"
|
|
||||||
"github.com/containerd/containerd/plugin"
|
|
||||||
cni "github.com/containerd/go-cni"
|
cni "github.com/containerd/go-cni"
|
||||||
"github.com/containerd/typeurl"
|
"github.com/containerd/typeurl"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
"k8s.io/kubernetes/pkg/util/bandwidth"
|
"k8s.io/kubernetes/pkg/util/bandwidth"
|
||||||
|
|
||||||
@ -110,10 +104,14 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
|
|||||||
}
|
}
|
||||||
log.G(ctx).Debugf("Use OCI %+v for sandbox %q", ociRuntime, id)
|
log.G(ctx).Debugf("Use OCI %+v for sandbox %q", ociRuntime, id)
|
||||||
|
|
||||||
securityContext := config.GetLinux().GetSecurityContext()
|
podNetwork := true
|
||||||
//Create Network Namespace if it is not in host network
|
// Pod network is always needed on windows.
|
||||||
hostNet := securityContext.GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE
|
if goruntime.GOOS != "windows" &&
|
||||||
if !hostNet {
|
config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
|
||||||
|
// Pod network is not needed on linux with host network.
|
||||||
|
podNetwork = false
|
||||||
|
}
|
||||||
|
if podNetwork {
|
||||||
// If it is not in host network namespace then create a namespace and set the sandbox
|
// If it is not in host network namespace then create a namespace and set the sandbox
|
||||||
// handle. NetNSPath in sandbox metadata and NetNS is non empty only for non host network
|
// handle. NetNSPath in sandbox metadata and NetNS is non empty only for non host network
|
||||||
// namespaces. If the pod is in host network namespace then both are empty and should not
|
// namespaces. If the pod is in host network namespace then both are empty and should not
|
||||||
@ -154,39 +152,19 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create sandbox container.
|
// Create sandbox container.
|
||||||
spec, err := c.generateSandboxContainerSpec(id, config, &image.ImageSpec.Config, sandbox.NetNSPath, ociRuntime.PodAnnotations)
|
// NOTE: sandboxContainerSpec SHOULD NOT have side
|
||||||
|
// effect, e.g. accessing/creating files, so that we can test
|
||||||
|
// it safely.
|
||||||
|
spec, err := c.sandboxContainerSpec(id, config, &image.ImageSpec.Config, sandbox.NetNSPath, ociRuntime.PodAnnotations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to generate sandbox container spec")
|
return nil, errors.Wrap(err, "failed to generate sandbox container spec")
|
||||||
}
|
}
|
||||||
log.G(ctx).Debugf("Sandbox container %q spec: %#+v", id, spew.NewFormatter(spec))
|
log.G(ctx).Debugf("Sandbox container %q spec: %#+v", id, spew.NewFormatter(spec))
|
||||||
|
|
||||||
var specOpts []oci.SpecOpts
|
// Generate spec options that will be applied to the spec later.
|
||||||
userstr, err := generateUserString(
|
specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config)
|
||||||
"",
|
|
||||||
securityContext.GetRunAsUser(),
|
|
||||||
securityContext.GetRunAsGroup(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to generate user string")
|
return nil, errors.Wrap(err, "failed to generate sanbdox container spec options")
|
||||||
}
|
|
||||||
if userstr == "" {
|
|
||||||
// Lastly, since no user override was passed via CRI try to set via OCI
|
|
||||||
// Image
|
|
||||||
userstr = image.ImageSpec.Config.User
|
|
||||||
}
|
|
||||||
if userstr != "" {
|
|
||||||
specOpts = append(specOpts, oci.WithUser(userstr))
|
|
||||||
}
|
|
||||||
|
|
||||||
seccompSpecOpts, err := generateSeccompSpecOpts(
|
|
||||||
securityContext.GetSeccompProfilePath(),
|
|
||||||
securityContext.GetPrivileged(),
|
|
||||||
c.seccompEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to generate seccomp spec opts")
|
|
||||||
}
|
|
||||||
if seccompSpecOpts != nil {
|
|
||||||
specOpts = append(specOpts, seccompSpecOpts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sandboxLabels := buildLabels(config.Labels, containerKindSandbox)
|
sandboxLabels := buildLabels(config.Labels, containerKindSandbox)
|
||||||
@ -247,14 +225,14 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Setup sandbox /dev/shm, /etc/hosts, /etc/resolv.conf and /etc/hostname.
|
// Setup files required for the sandbox.
|
||||||
if err = c.setupSandboxFiles(id, config); err != nil {
|
if err = c.setupSandboxFiles(id, config); err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to setup sandbox files")
|
return nil, errors.Wrapf(err, "failed to setup sandbox files")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
if err = c.unmountSandboxFiles(id, config); err != nil {
|
if err = c.cleanupSandboxFiles(id, config); err != nil {
|
||||||
log.G(ctx).WithError(err).Errorf("Failed to unmount sandbox files in %q",
|
log.G(ctx).WithError(err).Errorf("Failed to cleanup sandbox files in %q",
|
||||||
sandboxRootDir)
|
sandboxRootDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,11 +248,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
|
|||||||
log.G(ctx).Tracef("Create sandbox container (id=%q, name=%q).",
|
log.G(ctx).Tracef("Create sandbox container (id=%q, name=%q).",
|
||||||
id, name)
|
id, name)
|
||||||
|
|
||||||
var taskOpts []containerd.NewTaskOpts
|
taskOpts := c.taskOpts(ociRuntime.Type)
|
||||||
// TODO(random-liu): Remove this after shim v1 is deprecated.
|
|
||||||
if c.config.NoPivot && ociRuntime.Type == plugin.RuntimeRuncV1 {
|
|
||||||
taskOpts = append(taskOpts, containerd.WithNoPivotRoot)
|
|
||||||
}
|
|
||||||
// We don't need stdio for sandbox container.
|
// We don't need stdio for sandbox container.
|
||||||
task, err := container.NewTask(ctx, containerdio.NullIO, taskOpts...)
|
task, err := container.NewTask(ctx, containerdio.NullIO, taskOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -328,222 +302,6 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
|
|||||||
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *criService) generateSandboxContainerSpec(id string, config *runtime.PodSandboxConfig,
|
|
||||||
imageConfig *imagespec.ImageConfig, nsPath string, runtimePodAnnotations []string) (*runtimespec.Spec, error) {
|
|
||||||
// Creates a spec Generator with the default spec.
|
|
||||||
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
|
||||||
specOpts := []oci.SpecOpts{
|
|
||||||
customopts.WithoutRunMount,
|
|
||||||
customopts.WithoutDefaultSecuritySettings,
|
|
||||||
customopts.WithRelativeRoot(relativeRootfsPath),
|
|
||||||
oci.WithEnv(imageConfig.Env),
|
|
||||||
oci.WithRootFSReadonly(),
|
|
||||||
oci.WithHostname(config.GetHostname()),
|
|
||||||
}
|
|
||||||
if imageConfig.WorkingDir != "" {
|
|
||||||
specOpts = append(specOpts, oci.WithProcessCwd(imageConfig.WorkingDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(imageConfig.Entrypoint) == 0 && len(imageConfig.Cmd) == 0 {
|
|
||||||
// Pause image must have entrypoint or cmd.
|
|
||||||
return nil, errors.Errorf("invalid empty entrypoint and cmd in image config %+v", imageConfig)
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, oci.WithProcessArgs(append(imageConfig.Entrypoint, imageConfig.Cmd...)...))
|
|
||||||
|
|
||||||
// TODO(random-liu): [P2] Consider whether to add labels and annotations to the container.
|
|
||||||
|
|
||||||
// Set cgroups parent.
|
|
||||||
if c.config.DisableCgroup {
|
|
||||||
specOpts = append(specOpts, customopts.WithDisabledCgroups)
|
|
||||||
} else {
|
|
||||||
if config.GetLinux().GetCgroupParent() != "" {
|
|
||||||
cgroupsPath := getCgroupsPath(config.GetLinux().GetCgroupParent(), id)
|
|
||||||
specOpts = append(specOpts, oci.WithCgroup(cgroupsPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When cgroup parent is not set, containerd-shim will create container in a child cgroup
|
|
||||||
// of the cgroup itself is in.
|
|
||||||
// TODO(random-liu): [P2] Set default cgroup path if cgroup parent is not specified.
|
|
||||||
|
|
||||||
// Set namespace options.
|
|
||||||
var (
|
|
||||||
securityContext = config.GetLinux().GetSecurityContext()
|
|
||||||
nsOptions = securityContext.GetNamespaceOptions()
|
|
||||||
)
|
|
||||||
if nsOptions.GetNetwork() == runtime.NamespaceMode_NODE {
|
|
||||||
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.NetworkNamespace))
|
|
||||||
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.UTSNamespace))
|
|
||||||
} else {
|
|
||||||
//TODO(Abhi): May be move this to containerd spec opts (WithLinuxSpaceOption)
|
|
||||||
specOpts = append(specOpts, oci.WithLinuxNamespace(
|
|
||||||
runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.NetworkNamespace,
|
|
||||||
Path: nsPath,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
if nsOptions.GetPid() == runtime.NamespaceMode_NODE {
|
|
||||||
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.PIDNamespace))
|
|
||||||
}
|
|
||||||
if nsOptions.GetIpc() == runtime.NamespaceMode_NODE {
|
|
||||||
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.IPCNamespace))
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's fine to generate the spec before the sandbox /dev/shm
|
|
||||||
// is actually created.
|
|
||||||
sandboxDevShm := c.getSandboxDevShm(id)
|
|
||||||
if nsOptions.GetIpc() == runtime.NamespaceMode_NODE {
|
|
||||||
sandboxDevShm = devShm
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, oci.WithMounts([]runtimespec.Mount{
|
|
||||||
{
|
|
||||||
Source: sandboxDevShm,
|
|
||||||
Destination: devShm,
|
|
||||||
Type: "bind",
|
|
||||||
Options: []string{"rbind", "ro"},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
selinuxOpt := securityContext.GetSelinuxOptions()
|
|
||||||
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
|
|
||||||
}
|
|
||||||
|
|
||||||
supplementalGroups := securityContext.GetSupplementalGroups()
|
|
||||||
specOpts = append(specOpts,
|
|
||||||
customopts.WithSelinuxLabels(processLabel, mountLabel),
|
|
||||||
customopts.WithSupplementalGroups(supplementalGroups),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add sysctls
|
|
||||||
sysctls := config.GetLinux().GetSysctls()
|
|
||||||
specOpts = append(specOpts, customopts.WithSysctls(sysctls))
|
|
||||||
|
|
||||||
// Note: LinuxSandboxSecurityContext does not currently provide an apparmor profile
|
|
||||||
|
|
||||||
if !c.config.DisableCgroup {
|
|
||||||
specOpts = append(specOpts, customopts.WithDefaultSandboxShares)
|
|
||||||
}
|
|
||||||
specOpts = append(specOpts, customopts.WithPodOOMScoreAdj(int(defaultSandboxOOMAdj), c.config.RestrictOOMScoreAdj))
|
|
||||||
|
|
||||||
for pKey, pValue := range getPassthroughAnnotations(config.Annotations,
|
|
||||||
runtimePodAnnotations) {
|
|
||||||
specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
specOpts = append(specOpts,
|
|
||||||
customopts.WithAnnotation(annotations.ContainerType, annotations.ContainerTypeSandbox),
|
|
||||||
customopts.WithAnnotation(annotations.SandboxID, id),
|
|
||||||
customopts.WithAnnotation(annotations.SandboxLogDir, config.GetLogDirectory()),
|
|
||||||
)
|
|
||||||
|
|
||||||
return runtimeSpec(id, specOpts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupSandboxFiles sets up necessary sandbox files including /dev/shm, /etc/hosts,
|
|
||||||
// /etc/resolv.conf and /etc/hostname.
|
|
||||||
func (c *criService) setupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
|
||||||
sandboxEtcHostname := c.getSandboxHostname(id)
|
|
||||||
hostname := config.GetHostname()
|
|
||||||
if hostname == "" {
|
|
||||||
var err error
|
|
||||||
hostname, err = c.os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to get hostname")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.os.WriteFile(sandboxEtcHostname, []byte(hostname+"\n"), 0644); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to write hostname to %q", sandboxEtcHostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(random-liu): Consider whether we should maintain /etc/hosts and /etc/resolv.conf in kubelet.
|
|
||||||
sandboxEtcHosts := c.getSandboxHosts(id)
|
|
||||||
if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0644); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to generate sandbox hosts file %q", sandboxEtcHosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set DNS options. Maintain a resolv.conf for the sandbox.
|
|
||||||
var err error
|
|
||||||
resolvContent := ""
|
|
||||||
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
|
|
||||||
resolvContent, err = parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to parse sandbox DNSConfig %+v", dnsConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resolvPath := c.getResolvPath(id)
|
|
||||||
if resolvContent == "" {
|
|
||||||
// copy host's resolv.conf to resolvPath
|
|
||||||
err = c.os.CopyFile(resolvConfPath, resolvPath, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to copy host's resolv.conf to %q", resolvPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = c.os.WriteFile(resolvPath, []byte(resolvContent), 0644)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to write resolv content to %q", resolvPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup sandbox /dev/shm.
|
|
||||||
if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetIpc() == runtime.NamespaceMode_NODE {
|
|
||||||
if _, err := c.os.Stat(devShm); err != nil {
|
|
||||||
return errors.Wrapf(err, "host %q is not available for host ipc", devShm)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sandboxDevShm := c.getSandboxDevShm(id)
|
|
||||||
if err := c.os.MkdirAll(sandboxDevShm, 0700); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create sandbox shm")
|
|
||||||
}
|
|
||||||
shmproperty := fmt.Sprintf("mode=1777,size=%d", defaultShmSize)
|
|
||||||
if err := c.os.Mount("shm", sandboxDevShm, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), shmproperty); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to mount sandbox shm")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDNSOptions parse DNS options into resolv.conf format content,
|
|
||||||
// if none option is specified, will return empty with no error.
|
|
||||||
func parseDNSOptions(servers, searches, options []string) (string, error) {
|
|
||||||
resolvContent := ""
|
|
||||||
|
|
||||||
if len(searches) > maxDNSSearches {
|
|
||||||
return "", errors.Errorf("DNSOption.Searches has more than %d domains", maxDNSSearches)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(searches) > 0 {
|
|
||||||
resolvContent += fmt.Sprintf("search %s\n", strings.Join(searches, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(servers) > 0 {
|
|
||||||
resolvContent += fmt.Sprintf("nameserver %s\n", strings.Join(servers, "\nnameserver "))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(options) > 0 {
|
|
||||||
resolvContent += fmt.Sprintf("options %s\n", strings.Join(options, " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolvContent, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmountSandboxFiles unmount some sandbox files, we rely on the removal of sandbox root directory to
|
|
||||||
// remove these files. Unmount should *NOT* return error if the mount point is already unmounted.
|
|
||||||
func (c *criService) unmountSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
|
||||||
if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetIpc() != runtime.NamespaceMode_NODE {
|
|
||||||
path, err := c.os.FollowSymlinkInScope(c.getSandboxDevShm(id), "/")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to follow symlink")
|
|
||||||
}
|
|
||||||
if err := c.os.Unmount(path); err != nil && !os.IsNotExist(err) {
|
|
||||||
return errors.Wrapf(err, "failed to unmount %q", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupPod setups up the network for a pod
|
// setupPod setups up the network for a pod
|
||||||
func (c *criService) setupPod(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) (string, *cni.CNIResult, error) {
|
func (c *criService) setupPod(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) (string, *cni.CNIResult, error) {
|
||||||
if c.netPlugin == nil {
|
if c.netPlugin == nil {
|
||||||
@ -713,6 +471,7 @@ func (c *criService) getSandboxRuntime(config *runtime.PodSandboxConfig, runtime
|
|||||||
// Note: If the workload is marked untrusted but requests privileged, this can be granted, as the
|
// Note: If the workload is marked untrusted but requests privileged, this can be granted, as the
|
||||||
// runtime may support this. For example, in a virtual-machine isolated runtime, privileged
|
// runtime may support this. For example, in a virtual-machine isolated runtime, privileged
|
||||||
// is a supported option, granting the workload to access the entire guest VM instead of host.
|
// is a supported option, granting the workload to access the entire guest VM instead of host.
|
||||||
|
// TODO(windows): Deprecate this so that we don't need to handle it for windows.
|
||||||
if hostAccessingSandbox(config) {
|
if hostAccessingSandbox(config) {
|
||||||
return criconfig.Runtime{}, errors.New("untrusted workload with host access is not allowed")
|
return criconfig.Runtime{}, errors.New("untrusted workload with host access is not allowed")
|
||||||
}
|
}
|
||||||
|
@ -18,449 +18,16 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cni "github.com/containerd/go-cni"
|
cni "github.com/containerd/go-cni"
|
||||||
"github.com/containerd/typeurl"
|
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
"github.com/containerd/cri/pkg/annotations"
|
"github.com/containerd/cri/pkg/annotations"
|
||||||
criconfig "github.com/containerd/cri/pkg/config"
|
criconfig "github.com/containerd/cri/pkg/config"
|
||||||
"github.com/containerd/cri/pkg/containerd/opts"
|
|
||||||
ostesting "github.com/containerd/cri/pkg/os/testing"
|
|
||||||
sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, *imagespec.ImageConfig, func(*testing.T, string, *runtimespec.Spec)) {
|
|
||||||
config := &runtime.PodSandboxConfig{
|
|
||||||
Metadata: &runtime.PodSandboxMetadata{
|
|
||||||
Name: "test-name",
|
|
||||||
Uid: "test-uid",
|
|
||||||
Namespace: "test-ns",
|
|
||||||
Attempt: 1,
|
|
||||||
},
|
|
||||||
Hostname: "test-hostname",
|
|
||||||
LogDirectory: "test-log-directory",
|
|
||||||
Labels: map[string]string{"a": "b"},
|
|
||||||
Annotations: map[string]string{"c": "d"},
|
|
||||||
Linux: &runtime.LinuxPodSandboxConfig{
|
|
||||||
CgroupParent: "/test/cgroup/parent",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
imageConfig := &imagespec.ImageConfig{
|
|
||||||
Env: []string{"a=b", "c=d"},
|
|
||||||
Entrypoint: []string{"/pause"},
|
|
||||||
Cmd: []string{"forever"},
|
|
||||||
WorkingDir: "/workspace",
|
|
||||||
}
|
|
||||||
specCheck := func(t *testing.T, id string, spec *runtimespec.Spec) {
|
|
||||||
assert.Equal(t, "test-hostname", spec.Hostname)
|
|
||||||
assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
|
|
||||||
assert.Equal(t, relativeRootfsPath, spec.Root.Path)
|
|
||||||
assert.Equal(t, true, spec.Root.Readonly)
|
|
||||||
assert.Contains(t, spec.Process.Env, "a=b", "c=d")
|
|
||||||
assert.Equal(t, []string{"/pause", "forever"}, spec.Process.Args)
|
|
||||||
assert.Equal(t, "/workspace", spec.Process.Cwd)
|
|
||||||
assert.EqualValues(t, *spec.Linux.Resources.CPU.Shares, opts.DefaultSandboxCPUshares)
|
|
||||||
assert.EqualValues(t, *spec.Process.OOMScoreAdj, defaultSandboxOOMAdj)
|
|
||||||
|
|
||||||
t.Logf("Check PodSandbox annotations")
|
|
||||||
assert.Contains(t, spec.Annotations, annotations.SandboxID)
|
|
||||||
assert.EqualValues(t, spec.Annotations[annotations.SandboxID], id)
|
|
||||||
|
|
||||||
assert.Contains(t, spec.Annotations, annotations.ContainerType)
|
|
||||||
assert.EqualValues(t, spec.Annotations[annotations.ContainerType], annotations.ContainerTypeSandbox)
|
|
||||||
|
|
||||||
assert.Contains(t, spec.Annotations, annotations.SandboxLogDir)
|
|
||||||
assert.EqualValues(t, spec.Annotations[annotations.SandboxLogDir], "test-log-directory")
|
|
||||||
}
|
|
||||||
return config, imageConfig, specCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateSandboxContainerSpec(t *testing.T) {
|
|
||||||
testID := "test-id"
|
|
||||||
nsPath := "test-cni"
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
configChange func(*runtime.PodSandboxConfig)
|
|
||||||
podAnnotations []string
|
|
||||||
imageConfigChange func(*imagespec.ImageConfig)
|
|
||||||
specCheck func(*testing.T, *runtimespec.Spec)
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
"spec should reflect original config": {
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
// runtime spec should have expected namespaces enabled by default.
|
|
||||||
require.NotNil(t, spec.Linux)
|
|
||||||
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.NetworkNamespace,
|
|
||||||
Path: nsPath,
|
|
||||||
})
|
|
||||||
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.UTSNamespace,
|
|
||||||
})
|
|
||||||
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.PIDNamespace,
|
|
||||||
})
|
|
||||||
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.IPCNamespace,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"host namespace": {
|
|
||||||
configChange: func(c *runtime.PodSandboxConfig) {
|
|
||||||
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
|
||||||
NamespaceOptions: &runtime.NamespaceOption{
|
|
||||||
Network: runtime.NamespaceMode_NODE,
|
|
||||||
Pid: runtime.NamespaceMode_NODE,
|
|
||||||
Ipc: runtime.NamespaceMode_NODE,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
// runtime spec should disable expected namespaces in host mode.
|
|
||||||
require.NotNil(t, spec.Linux)
|
|
||||||
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.NetworkNamespace,
|
|
||||||
})
|
|
||||||
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.UTSNamespace,
|
|
||||||
})
|
|
||||||
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.PIDNamespace,
|
|
||||||
})
|
|
||||||
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
|
||||||
Type: runtimespec.IPCNamespace,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"should return error when entrypoint and cmd are empty": {
|
|
||||||
imageConfigChange: func(c *imagespec.ImageConfig) {
|
|
||||||
c.Entrypoint = nil
|
|
||||||
c.Cmd = nil
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
"should set supplemental groups correctly": {
|
|
||||||
configChange: func(c *runtime.PodSandboxConfig) {
|
|
||||||
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
|
||||||
SupplementalGroups: []int64{1111, 2222},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
require.NotNil(t, spec.Process)
|
|
||||||
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
|
|
||||||
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"a passthrough annotation should be passed as an OCI annotation": {
|
|
||||||
podAnnotations: []string{"c"},
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
assert.Equal(t, spec.Annotations["c"], "d")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"a non-passthrough annotation should not be passed as an OCI annotation": {
|
|
||||||
configChange: func(c *runtime.PodSandboxConfig) {
|
|
||||||
c.Annotations["d"] = "e"
|
|
||||||
},
|
|
||||||
podAnnotations: []string{"c"},
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
assert.Equal(t, spec.Annotations["c"], "d")
|
|
||||||
_, ok := spec.Annotations["d"]
|
|
||||||
assert.False(t, ok)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"passthrough annotations should support wildcard match": {
|
|
||||||
configChange: func(c *runtime.PodSandboxConfig) {
|
|
||||||
c.Annotations["t.f"] = "j"
|
|
||||||
c.Annotations["z.g"] = "o"
|
|
||||||
c.Annotations["z"] = "o"
|
|
||||||
c.Annotations["y.ca"] = "b"
|
|
||||||
c.Annotations["y"] = "b"
|
|
||||||
},
|
|
||||||
podAnnotations: []string{"t*", "z.*", "y.c*"},
|
|
||||||
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
|
||||||
assert.Equal(t, spec.Annotations["t.f"], "j")
|
|
||||||
assert.Equal(t, spec.Annotations["z.g"], "o")
|
|
||||||
assert.Equal(t, spec.Annotations["y.ca"], "b")
|
|
||||||
_, ok := spec.Annotations["y"]
|
|
||||||
assert.False(t, ok)
|
|
||||||
_, ok = spec.Annotations["z"]
|
|
||||||
assert.False(t, ok)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Logf("TestCase %q", desc)
|
|
||||||
c := newTestCRIService()
|
|
||||||
config, imageConfig, specCheck := getRunPodSandboxTestData()
|
|
||||||
if test.configChange != nil {
|
|
||||||
test.configChange(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.imageConfigChange != nil {
|
|
||||||
test.imageConfigChange(imageConfig)
|
|
||||||
}
|
|
||||||
spec, err := c.generateSandboxContainerSpec(testID, config, imageConfig, nsPath,
|
|
||||||
test.podAnnotations)
|
|
||||||
if test.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Nil(t, spec)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, spec)
|
|
||||||
specCheck(t, testID, spec)
|
|
||||||
if test.specCheck != nil {
|
|
||||||
test.specCheck(t, spec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetupSandboxFiles(t *testing.T) {
|
|
||||||
const (
|
|
||||||
testID = "test-id"
|
|
||||||
realhostname = "test-real-hostname"
|
|
||||||
)
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
dnsConfig *runtime.DNSConfig
|
|
||||||
hostname string
|
|
||||||
ipcMode runtime.NamespaceMode
|
|
||||||
expectedCalls []ostesting.CalledDetail
|
|
||||||
}{
|
|
||||||
"should check host /dev/shm existence when ipc mode is NODE": {
|
|
||||||
ipcMode: runtime.NamespaceMode_NODE,
|
|
||||||
expectedCalls: []ostesting.CalledDetail{
|
|
||||||
{
|
|
||||||
Name: "Hostname",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "WriteFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
|
||||||
[]byte(realhostname + "\n"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/hosts",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/resolv.conf",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Stat",
|
|
||||||
Arguments: []interface{}{"/dev/shm"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"should create new /etc/resolv.conf if DNSOptions is set": {
|
|
||||||
dnsConfig: &runtime.DNSConfig{
|
|
||||||
Servers: []string{"8.8.8.8"},
|
|
||||||
Searches: []string{"114.114.114.114"},
|
|
||||||
Options: []string{"timeout:1"},
|
|
||||||
},
|
|
||||||
ipcMode: runtime.NamespaceMode_NODE,
|
|
||||||
expectedCalls: []ostesting.CalledDetail{
|
|
||||||
{
|
|
||||||
Name: "Hostname",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "WriteFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
|
||||||
[]byte(realhostname + "\n"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/hosts",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "WriteFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
|
||||||
[]byte(`search 114.114.114.114
|
|
||||||
nameserver 8.8.8.8
|
|
||||||
options timeout:1
|
|
||||||
`), os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Stat",
|
|
||||||
Arguments: []interface{}{"/dev/shm"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"should create sandbox shm when ipc namespace mode is not NODE": {
|
|
||||||
ipcMode: runtime.NamespaceMode_POD,
|
|
||||||
expectedCalls: []ostesting.CalledDetail{
|
|
||||||
{
|
|
||||||
Name: "Hostname",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "WriteFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
|
||||||
[]byte(realhostname + "\n"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/hosts",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/resolv.conf",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "MkdirAll",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testStateDir, sandboxesDir, testID, "shm"),
|
|
||||||
os.FileMode(0700),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Mount",
|
|
||||||
// Ignore arguments which are too complex to check.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"should create /etc/hostname when hostname is set": {
|
|
||||||
hostname: "test-hostname",
|
|
||||||
ipcMode: runtime.NamespaceMode_NODE,
|
|
||||||
expectedCalls: []ostesting.CalledDetail{
|
|
||||||
{
|
|
||||||
Name: "WriteFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
|
||||||
[]byte("test-hostname\n"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/hosts",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "CopyFile",
|
|
||||||
Arguments: []interface{}{
|
|
||||||
"/etc/resolv.conf",
|
|
||||||
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
|
||||||
os.FileMode(0644),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Stat",
|
|
||||||
Arguments: []interface{}{"/dev/shm"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Logf("TestCase %q", desc)
|
|
||||||
c := newTestCRIService()
|
|
||||||
c.os.(*ostesting.FakeOS).HostnameFn = func() (string, error) {
|
|
||||||
return realhostname, nil
|
|
||||||
}
|
|
||||||
cfg := &runtime.PodSandboxConfig{
|
|
||||||
Hostname: test.hostname,
|
|
||||||
DnsConfig: test.dnsConfig,
|
|
||||||
Linux: &runtime.LinuxPodSandboxConfig{
|
|
||||||
SecurityContext: &runtime.LinuxSandboxSecurityContext{
|
|
||||||
NamespaceOptions: &runtime.NamespaceOption{
|
|
||||||
Ipc: test.ipcMode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.setupSandboxFiles(testID, cfg)
|
|
||||||
calls := c.os.(*ostesting.FakeOS).GetCalls()
|
|
||||||
assert.Len(t, calls, len(test.expectedCalls))
|
|
||||||
for i, expected := range test.expectedCalls {
|
|
||||||
if expected.Arguments == nil {
|
|
||||||
// Ignore arguments.
|
|
||||||
expected.Arguments = calls[i].Arguments
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected, calls[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseDNSOption(t *testing.T) {
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
servers []string
|
|
||||||
searches []string
|
|
||||||
options []string
|
|
||||||
expectedContent string
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
"empty dns options should return empty content": {},
|
|
||||||
"non-empty dns options should return correct content": {
|
|
||||||
servers: []string{"8.8.8.8", "server.google.com"},
|
|
||||||
searches: []string{"114.114.114.114"},
|
|
||||||
options: []string{"timeout:1"},
|
|
||||||
expectedContent: `search 114.114.114.114
|
|
||||||
nameserver 8.8.8.8
|
|
||||||
nameserver server.google.com
|
|
||||||
options timeout:1
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
"should return error if dns search exceeds limit(6)": {
|
|
||||||
searches: []string{
|
|
||||||
"server0.google.com",
|
|
||||||
"server1.google.com",
|
|
||||||
"server2.google.com",
|
|
||||||
"server3.google.com",
|
|
||||||
"server4.google.com",
|
|
||||||
"server5.google.com",
|
|
||||||
"server6.google.com",
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Logf("TestCase %q", desc)
|
|
||||||
resolvContent, err := parseDNSOptions(test.servers, test.searches, test.options)
|
|
||||||
if test.expectErr {
|
|
||||||
assert.Error(t, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, resolvContent, test.expectedContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToCNIPortMappings(t *testing.T) {
|
func TestToCNIPortMappings(t *testing.T) {
|
||||||
for desc, test := range map[string]struct {
|
for desc, test := range map[string]struct {
|
||||||
criPortMappings []*runtime.PortMapping
|
criPortMappings []*runtime.PortMapping
|
||||||
@ -575,46 +142,6 @@ func TestSelectPodIP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTypeurlMarshalUnmarshalSandboxMeta(t *testing.T) {
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
configChange func(*runtime.PodSandboxConfig)
|
|
||||||
}{
|
|
||||||
"should marshal original config": {},
|
|
||||||
"should marshal Linux": {
|
|
||||||
configChange: func(c *runtime.PodSandboxConfig) {
|
|
||||||
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
|
||||||
NamespaceOptions: &runtime.NamespaceOption{
|
|
||||||
Network: runtime.NamespaceMode_NODE,
|
|
||||||
Pid: runtime.NamespaceMode_NODE,
|
|
||||||
Ipc: runtime.NamespaceMode_NODE,
|
|
||||||
},
|
|
||||||
SupplementalGroups: []int64{1111, 2222},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Logf("TestCase %q", desc)
|
|
||||||
meta := &sandboxstore.Metadata{
|
|
||||||
ID: "1",
|
|
||||||
Name: "sandbox_1",
|
|
||||||
NetNSPath: "/home/cloud",
|
|
||||||
}
|
|
||||||
meta.Config, _, _ = getRunPodSandboxTestData()
|
|
||||||
if test.configChange != nil {
|
|
||||||
test.configChange(meta.Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
any, err := typeurl.MarshalAny(meta)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
data, err := typeurl.UnmarshalAny(any)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.IsType(t, &sandboxstore.Metadata{}, data)
|
|
||||||
curMeta, ok := data.(*sandboxstore.Metadata)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, meta, curMeta)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostAccessingSandbox(t *testing.T) {
|
func TestHostAccessingSandbox(t *testing.T) {
|
||||||
privilegedContext := &runtime.PodSandboxConfig{
|
privilegedContext := &runtime.PodSandboxConfig{
|
||||||
Linux: &runtime.LinuxPodSandboxConfig{
|
Linux: &runtime.LinuxPodSandboxConfig{
|
||||||
@ -823,21 +350,3 @@ func TestGetSandboxRuntime(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSandboxDisableCgroup(t *testing.T) {
|
|
||||||
config, imageConfig, _ := getRunPodSandboxTestData()
|
|
||||||
c := newTestCRIService()
|
|
||||||
c.config.DisableCgroup = true
|
|
||||||
spec, err := c.generateSandboxContainerSpec("test-id", config, imageConfig, "test-cni", []string{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Log("resource limit should not be set")
|
|
||||||
assert.Nil(t, spec.Linux.Resources.Memory)
|
|
||||||
assert.Nil(t, spec.Linux.Resources.CPU)
|
|
||||||
|
|
||||||
t.Log("cgroup path should be empty")
|
|
||||||
assert.Empty(t, spec.Linux.CgroupsPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(random-liu): [P1] Add unit test for different error cases to make sure
|
|
||||||
// the function cleans up on error properly.
|
|
||||||
|
298
pkg/server/sandbox_run_unix.go
Normal file
298
pkg/server/sandbox_run_unix.go
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
"github.com/containerd/containerd/plugin"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/annotations"
|
||||||
|
customopts "github.com/containerd/cri/pkg/containerd/opts"
|
||||||
|
osinterface "github.com/containerd/cri/pkg/os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxConfig,
|
||||||
|
imageConfig *imagespec.ImageConfig, nsPath string, runtimePodAnnotations []string) (*runtimespec.Spec, error) {
|
||||||
|
// Creates a spec Generator with the default spec.
|
||||||
|
// TODO(random-liu): [P1] Compare the default settings with docker and containerd default.
|
||||||
|
specOpts := []oci.SpecOpts{
|
||||||
|
customopts.WithoutRunMount,
|
||||||
|
customopts.WithoutDefaultSecuritySettings,
|
||||||
|
customopts.WithRelativeRoot(relativeRootfsPath),
|
||||||
|
oci.WithEnv(imageConfig.Env),
|
||||||
|
oci.WithRootFSReadonly(),
|
||||||
|
oci.WithHostname(config.GetHostname()),
|
||||||
|
}
|
||||||
|
if imageConfig.WorkingDir != "" {
|
||||||
|
specOpts = append(specOpts, oci.WithProcessCwd(imageConfig.WorkingDir))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(imageConfig.Entrypoint) == 0 && len(imageConfig.Cmd) == 0 {
|
||||||
|
// Pause image must have entrypoint or cmd.
|
||||||
|
return nil, errors.Errorf("invalid empty entrypoint and cmd in image config %+v", imageConfig)
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, oci.WithProcessArgs(append(imageConfig.Entrypoint, imageConfig.Cmd...)...))
|
||||||
|
|
||||||
|
// Set cgroups parent.
|
||||||
|
if c.config.DisableCgroup {
|
||||||
|
specOpts = append(specOpts, customopts.WithDisabledCgroups)
|
||||||
|
} else {
|
||||||
|
if config.GetLinux().GetCgroupParent() != "" {
|
||||||
|
cgroupsPath := getCgroupsPath(config.GetLinux().GetCgroupParent(), id)
|
||||||
|
specOpts = append(specOpts, oci.WithCgroup(cgroupsPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When cgroup parent is not set, containerd-shim will create container in a child cgroup
|
||||||
|
// of the cgroup itself is in.
|
||||||
|
// TODO(random-liu): [P2] Set default cgroup path if cgroup parent is not specified.
|
||||||
|
|
||||||
|
// Set namespace options.
|
||||||
|
var (
|
||||||
|
securityContext = config.GetLinux().GetSecurityContext()
|
||||||
|
nsOptions = securityContext.GetNamespaceOptions()
|
||||||
|
)
|
||||||
|
if nsOptions.GetNetwork() == runtime.NamespaceMode_NODE {
|
||||||
|
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.NetworkNamespace))
|
||||||
|
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.UTSNamespace))
|
||||||
|
} else {
|
||||||
|
specOpts = append(specOpts, oci.WithLinuxNamespace(
|
||||||
|
runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.NetworkNamespace,
|
||||||
|
Path: nsPath,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if nsOptions.GetPid() == runtime.NamespaceMode_NODE {
|
||||||
|
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.PIDNamespace))
|
||||||
|
}
|
||||||
|
if nsOptions.GetIpc() == runtime.NamespaceMode_NODE {
|
||||||
|
specOpts = append(specOpts, customopts.WithoutNamespace(runtimespec.IPCNamespace))
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's fine to generate the spec before the sandbox /dev/shm
|
||||||
|
// is actually created.
|
||||||
|
sandboxDevShm := c.getSandboxDevShm(id)
|
||||||
|
if nsOptions.GetIpc() == runtime.NamespaceMode_NODE {
|
||||||
|
sandboxDevShm = devShm
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, oci.WithMounts([]runtimespec.Mount{
|
||||||
|
{
|
||||||
|
Source: sandboxDevShm,
|
||||||
|
Destination: devShm,
|
||||||
|
Type: "bind",
|
||||||
|
Options: []string{"rbind", "ro"},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
selinuxOpt := securityContext.GetSelinuxOptions()
|
||||||
|
processLabel, mountLabel, err := initSelinuxOpts(selinuxOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to init selinux options %+v", securityContext.GetSelinuxOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
supplementalGroups := securityContext.GetSupplementalGroups()
|
||||||
|
specOpts = append(specOpts,
|
||||||
|
customopts.WithSelinuxLabels(processLabel, mountLabel),
|
||||||
|
customopts.WithSupplementalGroups(supplementalGroups),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add sysctls
|
||||||
|
sysctls := config.GetLinux().GetSysctls()
|
||||||
|
specOpts = append(specOpts, customopts.WithSysctls(sysctls))
|
||||||
|
|
||||||
|
// Note: LinuxSandboxSecurityContext does not currently provide an apparmor profile
|
||||||
|
|
||||||
|
if !c.config.DisableCgroup {
|
||||||
|
specOpts = append(specOpts, customopts.WithDefaultSandboxShares)
|
||||||
|
}
|
||||||
|
specOpts = append(specOpts, customopts.WithPodOOMScoreAdj(int(defaultSandboxOOMAdj), c.config.RestrictOOMScoreAdj))
|
||||||
|
|
||||||
|
for pKey, pValue := range getPassthroughAnnotations(config.Annotations,
|
||||||
|
runtimePodAnnotations) {
|
||||||
|
specOpts = append(specOpts, customopts.WithAnnotation(pKey, pValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
specOpts = append(specOpts,
|
||||||
|
customopts.WithAnnotation(annotations.ContainerType, annotations.ContainerTypeSandbox),
|
||||||
|
customopts.WithAnnotation(annotations.SandboxID, id),
|
||||||
|
customopts.WithAnnotation(annotations.SandboxLogDir, config.GetLogDirectory()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return runtimeSpec(id, specOpts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sandboxContainerSpecOpts generates OCI spec options for
|
||||||
|
// the sandbox container.
|
||||||
|
func (c *criService) sandboxContainerSpecOpts(config *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig) ([]oci.SpecOpts, error) {
|
||||||
|
var (
|
||||||
|
securityContext = config.GetLinux().GetSecurityContext()
|
||||||
|
specOpts []oci.SpecOpts
|
||||||
|
)
|
||||||
|
seccompSpecOpts, err := generateSeccompSpecOpts(
|
||||||
|
securityContext.GetSeccompProfilePath(),
|
||||||
|
securityContext.GetPrivileged(),
|
||||||
|
c.seccompEnabled())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate seccomp spec opts")
|
||||||
|
}
|
||||||
|
if seccompSpecOpts != nil {
|
||||||
|
specOpts = append(specOpts, seccompSpecOpts)
|
||||||
|
}
|
||||||
|
|
||||||
|
userstr, err := generateUserString(
|
||||||
|
"",
|
||||||
|
securityContext.GetRunAsUser(),
|
||||||
|
securityContext.GetRunAsGroup(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate user string")
|
||||||
|
}
|
||||||
|
if userstr == "" {
|
||||||
|
// Lastly, since no user override was passed via CRI try to set via OCI
|
||||||
|
// Image
|
||||||
|
userstr = imageConfig.User
|
||||||
|
}
|
||||||
|
if userstr != "" {
|
||||||
|
specOpts = append(specOpts, oci.WithUser(userstr))
|
||||||
|
}
|
||||||
|
return specOpts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setupSandboxFiles sets up necessary sandbox files including /dev/shm, /etc/hosts,
|
||||||
|
// /etc/resolv.conf and /etc/hostname.
|
||||||
|
func (c *criService) setupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
||||||
|
sandboxEtcHostname := c.getSandboxHostname(id)
|
||||||
|
hostname := config.GetHostname()
|
||||||
|
if hostname == "" {
|
||||||
|
var err error
|
||||||
|
hostname, err = c.os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get hostname")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.os.WriteFile(sandboxEtcHostname, []byte(hostname+"\n"), 0644); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to write hostname to %q", sandboxEtcHostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): Consider whether we should maintain /etc/hosts and /etc/resolv.conf in kubelet.
|
||||||
|
sandboxEtcHosts := c.getSandboxHosts(id)
|
||||||
|
if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0644); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to generate sandbox hosts file %q", sandboxEtcHosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set DNS options. Maintain a resolv.conf for the sandbox.
|
||||||
|
var err error
|
||||||
|
resolvContent := ""
|
||||||
|
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
|
||||||
|
resolvContent, err = parseDNSOptions(dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse sandbox DNSConfig %+v", dnsConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolvPath := c.getResolvPath(id)
|
||||||
|
if resolvContent == "" {
|
||||||
|
// copy host's resolv.conf to resolvPath
|
||||||
|
err = c.os.CopyFile(resolvConfPath, resolvPath, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to copy host's resolv.conf to %q", resolvPath)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = c.os.WriteFile(resolvPath, []byte(resolvContent), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to write resolv content to %q", resolvPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup sandbox /dev/shm.
|
||||||
|
if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetIpc() == runtime.NamespaceMode_NODE {
|
||||||
|
if _, err := c.os.Stat(devShm); err != nil {
|
||||||
|
return errors.Wrapf(err, "host %q is not available for host ipc", devShm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sandboxDevShm := c.getSandboxDevShm(id)
|
||||||
|
if err := c.os.MkdirAll(sandboxDevShm, 0700); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create sandbox shm")
|
||||||
|
}
|
||||||
|
shmproperty := fmt.Sprintf("mode=1777,size=%d", defaultShmSize)
|
||||||
|
if err := c.os.(osinterface.UNIX).Mount("shm", sandboxDevShm, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), shmproperty); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to mount sandbox shm")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDNSOptions parse DNS options into resolv.conf format content,
|
||||||
|
// if none option is specified, will return empty with no error.
|
||||||
|
func parseDNSOptions(servers, searches, options []string) (string, error) {
|
||||||
|
resolvContent := ""
|
||||||
|
|
||||||
|
if len(searches) > maxDNSSearches {
|
||||||
|
return "", errors.Errorf("DNSOption.Searches has more than %d domains", maxDNSSearches)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(searches) > 0 {
|
||||||
|
resolvContent += fmt.Sprintf("search %s\n", strings.Join(searches, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) > 0 {
|
||||||
|
resolvContent += fmt.Sprintf("nameserver %s\n", strings.Join(servers, "\nnameserver "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(options) > 0 {
|
||||||
|
resolvContent += fmt.Sprintf("options %s\n", strings.Join(options, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvContent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupSandboxFiles unmount some sandbox files, we rely on the removal of sandbox root directory to
|
||||||
|
// remove these files. Unmount should *NOT* return error if the mount point is already unmounted.
|
||||||
|
func (c *criService) cleanupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
||||||
|
if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetIpc() != runtime.NamespaceMode_NODE {
|
||||||
|
path, err := c.os.FollowSymlinkInScope(c.getSandboxDevShm(id), "/")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to follow symlink")
|
||||||
|
}
|
||||||
|
if err := c.os.(osinterface.UNIX).Unmount(path); err != nil && !os.IsNotExist(err) {
|
||||||
|
return errors.Wrapf(err, "failed to unmount %q", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskOpts generates task options for a (sandbox) container.
|
||||||
|
func (c *criService) taskOpts(runtimeType string) []containerd.NewTaskOpts {
|
||||||
|
// TODO(random-liu): Remove this after shim v1 is deprecated.
|
||||||
|
var taskOpts []containerd.NewTaskOpts
|
||||||
|
if c.config.NoPivot && runtimeType == plugin.RuntimeRuncV1 {
|
||||||
|
taskOpts = append(taskOpts, containerd.WithNoPivotRoot)
|
||||||
|
}
|
||||||
|
return taskOpts
|
||||||
|
}
|
520
pkg/server/sandbox_run_unix_test.go
Normal file
520
pkg/server/sandbox_run_unix_test.go
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
|
||||||
|
"github.com/containerd/cri/pkg/annotations"
|
||||||
|
"github.com/containerd/cri/pkg/containerd/opts"
|
||||||
|
ostesting "github.com/containerd/cri/pkg/os/testing"
|
||||||
|
sandboxstore "github.com/containerd/cri/pkg/store/sandbox"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getRunPodSandboxTestData() (*runtime.PodSandboxConfig, *imagespec.ImageConfig, func(*testing.T, string, *runtimespec.Spec)) {
|
||||||
|
config := &runtime.PodSandboxConfig{
|
||||||
|
Metadata: &runtime.PodSandboxMetadata{
|
||||||
|
Name: "test-name",
|
||||||
|
Uid: "test-uid",
|
||||||
|
Namespace: "test-ns",
|
||||||
|
Attempt: 1,
|
||||||
|
},
|
||||||
|
Hostname: "test-hostname",
|
||||||
|
LogDirectory: "test-log-directory",
|
||||||
|
Labels: map[string]string{"a": "b"},
|
||||||
|
Annotations: map[string]string{"c": "d"},
|
||||||
|
Linux: &runtime.LinuxPodSandboxConfig{
|
||||||
|
CgroupParent: "/test/cgroup/parent",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
imageConfig := &imagespec.ImageConfig{
|
||||||
|
Env: []string{"a=b", "c=d"},
|
||||||
|
Entrypoint: []string{"/pause"},
|
||||||
|
Cmd: []string{"forever"},
|
||||||
|
WorkingDir: "/workspace",
|
||||||
|
}
|
||||||
|
specCheck := func(t *testing.T, id string, spec *runtimespec.Spec) {
|
||||||
|
assert.Equal(t, "test-hostname", spec.Hostname)
|
||||||
|
assert.Equal(t, getCgroupsPath("/test/cgroup/parent", id), spec.Linux.CgroupsPath)
|
||||||
|
assert.Equal(t, relativeRootfsPath, spec.Root.Path)
|
||||||
|
assert.Equal(t, true, spec.Root.Readonly)
|
||||||
|
assert.Contains(t, spec.Process.Env, "a=b", "c=d")
|
||||||
|
assert.Equal(t, []string{"/pause", "forever"}, spec.Process.Args)
|
||||||
|
assert.Equal(t, "/workspace", spec.Process.Cwd)
|
||||||
|
assert.EqualValues(t, *spec.Linux.Resources.CPU.Shares, opts.DefaultSandboxCPUshares)
|
||||||
|
assert.EqualValues(t, *spec.Process.OOMScoreAdj, defaultSandboxOOMAdj)
|
||||||
|
|
||||||
|
t.Logf("Check PodSandbox annotations")
|
||||||
|
assert.Contains(t, spec.Annotations, annotations.SandboxID)
|
||||||
|
assert.EqualValues(t, spec.Annotations[annotations.SandboxID], id)
|
||||||
|
|
||||||
|
assert.Contains(t, spec.Annotations, annotations.ContainerType)
|
||||||
|
assert.EqualValues(t, spec.Annotations[annotations.ContainerType], annotations.ContainerTypeSandbox)
|
||||||
|
|
||||||
|
assert.Contains(t, spec.Annotations, annotations.SandboxLogDir)
|
||||||
|
assert.EqualValues(t, spec.Annotations[annotations.SandboxLogDir], "test-log-directory")
|
||||||
|
}
|
||||||
|
return config, imageConfig, specCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSandboxContainerSpec(t *testing.T) {
|
||||||
|
testID := "test-id"
|
||||||
|
nsPath := "test-cni"
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
configChange func(*runtime.PodSandboxConfig)
|
||||||
|
podAnnotations []string
|
||||||
|
imageConfigChange func(*imagespec.ImageConfig)
|
||||||
|
specCheck func(*testing.T, *runtimespec.Spec)
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
"spec should reflect original config": {
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
// runtime spec should have expected namespaces enabled by default.
|
||||||
|
require.NotNil(t, spec.Linux)
|
||||||
|
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.NetworkNamespace,
|
||||||
|
Path: nsPath,
|
||||||
|
})
|
||||||
|
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.UTSNamespace,
|
||||||
|
})
|
||||||
|
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.PIDNamespace,
|
||||||
|
})
|
||||||
|
assert.Contains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.IPCNamespace,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"host namespace": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||||
|
NamespaceOptions: &runtime.NamespaceOption{
|
||||||
|
Network: runtime.NamespaceMode_NODE,
|
||||||
|
Pid: runtime.NamespaceMode_NODE,
|
||||||
|
Ipc: runtime.NamespaceMode_NODE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
// runtime spec should disable expected namespaces in host mode.
|
||||||
|
require.NotNil(t, spec.Linux)
|
||||||
|
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.NetworkNamespace,
|
||||||
|
})
|
||||||
|
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.UTSNamespace,
|
||||||
|
})
|
||||||
|
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.PIDNamespace,
|
||||||
|
})
|
||||||
|
assert.NotContains(t, spec.Linux.Namespaces, runtimespec.LinuxNamespace{
|
||||||
|
Type: runtimespec.IPCNamespace,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"should return error when entrypoint and cmd are empty": {
|
||||||
|
imageConfigChange: func(c *imagespec.ImageConfig) {
|
||||||
|
c.Entrypoint = nil
|
||||||
|
c.Cmd = nil
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
"should set supplemental groups correctly": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||||
|
SupplementalGroups: []int64{1111, 2222},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
require.NotNil(t, spec.Process)
|
||||||
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(1111))
|
||||||
|
assert.Contains(t, spec.Process.User.AdditionalGids, uint32(2222))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a passthrough annotation should be passed as an OCI annotation": {
|
||||||
|
podAnnotations: []string{"c"},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
assert.Equal(t, spec.Annotations["c"], "d")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a non-passthrough annotation should not be passed as an OCI annotation": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Annotations["d"] = "e"
|
||||||
|
},
|
||||||
|
podAnnotations: []string{"c"},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
assert.Equal(t, spec.Annotations["c"], "d")
|
||||||
|
_, ok := spec.Annotations["d"]
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"passthrough annotations should support wildcard match": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Annotations["t.f"] = "j"
|
||||||
|
c.Annotations["z.g"] = "o"
|
||||||
|
c.Annotations["z"] = "o"
|
||||||
|
c.Annotations["y.ca"] = "b"
|
||||||
|
c.Annotations["y"] = "b"
|
||||||
|
},
|
||||||
|
podAnnotations: []string{"t*", "z.*", "y.c*"},
|
||||||
|
specCheck: func(t *testing.T, spec *runtimespec.Spec) {
|
||||||
|
assert.Equal(t, spec.Annotations["t.f"], "j")
|
||||||
|
assert.Equal(t, spec.Annotations["z.g"], "o")
|
||||||
|
assert.Equal(t, spec.Annotations["y.ca"], "b")
|
||||||
|
_, ok := spec.Annotations["y"]
|
||||||
|
assert.False(t, ok)
|
||||||
|
_, ok = spec.Annotations["z"]
|
||||||
|
assert.False(t, ok)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", desc)
|
||||||
|
c := newTestCRIService()
|
||||||
|
config, imageConfig, specCheck := getRunPodSandboxTestData()
|
||||||
|
if test.configChange != nil {
|
||||||
|
test.configChange(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.imageConfigChange != nil {
|
||||||
|
test.imageConfigChange(imageConfig)
|
||||||
|
}
|
||||||
|
spec, err := c.sandboxContainerSpec(testID, config, imageConfig, nsPath,
|
||||||
|
test.podAnnotations)
|
||||||
|
if test.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, spec)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, spec)
|
||||||
|
specCheck(t, testID, spec)
|
||||||
|
if test.specCheck != nil {
|
||||||
|
test.specCheck(t, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupSandboxFiles(t *testing.T) {
|
||||||
|
const (
|
||||||
|
testID = "test-id"
|
||||||
|
realhostname = "test-real-hostname"
|
||||||
|
)
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
dnsConfig *runtime.DNSConfig
|
||||||
|
hostname string
|
||||||
|
ipcMode runtime.NamespaceMode
|
||||||
|
expectedCalls []ostesting.CalledDetail
|
||||||
|
}{
|
||||||
|
"should check host /dev/shm existence when ipc mode is NODE": {
|
||||||
|
ipcMode: runtime.NamespaceMode_NODE,
|
||||||
|
expectedCalls: []ostesting.CalledDetail{
|
||||||
|
{
|
||||||
|
Name: "Hostname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "WriteFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
||||||
|
[]byte(realhostname + "\n"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/hosts",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/resolv.conf",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Stat",
|
||||||
|
Arguments: []interface{}{"/dev/shm"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"should create new /etc/resolv.conf if DNSOptions is set": {
|
||||||
|
dnsConfig: &runtime.DNSConfig{
|
||||||
|
Servers: []string{"8.8.8.8"},
|
||||||
|
Searches: []string{"114.114.114.114"},
|
||||||
|
Options: []string{"timeout:1"},
|
||||||
|
},
|
||||||
|
ipcMode: runtime.NamespaceMode_NODE,
|
||||||
|
expectedCalls: []ostesting.CalledDetail{
|
||||||
|
{
|
||||||
|
Name: "Hostname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "WriteFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
||||||
|
[]byte(realhostname + "\n"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/hosts",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "WriteFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
||||||
|
[]byte(`search 114.114.114.114
|
||||||
|
nameserver 8.8.8.8
|
||||||
|
options timeout:1
|
||||||
|
`), os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Stat",
|
||||||
|
Arguments: []interface{}{"/dev/shm"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"should create sandbox shm when ipc namespace mode is not NODE": {
|
||||||
|
ipcMode: runtime.NamespaceMode_POD,
|
||||||
|
expectedCalls: []ostesting.CalledDetail{
|
||||||
|
{
|
||||||
|
Name: "Hostname",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "WriteFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
||||||
|
[]byte(realhostname + "\n"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/hosts",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/resolv.conf",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "MkdirAll",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testStateDir, sandboxesDir, testID, "shm"),
|
||||||
|
os.FileMode(0700),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Mount",
|
||||||
|
// Ignore arguments which are too complex to check.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"should create /etc/hostname when hostname is set": {
|
||||||
|
hostname: "test-hostname",
|
||||||
|
ipcMode: runtime.NamespaceMode_NODE,
|
||||||
|
expectedCalls: []ostesting.CalledDetail{
|
||||||
|
{
|
||||||
|
Name: "WriteFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hostname"),
|
||||||
|
[]byte("test-hostname\n"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/hosts",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "hosts"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CopyFile",
|
||||||
|
Arguments: []interface{}{
|
||||||
|
"/etc/resolv.conf",
|
||||||
|
filepath.Join(testRootDir, sandboxesDir, testID, "resolv.conf"),
|
||||||
|
os.FileMode(0644),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Stat",
|
||||||
|
Arguments: []interface{}{"/dev/shm"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", desc)
|
||||||
|
c := newTestCRIService()
|
||||||
|
c.os.(*ostesting.FakeOS).HostnameFn = func() (string, error) {
|
||||||
|
return realhostname, nil
|
||||||
|
}
|
||||||
|
cfg := &runtime.PodSandboxConfig{
|
||||||
|
Hostname: test.hostname,
|
||||||
|
DnsConfig: test.dnsConfig,
|
||||||
|
Linux: &runtime.LinuxPodSandboxConfig{
|
||||||
|
SecurityContext: &runtime.LinuxSandboxSecurityContext{
|
||||||
|
NamespaceOptions: &runtime.NamespaceOption{
|
||||||
|
Ipc: test.ipcMode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c.setupSandboxFiles(testID, cfg)
|
||||||
|
calls := c.os.(*ostesting.FakeOS).GetCalls()
|
||||||
|
assert.Len(t, calls, len(test.expectedCalls))
|
||||||
|
for i, expected := range test.expectedCalls {
|
||||||
|
if expected.Arguments == nil {
|
||||||
|
// Ignore arguments.
|
||||||
|
expected.Arguments = calls[i].Arguments
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, calls[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDNSOption(t *testing.T) {
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
servers []string
|
||||||
|
searches []string
|
||||||
|
options []string
|
||||||
|
expectedContent string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
"empty dns options should return empty content": {},
|
||||||
|
"non-empty dns options should return correct content": {
|
||||||
|
servers: []string{"8.8.8.8", "server.google.com"},
|
||||||
|
searches: []string{"114.114.114.114"},
|
||||||
|
options: []string{"timeout:1"},
|
||||||
|
expectedContent: `search 114.114.114.114
|
||||||
|
nameserver 8.8.8.8
|
||||||
|
nameserver server.google.com
|
||||||
|
options timeout:1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
"should return error if dns search exceeds limit(6)": {
|
||||||
|
searches: []string{
|
||||||
|
"server0.google.com",
|
||||||
|
"server1.google.com",
|
||||||
|
"server2.google.com",
|
||||||
|
"server3.google.com",
|
||||||
|
"server4.google.com",
|
||||||
|
"server5.google.com",
|
||||||
|
"server6.google.com",
|
||||||
|
},
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", desc)
|
||||||
|
resolvContent, err := parseDNSOptions(test.servers, test.searches, test.options)
|
||||||
|
if test.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, resolvContent, test.expectedContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(windows): Move this to sandbox_run_test.go
|
||||||
|
func TestTypeurlMarshalUnmarshalSandboxMeta(t *testing.T) {
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
configChange func(*runtime.PodSandboxConfig)
|
||||||
|
}{
|
||||||
|
"should marshal original config": {},
|
||||||
|
"should marshal Linux": {
|
||||||
|
configChange: func(c *runtime.PodSandboxConfig) {
|
||||||
|
c.Linux.SecurityContext = &runtime.LinuxSandboxSecurityContext{
|
||||||
|
NamespaceOptions: &runtime.NamespaceOption{
|
||||||
|
Network: runtime.NamespaceMode_NODE,
|
||||||
|
Pid: runtime.NamespaceMode_NODE,
|
||||||
|
Ipc: runtime.NamespaceMode_NODE,
|
||||||
|
},
|
||||||
|
SupplementalGroups: []int64{1111, 2222},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Logf("TestCase %q", desc)
|
||||||
|
meta := &sandboxstore.Metadata{
|
||||||
|
ID: "1",
|
||||||
|
Name: "sandbox_1",
|
||||||
|
NetNSPath: "/home/cloud",
|
||||||
|
}
|
||||||
|
meta.Config, _, _ = getRunPodSandboxTestData()
|
||||||
|
if test.configChange != nil {
|
||||||
|
test.configChange(meta.Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
any, err := typeurl.MarshalAny(meta)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
data, err := typeurl.UnmarshalAny(any)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.IsType(t, &sandboxstore.Metadata{}, data)
|
||||||
|
curMeta, ok := data.(*sandboxstore.Metadata)
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, meta, curMeta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSandboxDisableCgroup(t *testing.T) {
|
||||||
|
config, imageConfig, _ := getRunPodSandboxTestData()
|
||||||
|
c := newTestCRIService()
|
||||||
|
c.config.DisableCgroup = true
|
||||||
|
spec, err := c.sandboxContainerSpec("test-id", config, imageConfig, "test-cni", []string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log("resource limit should not be set")
|
||||||
|
assert.Nil(t, spec.Linux.Resources.Memory)
|
||||||
|
assert.Nil(t, spec.Linux.Resources.CPU)
|
||||||
|
|
||||||
|
t.Log("cgroup path should be empty")
|
||||||
|
assert.Empty(t, spec.Linux.CgroupsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(random-liu): [P1] Add unit test for different error cases to make sure
|
||||||
|
// the function cleans up on error properly.
|
55
pkg/server/sandbox_run_windows.go
Normal file
55
pkg/server/sandbox_run_windows.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/oci"
|
||||||
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(windows): Add windows support.
|
||||||
|
// TODO(windows): Configure windows sandbox shares
|
||||||
|
func (c *criService) sandboxContainerSpec(id string, config *runtime.PodSandboxConfig,
|
||||||
|
imageConfig *imagespec.ImageConfig, nsPath string, runtimePodAnnotations []string) (*runtimespec.Spec, error) {
|
||||||
|
return nil, errdefs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// No sandbox container spec options for windows yet.
|
||||||
|
func (c *criService) sandboxContainerSpecOpts(config *runtime.PodSandboxConfig, imageConfig *imagespec.ImageConfig) ([]oci.SpecOpts, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No sandbox files needed for windows.
|
||||||
|
func (c *criService) setupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No sandbox files needed for windows.
|
||||||
|
func (c *criService) cleanupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No task options needed for windows.
|
||||||
|
func (c *criService) taskOpts(runtimeType string) []containerd.NewTaskOpts {
|
||||||
|
return nil
|
||||||
|
}
|
@ -18,6 +18,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
goruntime "runtime"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
@ -69,7 +70,8 @@ func (c *criService) PodSandboxStatus(ctx context.Context, r *runtime.PodSandbox
|
|||||||
func (c *criService) getIP(sandbox sandboxstore.Sandbox) (string, error) {
|
func (c *criService) getIP(sandbox sandboxstore.Sandbox) (string, error) {
|
||||||
config := sandbox.Config
|
config := sandbox.Config
|
||||||
|
|
||||||
if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
|
if goruntime.GOOS != "windows" &&
|
||||||
|
config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE {
|
||||||
// For sandboxes using the node network we are not
|
// For sandboxes using the node network we are not
|
||||||
// responsible for reporting the IP.
|
// responsible for reporting the IP.
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -57,8 +57,8 @@ func (c *criService) StopPodSandbox(ctx context.Context, r *runtime.StopPodSandb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.unmountSandboxFiles(id, sandbox.Config); err != nil {
|
if err := c.cleanupSandboxFiles(id, sandbox.Config); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmount sandbox files")
|
return nil, errors.Wrap(err, "failed to cleanup sandbox files")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only stop sandbox container when it's running or unknown.
|
// Only stop sandbox container when it's running or unknown.
|
||||||
|
@ -26,10 +26,6 @@ import (
|
|||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
cni "github.com/containerd/go-cni"
|
cni "github.com/containerd/go-cni"
|
||||||
runcapparmor "github.com/opencontainers/runc/libcontainer/apparmor"
|
|
||||||
runcseccomp "github.com/opencontainers/runc/libcontainer/seccomp"
|
|
||||||
runcsystem "github.com/opencontainers/runc/libcontainer/system"
|
|
||||||
"github.com/opencontainers/selinux/go-selinux"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -68,10 +64,6 @@ type criService struct {
|
|||||||
config criconfig.Config
|
config criconfig.Config
|
||||||
// imageFSPath is the path to image filesystem.
|
// imageFSPath is the path to image filesystem.
|
||||||
imageFSPath string
|
imageFSPath string
|
||||||
// apparmorEnabled indicates whether apparmor is enabled.
|
|
||||||
apparmorEnabled bool
|
|
||||||
// seccompEnabled indicates whether seccomp is enabled.
|
|
||||||
seccompEnabled bool
|
|
||||||
// os is an interface for all required os operations.
|
// os is an interface for all required os operations.
|
||||||
os osinterface.OS
|
os osinterface.OS
|
||||||
// sandboxStore stores all resources associated with sandboxes.
|
// sandboxStore stores all resources associated with sandboxes.
|
||||||
@ -107,8 +99,6 @@ func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIServi
|
|||||||
c := &criService{
|
c := &criService{
|
||||||
config: config,
|
config: config,
|
||||||
client: client,
|
client: client,
|
||||||
apparmorEnabled: runcapparmor.IsEnabled() && !config.DisableApparmor,
|
|
||||||
seccompEnabled: runcseccomp.IsEnabled(),
|
|
||||||
os: osinterface.RealOS{},
|
os: osinterface.RealOS{},
|
||||||
sandboxStore: sandboxstore.NewStore(),
|
sandboxStore: sandboxstore.NewStore(),
|
||||||
containerStore: containerstore.NewStore(),
|
containerStore: containerstore.NewStore(),
|
||||||
@ -119,20 +109,6 @@ func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIServi
|
|||||||
initialized: atomic.NewBool(false),
|
initialized: atomic.NewBool(false),
|
||||||
}
|
}
|
||||||
|
|
||||||
if runcsystem.RunningInUserNS() {
|
|
||||||
if !(config.DisableCgroup && !c.apparmorEnabled && config.RestrictOOMScoreAdj) {
|
|
||||||
logrus.Warn("Running containerd in a user namespace typically requires disable_cgroup, disable_apparmor, restrict_oom_score_adj set to be true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.config.EnableSelinux {
|
|
||||||
if !selinux.GetEnabled() {
|
|
||||||
logrus.Warn("Selinux is not supported")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selinux.SetDisabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
if client.SnapshotService(c.config.ContainerdConfig.Snapshotter) == nil {
|
if client.SnapshotService(c.config.ContainerdConfig.Snapshotter) == nil {
|
||||||
return nil, errors.Errorf("failed to find snapshotter %q", c.config.ContainerdConfig.Snapshotter)
|
return nil, errors.Errorf("failed to find snapshotter %q", c.config.ContainerdConfig.Snapshotter)
|
||||||
}
|
}
|
||||||
@ -140,23 +116,10 @@ func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIServi
|
|||||||
c.imageFSPath = imageFSPath(config.ContainerdRootDir, config.ContainerdConfig.Snapshotter)
|
c.imageFSPath = imageFSPath(config.ContainerdRootDir, config.ContainerdConfig.Snapshotter)
|
||||||
logrus.Infof("Get image filesystem path %q", c.imageFSPath)
|
logrus.Infof("Get image filesystem path %q", c.imageFSPath)
|
||||||
|
|
||||||
// Pod needs to attach to atleast loopback network and a non host network,
|
if err := c.initPlatform(); err != nil {
|
||||||
// hence networkAttachCount is 2. If there are more network configs the
|
return nil, errors.Wrap(err, "initialize platform")
|
||||||
// pod will be attached to all the networks but we will only use the ip
|
|
||||||
// of the default network interface as the pod IP.
|
|
||||||
c.netPlugin, err = cni.New(cni.WithMinNetworkCount(networkAttachCount),
|
|
||||||
cni.WithPluginConfDir(config.NetworkPluginConfDir),
|
|
||||||
cni.WithPluginMaxConfNum(config.NetworkPluginMaxConfNum),
|
|
||||||
cni.WithPluginDir([]string{config.NetworkPluginBinDir}))
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to initialize cni")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to load the config if it exists. Just log the error if load fails
|
|
||||||
// This is not disruptive for containerd to panic
|
|
||||||
if err := c.netPlugin.Load(cni.WithLoNetwork, cni.WithDefaultConf); err != nil {
|
|
||||||
logrus.WithError(err).Error("Failed to load cni during init, please check CRI plugin status before setting up network for pods")
|
|
||||||
}
|
|
||||||
// prepare streaming server
|
// prepare streaming server
|
||||||
c.streamServer, err = newStreamServer(c, config.StreamServerAddress, config.StreamServerPort, config.StreamIdleTimeout)
|
c.streamServer, err = newStreamServer(c, config.StreamServerAddress, config.StreamServerPort, config.StreamIdleTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
74
pkg/server/service_unix.go
Normal file
74
pkg/server/service_unix.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
cni "github.com/containerd/go-cni"
|
||||||
|
runcsystem "github.com/opencontainers/runc/libcontainer/system"
|
||||||
|
"github.com/opencontainers/selinux/go-selinux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// networkAttachCount is the minimum number of networks the PodSandbox
|
||||||
|
// attaches to
|
||||||
|
const networkAttachCount = 2
|
||||||
|
|
||||||
|
// initPlatform handles linux specific initialization for the CRI service.
|
||||||
|
func (c *criService) initPlatform() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if runcsystem.RunningInUserNS() {
|
||||||
|
if !(c.config.DisableCgroup && !c.apparmorEnabled() && c.config.RestrictOOMScoreAdj) {
|
||||||
|
logrus.Warn("Running containerd in a user namespace typically requires disable_cgroup, disable_apparmor, restrict_oom_score_adj set to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.EnableSelinux {
|
||||||
|
if !selinux.GetEnabled() {
|
||||||
|
logrus.Warn("Selinux is not supported")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
selinux.SetDisabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pod needs to attach to at least loopback network and a non host network,
|
||||||
|
// hence networkAttachCount is 2. If there are more network configs the
|
||||||
|
// pod will be attached to all the networks but we will only use the ip
|
||||||
|
// of the default network interface as the pod IP.
|
||||||
|
c.netPlugin, err = cni.New(cni.WithMinNetworkCount(networkAttachCount),
|
||||||
|
cni.WithPluginConfDir(c.config.NetworkPluginConfDir),
|
||||||
|
cni.WithPluginMaxConfNum(c.config.NetworkPluginMaxConfNum),
|
||||||
|
cni.WithPluginDir([]string{c.config.NetworkPluginBinDir}))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to initialize cni")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load the config if it exists. Just log the error if load fails
|
||||||
|
// This is not disruptive for containerd to panic
|
||||||
|
if err := c.netPlugin.Load(c.cniLoadOptions()...); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to load cni during init, please check CRI plugin status before setting up network for pods")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cniLoadOptions returns cni load options for the linux.
|
||||||
|
func (c *criService) cniLoadOptions() []cni.CNIOpt {
|
||||||
|
return []cni.CNIOpt{cni.WithLoNetwork, cni.WithDefaultConf}
|
||||||
|
}
|
35
pkg/server/service_windows.go
Normal file
35
pkg/server/service_windows.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
cni "github.com/containerd/go-cni"
|
||||||
|
)
|
||||||
|
|
||||||
|
// initPlatform handles linux specific initialization for the CRI service.
|
||||||
|
// TODO(windows): Initialize CRI plugin for windows
|
||||||
|
func (c *criService) initPlatform() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cniLoadOptions returns cni load options for the windows.
|
||||||
|
// TODO(windows): Implement CNI options for windows.
|
||||||
|
func (c *criService) cniLoadOptions() []cni.CNIOpt {
|
||||||
|
return nil
|
||||||
|
}
|
@ -22,7 +22,6 @@ import (
|
|||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
cni "github.com/containerd/go-cni"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
)
|
)
|
||||||
@ -42,9 +41,8 @@ func (c *criService) Status(ctx context.Context, r *runtime.StatusRequest) (*run
|
|||||||
Type: runtime.NetworkReady,
|
Type: runtime.NetworkReady,
|
||||||
Status: true,
|
Status: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the latest cni configuration to be in sync with the latest network configuration
|
// Load the latest cni configuration to be in sync with the latest network configuration
|
||||||
if err := c.netPlugin.Load(cni.WithLoNetwork, cni.WithDefaultConf); err != nil {
|
if err := c.netPlugin.Load(c.cniLoadOptions()...); err != nil {
|
||||||
log.G(ctx).WithError(err).Errorf("Failed to load cni configuration")
|
log.G(ctx).WithError(err).Errorf("Failed to load cni configuration")
|
||||||
}
|
}
|
||||||
// Check the status of the cni initialization
|
// Check the status of the cni initialization
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
cni "github.com/containerd/go-cni"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||||
@ -52,7 +51,7 @@ func (c *criService) UpdateRuntimeConfig(ctx context.Context, r *runtime.UpdateR
|
|||||||
if err := c.netPlugin.Status(); err == nil {
|
if err := c.netPlugin.Status(); err == nil {
|
||||||
log.G(ctx).Infof("Network plugin is ready, skip generating cni config from template %q", confTemplate)
|
log.G(ctx).Infof("Network plugin is ready, skip generating cni config from template %q", confTemplate)
|
||||||
return &runtime.UpdateRuntimeConfigResponse{}, nil
|
return &runtime.UpdateRuntimeConfigResponse{}, nil
|
||||||
} else if err := c.netPlugin.Load(cni.WithLoNetwork, cni.WithDefaultConf); err == nil {
|
} else if err := c.netPlugin.Load(c.cniLoadOptions()...); err == nil {
|
||||||
log.G(ctx).Infof("CNI config is successfully loaded, skip generating cni config from template %q", confTemplate)
|
log.G(ctx).Infof("CNI config is successfully loaded, skip generating cni config from template %q", confTemplate)
|
||||||
return &runtime.UpdateRuntimeConfigResponse{}, nil
|
return &runtime.UpdateRuntimeConfigResponse{}, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user