diff --git a/api/server/router/system/system_routes.go b/api/server/router/system/system_routes.go index c2453334aa..e58ef781b9 100644 --- a/api/server/router/system/system_routes.go +++ b/api/server/router/system/system_routes.go @@ -97,6 +97,10 @@ func (s *systemRouter) getInfo(ctx context.Context, w http.ResponseWriter, r *ht info.Runtimes[k] = system.RuntimeWithStatus{Runtime: rt.Runtime} } } + if versions.LessThan(version, "1.46") { + // Containerd field introduced in API v1.46. + info.Containerd = nil + } if versions.GreaterThanOrEqualTo(version, "1.42") { info.KernelMemory = false } diff --git a/api/swagger.yaml b/api/swagger.yaml index fcafbd4feb..d879fb0bdd 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -5824,6 +5824,58 @@ definitions: example: - "/etc/cdi" - "/var/run/cdi" + Containerd: + $ref: "#/definitions/ContainerdInfo" + x-nullable: true + + ContainerdInfo: + description: | + Information for connecting to the containerd instance that is used by the daemon. + This is included for debugging purposes only. + type: "object" + properties: + Address: + description: "The address of the containerd socket." + type: "string" + example: "/run/containerd/containerd.sock" + Namespaces: + description: | + The namespaces that the daemon uses for running containers and + plugins in containerd. These namespaces can be configured in the + daemon configuration, and are considered to be used exclusively + by the daemon, Tampering with the containerd instance may cause + unexpected behavior. + + As these namespaces are considered to be exclusively accessed + by the daemon, it is not recommended to change these values, + or to change them to a value that is used by other systems, + such as cri-containerd. + type: "object" + properties: + Containers: + description: | + The default containerd namespace used for containers managed + by the daemon. + + The default namespace for containers is "moby", but will be + suffixed with the `.` of the remapped `root` if + user-namespaces are enabled and the containerd image-store + is used. + type: "string" + default: "moby" + example: "moby" + Plugins: + description: | + The default containerd namespace used for plugins managed by + the daemon. + + The default namespace for plugins is "plugins.moby", but will be + suffixed with the `.` of the remapped `root` if + user-namespaces are enabled and the containerd image-store + is used. + type: "string" + default: "plugins.moby" + example: "plugins.moby" # PluginsInfo is a temp struct holding Plugins name # registered with docker daemon. It is used by Info struct diff --git a/api/types/system/info.go b/api/types/system/info.go index 89d4a0098e..6791cf3284 100644 --- a/api/types/system/info.go +++ b/api/types/system/info.go @@ -75,6 +75,8 @@ type Info struct { DefaultAddressPools []NetworkAddressPool `json:",omitempty"` CDISpecDirs []string + Containerd *ContainerdInfo `json:",omitempty"` + // Legacy API fields for older API versions. legacyFields @@ -85,6 +87,43 @@ type Info struct { Warnings []string } +// ContainerdInfo holds information about the containerd instance used by the daemon. +type ContainerdInfo struct { + // Address is the path to the containerd socket. + Address string `json:",omitempty"` + // Namespaces is the containerd namespaces used by the daemon. + Namespaces ContainerdNamespaces +} + +// ContainerdNamespaces reflects the containerd namespaces used by the daemon. +// +// These namespaces can be configured in the daemon configuration, and are +// considered to be used exclusively by the daemon, +// +// As these namespaces are considered to be exclusively accessed +// by the daemon, it is not recommended to change these values, +// or to change them to a value that is used by other systems, +// such as cri-containerd. +type ContainerdNamespaces struct { + // Containers holds the default containerd namespace used for + // containers managed by the daemon. + // + // The default namespace for containers is "moby", but will be + // suffixed with the `.` of the remapped `root` if + // user-namespaces are enabled and the containerd image-store + // is used. + Containers string + + // Plugins holds the default containerd namespace used for + // plugins managed by the daemon. + // + // The default namespace for plugins is "moby", but will be + // suffixed with the `.` of the remapped `root` if + // user-namespaces are enabled and the containerd image-store + // is used. + Plugins string +} + type legacyFields struct { ExecutionDriver string `json:",omitempty"` // Deprecated: deprecated since API v1.25, but returned for older versions. } diff --git a/daemon/containerd/image.go b/daemon/containerd/image.go index 70a72f674b..81ae4711c8 100644 --- a/daemon/containerd/image.go +++ b/daemon/containerd/image.go @@ -273,6 +273,11 @@ func (i *ImageService) resolveDescriptor(ctx context.Context, refOrID string) (o return img.Target, nil } +// ResolveImage looks up an image by reference or identifier in the image store. +func (i *ImageService) ResolveImage(ctx context.Context, refOrID string) (containerdimages.Image, error) { + return i.resolveImage(ctx, refOrID) +} + func (i *ImageService) resolveImage(ctx context.Context, refOrID string) (containerdimages.Image, error) { parsed, err := reference.ParseAnyReference(refOrID) if err != nil { diff --git a/daemon/info.go b/daemon/info.go index 6c9d4844b7..6669d7644e 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -82,7 +82,9 @@ func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) { daemon.fillContainerStates(v) daemon.fillDebugInfo(ctx, v) + daemon.fillContainerdInfo(v, &cfg.Config) daemon.fillAPIInfo(v, &cfg.Config) + // Retrieve platform specific info if err := daemon.fillPlatformInfo(ctx, v, sysInfo, cfg); err != nil { return nil, err @@ -227,6 +229,22 @@ func (daemon *Daemon) fillDebugInfo(ctx context.Context, v *system.Info) { v.NFd = fileutils.GetTotalUsedFds(ctx) v.NGoroutines = runtime.NumGoroutine() v.NEventsListener = daemon.EventsService.SubscribersCount() + +} + +// fillContainerdInfo provides information about the containerd configuration +// for debugging purposes. +func (daemon *Daemon) fillContainerdInfo(v *system.Info, cfg *config.Config) { + if cfg.ContainerdAddr == "" { + return + } + v.Containerd = &system.ContainerdInfo{ + Address: cfg.ContainerdAddr, + Namespaces: system.ContainerdNamespaces{ + Containers: cfg.ContainerdNamespace, + Plugins: cfg.ContainerdPluginNamespace, + }, + } } func (daemon *Daemon) fillAPIInfo(v *system.Info, cfg *config.Config) { diff --git a/daemon/start.go b/daemon/start.go index 9eb212efb1..027f08a6dc 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -4,10 +4,13 @@ import ( "context" "time" + "github.com/containerd/containerd" + "github.com/containerd/containerd/containers" "github.com/containerd/log" "github.com/docker/docker/api/types/backend" "github.com/docker/docker/api/types/events" "github.com/docker/docker/container" + mobyc8dstore "github.com/docker/docker/daemon/containerd" "github.com/docker/docker/errdefs" "github.com/docker/docker/libcontainerd" "github.com/pkg/errors" @@ -173,7 +176,22 @@ func (daemon *Daemon) containerStart(ctx context.Context, daemonCfg *configStore return err } - ctr, err := libcontainerd.ReplaceContainer(ctx, daemon.containerd, container.ID, spec, shim, createOptions) + ctr, err := libcontainerd.ReplaceContainer(ctx, daemon.containerd, container.ID, spec, shim, createOptions, func(ctx context.Context, client *containerd.Client, c *containers.Container) error { + // Only set the image if we are using containerd for image storage. + // This is for metadata purposes only. + // Other lower-level components may make use of this information. + is, ok := daemon.imageService.(*mobyc8dstore.ImageService) + if !ok { + return nil + } + img, err := is.ResolveImage(ctx, container.Config.Image) + if err != nil { + log.G(ctx).WithError(err).WithField("container", container.ID).Warn("Failed to resolve containerd image reference") + return nil + } + c.Image = img.Name + return nil + }) if err != nil { return setExitCodeFromError(container.SetExitCode, err) } diff --git a/docs/api/version-history.md b/docs/api/version-history.md index 7ae06f44cc..e4221fa286 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -17,6 +17,9 @@ keywords: "API, Docker, rcli, REST, documentation" [Docker Engine API v1.46](https://docs.docker.com/engine/api/v1.46/) documentation +* `GET /info` now includes a `Containerd` field containing information about + the location of the containerd API socket and containerd namespaces used + by the daemon to run containers and plugins. * `POST /containers/create` field `NetworkingConfig.EndpointsConfig.DriverOpts`, and `POST /networks/{id}/connect` field `EndpointsConfig.DriverOpts`, now support label `com.docker.network.endpoint.sysctls` for setting per-interface diff --git a/integration/container/create_test.go b/integration/container/create_test.go index a4541d12a1..bed416e3fd 100644 --- a/integration/container/create_test.go +++ b/integration/container/create_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/containerd/containerd" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/versions" @@ -668,3 +669,40 @@ func TestCreateWithCustomMACs(t *testing.T) { assert.Equal(t, mac, "02:32:1c:23:00:04") } } + +// Tests that when using containerd backed storage the containerd container has the image referenced stored. +func TestContainerdContainerImageInfo(t *testing.T) { + skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.46"), "requires API v1.46") + + ctx := setupTest(t) + + apiClient := testEnv.APIClient() + defer apiClient.Close() + + info, err := apiClient.Info(ctx) + assert.NilError(t, err) + + skip.If(t, info.Containerd == nil, "requires containerd") + + // Currently a containerd container is only created when the container is started. + // So start the container and then inspect the containerd container to verify the image info. + id := ctr.Run(ctx, t, apiClient, func(cfg *ctr.TestContainerConfig) { + // busybox is the default (as of this writing) used by the test client, but lets be explicit here. + cfg.Config.Image = "busybox" + }) + defer apiClient.ContainerRemove(ctx, id, container.RemoveOptions{Force: true}) + + client, err := containerd.New(info.Containerd.Address, containerd.WithDefaultNamespace(info.Containerd.Namespaces.Containers)) + assert.NilError(t, err) + defer client.Close() + + ctr, err := client.ContainerService().Get(ctx, id) + assert.NilError(t, err) + + if testEnv.UsingSnapshotter() { + assert.Equal(t, ctr.Image, "docker.io/library/busybox:latest") + } else { + // This field is not set when not using contianerd backed storage. + assert.Equal(t, ctr.Image, "") + } +}