func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() if container.State.IsRunning() { return fmt.Errorf("The container %s is already running.", container.ID) } defer func() { if err != nil { container.cleanup() } }() if err := container.EnsureMounted(); err != nil { return err } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true container.buildHostnameAndHostsFiles("127.0.1.1") } else { if err := container.allocateNetwork(); err != nil { return err } container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) } // Make sure the config is compatible with the current kernel if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit { log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } if container.runtime.capabilities.IPv4ForwardingDisabled { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) } // Apply volumes from another container if requested if err := container.applyExternalVolumes(); err != nil { return err } if err := container.createVolumes(); err != nil { return err } if err := container.generateLXCConfig(); err != nil { return err } var lxcStart string = "lxc-start" if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") } params := []string{ lxcStart, "-n", container.ID, "-f", container.lxcConfigPath(), "--", "/.dockerinit", } // Networking if !container.Config.NetworkDisabled { network := container.NetworkSettings params = append(params, "-g", network.Gateway, "-i", fmt.Sprintf("%s/%d", network.IPAddress, network.IPPrefixLen), "-mtu", strconv.Itoa(container.runtime.config.Mtu), ) } // User if container.Config.User != "" { params = append(params, "-u", container.Config.User) } // Setup environment env := []string{ "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOSTNAME=" + container.Config.Hostname, } if container.Config.Tty { env = append(env, "TERM=xterm") } if container.hostConfig.Privileged { params = append(params, "-privileged") } // Init any links between the parent and children runtime := container.runtime children, err := runtime.Children(container.Name) if err != nil { return err } if len(children) > 0 { container.activeLinks = make(map[string]*Link, len(children)) // If we encounter an error make sure that we rollback any network // config and ip table changes rollback := func() { for _, link := range container.activeLinks { link.Disable() } container.activeLinks = nil } for p, child := range children { link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) if err != nil { rollback() return err } container.activeLinks[link.Alias()] = link if err := link.Enable(); err != nil { rollback() return err } for _, envVar := range link.ToEnv() { env = append(env, envVar) } } } for _, elem := range container.Config.Env { env = append(env, elem) } if err := container.generateEnvConfig(env); err != nil { return err } if container.Config.WorkingDir != "" { workingDir := path.Clean(container.Config.WorkingDir) utils.Debugf("[working dir] working dir is %s", workingDir) if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { return nil } params = append(params, "-w", workingDir, ) } // Program params = append(params, "--", container.Path) params = append(params, container.Args...) if RootIsShared() { // lxc-start really needs / to be non-shared, or all kinds of stuff break // when lxc-start unmount things and those unmounts propagate to the main // mount namespace. // What we really want is to clone into a new namespace and then // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rslave /; exec " + utils.ShellQuoteArguments(params) params = []string{ "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } root := container.RootfsPath() envPath, err := container.EnvConfigPath() if err != nil { return err } // Mount docker specific files into the containers root fs if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(envPath, path.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.ResolvConfPath, path.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil { return err } if container.HostnamePath != "" && container.HostsPath != "" { if err := mount.Mount(container.HostnamePath, path.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.HostsPath, path.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil { return err } } // Mount user specified volumes for r, v := range container.Volumes { mountAs := "ro" if container.VolumesRW[v] { mountAs = "rw" } if err := mount.Mount(v, path.Join(root, r), "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { return err } } container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} if container.Config.Tty { err = container.startPty() } else { err = container.start() } if err != nil { return err } // FIXME: save state on disk *first*, then converge // this way disk state is used as a journal, eg. we can restore after crash etc. container.State.SetRunning(container.cmd.Process.Pid) // Init the lock container.waitLock = make(chan struct{}) container.ToDisk() go container.monitor() defer utils.Debugf("Container running: %v", container.State.IsRunning()) // We wait for the container to be fully running. // Timeout after 5 seconds. In case of broken pipe, just retry. // Note: The container can run and finish correctly before // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { // If the container dies while waiting for it, just return if !container.State.IsRunning() { return nil } output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput() if err != nil { utils.Debugf("Error with lxc-info: %s (%s)", err, output) output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput() if err != nil { utils.Debugf("Second Error with lxc-info: %s (%s)", err, output) return err } } if strings.Contains(string(output), "RUNNING") { return nil } utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output)) time.Sleep(50 * time.Millisecond) } if container.State.IsRunning() { return ErrContainerStartTimeout } return ErrContainerStart }
func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() if container.State.IsRunning() { return fmt.Errorf("The container %s is already running.", container.ID) } defer func() { if err != nil { container.cleanup() } }() if err := container.EnsureMounted(); err != nil { return err } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true container.buildHostnameAndHostsFiles("127.0.1.1") } else { if err := container.allocateNetwork(); err != nil { return err } container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) } // Make sure the config is compatible with the current kernel if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } if container.runtime.sysInfo.IPv4ForwardingDisabled { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) } // Apply volumes from another container if requested if err := container.applyExternalVolumes(); err != nil { return err } if err := container.createVolumes(); err != nil { return err } // Setup environment env := []string{ "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOSTNAME=" + container.Config.Hostname, } if container.Config.Tty { env = append(env, "TERM=xterm") } // Init any links between the parent and children runtime := container.runtime children, err := runtime.Children(container.Name) if err != nil { return err } if len(children) > 0 { container.activeLinks = make(map[string]*Link, len(children)) // If we encounter an error make sure that we rollback any network // config and ip table changes rollback := func() { for _, link := range container.activeLinks { link.Disable() } container.activeLinks = nil } for p, child := range children { link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) if err != nil { rollback() return err } container.activeLinks[link.Alias()] = link if err := link.Enable(); err != nil { rollback() return err } for _, envVar := range link.ToEnv() { env = append(env, envVar) } } } for _, elem := range container.Config.Env { env = append(env, elem) } if err := container.generateEnvConfig(env); err != nil { return err } var workingDir string if container.Config.WorkingDir != "" { workingDir = path.Clean(container.Config.WorkingDir) if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { return nil } } root := container.RootfsPath() envPath, err := container.EnvConfigPath() if err != nil { return err } // Mount docker specific files into the containers root fs if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(envPath, path.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.ResolvConfPath, path.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil { return err } if container.HostnamePath != "" && container.HostsPath != "" { if err := mount.Mount(container.HostnamePath, path.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.HostsPath, path.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil { return err } } // Mount user specified volumes for r, v := range container.Volumes { mountAs := "ro" if container.VolumesRW[r] { mountAs = "rw" } if err := mount.Mount(v, path.Join(root, r), "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { return err } } var ( en *execdriver.Network driverConfig []string ) if !container.Config.NetworkDisabled { network := container.NetworkSettings en = &execdriver.Network{ Gateway: network.Gateway, Bridge: network.Bridge, IPAddress: network.IPAddress, IPPrefixLen: network.IPPrefixLen, Mtu: container.runtime.config.Mtu, } } if lxcConf := container.hostConfig.LxcConf; lxcConf != nil { for _, pair := range lxcConf { driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) } } cgroupValues := &cgroups.Values{ Memory: container.Config.Memory, MemorySwap: container.Config.MemorySwap, CpuShares: container.Config.CpuShares, } container.process = &execdriver.Process{ ID: container.ID, Privileged: container.hostConfig.Privileged, Rootfs: root, InitPath: "/.dockerinit", Entrypoint: container.Path, Arguments: container.Args, WorkingDir: workingDir, Network: en, Tty: container.Config.Tty, User: container.Config.User, Config: driverConfig, Cgroups: cgroupValues, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } container.waitLock = make(chan struct{}) // Setuping pipes and/or Pty var setup func() error if container.Config.Tty { setup = container.setupPty } else { setup = container.setupStd } if err := setup(); err != nil { return err } callbackLock := make(chan struct{}) callback := func(process *execdriver.Process) { container.State.SetRunning(process.Pid()) if process.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. if c, ok := process.Stdout.(io.Closer); ok { c.Close() } } if err := container.ToDisk(); err != nil { utils.Debugf("%s", err) } close(callbackLock) } // We use a callback here instead of a goroutine and an chan for // syncronization purposes cErr := utils.Go(func() error { return container.monitor(callback) }) // Start should not return until the process is actually running select { case <-callbackLock: case err := <-cErr: return err } return nil }