sandbox: make a clear dependency of cri plugins

Signed-off-by: Abel Feng <fshb1988@gmail.com>
This commit is contained in:
Abel Feng 2023-10-23 11:19:29 +08:00
parent e15c246550
commit c8012b6d74
10 changed files with 245 additions and 232 deletions

View File

@ -22,8 +22,10 @@ import (
fuzz "github.com/AdaLogics/go-fuzz-headers" fuzz "github.com/AdaLogics/go-fuzz-headers"
containerd "github.com/containerd/containerd/v2/client" containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config" criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/v2/pkg/cri/server" "github.com/containerd/containerd/v2/pkg/cri/server"
"github.com/containerd/containerd/v2/pkg/cri/server/base"
"github.com/containerd/containerd/v2/pkg/cri/server/images" "github.com/containerd/containerd/v2/pkg/cri/server/images"
) )
@ -38,12 +40,19 @@ func FuzzCRIServer(data []byte) int {
} }
defer client.Close() defer client.Close()
imageService, err := images.NewService(criconfig.Config{}, client) config := criconfig.Config{}
criBase := &base.CRIBase{
Config: config,
BaseOCISpecs: map[string]*oci.Spec{},
}
imageService, err := images.NewService(config, client)
if err != nil { if err != nil {
panic(err) panic(err)
} }
c, err := server.NewCRIService(criconfig.Config{}, imageService, client, nil) c, err := server.NewCRIService(criBase, imageService, client, nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -33,17 +33,21 @@ import (
"testing" "testing"
"time" "time"
containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/content"
"github.com/containerd/containerd/v2/leases"
"github.com/containerd/containerd/v2/namespaces"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
criserver "github.com/containerd/containerd/v2/pkg/cri/server"
"github.com/containerd/log" "github.com/containerd/log"
"github.com/containerd/log/logtest" "github.com/containerd/log/logtest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/content"
"github.com/containerd/containerd/v2/leases"
"github.com/containerd/containerd/v2/namespaces"
"github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
criserver "github.com/containerd/containerd/v2/pkg/cri/server"
"github.com/containerd/containerd/v2/pkg/cri/server/base"
"github.com/containerd/containerd/v2/pkg/cri/server/images"
) )
var ( var (
@ -465,5 +469,16 @@ func initLocalCRIPlugin(client *containerd.Client, tmpDir string, registryCfg cr
RootDir: filepath.Join(criWorkDir, "root"), RootDir: filepath.Join(criWorkDir, "root"),
StateDir: filepath.Join(criWorkDir, "state"), StateDir: filepath.Join(criWorkDir, "state"),
} }
return criserver.NewCRIService(cfg, client, nil)
criBase := &base.CRIBase{
Config: cfg,
BaseOCISpecs: map[string]*oci.Spec{},
}
imageService, err := images.NewService(cfg, client)
if err != nil {
panic(err)
}
return criserver.NewCRIService(criBase, imageService, client, nil)
} }

View File

