Extract image service from CRI
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
This commit is contained in:
parent
662ff50b73
commit
3557ac884b
@ -107,7 +107,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
|
|
||||||
// Prepare container image snapshot. For container, the image should have
|
// Prepare container image snapshot. For container, the image should have
|
||||||
// been pulled before creating the container, so do not ensure the image.
|
// been pulled before creating the container, so do not ensure the image.
|
||||||
image, err := c.localResolve(config.GetImage().GetImage())
|
image, err := c.LocalResolve(config.GetImage().GetImage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve image %q: %w", config.GetImage().GetImage(), err)
|
return nil, fmt.Errorf("failed to resolve image %q: %w", config.GetImage().GetImage(), err)
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
|
|||||||
|
|
||||||
// Set snapshotter before any other options.
|
// Set snapshotter before any other options.
|
||||||
opts := []containerd.NewContainerOpts{
|
opts := []containerd.NewContainerOpts{
|
||||||
containerd.WithSnapshotter(c.runtimeSnapshotter(ctx, ociRuntime)),
|
containerd.WithSnapshotter(c.RuntimeSnapshotter(ctx, ociRuntime)),
|
||||||
// Prepare container rootfs. This is always writeable even if
|
// Prepare container rootfs. This is always writeable even if
|
||||||
// the container wants a readonly rootfs since we want to give
|
// the container wants a readonly rootfs since we want to give
|
||||||
// the runtime (runc) a chance to modify (e.g. to create mount
|
// the runtime (runc) a chance to modify (e.g. to create mount
|
||||||
@ -400,17 +400,6 @@ func (c *criService) runtimeSpec(id string, platform platforms.Platform, baseSpe
|
|||||||
return spec, nil
|
return spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overrides the default snapshotter if Snapshotter is set for this runtime.
|
|
||||||
// See https://github.com/containerd/containerd/issues/6657
|
|
||||||
func (c *criService) runtimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string {
|
|
||||||
if ociRuntime.Snapshotter == "" {
|
|
||||||
return c.config.ContainerdConfig.Snapshotter
|
|
||||||
}
|
|
||||||
|
|
||||||
log.G(ctx).Debugf("Set snapshotter for runtime %s to %s", ociRuntime.Type, ociRuntime.Snapshotter)
|
|
||||||
return ociRuntime.Snapshotter
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// relativeRootfsPath is the rootfs path relative to bundle path.
|
// relativeRootfsPath is the rootfs path relative to bundle path.
|
||||||
relativeRootfsPath = "rootfs"
|
relativeRootfsPath = "rootfs"
|
||||||
|
@ -435,35 +435,3 @@ func TestBaseRuntimeSpec(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, filepath.Join("/", constants.K8sContainerdNamespace, "id1"), out.Linux.CgroupsPath)
|
assert.Equal(t, filepath.Join("/", constants.K8sContainerdNamespace, "id1"), out.Linux.CgroupsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuntimeSnapshotter(t *testing.T) {
|
|
||||||
defaultRuntime := config.Runtime{
|
|
||||||
Snapshotter: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
fooRuntime := config.Runtime{
|
|
||||||
Snapshotter: "devmapper",
|
|
||||||
}
|
|
||||||
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
runtime config.Runtime
|
|
||||||
expectSnapshotter string
|
|
||||||
}{
|
|
||||||
"should return default snapshotter when runtime.Snapshotter is not set": {
|
|
||||||
runtime: defaultRuntime,
|
|
||||||
expectSnapshotter: config.DefaultConfig().Snapshotter,
|
|
||||||
},
|
|
||||||
"should return overridden snapshotter when runtime.Snapshotter is set": {
|
|
||||||
runtime: fooRuntime,
|
|
||||||
expectSnapshotter: "devmapper",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
|
||||||
cri := newTestCRIService()
|
|
||||||
cri.config = config.Config{
|
|
||||||
PluginConfig: config.DefaultConfig(),
|
|
||||||
}
|
|
||||||
assert.Equal(t, test.expectSnapshotter, cri.runtimeSnapshotter(context.Background(), test.runtime))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -37,7 +37,7 @@ func (c *criService) containerMetrics(
|
|||||||
) (*runtime.ContainerStats, error) {
|
) (*runtime.ContainerStats, error) {
|
||||||
var cs runtime.ContainerStats
|
var cs runtime.ContainerStats
|
||||||
var usedBytes, inodesUsed uint64
|
var usedBytes, inodesUsed uint64
|
||||||
sn, err := c.snapshotStore.Get(meta.ID)
|
sn, err := c.GetSnapshot(meta.ID)
|
||||||
// If snapshotstore doesn't have cached snapshot information
|
// If snapshotstore doesn't have cached snapshot information
|
||||||
// set WritableLayer usage to zero
|
// set WritableLayer usage to zero
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -34,7 +34,7 @@ func (c *criService) containerMetrics(
|
|||||||
) (*runtime.ContainerStats, error) {
|
) (*runtime.ContainerStats, error) {
|
||||||
var cs runtime.ContainerStats
|
var cs runtime.ContainerStats
|
||||||
var usedBytes, inodesUsed uint64
|
var usedBytes, inodesUsed uint64
|
||||||
sn, err := c.snapshotStore.Get(meta.ID)
|
sn, err := c.GetSnapshot(meta.ID)
|
||||||
// If snapshotstore doesn't have cached snapshot information
|
// If snapshotstore doesn't have cached snapshot information
|
||||||
// set WritableLayer usage to zero
|
// set WritableLayer usage to zero
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/pkg/cri/sbserver/images"
|
||||||
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
||||||
|
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
@ -42,13 +43,13 @@ func (c *criService) ContainerStatus(ctx context.Context, r *runtime.ContainerSt
|
|||||||
// * ImageRef in container status is repo digest.
|
// * ImageRef in container status is repo digest.
|
||||||
spec := container.Config.GetImage()
|
spec := container.Config.GetImage()
|
||||||
imageRef := container.ImageRef
|
imageRef := container.ImageRef
|
||||||
image, err := c.imageStore.Get(imageRef)
|
image, err := c.GetImage(imageRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errdefs.IsNotFound(err) {
|
if !errdefs.IsNotFound(err) {
|
||||||
return nil, fmt.Errorf("failed to get image %q: %w", imageRef, err)
|
return nil, fmt.Errorf("failed to get image %q: %w", imageRef, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
repoTags, repoDigests := parseImageReferences(image.References)
|
repoTags, repoDigests := images.ParseImageReferences(image.References)
|
||||||
if len(repoTags) > 0 {
|
if len(repoTags) > 0 {
|
||||||
// Based on current behavior of dockershim, this field should be
|
// Based on current behavior of dockershim, this field should be
|
||||||
// image tag.
|
// image tag.
|
||||||
|
@ -18,9 +18,12 @@ package sbserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
|
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||||
|
|
||||||
@ -228,8 +231,9 @@ func TestContainerStatus(t *testing.T) {
|
|||||||
assert.NoError(t, c.containerStore.Add(container))
|
assert.NoError(t, c.containerStore.Add(container))
|
||||||
}
|
}
|
||||||
if test.imageExist {
|
if test.imageExist {
|
||||||
c.imageStore, err = imagestore.NewFakeStore([]imagestore.Image{*image})
|
imageStore, err := imagestore.NewFakeStore([]imagestore.Image{*image})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
c.imageService = &fakeImageService{imageStore: imageStore}
|
||||||
}
|
}
|
||||||
resp, err := c.ContainerStatus(context.Background(), &runtime.ContainerStatusRequest{ContainerId: container.ID})
|
resp, err := c.ContainerStatus(context.Background(), &runtime.ContainerStatusRequest{ContainerId: container.ID})
|
||||||
if test.expectErr {
|
if test.expectErr {
|
||||||
@ -247,6 +251,27 @@ func TestContainerStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeImageService struct {
|
||||||
|
runtime.ImageServiceServer
|
||||||
|
imageStore *imagestore.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeImageService) RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeImageService) UpdateImage(ctx context.Context, r string) error { return nil }
|
||||||
|
|
||||||
|
func (s *fakeImageService) GetImage(id string) (imagestore.Image, error) { return s.imageStore.Get(id) }
|
||||||
|
|
||||||
|
func (s *fakeImageService) GetSnapshot(key string) (snapshotstore.Snapshot, error) {
|
||||||
|
return snapshotstore.Snapshot{}, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *fakeImageService) LocalResolve(refOrID string) (imagestore.Image, error) {
|
||||||
|
return imagestore.Image{}, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func patchExceptedWithState(expected *runtime.ContainerStatus, state runtime.ContainerState) {
|
func patchExceptedWithState(expected *runtime.ContainerStatus, state runtime.ContainerState) {
|
||||||
expected.State = state
|
expected.State = state
|
||||||
switch state {
|
switch state {
|
||||||
|
@ -133,7 +133,7 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore
|
|||||||
// default SIGTERM is still better than returning error and leaving
|
// default SIGTERM is still better than returning error and leaving
|
||||||
// the container unstoppable. (See issue #990)
|
// the container unstoppable. (See issue #990)
|
||||||
// TODO(random-liu): Remove this logic when containerd 1.2 is deprecated.
|
// TODO(random-liu): Remove this logic when containerd 1.2 is deprecated.
|
||||||
image, err := c.imageStore.Get(container.ImageRef)
|
image, err := c.GetImage(container.ImageRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errdefs.IsNotFound(err) {
|
if !errdefs.IsNotFound(err) {
|
||||||
return fmt.Errorf("failed to get image %q: %w", container.ImageRef, err)
|
return fmt.Errorf("failed to get image %q: %w", container.ImageRef, err)
|
||||||
|
@ -350,13 +350,13 @@ func (em *eventMonitor) handleEvent(any interface{}) error {
|
|||||||
}
|
}
|
||||||
case *eventtypes.ImageCreate:
|
case *eventtypes.ImageCreate:
|
||||||
logrus.Infof("ImageCreate event %+v", e)
|
logrus.Infof("ImageCreate event %+v", e)
|
||||||
return em.c.updateImage(ctx, e.Name)
|
return em.c.UpdateImage(ctx, e.Name)
|
||||||
case *eventtypes.ImageUpdate:
|
case *eventtypes.ImageUpdate:
|
||||||
logrus.Infof("ImageUpdate event %+v", e)
|
logrus.Infof("ImageUpdate event %+v", e)
|
||||||
return em.c.updateImage(ctx, e.Name)
|
return em.c.UpdateImage(ctx, e.Name)
|
||||||
case *eventtypes.ImageDelete:
|
case *eventtypes.ImageDelete:
|
||||||
logrus.Infof("ImageDelete event %+v", e)
|
logrus.Infof("ImageDelete event %+v", e)
|
||||||
return em.c.updateImage(ctx, e.Name)
|
return em.c.UpdateImage(ctx, e.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -41,11 +41,9 @@ import (
|
|||||||
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
||||||
runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1"
|
runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/reference/docker"
|
|
||||||
runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
|
runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
|
|
||||||
runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
|
runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
|
||||||
imagedigest "github.com/opencontainers/go-digest"
|
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||||
)
|
)
|
||||||
@ -83,10 +81,7 @@ const (
|
|||||||
containerKindSandbox = "sandbox"
|
containerKindSandbox = "sandbox"
|
||||||
// containerKindContainer is a label value indicating container is application container
|
// containerKindContainer is a label value indicating container is application container
|
||||||
containerKindContainer = "container"
|
containerKindContainer = "container"
|
||||||
// imageLabelKey is the label key indicating the image is managed by cri plugin.
|
|
||||||
imageLabelKey = criContainerdPrefix + ".image"
|
|
||||||
// imageLabelValue is the label value indicating the image is managed by cri plugin.
|
|
||||||
imageLabelValue = "managed"
|
|
||||||
// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
|
// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
|
||||||
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
|
||||||
@ -140,51 +135,6 @@ func criContainerStateToString(state runtime.ContainerState) string {
|
|||||||
return runtime.ContainerState_name[int32(state)]
|
return runtime.ContainerState_name[int32(state)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference.
|
|
||||||
func getRepoDigestAndTag(namedRef docker.Named, digest imagedigest.Digest, schema1 bool) (string, string) {
|
|
||||||
var repoTag, repoDigest string
|
|
||||||
if _, ok := namedRef.(docker.NamedTagged); ok {
|
|
||||||
repoTag = namedRef.String()
|
|
||||||
}
|
|
||||||
if _, ok := namedRef.(docker.Canonical); ok {
|
|
||||||
repoDigest = namedRef.String()
|
|
||||||
} else if !schema1 {
|
|
||||||
// digest is not actual repo digest for schema1 image.
|
|
||||||
repoDigest = namedRef.Name() + "@" + digest.String()
|
|
||||||
}
|
|
||||||
return repoDigest, repoTag
|
|
||||||
}
|
|
||||||
|
|
||||||
// localResolve resolves image reference locally and returns corresponding image metadata. It
|
|
||||||
// returns errdefs.ErrNotFound if the reference doesn't exist.
|
|
||||||
func (c *criService) localResolve(refOrID string) (imagestore.Image, error) {
|
|
||||||
getImageID := func(refOrId string) string {
|
|
||||||
if _, err := imagedigest.Parse(refOrID); err == nil {
|
|
||||||
return refOrID
|
|
||||||
}
|
|
||||||
return func(ref string) string {
|
|
||||||
// ref is not image id, try to resolve it locally.
|
|
||||||
// TODO(random-liu): Handle this error better for debugging.
|
|
||||||
normalized, err := docker.ParseDockerRef(ref)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
id, err := c.imageStore.Resolve(normalized.String())
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}(refOrID)
|
|
||||||
}
|
|
||||||
|
|
||||||
imageID := getImageID(refOrID)
|
|
||||||
if imageID == "" {
|
|
||||||
// Try to treat ref as imageID
|
|
||||||
imageID = refOrID
|
|
||||||
}
|
|
||||||
return c.imageStore.Get(imageID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// toContainerdImage converts an image object in image store to containerd image handler.
|
// toContainerdImage converts an image object in image store to containerd image handler.
|
||||||
func (c *criService) toContainerdImage(ctx context.Context, image imagestore.Image) (containerd.Image, error) {
|
func (c *criService) toContainerdImage(ctx context.Context, image imagestore.Image) (containerd.Image, error) {
|
||||||
// image should always have at least one reference.
|
// image should always have at least one reference.
|
||||||
@ -213,30 +163,6 @@ func getUserFromImage(user string) (*int64, string) {
|
|||||||
return &uid, ""
|
return &uid, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnsureImageExists returns corresponding metadata of the image reference, if image is not
|
|
||||||
// pulled yet, the function will pull the image.
|
|
||||||
func (c *criService) EnsureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig) (*imagestore.Image, error) {
|
|
||||||
image, err := c.localResolve(ref)
|
|
||||||
if err != nil && !errdefs.IsNotFound(err) {
|
|
||||||
return nil, fmt.Errorf("failed to get image %q: %w", ref, err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return &image, nil
|
|
||||||
}
|
|
||||||
// Pull image to ensure the image exists
|
|
||||||
resp, err := c.PullImage(ctx, &runtime.PullImageRequest{Image: &runtime.ImageSpec{Image: ref}, SandboxConfig: config})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to pull image %q: %w", ref, err)
|
|
||||||
}
|
|
||||||
imageID := resp.GetImageRef()
|
|
||||||
newImage, err := c.imageStore.Get(imageID)
|
|
||||||
if err != nil {
|
|
||||||
// It's still possible that someone removed the image right after it is pulled.
|
|
||||||
return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err)
|
|
||||||
}
|
|
||||||
return &newImage, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateTargetContainer checks that a container is a valid
|
// validateTargetContainer checks that a container is a valid
|
||||||
// target for a container using PID NamespaceMode_TARGET.
|
// target for a container using PID NamespaceMode_TARGET.
|
||||||
// The target container must be in the same sandbox and must be running.
|
// The target container must be in the same sandbox and must be running.
|
||||||
@ -298,34 +224,6 @@ func buildLabels(configLabels, imageConfigLabels map[string]string, containerTyp
|
|||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
// toRuntimeAuthConfig converts cri plugin auth config to runtime auth config.
|
|
||||||
func toRuntimeAuthConfig(a criconfig.AuthConfig) *runtime.AuthConfig {
|
|
||||||
return &runtime.AuthConfig{
|
|
||||||
Username: a.Username,
|
|
||||||
Password: a.Password,
|
|
||||||
Auth: a.Auth,
|
|
||||||
IdentityToken: a.IdentityToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseImageReferences parses a list of arbitrary image references and returns
|
|
||||||
// the repotags and repodigests
|
|
||||||
func parseImageReferences(refs []string) ([]string, []string) {
|
|
||||||
var tags, digests []string
|
|
||||||
for _, ref := range refs {
|
|
||||||
parsed, err := docker.ParseAnyReference(ref)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := parsed.(docker.Canonical); ok {
|
|
||||||
digests = append(digests, parsed.String())
|
|
||||||
} else if _, ok := parsed.(docker.Tagged); ok {
|
|
||||||
tags = append(tags, parsed.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tags, digests
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateRuntimeOptions generates runtime options from cri plugin config.
|
// generateRuntimeOptions generates runtime options from cri plugin config.
|
||||||
func generateRuntimeOptions(r criconfig.Runtime, c criconfig.Config) (interface{}, error) {
|
func generateRuntimeOptions(r criconfig.Runtime, c criconfig.Config) (interface{}, error) {
|
||||||
if r.Options == nil {
|
if r.Options == nil {
|
||||||
|
@ -27,18 +27,14 @@ import (
|
|||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
||||||
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/protobuf/types"
|
"github.com/containerd/containerd/protobuf/types"
|
||||||
"github.com/containerd/containerd/reference/docker"
|
|
||||||
runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
|
runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
|
||||||
"github.com/containerd/typeurl/v2"
|
"github.com/containerd/typeurl/v2"
|
||||||
|
|
||||||
imagedigest "github.com/opencontainers/go-digest"
|
|
||||||
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
runtimespec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pelletier/go-toml"
|
"github.com/pelletier/go-toml"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -85,46 +81,6 @@ func TestGetUserFromImage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRepoDigestAndTag(t *testing.T) {
|
|
||||||
digest := imagedigest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
|
|
||||||
for desc, test := range map[string]struct {
|
|
||||||
ref string
|
|
||||||
schema1 bool
|
|
||||||
expectedRepoDigest string
|
|
||||||
expectedRepoTag string
|
|
||||||
}{
|
|
||||||
"repo tag should be empty if original ref has no tag": {
|
|
||||||
ref: "gcr.io/library/busybox@" + digest.String(),
|
|
||||||
expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
|
|
||||||
},
|
|
||||||
"repo tag should not be empty if original ref has tag": {
|
|
||||||
ref: "gcr.io/library/busybox:latest",
|
|
||||||
expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
|
|
||||||
expectedRepoTag: "gcr.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
"repo digest should be empty if original ref is schema1 and has no digest": {
|
|
||||||
ref: "gcr.io/library/busybox:latest",
|
|
||||||
schema1: true,
|
|
||||||
expectedRepoDigest: "",
|
|
||||||
expectedRepoTag: "gcr.io/library/busybox:latest",
|
|
||||||
},
|
|
||||||
"repo digest should not be empty if original ref is schema1 but has digest": {
|
|
||||||
ref: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
|
|
||||||
schema1: true,
|
|
||||||
expectedRepoDigest: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
|
|
||||||
expectedRepoTag: "",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
|
||||||
named, err := docker.ParseDockerRef(test.ref)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1)
|
|
||||||
assert.Equal(t, test.expectedRepoDigest, repoDigest)
|
|
||||||
assert.Equal(t, test.expectedRepoTag, repoTag)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildLabels(t *testing.T) {
|
func TestBuildLabels(t *testing.T) {
|
||||||
imageConfigLabels := map[string]string{
|
imageConfigLabels := map[string]string{
|
||||||
"a": "z",
|
"a": "z",
|
||||||
@ -148,61 +104,6 @@ func TestBuildLabels(t *testing.T) {
|
|||||||
assert.Equal(t, "b", configLabels["a"], "change in new labels should not affect original label")
|
assert.Equal(t, "b", configLabels["a"], "change in new labels should not affect original label")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseImageReferences(t *testing.T) {
|
|
||||||
refs := []string{
|
|
||||||
"gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
"gcr.io/library/busybox:1.2",
|
|
||||||
"sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
"arbitrary-ref",
|
|
||||||
}
|
|
||||||
expectedTags := []string{
|
|
||||||
"gcr.io/library/busybox:1.2",
|
|
||||||
}
|
|
||||||
expectedDigests := []string{"gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582"}
|
|
||||||
tags, digests := parseImageReferences(refs)
|
|
||||||
assert.Equal(t, expectedTags, tags)
|
|
||||||
assert.Equal(t, expectedDigests, digests)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLocalResolve(t *testing.T) {
|
|
||||||
image := imagestore.Image{
|
|
||||||
ID: "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113799",
|
|
||||||
ChainID: "test-chain-id-1",
|
|
||||||
References: []string{
|
|
||||||
"docker.io/library/busybox:latest",
|
|
||||||
"docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
},
|
|
||||||
Size: 10,
|
|
||||||
}
|
|
||||||
c := newTestCRIService()
|
|
||||||
var err error
|
|
||||||
c.imageStore, err = imagestore.NewFakeStore([]imagestore.Image{image})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
for _, ref := range []string{
|
|
||||||
"sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113799",
|
|
||||||
"busybox",
|
|
||||||
"busybox:latest",
|
|
||||||
"busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
"library/busybox",
|
|
||||||
"library/busybox:latest",
|
|
||||||
"library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
"docker.io/busybox",
|
|
||||||
"docker.io/busybox:latest",
|
|
||||||
"docker.io/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
"docker.io/library/busybox",
|
|
||||||
"docker.io/library/busybox:latest",
|
|
||||||
"docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
|
||||||
} {
|
|
||||||
img, err := c.localResolve(ref)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, image, img)
|
|
||||||
}
|
|
||||||
img, err := c.localResolve("randomid")
|
|
||||||
assert.Equal(t, errdefs.IsNotFound(err), true)
|
|
||||||
assert.Equal(t, imagestore.Image{}, img)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateRuntimeOptions(t *testing.T) {
|
func TestGenerateRuntimeOptions(t *testing.T) {
|
||||||
nilOpts := `
|
nilOpts := `
|
||||||
systemd_cgroup = true
|
systemd_cgroup = true
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,7 +25,7 @@ import (
|
|||||||
// ListImages lists existing images.
|
// ListImages lists existing images.
|
||||||
// TODO(random-liu): Add image list filters after CRI defines this more clear, and kubelet
|
// TODO(random-liu): Add image list filters after CRI defines this more clear, and kubelet
|
||||||
// actually needs it.
|
// actually needs it.
|
||||||
func (c *criService) ListImages(ctx context.Context, r *runtime.ListImagesRequest) (*runtime.ListImagesResponse, error) {
|
func (c *CRIImageService) ListImages(ctx context.Context, r *runtime.ListImagesRequest) (*runtime.ListImagesResponse, error) {
|
||||||
imagesInStore := c.imageStore.List()
|
imagesInStore := c.imageStore.List()
|
||||||
|
|
||||||
var images []*runtime.Image
|
var images []*runtime.Image
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -14,13 +14,14 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@ -36,6 +37,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/imgcrypt"
|
"github.com/containerd/imgcrypt"
|
||||||
"github.com/containerd/imgcrypt/images/encryption"
|
"github.com/containerd/imgcrypt/images/encryption"
|
||||||
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||||
|
|
||||||
@ -94,7 +96,7 @@ import (
|
|||||||
// contents are missing but snapshots are ready, is the image still "READY"?
|
// contents are missing but snapshots are ready, is the image still "READY"?
|
||||||
|
|
||||||
// PullImage pulls an image with authentication config.
|
// PullImage pulls an image with authentication config.
|
||||||
func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (_ *runtime.PullImageResponse, err error) {
|
func (c *CRIImageService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (_ *runtime.PullImageResponse, err error) {
|
||||||
span := tracing.SpanFromContext(ctx)
|
span := tracing.SpanFromContext(ctx)
|
||||||
defer func() {
|
defer func() {
|
||||||
// TODO: add domain label for imagePulls metrics, and we may need to provide a mechanism
|
// TODO: add domain label for imagePulls metrics, and we may need to provide a mechanism
|
||||||
@ -224,6 +226,21 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
|
|||||||
return &runtime.PullImageResponse{ImageRef: imageID}, nil
|
return &runtime.PullImageResponse{ImageRef: imageID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRepoDigestAngTag returns image repoDigest and repoTag of the named image reference.
|
||||||
|
func getRepoDigestAndTag(namedRef distribution.Named, digest imagedigest.Digest, schema1 bool) (string, string) {
|
||||||
|
var repoTag, repoDigest string
|
||||||
|
if _, ok := namedRef.(distribution.NamedTagged); ok {
|
||||||
|
repoTag = namedRef.String()
|
||||||
|
}
|
||||||
|
if _, ok := namedRef.(distribution.Canonical); ok {
|
||||||
|
repoDigest = namedRef.String()
|
||||||
|
} else if !schema1 {
|
||||||
|
// digest is not actual repo digest for schema1 image.
|
||||||
|
repoDigest = namedRef.Name() + "@" + digest.String()
|
||||||
|
}
|
||||||
|
return repoDigest, repoTag
|
||||||
|
}
|
||||||
|
|
||||||
// ParseAuth parses AuthConfig and returns username and password/secret required by containerd.
|
// ParseAuth parses AuthConfig and returns username and password/secret required by containerd.
|
||||||
func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
|
func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
@ -267,7 +284,7 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
|
|||||||
// Note that because create and update are not finished in one transaction, there could be race. E.g.
|
// Note that because create and update are not finished in one transaction, there could be race. E.g.
|
||||||
// the image reference is deleted by someone else after create returns already exists, but before update
|
// the image reference is deleted by someone else after create returns already exists, but before update
|
||||||
// happens.
|
// happens.
|
||||||
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
|
func (c *CRIImageService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
|
||||||
img := containerdimages.Image{
|
img := containerdimages.Image{
|
||||||
Name: name,
|
Name: name,
|
||||||
Target: desc,
|
Target: desc,
|
||||||
@ -287,10 +304,10 @@ func (c *criService) createImageReference(ctx context.Context, name string, desc
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateImage updates image store to reflect the newest state of an image reference
|
// UpdateImage updates image store to reflect the newest state of an image reference
|
||||||
// in containerd. If the reference is not managed by the cri plugin, the function also
|
// in containerd. If the reference is not managed by the cri plugin, the function also
|
||||||
// generates necessary metadata for the image and make it managed.
|
// generates necessary metadata for the image and make it managed.
|
||||||
func (c *criService) updateImage(ctx context.Context, r string) error {
|
func (c *CRIImageService) UpdateImage(ctx context.Context, r string) error {
|
||||||
img, err := c.client.GetImage(ctx, r)
|
img, err := c.client.GetImage(ctx, r)
|
||||||
if err != nil && !errdefs.IsNotFound(err) {
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
return fmt.Errorf("get image by reference: %w", err)
|
return fmt.Errorf("get image by reference: %w", err)
|
||||||
@ -323,7 +340,7 @@ func (c *criService) updateImage(ctx context.Context, r string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getTLSConfig returns a TLSConfig configured with a CA/Cert/Key specified by registryTLSConfig
|
// getTLSConfig returns a TLSConfig configured with a CA/Cert/Key specified by registryTLSConfig
|
||||||
func (c *criService) getTLSConfig(registryTLSConfig criconfig.TLSConfig) (*tls.Config, error) {
|
func (c *CRIImageService) getTLSConfig(registryTLSConfig criconfig.TLSConfig) (*tls.Config, error) {
|
||||||
var (
|
var (
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
cert tls.Certificate
|
cert tls.Certificate
|
||||||
@ -380,7 +397,7 @@ func hostDirFromRoots(roots []string) func(string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// registryHosts is the registry hosts to be used by the resolver.
|
// registryHosts is the registry hosts to be used by the resolver.
|
||||||
func (c *criService) registryHosts(ctx context.Context, auth *runtime.AuthConfig, updateClientFn config.UpdateClientFunc) docker.RegistryHosts {
|
func (c *CRIImageService) registryHosts(ctx context.Context, auth *runtime.AuthConfig, updateClientFn config.UpdateClientFunc) docker.RegistryHosts {
|
||||||
paths := filepath.SplitList(c.config.Registry.ConfigPath)
|
paths := filepath.SplitList(c.config.Registry.ConfigPath)
|
||||||
if len(paths) > 0 {
|
if len(paths) > 0 {
|
||||||
hostOptions := config.HostOptions{
|
hostOptions := config.HostOptions{
|
||||||
@ -468,6 +485,16 @@ func (c *criService) registryHosts(ctx context.Context, auth *runtime.AuthConfig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toRuntimeAuthConfig converts cri plugin auth config to runtime auth config.
|
||||||
|
func toRuntimeAuthConfig(a criconfig.AuthConfig) *runtime.AuthConfig {
|
||||||
|
return &runtime.AuthConfig{
|
||||||
|
Username: a.Username,
|
||||||
|
Password: a.Password,
|
||||||
|
Auth: a.Auth,
|
||||||
|
IdentityToken: a.IdentityToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// defaultScheme returns the default scheme for a registry host.
|
// defaultScheme returns the default scheme for a registry host.
|
||||||
func defaultScheme(host string) string {
|
func defaultScheme(host string) string {
|
||||||
if docker.IsLocalhost(host) {
|
if docker.IsLocalhost(host) {
|
||||||
@ -492,7 +519,7 @@ func addDefaultScheme(endpoint string) (string, error) {
|
|||||||
// registryEndpoints returns endpoints for a given host.
|
// registryEndpoints returns endpoints for a given host.
|
||||||
// It adds default registry endpoint if it does not exist in the passed-in endpoint list.
|
// It adds default registry endpoint if it does not exist in the passed-in endpoint list.
|
||||||
// It also supports wildcard host matching with `*`.
|
// It also supports wildcard host matching with `*`.
|
||||||
func (c *criService) registryEndpoints(host string) ([]string, error) {
|
func (c *CRIImageService) registryEndpoints(host string) ([]string, error) {
|
||||||
var endpoints []string
|
var endpoints []string
|
||||||
_, ok := c.config.Registry.Mirrors[host]
|
_, ok := c.config.Registry.Mirrors[host]
|
||||||
if ok {
|
if ok {
|
||||||
@ -543,7 +570,7 @@ func newTransport() *http.Transport {
|
|||||||
|
|
||||||
// encryptedImagesPullOpts returns the necessary list of pull options required
|
// encryptedImagesPullOpts returns the necessary list of pull options required
|
||||||
// for decryption of encrypted images based on the cri decryption configuration.
|
// for decryption of encrypted images based on the cri decryption configuration.
|
||||||
func (c *criService) encryptedImagesPullOpts() []containerd.RemoteOpt {
|
func (c *CRIImageService) encryptedImagesPullOpts() []containerd.RemoteOpt {
|
||||||
if c.config.ImageDecryption.KeyModel == criconfig.KeyModelNode {
|
if c.config.ImageDecryption.KeyModel == criconfig.KeyModelNode {
|
||||||
ltdd := imgcrypt.Payload{}
|
ltdd := imgcrypt.Payload{}
|
||||||
decUnpackOpt := encryption.WithUnpackConfigApplyOpts(encryption.WithDecryptedUnpack(<dd))
|
decUnpackOpt := encryption.WithUnpackConfigApplyOpts(encryption.WithDecryptedUnpack(<dd))
|
||||||
@ -740,7 +767,7 @@ func (rt *pullRequestReporterRoundTripper) RoundTrip(req *http.Request) (*http.R
|
|||||||
// passed from pod sandbox config to get the runtimeHandler. The annotation key is specified in configuration.
|
// passed from pod sandbox config to get the runtimeHandler. The annotation key is specified in configuration.
|
||||||
// Once we know the runtime, try to override default snapshotter if it is set for this runtime.
|
// Once we know the runtime, try to override default snapshotter if it is set for this runtime.
|
||||||
// See https://github.com/containerd/containerd/issues/6657
|
// See https://github.com/containerd/containerd/issues/6657
|
||||||
func (c *criService) snapshotterFromPodSandboxConfig(ctx context.Context, imageRef string,
|
func (c *CRIImageService) snapshotterFromPodSandboxConfig(ctx context.Context, imageRef string,
|
||||||
s *runtime.PodSandboxConfig) (string, error) {
|
s *runtime.PodSandboxConfig) (string, error) {
|
||||||
snapshotter := c.config.ContainerdConfig.Snapshotter
|
snapshotter := c.config.ContainerdConfig.Snapshotter
|
||||||
if s == nil || s.Annotations == nil {
|
if s == nil || s.Annotations == nil {
|
||||||
@ -752,12 +779,65 @@ func (c *criService) snapshotterFromPodSandboxConfig(ctx context.Context, imageR
|
|||||||
return snapshotter, nil
|
return snapshotter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Find other way to retrieve sandbox runtime, this must belong to the Runtime part of the CRI.
|
||||||
ociRuntime, err := c.getSandboxRuntime(s, runtimeHandler)
|
ociRuntime, err := c.getSandboxRuntime(s, runtimeHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("experimental: failed to get sandbox runtime for %s, err: %+v", runtimeHandler, err)
|
return "", fmt.Errorf("experimental: failed to get sandbox runtime for %s, err: %+v", runtimeHandler, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotter = c.runtimeSnapshotter(context.Background(), ociRuntime)
|
snapshotter = c.RuntimeSnapshotter(context.Background(), ociRuntime)
|
||||||
log.G(ctx).Infof("experimental: PullImage %q for runtime %s, using snapshotter %s", imageRef, runtimeHandler, snapshotter)
|
log.G(ctx).Infof("experimental: PullImage %q for runtime %s, using snapshotter %s", imageRef, runtimeHandler, snapshotter)
|
||||||
return snapshotter, nil
|
return snapshotter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: copy-pasted from the runtime service implementation. This should not be in image service.
|
||||||
|
func (c *CRIImageService) getSandboxRuntime(config *runtime.PodSandboxConfig, runtimeHandler string) (criconfig.Runtime, error) {
|
||||||
|
if untrustedWorkload(config) {
|
||||||
|
// If the untrusted annotation is provided, runtimeHandler MUST be empty.
|
||||||
|
if runtimeHandler != "" && runtimeHandler != criconfig.RuntimeUntrusted {
|
||||||
|
return criconfig.Runtime{}, errors.New("untrusted workload with explicit runtime handler is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the untrusted workload is requesting access to the host/node, this request will fail.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// 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) {
|
||||||
|
return criconfig.Runtime{}, errors.New("untrusted workload with host access is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimeHandler = criconfig.RuntimeUntrusted
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtimeHandler == "" {
|
||||||
|
runtimeHandler = c.config.ContainerdConfig.DefaultRuntimeName
|
||||||
|
}
|
||||||
|
|
||||||
|
handler, ok := c.config.ContainerdConfig.Runtimes[runtimeHandler]
|
||||||
|
if !ok {
|
||||||
|
return criconfig.Runtime{}, fmt.Errorf("no runtime for %q is configured", runtimeHandler)
|
||||||
|
}
|
||||||
|
return handler, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// untrustedWorkload returns true if the sandbox contains untrusted workload.
|
||||||
|
func untrustedWorkload(config *runtime.PodSandboxConfig) bool {
|
||||||
|
return config.GetAnnotations()[annotations.UntrustedWorkload] == "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
// hostAccessingSandbox returns true if the sandbox configuration
|
||||||
|
// requires additional host access for the sandbox.
|
||||||
|
func hostAccessingSandbox(config *runtime.PodSandboxConfig) bool {
|
||||||
|
securityContext := config.GetLinux().GetSecurityContext()
|
||||||
|
|
||||||
|
namespaceOptions := securityContext.GetNamespaceOptions()
|
||||||
|
if namespaceOptions.GetNetwork() == runtime.NamespaceMode_NODE ||
|
||||||
|
namespaceOptions.GetPid() == runtime.NamespaceMode_NODE ||
|
||||||
|
namespaceOptions.GetIpc() == runtime.NamespaceMode_NODE {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -22,6 +22,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/reference/docker"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||||
|
|
||||||
@ -395,3 +397,43 @@ func TestSnapshotterFromPodSandboxConfig(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetRepoDigestAndTag(t *testing.T) {
|
||||||
|
digest := digest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
ref string
|
||||||
|
schema1 bool
|
||||||
|
expectedRepoDigest string
|
||||||
|
expectedRepoTag string
|
||||||
|
}{
|
||||||
|
"repo tag should be empty if original ref has no tag": {
|
||||||
|
ref: "gcr.io/library/busybox@" + digest.String(),
|
||||||
|
expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
|
||||||
|
},
|
||||||
|
"repo tag should not be empty if original ref has tag": {
|
||||||
|
ref: "gcr.io/library/busybox:latest",
|
||||||
|
expectedRepoDigest: "gcr.io/library/busybox@" + digest.String(),
|
||||||
|
expectedRepoTag: "gcr.io/library/busybox:latest",
|
||||||
|
},
|
||||||
|
"repo digest should be empty if original ref is schema1 and has no digest": {
|
||||||
|
ref: "gcr.io/library/busybox:latest",
|
||||||
|
schema1: true,
|
||||||
|
expectedRepoDigest: "",
|
||||||
|
expectedRepoTag: "gcr.io/library/busybox:latest",
|
||||||
|
},
|
||||||
|
"repo digest should not be empty if original ref is schema1 but has digest": {
|
||||||
|
ref: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
|
||||||
|
schema1: true,
|
||||||
|
expectedRepoDigest: "gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59594",
|
||||||
|
expectedRepoTag: "",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(desc, func(t *testing.T) {
|
||||||
|
named, err := docker.ParseDockerRef(test.ref)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repoDigest, repoTag := getRepoDigestAndTag(named, digest, test.schema1)
|
||||||
|
assert.Equal(t, test.expectedRepoDigest, repoDigest)
|
||||||
|
assert.Equal(t, test.expectedRepoTag, repoTag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -33,9 +33,9 @@ import (
|
|||||||
// TODO(random-liu): We should change CRI to distinguish image id and image spec.
|
// TODO(random-liu): We should change CRI to distinguish image id and image spec.
|
||||||
// Remove the whole image no matter the it's image id or reference. This is the
|
// Remove the whole image no matter the it's image id or reference. This is the
|
||||||
// semantic defined in CRI now.
|
// semantic defined in CRI now.
|
||||||
func (c *criService) RemoveImage(ctx context.Context, r *runtime.RemoveImageRequest) (*runtime.RemoveImageResponse, error) {
|
func (c *CRIImageService) RemoveImage(ctx context.Context, r *runtime.RemoveImageRequest) (*runtime.RemoveImageResponse, error) {
|
||||||
span := tracing.SpanFromContext(ctx)
|
span := tracing.SpanFromContext(ctx)
|
||||||
image, err := c.localResolve(r.GetImage().GetImage())
|
image, err := c.LocalResolve(r.GetImage().GetImage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
span.AddEvent(err.Error())
|
span.AddEvent(err.Error())
|
@ -14,16 +14,19 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
||||||
|
"github.com/containerd/containerd/reference/docker"
|
||||||
"github.com/containerd/containerd/tracing"
|
"github.com/containerd/containerd/tracing"
|
||||||
|
|
||||||
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@ -33,9 +36,9 @@ import (
|
|||||||
// ImageStatus returns the status of the image, returns nil if the image isn't present.
|
// ImageStatus returns the status of the image, returns nil if the image isn't present.
|
||||||
// TODO(random-liu): We should change CRI to distinguish image id and image spec. (See
|
// TODO(random-liu): We should change CRI to distinguish image id and image spec. (See
|
||||||
// kubernetes/kubernetes#46255)
|
// kubernetes/kubernetes#46255)
|
||||||
func (c *criService) ImageStatus(ctx context.Context, r *runtime.ImageStatusRequest) (*runtime.ImageStatusResponse, error) {
|
func (c *CRIImageService) ImageStatus(ctx context.Context, r *runtime.ImageStatusRequest) (*runtime.ImageStatusResponse, error) {
|
||||||
span := tracing.SpanFromContext(ctx)
|
span := tracing.SpanFromContext(ctx)
|
||||||
image, err := c.localResolve(r.GetImage().GetImage())
|
image, err := c.LocalResolve(r.GetImage().GetImage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
span.AddEvent(err.Error())
|
span.AddEvent(err.Error())
|
||||||
@ -62,7 +65,7 @@ func (c *criService) ImageStatus(ctx context.Context, r *runtime.ImageStatusRequ
|
|||||||
|
|
||||||
// toCRIImage converts internal image object to CRI runtime.Image.
|
// toCRIImage converts internal image object to CRI runtime.Image.
|
||||||
func toCRIImage(image imagestore.Image) *runtime.Image {
|
func toCRIImage(image imagestore.Image) *runtime.Image {
|
||||||
repoTags, repoDigests := parseImageReferences(image.References)
|
repoTags, repoDigests := ParseImageReferences(image.References)
|
||||||
runtimeImage := &runtime.Image{
|
runtimeImage := &runtime.Image{
|
||||||
Id: image.ID,
|
Id: image.ID,
|
||||||
RepoTags: repoTags,
|
RepoTags: repoTags,
|
||||||
@ -78,6 +81,43 @@ func toCRIImage(image imagestore.Image) *runtime.Image {
|
|||||||
return runtimeImage
|
return runtimeImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getUserFromImage gets uid or user name of the image user.
|
||||||
|
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
|
||||||
|
func getUserFromImage(user string) (*int64, string) {
|
||||||
|
// return both empty if user is not specified in the image.
|
||||||
|
if user == "" {
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
// split instances where the id may contain user:group
|
||||||
|
user = strings.Split(user, ":")[0]
|
||||||
|
// user could be either uid or user name. Try to interpret as numeric uid.
|
||||||
|
uid, err := strconv.ParseInt(user, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// If user is non numeric, assume it's user name.
|
||||||
|
return nil, user
|
||||||
|
}
|
||||||
|
// If user is a numeric uid.
|
||||||
|
return &uid, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseImageReferences parses a list of arbitrary image references and returns
|
||||||
|
// the repotags and repodigests
|
||||||
|
func ParseImageReferences(refs []string) ([]string, []string) {
|
||||||
|
var tags, digests []string
|
||||||
|
for _, ref := range refs {
|
||||||
|
parsed, err := docker.ParseAnyReference(ref)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := parsed.(docker.Canonical); ok {
|
||||||
|
digests = append(digests, parsed.String())
|
||||||
|
} else if _, ok := parsed.(docker.Tagged); ok {
|
||||||
|
tags = append(tags, parsed.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags, digests
|
||||||
|
}
|
||||||
|
|
||||||
// TODO (mikebrow): discuss moving this struct and / or constants for info map for some or all of these fields to CRI
|
// TODO (mikebrow): discuss moving this struct and / or constants for info map for some or all of these fields to CRI
|
||||||
type verboseImageInfo struct {
|
type verboseImageInfo struct {
|
||||||
ChainID string `json:"chainID"`
|
ChainID string `json:"chainID"`
|
||||||
@ -85,7 +125,7 @@ type verboseImageInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toCRIImageInfo converts internal image object information to CRI image status response info map.
|
// toCRIImageInfo converts internal image object information to CRI image status response info map.
|
||||||
func (c *criService) toCRIImageInfo(ctx context.Context, image *imagestore.Image, verbose bool) (map[string]string, error) {
|
func (c *CRIImageService) toCRIImageInfo(ctx context.Context, image *imagestore.Image, verbose bool) (map[string]string, error) {
|
||||||
if !verbose {
|
if !verbose {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -72,3 +72,59 @@ func TestImageStatus(t *testing.T) {
|
|||||||
assert.NotNil(t, resp)
|
assert.NotNil(t, resp)
|
||||||
assert.Equal(t, expected, resp.GetImage())
|
assert.Equal(t, expected, resp.GetImage())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseImageReferences(t *testing.T) {
|
||||||
|
refs := []string{
|
||||||
|
"gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
"gcr.io/library/busybox:1.2",
|
||||||
|
"sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
"arbitrary-ref",
|
||||||
|
}
|
||||||
|
expectedTags := []string{
|
||||||
|
"gcr.io/library/busybox:1.2",
|
||||||
|
}
|
||||||
|
expectedDigests := []string{"gcr.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582"}
|
||||||
|
tags, digests := ParseImageReferences(refs)
|
||||||
|
assert.Equal(t, expectedTags, tags)
|
||||||
|
assert.Equal(t, expectedDigests, digests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGetUserFromImage tests the logic of getting image uid or user name of image user.
|
||||||
|
func TestGetUserFromImage(t *testing.T) {
|
||||||
|
newI64 := func(i int64) *int64 { return &i }
|
||||||
|
for c, test := range map[string]struct {
|
||||||
|
user string
|
||||||
|
uid *int64
|
||||||
|
name string
|
||||||
|
}{
|
||||||
|
"no gid": {
|
||||||
|
user: "0",
|
||||||
|
uid: newI64(0),
|
||||||
|
},
|
||||||
|
"uid/gid": {
|
||||||
|
user: "0:1",
|
||||||
|
uid: newI64(0),
|
||||||
|
},
|
||||||
|
"empty user": {
|
||||||
|
user: "",
|
||||||
|
},
|
||||||
|
"multiple separators": {
|
||||||
|
user: "1:2:3",
|
||||||
|
uid: newI64(1),
|
||||||
|
},
|
||||||
|
"root username": {
|
||||||
|
user: "root:root",
|
||||||
|
name: "root",
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
user: "test:test",
|
||||||
|
name: "test",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(c, func(t *testing.T) {
|
||||||
|
actualUID, actualName := getUserFromImage(test.user)
|
||||||
|
assert.Equal(t, test.uid, actualUID)
|
||||||
|
assert.Equal(t, test.name, actualName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,7 +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.
|
// 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 *CRIImageService) 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()
|
||||||
var usedBytes, inodesUsed uint64
|
var usedBytes, inodesUsed uint64
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
46
pkg/cri/sbserver/images/metrics.go
Normal file
46
pkg/cri/sbserver/images/metrics.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
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 images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/go-metrics"
|
||||||
|
prom "github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
imagePulls metrics.LabeledCounter
|
||||||
|
inProgressImagePulls metrics.Gauge
|
||||||
|
// pull duration / (image size / 1MBi)
|
||||||
|
imagePullThroughput prom.Histogram
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// these CRI metrics record latencies for successful operations around a sandbox and container's lifecycle.
|
||||||
|
ns := metrics.NewNamespace("containerd", "cri_sandboxed", nil)
|
||||||
|
|
||||||
|
imagePulls = ns.NewLabeledCounter("image_pulls", "succeeded and failed counters", "status")
|
||||||
|
inProgressImagePulls = ns.NewGauge("in_progress_image_pulls", "in progress pulls", metrics.Total)
|
||||||
|
imagePullThroughput = prom.NewHistogram(
|
||||||
|
prom.HistogramOpts{
|
||||||
|
Name: "image_pulling_throughput",
|
||||||
|
Help: "image pull throughput",
|
||||||
|
Buckets: prom.DefBuckets,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
metrics.Register(ns)
|
||||||
|
}
|
134
pkg/cri/sbserver/images/service.go
Normal file
134
pkg/cri/sbserver/images/service.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
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 images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
|
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
||||||
|
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
||||||
|
"github.com/containerd/containerd/pkg/kmutex"
|
||||||
|
"github.com/containerd/containerd/reference/docker"
|
||||||
|
imagedigest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// imageLabelKey is the label key indicating the image is managed by cri plugin.
|
||||||
|
imageLabelKey = "io.cri-containerd.image"
|
||||||
|
// imageLabelValue is the label value indicating the image is managed by cri plugin.
|
||||||
|
imageLabelValue = "managed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CRIImageService struct {
|
||||||
|
// config contains all configurations.
|
||||||
|
config criconfig.Config
|
||||||
|
// client is an instance of the containerd client
|
||||||
|
client *containerd.Client
|
||||||
|
// imageFSPath is the path to image filesystem.
|
||||||
|
imageFSPath string
|
||||||
|
// imageStore stores all resources associated with images.
|
||||||
|
imageStore *imagestore.Store
|
||||||
|
// snapshotStore stores information of all snapshots.
|
||||||
|
snapshotStore *snapshotstore.Store
|
||||||
|
// unpackDuplicationSuppressor is used to make sure that there is only
|
||||||
|
// one in-flight fetch request or unpack handler for a given descriptor's
|
||||||
|
// or chain ID.
|
||||||
|
unpackDuplicationSuppressor kmutex.KeyedLocker
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(config criconfig.Config, imageFSPath string, client *containerd.Client) (*CRIImageService, error) {
|
||||||
|
svc := CRIImageService{
|
||||||
|
config: config,
|
||||||
|
client: client,
|
||||||
|
imageStore: imagestore.NewStore(client),
|
||||||
|
imageFSPath: imageFSPath,
|
||||||
|
snapshotStore: snapshotstore.NewStore(),
|
||||||
|
unpackDuplicationSuppressor: kmutex.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.SnapshotService(svc.config.ContainerdConfig.Snapshotter) == nil {
|
||||||
|
return nil, fmt.Errorf("failed to find snapshotter %q", svc.config.ContainerdConfig.Snapshotter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start snapshot stats syncer, it doesn't need to be stopped.
|
||||||
|
logrus.Info("Start snapshots syncer")
|
||||||
|
snapshotsSyncer := newSnapshotsSyncer(
|
||||||
|
svc.snapshotStore,
|
||||||
|
svc.client.SnapshotService(svc.config.ContainerdConfig.Snapshotter),
|
||||||
|
time.Duration(svc.config.StatsCollectPeriod)*time.Second,
|
||||||
|
)
|
||||||
|
snapshotsSyncer.start()
|
||||||
|
|
||||||
|
return &svc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalResolve resolves image reference locally and returns corresponding image metadata. It
|
||||||
|
// returns errdefs.ErrNotFound if the reference doesn't exist.
|
||||||
|
func (c *CRIImageService) LocalResolve(refOrID string) (imagestore.Image, error) {
|
||||||
|
getImageID := func(refOrId string) string {
|
||||||
|
if _, err := imagedigest.Parse(refOrID); err == nil {
|
||||||
|
return refOrID
|
||||||
|
}
|
||||||
|
return func(ref string) string {
|
||||||
|
// ref is not image id, try to resolve it locally.
|
||||||
|
// TODO(random-liu): Handle this error better for debugging.
|
||||||
|
normalized, err := docker.ParseDockerRef(ref)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
id, err := c.imageStore.Resolve(normalized.String())
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}(refOrID)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageID := getImageID(refOrID)
|
||||||
|
if imageID == "" {
|
||||||
|
// Try to treat ref as imageID
|
||||||
|
imageID = refOrID
|
||||||
|
}
|
||||||
|
return c.imageStore.Get(imageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeSnapshotter overrides the default snapshotter if Snapshotter is set for this runtime.
|
||||||
|
// See https://github.com/containerd/containerd/issues/6657
|
||||||
|
func (c *CRIImageService) RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string {
|
||||||
|
if ociRuntime.Snapshotter == "" {
|
||||||
|
return c.config.ContainerdConfig.Snapshotter
|
||||||
|
}
|
||||||
|
|
||||||
|
log.G(ctx).Debugf("Set snapshotter for runtime %s to %s", ociRuntime.Type, ociRuntime.Snapshotter)
|
||||||
|
return ociRuntime.Snapshotter
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetImage gets image metadata by image id.
|
||||||
|
func (c *CRIImageService) GetImage(id string) (imagestore.Image, error) {
|
||||||
|
return c.imageStore.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSnapshot returns the snapshot with specified key.
|
||||||
|
func (c *CRIImageService) GetSnapshot(key string) (snapshotstore.Snapshot, error) {
|
||||||
|
return c.snapshotStore.Get(key)
|
||||||
|
}
|
128
pkg/cri/sbserver/images/service_test.go
Normal file
128
pkg/cri/sbserver/images/service_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
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 images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
|
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
||||||
|
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testImageFSPath = "/test/image/fs/path"
|
||||||
|
testRootDir = "/test/root"
|
||||||
|
testStateDir = "/test/state"
|
||||||
|
// Use an image id as test sandbox image to avoid image name resolve.
|
||||||
|
// TODO(random-liu): Change this to image name after we have complete image
|
||||||
|
// management unit test framework.
|
||||||
|
testSandboxImage = "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113798"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newTestCRIService creates a fake criService for test.
|
||||||
|
func newTestCRIService() *CRIImageService {
|
||||||
|
return &CRIImageService{
|
||||||
|
config: testConfig,
|
||||||
|
imageFSPath: testImageFSPath,
|
||||||
|
imageStore: imagestore.NewStore(nil),
|
||||||
|
snapshotStore: snapshotstore.NewStore(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testConfig = criconfig.Config{
|
||||||
|
RootDir: testRootDir,
|
||||||
|
StateDir: testStateDir,
|
||||||
|
PluginConfig: criconfig.PluginConfig{
|
||||||
|
SandboxImage: testSandboxImage,
|
||||||
|
TolerateMissingHugetlbController: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalResolve(t *testing.T) {
|
||||||
|
image := imagestore.Image{
|
||||||
|
ID: "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113799",
|
||||||
|
ChainID: "test-chain-id-1",
|
||||||
|
References: []string{
|
||||||
|
"docker.io/library/busybox:latest",
|
||||||
|
"docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
},
|
||||||
|
Size: 10,
|
||||||
|
}
|
||||||
|
c := newTestCRIService()
|
||||||
|
var err error
|
||||||
|
c.imageStore, err = imagestore.NewFakeStore([]imagestore.Image{image})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, ref := range []string{
|
||||||
|
"sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113799",
|
||||||
|
"busybox",
|
||||||
|
"busybox:latest",
|
||||||
|
"busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
"library/busybox",
|
||||||
|
"library/busybox:latest",
|
||||||
|
"library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
"docker.io/busybox",
|
||||||
|
"docker.io/busybox:latest",
|
||||||
|
"docker.io/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
"docker.io/library/busybox",
|
||||||
|
"docker.io/library/busybox:latest",
|
||||||
|
"docker.io/library/busybox@sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582",
|
||||||
|
} {
|
||||||
|
img, err := c.LocalResolve(ref)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, image, img)
|
||||||
|
}
|
||||||
|
img, err := c.LocalResolve("randomid")
|
||||||
|
assert.Equal(t, errdefs.IsNotFound(err), true)
|
||||||
|
assert.Equal(t, imagestore.Image{}, img)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntimeSnapshotter(t *testing.T) {
|
||||||
|
defaultRuntime := criconfig.Runtime{
|
||||||
|
Snapshotter: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
fooRuntime := criconfig.Runtime{
|
||||||
|
Snapshotter: "devmapper",
|
||||||
|
}
|
||||||
|
|
||||||
|
for desc, test := range map[string]struct {
|
||||||
|
runtime criconfig.Runtime
|
||||||
|
expectSnapshotter string
|
||||||
|
}{
|
||||||
|
"should return default snapshotter when runtime.Snapshotter is not set": {
|
||||||
|
runtime: defaultRuntime,
|
||||||
|
expectSnapshotter: criconfig.DefaultConfig().Snapshotter,
|
||||||
|
},
|
||||||
|
"should return overridden snapshotter when runtime.Snapshotter is set": {
|
||||||
|
runtime: fooRuntime,
|
||||||
|
expectSnapshotter: "devmapper",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(desc, func(t *testing.T) {
|
||||||
|
cri := newTestCRIService()
|
||||||
|
cri.config = criconfig.Config{
|
||||||
|
PluginConfig: criconfig.DefaultConfig(),
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.expectSnapshotter, cri.RuntimeSnapshotter(context.Background(), test.runtime))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sbserver
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -18,7 +18,6 @@ package sbserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/go-metrics"
|
"github.com/docker/go-metrics"
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -39,11 +38,6 @@ var (
|
|||||||
networkPluginOperations metrics.LabeledCounter
|
networkPluginOperations metrics.LabeledCounter
|
||||||
networkPluginOperationsErrors metrics.LabeledCounter
|
networkPluginOperationsErrors metrics.LabeledCounter
|
||||||
networkPluginOperationsLatency metrics.LabeledTimer
|
networkPluginOperationsLatency metrics.LabeledTimer
|
||||||
|
|
||||||
imagePulls metrics.LabeledCounter
|
|
||||||
inProgressImagePulls metrics.Gauge
|
|
||||||
// pull duration / (image size / 1MBi)
|
|
||||||
imagePullThroughput prom.Histogram
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -68,16 +62,6 @@ func init() {
|
|||||||
networkPluginOperationsErrors = ns.NewLabeledCounter("network_plugin_operations_errors_total", "cumulative number of network plugin operations by operation type", "operation_type")
|
networkPluginOperationsErrors = ns.NewLabeledCounter("network_plugin_operations_errors_total", "cumulative number of network plugin operations by operation type", "operation_type")
|
||||||
networkPluginOperationsLatency = ns.NewLabeledTimer("network_plugin_operations_duration_seconds", "latency in seconds of network plugin operations. Broken down by operation type", "operation_type")
|
networkPluginOperationsLatency = ns.NewLabeledTimer("network_plugin_operations_duration_seconds", "latency in seconds of network plugin operations. Broken down by operation type", "operation_type")
|
||||||
|
|
||||||
imagePulls = ns.NewLabeledCounter("image_pulls", "succeeded and failed counters", "status")
|
|
||||||
inProgressImagePulls = ns.NewGauge("in_progress_image_pulls", "in progress pulls", metrics.Total)
|
|
||||||
imagePullThroughput = prom.NewHistogram(
|
|
||||||
prom.HistogramOpts{
|
|
||||||
Name: "image_pulling_throughput",
|
|
||||||
Help: "image pull throughput",
|
|
||||||
Buckets: prom.DefBuckets,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
metrics.Register(ns)
|
metrics.Register(ns)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,8 +41,6 @@ import (
|
|||||||
// CRIService interface contains things required by controller, but not yet refactored from criService.
|
// CRIService interface contains things required by controller, but not yet refactored from criService.
|
||||||
// TODO: this will be removed in subsequent iterations.
|
// TODO: this will be removed in subsequent iterations.
|
||||||
type CRIService interface {
|
type CRIService interface {
|
||||||
EnsureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig) (*imagestore.Image, error)
|
|
||||||
|
|
||||||
// TODO: we should implement Event backoff in Controller.
|
// TODO: we should implement Event backoff in Controller.
|
||||||
BackOffEvent(id string, event interface{})
|
BackOffEvent(id string, event interface{})
|
||||||
|
|
||||||
@ -51,11 +49,21 @@ type CRIService interface {
|
|||||||
GenerateAndSendContainerEvent(ctx context.Context, containerID string, sandboxID string, eventType runtime.ContainerEventType)
|
GenerateAndSendContainerEvent(ctx context.Context, containerID string, sandboxID string, eventType runtime.ContainerEventType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImageService specifies dependencies to CRI image service.
|
||||||
|
type ImageService interface {
|
||||||
|
runtime.ImageServiceServer
|
||||||
|
|
||||||
|
LocalResolve(refOrID string) (imagestore.Image, error)
|
||||||
|
GetImage(id string) (imagestore.Image, error)
|
||||||
|
}
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
// config contains all configurations.
|
// config contains all configurations.
|
||||||
config criconfig.Config
|
config criconfig.Config
|
||||||
// client is an instance of the containerd client
|
// client is an instance of the containerd client
|
||||||
client *containerd.Client
|
client *containerd.Client
|
||||||
|
// imageService is a dependency to CRI image service.
|
||||||
|
imageService ImageService
|
||||||
// sandboxStore stores all resources associated with sandboxes.
|
// sandboxStore stores all resources associated with sandboxes.
|
||||||
sandboxStore *sandboxstore.Store
|
sandboxStore *sandboxstore.Store
|
||||||
// os is an interface for all required os operations.
|
// os is an interface for all required os operations.
|
||||||
@ -74,11 +82,13 @@ func New(
|
|||||||
sandboxStore *sandboxstore.Store,
|
sandboxStore *sandboxstore.Store,
|
||||||
os osinterface.OS,
|
os osinterface.OS,
|
||||||
cri CRIService,
|
cri CRIService,
|
||||||
|
imageService ImageService,
|
||||||
baseOCISpecs map[string]*oci.Spec,
|
baseOCISpecs map[string]*oci.Spec,
|
||||||
) *Controller {
|
) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
config: config,
|
config: config,
|
||||||
client: client,
|
client: client,
|
||||||
|
imageService: imageService,
|
||||||
sandboxStore: sandboxStore,
|
sandboxStore: sandboxStore,
|
||||||
os: os,
|
os: os,
|
||||||
cri: cri,
|
cri: cri,
|
||||||
|
@ -21,8 +21,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
@ -103,25 +101,6 @@ func (c *Controller) toContainerdImage(ctx context.Context, image imagestore.Ima
|
|||||||
return c.client.GetImage(ctx, image.References[0])
|
return c.client.GetImage(ctx, image.References[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUserFromImage gets uid or user name of the image user.
|
|
||||||
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
|
|
||||||
func getUserFromImage(user string) (*int64, string) {
|
|
||||||
// return both empty if user is not specified in the image.
|
|
||||||
if user == "" {
|
|
||||||
return nil, ""
|
|
||||||
}
|
|
||||||
// split instances where the id may contain user:group
|
|
||||||
user = strings.Split(user, ":")[0]
|
|
||||||
// user could be either uid or user name. Try to interpret as numeric uid.
|
|
||||||
uid, err := strconv.ParseInt(user, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// If user is non numeric, assume it's user name.
|
|
||||||
return nil, user
|
|
||||||
}
|
|
||||||
// If user is a numeric uid.
|
|
||||||
return &uid, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildLabel builds the labels from config to be passed to containerd
|
// buildLabel builds the labels from config to be passed to containerd
|
||||||
func buildLabels(configLabels, imageConfigLabels map[string]string, containerType string) map[string]string {
|
func buildLabels(configLabels, imageConfigLabels map[string]string, containerType string) map[string]string {
|
||||||
labels := make(map[string]string)
|
labels := make(map[string]string)
|
||||||
|
@ -29,46 +29,6 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGetUserFromImage tests the logic of getting image uid or user name of image user.
|
|
||||||
func TestGetUserFromImage(t *testing.T) {
|
|
||||||
newI64 := func(i int64) *int64 { return &i }
|
|
||||||
for c, test := range map[string]struct {
|
|
||||||
user string
|
|
||||||
uid *int64
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
"no gid": {
|
|
||||||
user: "0",
|
|
||||||
uid: newI64(0),
|
|
||||||
},
|
|
||||||
"uid/gid": {
|
|
||||||
user: "0:1",
|
|
||||||
uid: newI64(0),
|
|
||||||
},
|
|
||||||
"empty user": {
|
|
||||||
user: "",
|
|
||||||
},
|
|
||||||
"multiple separators": {
|
|
||||||
user: "1:2:3",
|
|
||||||
uid: newI64(1),
|
|
||||||
},
|
|
||||||
"root username": {
|
|
||||||
user: "root:root",
|
|
||||||
name: "root",
|
|
||||||
},
|
|
||||||
"username": {
|
|
||||||
user: "test:test",
|
|
||||||
name: "test",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(c, func(t *testing.T) {
|
|
||||||
actualUID, actualName := getUserFromImage(test.user)
|
|
||||||
assert.Equal(t, test.uid, actualUID)
|
|
||||||
assert.Equal(t, test.name, actualName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRepoDigestAndTag(t *testing.T) {
|
func TestGetRepoDigestAndTag(t *testing.T) {
|
||||||
digest := imagedigest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
|
digest := imagedigest.Digest("sha256:e6693c20186f837fc393390135d8a598a96a833917917789d63766cab6c59582")
|
||||||
for desc, test := range map[string]struct {
|
for desc, test := range map[string]struct {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
||||||
"github.com/containerd/nri"
|
"github.com/containerd/nri"
|
||||||
v1 "github.com/containerd/nri/types/v1"
|
v1 "github.com/containerd/nri/types/v1"
|
||||||
"github.com/containerd/typeurl/v2"
|
"github.com/containerd/typeurl/v2"
|
||||||
@ -80,7 +81,7 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Ensure sandbox container image snapshot.
|
// Ensure sandbox container image snapshot.
|
||||||
image, err := c.cri.EnsureImageExists(ctx, c.config.SandboxImage, config)
|
image, err := c.ensureImageExists(ctx, c.config.SandboxImage, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cin, fmt.Errorf("failed to get sandbox image %q: %w", c.config.SandboxImage, err)
|
return cin, fmt.Errorf("failed to get sandbox image %q: %w", c.config.SandboxImage, err)
|
||||||
}
|
}
|
||||||
@ -272,6 +273,28 @@ func (c *Controller) Create(ctx context.Context, _id string, _ ...sandbox.Create
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig) (*imagestore.Image, error) {
|
||||||
|
image, err := c.imageService.LocalResolve(ref)
|
||||||
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
return nil, fmt.Errorf("failed to get image %q: %w", ref, err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return &image, nil
|
||||||
|
}
|
||||||
|
// Pull image to ensure the image exists
|
||||||
|
resp, err := c.imageService.PullImage(ctx, &runtime.PullImageRequest{Image: &runtime.ImageSpec{Image: ref}, SandboxConfig: config})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to pull image %q: %w", ref, err)
|
||||||
|
}
|
||||||
|
imageID := resp.GetImageRef()
|
||||||
|
newImage, err := c.imageService.GetImage(imageID)
|
||||||
|
if err != nil {
|
||||||
|
// It's still possible that someone removed the image right after it is pulled.
|
||||||
|
return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err)
|
||||||
|
}
|
||||||
|
return &newImage, nil
|
||||||
|
}
|
||||||
|
|
||||||
// untrustedWorkload returns true if the sandbox contains untrusted workload.
|
// untrustedWorkload returns true if the sandbox contains untrusted workload.
|
||||||
func untrustedWorkload(config *runtime.PodSandboxConfig) bool {
|
func untrustedWorkload(config *runtime.PodSandboxConfig) bool {
|
||||||
return config.GetAnnotations()[annotations.UntrustedWorkload] == "true"
|
return config.GetAnnotations()[annotations.UntrustedWorkload] == "true"
|
||||||
|
@ -519,7 +519,7 @@ func (c *criService) loadImages(ctx context.Context, cImages []containerd.Image)
|
|||||||
log.G(ctx).Warnf("The image %s is not unpacked.", i.Name())
|
log.G(ctx).Warnf("The image %s is not unpacked.", i.Name())
|
||||||
// TODO(random-liu): Consider whether we should try unpack here.
|
// TODO(random-liu): Consider whether we should try unpack here.
|
||||||
}
|
}
|
||||||
if err := c.updateImage(ctx, i.Name()); err != nil {
|
if err := c.UpdateImage(ctx, i.Name()); err != nil {
|
||||||
log.G(ctx).WithError(err).Warnf("Failed to update reference for image %q", i.Name())
|
log.G(ctx).WithError(err).Warnf("Failed to update reference for image %q", i.Name())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func (c *criService) toPodSandboxStats(sandbox sandboxstore.Sandbox, statsMap ma
|
|||||||
// If snapshotstore doesn't have cached snapshot information
|
// If snapshotstore doesn't have cached snapshot information
|
||||||
// set WritableLayer usage to zero
|
// set WritableLayer usage to zero
|
||||||
var usedBytes uint64
|
var usedBytes uint64
|
||||||
sn, err := c.snapshotStore.Get(cntr.ID)
|
sn, err := c.GetSnapshot(cntr.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
usedBytes = sn.Size
|
usedBytes = sn.Size
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,16 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/containerd/containerd/pkg/cri/instrument"
|
"github.com/containerd/containerd/pkg/cri/instrument"
|
||||||
"github.com/containerd/containerd/pkg/cri/nri"
|
"github.com/containerd/containerd/pkg/cri/nri"
|
||||||
|
"github.com/containerd/containerd/pkg/cri/sbserver/images"
|
||||||
"github.com/containerd/containerd/pkg/cri/sbserver/podsandbox"
|
"github.com/containerd/containerd/pkg/cri/sbserver/podsandbox"
|
||||||
|
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
||||||
|
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
||||||
"github.com/containerd/containerd/pkg/cri/streaming"
|
"github.com/containerd/containerd/pkg/cri/streaming"
|
||||||
"github.com/containerd/containerd/pkg/kmutex"
|
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/sandbox"
|
"github.com/containerd/containerd/sandbox"
|
||||||
"github.com/containerd/go-cni"
|
"github.com/containerd/go-cni"
|
||||||
@ -46,9 +47,7 @@ import (
|
|||||||
|
|
||||||
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
||||||
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
|
||||||
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
||||||
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
|
||||||
ctrdutil "github.com/containerd/containerd/pkg/cri/util"
|
ctrdutil "github.com/containerd/containerd/pkg/cri/util"
|
||||||
osinterface "github.com/containerd/containerd/pkg/os"
|
osinterface "github.com/containerd/containerd/pkg/os"
|
||||||
"github.com/containerd/containerd/pkg/registrar"
|
"github.com/containerd/containerd/pkg/registrar"
|
||||||
@ -69,8 +68,23 @@ type CRIService interface {
|
|||||||
Register(*grpc.Server) error
|
Register(*grpc.Server) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// imageService specifies dependencies to image service.
|
||||||
|
type imageService interface {
|
||||||
|
runtime.ImageServiceServer
|
||||||
|
|
||||||
|
RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string
|
||||||
|
|
||||||
|
UpdateImage(ctx context.Context, r string) error
|
||||||
|
|
||||||
|
GetImage(id string) (imagestore.Image, error)
|
||||||
|
GetSnapshot(key string) (snapshotstore.Snapshot, error)
|
||||||
|
|
||||||
|
LocalResolve(refOrID string) (imagestore.Image, error)
|
||||||
|
}
|
||||||
|
|
||||||
// criService implements CRIService.
|
// criService implements CRIService.
|
||||||
type criService struct {
|
type criService struct {
|
||||||
|
imageService
|
||||||
// config contains all configurations.
|
// config contains all configurations.
|
||||||
config criconfig.Config
|
config criconfig.Config
|
||||||
// imageFSPath is the path to image filesystem.
|
// imageFSPath is the path to image filesystem.
|
||||||
@ -90,10 +104,6 @@ type criService struct {
|
|||||||
// containerNameIndex stores all container names and make sure each
|
// containerNameIndex stores all container names and make sure each
|
||||||
// name is unique.
|
// name is unique.
|
||||||
containerNameIndex *registrar.Registrar
|
containerNameIndex *registrar.Registrar
|
||||||
// imageStore stores all resources associated with images.
|
|
||||||
imageStore *imagestore.Store
|
|
||||||
// snapshotStore stores information of all snapshots.
|
|
||||||
snapshotStore *snapshotstore.Store
|
|
||||||
// netPlugin is used to setup and teardown network when run/stop pod sandbox.
|
// netPlugin is used to setup and teardown network when run/stop pod sandbox.
|
||||||
netPlugin map[string]cni.CNI
|
netPlugin map[string]cni.CNI
|
||||||
// client is an instance of the containerd client
|
// client is an instance of the containerd client
|
||||||
@ -113,10 +123,6 @@ type criService struct {
|
|||||||
// allCaps is the list of the capabilities.
|
// allCaps is the list of the capabilities.
|
||||||
// When nil, parsed from CapEff of /proc/self/status.
|
// When nil, parsed from CapEff of /proc/self/status.
|
||||||
allCaps []string //nolint:nolintlint,unused // Ignore on non-Linux
|
allCaps []string //nolint:nolintlint,unused // Ignore on non-Linux
|
||||||
// unpackDuplicationSuppressor is used to make sure that there is only
|
|
||||||
// one in-flight fetch request or unpack handler for a given descriptor's
|
|
||||||
// or chain ID.
|
|
||||||
unpackDuplicationSuppressor kmutex.KeyedLocker
|
|
||||||
// containerEventsChan is used to capture container events and send them
|
// containerEventsChan is used to capture container events and send them
|
||||||
// to the caller of GetContainerEvents.
|
// to the caller of GetContainerEvents.
|
||||||
containerEventsChan chan runtime.ContainerEventResponse
|
containerEventsChan chan runtime.ContainerEventResponse
|
||||||
@ -128,31 +134,37 @@ type criService struct {
|
|||||||
func NewCRIService(config criconfig.Config, client *containerd.Client, nri *nri.API) (CRIService, error) {
|
func NewCRIService(config criconfig.Config, client *containerd.Client, nri *nri.API) (CRIService, error) {
|
||||||
var err error
|
var err error
|
||||||
labels := label.NewStore()
|
labels := label.NewStore()
|
||||||
|
|
||||||
|
if client.SnapshotService(config.ContainerdConfig.Snapshotter) == nil {
|
||||||
|
return nil, fmt.Errorf("failed to find snapshotter %q", config.ContainerdConfig.Snapshotter)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageFSPath := imageFSPath(config.ContainerdRootDir, config.ContainerdConfig.Snapshotter)
|
||||||
|
logrus.Infof("Get image filesystem path %q", imageFSPath)
|
||||||
|
|
||||||
|
// TODO: expose this as a separate containerd plugin.
|
||||||
|
imageService, err := images.NewService(config, imageFSPath, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to create CRI image service: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
c := &criService{
|
c := &criService{
|
||||||
config: config,
|
imageService: imageService,
|
||||||
client: client,
|
config: config,
|
||||||
os: osinterface.RealOS{},
|
client: client,
|
||||||
sandboxStore: sandboxstore.NewStore(labels),
|
imageFSPath: imageFSPath,
|
||||||
containerStore: containerstore.NewStore(labels),
|
os: osinterface.RealOS{},
|
||||||
imageStore: imagestore.NewStore(client),
|
sandboxStore: sandboxstore.NewStore(labels),
|
||||||
snapshotStore: snapshotstore.NewStore(),
|
containerStore: containerstore.NewStore(labels),
|
||||||
sandboxNameIndex: registrar.NewRegistrar(),
|
sandboxNameIndex: registrar.NewRegistrar(),
|
||||||
containerNameIndex: registrar.NewRegistrar(),
|
containerNameIndex: registrar.NewRegistrar(),
|
||||||
netPlugin: make(map[string]cni.CNI),
|
netPlugin: make(map[string]cni.CNI),
|
||||||
unpackDuplicationSuppressor: kmutex.New(),
|
sandboxControllers: make(map[criconfig.SandboxControllerMode]sandbox.Controller),
|
||||||
sandboxControllers: make(map[criconfig.SandboxControllerMode]sandbox.Controller),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: figure out a proper channel size.
|
// TODO: figure out a proper channel size.
|
||||||
c.containerEventsChan = make(chan runtime.ContainerEventResponse, 1000)
|
c.containerEventsChan = make(chan runtime.ContainerEventResponse, 1000)
|
||||||
|
|
||||||
if client.SnapshotService(c.config.ContainerdConfig.Snapshotter) == nil {
|
|
||||||
return nil, fmt.Errorf("failed to find snapshotter %q", c.config.ContainerdConfig.Snapshotter)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.imageFSPath = imageFSPath(config.ContainerdRootDir, config.ContainerdConfig.Snapshotter)
|
|
||||||
logrus.Infof("Get image filesystem path %q", c.imageFSPath)
|
|
||||||
|
|
||||||
if err := c.initPlatform(); err != nil {
|
if err := c.initPlatform(); err != nil {
|
||||||
return nil, fmt.Errorf("initialize platform: %w", err)
|
return nil, fmt.Errorf("initialize platform: %w", err)
|
||||||
}
|
}
|
||||||
@ -189,7 +201,7 @@ func NewCRIService(config criconfig.Config, client *containerd.Client, nri *nri.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load all sandbox controllers(pod sandbox controller and remote shim controller)
|
// Load all sandbox controllers(pod sandbox controller and remote shim controller)
|
||||||
c.sandboxControllers[criconfig.ModePodSandbox] = podsandbox.New(config, client, c.sandboxStore, c.os, c, c.baseOCISpecs)
|
c.sandboxControllers[criconfig.ModePodSandbox] = podsandbox.New(config, client, c.sandboxStore, c.os, c, imageService, c.baseOCISpecs)
|
||||||
c.sandboxControllers[criconfig.ModeShim] = client.SandboxController()
|
c.sandboxControllers[criconfig.ModeShim] = client.SandboxController()
|
||||||
|
|
||||||
c.nri = nri
|
c.nri = nri
|
||||||
@ -238,15 +250,6 @@ func (c *criService) Run() error {
|
|||||||
logrus.Info("Start event monitor")
|
logrus.Info("Start event monitor")
|
||||||
eventMonitorErrCh := c.eventMonitor.start()
|
eventMonitorErrCh := c.eventMonitor.start()
|
||||||
|
|
||||||
// Start snapshot stats syncer, it doesn't need to be stopped.
|
|
||||||
logrus.Info("Start snapshots syncer")
|
|
||||||
snapshotsSyncer := newSnapshotsSyncer(
|
|
||||||
c.snapshotStore,
|
|
||||||
c.client.SnapshotService(c.config.ContainerdConfig.Snapshotter),
|
|
||||||
time.Duration(c.config.StatsCollectPeriod)*time.Second,
|
|
||||||
)
|
|
||||||
snapshotsSyncer.start()
|
|
||||||
|
|
||||||
// Start CNI network conf syncers
|
// Start CNI network conf syncers
|
||||||
cniNetConfMonitorErrCh := make(chan error, len(c.cniNetConfMonitor))
|
cniNetConfMonitorErrCh := make(chan error, len(c.cniNetConfMonitor))
|
||||||
var netSyncGroup sync.WaitGroup
|
var netSyncGroup sync.WaitGroup
|
||||||
|
@ -29,10 +29,8 @@ import (
|
|||||||
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
criconfig "github.com/containerd/containerd/pkg/cri/config"
|
||||||
servertesting "github.com/containerd/containerd/pkg/cri/server/testing"
|
servertesting "github.com/containerd/containerd/pkg/cri/server/testing"
|
||||||
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
containerstore "github.com/containerd/containerd/pkg/cri/store/container"
|
||||||
imagestore "github.com/containerd/containerd/pkg/cri/store/image"
|
|
||||||
"github.com/containerd/containerd/pkg/cri/store/label"
|
"github.com/containerd/containerd/pkg/cri/store/label"
|
||||||
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox"
|
||||||
snapshotstore "github.com/containerd/containerd/pkg/cri/store/snapshot"
|
|
||||||
ostesting "github.com/containerd/containerd/pkg/os/testing"
|
ostesting "github.com/containerd/containerd/pkg/os/testing"
|
||||||
"github.com/containerd/containerd/pkg/registrar"
|
"github.com/containerd/containerd/pkg/registrar"
|
||||||
)
|
)
|
||||||
@ -41,12 +39,10 @@ import (
|
|||||||
func newTestCRIService() *criService {
|
func newTestCRIService() *criService {
|
||||||
labels := label.NewStore()
|
labels := label.NewStore()
|
||||||
return &criService{
|
return &criService{
|
||||||
|
imageService: &fakeImageService{},
|
||||||
config: testConfig,
|
config: testConfig,
|
||||||
imageFSPath: testImageFSPath,
|
|
||||||
os: ostesting.NewFakeOS(),
|
os: ostesting.NewFakeOS(),
|
||||||
sandboxStore: sandboxstore.NewStore(labels),
|
sandboxStore: sandboxstore.NewStore(labels),
|
||||||
imageStore: imagestore.NewStore(nil),
|
|
||||||
snapshotStore: snapshotstore.NewStore(),
|
|
||||||
sandboxNameIndex: registrar.NewRegistrar(),
|
sandboxNameIndex: registrar.NewRegistrar(),
|
||||||
containerStore: containerstore.NewStore(labels),
|
containerStore: containerstore.NewStore(labels),
|
||||||
containerNameIndex: registrar.NewRegistrar(),
|
containerNameIndex: registrar.NewRegistrar(),
|
||||||
|
@ -25,7 +25,6 @@ const (
|
|||||||
// TODO(random-liu): Change this to image name after we have complete image
|
// TODO(random-liu): Change this to image name after we have complete image
|
||||||
// management unit test framework.
|
// management unit test framework.
|
||||||
testSandboxImage = "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113798"
|
testSandboxImage = "sha256:c75bebcdd211f41b3a460c7bf82970ed6c75acaab9cd4c9a4e125b03ca113798"
|
||||||
testImageFSPath = "/test/image/fs/path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testConfig = criconfig.Config{
|
var testConfig = criconfig.Config{
|
||||||
|
Loading…
Reference in New Issue
Block a user