diff --git a/client.go b/client.go index 39adfd9e7..af35ec9c9 100644 --- a/client.go +++ b/client.go @@ -136,6 +136,20 @@ func New(address string, opts ...ClientOpt) (*Client, error) { if copts.services == nil && c.conn == nil { return nil, errors.New("no grpc connection or services is available") } + + // check namespace labels for default runtime + if copts.defaultRuntime == "" && copts.defaultns != "" { + namespaces := c.NamespaceService() + ctx := context.Background() + if labels, err := namespaces.Labels(ctx, copts.defaultns); err == nil { + if defaultRuntime, ok := labels[defaults.DefaultRuntimeNSLabel]; ok { + c.runtime = defaultRuntime + } + } else { + return nil, err + } + } + return c, nil } @@ -152,6 +166,20 @@ func NewWithConn(conn *grpc.ClientConn, opts ...ClientOpt) (*Client, error) { conn: conn, runtime: fmt.Sprintf("%s.%s", plugin.RuntimePlugin, runtime.GOOS), } + + // check namespace labels for default runtime + if copts.defaultRuntime == "" && copts.defaultns != "" { + namespaces := c.NamespaceService() + ctx := context.Background() + if labels, err := namespaces.Labels(ctx, copts.defaultns); err == nil { + if defaultRuntime, ok := labels[defaults.DefaultRuntimeNSLabel]; ok { + c.runtime = defaultRuntime + } + } else { + return nil, err + } + } + if copts.services != nil { c.services = *copts.services } diff --git a/client_test.go b/client_test.go index 821517e43..b9f2febbb 100644 --- a/client_test.go +++ b/client_test.go @@ -28,6 +28,7 @@ import ( "testing" "time" + "github.com/containerd/containerd/defaults" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" "github.com/containerd/containerd/namespaces" @@ -396,3 +397,29 @@ func createShimDebugConfig() string { return f.Name() } + +func TestDefaultRuntimeWithNamespaceLabels(t *testing.T) { + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + ctx, cancel := testContext() + defer cancel() + namespaces := client.NamespaceService() + testRuntime := "testRuntime" + runtimeLabel := defaults.DefaultRuntimeNSLabel + if err := namespaces.SetLabel(ctx, testNamespace, runtimeLabel, testRuntime); err != nil { + t.Fatal(err) + } + + testClient, err := New(address, WithDefaultNamespace(testNamespace)) + if err != nil { + t.Fatal(err) + } + defer testClient.Close() + if testClient.runtime != testRuntime { + t.Error("failed to set default runtime from namespace labels") + } +} diff --git a/container_opts.go b/container_opts.go index de900f43e..1ce989432 100644 --- a/container_opts.go +++ b/container_opts.go @@ -20,7 +20,9 @@ import ( "context" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/defaults" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/oci" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/snapshots" @@ -107,7 +109,7 @@ func WithSnapshotter(name string) NewContainerOpts { // WithSnapshot uses an existing root filesystem for the container func WithSnapshot(id string) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - setSnapshotterIfEmpty(c) + setSnapshotterIfEmpty(ctx, client, c) // check that the snapshot exists, if not, fail on creation if _, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, id); err != nil { return err @@ -125,7 +127,7 @@ func WithNewSnapshot(id string, i Image, opts ...snapshots.Opt) NewContainerOpts if err != nil { return err } - setSnapshotterIfEmpty(c) + setSnapshotterIfEmpty(ctx, client, c) parent := identity.ChainID(diffIDs).String() if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, parent, opts...); err != nil { return err @@ -155,7 +157,7 @@ func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainer if err != nil { return err } - setSnapshotterIfEmpty(c) + setSnapshotterIfEmpty(ctx, client, c) parent := identity.ChainID(diffIDs).String() if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, parent, opts...); err != nil { return err @@ -166,9 +168,18 @@ func WithNewSnapshotView(id string, i Image, opts ...snapshots.Opt) NewContainer } } -func setSnapshotterIfEmpty(c *containers.Container) { +func setSnapshotterIfEmpty(ctx context.Context, client *Client, c *containers.Container) { if c.Snapshotter == "" { - c.Snapshotter = DefaultSnapshotter + defaultSnapshotter := DefaultSnapshotter + namespaceService := client.NamespaceService() + if ns, err := namespaces.NamespaceRequired(ctx); err == nil { + if labels, err := namespaceService.Labels(ctx, ns); err == nil { + if snapshotLabel, ok := labels[defaults.DefaultSnapshotterNSLabel]; ok { + defaultSnapshotter = snapshotLabel + } + } + } + c.Snapshotter = defaultSnapshotter } } diff --git a/container_opts_unix.go b/container_opts_unix.go index 9e013f1a4..340a91857 100644 --- a/container_opts_unix.go +++ b/container_opts_unix.go @@ -50,7 +50,7 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool return err } - setSnapshotterIfEmpty(c) + setSnapshotterIfEmpty(ctx, client, c) var ( snapshotter = client.SnapshotService(c.Snapshotter) diff --git a/defaults/defaults.go b/defaults/defaults.go index 7040f5b85..3a748e4e8 100644 --- a/defaults/defaults.go +++ b/defaults/defaults.go @@ -23,4 +23,10 @@ const ( // DefaultMaxSendMsgSize defines the default maximum message size for // sending protobufs passed over the GRPC API. DefaultMaxSendMsgSize = 16 << 20 + // DefaultRuntimeNSLabel defines the namespace label to check for + // default runtime + DefaultRuntimeNSLabel = "containerd.io/defaults/runtime" + // DefaultSnapshotterNSLabel defines the namespances label to check for + // default snapshotter + DefaultSnapshotterNSLabel = "containerd.io/defaults/snapshotter" ) diff --git a/docs/namespaces.md b/docs/namespaces.md index 5f2fcdddf..ff939b1d0 100644 --- a/docs/namespaces.md +++ b/docs/namespaces.md @@ -34,9 +34,7 @@ Filesystem paths, IDs, and other system level resources must be namespaced for a Simply create a new `context` and set your application's namespace on the `context`. Make sure to use a unique namespace for applications that does not conflict with existing namespaces. The namespaces -API, or the `ctr namespaces` client command, can be used to query/list and create new namespaces. Note that namespaces -can have a list of labels associated with the namespace. This can be useful for associating metadata with a particular -namespace. +API, or the `ctr namespaces` client command, can be used to query/list and create new namespaces. ```go ctx := context.Background() @@ -49,6 +47,19 @@ var ( ) ``` +## Namespace Labels + +Namespaces can have a list of labels associated with the namespace. This can be useful for associating metadata with a particular namespace. +Labels can also be used to configure the defaults for containerd, for example: + +```bash +> sudo ctr namespaces label k8s.io containerd.io/defaults/snapshotter=btrfs +> sudo ctr namespaces label k8s.io containerd.io/defaults/runtime=testRuntime +``` + +This will set the default snapshotter as `btrfs` and runtime as `testRuntime`. +Note that currently only these two labels are used to configure the defaults and labels of `default` namespace are not considered for the same. + ## Inspecting Namespaces If we need to inspect containers, images, or other resources in various namespaces the `ctr` tool allows you to do this.