@ -24,7 +24,6 @@ import (
"github.com/containerd/plugin/registry" "github.com/containerd/plugin/registry"
containerd "github.com/containerd/containerd/v2/client" containerd "github.com/containerd/containerd/v2/client"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/v2/pkg/cri/constants" "github.com/containerd/containerd/v2/pkg/cri/constants"
"github.com/containerd/containerd/v2/pkg/cri/nri" "github.com/containerd/containerd/v2/pkg/cri/nri"
"github.com/containerd/containerd/v2/pkg/cri/server" "github.com/containerd/containerd/v2/pkg/cri/server"
@ -33,22 +32,23 @@ import (
nriservice "github.com/containerd/containerd/v2/pkg/nri" nriservice "github.com/containerd/containerd/v2/pkg/nri"
"github.com/containerd/containerd/v2/platforms" "github.com/containerd/containerd/v2/platforms"
"github.com/containerd/containerd/v2/plugins" "github.com/containerd/containerd/v2/plugins"
"github.com/containerd/containerd/v2/services/warning"
) )
// Register CRI service plugin // Register CRI service plugin
func init() { func init() {
config := criconfig.DefaultConfig()
registry.Register(&plugin.Registration{ registry.Register(&plugin.Registration{
Type: plugins.GRPCPlugin, Type: plugins.GRPCPlugin,
ID: "cri-runtime-service", ID: "cri",
Config: &config,
Requires: []plugin.Type{ Requires: []plugin.Type{
plugins.CRIImagePlugin,
plugins.InternalPlugin,
plugins.SandboxControllerPlugin,
plugins.NRIApiPlugin,
plugins.EventPlugin, plugins.EventPlugin,
plugins.ServicePlugin, plugins.ServicePlugin,
plugins.NRIApiPlugin, plugins.LeasePlugin,
plugins.WarningPlugin, plugins.SandboxStorePlugin,
plugins.SandboxControllerPlugin,
}, },
InitFn: initCRIService, InitFn: initCRIService,
}) })
@ -56,29 +56,16 @@ func init() {
func initCRIService(ic *plugin.InitContext) (interface{}, error) { func initCRIService(ic *plugin.InitContext) (interface{}, error) {
ctx := ic.Context ctx := ic.Context
pluginConfig := ic.Config.(*criconfig.PluginConfig)
if warnings, err := criconfig.ValidatePluginConfig(ctx, pluginConfig); err != nil {
return nil, fmt.Errorf("invalid plugin config: %w", err)
} else if len(warnings) > 0 {
ws, err := ic.GetSingle(plugins.WarningPlugin)
if err != nil {
return nil, err
}
warn := ws.(warning.Service)
for _, w := range warnings {
warn.Emit(ctx, w)
}
}
// Get base CRI dependencies. // Get base CRI dependencies.
criBasePlugin, err := ic.GetByID(plugins.GRPCPlugin, "cri") criBasePlugin, err := ic.GetByID(plugins.InternalPlugin, "cri")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err) return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err)
} }
criBase := criBasePlugin.(*base.CRIBase) criBase := criBasePlugin.(*base.CRIBase)
// Get image service. // Get image service.
criImagePlugin, err := ic.GetByID(plugins.GRPCPlugin, "cri-image-service") criImagePlugin, err := ic.GetByID(plugins.CRIImagePlugin, "cri-image-service")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err) return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err)
} }
@ -92,8 +79,11 @@ func initCRIService(ic *plugin.InitContext) (interface{}, error) {
containerd.WithInMemoryServices(ic), containerd.WithInMemoryServices(ic),
containerd.WithInMemorySandboxControllers(ic), containerd.WithInMemorySandboxControllers(ic),
) )
if err != nil {
return nil, fmt.Errorf("failed to create containerd client: %w", err)
}
s, err := server.NewCRIService(criBase.Config, imageService, client, getNRIAPI(ic)) s, err := server.NewCRIService(criBase, imageService, client, getNRIAPI(ic))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create CRI service: %w", err) return nil, fmt.Errorf("failed to create CRI service: %w", err)
} }

View File

