github.com/moby/docker@v26.1.3+incompatible/daemon/cluster/executor/container/executor.go (about) 1 package container // import "github.com/docker/docker/daemon/cluster/executor/container" 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "strings" 8 "sync" 9 10 "github.com/containerd/log" 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/filters" 13 "github.com/docker/docker/api/types/network" 14 swarmtypes "github.com/docker/docker/api/types/swarm" 15 "github.com/docker/docker/daemon/cluster/controllers/plugin" 16 "github.com/docker/docker/daemon/cluster/convert" 17 executorpkg "github.com/docker/docker/daemon/cluster/executor" 18 clustertypes "github.com/docker/docker/daemon/cluster/provider" 19 "github.com/docker/docker/libnetwork" 20 networktypes "github.com/docker/docker/libnetwork/types" 21 "github.com/moby/swarmkit/v2/agent" 22 "github.com/moby/swarmkit/v2/agent/exec" 23 "github.com/moby/swarmkit/v2/api" 24 "github.com/moby/swarmkit/v2/api/naming" 25 swarmlog "github.com/moby/swarmkit/v2/log" 26 "github.com/moby/swarmkit/v2/template" 27 "github.com/pkg/errors" 28 ) 29 30 type executor struct { 31 backend executorpkg.Backend 32 imageBackend executorpkg.ImageBackend 33 pluginBackend plugin.Backend 34 volumeBackend executorpkg.VolumeBackend 35 dependencies exec.DependencyManager 36 mutex sync.Mutex // This mutex protects the following node field 37 node *api.NodeDescription 38 39 // nodeObj holds a copy of the swarmkit Node object from the time of the 40 // last call to executor.Configure. This allows us to discover which 41 // network attachments the node previously had, which further allows us to 42 // determine which, if any, need to be removed. nodeObj is not protected by 43 // a mutex, because it is only written to in the method (Configure) that it 44 // is read from. If that changes, it may need to be guarded. 45 nodeObj *api.Node 46 } 47 48 // NewExecutor returns an executor from the docker client. 49 func NewExecutor(b executorpkg.Backend, p plugin.Backend, i executorpkg.ImageBackend, v executorpkg.VolumeBackend) exec.Executor { 50 return &executor{ 51 backend: b, 52 pluginBackend: p, 53 imageBackend: i, 54 volumeBackend: v, 55 dependencies: agent.NewDependencyManager(convert.SwarmPluginGetter(b.PluginGetter())), 56 } 57 } 58 59 // Describe returns the underlying node description from the docker client. 60 func (e *executor) Describe(ctx context.Context) (*api.NodeDescription, error) { 61 info, err := e.backend.SystemInfo(ctx) 62 if err != nil { 63 return nil, err 64 } 65 66 plugins := map[api.PluginDescription]struct{}{} 67 addPlugins := func(typ string, names []string) { 68 for _, name := range names { 69 plugins[api.PluginDescription{ 70 Type: typ, 71 Name: name, 72 }] = struct{}{} 73 } 74 } 75 76 // add v1 plugins 77 addPlugins("Volume", info.Plugins.Volume) 78 // Add builtin driver "overlay" (the only builtin multi-host driver) to 79 // the plugin list by default. 80 addPlugins("Network", append([]string{"overlay"}, info.Plugins.Network...)) 81 addPlugins("Authorization", info.Plugins.Authorization) 82 addPlugins("Log", info.Plugins.Log) 83 84 // add v2 plugins 85 v2Plugins, err := e.backend.PluginManager().List(filters.NewArgs()) 86 if err == nil { 87 for _, plgn := range v2Plugins { 88 for _, typ := range plgn.Config.Interface.Types { 89 if typ.Prefix != "docker" || !plgn.Enabled { 90 continue 91 } 92 plgnTyp := typ.Capability 93 switch typ.Capability { 94 case "volumedriver": 95 plgnTyp = "Volume" 96 case "networkdriver": 97 plgnTyp = "Network" 98 case "logdriver": 99 plgnTyp = "Log" 100 } 101 102 plugins[api.PluginDescription{ 103 Type: plgnTyp, 104 Name: plgn.Name, 105 }] = struct{}{} 106 } 107 } 108 } 109 110 pluginFields := make([]api.PluginDescription, 0, len(plugins)) 111 for k := range plugins { 112 pluginFields = append(pluginFields, k) 113 } 114 115 sort.Sort(sortedPlugins(pluginFields)) 116 117 // parse []string labels into a map[string]string 118 labels := map[string]string{} 119 for _, l := range info.Labels { 120 k, v, ok := strings.Cut(l, "=") 121 // this will take the last value in the list for a given key 122 // ideally, one shouldn't assign multiple values to the same key 123 if ok { 124 labels[k] = v 125 } 126 } 127 128 // TODO(dperny): don't ignore the error here 129 csiInfo, _ := e.Volumes().Plugins().NodeInfo(ctx) 130 131 description := &api.NodeDescription{ 132 Hostname: info.Name, 133 Platform: &api.Platform{ 134 Architecture: info.Architecture, 135 OS: info.OSType, 136 }, 137 Engine: &api.EngineDescription{ 138 EngineVersion: info.ServerVersion, 139 Labels: labels, 140 Plugins: pluginFields, 141 }, 142 Resources: &api.Resources{ 143 NanoCPUs: int64(info.NCPU) * 1e9, 144 MemoryBytes: info.MemTotal, 145 Generic: convert.GenericResourcesToGRPC(info.GenericResources), 146 }, 147 CSIInfo: csiInfo, 148 } 149 150 // Save the node information in the executor field 151 e.mutex.Lock() 152 e.node = description 153 e.mutex.Unlock() 154 155 return description, nil 156 } 157 158 func (e *executor) Configure(ctx context.Context, node *api.Node) error { 159 var ingressNA *api.NetworkAttachment 160 attachments := make(map[string]string) 161 162 for _, na := range node.Attachments { 163 if na == nil || na.Network == nil || len(na.Addresses) == 0 { 164 // this should not happen, but we got a panic here and don't have a 165 // good idea about what the underlying data structure looks like. 166 swarmlog.G(ctx).WithField("NetworkAttachment", fmt.Sprintf("%#v", na)).Warn("skipping nil or malformed node network attachment entry") 167 continue 168 } 169 170 if na.Network.Spec.Ingress { 171 ingressNA = na 172 } 173 174 attachments[na.Network.ID] = na.Addresses[0] 175 } 176 177 // discover which, if any, attachments have been removed. 178 // 179 // we aren't responsible directly for creating these networks. that is 180 // handled indirectly when a container using that network is created. 181 // however, when it comes time to remove the network, none of the relevant 182 // tasks may exist anymore. this means we should go ahead and try to remove 183 // any network we know to no longer be in use. 184 185 // removeAttachments maps the network ID to a boolean. This boolean 186 // indicates whether the attachment in question is totally removed (true), 187 // or has just had its IP changed (false) 188 removeAttachments := make(map[string]bool) 189 190 // the first time we Configure, nodeObj wil be nil, because it will not be 191 // set yet. in that case, skip this check. 192 if e.nodeObj != nil { 193 for _, na := range e.nodeObj.Attachments { 194 // same thing as above, check sanity of the attachments so we don't 195 // get a panic. 196 if na == nil || na.Network == nil || len(na.Addresses) == 0 { 197 swarmlog.G(ctx).WithField("NetworkAttachment", fmt.Sprintf("%#v", na)).Warn("skipping nil or malformed node network attachment entry") 198 continue 199 } 200 201 // now, check if the attachment exists and shares the same IP address. 202 if ip, ok := attachments[na.Network.ID]; !ok || na.Addresses[0] != ip { 203 // if the map entry exists, then the network still exists, and the 204 // IP must be what has changed 205 removeAttachments[na.Network.ID] = !ok 206 } 207 } 208 } 209 210 if (ingressNA == nil) && (node.Attachment != nil) && (len(node.Attachment.Addresses) > 0) { 211 ingressNA = node.Attachment 212 attachments[ingressNA.Network.ID] = ingressNA.Addresses[0] 213 } 214 215 if ingressNA == nil { 216 e.backend.ReleaseIngress() 217 return e.backend.GetAttachmentStore().ResetAttachments(attachments) 218 } 219 220 options := types.NetworkCreate{ 221 Driver: ingressNA.Network.DriverState.Name, 222 IPAM: &network.IPAM{ 223 Driver: ingressNA.Network.IPAM.Driver.Name, 224 }, 225 Options: ingressNA.Network.DriverState.Options, 226 Ingress: true, 227 } 228 229 for _, ic := range ingressNA.Network.IPAM.Configs { 230 c := network.IPAMConfig{ 231 Subnet: ic.Subnet, 232 IPRange: ic.Range, 233 Gateway: ic.Gateway, 234 } 235 options.IPAM.Config = append(options.IPAM.Config, c) 236 } 237 238 _, err := e.backend.SetupIngress(clustertypes.NetworkCreateRequest{ 239 ID: ingressNA.Network.ID, 240 NetworkCreateRequest: types.NetworkCreateRequest{ 241 Name: ingressNA.Network.Spec.Annotations.Name, 242 NetworkCreate: options, 243 }, 244 }, ingressNA.Addresses[0]) 245 if err != nil { 246 return err 247 } 248 249 var ( 250 activeEndpointsError *libnetwork.ActiveEndpointsError 251 errNoSuchNetwork libnetwork.ErrNoSuchNetwork 252 ) 253 254 // now, finally, remove any network LB attachments that we no longer have. 255 for nw, gone := range removeAttachments { 256 err := e.backend.DeleteManagedNetwork(nw) 257 switch { 258 case err == nil: 259 continue 260 case errors.As(err, &activeEndpointsError): 261 // this is the purpose of the boolean in the map. it's literally 262 // just to log an appropriate, informative error. i'm unsure if 263 // this can ever actually occur, but we need to know if it does. 264 if gone { 265 swarmlog.G(ctx).Warnf("network %s should be removed, but still has active attachments", nw) 266 } else { 267 swarmlog.G(ctx).Warnf("network %s should have its node LB IP changed, but cannot be removed because of active attachments", nw) 268 } 269 continue 270 case errors.As(err, &errNoSuchNetwork): 271 // NoSuchNetworkError indicates the network is already gone. 272 continue 273 default: 274 swarmlog.G(ctx).Errorf("network %s remove failed: %v", nw, err) 275 } 276 } 277 278 // now update our copy of the node object, reset the attachment store, and 279 // return 280 e.nodeObj = node 281 282 return e.backend.GetAttachmentStore().ResetAttachments(attachments) 283 } 284 285 // Controller returns a docker container runner. 286 func (e *executor) Controller(t *api.Task) (exec.Controller, error) { 287 dependencyGetter := template.NewTemplatedDependencyGetter(agent.Restrict(e.dependencies, t), t, nil) 288 289 // Get the node description from the executor field 290 e.mutex.Lock() 291 nodeDescription := e.node 292 e.mutex.Unlock() 293 294 if t.Spec.GetAttachment() != nil { 295 return newNetworkAttacherController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter) 296 } 297 298 var ctlr exec.Controller 299 switch r := t.Spec.GetRuntime().(type) { 300 case *api.TaskSpec_Generic: 301 swarmlog.G(context.TODO()).WithFields(log.Fields{ 302 "kind": r.Generic.Kind, 303 "type_url": r.Generic.Payload.TypeUrl, 304 }).Debug("custom runtime requested") 305 runtimeKind, err := naming.Runtime(t.Spec) 306 if err != nil { 307 return ctlr, err 308 } 309 switch runtimeKind { 310 case string(swarmtypes.RuntimePlugin): 311 if !e.backend.HasExperimental() { 312 return ctlr, fmt.Errorf("runtime type %q only supported in experimental", swarmtypes.RuntimePlugin) 313 } 314 c, err := plugin.NewController(e.pluginBackend, t) 315 if err != nil { 316 return ctlr, err 317 } 318 ctlr = c 319 default: 320 return ctlr, fmt.Errorf("unsupported runtime type: %q", runtimeKind) 321 } 322 case *api.TaskSpec_Container: 323 c, err := newController(e.backend, e.imageBackend, e.volumeBackend, t, nodeDescription, dependencyGetter) 324 if err != nil { 325 return ctlr, err 326 } 327 ctlr = c 328 default: 329 return ctlr, fmt.Errorf("unsupported runtime: %q", r) 330 } 331 332 return ctlr, nil 333 } 334 335 func (e *executor) SetNetworkBootstrapKeys(keys []*api.EncryptionKey) error { 336 nwKeys := []*networktypes.EncryptionKey{} 337 for _, key := range keys { 338 nwKey := &networktypes.EncryptionKey{ 339 Subsystem: key.Subsystem, 340 Algorithm: int32(key.Algorithm), 341 Key: make([]byte, len(key.Key)), 342 LamportTime: key.LamportTime, 343 } 344 copy(nwKey.Key, key.Key) 345 nwKeys = append(nwKeys, nwKey) 346 } 347 e.backend.SetNetworkBootstrapKeys(nwKeys) 348 349 return nil 350 } 351 352 func (e *executor) Secrets() exec.SecretsManager { 353 return e.dependencies.Secrets() 354 } 355 356 func (e *executor) Configs() exec.ConfigsManager { 357 return e.dependencies.Configs() 358 } 359 360 func (e *executor) Volumes() exec.VolumesManager { 361 return e.dependencies.Volumes() 362 } 363 364 type sortedPlugins []api.PluginDescription 365 366 func (sp sortedPlugins) Len() int { return len(sp) } 367 368 func (sp sortedPlugins) Swap(i, j int) { sp[i], sp[j] = sp[j], sp[i] } 369 370 func (sp sortedPlugins) Less(i, j int) bool { 371 if sp[i].Type != sp[j].Type { 372 return sp[i].Type < sp[j].Type 373 } 374 return sp[i].Name < sp[j].Name 375 }