func getImageTags(templateRepos []string, serviceRepos []string, tags []string) (map[string][]string, error) { imagemap := make(map[string][]string) // find all the template repos for _, repo := range templateRepos { image, err := docker.FindImage(repo, false) if err == docker.ErrNoSuchImage { glog.Warningf("Could not find template image %s", repo) continue } else if err != nil { glog.Errorf("Could not look up repo %s: %s", repo, err) return nil, err } if image.ID.Tag == DockerLatest { image.ID.Tag = "" } images := imagemap[image.UUID] imagemap[image.UUID] = append(images, image.ID.String()) } // find all the service repos for _, repo := range serviceRepos { image, err := docker.FindImage(repo, false) if err != nil { glog.Errorf("Could not look up repo %s: %s", repo, err) return nil, err } if image.ID.Tag == DockerLatest { image.ID.Tag = "" } images := imagemap[image.UUID] imagemap[image.UUID] = append(images, image.ID.String()) for _, tag := range tags { image, err := docker.FindImage(commons.JoinRepoTag(repo, tag), false) if err == docker.ErrNoSuchImage { continue } else if err != nil { glog.Errorf("Could not look up repo %s: %s", commons.JoinRepoTag(repo, tag), err) return nil, err } images := imagemap[image.UUID] imagemap[image.UUID] = append(images, image.ID.String()) } } return imagemap, nil }
func loadImage(filename string, uuid string, tags []string) error { // look up the image by UUID images, err := docker.Images() if err != nil { glog.Errorf("Could not look up images: %s", err) return err } var image *docker.Image for _, i := range images { if i.UUID == uuid { image = i break } } // image not found so import if image == nil { glog.Warningf("Importing image from file, don't forget to sync (serviced docker sync)") if err := docker.ImportImage(tags[0], filename); err != nil { glog.Errorf("Could not import image from file %s: %s", filename, err) return err } else if image, err = docker.FindImage(tags[0], false); err != nil { glog.Errorf("Could not look up docker image %s: %s", tags[0], err) return err } glog.Infof("Tagging images %v at %s", tags, image.UUID) tags = tags[1:] } // tag the remaining images for _, tag := range tags { if _, err := image.Tag(tag); err != nil { glog.Errorf("Could not tag image %s as %s: %s", image.UUID, tag, err) return err } } return nil }
func (f *Facade) deployServiceDefinitions(ctx datastore.Context, sds []servicedefinition.ServiceDefinition, pool string, parentServiceID string, volumes map[string]string, deploymentId string, tenantId *string) error { // ensure that all images in the templates exist imageIds := make(map[string]struct{}) for _, svc := range sds { getSubServiceImageIDs(imageIds, svc) } for imageId, _ := range imageIds { _, err := docker.FindImage(imageId, false) if err != nil { msg := fmt.Errorf("could not look up image %s: %s. Check your docker login and retry service deployment.", imageId, err) glog.Error(err.Error()) return msg } } for _, sd := range sds { if _, err := f.deployServiceDefinition(ctx, sd, pool, parentServiceID, volumes, deploymentId, tenantId); err != nil { return err } } return 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 }
func StartDocker(cfg *ProcessConfig, port string) (*exec.Cmd, error) { var svc service.Service // Create a control center client to look up the service cp, err := node.NewControlClient(port) if err != nil { glog.Errorf("could not create a control center client %v", err) return nil, err } glog.Infof("Connected to the control center at port %s", port) if err := cp.GetService(cfg.ServiceID, &svc); err != nil { glog.Errorf("unable to find service %s", cfg.ServiceID) return nil, err } // make sure docker image is present if _, err = docker.FindImage(svc.ImageID, false); err != nil { if err == docker.ErrNoSuchImage { if err := docker.PullImage(svc.ImageID); err != nil { glog.Errorf("unable to pull image %s: %s", svc.ImageID, err) return nil, err } } else { glog.Errorf("unable to inspect image %s: %s", svc.ImageID, err) return nil, err } } // bind mount on /serviced dir, bin, err := node.ExecPath() if err != nil { glog.Errorf("serviced not found: %s", err) return nil, err } servicedVolume := fmt.Sprintf("%s:/serviced", dir) // bind mount the pwd dir, err = os.Getwd() pwdVolume := fmt.Sprintf("%s:/mnt/pwd", dir) // get the shell command shellcmd := cfg.Command if cfg.Command == "" { shellcmd = "su -" } // get the serviced command svcdcmd := fmt.Sprintf("/serviced/%s", bin) // get the proxy command proxycmd := []string{ svcdcmd, fmt.Sprintf("--logtostderr=%t", cfg.LogToStderr), "service", "proxy", "--autorestart=false", "--disable-metric-forwarding", fmt.Sprintf("--logstash=%t", cfg.LogStash.Enable), fmt.Sprintf("--logstash-idle-flush-time=%s", cfg.LogStash.IdleFlushTime), fmt.Sprintf("--logstash-settle-time=%s", cfg.LogStash.SettleTime), svc.ID, "0", shellcmd, } // get the docker start command docker, err := exec.LookPath("docker") if err != nil { glog.Errorf("Docker not found: %v", err) return nil, err } argv := []string{"run", "-v", servicedVolume, "-v", pwdVolume, "-v", utils.ResourcesDir() + ":" + "/usr/local/serviced/resources"} for _, mount := range cfg.Mount { hostPath, containerPath, err := parseMountArg(mount) if err != nil { return nil, err } argv = append(argv, "-v", fmt.Sprintf("%s:%s", hostPath, containerPath)) } argv = append(argv, cfg.Envv...) if cfg.SaveAs != "" { argv = append(argv, fmt.Sprintf("--name=%s", cfg.SaveAs)) } else { argv = append(argv, "--rm") } if cfg.IsTTY { argv = append(argv, "-i", "-t") } // set the systemuser and password unused := 0 systemUser := user.User{} err = cp.GetSystemUser(unused, &systemUser) if err != nil { glog.Errorf("Unable to get system user account for client %s", err) } argv = append(argv, "-e", fmt.Sprintf("CONTROLPLANE_SYSTEM_USER=%s ", systemUser.Name)) argv = append(argv, "-e", fmt.Sprintf("CONTROLPLANE_SYSTEM_PASSWORD=%s ", systemUser.Password)) argv = append(argv, "-e", fmt.Sprintf("SERVICED_NOREGISTRY=%s", os.Getenv("SERVICED_NOREGISTRY"))) argv = append(argv, "-e", fmt.Sprintf("SERVICED_IS_SERVICE_SHELL=true")) argv = append(argv, "-e", fmt.Sprintf("SERVICED_SERVICE_IMAGE=%s", svc.ImageID)) argv = append(argv, svc.ImageID) argv = append(argv, proxycmd...) // wait for the DFS to be ready in order to start container on the latest image glog.Infof("Acquiring image from the dfs...") cp.ReadyDFS(false, nil) glog.Infof("Acquired! Starting shell") glog.V(1).Infof("command: docker %+v", argv) return exec.Command(docker, argv...), nil }
func (f *Facade) deployServiceDefinition(ctx datastore.Context, sd servicedefinition.ServiceDefinition, pool string, parentServiceID string, volumes map[string]string, deploymentId string, tenantId *string) (string, error) { // Always deploy in stopped state, starting is a separate step ds := int(service.SVCStop) exportedVolumes := make(map[string]string) for k, v := range volumes { exportedVolumes[k] = v } svc, err := service.BuildService(sd, parentServiceID, pool, ds, deploymentId) if err != nil { return "", err } UpdateDeployTemplateStatus(deploymentId, "deploy_loading_service|"+svc.Name) getSvc := func(svcID string) (service.Service, error) { svc, err := f.GetService(ctx, svcID) if err != nil { return service.Service{}, err } return *svc, err } findChild := func(svcID, childName string) (service.Service, error) { svc, err := f.FindChildService(ctx, svcID, childName) if err != nil { return service.Service{}, err } return *svc, err } //for each endpoint, evaluate its Application if err = svc.EvaluateEndpointTemplates(getSvc, findChild); err != nil { return "", err } //for each endpoint, evaluate its Application if err = svc.EvaluateEndpointTemplates(getSvc, findChild); err != nil { return "", err } if parentServiceID == "" { *tenantId = svc.ID } // Using the tenant id, tag the base image with the tenantID if svc.ImageID != "" { UpdateDeployTemplateStatus(deploymentId, "deploy_renaming_image|"+svc.Name) name, err := renameImageID(f.dockerRegistry, svc.ImageID, *tenantId) if err != nil { glog.Errorf("malformed imageId: %s", svc.ImageID) return "", err } _, err = docker.FindImage(name, false) if err != nil { if err != docker.ErrNoSuchImage && !strings.HasPrefix(err.Error(), "No such id:") { glog.Error(err) return "", err } UpdateDeployTemplateStatus(deploymentId, "deploy_loading_image|"+name) image, err := docker.FindImage(svc.ImageID, false) if err != nil { msg := fmt.Errorf("could not look up image %s: %s. Check your docker login and retry application deployment.", svc.ImageID, err) glog.Error(err.Error()) return "", msg } UpdateDeployTemplateStatus(deploymentId, "deploy_tagging_image|"+name) if _, err := image.Tag(name); err != nil { glog.Errorf("could not tag image: %s (%v)", image.ID, err) return "", err } } svc.ImageID = name } err = f.AddService(ctx, *svc) if err != nil { return "", err } return svc.ID, f.deployServiceDefinitions(ctx, sd.Services, pool, svc.ID, exportedVolumes, deploymentId, tenantId) }
// Commit will merge a container into existing services' image func (dfs *DistributedFilesystem) Commit(dockerID string) (string, error) { // get the container ctr, err := docker.FindContainer(dockerID) if err != nil { glog.Errorf("Could not get container %s: %s", dockerID, err) return "", err } // verify the container is not currently running if ctr.IsRunning() { err := fmt.Errorf("cannot commit a running container") glog.Errorf("Error committing container %s: %s", ctr.ID, err) return "", err } // find the image to commit (ctr.Config.Image is the repotag) image, err := docker.FindImage(ctr.Config.Image, false) if err != nil { glog.Errorf("Could not find image %s from %s: %s", ctr.Config.Image, dockerID, err) return "", err } // verify the container is not stale (ctr.Image is the UUID) if !image.ID.IsLatest() || image.UUID != ctr.Image { err := fmt.Errorf("cannot commit a stale container") glog.Errorf("Could not commit %s (%s): %s", dockerID, image.ID, err) return "", err } // verify the tenantID tenantID, err := dfs.facade.GetTenantID(datastore.Get(), image.ID.User) if err != nil { glog.Errorf("Could not look up tenant %s from image %s for container %s: %s", image.ID.User, image.ID, dockerID, err) return "", err } else if tenantID != image.ID.User { err := fmt.Errorf("service is not the tenant") glog.Errorf("Could not commit %s (%s): %s", dockerID, image.ID, err) return "", err } // check the number of image layers if layers, err := image.History(); err != nil { glog.Errorf("Could not check history for image %s: %s", image.ID, err) return "", err } else if numLayers := len(layers); numLayers >= layer.WARN_LAYER_COUNT { glog.Warningf("Image %s has %d layers and is approaching the maximum (%d). Please squash image layers.", image.ID, numLayers, layer.MAX_LAYER_COUNT) } else { glog.V(3).Infof("Image %s has %d layers", image.ID, numLayers) } // commit the container to the image and tag newImage, err := ctr.Commit(image.ID.BaseName()) if err != nil { glog.Errorf("Could not commit %s (%s): %s", dockerID, image.ID, err) return "", err } // desynchronize any running containers if err := dfs.desynchronize(newImage); err != nil { glog.Warningf("Could not denote all desynchronized services: %s", err) } // snapshot the filesystem and images snapshotID, err := dfs.Snapshot(tenantID) if err != nil { glog.Errorf("Could not create a snapshot of the new image %s: %s", tenantID, err) return "", err } return snapshotID, nil }