func (svc *IService) create() (*docker.Container, error) { var config dockerclient.Config cd := &docker.ContainerDefinition{ dockerclient.CreateContainerOptions{Name: svc.name(), Config: &config}, dockerclient.HostConfig{}, } config.Image = commons.JoinRepoTag(svc.Repo, svc.Tag) config.Cmd = []string{"/bin/sh", "-c", svc.Command()} // NOTE: USE WITH CARE! // Enabling host networking for an isvc may expose ports // of the isvcs to access outside of the serviced host, potentially // compromising security. if svc.HostNetwork { cd.NetworkMode = "host" glog.Warningf("Host networking enabled for isvc %s", svc.Name) } // attach all exported ports if svc.PortBindings != nil && len(svc.PortBindings) > 0 { config.ExposedPorts = make(map[dockerclient.Port]struct{}) cd.PortBindings = make(map[dockerclient.Port][]dockerclient.PortBinding) for _, binding := range svc.PortBindings { port := dockerclient.Port(fmt.Sprintf("%d", binding.HostPort)) config.ExposedPorts[port] = struct{}{} portBinding := dockerclient.PortBinding{ HostIp: getHostIp(binding), HostPort: port.Port(), } cd.PortBindings[port] = append(cd.PortBindings[port], portBinding) } } glog.V(1).Infof("Bindings for %s = %v", svc.Name, cd.PortBindings) // copy any links to other isvcs if svc.Links != nil && len(svc.Links) > 0 { // To use a link, the source container must be instantiated already, so // the service using a link can't be in the first start group. // // FIXME: Other sanity checks we could add - make sure that the source // container is not in the same group or a later group if svc.StartGroup == 0 { glog.Fatalf("isvc %s can not use docker Links with StartGroup=0", svc.Name) } cd.Links = make([]string, len(svc.Links)) copy(cd.Links, svc.Links) glog.V(1).Infof("Links for %s = %v", svc.Name, cd.Links) } // attach all exported volumes config.Volumes = make(map[string]struct{}) cd.Binds = []string{} // service-specific volumes if svc.Volumes != nil && len(svc.Volumes) > 0 { for src, dest := range svc.Volumes { hostpath := svc.getResourcePath(src) if exists, _ := isDir(hostpath); !exists { if err := os.MkdirAll(hostpath, 0777); err != nil { glog.Errorf("could not create %s on host: %s", hostpath, err) return nil, err } } cd.Binds = append(cd.Binds, fmt.Sprintf("%s:%s", hostpath, dest)) config.Volumes[dest] = struct{}{} } } // global volumes if isvcsVolumes != nil && len(isvcsVolumes) > 0 { for src, dest := range isvcsVolumes { if exists, _ := isDir(src); !exists { glog.Warningf("Could not mount source %s: path does not exist", src) continue } cd.Binds = append(cd.Binds, fmt.Sprintf("%s:%s", src, dest)) config.Volumes[dest] = struct{}{} } } // attach environment variables for key, val := range envPerService[svc.Name] { config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val)) } return docker.NewContainer(cd, false, 5*time.Second, nil, nil) }
// configureContainer creates and populates two structures, a docker client Config and a docker client HostConfig structure // that are used to create and start a container respectively. The information used to populate the structures is pulled from // the service, serviceState, and conn values that are passed into configureContainer. func configureContainer(a *HostAgent, client *ControlClient, svc *service.Service, serviceState *servicestate.ServiceState, virtualAddressSubnet string) (*dockerclient.Config, *dockerclient.HostConfig, error) { cfg := &dockerclient.Config{} hcfg := &dockerclient.HostConfig{} //get this service's tenantId for volume mapping var tenantID string err := client.GetTenantId(svc.ID, &tenantID) if err != nil { glog.Errorf("Failed getting tenantID for service: %s, %s", svc.ID, err) return nil, nil, err } // get the system user unused := 0 systemUser := user.User{} err = client.GetSystemUser(unused, &systemUser) if err != nil { glog.Errorf("Unable to get system user account for agent %s", err) return nil, nil, err } glog.V(1).Infof("System User %v", systemUser) cfg.Image = svc.ImageID // get the endpoints cfg.ExposedPorts = make(map[dockerclient.Port]struct{}) hcfg.PortBindings = make(map[dockerclient.Port][]dockerclient.PortBinding) if svc.Endpoints != nil { glog.V(1).Info("Endpoints for service: ", svc.Endpoints) for _, endpoint := range svc.Endpoints { if endpoint.Purpose == "export" { // only expose remote endpoints var port uint16 port = endpoint.PortNumber if endpoint.PortTemplate != "" { t := template.Must(template.New("PortTemplate").Funcs(funcmap).Parse(endpoint.PortTemplate)) b := bytes.Buffer{} err := t.Execute(&b, serviceState) if err == nil { j, err := strconv.Atoi(b.String()) if err != nil { glog.Errorf("%+v", err) } else if j > 0 { port = uint16(j) } } } var p string switch endpoint.Protocol { case commons.UDP: p = fmt.Sprintf("%d/%s", port, "udp") default: p = fmt.Sprintf("%d/%s", port, "tcp") } cfg.ExposedPorts[dockerclient.Port(p)] = struct{}{} hcfg.PortBindings[dockerclient.Port(p)] = append(hcfg.PortBindings[dockerclient.Port(p)], dockerclient.PortBinding{}) } } } if len(tenantID) == 0 && len(svc.Volumes) > 0 { // FIXME: find a better way of handling this error condition glog.Fatalf("Could not get tenant ID and need to mount a volume, service state: %s, service id: %s", serviceState.ID, svc.ID) } // Make sure the image exists locally. if _, err = docker.FindImage(svc.ImageID, true); err != nil { glog.Errorf("can't find docker image %s: %s", svc.ImageID, err) return nil, nil, err } cfg.Volumes = make(map[string]struct{}) hcfg.Binds = []string{} if err := injectContext(svc, serviceState, client); err != nil { glog.Errorf("Error injecting context: %s", err) return nil, nil, err } for _, volume := range svc.Volumes { if volume.Type != "" && volume.Type != "dfs" { continue } resourcePath, err := a.setupVolume(tenantID, svc, volume) if err != nil { return nil, nil, err } binding := fmt.Sprintf("%s:%s", resourcePath, volume.ContainerPath) cfg.Volumes[strings.Split(binding, ":")[1]] = struct{}{} hcfg.Binds = append(hcfg.Binds, strings.TrimSpace(binding)) } dir, binary, err := ExecPath() if err != nil { glog.Errorf("Error getting exec path: %v", err) return nil, nil, err } volumeBinding := fmt.Sprintf("%s:/serviced", dir) cfg.Volumes[strings.Split(volumeBinding, ":")[1]] = struct{}{} hcfg.Binds = append(hcfg.Binds, strings.TrimSpace(volumeBinding)) // bind mount everything we need for logstash-forwarder if len(svc.LogConfigs) != 0 { const LOGSTASH_CONTAINER_DIRECTORY = "/usr/local/serviced/resources/logstash" logstashPath := utils.ResourcesDir() + "/logstash" binding := fmt.Sprintf("%s:%s", logstashPath, LOGSTASH_CONTAINER_DIRECTORY) cfg.Volumes[LOGSTASH_CONTAINER_DIRECTORY] = struct{}{} hcfg.Binds = append(hcfg.Binds, binding) glog.V(1).Infof("added logstash bind mount: %s", binding) } // specify temporary volume paths for docker to create tmpVolumes := []string{"/tmp"} for _, volume := range svc.Volumes { if volume.Type == "tmp" { tmpVolumes = append(tmpVolumes, volume.ContainerPath) } } for _, path := range tmpVolumes { cfg.Volumes[path] = struct{}{} glog.V(4).Infof("added temporary docker container path: %s", path) } // add arguments to mount requested directory (if requested) glog.V(2).Infof("Checking Mount options for service %#v", svc) for _, bindMountString := range a.mount { glog.V(2).Infof("bindmount is %#v", bindMountString) splitMount := strings.Split(bindMountString, ",") numMountArgs := len(splitMount) if numMountArgs == 2 || numMountArgs == 3 { requestedImage := splitMount[0] glog.V(2).Infof("mount requestedImage %#v", requestedImage) hostPath := splitMount[1] glog.V(2).Infof("mount hostPath %#v", hostPath) // assume the container path is going to be the same as the host path containerPath := hostPath // if the container path is provided, use it if numMountArgs > 2 { containerPath = splitMount[2] } glog.V(2).Infof("mount containerPath %#v", containerPath) // insert tenantId into requestedImage - see facade.DeployService matchedRequestedImage := false if requestedImage == "*" { matchedRequestedImage = true } else { imageID, err := commons.ParseImageID(requestedImage) if err != nil { glog.Errorf("error parsing imageid %v: %v", requestedImage, err) continue } svcImageID, err := commons.ParseImageID(svc.ImageID) if err != nil { glog.Errorf("error parsing service imageid %v; %v", svc.ImageID, err) continue } glog.V(2).Infof("mount checking %#v and %#v ", imageID, svcImageID) matchedRequestedImage = (imageID.Repo == svcImageID.Repo) } if matchedRequestedImage { binding := fmt.Sprintf("%s:%s", hostPath, containerPath) cfg.Volumes[strings.Split(binding, ":")[1]] = struct{}{} hcfg.Binds = append(hcfg.Binds, strings.TrimSpace(binding)) } } else { glog.Warningf("Could not bind mount the following: %s", bindMountString) } } // Get host IP ips, err := utils.GetIPv4Addresses() if err != nil { glog.Errorf("Error getting host IP addresses: %v", err) return nil, nil, err } // add arguments for environment variables cfg.Env = append([]string{}, fmt.Sprintf("CONTROLPLANE_SYSTEM_USER=%s", systemUser.Name), fmt.Sprintf("CONTROLPLANE_SYSTEM_PASSWORD=%s", systemUser.Password), fmt.Sprintf("CONTROLPLANE_HOST_IPS='%s'", strings.Join(ips, " ")), fmt.Sprintf("SERVICED_VIRTUAL_ADDRESS_SUBNET=%s", virtualAddressSubnet), fmt.Sprintf("SERVICED_IS_SERVICE_SHELL=false"), fmt.Sprintf("SERVICED_NOREGISTRY=%s", os.Getenv("SERVICED_NOREGISTRY")), fmt.Sprintf("SERVICED_SERVICE_IMAGE=%s", svc.ImageID), fmt.Sprintf("SERVICED_MAX_RPC_CLIENTS=1"), fmt.Sprintf("SERVICED_RPC_PORT=%s", a.rpcport), fmt.Sprintf("TZ=%s", os.Getenv("TZ"))) // add dns values to setup for _, addr := range a.dockerDNS { _addr := strings.TrimSpace(addr) if len(_addr) > 0 { cfg.Dns = append(cfg.Dns, addr) } } // Add hostname if set if svc.Hostname != "" { cfg.Hostname = svc.Hostname } cfg.Cmd = append([]string{}, fmt.Sprintf("/serviced/%s", binary), "service", "proxy", svc.ID, strconv.Itoa(serviceState.InstanceID), svc.Startup) if svc.Privileged { hcfg.Privileged = true } // Memory and CpuShares should never be negative if svc.MemoryLimit < 0 { cfg.Memory = 0 } else { cfg.Memory = svc.MemoryLimit } if svc.CPUShares < 0 { cfg.CpuShares = 0 } else { cfg.CpuShares = svc.CPUShares } return cfg, hcfg, nil }