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