Create the containerd image service

Initial pull/ls works
Build is deactivated if the feature is active

Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Djordje Lukic 2022-07-05 16:33:39 +02:00 committed by Sebastiaan van Stijn
parent 11b03710ed
commit 7d74269c0d
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
4 changed files with 471 additions and 95 deletions

View File

@ -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() {

View File

@ -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")
}

View File

@ -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()

View File

@ -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,