github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/daemon/create.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "strings" 8 "time" 9 10 "github.com/containerd/containerd/platforms" 11 "github.com/containerd/log" 12 "github.com/docker/docker/api/types/backend" 13 containertypes "github.com/docker/docker/api/types/container" 14 "github.com/docker/docker/api/types/events" 15 networktypes "github.com/docker/docker/api/types/network" 16 "github.com/docker/docker/container" 17 "github.com/docker/docker/daemon/config" 18 "github.com/docker/docker/daemon/images" 19 "github.com/docker/docker/errdefs" 20 "github.com/docker/docker/image" 21 "github.com/docker/docker/internal/multierror" 22 "github.com/docker/docker/pkg/idtools" 23 "github.com/docker/docker/runconfig" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "github.com/opencontainers/selinux/go-selinux" 26 archvariant "github.com/tonistiigi/go-archvariant" 27 ) 28 29 type createOpts struct { 30 params backend.ContainerCreateConfig 31 managed bool 32 ignoreImagesArgsEscaped bool 33 } 34 35 // CreateManagedContainer creates a container that is managed by a Service 36 func (daemon *Daemon) CreateManagedContainer(ctx context.Context, params backend.ContainerCreateConfig) (containertypes.CreateResponse, error) { 37 return daemon.containerCreate(ctx, daemon.config(), createOpts{ 38 params: params, 39 managed: true, 40 }) 41 } 42 43 // ContainerCreate creates a regular container 44 func (daemon *Daemon) ContainerCreate(ctx context.Context, params backend.ContainerCreateConfig) (containertypes.CreateResponse, error) { 45 return daemon.containerCreate(ctx, daemon.config(), createOpts{ 46 params: params, 47 }) 48 } 49 50 // ContainerCreateIgnoreImagesArgsEscaped creates a regular container. This is called from the builder RUN case 51 // and ensures that we do not take the images ArgsEscaped 52 func (daemon *Daemon) ContainerCreateIgnoreImagesArgsEscaped(ctx context.Context, params backend.ContainerCreateConfig) (containertypes.CreateResponse, error) { 53 return daemon.containerCreate(ctx, daemon.config(), createOpts{ 54 params: params, 55 ignoreImagesArgsEscaped: true, 56 }) 57 } 58 59 func (daemon *Daemon) containerCreate(ctx context.Context, daemonCfg *configStore, opts createOpts) (containertypes.CreateResponse, error) { 60 start := time.Now() 61 if opts.params.Config == nil { 62 return containertypes.CreateResponse{}, errdefs.InvalidParameter(runconfig.ErrEmptyConfig) 63 } 64 // TODO(thaJeztah): remove logentries check and migration code in release v26.0.0. 65 if opts.params.HostConfig != nil && opts.params.HostConfig.LogConfig.Type == "logentries" { 66 return containertypes.CreateResponse{}, errdefs.InvalidParameter(fmt.Errorf("the logentries logging driver has been deprecated and removed")) 67 } 68 69 // Normalize some defaults. Doing this "ad-hoc" here for now, as there's 70 // only one field to migrate, but we should consider having a better 71 // location for this (and decide where in the flow would be most appropriate). 72 // 73 // TODO(thaJeztah): we should have a more visible, more canonical location for this. 74 if opts.params.HostConfig != nil && opts.params.HostConfig.RestartPolicy.Name == "" { 75 // Set the default restart-policy ("none") if no restart-policy was set. 76 opts.params.HostConfig.RestartPolicy.Name = containertypes.RestartPolicyDisabled 77 } 78 79 warnings, err := daemon.verifyContainerSettings(daemonCfg, opts.params.HostConfig, opts.params.Config, false) 80 if err != nil { 81 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 82 } 83 84 if opts.params.Platform == nil && opts.params.Config.Image != "" { 85 img, err := daemon.imageService.GetImage(ctx, opts.params.Config.Image, backend.GetImageOpts{Platform: opts.params.Platform}) 86 if err != nil { 87 return containertypes.CreateResponse{}, err 88 } 89 if img != nil { 90 p := maximumSpec() 91 imgPlat := ocispec.Platform{ 92 OS: img.OS, 93 Architecture: img.Architecture, 94 Variant: img.Variant, 95 } 96 97 if !images.OnlyPlatformWithFallback(p).Match(imgPlat) { 98 warnings = append(warnings, fmt.Sprintf("The requested image's platform (%s) does not match the detected host platform (%s) and no specific platform was requested", platforms.Format(imgPlat), platforms.Format(p))) 99 } 100 } 101 } 102 103 err = daemon.validateNetworkingConfig(opts.params.NetworkingConfig) 104 if err != nil { 105 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 106 } 107 108 if opts.params.HostConfig == nil { 109 opts.params.HostConfig = &containertypes.HostConfig{} 110 } 111 err = daemon.adaptContainerSettings(&daemonCfg.Config, opts.params.HostConfig) 112 if err != nil { 113 return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err) 114 } 115 116 ctr, err := daemon.create(ctx, &daemonCfg.Config, opts) 117 if err != nil { 118 return containertypes.CreateResponse{Warnings: warnings}, err 119 } 120 containerActions.WithValues("create").UpdateSince(start) 121 122 if warnings == nil { 123 warnings = make([]string, 0) // Create an empty slice to avoid https://github.com/moby/moby/issues/38222 124 } 125 126 return containertypes.CreateResponse{ID: ctr.ID, Warnings: warnings}, nil 127 } 128 129 // Create creates a new container from the given configuration with a given name. 130 func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts createOpts) (retC *container.Container, retErr error) { 131 var ( 132 ctr *container.Container 133 img *image.Image 134 imgManifest *ocispec.Descriptor 135 imgID image.ID 136 err error 137 os = runtime.GOOS 138 ) 139 140 if opts.params.Config.Image != "" { 141 img, err = daemon.imageService.GetImage(ctx, opts.params.Config.Image, backend.GetImageOpts{Platform: opts.params.Platform}) 142 if err != nil { 143 return nil, err 144 } 145 // when using the containerd store, we need to get the actual 146 // image manifest so we can store it and later deterministically 147 // resolve the specific image the container is running 148 if daemon.UsesSnapshotter() { 149 imgManifest, err = daemon.imageService.GetImageManifest(ctx, opts.params.Config.Image, backend.GetImageOpts{Platform: opts.params.Platform}) 150 if err != nil { 151 log.G(ctx).WithError(err).Error("failed to find image manifest") 152 return nil, err 153 } 154 } 155 os = img.OperatingSystem() 156 imgID = img.ID() 157 } else if isWindows { 158 os = "linux" // 'scratch' case. 159 } 160 161 // On WCOW, if are not being invoked by the builder to create this container (where 162 // ignoreImagesArgEscaped will be true) - if the image already has its arguments escaped, 163 // ensure that this is replicated across to the created container to avoid double-escaping 164 // of the arguments/command line when the runtime attempts to run the container. 165 if os == "windows" && !opts.ignoreImagesArgsEscaped && img != nil && img.RunConfig().ArgsEscaped { 166 opts.params.Config.ArgsEscaped = true 167 } 168 169 if err := daemon.mergeAndVerifyConfig(opts.params.Config, img); err != nil { 170 return nil, errdefs.InvalidParameter(err) 171 } 172 173 if err := daemon.mergeAndVerifyLogConfig(&opts.params.HostConfig.LogConfig); err != nil { 174 return nil, errdefs.InvalidParameter(err) 175 } 176 177 if ctr, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil { 178 return nil, err 179 } 180 defer func() { 181 if retErr != nil { 182 err = daemon.cleanupContainer(ctr, backend.ContainerRmConfig{ 183 ForceRemove: true, 184 RemoveVolume: true, 185 }) 186 if err != nil { 187 log.G(ctx).WithError(err).Error("failed to cleanup container on create error") 188 } 189 } 190 }() 191 192 if err := daemon.setSecurityOptions(daemonCfg, ctr, opts.params.HostConfig); err != nil { 193 return nil, err 194 } 195 196 ctr.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt 197 ctr.ImageManifest = imgManifest 198 199 if daemon.UsesSnapshotter() { 200 if err := daemon.imageService.PrepareSnapshot(ctx, ctr.ID, opts.params.Config.Image, opts.params.Platform, setupInitLayer(daemon.idMapping)); err != nil { 201 return nil, err 202 } 203 } else { 204 // Set RWLayer for container after mount labels have been set 205 rwLayer, err := daemon.imageService.CreateLayer(ctr, setupInitLayer(daemon.idMapping)) 206 if err != nil { 207 return nil, errdefs.System(err) 208 } 209 ctr.RWLayer = rwLayer 210 } 211 212 current := idtools.CurrentIdentity() 213 if err := idtools.MkdirAndChown(ctr.Root, 0o710, idtools.Identity{UID: current.UID, GID: daemon.IdentityMapping().RootPair().GID}); err != nil { 214 return nil, err 215 } 216 if err := idtools.MkdirAndChown(ctr.CheckpointDir(), 0o700, current); err != nil { 217 return nil, err 218 } 219 220 if err := daemon.setHostConfig(ctr, opts.params.HostConfig, opts.params.DefaultReadOnlyNonRecursive); err != nil { 221 return nil, err 222 } 223 224 if err := daemon.createContainerOSSpecificSettings(ctx, ctr, opts.params.Config, opts.params.HostConfig); err != nil { 225 return nil, err 226 } 227 228 var endpointsConfigs map[string]*networktypes.EndpointSettings 229 if opts.params.NetworkingConfig != nil { 230 endpointsConfigs = opts.params.NetworkingConfig.EndpointsConfig 231 } 232 // Make sure NetworkMode has an acceptable value. We do this to ensure 233 // backwards API compatibility. 234 runconfig.SetDefaultNetModeIfBlank(ctr.HostConfig) 235 236 daemon.updateContainerNetworkSettings(ctr, endpointsConfigs) 237 if err := daemon.Register(ctr); err != nil { 238 return nil, err 239 } 240 stateCtr.set(ctr.ID, "stopped") 241 daemon.LogContainerEvent(ctr, events.ActionCreate) 242 return ctr, nil 243 } 244 245 func toHostConfigSelinuxLabels(labels []string) []string { 246 for i, l := range labels { 247 labels[i] = "label=" + l 248 } 249 return labels 250 } 251 252 func (daemon *Daemon) generateSecurityOpt(hostConfig *containertypes.HostConfig) ([]string, error) { 253 for _, opt := range hostConfig.SecurityOpt { 254 con := strings.Split(opt, "=") 255 if con[0] == "label" { 256 // Caller overrode SecurityOpts 257 return nil, nil 258 } 259 } 260 ipcMode := hostConfig.IpcMode 261 pidMode := hostConfig.PidMode 262 privileged := hostConfig.Privileged 263 if ipcMode.IsHost() || pidMode.IsHost() || privileged { 264 return toHostConfigSelinuxLabels(selinux.DisableSecOpt()), nil 265 } 266 267 var ipcLabel []string 268 var pidLabel []string 269 ipcContainer := ipcMode.Container() 270 pidContainer := pidMode.Container() 271 if ipcContainer != "" { 272 c, err := daemon.GetContainer(ipcContainer) 273 if err != nil { 274 return nil, err 275 } 276 ipcLabel, err = selinux.DupSecOpt(c.ProcessLabel) 277 if err != nil { 278 return nil, err 279 } 280 if pidContainer == "" { 281 return toHostConfigSelinuxLabels(ipcLabel), err 282 } 283 } 284 if pidContainer != "" { 285 c, err := daemon.GetContainer(pidContainer) 286 if err != nil { 287 return nil, err 288 } 289 290 pidLabel, err = selinux.DupSecOpt(c.ProcessLabel) 291 if err != nil { 292 return nil, err 293 } 294 if ipcContainer == "" { 295 return toHostConfigSelinuxLabels(pidLabel), err 296 } 297 } 298 299 if pidLabel != nil && ipcLabel != nil { 300 for i := 0; i < len(pidLabel); i++ { 301 if pidLabel[i] != ipcLabel[i] { 302 return nil, fmt.Errorf("--ipc and --pid containers SELinux labels aren't the same") 303 } 304 } 305 return toHostConfigSelinuxLabels(pidLabel), nil 306 } 307 return nil, nil 308 } 309 310 func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *image.Image) error { 311 if img != nil && img.Config != nil { 312 if err := merge(config, img.Config); err != nil { 313 return err 314 } 315 } 316 // Reset the Entrypoint if it is [""] 317 if len(config.Entrypoint) == 1 && config.Entrypoint[0] == "" { 318 config.Entrypoint = nil 319 } 320 if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { 321 return fmt.Errorf("no command specified") 322 } 323 return nil 324 } 325 326 // validateNetworkingConfig checks whether a container's NetworkingConfig is valid. 327 func (daemon *Daemon) validateNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error { 328 if nwConfig == nil { 329 return nil 330 } 331 332 var errs []error 333 for k, v := range nwConfig.EndpointsConfig { 334 if v == nil { 335 errs = append(errs, fmt.Errorf("invalid config for network %s: EndpointsConfig is nil", k)) 336 continue 337 } 338 339 // The referenced network k might not exist when the container is created, so just ignore the error in that case. 340 nw, _ := daemon.FindNetwork(k) 341 if err := validateEndpointSettings(nw, k, v); err != nil { 342 errs = append(errs, fmt.Errorf("invalid config for network %s: %w", k, err)) 343 } 344 } 345 346 if len(errs) > 0 { 347 return errdefs.InvalidParameter(multierror.Join(errs...)) 348 } 349 350 return nil 351 } 352 353 // maximumSpec returns the distribution platform with maximum compatibility for the current node. 354 func maximumSpec() ocispec.Platform { 355 p := platforms.DefaultSpec() 356 if p.Architecture == "amd64" { 357 p.Variant = archvariant.AMD64Variant() 358 } 359 return p 360 }