1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/daemon/container.go
2015-03-23 16:58:29 -07:00

1511 lines
42 KiB
Go

package daemon
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/devices"
"github.com/docker/libcontainer/label"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/docker/docker/daemon/logger/syslog"
"github.com/docker/docker/engine"
"github.com/docker/docker/image"
"github.com/docker/docker/links"
"github.com/docker/docker/nat"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/broadcastwriter"
"github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/directory"
"github.com/docker/docker/pkg/etchosts"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/resolvconf"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/ulimit"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
var (
ErrNotATTY = errors.New("The PTY is not a file")
ErrNoTTY = errors.New("No PTY found")
ErrContainerStart = errors.New("The container failed to start. Unknown error")
ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.")
)
type StreamConfig struct {
stdout *broadcastwriter.BroadcastWriter
stderr *broadcastwriter.BroadcastWriter
stdin io.ReadCloser
stdinPipe io.WriteCloser
}
type Container struct {
*State `json:"State"` // Needed for remote api version <= 1.11
root string // Path to the "home" of the container, including metadata.
basefs string // Path to the graphdriver mountpoint
ID string
Created time.Time
Path string
Args []string
Config *runconfig.Config
ImageID string `json:"Image"`
NetworkSettings *NetworkSettings
ResolvConfPath string
HostnamePath string
HostsPath string
LogPath string
Name string
Driver string
ExecDriver string
command *execdriver.Command
StreamConfig
daemon *Daemon
MountLabel, ProcessLabel string
AppArmorProfile string
RestartCount int
UpdateDns bool
// Maps container paths to volume paths. The key in this is the path to which
// the volume is being mounted inside the container. Value is the path of the
// volume on disk
Volumes map[string]string
// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
// Easier than migrating older container configs :)
VolumesRW map[string]bool
hostConfig *runconfig.HostConfig
activeLinks map[string]*links.Link
monitor *containerMonitor
execCommands *execStore
// logDriver for closing
logDriver logger.Logger
logCopier *logger.Copier
AppliedVolumesFrom map[string]struct{}
}
func (container *Container) FromDisk() error {
pth, err := container.jsonPath()
if err != nil {
return err
}
jsonSource, err := os.Open(pth)
if err != nil {
return err
}
defer jsonSource.Close()
dec := json.NewDecoder(jsonSource)
// Load container settings
// udp broke compat of docker.PortMapping, but it's not used when loading a container, we can skip it
if err := dec.Decode(container); err != nil && !strings.Contains(err.Error(), "docker.PortMapping") {
return err
}
if err := label.ReserveLabel(container.ProcessLabel); err != nil {
return err
}
return container.readHostConfig()
}
func (container *Container) toDisk() error {
data, err := json.Marshal(container)
if err != nil {
return err
}
pth, err := container.jsonPath()
if err != nil {
return err
}
err = ioutil.WriteFile(pth, data, 0666)
if err != nil {
return err
}
return container.WriteHostConfig()
}
func (container *Container) ToDisk() error {
container.Lock()
err := container.toDisk()
container.Unlock()
return err
}
func (container *Container) readHostConfig() error {
container.hostConfig = &runconfig.HostConfig{}
// If the hostconfig file does not exist, do not read it.
// (We still have to initialize container.hostConfig,
// but that's OK, since we just did that above.)
pth, err := container.hostConfigPath()
if err != nil {
return err
}
_, err = os.Stat(pth)
if os.IsNotExist(err) {
return nil
}
data, err := ioutil.ReadFile(pth)
if err != nil {
return err
}
return json.Unmarshal(data, container.hostConfig)
}
func (container *Container) WriteHostConfig() error {
data, err := json.Marshal(container.hostConfig)
if err != nil {
return err
}
pth, err := container.hostConfigPath()
if err != nil {
return err
}
return ioutil.WriteFile(pth, data, 0666)
}
func (container *Container) LogEvent(action string) {
d := container.daemon
if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.ImageID)).Run(); err != nil {
log.Errorf("Error logging event %s for %s: %s", action, container.ID, err)
}
}
func (container *Container) getResourcePath(path string) (string, error) {
cleanPath := filepath.Join("/", path)
return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs)
}
func (container *Container) getRootResourcePath(path string) (string, error) {
cleanPath := filepath.Join("/", path)
return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root)
}
func populateCommand(c *Container, env []string) error {
en := &execdriver.Network{
Mtu: c.daemon.config.Mtu,
Interface: nil,
}
parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "none":
case "host":
en.HostNetworking = true
case "bridge", "": // empty string to support existing containers
if !c.Config.NetworkDisabled {
network := c.NetworkSettings
en.Interface = &execdriver.NetworkInterface{
Gateway: network.Gateway,
Bridge: network.Bridge,
IPAddress: network.IPAddress,
IPPrefixLen: network.IPPrefixLen,
MacAddress: network.MacAddress,
LinkLocalIPv6Address: network.LinkLocalIPv6Address,
GlobalIPv6Address: network.GlobalIPv6Address,
GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
IPv6Gateway: network.IPv6Gateway,
}
}
case "container":
nc, err := c.getNetworkedContainer()
if err != nil {
return err
}
en.ContainerID = nc.ID
default:
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
}
ipc := &execdriver.Ipc{}
if c.hostConfig.IpcMode.IsContainer() {
ic, err := c.getIpcContainer()
if err != nil {
return err
}
ipc.ContainerID = ic.ID
} else {
ipc.HostIpc = c.hostConfig.IpcMode.IsHost()
}
pid := &execdriver.Pid{}
pid.HostPid = c.hostConfig.PidMode.IsHost()
// Build lists of devices allowed and created within the container.
userSpecifiedDevices := make([]*configs.Device, len(c.hostConfig.Devices))
for i, deviceMapping := range c.hostConfig.Devices {
device, err := devices.DeviceFromPath(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
if err != nil {
return fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
}
device.Path = deviceMapping.PathInContainer
userSpecifiedDevices[i] = device
}
allowedDevices := append(configs.DefaultAllowedDevices, userSpecifiedDevices...)
autoCreatedDevices := append(configs.DefaultAutoCreatedDevices, userSpecifiedDevices...)
// TODO: this can be removed after lxc-conf is fully deprecated
lxcConfig, err := mergeLxcConfIntoOptions(c.hostConfig)
if err != nil {
return err
}
var rlimits []*ulimit.Rlimit
ulimits := c.hostConfig.Ulimits
// Merge ulimits with daemon defaults
ulIdx := make(map[string]*ulimit.Ulimit)
for _, ul := range ulimits {
ulIdx[ul.Name] = ul
}
for name, ul := range c.daemon.config.Ulimits {
if _, exists := ulIdx[name]; !exists {
ulimits = append(ulimits, ul)
}
}
for _, limit := range ulimits {
rl, err := limit.GetRlimit()
if err != nil {
return err
}
rlimits = append(rlimits, rl)
}
resources := &execdriver.Resources{
Memory: c.hostConfig.Memory,
MemorySwap: c.hostConfig.MemorySwap,
CpuShares: c.hostConfig.CpuShares,
CpusetCpus: c.hostConfig.CpusetCpus,
Rlimits: rlimits,
}
processConfig := execdriver.ProcessConfig{
Privileged: c.hostConfig.Privileged,
Entrypoint: c.Path,
Arguments: c.Args,
Tty: c.Config.Tty,
User: c.Config.User,
}
processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
processConfig.Env = env
c.command = &execdriver.Command{
ID: c.ID,
Rootfs: c.RootfsPath(),
ReadonlyRootfs: c.hostConfig.ReadonlyRootfs,
InitPath: "/.dockerinit",
WorkingDir: c.Config.WorkingDir,
Network: en,
Ipc: ipc,
Pid: pid,
Resources: resources,
AllowedDevices: allowedDevices,
AutoCreatedDevices: autoCreatedDevices,
CapAdd: c.hostConfig.CapAdd,
CapDrop: c.hostConfig.CapDrop,
ProcessConfig: processConfig,
ProcessLabel: c.GetProcessLabel(),
MountLabel: c.GetMountLabel(),
LxcConfig: lxcConfig,
AppArmorProfile: c.AppArmorProfile,
CgroupParent: c.hostConfig.CgroupParent,
}
return nil
}
func (container *Container) Start() (err error) {
container.Lock()
defer container.Unlock()
if container.Running {
return nil
}
// if we encounter an error during start we need to ensure that any other
// setup has been cleaned up properly
defer func() {
if err != nil {
container.setError(err)
// if no one else has set it, make sure we don't leave it at zero
if container.ExitCode == 0 {
container.ExitCode = 128
}
container.toDisk()
container.cleanup()
}
}()
if err := container.setupContainerDns(); err != nil {
return err
}
if err := container.Mount(); err != nil {
return err
}
if err := container.initializeNetworking(); err != nil {
return err
}
if err := container.updateParentsHosts(); err != nil {
return err
}
container.verifyDaemonSettings()
if err := container.prepareVolumes(); err != nil {
return err
}
linkedEnv, err := container.setupLinkedContainers()
if err != nil {
return err
}
if err := container.setupWorkingDirectory(); err != nil {
return err
}
env := container.createDaemonEnvironment(linkedEnv)
if err := populateCommand(container, env); err != nil {
return err
}
if err := container.setupMounts(); err != nil {
return err
}
return container.waitForStart()
}
func (container *Container) Run() error {
if err := container.Start(); err != nil {
return err
}
container.WaitStop(-1 * time.Second)
return nil
}
func (container *Container) Output() (output []byte, err error) {
pipe := container.StdoutPipe()
defer pipe.Close()
if err := container.Start(); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
container.WaitStop(-1 * time.Second)
return output, err
}
// StreamConfig.StdinPipe returns a WriteCloser which can be used to feed data
// to the standard input of the container's active process.
// Container.StdoutPipe and Container.StderrPipe each return a ReadCloser
// which can be used to retrieve the standard output (and error) generated
// by the container's active process. The output (and error) are actually
// copied and delivered to all StdoutPipe and StderrPipe consumers, using
// a kind of "broadcaster".
func (streamConfig *StreamConfig) StdinPipe() io.WriteCloser {
return streamConfig.stdinPipe
}
func (streamConfig *StreamConfig) StdoutPipe() io.ReadCloser {
reader, writer := io.Pipe()
streamConfig.stdout.AddWriter(writer, "")
return ioutils.NewBufReader(reader)
}
func (streamConfig *StreamConfig) StderrPipe() io.ReadCloser {
reader, writer := io.Pipe()
streamConfig.stderr.AddWriter(writer, "")
return ioutils.NewBufReader(reader)
}
func (streamConfig *StreamConfig) StdoutLogPipe() io.ReadCloser {
reader, writer := io.Pipe()
streamConfig.stdout.AddWriter(writer, "stdout")
return ioutils.NewBufReader(reader)
}
func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser {
reader, writer := io.Pipe()
streamConfig.stderr.AddWriter(writer, "stderr")
return ioutils.NewBufReader(reader)
}
func (container *Container) buildHostnameFile() error {
hostnamePath, err := container.getRootResourcePath("hostname")
if err != nil {
return err
}
container.HostnamePath = hostnamePath
if container.Config.Domainname != "" {
return ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644)
}
return ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
}
func (container *Container) buildHostsFiles(IP string) error {
hostsPath, err := container.getRootResourcePath("hosts")
if err != nil {
return err
}
container.HostsPath = hostsPath
var extraContent []etchosts.Record
children, err := container.daemon.Children(container.Name)
if err != nil {
return err
}
for linkAlias, child := range children {
_, alias := path.Split(linkAlias)
// allow access to the linked container via the alias, real name, and container hostname
aliasList := alias + " " + child.Config.Hostname
// only add the name if alias isn't equal to the name
if alias != child.Name[1:] {
aliasList = aliasList + " " + child.Name[1:]
}
extraContent = append(extraContent, etchosts.Record{Hosts: aliasList, IP: child.NetworkSettings.IPAddress})
}
for _, extraHost := range container.hostConfig.ExtraHosts {
// allow IPv6 addresses in extra hosts; only split on first ":"
parts := strings.SplitN(extraHost, ":", 2)
extraContent = append(extraContent, etchosts.Record{Hosts: parts[0], IP: parts[1]})
}
return etchosts.Build(container.HostsPath, IP, container.Config.Hostname, container.Config.Domainname, extraContent)
}
func (container *Container) buildHostnameAndHostsFiles(IP string) error {
if err := container.buildHostnameFile(); err != nil {
return err
}
return container.buildHostsFiles(IP)
}
func (container *Container) AllocateNetwork() error {
mode := container.hostConfig.NetworkMode
if container.Config.NetworkDisabled || !mode.IsPrivate() {
return nil
}
var (
env *engine.Env
err error
eng = container.daemon.eng
)
job := eng.Job("allocate_interface", container.ID)
job.Setenv("RequestedMac", container.Config.MacAddress)
if env, err = job.Stdout.AddEnv(); err != nil {
return err
}
if err = job.Run(); err != nil {
return err
}
// Error handling: At this point, the interface is allocated so we have to
// make sure that it is always released in case of error, otherwise we
// might leak resources.
if container.Config.PortSpecs != nil {
if err = migratePortMappings(container.Config, container.hostConfig); err != nil {
eng.Job("release_interface", container.ID).Run()
return err
}
container.Config.PortSpecs = nil
if err = container.WriteHostConfig(); err != nil {
eng.Job("release_interface", container.ID).Run()
return err
}
}
var (
portSpecs = make(nat.PortSet)
bindings = make(nat.PortMap)
)
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
}
if container.hostConfig.PortBindings != nil {
for p, b := range container.hostConfig.PortBindings {
bindings[p] = []nat.PortBinding{}
for _, bb := range b {
bindings[p] = append(bindings[p], nat.PortBinding{
HostIp: bb.HostIp,
HostPort: bb.HostPort,
})
}
}
}
container.NetworkSettings.PortMapping = nil
for port := range portSpecs {
if err = container.allocatePort(eng, port, bindings); err != nil {
eng.Job("release_interface", container.ID).Run()
return err
}
}
container.WriteHostConfig()
container.NetworkSettings.Ports = bindings
container.NetworkSettings.Bridge = env.Get("Bridge")
container.NetworkSettings.IPAddress = env.Get("IP")
container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen")
container.NetworkSettings.MacAddress = env.Get("MacAddress")
container.NetworkSettings.Gateway = env.Get("Gateway")
container.NetworkSettings.LinkLocalIPv6Address = env.Get("LinkLocalIPv6")
container.NetworkSettings.LinkLocalIPv6PrefixLen = 64
container.NetworkSettings.GlobalIPv6Address = env.Get("GlobalIPv6")
container.NetworkSettings.GlobalIPv6PrefixLen = env.GetInt("GlobalIPv6PrefixLen")
container.NetworkSettings.IPv6Gateway = env.Get("IPv6Gateway")
return nil
}
func (container *Container) ReleaseNetwork() {
if container.Config.NetworkDisabled || !container.hostConfig.NetworkMode.IsPrivate() {
return
}
eng := container.daemon.eng
job := eng.Job("release_interface", container.ID)
job.SetenvBool("overrideShutdown", true)
job.Run()
container.NetworkSettings = &NetworkSettings{}
}
func (container *Container) isNetworkAllocated() bool {
return container.NetworkSettings.IPAddress != ""
}
func (container *Container) RestoreNetwork() error {
mode := container.hostConfig.NetworkMode
// Don't attempt a restore if we previously didn't allocate networking.
// This might be a legacy container with no network allocated, in which case the
// allocation will happen once and for all at start.
if !container.isNetworkAllocated() || container.Config.NetworkDisabled || !mode.IsPrivate() {
return nil
}
eng := container.daemon.eng
// Re-allocate the interface with the same IP and MAC address.
job := eng.Job("allocate_interface", container.ID)
job.Setenv("RequestedIP", container.NetworkSettings.IPAddress)
job.Setenv("RequestedMac", container.NetworkSettings.MacAddress)
if err := job.Run(); err != nil {
return err
}
// Re-allocate any previously allocated ports.
for port := range container.NetworkSettings.Ports {
if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil {
return err
}
}
return nil
}
// cleanup releases any network resources allocated to the container along with any rules
// around how containers are linked together. It also unmounts the container's root filesystem.
func (container *Container) cleanup() {
container.ReleaseNetwork()
// Disable all active links
if container.activeLinks != nil {
for _, link := range container.activeLinks {
link.Disable()
}
}
if err := container.Unmount(); err != nil {
log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
}
for _, eConfig := range container.execCommands.s {
container.daemon.unregisterExecCommand(eConfig)
}
}
func (container *Container) KillSig(sig int) error {
log.Debugf("Sending %d to %s", sig, container.ID)
container.Lock()
defer container.Unlock()
// We could unpause the container for them rather than returning this error
if container.Paused {
return fmt.Errorf("Container %s is paused. Unpause the container before stopping", container.ID)
}
if !container.Running {
return nil
}
// signal to the monitor that it should not restart the container
// after we send the kill signal
container.monitor.ExitOnNext()
// if the container is currently restarting we do not need to send the signal
// to the process. Telling the monitor that it should exit on it's next event
// loop is enough
if container.Restarting {
return nil
}
return container.daemon.Kill(container, sig)
}
// Wrapper aroung KillSig() suppressing "no such process" error.
func (container *Container) killPossiblyDeadProcess(sig int) error {
err := container.KillSig(sig)
if err == syscall.ESRCH {
log.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.GetPid(), sig)
return nil
}
return err
}
func (container *Container) Pause() error {
if container.IsPaused() {
return fmt.Errorf("Container %s is already paused", container.ID)
}
if !container.IsRunning() {
return fmt.Errorf("Container %s is not running", container.ID)
}
return container.daemon.Pause(container)
}
func (container *Container) Unpause() error {
if !container.IsPaused() {
return fmt.Errorf("Container %s is not paused", container.ID)
}
if !container.IsRunning() {
return fmt.Errorf("Container %s is not running", container.ID)
}
return container.daemon.Unpause(container)
}
func (container *Container) Kill() error {
if !container.IsRunning() {
return nil
}
// 1. Send SIGKILL
if err := container.killPossiblyDeadProcess(9); err != nil {
return err
}
// 2. Wait for the process to die, in last resort, try to kill the process directly
if _, err := container.WaitStop(10 * time.Second); err != nil {
// Ensure that we don't kill ourselves
if pid := container.GetPid(); pid != 0 {
log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", common.TruncateID(container.ID))
if err := syscall.Kill(pid, 9); err != nil {
if err != syscall.ESRCH {
return err
}
log.Debugf("Cannot kill process (pid=%d) with signal 9: no such process.", pid)
}
}
}
container.WaitStop(-1 * time.Second)
return nil
}
func (container *Container) Stop(seconds int) error {
if !container.IsRunning() {
return nil
}
// 1. Send a SIGTERM
if err := container.killPossiblyDeadProcess(15); err != nil {
log.Infof("Failed to send SIGTERM to the process, force killing")
if err := container.killPossiblyDeadProcess(9); err != nil {
return err
}
}
// 2. Wait for the process to exit on its own
if _, err := container.WaitStop(time.Duration(seconds) * time.Second); err != nil {
log.Infof("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds)
// 3. If it doesn't, then send SIGKILL
if err := container.Kill(); err != nil {
container.WaitStop(-1 * time.Second)
return err
}
}
return nil
}
func (container *Container) Restart(seconds int) error {
// Avoid unnecessarily unmounting and then directly mounting
// the container when the container stops and then starts
// again
if err := container.Mount(); err == nil {
defer container.Unmount()
}
if err := container.Stop(seconds); err != nil {
return err
}
return container.Start()
}
func (container *Container) Resize(h, w int) error {
if !container.IsRunning() {
return fmt.Errorf("Cannot resize container %s, container is not running", container.ID)
}
return container.command.ProcessConfig.Terminal.Resize(h, w)
}
func (container *Container) ExportRw() (archive.Archive, error) {
if err := container.Mount(); err != nil {
return nil, err
}
if container.daemon == nil {
return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
}
archive, err := container.daemon.Diff(container)
if err != nil {
container.Unmount()
return nil, err
}
return ioutils.NewReadCloserWrapper(archive, func() error {
err := archive.Close()
container.Unmount()
return err
}),
nil
}
func (container *Container) Export() (archive.Archive, error) {
if err := container.Mount(); err != nil {
return nil, err
}
archive, err := archive.Tar(container.basefs, archive.Uncompressed)
if err != nil {
container.Unmount()
return nil, err
}
return ioutils.NewReadCloserWrapper(archive, func() error {
err := archive.Close()
container.Unmount()
return err
}),
nil
}
func (container *Container) Mount() error {
return container.daemon.Mount(container)
}
func (container *Container) changes() ([]archive.Change, error) {
return container.daemon.Changes(container)
}
func (container *Container) Changes() ([]archive.Change, error) {
container.Lock()
defer container.Unlock()
return container.changes()
}
func (container *Container) GetImage() (*image.Image, error) {
if container.daemon == nil {
return nil, fmt.Errorf("Can't get image of unregistered container")
}
return container.daemon.graph.Get(container.ImageID)
}
func (container *Container) Unmount() error {
return container.daemon.Unmount(container)
}
func (container *Container) logPath(name string) (string, error) {
return container.getRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name))
}
func (container *Container) ReadLog(name string) (io.Reader, error) {
pth, err := container.logPath(name)
if err != nil {
return nil, err
}
return os.Open(pth)
}
func (container *Container) hostConfigPath() (string, error) {
return container.getRootResourcePath("hostconfig.json")
}
func (container *Container) jsonPath() (string, error) {
return container.getRootResourcePath("config.json")
}
// This method must be exported to be used from the lxc template
// This directory is only usable when the container is running
func (container *Container) RootfsPath() string {
return container.basefs
}
func validateID(id string) error {
if id == "" {
return fmt.Errorf("Invalid empty id")
}
return nil
}
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var (
sizeRw, sizeRootfs int64
err error
driver = container.daemon.driver
)
if err := container.Mount(); err != nil {
log.Errorf("Failed to compute size of container rootfs %s: %s", container.ID, err)
return sizeRw, sizeRootfs
}
defer container.Unmount()
initID := fmt.Sprintf("%s-init", container.ID)
sizeRw, err = driver.DiffSize(container.ID, initID)
if err != nil {
log.Errorf("Driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
// FIXME: GetSize should return an error. Not changing it now in case
// there is a side-effect.
sizeRw = -1
}
if _, err = os.Stat(container.basefs); err != nil {
if sizeRootfs, err = directory.Size(container.basefs); err != nil {
sizeRootfs = -1
}
}
return sizeRw, sizeRootfs
}
func (container *Container) Copy(resource string) (io.ReadCloser, error) {
if err := container.Mount(); err != nil {
return nil, err
}
basePath, err := container.getResourcePath(resource)
if err != nil {
container.Unmount()
return nil, err
}
// Check if this is actually in a volume
for _, mnt := range container.VolumeMounts() {
if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) {
return mnt.Export(resource)
}
}
stat, err := os.Stat(basePath)
if err != nil {
container.Unmount()
return nil, err
}
var filter []string
if !stat.IsDir() {
d, f := path.Split(basePath)
basePath = d
filter = []string{f}
} else {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
archive, err := archive.TarWithOptions(basePath, &archive.TarOptions{
Compression: archive.Uncompressed,
IncludeFiles: filter,
})
if err != nil {
container.Unmount()
return nil, err
}
return ioutils.NewReadCloserWrapper(archive, func() error {
err := archive.Close()
container.Unmount()
return err
}),
nil
}
// Returns true if the container exposes a certain port
func (container *Container) Exposes(p nat.Port) bool {
_, exists := container.Config.ExposedPorts[p]
return exists
}
func (container *Container) GetPtyMaster() (libcontainer.Console, error) {
ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal)
if !ok {
return nil, ErrNoTTY
}
return ttyConsole.Master(), nil
}
func (container *Container) HostConfig() *runconfig.HostConfig {
container.Lock()
res := container.hostConfig
container.Unlock()
return res
}
func (container *Container) SetHostConfig(hostConfig *runconfig.HostConfig) {
container.Lock()
container.hostConfig = hostConfig
container.Unlock()
}
func (container *Container) DisableLink(name string) {
if container.activeLinks != nil {
if link, exists := container.activeLinks[name]; exists {
link.Disable()
} else {
log.Debugf("Could not find active link for %s", name)
}
}
}
func (container *Container) setupContainerDns() error {
if container.ResolvConfPath != "" {
// check if this is an existing container that needs DNS update:
if container.UpdateDns {
// read the host's resolv.conf, get the hash and call updateResolvConf
log.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID)
latestResolvConf, latestHash := resolvconf.GetLastModified()
// clean container resolv.conf re: localhost nameservers and IPv6 NS (if IPv6 disabled)
updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.EnableIPv6)
if modified {
// changes have occurred during resolv.conf localhost cleanup: generate an updated hash
newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf))
if err != nil {
return err
}
latestHash = newHash
}
if err := container.updateResolvConf(updatedResolvConf, latestHash); err != nil {
return err
}
// successful update of the restarting container; set the flag off
container.UpdateDns = false
}
return nil
}
var (
config = container.hostConfig
daemon = container.daemon
)
resolvConf, err := resolvconf.Get()
if err != nil {
return err
}
container.ResolvConfPath, err = container.getRootResourcePath("resolv.conf")
if err != nil {
return err
}
if config.NetworkMode != "host" {
// check configurations for any container/daemon dns settings
if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 {
var (
dns = resolvconf.GetNameservers(resolvConf)
dnsSearch = resolvconf.GetSearchDomains(resolvConf)
)
if len(config.Dns) > 0 {
dns = config.Dns
} else if len(daemon.config.Dns) > 0 {
dns = daemon.config.Dns
}
if len(config.DnsSearch) > 0 {
dnsSearch = config.DnsSearch
} else if len(daemon.config.DnsSearch) > 0 {
dnsSearch = daemon.config.DnsSearch
}
return resolvconf.Build(container.ResolvConfPath, dns, dnsSearch)
}
// replace any localhost/127.*, and remove IPv6 nameservers if IPv6 disabled in daemon
resolvConf, _ = resolvconf.FilterResolvDns(resolvConf, daemon.config.EnableIPv6)
}
//get a sha256 hash of the resolv conf at this point so we can check
//for changes when the host resolv.conf changes (e.g. network update)
resolvHash, err := utils.HashData(bytes.NewReader(resolvConf))
if err != nil {
return err
}
resolvHashFile := container.ResolvConfPath + ".hash"
if err = ioutil.WriteFile(resolvHashFile, []byte(resolvHash), 0644); err != nil {
return err
}
return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644)
}
// called when the host's resolv.conf changes to check whether container's resolv.conf
// is unchanged by the container "user" since container start: if unchanged, the
// container's resolv.conf will be updated to match the host's new resolv.conf
func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolvHash string) error {
if container.ResolvConfPath == "" {
return nil
}
if container.Running {
//set a marker in the hostConfig to update on next start/restart
container.UpdateDns = true
return nil
}
resolvHashFile := container.ResolvConfPath + ".hash"
//read the container's current resolv.conf and compute the hash
resolvBytes, err := ioutil.ReadFile(container.ResolvConfPath)
if err != nil {
return err
}
curHash, err := utils.HashData(bytes.NewReader(resolvBytes))
if err != nil {
return err
}
//read the hash from the last time we wrote resolv.conf in the container
hashBytes, err := ioutil.ReadFile(resolvHashFile)
if err != nil {
if !os.IsNotExist(err) {
return err
}
// backwards compat: if no hash file exists, this container pre-existed from
// a Docker daemon that didn't contain this update feature. Given we can't know
// if the user has modified the resolv.conf since container start time, safer
// to just never update the container's resolv.conf during it's lifetime which
// we can control by setting hashBytes to an empty string
hashBytes = []byte("")
}
//if the user has not modified the resolv.conf of the container since we wrote it last
//we will replace it with the updated resolv.conf from the host
if string(hashBytes) == curHash {
log.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath)
// for atomic updates to these files, use temporary files with os.Rename:
dir := path.Dir(container.ResolvConfPath)
tmpHashFile, err := ioutil.TempFile(dir, "hash")
if err != nil {
return err
}
tmpResolvFile, err := ioutil.TempFile(dir, "resolv")
if err != nil {
return err
}
// write the updates to the temp files
if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newResolvHash), 0644); err != nil {
return err
}
if err = ioutil.WriteFile(tmpResolvFile.Name(), updatedResolvConf, 0644); err != nil {
return err
}
// rename the temp files for atomic replace
if err = os.Rename(tmpHashFile.Name(), resolvHashFile); err != nil {
return err
}
return os.Rename(tmpResolvFile.Name(), container.ResolvConfPath)
}
return nil
}
func (container *Container) updateParentsHosts() error {
refs := container.daemon.ContainerGraph().RefPaths(container.ID)
for _, ref := range refs {
if ref.ParentID == "0" {
continue
}
c, err := container.daemon.Get(ref.ParentID)
if err != nil {
log.Error(err)
}
if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() {
log.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress)
if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, ref.Name); err != nil {
log.Errorf("Failed to update /etc/hosts in parent container %s for alias %s: %v", c.ID, ref.Name, err)
}
}
}
return nil
}
func (container *Container) initializeNetworking() error {
var err error
if container.hostConfig.NetworkMode.IsHost() {
container.Config.Hostname, err = os.Hostname()
if err != nil {
return err
}
parts := strings.SplitN(container.Config.Hostname, ".", 2)
if len(parts) > 1 {
container.Config.Hostname = parts[0]
container.Config.Domainname = parts[1]
}
content, err := ioutil.ReadFile("/etc/hosts")
if os.IsNotExist(err) {
return container.buildHostnameAndHostsFiles("")
} else if err != nil {
return err
}
if err := container.buildHostnameFile(); err != nil {
return err
}
hostsPath, err := container.getRootResourcePath("hosts")
if err != nil {
return err
}
container.HostsPath = hostsPath
return ioutil.WriteFile(container.HostsPath, content, 0644)
}
if container.hostConfig.NetworkMode.IsContainer() {
// we need to get the hosts files from the container to join
nc, err := container.getNetworkedContainer()
if err != nil {
return err
}
container.HostnamePath = nc.HostnamePath
container.HostsPath = nc.HostsPath
container.ResolvConfPath = nc.ResolvConfPath
container.Config.Hostname = nc.Config.Hostname
container.Config.Domainname = nc.Config.Domainname
return nil
}
if container.daemon.config.DisableNetwork {
container.Config.NetworkDisabled = true
return container.buildHostnameAndHostsFiles("127.0.1.1")
}
if err := container.AllocateNetwork(); err != nil {
return err
}
return container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress)
}
// Make sure the config is compatible with the current kernel
func (container *Container) verifyDaemonSettings() {
if container.Config.Memory > 0 && !container.daemon.sysInfo.MemoryLimit {
log.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.")
container.Config.Memory = 0
}
if container.Config.Memory > 0 && !container.daemon.sysInfo.SwapLimit {
log.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.")
container.Config.MemorySwap = -1
}
if container.daemon.sysInfo.IPv4ForwardingDisabled {
log.Warnf("IPv4 forwarding is disabled. Networking will not work")
}
}
func (container *Container) setupLinkedContainers() ([]string, error) {
var (
env []string
daemon = container.daemon
)
children, err := daemon.Children(container.Name)
if err != nil {
return nil, err
}
if len(children) > 0 {
container.activeLinks = make(map[string]*links.Link, len(children))
// If we encounter an error make sure that we rollback any network
// config and iptables changes
rollback := func() {
for _, link := range container.activeLinks {
link.Disable()
}
container.activeLinks = nil
}
for linkAlias, child := range children {
if !child.IsRunning() {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias)
}
link, err := links.NewLink(
container.NetworkSettings.IPAddress,
child.NetworkSettings.IPAddress,
linkAlias,
child.Config.Env,
child.Config.ExposedPorts,
daemon.eng)
if err != nil {
rollback()
return nil, err
}
container.activeLinks[link.Alias()] = link
if err := link.Enable(); err != nil {
rollback()
return nil, err
}
for _, envVar := range link.ToEnv() {
env = append(env, envVar)
}
}
}
return env, nil
}
func (container *Container) createDaemonEnvironment(linkedEnv []string) []string {
// if a domain name was specified, append it to the hostname (see #7851)
fullHostname := container.Config.Hostname
if container.Config.Domainname != "" {
fullHostname = fmt.Sprintf("%s.%s", fullHostname, container.Config.Domainname)
}
// Setup environment
env := []string{
"PATH=" + DefaultPathEnv,
"HOSTNAME=" + fullHostname,
// Note: we don't set HOME here because it'll get autoset intelligently
// based on the value of USER inside dockerinit, but only if it isn't
// set already (ie, that can be overridden by setting HOME via -e or ENV
// in a Dockerfile).
}
if container.Config.Tty {
env = append(env, "TERM=xterm")
}
env = append(env, linkedEnv...)
// because the env on the container can override certain default values
// we need to replace the 'env' keys where they match and append anything
// else.
env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env)
return env
}
func (container *Container) setupWorkingDirectory() error {
if container.Config.WorkingDir != "" {
container.Config.WorkingDir = path.Clean(container.Config.WorkingDir)
pth, err := container.getResourcePath(container.Config.WorkingDir)
if err != nil {
return err
}
pthInfo, err := os.Stat(pth)
if err != nil {
if !os.IsNotExist(err) {
return err
}
if err := os.MkdirAll(pth, 0755); err != nil {
return err
}
}
if pthInfo != nil && !pthInfo.IsDir() {
return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir)
}
}
return nil
}
func (container *Container) startLogging() error {
cfg := container.hostConfig.LogConfig
if cfg.Type == "" {
cfg = container.daemon.defaultLogConfig
}
var l logger.Logger
switch cfg.Type {
case "json-file":
pth, err := container.logPath("json")
if err != nil {
return err
}
dl, err := jsonfilelog.New(pth)
if err != nil {
return err
}
l = dl
case "syslog":
dl, err := syslog.New(container.ID[:12])
if err != nil {
return err
}
l = dl
case "none":
return nil
default:
return fmt.Errorf("Unknown logging driver: %s", cfg.Type)
}
copier, err := logger.NewCopier(container.ID, map[string]io.Reader{"stdout": container.StdoutPipe(), "stderr": container.StderrPipe()}, l)
if err != nil {
return err
}
container.logCopier = copier
copier.Run()
container.logDriver = l
return nil
}
func (container *Container) waitForStart() error {
container.monitor = newContainerMonitor(container, container.hostConfig.RestartPolicy)
// block until we either receive an error from the initial start of the container's
// process or until the process is running in the container
select {
case <-container.monitor.startSignal:
case err := <-promise.Go(container.monitor.Start):
return err
}
return nil
}
func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bindings nat.PortMap) error {
binding := bindings[port]
if container.hostConfig.PublishAllPorts && len(binding) == 0 {
binding = append(binding, nat.PortBinding{})
}
for i := 0; i < len(binding); i++ {
b := binding[i]
job := eng.Job("allocate_port", container.ID)
job.Setenv("HostIP", b.HostIp)
job.Setenv("HostPort", b.HostPort)
job.Setenv("Proto", port.Proto())
job.Setenv("ContainerPort", port.Port())
portEnv, err := job.Stdout.AddEnv()
if err != nil {
return err
}
if err := job.Run(); err != nil {
return err
}
b.HostIp = portEnv.Get("HostIP")
b.HostPort = portEnv.Get("HostPort")
binding[i] = b
}
bindings[port] = binding
return nil
}
func (container *Container) GetProcessLabel() string {
// even if we have a process label return "" if we are running
// in privileged mode
if container.hostConfig.Privileged {
return ""
}
return container.ProcessLabel
}
func (container *Container) GetMountLabel() string {
if container.hostConfig.Privileged {
return ""
}
return container.MountLabel
}
func (container *Container) getIpcContainer() (*Container, error) {
containerID := container.hostConfig.IpcMode.Container()
c, err := container.daemon.Get(containerID)
if err != nil {
return nil, err
}
if !c.IsRunning() {
return nil, fmt.Errorf("cannot join IPC of a non running container: %s", containerID)
}
return c, nil
}
func (container *Container) getNetworkedContainer() (*Container, error) {
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "container":
if len(parts) != 2 {
return nil, fmt.Errorf("no container specified to join network")
}
nc, err := container.daemon.Get(parts[1])
if err != nil {
return nil, err
}
if !nc.IsRunning() {
return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1])
}
return nc, nil
default:
return nil, fmt.Errorf("network mode not set to container")
}
}
func (container *Container) Stats() (*execdriver.ResourceStats, error) {
return container.daemon.Stats(container)
}
func (c *Container) LogDriverType() string {
c.Lock()
defer c.Unlock()
if c.hostConfig.LogConfig.Type == "" {
return c.daemon.defaultLogConfig.Type
}
return c.hostConfig.LogConfig.Type
}