github.com/kobeld/docker@v1.12.0-rc1/daemon/cluster/executor/container/container.go (about) 1 package container 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "net" 8 "strings" 9 "time" 10 11 clustertypes "github.com/docker/docker/daemon/cluster/provider" 12 "github.com/docker/docker/reference" 13 "github.com/docker/engine-api/types" 14 enginecontainer "github.com/docker/engine-api/types/container" 15 "github.com/docker/engine-api/types/network" 16 "github.com/docker/swarmkit/agent/exec" 17 "github.com/docker/swarmkit/api" 18 ) 19 20 const ( 21 // Explictly use the kernel's default setting for CPU quota of 100ms. 22 // https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt 23 cpuQuotaPeriod = 100 * time.Millisecond 24 25 // systemLabelPrefix represents the reserved namespace for system labels. 26 systemLabelPrefix = "com.docker.swarm" 27 ) 28 29 // containerConfig converts task properties into docker container compatible 30 // components. 31 type containerConfig struct { 32 task *api.Task 33 networksAttachments map[string]*api.NetworkAttachment 34 } 35 36 // newContainerConfig returns a validated container config. No methods should 37 // return an error if this function returns without error. 38 func newContainerConfig(t *api.Task) (*containerConfig, error) { 39 var c containerConfig 40 return &c, c.setTask(t) 41 } 42 43 func (c *containerConfig) setTask(t *api.Task) error { 44 container := t.Spec.GetContainer() 45 if container == nil { 46 return exec.ErrRuntimeUnsupported 47 } 48 49 if container.Image == "" { 50 return ErrImageRequired 51 } 52 53 // index the networks by name 54 c.networksAttachments = make(map[string]*api.NetworkAttachment, len(t.Networks)) 55 for _, attachment := range t.Networks { 56 c.networksAttachments[attachment.Network.Spec.Annotations.Name] = attachment 57 } 58 59 c.task = t 60 return nil 61 } 62 63 func (c *containerConfig) endpoint() *api.Endpoint { 64 return c.task.Endpoint 65 } 66 67 func (c *containerConfig) spec() *api.ContainerSpec { 68 return c.task.Spec.GetContainer() 69 } 70 71 func (c *containerConfig) name() string { 72 if c.task.Annotations.Name != "" { 73 // if set, use the container Annotations.Name field, set in the orchestrator. 74 return c.task.Annotations.Name 75 } 76 77 // fallback to service.slot.id. 78 return strings.Join([]string{c.task.ServiceAnnotations.Name, fmt.Sprint(c.task.Slot), c.task.ID}, ".") 79 } 80 81 func (c *containerConfig) image() string { 82 raw := c.spec().Image 83 ref, err := reference.ParseNamed(raw) 84 if err != nil { 85 return raw 86 } 87 return reference.WithDefaultTag(ref).String() 88 } 89 90 func (c *containerConfig) volumes() map[string]struct{} { 91 r := make(map[string]struct{}) 92 93 for _, mount := range c.spec().Mounts { 94 // pick off all the volume mounts. 95 if mount.Type != api.MountTypeVolume { 96 continue 97 } 98 99 r[fmt.Sprintf("%s:%s", mount.Target, getMountMask(&mount))] = struct{}{} 100 } 101 102 return r 103 } 104 105 func (c *containerConfig) config() *enginecontainer.Config { 106 config := &enginecontainer.Config{ 107 Labels: c.labels(), 108 User: c.spec().User, 109 Env: c.spec().Env, 110 WorkingDir: c.spec().Dir, 111 Image: c.image(), 112 Volumes: c.volumes(), 113 } 114 115 if len(c.spec().Command) > 0 { 116 // If Command is provided, we replace the whole invocation with Command 117 // by replacing Entrypoint and specifying Cmd. Args is ignored in this 118 // case. 119 config.Entrypoint = append(config.Entrypoint, c.spec().Command[0]) 120 config.Cmd = append(config.Cmd, c.spec().Command[1:]...) 121 } else if len(c.spec().Args) > 0 { 122 // In this case, we assume the image has an Entrypoint and Args 123 // specifies the arguments for that entrypoint. 124 config.Cmd = c.spec().Args 125 } 126 127 return config 128 } 129 130 func (c *containerConfig) labels() map[string]string { 131 var ( 132 system = map[string]string{ 133 "task": "", // mark as cluster task 134 "task.id": c.task.ID, 135 "task.name": fmt.Sprintf("%v.%v", c.task.ServiceAnnotations.Name, c.task.Slot), 136 "node.id": c.task.NodeID, 137 "service.id": c.task.ServiceID, 138 "service.name": c.task.ServiceAnnotations.Name, 139 } 140 labels = make(map[string]string) 141 ) 142 143 // base labels are those defined in the spec. 144 for k, v := range c.spec().Labels { 145 labels[k] = v 146 } 147 148 // we then apply the overrides from the task, which may be set via the 149 // orchestrator. 150 for k, v := range c.task.Annotations.Labels { 151 labels[k] = v 152 } 153 154 // finally, we apply the system labels, which override all labels. 155 for k, v := range system { 156 labels[strings.Join([]string{systemLabelPrefix, k}, ".")] = v 157 } 158 159 return labels 160 } 161 162 func (c *containerConfig) bindMounts() []string { 163 var r []string 164 165 for _, val := range c.spec().Mounts { 166 mask := getMountMask(&val) 167 if val.Type == api.MountTypeBind { 168 r = append(r, fmt.Sprintf("%s:%s:%s", val.Source, val.Target, mask)) 169 } 170 } 171 172 return r 173 } 174 175 func getMountMask(m *api.Mount) string { 176 maskOpts := []string{"ro"} 177 if m.Writable { 178 maskOpts[0] = "rw" 179 } 180 181 if m.BindOptions != nil { 182 switch m.BindOptions.Propagation { 183 case api.MountPropagationPrivate: 184 maskOpts = append(maskOpts, "private") 185 case api.MountPropagationRPrivate: 186 maskOpts = append(maskOpts, "rprivate") 187 case api.MountPropagationShared: 188 maskOpts = append(maskOpts, "shared") 189 case api.MountPropagationRShared: 190 maskOpts = append(maskOpts, "rshared") 191 case api.MountPropagationSlave: 192 maskOpts = append(maskOpts, "slave") 193 case api.MountPropagationRSlave: 194 maskOpts = append(maskOpts, "rslave") 195 } 196 } 197 198 if m.VolumeOptions != nil { 199 if !m.VolumeOptions.Populate { 200 maskOpts = append(maskOpts, "nocopy") 201 } 202 } 203 return strings.Join(maskOpts, ",") 204 } 205 206 func (c *containerConfig) hostConfig() *enginecontainer.HostConfig { 207 return &enginecontainer.HostConfig{ 208 Resources: c.resources(), 209 Binds: c.bindMounts(), 210 } 211 } 212 213 // This handles the case of volumes that are defined inside a service Mount 214 func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *types.VolumeCreateRequest { 215 var ( 216 driverName string 217 driverOpts map[string]string 218 labels map[string]string 219 ) 220 221 if mount.VolumeOptions != nil && mount.VolumeOptions.DriverConfig != nil { 222 driverName = mount.VolumeOptions.DriverConfig.Name 223 driverOpts = mount.VolumeOptions.DriverConfig.Options 224 labels = mount.VolumeOptions.Labels 225 } 226 227 if mount.VolumeOptions != nil { 228 return &types.VolumeCreateRequest{ 229 Name: mount.Source, 230 Driver: driverName, 231 DriverOpts: driverOpts, 232 Labels: labels, 233 } 234 } 235 return nil 236 } 237 238 func (c *containerConfig) resources() enginecontainer.Resources { 239 resources := enginecontainer.Resources{} 240 241 // If no limits are specified let the engine use its defaults. 242 // 243 // TODO(aluzzardi): We might want to set some limits anyway otherwise 244 // "unlimited" tasks will step over the reservation of other tasks. 245 r := c.task.Spec.Resources 246 if r == nil || r.Limits == nil { 247 return resources 248 } 249 250 if r.Limits.MemoryBytes > 0 { 251 resources.Memory = r.Limits.MemoryBytes 252 } 253 254 if r.Limits.NanoCPUs > 0 { 255 // CPU Period must be set in microseconds. 256 resources.CPUPeriod = int64(cpuQuotaPeriod / time.Microsecond) 257 resources.CPUQuota = r.Limits.NanoCPUs * resources.CPUPeriod / 1e9 258 } 259 260 return resources 261 } 262 263 // Docker daemon supports just 1 network during container create. 264 func (c *containerConfig) createNetworkingConfig() *network.NetworkingConfig { 265 var networks []*api.NetworkAttachment 266 if c.task.Spec.GetContainer() != nil { 267 networks = c.task.Networks 268 } 269 270 epConfig := make(map[string]*network.EndpointSettings) 271 if len(networks) > 0 { 272 epConfig[networks[0].Network.Spec.Annotations.Name] = getEndpointConfig(networks[0]) 273 } 274 275 return &network.NetworkingConfig{EndpointsConfig: epConfig} 276 } 277 278 // TODO: Merge this function with createNetworkingConfig after daemon supports multiple networks in container create 279 func (c *containerConfig) connectNetworkingConfig() *network.NetworkingConfig { 280 var networks []*api.NetworkAttachment 281 if c.task.Spec.GetContainer() != nil { 282 networks = c.task.Networks 283 } 284 285 // First network is used during container create. Other networks are used in "docker network connect" 286 if len(networks) < 2 { 287 return nil 288 } 289 290 epConfig := make(map[string]*network.EndpointSettings) 291 for _, na := range networks[1:] { 292 epConfig[na.Network.Spec.Annotations.Name] = getEndpointConfig(na) 293 } 294 return &network.NetworkingConfig{EndpointsConfig: epConfig} 295 } 296 297 func getEndpointConfig(na *api.NetworkAttachment) *network.EndpointSettings { 298 var ipv4, ipv6 string 299 for _, addr := range na.Addresses { 300 ip, _, err := net.ParseCIDR(addr) 301 if err != nil { 302 continue 303 } 304 305 if ip.To4() != nil { 306 ipv4 = ip.String() 307 continue 308 } 309 310 if ip.To16() != nil { 311 ipv6 = ip.String() 312 } 313 } 314 315 return &network.EndpointSettings{ 316 IPAMConfig: &network.EndpointIPAMConfig{ 317 IPv4Address: ipv4, 318 IPv6Address: ipv6, 319 }, 320 } 321 } 322 323 func (c *containerConfig) virtualIP(networkID string) string { 324 if c.task.Endpoint == nil { 325 return "" 326 } 327 328 for _, eVip := range c.task.Endpoint.VirtualIPs { 329 // We only support IPv4 VIPs for now. 330 if eVip.NetworkID == networkID { 331 vip, _, err := net.ParseCIDR(eVip.Addr) 332 if err != nil { 333 return "" 334 } 335 336 return vip.String() 337 } 338 } 339 340 return "" 341 } 342 343 func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig { 344 if len(c.task.Networks) == 0 { 345 return nil 346 } 347 348 log.Printf("Creating service config in agent for t = %+v", c.task) 349 svcCfg := &clustertypes.ServiceConfig{ 350 Name: c.task.ServiceAnnotations.Name, 351 ID: c.task.ServiceID, 352 VirtualAddresses: make(map[string]*clustertypes.VirtualAddress), 353 } 354 355 for _, na := range c.task.Networks { 356 svcCfg.VirtualAddresses[na.Network.ID] = &clustertypes.VirtualAddress{ 357 // We support only IPv4 virtual IP for now. 358 IPv4: c.virtualIP(na.Network.ID), 359 } 360 } 361 362 if c.task.Endpoint != nil { 363 for _, ePort := range c.task.Endpoint.Ports { 364 svcCfg.ExposedPorts = append(svcCfg.ExposedPorts, &clustertypes.PortConfig{ 365 Name: ePort.Name, 366 Protocol: int32(ePort.Protocol), 367 TargetPort: ePort.TargetPort, 368 PublishedPort: ePort.PublishedPort, 369 }) 370 } 371 } 372 373 return svcCfg 374 } 375 376 // networks returns a list of network names attached to the container. The 377 // returned name can be used to lookup the corresponding network create 378 // options. 379 func (c *containerConfig) networks() []string { 380 var networks []string 381 382 for name := range c.networksAttachments { 383 networks = append(networks, name) 384 } 385 386 return networks 387 } 388 389 func (c *containerConfig) networkCreateRequest(name string) (clustertypes.NetworkCreateRequest, error) { 390 na, ok := c.networksAttachments[name] 391 if !ok { 392 return clustertypes.NetworkCreateRequest{}, errors.New("container: unknown network referenced") 393 } 394 395 options := types.NetworkCreate{ 396 // ID: na.Network.ID, 397 Driver: na.Network.DriverState.Name, 398 IPAM: network.IPAM{ 399 Driver: na.Network.IPAM.Driver.Name, 400 }, 401 Options: na.Network.DriverState.Options, 402 CheckDuplicate: true, 403 } 404 405 for _, ic := range na.Network.IPAM.Configs { 406 c := network.IPAMConfig{ 407 Subnet: ic.Subnet, 408 IPRange: ic.Range, 409 Gateway: ic.Gateway, 410 } 411 options.IPAM.Config = append(options.IPAM.Config, c) 412 } 413 414 return clustertypes.NetworkCreateRequest{na.Network.ID, types.NetworkCreateRequest{Name: name, NetworkCreate: options}}, nil 415 }