@ -17,29 +17,34 @@
package base package base
import ( import (
"context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"github.com/containerd/log" "github.com/containerd/log"
"github.com/containerd/plugin"
"github.com/containerd/plugin/registry"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"github.com/containerd/containerd" "github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/pkg/cri/config" criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/pkg/cri/constants" "github.com/containerd/containerd/v2/pkg/cri/constants"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/v2/platforms"
"github.com/containerd/containerd/plugin" "github.com/containerd/containerd/v2/plugins"
"github.com/containerd/containerd/plugin/registry" srvconfig "github.com/containerd/containerd/v2/services/server/config"
"github.com/containerd/containerd/plugins" "github.com/containerd/containerd/v2/services/warning"
) )
// CRIBase contains common dependencies for CRI's runtime, image, and podsandbox services. // CRIBase contains common dependencies for CRI's runtime, image, and podsandbox services.
type CRIBase struct { type CRIBase struct {
// Config contains all configurations. // Config contains all configurations.
Config criconfig.Config Config criconfig.Config
// Client is an instance of the containerd client // BaseOCISpecs contains cached OCI specs loaded via `Runtime.BaseRuntimeSpec`
Client *containerd.Client BaseOCISpecs map[string]*oci.Spec
} }
func init() { func init() {
@ -47,13 +52,23 @@ func init() {
// Base plugin that other CRI services depend on. // Base plugin that other CRI services depend on.
registry.Register(&plugin.Registration{ registry.Register(&plugin.Registration{
Type: plugins.GRPCPlugin, Type: plugins.InternalPlugin,
ID: "cri", ID: "cri",
Config: &config, Config: &config,
Requires: []plugin.Type{ Requires: []plugin.Type{
plugins.EventPlugin, plugins.WarningPlugin,
plugins.ServicePlugin, },
plugins.NRIApiPlugin, ConfigMigration: func(ctx context.Context, version int, plugins map[string]interface{}) error {
if version >= srvconfig.CurrentConfigVersion {
return nil
}
c, ok := plugins["io.containerd.grpc.v1.cri"]
if !ok {
return nil
}
conf := c.(map[string]interface{})
plugins["io.containerd.internal.v1.cri"] = conf
return nil
}, },
InitFn: initCRIBase, InitFn: initCRIBase,
}) })
@ -64,8 +79,17 @@ func initCRIBase(ic *plugin.InitContext) (interface{}, error) {
ic.Meta.Exports = map[string]string{"CRIVersion": constants.CRIVersion} ic.Meta.Exports = map[string]string{"CRIVersion": constants.CRIVersion}
ctx := ic.Context ctx := ic.Context
pluginConfig := ic.Config.(*criconfig.PluginConfig) pluginConfig := ic.Config.(*criconfig.PluginConfig)
if err := criconfig.ValidatePluginConfig(ctx, pluginConfig); err != nil { if warnings, err := criconfig.ValidatePluginConfig(ctx, pluginConfig); err != nil {
return nil, fmt.Errorf("invalid plugin config: %w", err) return nil, fmt.Errorf("invalid plugin config: %w", err)
} else if len(warnings) > 0 {
ws, err := ic.GetSingle(plugins.WarningPlugin)
if err != nil {
return nil, err
}
warn := ws.(warning.Service)
for _, w := range warnings {
warn.Emit(ctx, w)
}
} }
c := criconfig.Config{ c := criconfig.Config{
@ -82,23 +106,55 @@ func initCRIBase(ic *plugin.InitContext) (interface{}, error) {
return nil, fmt.Errorf("failed to set glog level: %w", err) return nil, fmt.Errorf("failed to set glog level: %w", err)
} }
log.G(ctx).Info("Connect containerd service") ociSpec, err := loadBaseOCISpecs(&c)
client, err := containerd.New(
"",
containerd.WithDefaultNamespace(constants.K8sContainerdNamespace),
containerd.WithDefaultPlatform(platforms.Default()),
containerd.WithInMemoryServices(ic),
)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create containerd client: %w", err) return nil, fmt.Errorf("failed to create load basic oci spec: %w", err)
} }
return &CRIBase{ return &CRIBase{
Config: c, Config: c,
Client: client, BaseOCISpecs: ociSpec,
}, nil }, nil
} }
func loadBaseOCISpecs(config *criconfig.Config) (map[string]*oci.Spec, error) {
specs := map[string]*oci.Spec{}
for _, cfg := range config.Runtimes {
if cfg.BaseRuntimeSpec == "" {
continue
}
// Don't load same file twice
if _, ok := specs[cfg.BaseRuntimeSpec]; ok {
continue
}
spec, err := loadOCISpec(cfg.BaseRuntimeSpec)
if err != nil {
return nil, fmt.Errorf("failed to load base OCI spec from file: %s: %w", cfg.BaseRuntimeSpec, err)
}
specs[cfg.BaseRuntimeSpec] = spec
}
return specs, nil
}
func loadOCISpec(filename string) (*oci.Spec, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open base OCI spec: %s: %w", filename, err)
}
defer file.Close()
spec := oci.Spec{}
if err := json.NewDecoder(file).Decode(&spec); err != nil {
return nil, fmt.Errorf("failed to parse base OCI spec file: %w", err)
}
return &spec, nil
}
// Set glog level. // Set glog level.
func setGLogLevel() error { func setGLogLevel() error {
l := log.GetLevel() l := log.GetLevel()

View File

@ -0,0 +1,60 @@
/*
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 base
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
)
func TestLoadBaseOCISpec(t *testing.T) {
spec := oci.Spec{Version: "1.0.2", Hostname: "default"}
file, err := os.CreateTemp("", "spec-test-")
require.NoError(t, err)
defer func() {
assert.NoError(t, file.Close())
assert.NoError(t, os.RemoveAll(file.Name()))
}()
err = json.NewEncoder(file).Encode(&spec)
assert.NoError(t, err)
config := criconfig.Config{}
config.Runtimes = map[string]criconfig.Runtime{
"runc": {BaseRuntimeSpec: file.Name()},
}
specs, err := loadBaseOCISpecs(&config)
assert.NoError(t, err)
assert.Len(t, specs, 1)
out, ok := specs[file.Name()]
assert.True(t, ok, "expected spec with file name %q", file.Name())
assert.Equal(t, "1.0.2", out.Version)
assert.Equal(t, "default", out.Hostname)
}

View File

@ -30,6 +30,7 @@ import (
containerd "github.com/containerd/containerd/v2/client" containerd "github.com/containerd/containerd/v2/client"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config" criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/v2/pkg/cri/constants"
"github.com/containerd/containerd/v2/pkg/cri/server/base" "github.com/containerd/containerd/v2/pkg/cri/server/base"
imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image" imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image"
snapshotstore "github.com/containerd/containerd/v2/pkg/cri/store/snapshot" snapshotstore "github.com/containerd/containerd/v2/pkg/cri/store/snapshot"
@ -42,18 +43,33 @@ import (
func init() { func init() {
registry.Register(&plugin.Registration{ registry.Register(&plugin.Registration{
Type: plugins.GRPCPlugin, Type: plugins.CRIImagePlugin,
ID: "cri-image-service", ID: "cri-image-service",
Requires: []plugin.Type{
plugins.LeasePlugin,
plugins.EventPlugin,
plugins.SandboxStorePlugin,
plugins.InternalPlugin,
plugins.ServicePlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) { InitFn: func(ic *plugin.InitContext) (interface{}, error) {
// Get base CRI dependencies. // Get base CRI dependencies.
criPlugin, err := ic.GetByID(plugins.GRPCPlugin, "cri") criPlugin, err := ic.GetByID(plugins.InternalPlugin, "cri")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err) return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err)
} }
cri := criPlugin.(*base.CRIBase) cri := criPlugin.(*base.CRIBase)
service, err := NewService(cri.Config, cri.Client) client, err := containerd.New(
"",
containerd.WithDefaultNamespace(constants.K8sContainerdNamespace),
containerd.WithDefaultPlatform(platforms.Default()),
containerd.WithInMemoryServices(ic),
)
if err != nil {
return nil, fmt.Errorf("unable to init client for cri image service: %w", err)
}
service, err := NewService(cri.Config, client)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create image service: %w", err) return nil, fmt.Errorf("failed to create image service: %w", err)
} }

View File

@ -22,6 +22,8 @@ import (
"time" "time"
"github.com/containerd/log" "github.com/containerd/log"
"github.com/containerd/plugin"
"github.com/containerd/plugin/registry"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1" runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
eventtypes "github.com/containerd/containerd/v2/api/events" eventtypes "github.com/containerd/containerd/v2/api/events"
@ -30,6 +32,8 @@ import (
"github.com/containerd/containerd/v2/oci" "github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config" criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/v2/pkg/cri/constants" "github.com/containerd/containerd/v2/pkg/cri/constants"
"github.com/containerd/containerd/v2/pkg/cri/server/base"
"github.com/containerd/containerd/v2/pkg/cri/server/images"
imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image" imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image"
sandboxstore "github.com/containerd/containerd/v2/pkg/cri/store/sandbox" sandboxstore "github.com/containerd/containerd/v2/pkg/cri/store/sandbox"
ctrdutil "github.com/containerd/containerd/v2/pkg/cri/util" ctrdutil "github.com/containerd/containerd/v2/pkg/cri/util"
@ -38,8 +42,6 @@ import (
"github.com/containerd/containerd/v2/plugins" "github.com/containerd/containerd/v2/plugins"
"github.com/containerd/containerd/v2/protobuf" "github.com/containerd/containerd/v2/protobuf"
"github.com/containerd/containerd/v2/sandbox" "github.com/containerd/containerd/v2/sandbox"
"github.com/containerd/plugin"
"github.com/containerd/plugin/registry"
) )
func init() { func init() {
@ -49,20 +51,44 @@ func init() {
Requires: []plugin.Type{ Requires: []plugin.Type{
plugins.EventPlugin, plugins.EventPlugin,
plugins.LeasePlugin, plugins.LeasePlugin,
plugins.SandboxStorePlugin,
plugins.InternalPlugin,
plugins.CRIImagePlugin,
plugins.ServicePlugin, plugins.ServicePlugin,
}, },
InitFn: func(ic *plugin.InitContext) (interface{}, error) { InitFn: func(ic *plugin.InitContext) (interface{}, error) {
c := Controller{}
client, err := containerd.New( client, err := containerd.New(
"", "",
containerd.WithDefaultNamespace(constants.K8sContainerdNamespace), containerd.WithDefaultNamespace(constants.K8sContainerdNamespace),
containerd.WithDefaultPlatform(platforms.Default()), containerd.WithDefaultPlatform(platforms.Default()),
containerd.WithInMemoryServices(ic), containerd.WithInMemoryServices(ic),
) )
if err != nil {
return nil, fmt.Errorf("unable to init client for podsandbox: %w", err)
}
// Get base CRI dependencies.
criBasePlugin, err := ic.GetByID(plugins.InternalPlugin, "cri")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err) return nil, fmt.Errorf("unable to load CRI service base dependencies: %w", err)
} }
c.client = client criBase := criBasePlugin.(*base.CRIBase)
// Get image service.
criImagePlugin, err := ic.GetByID(plugins.CRIImagePlugin, "cri-image-service")
if err != nil {
return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err)
}
imageService := criImagePlugin.(*images.CRIImageService)
c := Controller{
client: client,
config: criBase.Config,
os: osinterface.RealOS{},
baseOCISpecs: criBase.BaseOCISpecs,
imageService: imageService,
store: NewStore(),
}
return &c, nil return &c, nil
}, },
}) })
@ -103,20 +129,11 @@ type Controller struct {
} }
func (c *Controller) Init( func (c *Controller) Init(
config criconfig.Config,
sandboxStore *sandboxstore.Store, sandboxStore *sandboxstore.Store,
os osinterface.OS,
cri CRIService, cri CRIService,
imageService ImageService,
baseOCISpecs map[string]*oci.Spec,
) { ) {
c.cri = cri c.cri = cri
c.config = config
c.sandboxStore = sandboxStore c.sandboxStore = sandboxStore
c.os = os
c.baseOCISpecs = baseOCISpecs
c.store = NewStore()
c.imageService = imageService
} }
var _ sandbox.Controller = (*Controller)(nil) var _ sandbox.Controller = (*Controller)(nil)

View File

@ -18,12 +18,9 @@ package server
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"os"
"path/filepath"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -38,6 +35,7 @@ import (
criconfig "github.com/containerd/containerd/v2/pkg/cri/config" criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
"github.com/containerd/containerd/v2/pkg/cri/instrument" "github.com/containerd/containerd/v2/pkg/cri/instrument"
"github.com/containerd/containerd/v2/pkg/cri/nri" "github.com/containerd/containerd/v2/pkg/cri/nri"
"github.com/containerd/containerd/v2/pkg/cri/server/base"
"github.com/containerd/containerd/v2/pkg/cri/server/podsandbox" "github.com/containerd/containerd/v2/pkg/cri/server/podsandbox"
containerstore "github.com/containerd/containerd/v2/pkg/cri/store/container" containerstore "github.com/containerd/containerd/v2/pkg/cri/store/container"
imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image" imagestore "github.com/containerd/containerd/v2/pkg/cri/store/image"
@ -47,7 +45,6 @@ import (
ctrdutil "github.com/containerd/containerd/v2/pkg/cri/util" ctrdutil "github.com/containerd/containerd/v2/pkg/cri/util"
osinterface "github.com/containerd/containerd/v2/pkg/os" osinterface "github.com/containerd/containerd/v2/pkg/os"
"github.com/containerd/containerd/v2/pkg/registrar" "github.com/containerd/containerd/v2/pkg/registrar"
"github.com/containerd/containerd/v2/plugins"
"github.com/containerd/containerd/v2/sandbox" "github.com/containerd/containerd/v2/sandbox"
) )
@ -134,15 +131,17 @@ type criService struct {
} }
// NewCRIService returns a new instance of CRIService // NewCRIService returns a new instance of CRIService
func NewCRIService(config criconfig.Config, imageService imageService, client *containerd.Client, nri *nri.API) (CRIService, error) { func NewCRIService(criBase *base.CRIBase, imageService imageService, client *containerd.Client, nri *nri.API) (CRIService, error) {
var err error var err error
labels := label.NewStore() labels := label.NewStore()
config := criBase.Config
c := &criService{ c := &criService{
imageService: imageService, imageService: imageService,
config: config, config: config,
client: client, client: client,
imageFSPaths: imageService.ImageFSPaths(), imageFSPaths: imageService.ImageFSPaths(),
os: osinterface.RealOS{}, os: osinterface.RealOS{},
baseOCISpecs: criBase.BaseOCISpecs,
sandboxStore: sandboxstore.NewStore(labels), sandboxStore: sandboxstore.NewStore(labels),
containerStore: containerstore.NewStore(labels), containerStore: containerstore.NewStore(labels),
sandboxNameIndex: registrar.NewRegistrar(), sandboxNameIndex: registrar.NewRegistrar(),
@ -183,15 +182,8 @@ func NewCRIService(config criconfig.Config, imageService imageService, client *c
} }
} }
// Preload base OCI specs
c.baseOCISpecs, err = loadBaseOCISpecs(&config)
if err != nil {
return nil, err
}
podSandboxController := client.SandboxController(string(criconfig.ModePodSandbox)).(*podsandbox.Controller) podSandboxController := client.SandboxController(string(criconfig.ModePodSandbox)).(*podsandbox.Controller)
// Initialize pod sandbox controller podSandboxController.Init(c.sandboxStore, c)
podSandboxController.Init(config, c.sandboxStore, c.os, c, c.imageService, c.baseOCISpecs)
c.nri = nri c.nri = nri
@ -335,50 +327,3 @@ func (c *criService) register(s *grpc.Server) error {
runtime.RegisterImageServiceServer(s, instrumented) runtime.RegisterImageServiceServer(s, instrumented)
return nil return nil
} }
// imageFSPath returns containerd image filesystem path.
// Note that if containerd changes directory layout, we also needs to change this.
func imageFSPath(rootDir, snapshotter string) string {
return filepath.Join(rootDir, plugins.SnapshotPlugin.String()+"."+snapshotter)
}
func loadOCISpec(filename string) (*oci.Spec, error) {
file, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("failed to open base OCI spec: %s: %w", filename, err)
}
defer file.Close()
spec := oci.Spec{}
if err := json.NewDecoder(file).Decode(&spec); err != nil {
return nil, fmt.Errorf("failed to parse base OCI spec file: %w", err)
}
return &spec, nil
}
func loadBaseOCISpecs(config *criconfig.Config) (map[string]*oci.Spec, error) {
specs := map[string]*oci.Spec{}
for _, cfg := range config.Runtimes {
if cfg.BaseRuntimeSpec == "" {
continue
}
// Don't load same file twice
if _, ok := specs[cfg.BaseRuntimeSpec]; ok {
continue
}
spec, err := loadOCISpec(cfg.BaseRuntimeSpec)
if err != nil {
return nil, fmt.Errorf("failed to load base OCI spec from file: %s: %w", cfg.BaseRuntimeSpec, err)
}
if spec.Process != nil && spec.Process.Capabilities != nil && len(spec.Process.Capabilities.Inheritable) > 0 {
log.L.WithField("base_runtime_spec", cfg.BaseRuntimeSpec).Warn("Provided base runtime spec includes inheritable capabilities, which may be unsafe. See CVE-2022-24769 for more details.")
}
specs[cfg.BaseRuntimeSpec] = spec
}
return specs, nil
}

View File

@ -17,25 +17,13 @@
package server package server
import ( import (
"bytes"
"context" "context"
"encoding/json"
"io"
"os"
"testing"
"github.com/containerd/go-cni" "github.com/containerd/go-cni"
"github.com/containerd/log"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1" runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
"github.com/containerd/containerd/v2/api/types" "github.com/containerd/containerd/v2/api/types"
"github.com/containerd/containerd/v2/errdefs" "github.com/containerd/containerd/v2/errdefs"
"github.com/containerd/containerd/v2/oci"
criconfig "github.com/containerd/containerd/v2/pkg/cri/config"
containerstore "github.com/containerd/containerd/v2/pkg/cri/store/container" containerstore "github.com/containerd/containerd/v2/pkg/cri/store/container"
"github.com/containerd/containerd/v2/pkg/cri/store/label" "github.com/containerd/containerd/v2/pkg/cri/store/label"
sandboxstore "github.com/containerd/containerd/v2/pkg/cri/store/sandbox" sandboxstore "github.com/containerd/containerd/v2/pkg/cri/store/sandbox"
@ -103,88 +91,3 @@ func newTestCRIService() *criService {
sandboxService: &fakeSandboxService{}, sandboxService: &fakeSandboxService{},
} }
} }
func TestLoadBaseOCISpec(t *testing.T) {
spec := oci.Spec{Version: "1.0.2", Hostname: "default"}
file, err := os.CreateTemp("", "spec-test-")
require.NoError(t, err)
defer func() {
assert.NoError(t, file.Close())
assert.NoError(t, os.RemoveAll(file.Name()))
}()
err = json.NewEncoder(file).Encode(&spec)
assert.NoError(t, err)
config := criconfig.Config{}
config.Runtimes = map[string]criconfig.Runtime{
"runc": {BaseRuntimeSpec: file.Name()},
}
specs, err := loadBaseOCISpecs(&config)
assert.NoError(t, err)
assert.Len(t, specs, 1)
out, ok := specs[file.Name()]
assert.True(t, ok, "expected spec with file name %q", file.Name())
assert.Equal(t, "1.0.2", out.Version)
assert.Equal(t, "default", out.Hostname)
}
func Test_loadBaseOCISpecs(t *testing.T) {
spec := oci.Spec{
Version: "1.0.2",
Hostname: "default",
Process: &specs.Process{
Capabilities: &specs.LinuxCapabilities{
Inheritable: []string{"CAP_NET_RAW"},
},
},
}
file, err := os.CreateTemp("", "spec-test-")
require.NoError(t, err)
defer func() {
assert.NoError(t, file.Close())
assert.NoError(t, os.RemoveAll(file.Name()))
}()
err = json.NewEncoder(file).Encode(&spec)
require.NoError(t, err)
config := criconfig.Config{}
config.Runtimes = map[string]criconfig.Runtime{
"runc": {BaseRuntimeSpec: file.Name()},
}
var buffer bytes.Buffer
logger := &logrus.Logger{
Out: &buffer,
Formatter: new(logrus.TextFormatter),
Hooks: make(logrus.LevelHooks),
Level: logrus.InfoLevel,
ExitFunc: os.Exit,
ReportCaller: false,
}
log.L = logrus.NewEntry(logger)
tests := []struct {
name string
args *criconfig.Config
message string
}{
{
name: "args is not nil,print warning",
args: &config,
message: "Provided base runtime spec includes inheritable capabilities, which may be unsafe. See CVE-2022-24769 for more details.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
loadBaseOCISpecs(tt.args)
readAll, _ := io.ReadAll(&buffer)
if tt.message != "" {
assert.Contains(t, string(readAll), tt.message)
}
})
}
}

View File

@ -67,6 +67,8 @@ const (
ImageVerifierPlugin plugin.Type = "io.containerd.image-verifier.v1" ImageVerifierPlugin plugin.Type = "io.containerd.image-verifier.v1"
// WarningPlugin implements a warning service // WarningPlugin implements a warning service
WarningPlugin plugin.Type = "io.containerd.warning.v1" WarningPlugin plugin.Type = "io.containerd.warning.v1"
// CRIImagePlugin implements a cri image service
CRIImagePlugin plugin.Type = "io.containerd.cri.image.v1"
) )
const ( const (