diff --git a/cmd/dockerd/daemon.go b/cmd/dockerd/daemon.go index ae8844c762..6277faffc4 100644 --- a/cmd/dockerd/daemon.go +++ b/cmd/dockerd/daemon.go @@ -288,34 +288,39 @@ func newRouterOptions(config *config.Config, d *daemon.Daemon) (routerOptions, e return opts, err } cgroupParent := newCgroupParent(config) - bk, err := buildkit.New(buildkit.Opt{ - SessionManager: sm, - Root: filepath.Join(config.Root, "buildkit"), - Dist: d.DistributionServices(), - NetworkController: d.NetworkController(), - DefaultCgroupParent: cgroupParent, - RegistryHosts: d.RegistryHosts(), - BuilderConfig: config.Builder, - Rootless: d.Rootless(), - IdentityMapping: d.IdentityMapping(), - DNSConfig: config.DNSConfig, - ApparmorProfile: daemon.DefaultApparmorProfile(), - }) - if err != nil { - return opts, err - } - - bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk, d.EventsService) - if err != nil { - return opts, errors.Wrap(err, "failed to create buildmanager") - } - return routerOptions{ + ro := routerOptions{ sessionManager: sm, - buildBackend: bb, - buildkit: bk, features: d.Features(), daemon: d, - }, nil + } + if !d.UsesSnapshotter() { + bk, err := buildkit.New(buildkit.Opt{ + SessionManager: sm, + Root: filepath.Join(config.Root, "buildkit"), + Dist: d.DistributionServices(), + NetworkController: d.NetworkController(), + DefaultCgroupParent: cgroupParent, + RegistryHosts: d.RegistryHosts(), + BuilderConfig: config.Builder, + Rootless: d.Rootless(), + IdentityMapping: d.IdentityMapping(), + DNSConfig: config.DNSConfig, + ApparmorProfile: daemon.DefaultApparmorProfile(), + }) + if err != nil { + return opts, err + } + + bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk, d.EventsService) + if err != nil { + return opts, errors.Wrap(err, "failed to create buildmanager") + } + + ro.buildBackend = bb + ro.buildkit = bk + } + + return ro, nil } func (cli *DaemonCli) reloadConfig() { @@ -536,14 +541,8 @@ func initRouter(opts routerOptions) { distributionrouter.NewRouter(opts.daemon.ImageService()), } - grpcBackends := []grpcrouter.Backend{} - for _, b := range []interface{}{opts.daemon, opts.buildBackend} { - if b, ok := b.(grpcrouter.Backend); ok { - grpcBackends = append(grpcBackends, b) - } - } - if len(grpcBackends) > 0 { - routers = append(routers, grpcrouter.NewRouter(grpcBackends...)) + if opts.buildBackend != nil { + routers = append(routers, grpcrouter.NewRouter(opts.buildBackend)) } if opts.daemon.NetworkControllerEnabled() { diff --git a/daemon/containerd/service.go b/daemon/containerd/service.go new file mode 100644 index 0000000000..1d841976b3 --- /dev/null +++ b/daemon/containerd/service.go @@ -0,0 +1,370 @@ +package containerd + +import ( + "context" + "io" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/platforms" + "github.com/docker/distribution" + "github.com/docker/distribution/reference" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/backend" + "github.com/docker/docker/api/types/filters" + imagetype "github.com/docker/docker/api/types/image" + registrytypes "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/builder" + "github.com/docker/docker/container" + "github.com/docker/docker/daemon/images" + "github.com/docker/docker/errdefs" + "github.com/docker/docker/image" + "github.com/docker/docker/layer" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// ImageService implements daemon.ImageService +type ImageService struct { + client *containerd.Client +} + +// NewService creates a new ImageService. +func NewService(c *containerd.Client) *ImageService { + return &ImageService{ + client: c, + } +} + +// PullImage initiates a pull operation. image is the repository name to pull, and +// tagOrDigest may be either empty, or indicate a specific tag or digest to pull. +func (cs *ImageService) PullImage(ctx context.Context, image, tagOrDigest string, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { + var opts []containerd.RemoteOpt + if platform != nil { + opts = append(opts, containerd.WithPlatform(platforms.Format(*platform))) + } + ref, err := reference.ParseNormalizedNamed(image) + if err != nil { + return errdefs.InvalidParameter(err) + } + + // TODO(thaJeztah) this could use a WithTagOrDigest() utility + if tagOrDigest != "" { + // The "tag" could actually be a digest. + var dgst digest.Digest + dgst, err = digest.Parse(tagOrDigest) + if err == nil { + ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst) + } else { + ref, err = reference.WithTag(ref, tagOrDigest) + } + if err != nil { + return errdefs.InvalidParameter(err) + } + } + + _, err = cs.client.Pull(ctx, ref.String(), opts...) + return err +} + +// Images returns a filtered list of images. +func (cs *ImageService) Images(ctx context.Context, opts types.ImageListOptions) ([]*types.ImageSummary, error) { + imgs, err := cs.client.ListImages(ctx) + if err != nil { + return nil, err + } + + var ret []*types.ImageSummary + for _, img := range imgs { + size, err := img.Size(ctx) + if err != nil { + return nil, err + } + + ret = append(ret, &types.ImageSummary{ + RepoDigests: []string{img.Name() + "@" + img.Target().Digest.String()}, // "hello-world@sha256:bfea6278a0a267fad2634554f4f0c6f31981eea41c553fdf5a83e95a41d40c38"}, + RepoTags: []string{img.Name()}, + Containers: -1, + ParentID: "", + SharedSize: -1, + VirtualSize: 10, + ID: img.Target().Digest.String(), + Created: img.Metadata().CreatedAt.Unix(), + Size: size, + }) + } + + return ret, nil +} + +// LogImageEvent generates an event related to an image with only the +// default attributes. +func (cs *ImageService) LogImageEvent(imageID, refName, action string) { + panic("not implemented") +} + +// LogImageEventWithAttributes generates an event related to an image with +// specific given attributes. +func (cs *ImageService) LogImageEventWithAttributes(imageID, refName, action string, attributes map[string]string) { + panic("not implemented") +} + +// GetLayerFolders returns the layer folders from an image RootFS. +func (cs *ImageService) GetLayerFolders(img *image.Image, rwLayer layer.RWLayer) ([]string, error) { + panic("not implemented") +} + +// Map returns a map of all images in the ImageStore. +func (cs *ImageService) Map() map[image.ID]*image.Image { + panic("not implemented") +} + +// GetLayerByID returns a layer by ID +// called from daemon.go Daemon.restore(), and Daemon.containerExport(). +func (cs *ImageService) GetLayerByID(string) (layer.RWLayer, error) { + panic("not implemented") +} + +// GetLayerMountID returns the mount ID for a layer +// called from daemon.go Daemon.Shutdown(), and Daemon.Cleanup() (cleanup is actually continerCleanup) +// TODO: needs to be refactored to Unmount (see callers), or removed and replaced with GetLayerByID +func (cs *ImageService) GetLayerMountID(string) (string, error) { + panic("not implemented") +} + +// Cleanup resources before the process is shutdown. +// called from daemon.go Daemon.Shutdown() +func (cs *ImageService) Cleanup() error { + return nil +} + +// GraphDriverName returns the name of the graph drvier +// moved from Daemon.GraphDriverName, used by: +// - newContainer +// - to report an error in Daemon.Mount(container) +func (cs *ImageService) GraphDriverName() string { + return "" +} + +// CommitBuildStep is used by the builder to create an image for each step in +// the build. +// +// This method is different from CreateImageFromContainer: +// - it doesn't attempt to validate container state +// - it doesn't send a commit action to metrics +// - it doesn't log a container commit event +// +// This is a temporary shim. Should be removed when builder stops using commit. +func (cs *ImageService) CommitBuildStep(c backend.CommitConfig) (image.ID, error) { + panic("not implemented") +} + +// CreateImage creates a new image by adding a config and ID to the image store. +// This is similar to LoadImage() except that it receives JSON encoded bytes of +// an image instead of a tar archive. +func (cs *ImageService) CreateImage(config []byte, parent string) (builder.Image, error) { + panic("not implemented") +} + +// GetImageAndReleasableLayer returns an image and releaseable layer for a +// eference or ID. Every call to GetImageAndReleasableLayer MUST call +// releasableLayer.Release() to prevent leaking of layers. +func (cs *ImageService) GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error) { + panic("not implemented") +} + +// MakeImageCache creates a stateful image cache. +func (cs *ImageService) MakeImageCache(sourceRefs []string) builder.ImageCache { + panic("not implemented") +} + +// TagImageWithReference adds the given reference to the image ID provided. +func (cs *ImageService) TagImageWithReference(imageID image.ID, newTag reference.Named) error { + panic("not implemented") +} + +// SquashImage creates a new image with the diff of the specified image and +// the specified parent. This new image contains only the layers from its +// parent + 1 extra layer which contains the diff of all the layers in between. +// The existing image(s) is not destroyed. If no parent is specified, a new +// image with the diff of all the specified image's layers merged into a new +// layer that has no parents. +func (cs *ImageService) SquashImage(id, parent string) (string, error) { + panic("not implemented") +} + +// ExportImage exports a list of images to the given output stream. The +// exported images are archived into a tar when written to the output +// stream. All images with the given tag and all versions containing +// the same tag are exported. names is the set of tags to export, and +// outStream is the writer which the images are written to. +func (cs *ImageService) ExportImage(names []string, outStream io.Writer) error { + panic("not implemented") +} + +// ImageDelete deletes the image referenced by the given imageRef from this +// daemon. The given imageRef can be an image ID, ID prefix, or a repository +// reference (with an optional tag or digest, defaulting to the tag name +// "latest"). There is differing behavior depending on whether the given +// imageRef is a repository reference or not. +// +// If the given imageRef is a repository reference then that repository +// reference will be removed. However, if there exists any containers which +// were created using the same image reference then the repository reference +// cannot be removed unless either there are other repository references to the +// same image or force is true. Following removal of the repository reference, +// the referenced image itself will attempt to be deleted as described below +// but quietly, meaning any image delete conflicts will cause the image to not +// be deleted and the conflict will not be reported. +// +// There may be conflicts preventing deletion of an image and these conflicts +// are divided into two categories grouped by their severity: +// +// Hard Conflict: +// - a pull or build using the image. +// - any descendant image. +// - any running container using the image. +// +// Soft Conflict: +// - any stopped container using the image. +// - any repository tag or digest references to the image. +// +// The image cannot be removed if there are any hard conflicts and can be +// removed if there are soft conflicts only if force is true. +// +// If prune is true, ancestor images will each attempt to be deleted quietly, +// meaning any delete conflicts will cause the image to not be deleted and the +// conflict will not be reported. +func (cs *ImageService) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDeleteResponseItem, error) { + panic("not implemented") +} + +// ImageHistory returns a slice of ImageHistory structures for the specified +// image name by walking the image lineage. +func (cs *ImageService) ImageHistory(name string) ([]*imagetype.HistoryResponseItem, error) { + panic("not implemented") +} + +// ImagesPrune removes unused images +func (cs *ImageService) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (*types.ImagesPruneReport, error) { + panic("not implemented") +} + +// ImportImage imports an image, getting the archived layer data either from +// inConfig (if src is "-"), or from a URI specified in src. Progress output is +// written to outStream. Repository and tag names can optionally be given in +// the repo and tag arguments, respectively. +func (cs *ImageService) ImportImage(src string, repository string, platform *ocispec.Platform, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error { + panic("not implemented") +} + +// LoadImage uploads a set of images into the repository. This is the +// complement of ExportImage. The input stream is an uncompressed tar +// ball containing images and metadata. +func (cs *ImageService) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error { + panic("not implemented") +} + +// LookupImage is not implemented. +func (cs *ImageService) LookupImage(ctx context.Context, name string) (*types.ImageInspect, error) { + panic("not implemented") +} + +// PushImage initiates a push operation on the repository named localName. +func (cs *ImageService) PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { + panic("not implemented") +} + +// SearchRegistryForImages queries the registry for images matching +// term. authConfig is used to login. +// +// TODO: this could be implemented in a registry service instead of the image +// service. +func (cs *ImageService) SearchRegistryForImages(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registrytypes.SearchResults, error) { + panic("not implemented") +} + +// TagImage creates the tag specified by newTag, pointing to the image named +// imageName (alternatively, imageName can also be an image ID). +func (cs *ImageService) TagImage(imageName, repository, tag string) (string, error) { + panic("not implemented") +} + +// GetRepository returns a repository from the registry. +func (cs *ImageService) GetRepository(context.Context, reference.Named, *types.AuthConfig) (distribution.Repository, error) { + panic("not implemented") +} + +// ImageDiskUsage returns information about image data disk usage. +func (cs *ImageService) ImageDiskUsage(ctx context.Context) ([]*types.ImageSummary, error) { + panic("not implemented") +} + +// LayerDiskUsage returns the number of bytes used by layer stores +// called from disk_usage.go +func (cs *ImageService) LayerDiskUsage(ctx context.Context) (int64, error) { + panic("not implemented") +} + +// ReleaseLayer releases a layer allowing it to be removed +// called from delete.go Daemon.cleanupContainer(), and Daemon.containerExport() +func (cs *ImageService) ReleaseLayer(rwlayer layer.RWLayer) error { + panic("not implemented") +} + +// CommitImage creates a new image from a commit config. +func (cs *ImageService) CommitImage(c backend.CommitConfig) (image.ID, error) { + panic("not implemented") +} + +// GetImage returns an image corresponding to the image referred to by refOrID. +func (cs *ImageService) GetImage(refOrID string, platform *ocispec.Platform) (retImg *image.Image, retErr error) { + panic("not implemented") +} + +// CreateLayer creates a filesystem layer for a container. +// called from create.go +// TODO: accept an opt struct instead of container? +func (cs *ImageService) CreateLayer(container *container.Container, initFunc layer.MountInit) (layer.RWLayer, error) { + panic("not implemented") +} + +// DistributionServices return services controlling daemon image storage. +func (cs *ImageService) DistributionServices() images.DistributionServices { + return images.DistributionServices{} +} + +// CountImages returns the number of images stored by ImageService +// called from info.go +func (cs *ImageService) CountImages() int { + imgs, err := cs.client.ListImages(context.TODO()) + if err != nil { + return 0 + } + + return len(imgs) +} + +// LayerStoreStatus returns the status for each layer store +// called from info.go +func (cs *ImageService) LayerStoreStatus() [][2]string { + return [][2]string{} +} + +// GetContainerLayerSize returns the real size & virtual size of the container. +func (cs *ImageService) GetContainerLayerSize(containerID string) (int64, int64) { + panic("not implemented") +} + +// UpdateConfig values +// +// called from reload.go +func (cs *ImageService) UpdateConfig(maxDownloads, maxUploads int) { + panic("not implemented") +} + +// Children returns the children image.IDs for a parent image. +// called from list.go to filter containers +// TODO: refactor to expose an ancestry for image.ID? +func (cs *ImageService) Children(id image.ID) []image.ID { + panic("not implemented") +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 470ec60428..e9c3765042 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/builder" "github.com/docker/docker/container" "github.com/docker/docker/daemon/config" + ctrd "github.com/docker/docker/daemon/containerd" "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/exec" _ "github.com/docker/docker/daemon/graphdriver/register" // register graph drivers @@ -141,8 +142,8 @@ func (daemon *Daemon) Features() *map[string]bool { return &daemon.configStore.Features } -// usesSnapshotter returns true if feature flag to use containerd snapshotter is enabled -func (daemon *Daemon) usesSnapshotter() bool { +// UsesSnapshotter returns true if feature flag to use containerd snapshotter is enabled +func (daemon *Daemon) UsesSnapshotter() bool { if daemon.configStore.Features != nil { if b, ok := daemon.configStore.Features["containerd-snapshotter"]; ok { return b @@ -964,15 +965,6 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S } imageRoot := filepath.Join(config.Root, "image", d.graphDriver) - ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb")) - if err != nil { - return nil, err - } - - imageStore, err := image.NewImageStore(ifs, layerStore) - if err != nil { - return nil, err - } d.volumes, err = volumesservice.NewVolumeService(config.Root, d.PluginStore, rootIDs, d) if err != nil { @@ -1004,11 +996,6 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S } d.ReferenceStore = rs - distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution")) - if err != nil { - return nil, err - } - // Check if Devices cgroup is mounted, it is hard requirement for container security, // on Linux. // @@ -1039,55 +1026,75 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S d.linkIndex = newLinkIndex() - imgSvcConfig := images.ImageServiceConfig{ - ContainerStore: d.containers, - DistributionMetadataStore: distributionMetadataStore, - EventsService: d.EventsService, - ImageStore: imageStore, - LayerStore: layerStore, - MaxConcurrentDownloads: config.MaxConcurrentDownloads, - MaxConcurrentUploads: config.MaxConcurrentUploads, - MaxDownloadAttempts: config.MaxDownloadAttempts, - ReferenceStore: rs, - RegistryService: registryService, - ContentNamespace: config.ContainerdNamespace, - } - - // This is a temporary environment variables used in CI to allow pushing - // manifest v2 schema 1 images to test-registries used for testing *pulling* - // these images. - if os.Getenv("DOCKER_ALLOW_SCHEMA1_PUSH_DONOTUSE") != "" { - imgSvcConfig.TrustKey, err = loadOrCreateTrustKey(config.TrustKeyPath) - if err != nil { - return nil, err - } - if err = system.MkdirAll(filepath.Join(config.Root, "trust"), 0700); err != nil { - return nil, err - } - } - - // containerd is not currently supported with Windows. - // So sometimes d.containerdCli will be nil - // In that case we'll create a local content store... but otherwise we'll use containerd - if d.containerdCli != nil { - imgSvcConfig.Leases = d.containerdCli.LeasesService() - imgSvcConfig.ContentStore = d.containerdCli.ContentStore() + if d.UsesSnapshotter() { + d.imageService = ctrd.NewService(d.containerdCli) } else { - cs, lm, err := d.configureLocalContentStore() + ifs, err := image.NewFSStoreBackend(filepath.Join(imageRoot, "imagedb")) if err != nil { return nil, err } - imgSvcConfig.ContentStore = cs - imgSvcConfig.Leases = lm - } - // TODO: imageStore, distributionMetadataStore, and ReferenceStore are only - // used above to run migration. They could be initialized in ImageService - // if migration is called from daemon/images. layerStore might move as well. - d.imageService = images.NewImageService(imgSvcConfig) - logrus.Debugf("Max Concurrent Downloads: %d", imgSvcConfig.MaxConcurrentDownloads) - logrus.Debugf("Max Concurrent Uploads: %d", imgSvcConfig.MaxConcurrentUploads) - logrus.Debugf("Max Download Attempts: %d", imgSvcConfig.MaxDownloadAttempts) + imageStore, err := image.NewImageStore(ifs, layerStore) + if err != nil { + return nil, err + } + + distributionMetadataStore, err := dmetadata.NewFSMetadataStore(filepath.Join(imageRoot, "distribution")) + if err != nil { + return nil, err + } + + imgSvcConfig := images.ImageServiceConfig{ + ContainerStore: d.containers, + DistributionMetadataStore: distributionMetadataStore, + EventsService: d.EventsService, + ImageStore: imageStore, + LayerStore: layerStore, + MaxConcurrentDownloads: config.MaxConcurrentDownloads, + MaxConcurrentUploads: config.MaxConcurrentUploads, + MaxDownloadAttempts: config.MaxDownloadAttempts, + ReferenceStore: rs, + RegistryService: registryService, + ContentNamespace: config.ContainerdNamespace, + } + + // This is a temporary environment variables used in CI to allow pushing + // manifest v2 schema 1 images to test-registries used for testing *pulling* + // these images. + if os.Getenv("DOCKER_ALLOW_SCHEMA1_PUSH_DONOTUSE") != "" { + imgSvcConfig.TrustKey, err = loadOrCreateTrustKey(config.TrustKeyPath) + if err != nil { + return nil, err + } + if err = system.MkdirAll(filepath.Join(config.Root, "trust"), 0700); err != nil { + return nil, err + } + } + + // containerd is not currently supported with Windows. + // So sometimes d.containerdCli will be nil + // In that case we'll create a local content store... but otherwise we'll use containerd + if d.containerdCli != nil { + imgSvcConfig.Leases = d.containerdCli.LeasesService() + imgSvcConfig.ContentStore = d.containerdCli.ContentStore() + } else { + cs, lm, err := d.configureLocalContentStore() + if err != nil { + return nil, err + } + imgSvcConfig.ContentStore = cs + imgSvcConfig.Leases = lm + } + + // TODO: imageStore, distributionMetadataStore, and ReferenceStore are only + // used above to run migration. They could be initialized in ImageService + // if migration is called from daemon/images. layerStore might move as well. + d.imageService = images.NewImageService(imgSvcConfig) + + logrus.Debugf("Max Concurrent Downloads: %d", imgSvcConfig.MaxConcurrentDownloads) + logrus.Debugf("Max Concurrent Uploads: %d", imgSvcConfig.MaxConcurrentUploads) + logrus.Debugf("Max Download Attempts: %d", imgSvcConfig.MaxDownloadAttempts) + } go d.execCommandGC() diff --git a/daemon/oci_linux.go b/daemon/oci_linux.go index 07caa78bc3..66ccdc5c6a 100644 --- a/daemon/oci_linux.go +++ b/daemon/oci_linux.go @@ -720,14 +720,14 @@ func sysctlExists(s string) bool { // WithCommonOptions sets common docker options func WithCommonOptions(daemon *Daemon, c *container.Container) coci.SpecOpts { return func(ctx context.Context, _ coci.Client, _ *containers.Container, s *coci.Spec) error { - if c.BaseFS == nil && !daemon.usesSnapshotter() { + if c.BaseFS == nil && !daemon.UsesSnapshotter() { return errors.New("populateCommonSpec: BaseFS of container " + c.ID + " is unexpectedly nil") } linkedEnv, err := daemon.setupLinkedContainers(c) if err != nil { return err } - if !daemon.usesSnapshotter() { + if !daemon.UsesSnapshotter() { s.Root = &specs.Root{ Path: c.BaseFS.Path(), Readonly: c.HostConfig.ReadonlyRootfs,