From a6c791e8a92f29a2ae2c6bc81e4ab873d3ceb41f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 8 Feb 2014 09:53:04 -0800 Subject: [PATCH 001/282] Add set master for interface Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 0ea5b4dba..01b3ff08f 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -386,6 +386,39 @@ func NetworkSetMTU(iface *net.Interface, mtu int) error { return s.HandleAck(wb.Seq) } +// same as ip link set $name master $master +func NetworkSetMaster(iface, master *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = 0xFFFFFFFF + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(master.Index)) + + data := newRtAttr(syscall.IFLA_MASTER, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + // Add an Ip address to an interface. This is identical to: // ip addr add $ip/$ipNet dev $iface func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { From e3762e8d6937741a5654b1443f95beb784f018f1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 8 Feb 2014 10:03:16 -0800 Subject: [PATCH 002/282] Add network set interface in namespace by pid Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 01b3ff08f..e69635e2f 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -419,6 +419,38 @@ func NetworkSetMaster(iface, master *net.Interface) error { return s.HandleAck(wb.Seq) } +func NetworkSetNsPid(iface *net.Interface, nspid int) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = 0xFFFFFFFF + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(nspid)) + + data := newRtAttr(syscall.IFLA_NET_NS_PID, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + // Add an Ip address to an interface. This is identical to: // ip addr add $ip/$ipNet dev $iface func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { From ee39033073ece35e91c6c5a8cb66d23246511fb0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sat, 8 Feb 2014 20:44:04 -0800 Subject: [PATCH 003/282] Use c to change interface name Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index e69635e2f..77ebfcd46 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -2,6 +2,60 @@ package netlink +/* +#include +#include +#include +#include + +static int get_socket(void) { + int s_errno; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) { + return fd; + } + s_errno = errno; + + fd = socket(PF_PACKET, SOCK_DGRAM, 0); + if (fd >= 0) { + return fd; + } + + fd = socket(PF_INET6, SOCK_DGRAM, 0); + if (fd >= 0) { + return fd; + } + errno = s_errno; + return -1; +} + + +static int change_name(const char *old_name, const char *new_name) { + struct ifreq ifr; + int err; + int fd; + + fd = get_socket(); + if (fd < 0) { + return -1; + } + + strncpy(ifr.ifr_name, old_name, IFNAMSIZ); + strncpy(ifr.ifr_newname, new_name, IFNAMSIZ); + + err = ioctl(fd, SIOCSIFNAME, &ifr); + if (err) { + close(fd); + return -1; + } + close(fd); + return err; +} +*/ +import "C" + import ( "encoding/binary" "fmt" @@ -641,3 +695,15 @@ done: return res, nil } + +func NetworkChangeName(oldName, newName string) error { + var ( + cold = C.CString(oldName) + cnew = C.CString(newName) + ) + + if errno := int(C.change_name(cold, cnew)); errno != 0 { + return fmt.Errorf("unable to change name %d", errno) + } + return nil +} From 524416560a4624d30023db32101c9fe5ebffc895 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 9 Feb 2014 05:54:13 -0800 Subject: [PATCH 004/282] Replace my C code with tianons Go code Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 97 +++++++++++++----------------------- 1 file changed, 35 insertions(+), 62 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 77ebfcd46..46bd3d837 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -2,60 +2,6 @@ package netlink -/* -#include -#include -#include -#include - -static int get_socket(void) { - int s_errno; - int fd; - - fd = socket(PF_INET, SOCK_DGRAM, 0); - if (fd >= 0) { - return fd; - } - s_errno = errno; - - fd = socket(PF_PACKET, SOCK_DGRAM, 0); - if (fd >= 0) { - return fd; - } - - fd = socket(PF_INET6, SOCK_DGRAM, 0); - if (fd >= 0) { - return fd; - } - errno = s_errno; - return -1; -} - - -static int change_name(const char *old_name, const char *new_name) { - struct ifreq ifr; - int err; - int fd; - - fd = get_socket(); - if (fd < 0) { - return -1; - } - - strncpy(ifr.ifr_name, old_name, IFNAMSIZ); - strncpy(ifr.ifr_newname, new_name, IFNAMSIZ); - - err = ioctl(fd, SIOCSIFNAME, &ifr); - if (err) { - close(fd); - return -1; - } - close(fd); - return err; -} -*/ -import "C" - import ( "encoding/binary" "fmt" @@ -696,14 +642,41 @@ done: return res, nil } -func NetworkChangeName(oldName, newName string) error { - var ( - cold = C.CString(oldName) - cnew = C.CString(newName) - ) - - if errno := int(C.change_name(cold, cnew)); errno != 0 { - return fmt.Errorf("unable to change name %d", errno) +func getIfSocket() (int, error) { + fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) + if err == nil { + return fd, err } + sErr := err + + fd, err = syscall.Socket(syscall.AF_PACKET, syscall.SOCK_DGRAM, 0) + if err == nil { + return fd, err + } + + fd, err = syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0) + if err == nil { + return fd, err + } + + return -1, sErr +} + +func NetworkChangeName(oldName, newName string) error { + fd, err := getIfSocket() + if err != nil { + return err + } + defer syscall.Close(fd) + IFNAMSIZ := 16 + + data := [32]byte{} + copy(data[:IFNAMSIZ-1], oldName) + copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName) + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 { + return errno + } + return nil } From 3a7c144e9992591f32daf2d4f1b35b7b6520a07e Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 9 Feb 2014 18:12:43 -0700 Subject: [PATCH 005/282] Update NetworkChangeName to be more similar to my original (moving IFNAMSIZ constant outside the function like it should've been) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- pkg/netlink/netlink_linux.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 46bd3d837..b94cbf682 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -662,15 +662,17 @@ func getIfSocket() (int, error) { return -1, sErr } +// from +const IFNAMSIZ = 16 + func NetworkChangeName(oldName, newName string) error { fd, err := getIfSocket() if err != nil { return err } defer syscall.Close(fd) - IFNAMSIZ := 16 - data := [32]byte{} + data := [IFNAMSIZ * 2]byte{} copy(data[:IFNAMSIZ-1], oldName) copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName) From 27df18ff11dbfc22ca539462710abf07d507c0d9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Feb 2014 11:36:23 -0800 Subject: [PATCH 006/282] Improve get if socket loop Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 43 +++++++++++++++++------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index b94cbf682..4e091ef10 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -10,6 +10,12 @@ import ( "unsafe" ) +const ( + IFNAMSIZ = 16 + DEFAULT_CHANGE = 0xFFFFFFFF + IFLA_INFO_KIND = 1 +) + var nextSeqNr int func nativeEndian() binary.ByteOrder { @@ -368,7 +374,7 @@ func NetworkSetMTU(iface *net.Interface, mtu int) error { msg.Type = syscall.RTM_SETLINK msg.Flags = syscall.NLM_F_REQUEST msg.Index = int32(iface.Index) - msg.Change = 0xFFFFFFFF + msg.Change = DEFAULT_CHANGE wb.AddData(msg) var ( @@ -400,7 +406,7 @@ func NetworkSetMaster(iface, master *net.Interface) error { msg.Type = syscall.RTM_SETLINK msg.Flags = syscall.NLM_F_REQUEST msg.Index = int32(iface.Index) - msg.Change = 0xFFFFFFFF + msg.Change = DEFAULT_CHANGE wb.AddData(msg) var ( @@ -432,7 +438,7 @@ func NetworkSetNsPid(iface *net.Interface, nspid int) error { msg.Type = syscall.RTM_SETLINK msg.Flags = syscall.NLM_F_REQUEST msg.Index = int32(iface.Index) - msg.Change = 0xFFFFFFFF + msg.Change = DEFAULT_CHANGE wb.AddData(msg) var ( @@ -524,8 +530,6 @@ func NetworkLinkAdd(name string, linkType string) error { nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) wb.AddData(nameData) - IFLA_INFO_KIND := 1 - kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType)) infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) @@ -642,29 +646,22 @@ done: return res, nil } -func getIfSocket() (int, error) { - fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) - if err == nil { - return fd, err +func getIfSocket() (fd int, err error) { + for _, socket := range []int{ + syscall.AF_INET, + syscall.AF_PACKET, + syscall.AF_INET6, + } { + if fd, err = syscall.Socket(socket, syscall.SOCK_DGRAM, 0); err == nil { + break + } } - sErr := err - - fd, err = syscall.Socket(syscall.AF_PACKET, syscall.SOCK_DGRAM, 0) if err == nil { - return fd, err + return fd, nil } - - fd, err = syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0) - if err == nil { - return fd, err - } - - return -1, sErr + return -1, err } -// from -const IFNAMSIZ = 16 - func NetworkChangeName(oldName, newName string) error { fd, err := getIfSocket() if err != nil { From 38eabfa65c5be78a08a8287b600e230dbe0bfac5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Feb 2014 13:37:16 -0800 Subject: [PATCH 007/282] Create veth pair via netlink Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 43 ++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 4e091ef10..52c77a26a 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -14,6 +14,8 @@ const ( IFNAMSIZ = 16 DEFAULT_CHANGE = 0xFFFFFFFF IFLA_INFO_KIND = 1 + IFLA_INFO_DATA = 2 + VETH_PEER = 1 ) var nextSeqNr int @@ -197,7 +199,9 @@ func (rr *NetlinkRequest) ToWireFormat() []byte { } func (rr *NetlinkRequest) AddData(data NetlinkRequestData) { - rr.Data = append(rr.Data, data) + if data != nil { + rr.Data = append(rr.Data, data) + } } func newNetlinkRequest(proto, flags int) *NetlinkRequest { @@ -676,6 +680,41 @@ func NetworkChangeName(oldName, newName string) error { if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 { return errno } - return nil } + +func NetworkCreateVethPair(name1, name2 string) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + wb.AddData(msg) + + kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated("veth")) + info := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) + // wb.AddData(info) + + peerName := newRtAttr(syscall.IFLA_IFNAME, nonZeroTerminated(name2)) + peer := newRtAttr(VETH_PEER, peerName.ToWireFormat()) + // wb.AddData(peer) + + b := []byte{} + b = append(b, peer.ToWireFormat()...) + b = append(b, info.ToWireFormat()...) + + infoData := newRtAttr(IFLA_INFO_DATA, b) + wb.AddData(infoData) + + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1)) + wb.AddData(nameData) + + if err := s.Send(wb); err != nil { + return err + } + return s.HandleAck(wb.Seq) +} From 4dec36d1ee8cfd8b396210daa5367d146bbb34bc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Feb 2014 16:41:16 -0800 Subject: [PATCH 008/282] Allow add of empty name Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 52c77a26a..23dba0c88 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -531,8 +531,10 @@ func NetworkLinkAdd(name string, linkType string) error { msg := newIfInfomsg(syscall.AF_UNSPEC) wb.AddData(msg) - nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) - wb.AddData(nameData) + if name != "" { + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) + wb.AddData(nameData) + } kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType)) From f9cd1be6ffbda85f20cc7926274fab7484a19823 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Feb 2014 22:32:07 -0800 Subject: [PATCH 009/282] Add more netlink functions for set ns by fd and bring iface down Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 61 ++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 23dba0c88..3b21a38d0 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -15,7 +15,8 @@ const ( DEFAULT_CHANGE = 0xFFFFFFFF IFLA_INFO_KIND = 1 IFLA_INFO_DATA = 2 - VETH_PEER = 1 + VETH_INFO_PEER = 1 + IFLA_NET_NS_FD = 28 ) var nextSeqNr int @@ -365,6 +366,28 @@ func NetworkLinkUp(iface *net.Interface) error { return s.HandleAck(wb.Seq) } +func NetworkLinkDown(iface *net.Interface) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Change = syscall.IFF_UP + msg.Flags = 0 & ^syscall.IFF_UP + msg.Index = int32(iface.Index) + wb.AddData(msg) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + func NetworkSetMTU(iface *net.Interface, mtu int) error { s, err := getNetlinkSocket() if err != nil { @@ -461,6 +484,38 @@ func NetworkSetNsPid(iface *net.Interface, nspid int) error { return s.HandleAck(wb.Seq) } +func NetworkSetNsFd(iface *net.Interface, fd int) error { + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + wb := newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Type = syscall.RTM_SETLINK + msg.Flags = syscall.NLM_F_REQUEST + msg.Index = int32(iface.Index) + msg.Change = DEFAULT_CHANGE + wb.AddData(msg) + + var ( + b = make([]byte, 4) + native = nativeEndian() + ) + native.PutUint32(b, uint32(fd)) + + data := newRtAttr(IFLA_NET_NS_FD, b) + wb.AddData(data) + + if err := s.Send(wb); err != nil { + return err + } + + return s.HandleAck(wb.Seq) +} + // Add an Ip address to an interface. This is identical to: // ip addr add $ip/$ipNet dev $iface func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { @@ -668,7 +723,7 @@ func getIfSocket() (fd int, err error) { return -1, err } -func NetworkChangeName(oldName, newName string) error { +func NetworkChangeName(iface *net.Interface, newName string) error { fd, err := getIfSocket() if err != nil { return err @@ -676,7 +731,7 @@ func NetworkChangeName(oldName, newName string) error { defer syscall.Close(fd) data := [IFNAMSIZ * 2]byte{} - copy(data[:IFNAMSIZ-1], oldName) + copy(data[:IFNAMSIZ-1], iface.Name) copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName) if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&data[0]))); errno != 0 { From 27ed9a9f98750ab666d6221553b6f4ea59d396b8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 11 Feb 2014 03:32:35 -0800 Subject: [PATCH 010/282] Exec out to ip right now for creating the veth pair Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_linux.go | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 3b21a38d0..b9e04a339 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" "net" + "os/exec" "syscall" "unsafe" ) @@ -741,37 +742,8 @@ func NetworkChangeName(iface *net.Interface, newName string) error { } func NetworkCreateVethPair(name1, name2 string) error { - s, err := getNetlinkSocket() - if err != nil { - return err + if data, err := exec.Command("ip", "link", "add", name1, "type", "veth", "peer", "name", name2).Output(); err != nil { + return fmt.Errorf("%s %s", data, err) } - defer s.Close() - - wb := newNetlinkRequest(syscall.RTM_NEWLINK, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) - - msg := newIfInfomsg(syscall.AF_UNSPEC) - wb.AddData(msg) - - kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated("veth")) - info := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) - // wb.AddData(info) - - peerName := newRtAttr(syscall.IFLA_IFNAME, nonZeroTerminated(name2)) - peer := newRtAttr(VETH_PEER, peerName.ToWireFormat()) - // wb.AddData(peer) - - b := []byte{} - b = append(b, peer.ToWireFormat()...) - b = append(b, info.ToWireFormat()...) - - infoData := newRtAttr(IFLA_INFO_DATA, b) - wb.AddData(infoData) - - nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1)) - wb.AddData(nameData) - - if err := s.Send(wb); err != nil { - return err - } - return s.HandleAck(wb.Seq) + return nil } From c626349f651a660302f64101055d65dc6e990307 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 10 Feb 2014 15:11:17 -0700 Subject: [PATCH 011/282] Add comment clarifying null termination Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- pkg/netlink/netlink_linux.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 3b21a38d0..85dac51ea 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -731,6 +731,8 @@ func NetworkChangeName(iface *net.Interface, newName string) error { defer syscall.Close(fd) data := [IFNAMSIZ * 2]byte{} + // the "-1"s here are very important for ensuring we get proper null + // termination of our new C strings copy(data[:IFNAMSIZ-1], iface.Name) copy(data[IFNAMSIZ:IFNAMSIZ*2-1], newName) From 2d2c237f50b7954993f6cd1db67c6f8c6d06f881 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 12 Feb 2014 04:09:56 -0800 Subject: [PATCH 012/282] Implement create veth Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/netlink/netlink_linux.go | 84 +++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 23dba0c88..b16dec061 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -142,29 +142,61 @@ func rtaAlignOf(attrlen int) int { type RtAttr struct { syscall.RtAttr - Data []byte + Data []byte + children []*RtAttr + prefix int } func newRtAttr(attrType int, data []byte) *RtAttr { - attr := &RtAttr{} + attr := &RtAttr{ + children: []*RtAttr{}, + } attr.Type = uint16(attrType) attr.Data = data return attr } -func (attr *RtAttr) ToWireFormat() []byte { +func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { + attr := newRtAttr(attrType, data) + parent.children = append(parent.children, attr) + return attr +} + +func (a *RtAttr) length() int { + l := 0 + for _, child := range a.children { + l += child.length() + syscall.SizeofRtAttr + child.prefix + } + if l == 0 { + l++ + } + return rtaAlignOf(l + len(a.Data)) +} + +func (a *RtAttr) ToWireFormat() []byte { native := nativeEndian() - len := syscall.SizeofRtAttr + len(attr.Data) - b := make([]byte, rtaAlignOf(len)) - native.PutUint16(b[0:2], uint16(len)) - native.PutUint16(b[2:4], attr.Type) - for i, d := range attr.Data { - b[4+i] = d + length := a.length() + buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr)) + + if a.Data != nil { + copy(buf[4:], a.Data) + } else { + next := 4 + for _, child := range a.children { + childBuf := child.ToWireFormat() + copy(buf[next+child.prefix:], childBuf) + next += rtaAlignOf(len(childBuf)) + } } - return b + if l := uint16(rtaAlignOf(length)); l != 0 { + native.PutUint16(buf[0:2], l+1) + } + native.PutUint16(buf[2:4], a.Type) + + return buf } type NetlinkRequest struct { @@ -501,12 +533,7 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { } func zeroTerminated(s string) []byte { - bytes := make([]byte, len(s)+1) - for i := 0; i < len(s); i++ { - bytes[i] = s[i] - } - bytes[len(s)] = 0 - return bytes + return []byte(s + "\000") } func nonZeroTerminated(s string) []byte { @@ -697,24 +724,19 @@ func NetworkCreateVethPair(name1, name2 string) error { msg := newIfInfomsg(syscall.AF_UNSPEC) wb.AddData(msg) - kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated("veth")) - info := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) - // wb.AddData(info) - - peerName := newRtAttr(syscall.IFLA_IFNAME, nonZeroTerminated(name2)) - peer := newRtAttr(VETH_PEER, peerName.ToWireFormat()) - // wb.AddData(peer) - - b := []byte{} - b = append(b, peer.ToWireFormat()...) - b = append(b, info.ToWireFormat()...) - - infoData := newRtAttr(IFLA_INFO_DATA, b) - wb.AddData(infoData) - nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name1)) wb.AddData(nameData) + nest1 := newRtAttr(syscall.IFLA_LINKINFO, nil) + newRtAttrChild(nest1, IFLA_INFO_KIND, zeroTerminated("veth")) + nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil) + nest3 := newRtAttrChild(nest2, VETH_PEER, nil) + + last := newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2)) + last.prefix = syscall.SizeofIfInfomsg + + wb.AddData(nest1) + if err := s.Send(wb); err != nil { return err } From 38e5b4e70fb6b93aa2b86e4d625a0032b97b991a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 12 Feb 2014 09:29:06 -0800 Subject: [PATCH 013/282] Simplify code + Allow more generic attr children + remove prefix Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/netlink/netlink.go | 10 ++- pkg/netlink/netlink_linux.go | 131 +++++++++++++++++++---------------- 2 files changed, 79 insertions(+), 62 deletions(-) diff --git a/pkg/netlink/netlink.go b/pkg/netlink/netlink.go index 5098b4b81..5cc756256 100644 --- a/pkg/netlink/netlink.go +++ b/pkg/netlink/netlink.go @@ -5,7 +5,15 @@ // netlink_darwin.go package netlink -import "net" +import ( + "errors" + "net" +) + +var ( + ErrWrongSockType = errors.New("Wrong socket type") + ErrShortResponse = errors.New("Got short response from netlink") +) // A Route is a subnet associated with the interface to reach it. type Route struct { diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 1f48a6425..f8bb6bac3 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -45,6 +45,7 @@ func getIpFamily(ip net.IP) int { } type NetlinkRequestData interface { + Len() int ToWireFormat() []byte } @@ -53,21 +54,24 @@ type IfInfomsg struct { } func newIfInfomsg(family int) *IfInfomsg { - msg := &IfInfomsg{} - msg.Family = uint8(family) - msg.Type = uint16(0) - msg.Index = int32(0) - msg.Flags = uint32(0) - msg.Change = uint32(0) + return &IfInfomsg{ + IfInfomsg: syscall.IfInfomsg{ + Family: uint8(family), + }, + } +} +func newIfInfomsgChild(parent *RtAttr, family int) *IfInfomsg { + msg := newIfInfomsg(family) + parent.children = append(parent.children, msg) return msg } func (msg *IfInfomsg) ToWireFormat() []byte { native := nativeEndian() - len := syscall.SizeofIfInfomsg - b := make([]byte, len) + length := syscall.SizeofIfInfomsg + b := make([]byte, length) b[0] = msg.Family b[1] = 0 native.PutUint16(b[2:4], msg.Type) @@ -77,26 +81,27 @@ func (msg *IfInfomsg) ToWireFormat() []byte { return b } +func (msg *IfInfomsg) Len() int { + return syscall.SizeofIfInfomsg +} + type IfAddrmsg struct { syscall.IfAddrmsg } func newIfAddrmsg(family int) *IfAddrmsg { - msg := &IfAddrmsg{} - msg.Family = uint8(family) - msg.Prefixlen = uint8(0) - msg.Flags = uint8(0) - msg.Scope = uint8(0) - msg.Index = uint32(0) - - return msg + return &IfAddrmsg{ + IfAddrmsg: syscall.IfAddrmsg{ + Family: uint8(family), + }, + } } func (msg *IfAddrmsg) ToWireFormat() []byte { native := nativeEndian() - len := syscall.SizeofIfAddrmsg - b := make([]byte, len) + length := syscall.SizeofIfAddrmsg + b := make([]byte, length) b[0] = msg.Family b[1] = msg.Prefixlen b[2] = msg.Flags @@ -105,26 +110,31 @@ func (msg *IfAddrmsg) ToWireFormat() []byte { return b } +func (msg *IfAddrmsg) Len() int { + return syscall.SizeofIfAddrmsg +} + type RtMsg struct { syscall.RtMsg } func newRtMsg(family int) *RtMsg { - msg := &RtMsg{} - msg.Family = uint8(family) - msg.Table = syscall.RT_TABLE_MAIN - msg.Scope = syscall.RT_SCOPE_UNIVERSE - msg.Protocol = syscall.RTPROT_BOOT - msg.Type = syscall.RTN_UNICAST - - return msg + return &RtMsg{ + RtMsg: syscall.RtMsg{ + Family: uint8(family), + Table: syscall.RT_TABLE_MAIN, + Scope: syscall.RT_SCOPE_UNIVERSE, + Protocol: syscall.RTPROT_BOOT, + Type: syscall.RTN_UNICAST, + }, + } } func (msg *RtMsg) ToWireFormat() []byte { native := nativeEndian() - len := syscall.SizeofRtMsg - b := make([]byte, len) + length := syscall.SizeofRtMsg + b := make([]byte, length) b[0] = msg.Family b[1] = msg.Dst_len b[2] = msg.Src_len @@ -137,6 +147,10 @@ func (msg *RtMsg) ToWireFormat() []byte { return b } +func (msg *RtMsg) Len() int { + return syscall.SizeofRtMsg +} + func rtaAlignOf(attrlen int) int { return (attrlen + syscall.RTA_ALIGNTO - 1) & ^(syscall.RTA_ALIGNTO - 1) } @@ -144,18 +158,17 @@ func rtaAlignOf(attrlen int) int { type RtAttr struct { syscall.RtAttr Data []byte - children []*RtAttr - prefix int + children []NetlinkRequestData } func newRtAttr(attrType int, data []byte) *RtAttr { - attr := &RtAttr{ - children: []*RtAttr{}, + return &RtAttr{ + RtAttr: syscall.RtAttr{ + Type: uint16(attrType), + }, + children: []NetlinkRequestData{}, + Data: data, } - attr.Type = uint16(attrType) - attr.Data = data - - return attr } func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { @@ -164,10 +177,10 @@ func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { return attr } -func (a *RtAttr) length() int { +func (a *RtAttr) Len() int { l := 0 for _, child := range a.children { - l += child.length() + syscall.SizeofRtAttr + child.prefix + l += child.Len() + syscall.SizeofRtAttr } if l == 0 { l++ @@ -178,7 +191,7 @@ func (a *RtAttr) length() int { func (a *RtAttr) ToWireFormat() []byte { native := nativeEndian() - length := a.length() + length := a.Len() buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr)) if a.Data != nil { @@ -187,7 +200,7 @@ func (a *RtAttr) ToWireFormat() []byte { next := 4 for _, child := range a.children { childBuf := child.ToWireFormat() - copy(buf[next+child.prefix:], childBuf) + copy(buf[next:], childBuf) next += rtaAlignOf(len(childBuf)) } } @@ -212,7 +225,7 @@ func (rr *NetlinkRequest) ToWireFormat() []byte { dataBytes := make([][]byte, len(rr.Data)) for i, data := range rr.Data { dataBytes[i] = data.ToWireFormat() - length = length + uint32(len(dataBytes[i])) + length += uint32(len(dataBytes[i])) } b := make([]byte, length) native.PutUint32(b[0:4], length) @@ -221,12 +234,10 @@ func (rr *NetlinkRequest) ToWireFormat() []byte { native.PutUint32(b[8:12], rr.Seq) native.PutUint32(b[12:16], rr.Pid) - i := 16 + next := 16 for _, data := range dataBytes { - for _, dataByte := range data { - b[i] = dataByte - i = i + 1 - } + copy(b[next:], data) + next += len(data) } return b } @@ -238,12 +249,14 @@ func (rr *NetlinkRequest) AddData(data NetlinkRequestData) { } func newNetlinkRequest(proto, flags int) *NetlinkRequest { - rr := &NetlinkRequest{} - rr.Len = uint32(syscall.NLMSG_HDRLEN) - rr.Type = uint16(proto) - rr.Flags = syscall.NLM_F_REQUEST | uint16(flags) - rr.Seq = uint32(getSeq()) - return rr + return &NetlinkRequest{ + NlMsghdr: syscall.NlMsghdr{ + Len: uint32(syscall.NLMSG_HDRLEN), + Type: uint16(proto), + Flags: syscall.NLM_F_REQUEST | uint16(flags), + Seq: uint32(getSeq()), + }, + } } type NetlinkSocket struct { @@ -286,7 +299,7 @@ func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) { return nil, err } if nr < syscall.NLMSG_HDRLEN { - return nil, fmt.Errorf("Got short response from netlink") + return nil, ErrShortResponse } rb = rb[:nr] return syscall.ParseNetlinkMessage(rb) @@ -301,7 +314,7 @@ func (s *NetlinkSocket) GetPid() (uint32, error) { case *syscall.SockaddrNetlink: return v.Pid, nil } - return 0, fmt.Errorf("Wrong socket type") + return 0, ErrWrongSockType } func (s *NetlinkSocket) HandleAck(seq uint32) error { @@ -592,11 +605,7 @@ func zeroTerminated(s string) []byte { } func nonZeroTerminated(s string) []byte { - bytes := make([]byte, len(s)) - for i := 0; i < len(s); i++ { - bytes[i] = s[i] - } - return bytes + return []byte(s) } // Add a new network link of a specified type. This is identical to @@ -789,8 +798,8 @@ func NetworkCreateVethPair(name1, name2 string) error { nest2 := newRtAttrChild(nest1, IFLA_INFO_DATA, nil) nest3 := newRtAttrChild(nest2, VETH_INFO_PEER, nil) - last := newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2)) - last.prefix = syscall.SizeofIfInfomsg + newIfInfomsgChild(nest3, syscall.AF_UNSPEC) + newRtAttrChild(nest3, syscall.IFLA_IFNAME, zeroTerminated(name2)) wb.AddData(nest1) From da3a52746b3ec43049dc3a8ca58b44d0188f043e Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Thu, 13 Feb 2014 11:42:14 -0800 Subject: [PATCH 014/282] hack/RELEASE: add step for updating doc branch Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: google) --- hack/RELEASE-CHECKLIST.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hack/RELEASE-CHECKLIST.md b/hack/RELEASE-CHECKLIST.md index a7ae45f2f..84a0ff70e 100644 --- a/hack/RELEASE-CHECKLIST.md +++ b/hack/RELEASE-CHECKLIST.md @@ -173,9 +173,13 @@ git push origin $VERSION It's very important that we don't make the tag until after the official release is uploaded to get.docker.io! -### 10. Go to github to merge the `bump_$VERSION` into release +### 10. Go to github to merge the `bump_$VERSION` branch into release -Merging the pull request to the release branch will automatically +Don't delete the leftover branch just yet, as we will need it for the next step. + +### 11. Go to github to merge the `bump_$VERSION` branch into docs + +Merging the pull request to the docs branch will automatically update the documentation on the "latest" revision of the docs. You should see the updated docs 5-10 minutes after the merge. The docs will appear on http://docs.docker.io/. For more information about @@ -184,7 +188,7 @@ documentation releases, see `docs/README.md`. Don't forget to push that pretty blue button to delete the leftover branch afterwards! -### 11. Create a new pull request to merge release back into master +### 12. Create a new pull request to merge release back into master ```bash git checkout master @@ -202,7 +206,7 @@ echo "https://github.com/dotcloud/docker/compare/master...merge_release_$VERSION Again, get two maintainers to validate, then merge, then push that pretty blue button to delete your branch. -### 12. Rejoice and Evangelize! +### 13. Rejoice and Evangelize! Congratulations! You're done. From a6182dc62ea335c40961c7e528b38b85d2569d07 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 13 Feb 2014 16:12:21 +1000 Subject: [PATCH 015/282] update the sshd example to use just a Dockerfile Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- .../examples/running_ssh_service.Dockerfile | 17 +++ docs/sources/examples/running_ssh_service.rst | 112 ++++-------------- 2 files changed, 43 insertions(+), 86 deletions(-) create mode 100644 docs/sources/examples/running_ssh_service.Dockerfile diff --git a/docs/sources/examples/running_ssh_service.Dockerfile b/docs/sources/examples/running_ssh_service.Dockerfile new file mode 100644 index 000000000..dd2acb7a4 --- /dev/null +++ b/docs/sources/examples/running_ssh_service.Dockerfile @@ -0,0 +1,17 @@ +# sshd +# +# VERSION 0.0.1 + +FROM ubuntu +MAINTAINER Thatcher R. Peskens "thatcher@dotcloud.com" + +# make sure the package repository is up to date +RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list +RUN apt-get update + +RUN apt-get install -y openssh-server +RUN mkdir /var/run/sshd +RUN echo 'root:screencast' |chpasswd + +EXPOSE 22 +CMD /usr/sbin/sshd -D diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index 52fe1f591..d27799bee 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -1,5 +1,5 @@ :title: Running an SSH service -:description: A screencast of installing and running an sshd service +:description: Installing and running an sshd service :keywords: docker, example, package installation, networking .. _running_ssh_service: @@ -9,101 +9,41 @@ SSH Daemon Service .. include:: example_header.inc +The following Dockerfile sets up an sshd service in a container that you can use +to connect to and inspect other container's volumes, or to get quick access to a +test container. -**Video:** +.. literalinclude:: running_ssh_service.Dockerfile -I've created a little screencast to show how to create an SSHd service -and connect to it. It is something like 11 minutes and not entirely -smooth, but it gives you a good idea. - -.. note:: - This screencast was created before Docker version 0.5.2, so the - daemon is unprotected and available via a TCP port. When you run - through the same steps in a newer version of Docker, you will - need to add ``sudo`` in front of each ``docker`` command in order - to reach the daemon over its protected Unix socket. - -.. raw:: html - - - -You can also get this sshd container by using: +Build the image using: .. code-block:: bash - sudo docker pull dhrp/sshd + $ sudo docker build -rm -t eg_sshd . - -The password is ``screencast``. - -**Video's Transcription:** +Then run it. You can then use ``docker port`` to find out what host port the container's +port 22 is mapped to: .. code-block:: bash - # Hello! We are going to try and install openssh on a container and run it as a service - # let's pull ubuntu to get a base ubuntu image. - $ docker pull ubuntu - # I had it so it was quick - # now let's connect using -i for interactive and with -t for terminal - # we execute /bin/bash to get a prompt. - $ docker run -i -t ubuntu /bin/bash - # yes! we are in! - # now lets install openssh - $ apt-get update - $ apt-get install openssh-server - # ok. lets see if we can run it. - $ which sshd - # we need to create privilege separation directory - $ mkdir /var/run/sshd - $ /usr/sbin/sshd - $ exit - # now let's commit it - # which container was it? - $ docker ps -a |more - $ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd - # I gave the name dhrp/sshd for the container - # now we can run it again - $ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode - # is it running? - $ docker ps - # yes! - # let's stop it - $ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562 - $ docker ps - # and reconnect, but now open a port to it - $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D - $ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22 - # it has now given us a port to connect to - # we have to connect using a public ip of our host - $ hostname - # *ifconfig* is deprecated, better use *ip addr show* now - $ ifconfig - $ ssh root@192.168.33.10 -p 49153 - # Ah! forgot to set root passwd - $ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd - $ docker ps -a - $ docker run -i -t dhrp/sshd /bin/bash - $ passwd - $ exit - $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd - $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D - $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22 - # *ifconfig* is deprecated, better use *ip addr show* now - $ ifconfig - $ ssh root@192.168.33.10 -p 49154 - # Thanks for watching, Thatcher thatcher@dotcloud.com - -Update: -------- + $ sudo docker run -d -P -name test_sshd eg_sshd + $ sudo docker port test_sshd 22 + 0.0.0.0:49154 -For Ubuntu 13.10 using stackbrew/ubuntu, you may need do these additional steps: +And now you can ssh to port ``49154`` on the Docker daemon's host IP address +(``ip address`` or ``ifconfig`` can tell you that): -1. change /etc/pam.d/sshd, pam_loginuid line 'required' to 'optional' -2. echo LANG=\"en_US.UTF-8\" > /etc/default/locale +.. code-block:: bash + $ ssh root@192.168.1.2 -p 49154 + # The password is ``screencast``. + $$ +Finally, clean up after your test by stopping and removing the container, and +then removing the image. + +.. code-block:: bash + + $ sudo docker stop test_sshd + $ sudo docker rm test_sshd + $ sudo docker rmi eg_sshd From 17719cab91e175a7bd11f9852e27638df1202b8b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 14 Feb 2014 12:12:35 -0800 Subject: [PATCH 016/282] Add new functions to unsupported file Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/netlink/netlink_unsupported.go | 42 +++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/pkg/netlink/netlink_unsupported.go b/pkg/netlink/netlink_unsupported.go index cd796b373..bd9e962d3 100644 --- a/pkg/netlink/netlink_unsupported.go +++ b/pkg/netlink/netlink_unsupported.go @@ -3,31 +3,59 @@ package netlink import ( - "fmt" + "errors" "net" ) +var ( + ErrNotImplemented = errors.New("not implemented") +) + func NetworkGetRoutes() ([]Route, error) { - return nil, fmt.Errorf("Not implemented") + return nil, ErrNotImplemented } func NetworkLinkAdd(name string, linkType string) error { - return fmt.Errorf("Not implemented") + return ErrNotImplemented } func NetworkLinkUp(iface *net.Interface) error { - return fmt.Errorf("Not implemented") + return ErrNotImplemented } func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { - return fmt.Errorf("Not implemented") + return ErrNotImplemented } func AddDefaultGw(ip net.IP) error { - return fmt.Errorf("Not implemented") + return ErrNotImplemented } func NetworkSetMTU(iface *net.Interface, mtu int) error { - return fmt.Errorf("Not implemented") + return ErrNotImplemented +} + +func NetworkCreateVethPair(name1, name2 string) error { + return ErrNotImplemented +} + +func NetworkChangeName(iface *net.Interface, newName string) error { + return ErrNotImplemented +} + +func NetworkSetNsFd(iface *net.Interface, fd int) error { + return ErrNotImplemented +} + +func NetworkSetNsPid(iface *net.Interface, nspid int) error { + return ErrNotImplemented +} + +func NetworkSetMaster(iface, master *net.Interface) error { + return ErrNotImplemented +} + +func NetworkLinkDown(iface *net.Interface) error { + return ErrNotImplemented } From 6a5a1d44f5d6c1281a66d562b7a4310dd6c3b997 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 17 Feb 2014 12:24:19 +1000 Subject: [PATCH 017/282] add a little space above h2->h6 - it gets a bit dense in there when there are lots of steps Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/theme/docker/static/css/main.css | 3 +++ docs/theme/docker/static/css/main.less | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/docs/theme/docker/static/css/main.css b/docs/theme/docker/static/css/main.css index 119580154..ce4ba7b86 100755 --- a/docs/theme/docker/static/css/main.css +++ b/docs/theme/docker/static/css/main.css @@ -428,6 +428,9 @@ dt:hover > a.headerlink { float: right; visibility: hidden; } +h2, h3, h4, h5, h6 { + margin-top: 0.7em; +} /* ===================================== Miscellaneous information ====================================== */ diff --git a/docs/theme/docker/static/css/main.less b/docs/theme/docker/static/css/main.less index 8c9296d97..e248e21c0 100644 --- a/docs/theme/docker/static/css/main.less +++ b/docs/theme/docker/static/css/main.less @@ -631,6 +631,10 @@ dt:hover > a.headerlink { visibility: hidden; } +h2, h3, h4, h5, h6 { + margin-top: 0.7em; +} + /* ===================================== Miscellaneous information ====================================== */ From 5f84d7f3146db1ac15974f1e28f77834de3e63ff Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 17 Feb 2014 16:54:36 -0500 Subject: [PATCH 018/282] execdriver flag for docker daemon like the storage-driver flag, this implements a flag for choosing the execdriver to be used, defaulting to lxc. Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- config.go | 2 ++ docker/docker.go | 2 ++ docs/sources/reference/commandline/cli.rst | 1 + runtime.go | 12 +++++++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index dc6e8b554..19aad9ed4 100644 --- a/config.go +++ b/config.go @@ -25,6 +25,7 @@ type DaemonConfig struct { BridgeIP string InterContainerCommunication bool GraphDriver string + ExecDriver string Mtu int DisableNetwork bool } @@ -43,6 +44,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { DefaultIp: net.ParseIP(job.Getenv("DefaultIp")), InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), GraphDriver: job.Getenv("GraphDriver"), + ExecDriver: job.Getenv("ExecDriver"), } if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns diff --git a/docker/docker.go b/docker/docker.go index 02c99b931..51aaf334d 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -39,6 +39,7 @@ func main() { flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") + flExecDriver = flag.String([]string{"e", "-exec-driver"}, "", "Force the docker runtime to use a specific exec driver") flHosts = opts.NewListOpts(api.ValidateHost) flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available") ) @@ -98,6 +99,7 @@ func main() { job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) job.Setenv("GraphDriver", *flGraphDriver) + job.Setenv("ExecDriver", *flExecDriver) job.SetenvInt("Mtu", *flMtu) if err := job.Run(); err != nil { log.Fatal(err) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 7ba012306..e0ec2b99c 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -79,6 +79,7 @@ Commands -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver + -e, --exec-driver="": Force the docker runtime to use a specific exec driver -v, --version=false: Print version information and quit -mtu, --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available diff --git a/runtime.go b/runtime.go index eed28f92a..8e12a4340 100644 --- a/runtime.go +++ b/runtime.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/execdriver/chroot" "github.com/dotcloud/docker/execdriver/lxc" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" @@ -703,7 +704,16 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime sysInfo := sysinfo.New(false) - ed, err := lxc.NewDriver(config.Root, sysInfo.AppArmor) + var ed execdriver.Driver + utils.Debugf("execDriver: provided %s", config.ExecDriver) + if config.ExecDriver == "chroot" && false { + // chroot is presently a noop driver https://github.com/dotcloud/docker/pull/4189#issuecomment-35330655 + ed, err = chroot.NewDriver() + utils.Debugf("execDriver: using chroot") + } else { + ed, err = lxc.NewDriver(config.Root, sysInfo.AppArmor) + utils.Debugf("execDriver: using lxc") + } if err != nil { return nil, err } From 3036b5a196fdbb744f94299c82dc44078361bdae Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 14 Feb 2014 21:02:54 +1000 Subject: [PATCH 019/282] rejig the helloflask example as more advanced, and move it to the end Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/index.rst | 2 +- docs/sources/examples/python_web_app.rst | 113 ++++++++++++----------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index cd08107e7..cf9ed9340 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -16,7 +16,6 @@ to more substantial services like those which you might find in production. :maxdepth: 1 hello_world - python_web_app nodejs_web_app running_redis_service running_ssh_service @@ -26,3 +25,4 @@ to more substantial services like those which you might find in production. running_riak_service using_supervisord cfengine_process_management + python_web_app diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index f31b31b7d..b44c0e1ee 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -9,85 +9,86 @@ Python Web App .. include:: example_header.inc -The goal of this example is to show you how you can author your own -Docker images using a parent image, making changes to it, and then -saving the results as a new image. We will do that by making a simple -hello Flask web application image. +While using Dockerfiles is the prefered way to create maintainable +and repeatable images, its useful to know how you can try things out +and then commit your live changes to an image. + +The goal of this example is to show you how you can modify your own +Docker images by making changes to a running +container, and then saving the results as a new image. We will do +that by making a simple 'hello world' Flask web application image. **Steps:** .. code-block:: bash - sudo docker pull shykes/pybuilder + $ sudo docker pull shykes/pybuilder + +Download the ``shykes/pybuilder`` Docker image from the ``http://index.docker.io`` +registry. Note that this container was built with a very old version of docker +(May 2013), but can still be used now. -We are downloading the ``shykes/pybuilder`` Docker image .. code-block:: bash - URL=http://github.com/shykes/helloflask/archive/master.tar.gz + $ sudo docker run -i -t -name pybuilder_run shykes/pybuilder bash -We set a ``URL`` variable that points to a tarball of a simple helloflask web app + $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz + $$ /usr/local/bin/buildapp $URL + [lots of output later] + $$ exit + + +We then start a new container running interactively using the +image. +First, we set a ``URL`` variable that points to a tarball of a simple +helloflask web app, and then we run a command contained in the image called +``buildapp``, passing it the ``$URL`` variable. The container is +given a name ``pybuilder_run`` which we will use in the next steps. + +While this example is simple, you could run any number of interactive commands, +try things out, and then exit when you're done. .. code-block:: bash - BUILD_JOB=$(sudo docker run -d -t shykes/pybuilder:latest /usr/local/bin/buildapp $URL) - -Inside of the ``shykes/pybuilder`` image there is a command called -``buildapp``, we are running that command and passing the ``$URL`` variable -from step 2 to it, and running the whole thing inside of a new -container. The ``BUILD_JOB`` environment variable will be set with the new container ID. - -.. code-block:: bash - - sudo docker attach -sig-proxy=false $BUILD_JOB - [...] - -While this container is running, we can attach to the new container to -see what is going on. The flag ``--sig-proxy`` set as ``false`` allows you to connect and -disconnect (Ctrl-C) to it without stopping the container. - -.. code-block:: bash - - sudo docker ps -a - -List all Docker containers. If this container has already finished -running, it will still be listed here. - -.. code-block:: bash - - BUILD_IMG=$(sudo docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master) + $ sudo docker commit pybuilder_run /builds/github.com/shykes/helloflask/master + c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9 Save the changes we just made in the container to a new image called -``_/builds/github.com/hykes/helloflask/master`` and save the image ID in -the ``BUILD_IMG`` variable name. +``/builds/github.com/hykes/helloflask/master``. You now have 3 different +ways to refer to the container, name, short-id ``c8b2e8228f11``, or +long-id ``c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9``. .. code-block:: bash - WEB_WORKER=$(sudo docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp) + $ WEB_WORKER=$(sudo docker run -d -p 5000 /builds/github.com/hykes/helloflask/master /usr/local/bin/runapp) + +Use the new image to create a new container with +network port 5000, and return the container ID and store in the +``WEB_WORKER`` variable (rather than naming a container/image, you can use the ID's). - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. - **"-p 5000"** the web app is going to listen on this port, so it must be mapped from the container to the host system. -- **"$BUILD_IMG"** is the image we want to run the command inside of. - **/usr/local/bin/runapp** is the command which starts the web app. -Use the new image we just created and create a new container with -network port 5000, and return the container ID and store in the -``WEB_WORKER`` variable. .. code-block:: bash - sudo docker logs $WEB_WORKER + $ sudo docker logs -f $WEB_WORKER * Running on http://0.0.0.0:5000/ View the logs for the new container using the ``WEB_WORKER`` variable, and if everything worked as planned you should see the line ``Running on http://0.0.0.0:5000/`` in the log output. +To exit the view without stopping the container, hit Ctrl-C, or open another +terminal and continue with the example while watching the result in the logs. + .. code-block:: bash - WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }') + $ WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }') Look up the public-facing port which is NAT-ed. Find the private port used by the container and store it inside of the ``WEB_PORT`` variable. @@ -95,23 +96,25 @@ used by the container and store it inside of the ``WEB_PORT`` variable. .. code-block:: bash # install curl if necessary, then ... - curl http://127.0.0.1:$WEB_PORT - Hello world! + $ curl http://127.0.0.1:$WEB_PORT + Hello world! Access the web app using the ``curl`` binary. If everything worked as planned you should see the line ``Hello world!`` inside of your console. -**Video:** +.. code-block:: bash -See the example in action + $ sudo docker ps --all -.. raw:: html +List ``--all`` the Docker containers. If this container had already finished +running, it will still be listed here with a status of 'Exit 0'. - +.. code-block:: bash + + $ sudo docker stop $WEB_WORKER + $ sudo docker rm $WEB_WORKER pybuilder_run + $ sudo docker rmi /builds/github.com/shykes/helloflask/master shykes/pybuilder:latest + +And now stop the running web worker, and delete the containers, so that we can +then delete the images that we used. -Continue to :ref:`running_ssh_service`. From f535a58959809dbb613bf529019f26cc6ae0c6f9 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 14 Feb 2014 21:27:57 +1000 Subject: [PATCH 020/282] swap busybox for ubuntu, its quicker.. Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/hello_world.rst | 30 +++++++++++++-------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index fd5d6421c..c8ecb0f6c 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -4,9 +4,6 @@ .. _examples: -Hello World ------------ - .. _running_examples: Check your Docker install @@ -18,7 +15,7 @@ your Docker install, run the following command: .. code-block:: bash # Check that you have a working install - docker info + $ sudo docker info If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied`` you may have an incomplete @@ -30,27 +27,28 @@ Please refer to :ref:`installation_list` for installation instructions. .. _hello_world: Hello World -=========== +----------- .. include:: example_header.inc This is the most basic example available for using Docker. -Download the base image which is named ``ubuntu``: +Download the small base image named ``busybox``: .. code-block:: bash - # Download an ubuntu image - sudo docker pull ubuntu + # Download an busybox image + $ sudo docker pull busybox -Alternatively to the ``ubuntu`` image, you can select ``busybox``, a bare -minimal Linux system. The images are retrieved from the Docker -repository. +The ``busybox`` image is a minimal Linux system. You can do the same +with any number of other images, such as ``debian``, ``ubuntu`` or ``centos``. +The images can be found and retrieved using the `Docker index`_. +.. _Docker index: http://index.docker.io .. code-block:: bash - sudo docker run ubuntu /bin/echo hello world + $ sudo docker run busybox /bin/echo hello world This command will run a simple ``echo`` command, that will echo ``hello world`` back to the console over standard out. @@ -58,7 +56,7 @@ This command will run a simple ``echo`` command, that will echo ``hello world`` - **"sudo"** execute the following commands as user *root* - **"docker run"** run a command in a new container -- **"ubuntu"** is the image we want to run the command inside of. +- **"busybox"** is the image we are running the command in. - **"/bin/echo"** is the command we want to run in the container - **"hello world"** is the input for the echo command @@ -82,7 +80,7 @@ See the example in action .. _hello_world_daemon: Hello World Daemon -================== +------------------ .. include:: example_header.inc @@ -172,14 +170,14 @@ See the example in action id="asciicast-2562" async>"> -The next example in the series is a :ref:`python_web_app` example, or +The next example in the series is a :ref:`nodejs_web_app` example, or you could skip to any of the other examples: -* :ref:`python_web_app` * :ref:`nodejs_web_app` * :ref:`running_redis_service` * :ref:`running_ssh_service` * :ref:`running_couchdb_service` * :ref:`postgresql_service` * :ref:`mongodb_image` +* :ref:`python_web_app` From b05602a0fe6d9b9731cee0008fbac27250f361bd Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 14 Feb 2014 22:41:59 +1000 Subject: [PATCH 021/282] update a new movie: http://asciinema.org/a/7658 Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/hello_world.rst | 8 +-- docs/sources/examples/python_web_app.rst | 80 ++++++++++++++++-------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index c8ecb0f6c..63362e7d7 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -2,8 +2,6 @@ :description: A simple hello world example with Docker :keywords: docker, example, hello world -.. _examples: - .. _running_examples: Check your Docker install @@ -37,7 +35,7 @@ Download the small base image named ``busybox``: .. code-block:: bash - # Download an busybox image + # Download a busybox image $ sudo docker pull busybox The ``busybox`` image is a minimal Linux system. You can do the same @@ -71,8 +69,8 @@ See the example in action ---- diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index b44c0e1ee..6992d0f7f 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -9,7 +9,7 @@ Python Web App .. include:: example_header.inc -While using Dockerfiles is the prefered way to create maintainable +While using Dockerfiles is the preffered way to create maintainable and repeatable images, its useful to know how you can try things out and then commit your live changes to an image. @@ -18,29 +18,29 @@ Docker images by making changes to a running container, and then saving the results as a new image. We will do that by making a simple 'hello world' Flask web application image. -**Steps:** +Download the initial image +-------------------------- + +Download the ``shykes/pybuilder`` Docker image from the ``http://index.docker.io`` +registry. + +This image contains a ``buildapp`` script to download the web app and then ``pip install`` +any required modules, and a ``runapp`` script that finds the ``app.py`` and runs it. + +.. _`shykes/pybuilder`: https://github.com/shykes/pybuilder .. code-block:: bash $ sudo docker pull shykes/pybuilder -Download the ``shykes/pybuilder`` Docker image from the ``http://index.docker.io`` -registry. Note that this container was built with a very old version of docker -(May 2013), but can still be used now. +.. note:: This container was built with a very old version of docker + (May 2013 - see `shykes/pybuilder`_ ), when the ``Dockerfile`` format was different, + but the image can still be used now. +Interactively make some modifications +------------------------------------- -.. code-block:: bash - - $ sudo docker run -i -t -name pybuilder_run shykes/pybuilder bash - - $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz - $$ /usr/local/bin/buildapp $URL - [lots of output later] - $$ exit - - -We then start a new container running interactively using the -image. +We then start a new container running interactively using the image. First, we set a ``URL`` variable that points to a tarball of a simple helloflask web app, and then we run a command contained in the image called ``buildapp``, passing it the ``$URL`` variable. The container is @@ -51,8 +51,15 @@ try things out, and then exit when you're done. .. code-block:: bash - $ sudo docker commit pybuilder_run /builds/github.com/shykes/helloflask/master - c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9 + $ sudo docker run -i -t -name pybuilder_run shykes/pybuilder bash + + $$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz + $$ /usr/local/bin/buildapp $URL + [...] + $$ exit + +Commit the container to create a new image +------------------------------------------ Save the changes we just made in the container to a new image called ``/builds/github.com/hykes/helloflask/master``. You now have 3 different @@ -61,12 +68,22 @@ long-id ``c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9``. .. code-block:: bash - $ WEB_WORKER=$(sudo docker run -d -p 5000 /builds/github.com/hykes/helloflask/master /usr/local/bin/runapp) + $ sudo docker commit pybuilder_run /builds/github.com/shykes/helloflask/master + c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9 + + +Run the new image to start the web worker +----------------------------------------- Use the new image to create a new container with network port 5000, and return the container ID and store in the ``WEB_WORKER`` variable (rather than naming a container/image, you can use the ID's). +.. code-block:: bash + + $ WEB_WORKER=$(sudo docker run -d -p 5000 /builds/github.com/hykes/helloflask/master /usr/local/bin/runapp) + + - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. - **"-p 5000"** the web app is going to listen on this port, so it @@ -74,10 +91,8 @@ network port 5000, and return the container ID and store in the - **/usr/local/bin/runapp** is the command which starts the web app. -.. code-block:: bash - - $ sudo docker logs -f $WEB_WORKER - * Running on http://0.0.0.0:5000/ +View the container logs +----------------------- View the logs for the new container using the ``WEB_WORKER`` variable, and if everything worked as planned you should see the line ``Running on @@ -88,19 +103,30 @@ terminal and continue with the example while watching the result in the logs. .. code-block:: bash - $ WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }') + $ sudo docker logs -f $WEB_WORKER + * Running on http://0.0.0.0:5000/ + + +See the webapp output +--------------------- Look up the public-facing port which is NAT-ed. Find the private port used by the container and store it inside of the ``WEB_PORT`` variable. +Access the web app using the ``curl`` binary. If everything worked as planned you +should see the line ``Hello world!`` inside of your console. + .. code-block:: bash + $ WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }') + # install curl if necessary, then ... $ curl http://127.0.0.1:$WEB_PORT Hello world! -Access the web app using the ``curl`` binary. If everything worked as planned you -should see the line ``Hello world!`` inside of your console. + +Clean up example containers and images +-------------------------------------- .. code-block:: bash From 29f7e1bfcbe6654d962a55eb5006e248284b9e0c Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 18 Feb 2014 13:23:18 +1000 Subject: [PATCH 022/282] suggested improvements Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/examples/python_web_app.rst | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 6992d0f7f..5b8e3f6b4 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -9,7 +9,7 @@ Python Web App .. include:: example_header.inc -While using Dockerfiles is the preffered way to create maintainable +While using Dockerfiles is the preferred way to create maintainable and repeatable images, its useful to know how you can try things out and then commit your live changes to an image. @@ -62,8 +62,8 @@ Commit the container to create a new image ------------------------------------------ Save the changes we just made in the container to a new image called -``/builds/github.com/hykes/helloflask/master``. You now have 3 different -ways to refer to the container, name, short-id ``c8b2e8228f11``, or +``/builds/github.com/shykes/helloflask/master``. You now have 3 different +ways to refer to the container: name ``pybuilder_run``, short-id ``c8b2e8228f11``, or long-id ``c8b2e8228f11b8b3e492cbf9a49923ae66496230056d61e07880dc74c5f495f9``. .. code-block:: bash @@ -76,12 +76,11 @@ Run the new image to start the web worker ----------------------------------------- Use the new image to create a new container with -network port 5000, and return the container ID and store in the -``WEB_WORKER`` variable (rather than naming a container/image, you can use the ID's). +network port 5000 mapped to a local port .. code-block:: bash - $ WEB_WORKER=$(sudo docker run -d -p 5000 /builds/github.com/hykes/helloflask/master /usr/local/bin/runapp) + $ sudo docker run -d -p 5000 --name web_worker /builds/github.com/shykes/helloflask/master /usr/local/bin/runapp - **"docker run -d "** run a command in a new container. We pass "-d" @@ -94,7 +93,7 @@ network port 5000, and return the container ID and store in the View the container logs ----------------------- -View the logs for the new container using the ``WEB_WORKER`` variable, and +View the logs for the new ``web_worker`` container and if everything worked as planned you should see the line ``Running on http://0.0.0.0:5000/`` in the log output. @@ -103,7 +102,7 @@ terminal and continue with the example while watching the result in the logs. .. code-block:: bash - $ sudo docker logs -f $WEB_WORKER + $ sudo docker logs -f web_worker * Running on http://0.0.0.0:5000/ @@ -118,7 +117,7 @@ should see the line ``Hello world!`` inside of your console. .. code-block:: bash - $ WEB_PORT=$(sudo docker port $WEB_WORKER 5000 | awk -F: '{ print $2 }') + $ WEB_PORT=$(sudo docker port web_worker 5000 | awk -F: '{ print $2 }') # install curl if necessary, then ... $ curl http://127.0.0.1:$WEB_PORT @@ -137,8 +136,8 @@ running, it will still be listed here with a status of 'Exit 0'. .. code-block:: bash - $ sudo docker stop $WEB_WORKER - $ sudo docker rm $WEB_WORKER pybuilder_run + $ sudo docker stop web_worker + $ sudo docker rm web_worker pybuilder_run $ sudo docker rmi /builds/github.com/shykes/helloflask/master shykes/pybuilder:latest And now stop the running web worker, and delete the containers, so that we can From 940c381dd5898df4dd863a2d76a122b6610f2940 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 18 Dec 2013 21:12:49 +1000 Subject: [PATCH 023/282] my attempt to disentagle repository and registry (Issue #1439) Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- api/client.go | 4 +-- docs/sources/reference/commandline/cli.rst | 2 +- docs/sources/terms/index.rst | 2 ++ docs/sources/terms/registry.rst | 16 ++++++++++ docs/sources/terms/repository.rst | 27 +++++++++++++++++ docs/sources/use/workingwithrepository.rst | 35 +++++++++++++--------- 6 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 docs/sources/terms/registry.rst create mode 100644 docs/sources/terms/repository.rst diff --git a/api/client.go b/api/client.go index 81e337b02..eb345ae40 100644 --- a/api/client.go +++ b/api/client.go @@ -1668,7 +1668,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { type ports []int func (cli *DockerCli) CmdTag(args ...string) error { - cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository") + cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository") force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") if err := cmd.Parse(args); err != nil { return nil @@ -1681,7 +1681,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { var repository, tag string if cmd.NArg() == 3 { - fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REPOSITORY[:TAG]]\n") + fmt.Fprintf(cli.err, "[DEPRECATED] The format 'IMAGE [REPOSITORY [TAG]]' as been deprecated. Please use IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG]]\n") repository, tag = cmd.Arg(1), cmd.Arg(2) } else { repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 7ba012306..927f6d3b2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1301,7 +1301,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe :: - Usage: docker tag [OPTIONS] IMAGE REPOSITORY[:TAG] + Usage: docker tag [OPTIONS] IMAGE [REGISTRYHOST/][USERNAME/]NAME[:TAG] Tag an image into a repository diff --git a/docs/sources/terms/index.rst b/docs/sources/terms/index.rst index 882d83f0d..40851082b 100644 --- a/docs/sources/terms/index.rst +++ b/docs/sources/terms/index.rst @@ -18,5 +18,7 @@ Contents: layer image container + registry + repository diff --git a/docs/sources/terms/registry.rst b/docs/sources/terms/registry.rst new file mode 100644 index 000000000..90c3ee721 --- /dev/null +++ b/docs/sources/terms/registry.rst @@ -0,0 +1,16 @@ +:title: Registry +:description: Definition of an Registry +:keywords: containers, lxc, concepts, explanation, image, repository, container + +.. _registry_def: + +Registry +========== + +A Registry is a hosted service containing :ref:`repositories` +of :ref:`images` which responds to the Registry API. + +The default registry can be accessed using a browser at http://images.docker.io +or using the ``sudo docker search`` command. + +For more information see :ref:`Working with Repositories` diff --git a/docs/sources/terms/repository.rst b/docs/sources/terms/repository.rst new file mode 100644 index 000000000..cceaeec23 --- /dev/null +++ b/docs/sources/terms/repository.rst @@ -0,0 +1,27 @@ +:title: Repository +:description: Definition of an Repository +:keywords: containers, lxc, concepts, explanation, image, repository, container + +.. _repository_def: + +Repository +========== + +A repository is a tagged set of images either on your local docker server, or +shared, by pushing it to a :ref:`Registry` server. + +Images can be labeld into a repository using ``sudo docker build -t LABEL``, +``sudo docker commit CONTAINERID LABEL`` or ``sudo docker tag IMAGEID LABEL``. + +The label can be made up of 3 parts: + +[registry_hostname[:port]/][user_name/]( repository_name[:version_tag] | image_id ) +[REGISTRYHOST/][USERNAME/]NAME[:TAG] + +TAG defaults to ``latest``, USERNAME and REGISTRYHOST default to an empty string. +When REGISTRYHOST is an empty string, then ``docker push`` will push to ``index.docker.io:80``. + +If you create a new repository which you want to share, you will need to set the +first part, as the 'default' blank REPOSITORY prefix is reserved for official Docker images. + +For more information see :ref:`Working with Repositories` diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 38062556c..36f9c590c 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -7,9 +7,9 @@ Share Images via Repositories ============================= -A *repository* is a hosted collection of tagged :ref:`images -` that together create the file system for a container. The -repository's name is a tag that indicates the provenance of the +A *repository* is a shareable collection of tagged :ref:`images` +that together create the file systems for containers. The +repository's name is a label that indicates the provenance of the repository, i.e. who created it and where the original copy is located. @@ -19,7 +19,7 @@ tag. The implicit registry is located at ``index.docker.io``, the home of "top-level" repositories and the Central Index. This registry may also include public "user" repositories. -So Docker is not only a tool for creating and managing your own +Docker is not only a tool for creating and managing your own :ref:`containers ` -- **Docker is also a tool for sharing**. The Docker project provides a Central Registry to host public repositories, namespaced by user, and a Central Index which @@ -28,6 +28,12 @@ repositories. You can host your own Registry too! Docker acts as a client for these services via ``docker search, pull, login`` and ``push``. +Local Repositories +------------------ + +Docker images which have been created and labled on your local docker server +need to be pushed to a Public or Private registry to be shared. + .. _using_public_repositories: Public Repositories @@ -136,13 +142,13 @@ name for the image. .. _image_push: -Pushing an image to its repository ----------------------------------- +Pushing a repository to its registry +------------------------------------ -In order to push an image to its repository you need to have committed -your container to a named image (see above) +In order to push an repository to its registry you need to have named an image, +or committed your container to a named image (see above) -Now you can commit this image to the repository designated by its name +Now you can push this repository to the registry designated by its name or tag. .. code-block:: bash @@ -187,14 +193,15 @@ manage it by committing code to your GitHub repository. You can create multiple Trusted Builds per repository and configure them to point to specific ``Dockerfile``'s or Git branches. -Private Repositories --------------------- +Private Registry +---------------- -Right now (version 0.6), private repositories are only possible by -hosting `your own registry +Private registries and private shared repositories are +only possible by hosting `your own registry `_. To push or pull to a repository on your own registry, you must prefix the tag with the -address of the registry's host, like this: +address of the registry's host (a ``.`` or ``:`` is used to identify a host), +like this: .. code-block:: bash From a049e93a4c164de25ce1fc1113f2929aa0aa0b5b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 18 Dec 2013 21:36:02 +1000 Subject: [PATCH 024/282] try out @metalivedev's suggestion, including FQIN Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/terms/repository.rst | 23 ++++++++++++---------- docs/sources/use/workingwithrepository.rst | 10 +++++----- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/sources/terms/repository.rst b/docs/sources/terms/repository.rst index cceaeec23..e4fe4b8fd 100644 --- a/docs/sources/terms/repository.rst +++ b/docs/sources/terms/repository.rst @@ -7,21 +7,24 @@ Repository ========== -A repository is a tagged set of images either on your local docker server, or +A repository is a set of images either on your local Docker server, or shared, by pushing it to a :ref:`Registry` server. -Images can be labeld into a repository using ``sudo docker build -t LABEL``, -``sudo docker commit CONTAINERID LABEL`` or ``sudo docker tag IMAGEID LABEL``. +Images can be associated with a repository (or multiple) by giving them an image name +using one of three different commands: -The label can be made up of 3 parts: +1. At build time (e.g. ``sudo docker build -t IMAGENAME``), +2. When committing a container (e.g. ``sudo docker commit CONTAINERID IMAGENAME``) or +3. When tagging an image id with an image name (e.g. ``sudo docker tag IMAGEID IMAGENAME``). -[registry_hostname[:port]/][user_name/]( repository_name[:version_tag] | image_id ) -[REGISTRYHOST/][USERNAME/]NAME[:TAG] +A `Fully Qualified Image Name` (FQIN) can be made up of 3 parts: -TAG defaults to ``latest``, USERNAME and REGISTRYHOST default to an empty string. -When REGISTRYHOST is an empty string, then ``docker push`` will push to ``index.docker.io:80``. +``[registry_hostname[:port]/][user_name/](repository_name[:version_tag])`` -If you create a new repository which you want to share, you will need to set the -first part, as the 'default' blank REPOSITORY prefix is reserved for official Docker images. +``version_tag`` defaults to ``latest``, ``username`` and ``registry_hostname`` default to an empty string. +When ``registry_hostname`` is an empty string, then ``docker push`` will push to ``index.docker.io:80``. + +If you create a new repository which you want to share, you will need to set at least the +``user_name``, as the 'default' blank ``user_name`` prefix is reserved for official Docker images. For more information see :ref:`Working with Repositories` diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 36f9c590c..cbde932cd 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -31,7 +31,7 @@ client for these services via ``docker search, pull, login`` and Local Repositories ------------------ -Docker images which have been created and labled on your local docker server +Docker images which have been created and labeled on your local Docker server need to be pushed to a Public or Private registry to be shared. .. _using_public_repositories: @@ -64,8 +64,8 @@ Find Public Images on the Central Index --------------------------------------- You can search the Central Index `online `_ -or by the CLI. Searching can find images by name, user name or -description: +or using the command line interface. Searching can find images by name, user +name or description: .. code-block:: bash @@ -162,7 +162,7 @@ Trusted Builds -------------- Trusted Builds automate the building and updating of images from GitHub, directly -on docker.io servers. It works by adding a commit hook to your selected repository, +on ``docker.io`` servers. It works by adding a commit hook to your selected repository, triggering a build and update when you push a commit. To setup a trusted build @@ -186,7 +186,7 @@ If you want to see the status of your Trusted Builds you can go to your `Trusted Builds page `_ on the Docker index, and it will show you the status of your builds, and the build history. -Once you've created a Trusted Build you can deactive or delete it. You cannot +Once you've created a Trusted Build you can deactivate or delete it. You cannot however push to a Trusted Build with the ``docker push`` command. You can only manage it by committing code to your GitHub repository. From a7f1b74dd812fbc86beefea6fcd3ba4c4abaa7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChnle?= Date: Fri, 14 Feb 2014 14:23:10 +0100 Subject: [PATCH 025/282] Env Clarification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarified that the env variables here are an example output of env. The user should not set them to the example values. Docker-DCO-1.1-Signed-off-by: Matthias Kühnle (github: float64) --- docs/sources/examples/running_redis_service.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index 9687f0cfa..a07250c35 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -68,13 +68,14 @@ Once inside our freshly created container we need to install Redis to get the service redis-server stop Now we can test the connection. Firstly, let's look at the available environmental -variables in our web application container. We can use these to get the IP and port +variables in our web application container that docker has setup for us. We can use these to get the IP and port of our ``redis`` container. .. code-block:: bash - env - . . . + env | grep DB_ + + # Should return something similar to this with your values DB_NAME=/violet_wolf/db DB_PORT_6379_TCP_PORT=6379 DB_PORT=tcp://172.17.0.33:6379 From 8a3cb0c65e7940e928e31d6e64cce5d81d9975a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChnle?= Date: Fri, 14 Feb 2014 14:29:09 +0100 Subject: [PATCH 026/282] Fixed misspelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Matthias Kühnle (github: float64) --- docs/sources/examples/running_redis_service.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index a07250c35..c679547b7 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -68,7 +68,7 @@ Once inside our freshly created container we need to install Redis to get the service redis-server stop Now we can test the connection. Firstly, let's look at the available environmental -variables in our web application container that docker has setup for us. We can use these to get the IP and port +variables in our web application container that Docker has setup for us. We can use these to get the IP and port of our ``redis`` container. .. code-block:: bash From d73d3b03e4216bf6cf44b393108dd520acac9670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChnle?= Date: Tue, 18 Feb 2014 12:36:14 +0100 Subject: [PATCH 027/282] Reworded description of the environment variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the "test now..." b/c the code example is only about inspection of env-variables. Test follows in the next section. Docker-DCO-1.1-Signed-off-by: Matthias Kühnle (github: float64) --- docs/sources/examples/running_redis_service.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index c679547b7..3b2f65c12 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -67,9 +67,8 @@ Once inside our freshly created container we need to install Redis to get the apt-get -y install redis-server service redis-server stop -Now we can test the connection. Firstly, let's look at the available environmental -variables in our web application container that Docker has setup for us. We can use these to get the IP and port -of our ``redis`` container. +Docker sets some environment variables in our web application container. +Let's inspect them with the ``env`` command. .. code-block:: bash From c1dfc63845cdf08d7609e1890f45d01f10326bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChnle?= Date: Tue, 18 Feb 2014 12:47:44 +0100 Subject: [PATCH 028/282] Updated environment description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .. to make it even more clear that this env variables are there because of the --link option. Docker-DCO-1.1-Signed-off-by: Matthias Kühnle (github: float64) --- docs/sources/examples/running_redis_service.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index 3b2f65c12..7cbc58b58 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -67,7 +67,8 @@ Once inside our freshly created container we need to install Redis to get the apt-get -y install redis-server service redis-server stop -Docker sets some environment variables in our web application container. +Since we used the ``--link redis:db`` option before. Docker has created some +environment variables in our web application container. Let's inspect them with the ``env`` command. .. code-block:: bash From 311cb5ca45c15c32408d54194470536e73ae016d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=BChnle?= Date: Tue, 18 Feb 2014 16:00:21 +0100 Subject: [PATCH 029/282] Reword - one sentence :-) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Matthias Kühnle (github: float64) --- docs/sources/examples/running_redis_service.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index 7cbc58b58..c9424867a 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -67,9 +67,8 @@ Once inside our freshly created container we need to install Redis to get the apt-get -y install redis-server service redis-server stop -Since we used the ``--link redis:db`` option before. Docker has created some -environment variables in our web application container. -Let's inspect them with the ``env`` command. +As we've used the ``--link redis:db`` option, Docker has created some environment +variables in our web application container. .. code-block:: bash From 349adf4d4df63cd35ecfd74aa21de01629964ea5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 18 Feb 2014 15:08:09 -0500 Subject: [PATCH 030/282] Add extra info regarding docker run funcationality Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- docs/sources/reference/commandline/cli.rst | 3 +++ docs/sources/use/basics.rst | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 927f6d3b2..8ef0ab8b7 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -917,6 +917,8 @@ Running ``docker ps`` showing 2 linked containers. The last container is marked as a ``Ghost`` container. It is a container that was running when the docker daemon was restarted (upgraded, or ``-H`` settings changed). The container is still running, but as this docker daemon process is not able to manage it, you can't attach to it. To bring them out of ``Ghost`` Status, you need to use ``docker kill`` or ``docker restart``. +``docker ps`` will show only running containers by default. To see all containers: ``docker ps -a`` + .. _cli_pull: ``pull`` @@ -1085,6 +1087,7 @@ The ``docker run`` command first ``creates`` a writeable container layer over the specified image, and then ``starts`` it using the specified command. That is, ``docker run`` is equivalent to the API ``/containers/create`` then ``/containers/(id)/start``. +Once the container is stopped it still exists and can be started back up. See ``docker ps -a`` to view a list of all containers. The ``docker run`` command can be used in combination with ``docker commit`` to :ref:`change the command that a container runs `. diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index d62f778b9..75907efa3 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -50,6 +50,7 @@ Running an interactive shell # allocate a tty, attach stdin and stdout # To detach the tty without exiting the shell, # use the escape sequence Ctrl-p + Ctrl-q + # note: This will continue to exist in a stopped state once exited (see "docker ps -a") sudo docker run -i -t ubuntu /bin/bash .. _bind_docker: From 97ed268a8281231cdab88b166e2fe3faf7ccb296 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Feb 2014 16:01:04 -0800 Subject: [PATCH 031/282] Change version to v0.8.1 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 6f4eebdf6..d182dc916 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.8.1 +0.8.1-dev From e2da84ee63b759173d4919481eeb0c3ddf58cfb5 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 19 Feb 2014 11:35:58 +1000 Subject: [PATCH 032/282] we moved the repo Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/mac.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index efb999ee1..75e788b59 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -39,7 +39,7 @@ boot2docker ``docker`` daemon. It also takes care of the installation for the OS image that is used for the job. -.. _GitHub page: https://github.com/steeve/boot2docker +.. _GitHub page: https://github.com/boot2docker/boot2docker Open up a new terminal window, if you have not already. @@ -51,7 +51,7 @@ Run the following commands to get boot2docker: cd ~/bin # Get the file - curl https://raw.github.com/steeve/boot2docker/master/boot2docker > boot2docker + curl https://raw.github.com/boot2docker/boot2docker/master/boot2docker > boot2docker # Mark it executable chmod +x boot2docker @@ -153,7 +153,7 @@ boot2docker: See the GitHub page for `boot2docker`_. -.. _boot2docker: https://github.com/steeve/boot2docker +.. _boot2docker: https://github.com/boot2docker/boot2docker If SSH complains about keys: ---------------------------- From 6dc2f5513648c42b2e71e55c4a4ea6cd3073ce55 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 18 Feb 2014 21:45:48 -0500 Subject: [PATCH 033/282] Add basics for controlling a container's lifecycle Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- docs/sources/use/basics.rst | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 75907efa3..24c22bba3 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -122,12 +122,38 @@ Starting a long-running worker process sudo docker kill $JOB -Listing all running containers ------------------------------- +Listing containers +------------------ .. code-block:: bash - sudo docker ps + sudo docker ps # Lists only running containers + sudo docker ps -a # Lists all containers + + +Controlling containers +---------------------- +.. code-block:: bash + + # Start a new container + JOB=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") + + # Stop the container + docker stop $JOB + + # Start the container + docker start $JOB + + # Restart the container + docker restart $JOB + + # SIGKILL a container + docker kill $JOB + + # Remove a container + docker stop $JOB # Container must be stopped to remove it + docker rm $JOB + Bind a service on a TCP port ------------------------------ From 05c692d64ec9cabe6c16385300405406ebe63f40 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 18 Feb 2014 19:21:12 -0800 Subject: [PATCH 034/282] Update the documentation docs with new branch information. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/README.md | 63 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3fdbad2ea..71b3a0999 100644 --- a/docs/README.md +++ b/docs/README.md @@ -19,10 +19,24 @@ post-commit hooks. The "release" branch maps to the "latest" documentation and the "master" branch maps to the "master" documentation. -**Warning**: The "master" documentation may include features not yet -part of any official docker release. "Master" docs should be used only -for understanding bleeding-edge development and "latest" should be -used for the latest official release. +## Branches + +**There are two branches related to editing docs**: ``master`` and a +``doc*`` branch (currently ``doc0.8.1``). You should normally edit +docs on the ``master`` branch. That way your fixes will automatically +get included in later releases, and docs maintainers can easily +cherry-pick your changes to bring over to the current docs branch. In +the rare case where your change is not forward-compatible, then you +could base your change on the appropriate ``doc*`` branch. + +Now that we have a ``doc*`` branch, we can keep the ``latest`` docs +up to date with any bugs found between ``docker`` code releases. + +**Warning**: When *reading* the docs, the ``master`` documentation may +include features not yet part of any official docker +release. ``Master`` docs should be used only for understanding +bleeding-edge development and ``latest`` (which points to the ``doc*`` +branch``) should be used for the latest official release. If you need to manually trigger a build of an existing branch, then you can do that through the [readthedocs @@ -39,7 +53,7 @@ Getting Started To edit and test the docs, you'll need to install the Sphinx tool and its dependencies. There are two main ways to install this tool: -###Native Installation +### Native Installation Install dependencies from `requirements.txt` file in your `docker/docs` directory: @@ -48,7 +62,7 @@ directory: * Mac OS X: `[sudo] pip-2.7 install -r docs/requirements.txt` -###Alternative Installation: Docker Container +### Alternative Installation: Docker Container If you're running ``docker`` on your development machine then you may find it easier and cleaner to use the docs Dockerfile. This installs Sphinx @@ -59,11 +73,16 @@ docs inside the container, even starting a simple HTTP server on port In the ``docker`` source directory, run: ```make docs``` -This is the equivalent to ``make clean server`` since each container starts clean. +This is the equivalent to ``make clean server`` since each container +starts clean. -Usage ------ -* Follow the contribution guidelines (``../CONTRIBUTING.md``) +# Contributing + +## Normal Case: + +* Follow the contribution guidelines ([see + ``../CONTRIBUTING.md``](../CONTRIBUTING)). +* Remember to sign your work! * Work in your own fork of the code, we accept pull requests. * Change the ``.rst`` files with your favorite editor -- try to keep the lines short and respect RST and Sphinx conventions. @@ -75,6 +94,20 @@ Usage ``make clean docs`` must complete without any warnings or errors. +## Special Case for RST Newbies: + +If you want to write a new doc or make substantial changes to an +existing doc, but **you don't know RST syntax**, we will accept pull +requests in Markdown and plain text formats. We really want to +encourage people to share their knowledge and don't want the markup +syntax to be the obstacle. So when you make the Pull Request, please +note in your comment that you need RST markup assistance, and we'll +make the changes for you, and then we will make a pull request to your +pull request so that you can get all the changes and learn about the +markup. You still need to follow the +[``CONTRIBUTING``](../CONTRIBUTING) guidelines, so please sign your +commits. + Working using GitHub's file editor ---------------------------------- @@ -93,8 +126,11 @@ exists. Notes ----- -* For the template the css is compiled from less. When changes are needed they can be compiled using -lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` + +* For the template the css is compiled from less. When changes are + needed they can be compiled using + + lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` Guides on using sphinx ---------------------- @@ -106,7 +142,8 @@ Guides on using sphinx Hello world =========== - This is.. (etc.) + This is a reference to :ref:`hello_world` and will work even if we + move the target to another file or change the title of the section. ``` The ``_hello_world:`` will make it possible to link to this position From 8a289a8a54480b968a6924536e084d749e567cf0 Mon Sep 17 00:00:00 2001 From: Thomas Schroeter Date: Wed, 19 Feb 2014 10:50:11 +0000 Subject: [PATCH 035/282] Correct port range in OS X installation doc Docker-DCO-1.1-Signed-off-by: Thomas Schroeter (github: thschroeter) --- docs/sources/installation/mac.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 75e788b59..5139324d0 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -126,7 +126,7 @@ with our containers as if they were running locally: .. code-block:: bash # vm must be powered off - for i in {4900..49900}; do + for i in {49000..49900}; do VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port$i,tcp,,$i,,$i"; VBoxManage modifyvm "boot2docker-vm" --natpf1 "udp-port$i,udp,,$i,,$i"; done From b3d5e9527a050a0c7252f0125a8a26a6fc5175f1 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Mon, 10 Feb 2014 23:21:20 +0000 Subject: [PATCH 036/282] Package coverage when running integration tests If coverpkg is missing on `go test` command, only the current package will be covered. That's the case of unit tests. For integration tests we need to explicitly declare each package. Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- hack/make.sh | 15 +++++++++++++-- hack/make/test | 13 +------------ hack/make/test-integration | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index c0092b106..59bd71602 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -16,7 +16,7 @@ set -e # in the Dockerfile at the root of the source. In other words: # DO NOT CALL THIS SCRIPT DIRECTLY. # - The right way to call this script is to invoke "make" from -# your checkout of the Docker repository. +# your checkout of the Docker repository. # the Makefile will do a "docker build -t docker ." and then # "docker run hack/make.sh" in the resulting container image. # @@ -101,13 +101,14 @@ fi # go_test_dir() { dir=$1 + coverpkg=$2 testcover=() if [ "$HAVE_GO_TEST_COVER" ]; then # if our current go install has -cover, we want to use it :) mkdir -p "$DEST/coverprofiles" coverprofile="docker${dir#.}" coverprofile="$DEST/coverprofiles/${coverprofile//\//-}" - testcover=( -cover -coverprofile "$coverprofile" ) + testcover=( -cover -coverprofile "$coverprofile" $coverpkg ) fi ( set -x @@ -116,6 +117,16 @@ go_test_dir() { ) } +# This helper function walks the current directory looking for directories +# holding certain files ($1 parameter), and prints their paths on standard +# output, one per line. +find_dirs() { + find -not \( \ + \( -wholename './vendor' -o -wholename './integration' -o -wholename './contrib' -o -wholename './pkg/mflag/example' \) \ + -prune \ + \) -name "$1" -print0 | xargs -0n1 dirname | sort -u +} + bundle() { bundlescript=$1 bundle=$(basename $bundlescript) diff --git a/hack/make/test b/hack/make/test index 760c5a5fc..68b4c5220 100644 --- a/hack/make/test +++ b/hack/make/test @@ -19,7 +19,7 @@ bundle_test() { date TESTS_FAILED=() - for test_dir in $(find_test_dirs); do + for test_dir in $(find_dirs '*_test.go'); do echo if ! LDFLAGS="$LDFLAGS $LDFLAGS_STATIC" go_test_dir "$test_dir"; then @@ -48,15 +48,4 @@ bundle_test() { } 2>&1 | tee $DEST/test.log } - -# This helper function walks the current directory looking for directories -# holding Go test files, and prints their paths on standard output, one per -# line. -find_test_dirs() { - find -not \( \ - \( -wholename './vendor' -o -wholename './integration' \) \ - -prune \ - \) -name '*_test.go' -print0 | xargs -0n1 dirname | sort -u -} - bundle_test diff --git a/hack/make/test-integration b/hack/make/test-integration index f1ab0b99c..93d63ad59 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -5,7 +5,8 @@ DEST=$1 set -e bundle_test_integration() { - LDFLAGS="$LDFLAGS $LDFLAGS_STATIC" go_test_dir ./integration + LDFLAGS="$LDFLAGS $LDFLAGS_STATIC" go_test_dir ./integration \ + "-coverpkg $(find_dirs '*.go' | sed 's,^\.,github.com/dotcloud/docker,g' | paste -d, -s)" } bundle_test_integration 2>&1 | tee $DEST/test.log From 196702fbf9905426e590c0dbcb6101e72c62aac6 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 18 Feb 2014 19:25:43 -0800 Subject: [PATCH 037/282] update links Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 71b3a0999..9379d8687 100644 --- a/docs/README.md +++ b/docs/README.md @@ -81,8 +81,8 @@ starts clean. ## Normal Case: * Follow the contribution guidelines ([see - ``../CONTRIBUTING.md``](../CONTRIBUTING)). -* Remember to sign your work! + ``../CONTRIBUTING.md``](../CONTRIBUTING.md)). +* [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work) * Work in your own fork of the code, we accept pull requests. * Change the ``.rst`` files with your favorite editor -- try to keep the lines short and respect RST and Sphinx conventions. @@ -115,6 +115,7 @@ Alternatively, for small changes and typos you might want to use GitHub's built in file editor. It allows you to preview your changes right online (though there can be some differences between GitHub markdown and Sphinx RST). Just be careful not to create many commits. +And you must still [sign your work!](../CONTRIBUTING.md#sign-your-work) Images ------ From 6073b450e83a67a27ffe8c11ebda36791a7923b4 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 19 Feb 2014 08:53:31 -0500 Subject: [PATCH 038/282] FIX: Linux mint troubleshooting tip Package is called 'cgroup-lite' not 'cgroups-lite'. Verified on Linux Mint 16. Ref http://packages.ubuntu.com/search?keywords=cgroup-lite Docker-DCO-1.1-Signed-off-by: Dan Williams (github: deedubs) --- docs/sources/installation/ubuntulinux.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index f37be90d7..dc8832a98 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -220,12 +220,12 @@ To install the latest version of docker, use the standard ``apt-get`` method: Troubleshooting ^^^^^^^^^^^^^^^ -On Linux Mint, the ``cgroups-lite`` package is not installed by default. +On Linux Mint, the ``cgroup-lite`` package is not installed by default. Before Docker will work correctly, you will need to install this via: .. code-block:: bash - sudo apt-get update && sudo apt-get install cgroups-lite + sudo apt-get update && sudo apt-get install cgroup-lite .. _ufw: From 73a277eb2fd66b815b11e9d2caff2c31c18a0854 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 20 Feb 2014 01:11:04 +0200 Subject: [PATCH 039/282] don't skip cert check for an example & use HTTPS This changes two URLs from http to https and it fixes a Dockerfile to stop skipping certificate validation. It also adds the ca-certificates package to that Dockerfile example. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- contrib/mkimage-arch.sh | 2 +- docs/sources/examples/cfengine_process_management.rst | 4 ++-- docs/sources/reference/api/docker_remote_api.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index d178a1df3..73a4173b1 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -39,7 +39,7 @@ arch-chroot $ROOTFS /bin/sh -c "haveged -w 1024; pacman-key --init; pkill havege arch-chroot $ROOTFS /bin/sh -c "ln -s /usr/share/zoneinfo/UTC /etc/localtime" echo 'en_US.UTF-8 UTF-8' > $ROOTFS/etc/locale.gen arch-chroot $ROOTFS locale-gen -arch-chroot $ROOTFS /bin/sh -c 'echo "Server = http://mirrors.kernel.org/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist' +arch-chroot $ROOTFS /bin/sh -c 'echo "Server = https://mirrors.kernel.org/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist' # udev doesn't work in containers, rebuild /dev DEV=$ROOTFS/dev diff --git a/docs/sources/examples/cfengine_process_management.rst b/docs/sources/examples/cfengine_process_management.rst index 9d7681f55..7ca2c3549 100644 --- a/docs/sources/examples/cfengine_process_management.rst +++ b/docs/sources/examples/cfengine_process_management.rst @@ -55,7 +55,7 @@ The first two steps can be done as part of a Dockerfile, as follows. FROM ubuntu MAINTAINER Eystein Måløy Stenberg - RUN apt-get -y install wget lsb-release unzip + RUN apt-get -y install wget lsb-release unzip ca-certificates # install latest CFEngine RUN wget -qO- http://cfengine.com/pub/gpg.key | apt-key add - @@ -64,7 +64,7 @@ The first two steps can be done as part of a Dockerfile, as follows. RUN apt-get install cfengine-community # install cfe-docker process management policy - RUN wget --no-check-certificate https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/ + RUN wget https://github.com/estenberg/cfe-docker/archive/master.zip -P /tmp/ && unzip /tmp/master.zip -d /tmp/ RUN cp /tmp/cfe-docker-master/cfengine/bin/* /var/cfengine/bin/ RUN cp /tmp/cfe-docker-master/cfengine/inputs/* /var/cfengine/inputs/ RUN rm -rf /tmp/cfe-docker-master /tmp/master.zip diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index f7cd7faf4..69bbf71ec 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -2,7 +2,7 @@ :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation -.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to +.. COMMENT use https://pythonhosted.org/sphinxcontrib-httpdomain/ to .. document the REST API. ================= From 42b97be441f5272ead365c3400b91fb756565b1a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 19 Feb 2014 16:53:15 -0700 Subject: [PATCH 040/282] Add back the Ubuntu "Memory and Swap Accounting" section that was lost when the "Kernel" page was culled Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/installation/ubuntulinux.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index f37be90d7..55ea03652 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -217,6 +217,26 @@ To install the latest version of docker, use the standard ``apt-get`` method: # install the latest sudo apt-get install lxc-docker +Memory and Swap Accounting +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If want to enable memory and swap accounting, you must add the following +command-line parameters to your kernel:: + + cgroup_enable=memory swapaccount=1 + +On systems using GRUB (which is the default for Ubuntu), you can add those +parameters by editing ``/etc/default/grub`` and extending +``GRUB_CMDLINE_LINUX``. Look for the following line:: + + GRUB_CMDLINE_LINUX="" + +And replace it by the following one:: + + GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" + +Then run ``update-grub``, and reboot. + Troubleshooting ^^^^^^^^^^^^^^^ From 9f1c69ca7b1b2afd96b4d154431395ee48ada97d Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 20 Feb 2014 13:49:15 +1000 Subject: [PATCH 041/282] lets talk about the other way to make base images Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/articles/baseimages.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/baseimages.rst b/docs/sources/articles/baseimages.rst index 6fd1823f8..61c8f7d9c 100644 --- a/docs/sources/articles/baseimages.rst +++ b/docs/sources/articles/baseimages.rst @@ -13,8 +13,8 @@ The specific process will depend heavily on the Linux distribution you want to package. We have some examples below, and you are encouraged to submit pull requests to contribute new ones. -Getting Started -............... +Create a full image using tar +............................. In general, you'll want to start with a working machine that is running the distribution you'd like to package as a base image, though @@ -44,3 +44,22 @@ Docker GitHub Repo: `_ * `Debian / Ubuntu `_ + + +Creating a simple base image using ``scratch`` +.............................................. + +There is a special repository in the Docker registry called ``scratch``, which +was created using an empty tar file:: + + $ tar cv --files-from /dev/null | docker import - scratch + +which you can ``docker pull``. You can then use that image to base your new +minimal containers ``FROM``:: + + FROM scratch + ADD true-asm /true + CMD ["/true"] + +The Dockerfile above is from extremely minimal image - +`tianon/true `_. From d19e998e7a99d24122da3e98d9886e3b7b4b3501 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 14 Feb 2014 14:24:37 +0100 Subject: [PATCH 042/282] Archive: Add Add Lgetxattr and Lsetxattr implementations Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/stat_linux.go | 53 +++++++++++++++++++++++++++++++++++++ archive/stat_unsupported.go | 8 ++++++ 2 files changed, 61 insertions(+) diff --git a/archive/stat_linux.go b/archive/stat_linux.go index f87a99c55..2910ce5ba 100644 --- a/archive/stat_linux.go +++ b/archive/stat_linux.go @@ -37,3 +37,56 @@ func UtimesNano(path string, ts []syscall.Timespec) error { } return nil } + +// Returns a nil slice and nil error if the xattr is not set +func Lgetxattr(path string, attr string) ([]byte, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return nil, err + } + attrBytes, err := syscall.BytePtrFromString(attr) + if err != nil { + return nil, err + } + + dest := make([]byte, 128) + destBytes := unsafe.Pointer(&dest[0]) + sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) + if errno == syscall.ENODATA { + return nil, nil + } + if errno == syscall.ERANGE { + dest = make([]byte, sz) + destBytes := unsafe.Pointer(&dest[0]) + sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) + } + if errno != 0 { + return nil, errno + } + + return dest[:sz], nil +} + +var _zero uintptr + +func Lsetxattr(path string, attr string, data []byte, flags int) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + attrBytes, err := syscall.BytePtrFromString(attr) + if err != nil { + return err + } + var dataBytes unsafe.Pointer + if len(data) > 0 { + dataBytes = unsafe.Pointer(&data[0]) + } else { + dataBytes = unsafe.Pointer(&_zero) + } + _, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0) + if errno != 0 { + return errno + } + return nil +} diff --git a/archive/stat_unsupported.go b/archive/stat_unsupported.go index 50ca46186..99f1fbc7d 100644 --- a/archive/stat_unsupported.go +++ b/archive/stat_unsupported.go @@ -19,3 +19,11 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { func UtimesNano(path string, ts []syscall.Timespec) error { return ErrNotImplemented } + +func Lgetxattr(path string, attr string) ([]byte, error) { + return nil, ErrNotImplemented +} + +func Lsetxattr(path string, attr string, data []byte, flags int) error { + return ErrNotImplemented +} From c8428d77fdde41786aa5c0c4e64e0e762f873676 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 15 Jan 2014 15:05:30 +0100 Subject: [PATCH 043/282] archive: extract xattrs from tarfiles Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/archive/archive.go b/archive/archive.go index 16c01993b..206580fa0 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -251,6 +251,12 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader) e return err } + for key, value := range hdr.Xattrs { + if err := Lsetxattr(path, key, []byte(value), 0); err != nil { + return err + } + } + // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if hdr.Typeflag != tar.TypeSymlink { From 87ca750cdc3114a340af1c5bc9394cc5f6242677 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 17 Jan 2014 10:51:59 +0100 Subject: [PATCH 044/282] archive: Detect file changes to capability bits Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/changes.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/archive/changes.go b/archive/changes.go index b46b13bbe..a9eba8196 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -1,6 +1,7 @@ package archive import ( + "bytes" "code.google.com/p/go/src/pkg/archive/tar" "fmt" "github.com/dotcloud/docker/utils" @@ -126,10 +127,11 @@ func Changes(layers []string, rw string) ([]Change, error) { } type FileInfo struct { - parent *FileInfo - name string - stat syscall.Stat_t - children map[string]*FileInfo + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo + capability []byte } func (root *FileInfo) LookUp(path string) *FileInfo { @@ -200,7 +202,8 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || - !sameFsTimeSpec(getLastModification(oldStat), getLastModification(newStat)) { + !sameFsTimeSpec(getLastModification(oldStat), getLastModification(newStat)) || + bytes.Compare(oldChild.capability, newChild.capability) != 0 { change := Change{ Path: newChild.path(), Kind: ChangeModify, @@ -275,6 +278,8 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return err } + info.capability, _ = Lgetxattr(path, "security.capability") + parent.children[info.name] = info return nil From 3b9953903b12eaca76655311bd44533768f6f3da Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 20 Jan 2014 12:26:08 +0100 Subject: [PATCH 045/282] archive: Handle capabilities in tar files If a file has a security.capability set, we push this to the tar file. This is important to handle in e.g. layer files or when copying files to containers, as some distros (e.g. Fedora) use capability bits as a more finegrained version of setuid bits, and thus if the capabilites are stripped (and setuid is not set) the binaries will fail to work. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/archive/archive.go b/archive/archive.go index 206580fa0..8bb0a8ed1 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -165,6 +165,13 @@ func addTarFile(path, name string, tw *tar.Writer) error { hdr.Devmajor = int64(major(uint64(stat.Rdev))) hdr.Devminor = int64(minor(uint64(stat.Rdev))) } + + } + + capability, _ := Lgetxattr(path, "security.capability") + if capability != nil { + hdr.Xattrs = make(map[string]string) + hdr.Xattrs["security.capability"] = string(capability) } if err := tw.WriteHeader(hdr); err != nil { From ef826621585a62d9b563e8ab2fd72f731b14817c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 20 Feb 2014 08:51:39 -0500 Subject: [PATCH 046/282] Site ver for Webmaster tools Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/theme/docker/layout.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index a96655604..7d78fb9c3 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -3,6 +3,7 @@ + {{ meta['title'] if meta and meta['title'] else title }} - Docker Documentation From 88d24df6687d64bf0e19c23973da32eed32fda28 Mon Sep 17 00:00:00 2001 From: Adrian Mouat Date: Thu, 20 Feb 2014 16:55:07 +0100 Subject: [PATCH 047/282] Fix misspelling Docker-DCO-1.1-Signed-off-by: Adrian Mouat (github: amouat) --- docs/sources/use/working_with_volumes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 40e282c98..8ddd4196c 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -80,7 +80,7 @@ similar to :ref:`ambassador_pattern_linking `. If you remove containers that mount volumes, including the initial DATA container, or the middleman, the volumes will not be deleted until there are no containers still -referencing those volumes. This allows you to upgrade, or effectivly migrate data volumes +referencing those volumes. This allows you to upgrade, or effectively migrate data volumes between containers. Mount a Host Directory as a Container Volume: From 937ea1d3805771b0e5763f336f9d64e5996c2c95 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Thu, 20 Feb 2014 11:18:27 -0600 Subject: [PATCH 048/282] Don't call applyVolumesFrom on containers with volumes already configured (closes #2973) Docker-DCO-1.1-Signed-off-by: Nolan Darilek (github: ndarilek) --- volumes.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/volumes.go b/volumes.go index a53ef722e..9f76e3698 100644 --- a/volumes.go +++ b/volumes.go @@ -23,11 +23,11 @@ func prepareVolumesForContainer(container *Container) error { if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) + if err := applyVolumesFrom(container); err != nil { + return err + } } - if err := applyVolumesFrom(container); err != nil { - return err - } if err := createVolumes(container); err != nil { return err } From cae554b32e8c3341767e0efe7b9a8977a499d826 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 20 Feb 2014 19:43:00 +0200 Subject: [PATCH 049/282] docs: explain DNS warnings & how to fix them This explains how to fix the DNS warnings on Ubuntu and why they're shown. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- docs/sources/installation/ubuntulinux.rst | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index f37be90d7..0fd24e999 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -261,6 +261,64 @@ incoming connections on the Docker port (default 4243): .. _installmirrors: +Docker and local DNS server warnings +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Systems which are running Ubuntu or an Ubuntu derivative on the desktop will +use `127.0.0.1` as the default nameserver in `/etc/resolv.conf`. NetworkManager +sets up dnsmasq to use the real DNS servers of the connection and sets up +`nameserver 127.0.0.1` in `/etc/resolv.conf`. + +When starting containers on these desktop machines, users will see a warning: + +.. code-block:: bash + + WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4] + +This warning is shown because the containers can't use the local DNS nameserver +and Docker will default to using an external nameserver. + +This can be worked around by specifying a DNS server to be used by the Docker +daemon for the containers: + +.. code-block:: bash + + sudo nano /etc/default/docker + --- + # Add: + DOCKER_OPTS="-dns 8.8.8.8" + # 8.8.8.8 could be replaced with a local DNS server, such as 192.168.1.1 + # multiple DNS servers can be specified: -dns 8.8.8.8 -dns 192.168.1.1 + +The Docker daemon has to be restarted: + +.. code-block:: bash + + sudo restart docker + +.. warning:: If you're doing this on a laptop which connects to various networks, make sure to choose a public DNS server. + +An alternative solution involves disabling dnsmasq in NetworkManager by +following these steps: + +.. code-block:: bash + + sudo nano /etc/NetworkManager/NetworkManager.conf + ---- + # Change: + dns=dnsmasq + # to + #dns=dnsmasq + +NetworkManager and Docker need to be restarted afterwards: + +.. code-block:: bash + + sudo restart network-manager + sudo restart docker + +.. warning:: This might make DNS resolution slower on some networks. + Mirrors ^^^^^^^ From b829e96cde4f798bc21f05a763b2f6a623dfe1eb Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 20 Feb 2014 17:16:45 +0200 Subject: [PATCH 050/282] forbid chained onbuild, from & maintainer triggers This changes the way onbuild works: - forbids the chaining of onbuild instructions - forbids the use of `onbuild from` - forbids the use of `onbuild maintainer` It also makes docker throw errors when encountering such triggers when executing the triggers during `FROM`. Three tests have been added: - ensure that chained onbuild (`onbuild onbuild`) is forbidden - ensure that `onbuild from` is forbidden - ensure that `onbuild maintainer` is forbidden Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- buildfile.go | 16 ++++++++++++ docs/sources/reference/builder.rst | 2 ++ integration/buildfile_test.go | 42 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/buildfile.go b/buildfile.go index c7181b914..b30a220dd 100644 --- a/buildfile.go +++ b/buildfile.go @@ -117,6 +117,14 @@ func (b *buildFile) CmdFrom(name string) error { fmt.Fprintf(b.errStream, "# Executing %d build triggers\n", nTriggers) } for n, step := range b.config.OnBuild { + splitStep := strings.Split(step, " ") + stepInstruction := strings.ToUpper(strings.Trim(splitStep[0], " ")) + switch stepInstruction { + case "ONBUILD": + return fmt.Errorf("Source image contains forbidden chained `ONBUILD ONBUILD` trigger: %s", step) + case "MAINTAINER", "FROM": + return fmt.Errorf("Source image contains forbidden %s trigger: %s", stepInstruction, step) + } if err := b.BuildStep(fmt.Sprintf("onbuild-%d", n), step); err != nil { return err } @@ -128,6 +136,14 @@ func (b *buildFile) CmdFrom(name string) error { // The ONBUILD command declares a build instruction to be executed in any future build // using the current image as a base. func (b *buildFile) CmdOnbuild(trigger string) error { + splitTrigger := strings.Split(trigger, " ") + triggerInstruction := strings.ToUpper(strings.Trim(splitTrigger[0], " ")) + switch triggerInstruction { + case "ONBUILD": + return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") + case "MAINTAINER", "FROM": + return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) + } b.config.OnBuild = append(b.config.OnBuild, trigger) return b.commit("", b.config.Cmd, fmt.Sprintf("ONBUILD %s", trigger)) } diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 4b6a15100..6fabd6629 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -466,6 +466,8 @@ For example you might add something like this: ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...] +.. warning:: Chaining ONBUILD instructions using `ONBUILD ONBUILD` isn't allowed. +.. warning:: ONBUILD may not trigger FROM or MAINTAINER instructions. .. _dockerfile_examples: diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 805932b57..efab9707e 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -924,3 +924,45 @@ func TestBuildOnBuildTrigger(t *testing.T) { } // FIXME: test that the 'foobar' file was created in the final build. } + +func TestBuildOnBuildForbiddenChainedTrigger(t *testing.T) { + _, err := buildImage(testContextTemplate{` + from {IMAGE} + onbuild onbuild run echo test + `, + nil, nil, + }, + t, nil, true, + ) + if err == nil { + t.Fatal("Error should not be nil") + } +} + +func TestBuildOnBuildForbiddenFromTrigger(t *testing.T) { + _, err := buildImage(testContextTemplate{` + from {IMAGE} + onbuild from {IMAGE} + `, + nil, nil, + }, + t, nil, true, + ) + if err == nil { + t.Fatal("Error should not be nil") + } +} + +func TestBuildOnBuildForbiddenMaintainerTrigger(t *testing.T) { + _, err := buildImage(testContextTemplate{` + from {IMAGE} + onbuild maintainer test + `, + nil, nil, + }, + t, nil, true, + ) + if err == nil { + t.Fatal("Error should not be nil") + } +} From 90b0cce07b4f68d8099903f7e1470f79541f45d0 Mon Sep 17 00:00:00 2001 From: Jake Moshenko Date: Thu, 20 Feb 2014 17:57:58 -0500 Subject: [PATCH 051/282] Fix registry auth by storing the string passed on the command line, and allowing for credential selection by normalizing on hostname. Also, remove remote ping calls from CmdPush and CmdPull. Docker-DCO-1.1-Signed-off-by: Jake Moshenko (github: jakedt) --- api/client.go | 26 ++++++++--------- auth/auth.go | 59 ++++++++++++++++----------------------- auth/auth_test.go | 7 +++++ registry/registry.go | 9 ++---- registry/registry_test.go | 2 +- server.go | 14 ++++++++-- 6 files changed, 60 insertions(+), 57 deletions(-) diff --git a/api/client.go b/api/client.go index eb345ae40..f421c6366 100644 --- a/api/client.go +++ b/api/client.go @@ -977,13 +977,13 @@ func (cli *DockerCli) CmdPush(args ...string) error { cli.LoadConfigFile() - // Resolve the Repository name from fqn to endpoint + name - endpoint, _, err := registry.ResolveRepositoryName(name) + // Resolve the Repository name from fqn to hostname + name + hostname, _, err := registry.ResolveRepositoryName(name) if err != nil { return err } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(endpoint) + authConfig := cli.configFile.ResolveAuthConfig(hostname) // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also @@ -1014,10 +1014,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { if err := push(authConfig); err != nil { if strings.Contains(err.Error(), "Status 401") { fmt.Fprintln(cli.out, "\nPlease login prior to push:") - if err := cli.CmdLogin(endpoint); err != nil { + if err := cli.CmdLogin(hostname); err != nil { return err } - authConfig := cli.configFile.ResolveAuthConfig(endpoint) + authConfig := cli.configFile.ResolveAuthConfig(hostname) return push(authConfig) } return err @@ -1042,8 +1042,8 @@ func (cli *DockerCli) CmdPull(args ...string) error { *tag = parsedTag } - // Resolve the Repository name from fqn to endpoint + name - endpoint, _, err := registry.ResolveRepositoryName(remote) + // Resolve the Repository name from fqn to hostname + name + hostname, _, err := registry.ResolveRepositoryName(remote) if err != nil { return err } @@ -1051,7 +1051,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { cli.LoadConfigFile() // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(endpoint) + authConfig := cli.configFile.ResolveAuthConfig(hostname) v := url.Values{} v.Set("fromImage", remote) v.Set("tag", *tag) @@ -1073,10 +1073,10 @@ func (cli *DockerCli) CmdPull(args ...string) error { if err := pull(authConfig); err != nil { if strings.Contains(err.Error(), "Status 401") { fmt.Fprintln(cli.out, "\nPlease login prior to pull:") - if err := cli.CmdLogin(endpoint); err != nil { + if err := cli.CmdLogin(hostname); err != nil { return err } - authConfig := cli.configFile.ResolveAuthConfig(endpoint) + authConfig := cli.configFile.ResolveAuthConfig(hostname) return pull(authConfig) } return err @@ -1753,8 +1753,8 @@ func (cli *DockerCli) CmdRun(args ...string) error { v.Set("fromImage", repos) v.Set("tag", tag) - // Resolve the Repository name from fqn to endpoint + name - endpoint, _, err := registry.ResolveRepositoryName(repos) + // Resolve the Repository name from fqn to hostname + name + hostname, _, err := registry.ResolveRepositoryName(repos) if err != nil { return err } @@ -1763,7 +1763,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { cli.LoadConfigFile() // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(endpoint) + authConfig := cli.configFile.ResolveAuthConfig(hostname) buf, err := json.Marshal(authConfig) if err != nil { return err diff --git a/auth/auth.go b/auth/auth.go index cbca81f3e..4417dd0f7 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -252,50 +252,39 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e } // this method matches a auth configuration to a server address or a url -func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig { - if registry == IndexServerAddress() || len(registry) == 0 { +func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig { + if hostname == IndexServerAddress() || len(hostname) == 0 { // default to the index server return config.Configs[IndexServerAddress()] } - // if it's not the index server there are three cases: - // - // 1. a full config url -> it should be used as is - // 2. a full url, but with the wrong protocol - // 3. a hostname, with an optional port - // - // as there is only one auth entry which is fully qualified we need to start - // parsing and matching - swapProtocol := func(url string) string { - if strings.HasPrefix(url, "http:") { - return strings.Replace(url, "http:", "https:", 1) - } - if strings.HasPrefix(url, "https:") { - return strings.Replace(url, "https:", "http:", 1) - } - return url + // First try the happy case + if c, found := config.Configs[hostname]; found { + return c } - resolveIgnoringProtocol := func(url string) AuthConfig { - if c, found := config.Configs[url]; found { - return c + convertToHostname := func(url string) string { + stripped := url + if strings.HasPrefix(url, "http://") { + stripped = strings.Replace(url, "http://", "", 1) + } else if strings.HasPrefix(url, "https://") { + stripped = strings.Replace(url, "https://", "", 1) } - registrySwappedProtocol := swapProtocol(url) - // now try to match with the different protocol - if c, found := config.Configs[registrySwappedProtocol]; found { - return c - } - return AuthConfig{} + + nameParts := strings.SplitN(stripped, "/", 2) + + return nameParts[0] } - // match both protocols as it could also be a server name like httpfoo - if strings.HasPrefix(registry, "http:") || strings.HasPrefix(registry, "https:") { - return resolveIgnoringProtocol(registry) + // Maybe they have a legacy config file, we will iterate the keys converting + // them to the new format and testing + normalizedHostename := convertToHostname(hostname) + for registry, config := range config.Configs { + if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename { + return config + } } - url := "https://" + registry - if !strings.Contains(registry, "/") { - url = url + "/v1/" - } - return resolveIgnoringProtocol(url) + // When all else fails, return an empty auth config + return AuthConfig{} } diff --git a/auth/auth_test.go b/auth/auth_test.go index 5f2d3b85f..233507260 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -108,6 +108,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { } configFile.Configs["https://registry.example.com/v1/"] = registryAuth configFile.Configs["http://localhost:8000/v1/"] = localAuth + configFile.Configs["registry.com"] = registryAuth validRegistries := map[string][]string{ "https://registry.example.com/v1/": { @@ -122,6 +123,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { "localhost:8000", "localhost:8000/v1/", }, + "registry.com": { + "https://registry.com/v1/", + "http://registry.com/v1/", + "registry.com", + "registry.com/v1/", + }, } for configKey, registries := range validRegistries { diff --git a/registry/registry.go b/registry/registry.go index df9430230..37e107fad 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -91,7 +91,7 @@ func validateRepositoryName(repositoryName string) error { return nil } -// Resolves a repository name to a endpoint + name +// Resolves a repository name to a hostname + name func ResolveRepositoryName(reposName string) (string, string, error) { if strings.Contains(reposName, "://") { // It cannot contain a scheme! @@ -117,11 +117,8 @@ func ResolveRepositoryName(reposName string) (string, string, error) { if err := validateRepositoryName(reposName); err != nil { return "", "", err } - endpoint, err := ExpandAndVerifyRegistryUrl(hostname) - if err != nil { - return "", "", err - } - return endpoint, reposName, err + + return hostname, reposName, nil } // this method expands the registry name as used in the prefix of a repo diff --git a/registry/registry_test.go b/registry/registry_test.go index 16bc431e5..5e398f993 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -145,7 +145,7 @@ func TestResolveRepositoryName(t *testing.T) { if err != nil { t.Fatal(err) } - assertEqual(t, ep, "http://"+u+"/v1/", "Expected endpoint to be "+u) + assertEqual(t, ep, u, "Expected endpoint to be "+u) assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase") } diff --git a/server.go b/server.go index 0ed96fee3..7555830c0 100644 --- a/server.go +++ b/server.go @@ -1336,7 +1336,12 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name - endpoint, remoteName, err := registry.ResolveRepositoryName(localName) + hostname, remoteName, err := registry.ResolveRepositoryName(localName) + if err != nil { + return job.Error(err) + } + + endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) if err != nil { return job.Error(err) } @@ -1538,7 +1543,12 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { defer srv.poolRemove("push", localName) // Resolve the Repository name from fqn to endpoint + name - endpoint, remoteName, err := registry.ResolveRepositoryName(localName) + hostname, remoteName, err := registry.ResolveRepositoryName(localName) + if err != nil { + return job.Error(err) + } + + endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) if err != nil { return job.Error(err) } From 7dcbae485cd46ad4b8af1ea758dd1d2c69f11fbe Mon Sep 17 00:00:00 2001 From: jakedt Date: Thu, 20 Feb 2014 19:22:13 -0500 Subject: [PATCH 052/282] Fix build command auth by adding the auth config and registry objects to the build job environment. Docker-DCO-1.1-Signed-off-by: Jake Moshenko (github: jakedt) --- api/api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/api.go b/api/api.go index 8d9bae978..fe965ddaf 100644 --- a/api/api.go +++ b/api/api.go @@ -912,6 +912,8 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht job.Setenv("q", r.FormValue("q")) job.Setenv("nocache", r.FormValue("nocache")) job.Setenv("rm", r.FormValue("rm")) + job.SetenvJson("authConfig", authConfig) + job.SetenvJson("configFile", configFile) if err := job.Run(); err != nil { if !job.Stdout.Used() { From e59485e148f5d1ab0cf5ec18ca3bb7c684e8cf7b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 21 Feb 2014 13:51:08 +1000 Subject: [PATCH 053/282] reinforce that docker build --rm is a good default to use Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 8ef0ab8b7..41cdfe7cb 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -197,6 +197,8 @@ is given as ``URL``, then no context is set. When a Git repository is set as ``URL``, then the repository is used as the context. Git repositories are cloned with their submodules (`git clone --recursive`). +.. note:: ``docker build --rm`` does not affect the image cache which is used to accelerate builds, it only removes the duplicate writeable container layers. + .. _cli_build_examples: .. seealso:: :ref:`dockerbuilder`. @@ -206,7 +208,7 @@ Examples: .. code-block:: bash - $ sudo docker build . + $ sudo docker build --rm . Uploading context 10240 bytes Step 1 : FROM busybox Pulling repository busybox @@ -229,6 +231,9 @@ Examples: ---> Running in 02071fceb21b ---> f52f38b7823e Successfully built f52f38b7823e + Removing intermediate container 9c9e81692ae9 + Removing intermediate container 02071fceb21b + This example specifies that the ``PATH`` is ``.``, and so all the files in the local directory get tar'd and sent to the Docker daemon. The ``PATH`` @@ -243,6 +248,10 @@ The transfer of context from the local machine to the Docker daemon is what the ``docker`` client means when you see the "Uploading context" message. +The ``--rm`` option tells Docker to remove the intermediate containers and +layers that were used to create each image layer. Doing so has no impact on +the image build cache. + .. code-block:: bash From 060da572d20dfeee4fe02ce6b975a6cb33e50dbe Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 20 Feb 2014 23:42:31 -0700 Subject: [PATCH 054/282] Add newlines to the JSON stream functions This makes the JSON streams a _lot_ easier to parse in less well-baked JSON parsers, and no less so in better ones. Line-based JSON streams are very, very common, where simply chunk-based is not very common at all. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- utils/streamformatter.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/utils/streamformatter.go b/utils/streamformatter.go index 9345c3cb1..8876fa5cb 100644 --- a/utils/streamformatter.go +++ b/utils/streamformatter.go @@ -14,6 +14,10 @@ func NewStreamFormatter(json bool) *StreamFormatter { return &StreamFormatter{json, false} } +const streamNewline = "\r\n" + +var streamNewlineBytes = []byte(streamNewline) + func (sf *StreamFormatter) FormatStream(str string) []byte { sf.used = true if sf.json { @@ -21,7 +25,7 @@ func (sf *StreamFormatter) FormatStream(str string) []byte { if err != nil { return sf.FormatError(err) } - return b + return append(b, streamNewlineBytes...) } return []byte(str + "\r") } @@ -34,9 +38,9 @@ func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []b if err != nil { return sf.FormatError(err) } - return b + return append(b, streamNewlineBytes...) } - return []byte(str + "\r\n") + return []byte(str + streamNewline) } func (sf *StreamFormatter) FormatError(err error) []byte { @@ -47,11 +51,11 @@ func (sf *StreamFormatter) FormatError(err error) []byte { jsonError = &JSONError{Message: err.Error()} } if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { - return b + return append(b, streamNewlineBytes...) } - return []byte("{\"error\":\"format error\"}") + return []byte("{\"error\":\"format error\"}" + streamNewline) } - return []byte("Error: " + err.Error() + "\r\n") + return []byte("Error: " + err.Error() + streamNewline) } func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgress) []byte { From f83e553452db74b73e9de7fa770646e72e7af88f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 21 Feb 2014 17:42:02 +1000 Subject: [PATCH 055/282] small update to API docs to go with #4276 Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/api/docker_remote_api_v1.9.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 47cdb46b2..a7372d5dc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -993,12 +993,12 @@ Search images 2.3 Misc -------- -Build an image from Dockerfile via stdin -**************************************** +Build an image from Dockerfile +****************************** .. http:post:: /build - Build an image from Dockerfile via stdin + Build an image from Dockerfile using a POST body. **Example request**: @@ -1032,6 +1032,7 @@ Build an image from Dockerfile via stdin :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success :query q: suppress verbose build output :query nocache: do not use the cache when building the image + :query rm: Remove intermediate containers after a successful build :reqheader Content-type: should be set to ``"application/tar"``. :reqheader X-Registry-Config: base64-encoded ConfigFile object :statuscode 200: no error From 0db53bd2ecba097c6ace1a1088e11458e139390a Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Fri, 21 Feb 2014 03:02:06 -0700 Subject: [PATCH 056/282] Add MTU to lxc conf to make host and container MTU match If you are using jumbo frames the host side of the veth was being set to 1500. Docker would set the MTU of the container side of the veth to 9001. This would lead to a situation in which the two sides of the veth had different MTU sizes causing issues in network traffic. Docker-DCO-1.1-Signed-off-by: Darren Shepherd (github: ibuildthecloud) --- execdriver/lxc/lxc_template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index 639780f5d..1181396a1 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -12,6 +12,7 @@ const LxcTemplate = ` lxc.network.type = veth lxc.network.link = {{.Network.Bridge}} lxc.network.name = eth0 +lxc.network.mtu = {{.Network.Mtu}} {{else}} # network is disabled (-n=false) lxc.network.type = empty From 8c4160fe61151f38f471e93e7bf98dcbf5406a9e Mon Sep 17 00:00:00 2001 From: Danny Berger Date: Fri, 21 Feb 2014 10:51:51 -0500 Subject: [PATCH 057/282] Fix cli argument usage typo for docker --mtu Docker-DCO-1.1-Signed-off-by: Danny Berger (github: dpb587) --- docker/docker.go | 2 +- docs/sources/reference/commandline/cli.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 02c99b931..9e93b7176 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -40,7 +40,7 @@ func main() { flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") flHosts = opts.NewListOpts(api.ValidateHost) - flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available") + flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 41cdfe7cb..d5267cd2d 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -80,7 +80,7 @@ Commands -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver -v, --version=false: Print version information and quit - -mtu, --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available + -mtu, --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. From c3faab3955a6cfb5b36249889269dd8d272e9e8b Mon Sep 17 00:00:00 2001 From: Manuel Woelker Date: Fri, 21 Feb 2014 19:25:49 +0100 Subject: [PATCH 058/282] docs: document some JSON parameters /containers/create and /containers/(id)/start in remote api (fixes #2948) Docker-DCO-1.1-Signed-off-by: Manuel Woelker (github: manuel-woelker) --- .../reference/api/docker_remote_api_v1.8.rst | 17 +++++++++++++++-- .../reference/api/docker_remote_api_v1.9.rst | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.rst b/docs/sources/reference/api/docker_remote_api_v1.8.rst index 5033f3421..b752f2f8a 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.8.rst @@ -118,6 +118,7 @@ Create a container "User":"", "Memory":0, "MemorySwap":0, + "CpuShares":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, @@ -153,7 +154,15 @@ Create a container "Warnings":[] } - :jsonparam config: the container's configuration + :jsonparam Hostname: Container host name + :jsonparam User: Username or UID + :jsonparam Memory: Memory Limit in bytes + :jsonparam CpuShares: CPU shares (relative weight) + :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false + :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false + :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false + :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false + :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. :statuscode 201: no error :statuscode 404: no such container @@ -394,7 +403,11 @@ Start a container HTTP/1.1 204 No Content Content-Type: text/plain - :jsonparam hostConfig: the container's host configuration (optional) + :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. + :jsonparam LxcConf: Map of custom lxc options + :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag + :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false + :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 47cdb46b2..3ea476153 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -118,6 +118,7 @@ Create a container "User":"", "Memory":0, "MemorySwap":0, + "CpuShares":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, @@ -153,7 +154,15 @@ Create a container "Warnings":[] } - :jsonparam config: the container's configuration + :jsonparam Hostname: Container host name + :jsonparam User: Username or UID + :jsonparam Memory: Memory Limit in bytes + :jsonparam CpuShares: CPU shares (relative weight) + :jsonparam AttachStdin: 1/True/true or 0/False/false, attach to standard input. Default false + :jsonparam AttachStdout: 1/True/true or 0/False/false, attach to standard output. Default false + :jsonparam AttachStderr: 1/True/true or 0/False/false, attach to standard error. Default false + :jsonparam Tty: 1/True/true or 0/False/false, allocate a pseudo-tty. Default false + :jsonparam OpenStdin: 1/True/true or 0/False/false, keep stdin open even if not attached. Default false :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. :statuscode 201: no error :statuscode 404: no such container @@ -394,7 +403,11 @@ Start a container HTTP/1.1 204 No Content Content-Type: text/plain - :jsonparam hostConfig: the container's host configuration (optional) + :jsonparam Binds: Create a bind mount to a directory or file with [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. + :jsonparam LxcConf: Map of custom lxc options + :jsonparam PortBindings: Expose ports from the container, optionally publishing them via the HostPort flag + :jsonparam PublishAllPorts: 1/True/true or 0/False/false, publish all exposed ports to the host interfaces. Default false + :jsonparam Privileged: 1/True/true or 0/False/false, give extended privileges to this container. Default false :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error From 41b1f93bf72e4c012a159a013697090efdcc183b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 21 Feb 2014 11:26:04 -0700 Subject: [PATCH 059/282] Add simple "grep" to hide the harmless "warning: no packages being tested depend on ..." in "go test -coverpkg ..." output Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/test-integration | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hack/make/test-integration b/hack/make/test-integration index 93d63ad59..fa8b8be71 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -9,4 +9,8 @@ bundle_test_integration() { "-coverpkg $(find_dirs '*.go' | sed 's,^\.,github.com/dotcloud/docker,g' | paste -d, -s)" } -bundle_test_integration 2>&1 | tee $DEST/test.log +# this "grep" hides some really irritating warnings that "go test -coverpkg" +# spews when it is given packages that aren't used +bundle_test_integration 2>&1 \ + | grep -v '^warning: no packages being tested depend on ' \ + | tee $DEST/test.log From afb565e97c79b87d4563e8e728513ca5b50514c7 Mon Sep 17 00:00:00 2001 From: Danny Berger Date: Fri, 21 Feb 2014 13:43:03 -0500 Subject: [PATCH 060/282] Drop -mtu from docs in favor of --mtu only Docker-DCO-1.1-Signed-off-by: Danny Berger (github: dpb587) --- docs/sources/reference/commandline/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d5267cd2d..67794a41c 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -80,7 +80,7 @@ Commands -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver -v, --version=false: Print version information and quit - -mtu, --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available + --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if no default route is available The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. From 8c783c1c1336d8f2d1b08b9cbd8e2298d066750c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 11:47:53 -0800 Subject: [PATCH 061/282] Move console into execdriver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 93 +++++----------------------- execdriver/console.go | 137 ++++++++++++++++++++++++++++++++++++++++++ utils/utils.go | 2 +- utils/utils_test.go | 2 +- 4 files changed, 155 insertions(+), 79 deletions(-) create mode 100644 execdriver/console.go diff --git a/container.go b/container.go index 9c7fc8ffd..f38876ed2 100644 --- a/container.go +++ b/container.go @@ -10,10 +10,8 @@ import ( "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" - "github.com/kr/pty" "io" "io/ioutil" "log" @@ -57,11 +55,11 @@ type Container struct { Driver string command *execdriver.Command + console execdriver.Console stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser stdinPipe io.WriteCloser - ptyMaster io.Closer runtime *Runtime @@ -213,56 +211,6 @@ func (container *Container) generateEnvConfig(env []string) error { return nil } -func (container *Container) setupPty() error { - ptyMaster, ptySlave, err := pty.Open() - if err != nil { - return err - } - container.ptyMaster = ptyMaster - container.command.Stdout = ptySlave - container.command.Stderr = ptySlave - container.command.Console = ptySlave.Name() - - // Copy the PTYs to our broadcasters - go func() { - defer container.stdout.CloseWriters() - utils.Debugf("startPty: begin of stdout pipe") - io.Copy(container.stdout, ptyMaster) - utils.Debugf("startPty: end of stdout pipe") - }() - - // stdin - if container.Config.OpenStdin { - container.command.Stdin = ptySlave - container.command.SysProcAttr.Setctty = true - go func() { - defer container.stdin.Close() - utils.Debugf("startPty: begin of stdin pipe") - io.Copy(ptyMaster, container.stdin) - utils.Debugf("startPty: end of stdin pipe") - }() - } - return nil -} - -func (container *Container) setupStd() error { - container.command.Stdout = container.stdout - container.command.Stderr = container.stderr - if container.Config.OpenStdin { - stdin, err := container.command.StdinPipe() - if err != nil { - return err - } - go func() { - defer stdin.Close() - utils.Debugf("start: begin of stdin pipe") - io.Copy(stdin, container.stdin) - utils.Debugf("start: end of stdin pipe") - }() - } - return nil -} - func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser @@ -593,14 +541,13 @@ func (container *Container) Start() (err error) { } 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 + container.console, err = execdriver.NewConsole( + container.stdin, container.stdout, container.stderr, + container.Config.OpenStdin, container.Config.Tty) + if err != nil { + return err } - if err := setup(); err != nil { + if err := container.console.AttachTo(container.command); err != nil { return err } @@ -887,22 +834,20 @@ func (container *Container) cleanup() { link.Disable() } } - if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { utils.Errorf("%s: Error close stdin: %s", container.ID, err) } } - if err := container.stdout.CloseWriters(); err != nil { + if err := container.stdout.Close(); err != nil { utils.Errorf("%s: Error close stdout: %s", container.ID, err) } - if err := container.stderr.CloseWriters(); err != nil { + if err := container.stderr.Close(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } - - if container.ptyMaster != nil { - if err := container.ptyMaster.Close(); err != nil { - utils.Errorf("%s: Error closing Pty master: %s", container.ID, err) + if container.console != nil { + if err := container.console.Close(); err != nil { + utils.Errorf("%s: Error closing console: %s", container.ID, err) } } @@ -994,11 +939,7 @@ func (container *Container) Wait() int { } func (container *Container) Resize(h, w int) error { - pty, ok := container.ptyMaster.(*os.File) - if !ok { - return fmt.Errorf("ptyMaster does not have Fd() method") - } - return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) + return container.console.Resize(h, w) } func (container *Container) ExportRw() (archive.Archive, error) { @@ -1202,11 +1143,9 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - if container.ptyMaster == nil { + ttyConsole, ok := container.console.(*execdriver.TtyConsole) + if !ok { return nil, ErrNoTTY } - if pty, ok := container.ptyMaster.(*os.File); ok { - return pty, nil - } - return nil, ErrNotATTY + return ttyConsole.Master, nil } diff --git a/execdriver/console.go b/execdriver/console.go new file mode 100644 index 000000000..01b45afb6 --- /dev/null +++ b/execdriver/console.go @@ -0,0 +1,137 @@ +package execdriver + +import ( + "github.com/dotcloud/docker/pkg/term" + "github.com/kr/pty" + "io" + "os" +) + +type Console interface { + io.Closer + Resize(height, width int) error + AttachTo(command *Command) error +} + +type pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.WriteCloser +} + +func (p *pipes) Close() error { + if p.Stderr != nil { + p.Stdin.Close() + } + if p.Stdout != nil { + p.Stdout.Close() + } + if p.Stderr != nil { + p.Stderr.Close() + } + return nil +} + +func NewConsole(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin, tty bool) (Console, error) { + p := &pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + if tty { + return NewTtyConsole(p) + } + return NewStdConsole(p) +} + +type TtyConsole struct { + Master *os.File + Slave *os.File + pipes *pipes +} + +func NewTtyConsole(p *pipes) (*TtyConsole, error) { + ptyMaster, ptySlave, err := pty.Open() + if err != nil { + return nil, err + } + tty := &TtyConsole{ + Master: ptyMaster, + Slave: ptySlave, + pipes: p, + } + return tty, nil +} + +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) + +} + +func (t *TtyConsole) AttachTo(command *Command) error { + command.Stdout = t.Slave + command.Stderr = t.Slave + + command.Console = t.Slave.Name() + + go func() { + defer t.pipes.Stdout.Close() + io.Copy(t.pipes.Stdout, t.Master) + }() + + if t.pipes.Stdin != nil { + command.Stdin = t.Slave + command.SysProcAttr.Setctty = true + + go func() { + defer t.pipes.Stdin.Close() + io.Copy(t.Master, t.pipes.Stdin) + }() + } + return nil +} + +func (t *TtyConsole) Close() error { + err := t.Slave.Close() + if merr := t.Master.Close(); err == nil { + err = merr + } + return err +} + +type StdConsole struct { + pipes *pipes +} + +func NewStdConsole(p *pipes) (*StdConsole, error) { + return &StdConsole{p}, nil +} + +func (s *StdConsole) AttachTo(command *Command) error { + command.Stdout = s.pipes.Stdout + command.Stderr = s.pipes.Stderr + + if s.pipes.Stdin != nil { + stdin, err := command.StdinPipe() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, s.pipes.Stdin) + }() + } + return nil +} + +func (s *StdConsole) Resize(h, w int) error { + // we do not need to reside a non tty + return nil +} + +func (s *StdConsole) Close() error { + // nothing to close here + return nil +} diff --git a/utils/utils.go b/utils/utils.go index 1aba80ff4..3a87f76f5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -388,7 +388,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { return len(p), nil } -func (w *WriteBroadcaster) CloseWriters() error { +func (w *WriteBroadcaster) Close() error { w.Lock() defer w.Unlock() for sw := range w.writers { diff --git a/utils/utils_test.go b/utils/utils_test.go index 7e63a45cf..f8b392054 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -110,7 +110,7 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - writer.CloseWriters() + writer.Close() } type devNullCloser int From 1e742876988546efb876f67f83de6737ee5e9cf6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:32:14 -0800 Subject: [PATCH 062/282] Change Console to Terminal Move creation and attach to driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 27 +++---- execdriver/chroot/driver.go | 2 +- execdriver/console.go | 137 ------------------------------------ execdriver/driver.go | 6 +- execdriver/lxc/driver.go | 7 +- execdriver/term.go | 137 ++++++++++++++++++++++++++++++++++++ runtime.go | 4 +- 7 files changed, 159 insertions(+), 161 deletions(-) delete mode 100644 execdriver/console.go create mode 100644 execdriver/term.go diff --git a/container.go b/container.go index f38876ed2..025479f24 100644 --- a/container.go +++ b/container.go @@ -55,7 +55,6 @@ type Container struct { Driver string command *execdriver.Command - console execdriver.Console stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser @@ -531,6 +530,9 @@ func (container *Container) Start() (err error) { } populateCommand(container) + if err := execdriver.NewTerminal(container.command); err != nil { + return err + } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { @@ -541,16 +543,6 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) - container.console, err = execdriver.NewConsole( - container.stdin, container.stdout, container.stderr, - container.Config.OpenStdin, container.Config.Tty) - if err != nil { - return err - } - if err := container.console.AttachTo(container.command); err != nil { - return err - } - callbackLock := make(chan struct{}) callback := func(command *execdriver.Command) { container.State.SetRunning(command.Pid()) @@ -790,7 +782,8 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { populateCommand(container) err = container.runtime.RestoreCommand(container) } else { - exitCode, err = container.runtime.Run(container, callback) + pipes := execdriver.NewPipes(container.stdin, container.stdout, container.stderr, container.Config.OpenStdin) + exitCode, err = container.runtime.Run(container, pipes, callback) } if err != nil { @@ -845,9 +838,9 @@ func (container *Container) cleanup() { if err := container.stderr.Close(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } - if container.console != nil { - if err := container.console.Close(); err != nil { - utils.Errorf("%s: Error closing console: %s", container.ID, err) + if container.command.Terminal != nil { + if err := container.command.Terminal.Close(); err != nil { + utils.Errorf("%s: Error closing terminal: %s", container.ID, err) } } @@ -939,7 +932,7 @@ func (container *Container) Wait() int { } func (container *Container) Resize(h, w int) error { - return container.console.Resize(h, w) + return container.command.Terminal.Resize(h, w) } func (container *Container) ExportRw() (archive.Archive, error) { @@ -1143,7 +1136,7 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - ttyConsole, ok := container.console.(*execdriver.TtyConsole) + ttyConsole, ok := container.command.Terminal.(*execdriver.TtyConsole) if !ok { return nil, ErrNoTTY } diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 396df87ba..dfec680d8 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -37,7 +37,7 @@ func NewDriver() (*driver, error) { return &driver{}, nil } -func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", c.Rootfs, diff --git a/execdriver/console.go b/execdriver/console.go deleted file mode 100644 index 01b45afb6..000000000 --- a/execdriver/console.go +++ /dev/null @@ -1,137 +0,0 @@ -package execdriver - -import ( - "github.com/dotcloud/docker/pkg/term" - "github.com/kr/pty" - "io" - "os" -) - -type Console interface { - io.Closer - Resize(height, width int) error - AttachTo(command *Command) error -} - -type pipes struct { - Stdin io.ReadCloser - Stdout, Stderr io.WriteCloser -} - -func (p *pipes) Close() error { - if p.Stderr != nil { - p.Stdin.Close() - } - if p.Stdout != nil { - p.Stdout.Close() - } - if p.Stderr != nil { - p.Stderr.Close() - } - return nil -} - -func NewConsole(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin, tty bool) (Console, error) { - p := &pipes{ - Stdout: stdout, - Stderr: stderr, - } - if useStdin { - p.Stdin = stdin - } - if tty { - return NewTtyConsole(p) - } - return NewStdConsole(p) -} - -type TtyConsole struct { - Master *os.File - Slave *os.File - pipes *pipes -} - -func NewTtyConsole(p *pipes) (*TtyConsole, error) { - ptyMaster, ptySlave, err := pty.Open() - if err != nil { - return nil, err - } - tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, - pipes: p, - } - return tty, nil -} - -func (t *TtyConsole) Resize(h, w int) error { - return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) - -} - -func (t *TtyConsole) AttachTo(command *Command) error { - command.Stdout = t.Slave - command.Stderr = t.Slave - - command.Console = t.Slave.Name() - - go func() { - defer t.pipes.Stdout.Close() - io.Copy(t.pipes.Stdout, t.Master) - }() - - if t.pipes.Stdin != nil { - command.Stdin = t.Slave - command.SysProcAttr.Setctty = true - - go func() { - defer t.pipes.Stdin.Close() - io.Copy(t.Master, t.pipes.Stdin) - }() - } - return nil -} - -func (t *TtyConsole) Close() error { - err := t.Slave.Close() - if merr := t.Master.Close(); err == nil { - err = merr - } - return err -} - -type StdConsole struct { - pipes *pipes -} - -func NewStdConsole(p *pipes) (*StdConsole, error) { - return &StdConsole{p}, nil -} - -func (s *StdConsole) AttachTo(command *Command) error { - command.Stdout = s.pipes.Stdout - command.Stderr = s.pipes.Stderr - - if s.pipes.Stdin != nil { - stdin, err := command.StdinPipe() - if err != nil { - return err - } - - go func() { - defer stdin.Close() - io.Copy(stdin, s.pipes.Stdin) - }() - } - return nil -} - -func (s *StdConsole) Resize(h, w int) error { - // we do not need to reside a non tty - return nil -} - -func (s *StdConsole) Close() error { - // nothing to close here - return nil -} diff --git a/execdriver/driver.go b/execdriver/driver.go index 32b39771b..c9f62f500 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -58,7 +58,7 @@ type Info interface { } type Driver interface { - Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code + Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error Restore(c *Command) error // Wait and try to re-attach on an out of process command Name() string // Driver name @@ -82,7 +82,6 @@ type Resources struct { } // Process wrapps an os/exec.Cmd to add more metadata -// TODO: Rename to Command type Command struct { exec.Cmd `json:"-"` @@ -100,7 +99,8 @@ type Command struct { Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` - Console string `json:"-"` + Terminal Term `json:"-"` + Console string `json:"-"` } // Return the pid of the process diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index ee4d02a6b..da3bc1ec7 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -76,7 +76,12 @@ func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, version) } -func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + if c.Terminal != nil { + if err := c.Terminal.Attach(pipes); err != nil { + return -1, err + } + } configPath, err := d.generateLXCConfig(c) if err != nil { return -1, err diff --git a/execdriver/term.go b/execdriver/term.go new file mode 100644 index 000000000..e7ec46653 --- /dev/null +++ b/execdriver/term.go @@ -0,0 +1,137 @@ +package execdriver + +import ( + "github.com/dotcloud/docker/pkg/term" + "github.com/kr/pty" + "io" + "os" +) + +type Term interface { + io.Closer + Resize(height, width int) error + Attach(pipes *Pipes) error +} + +type Pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.WriteCloser +} + +func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) *Pipes { + p := &Pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + return p +} + +func NewTerminal(command *Command) error { + var ( + term Term + err error + ) + if command.Tty { + term, err = NewTtyConsole(command) + } else { + term, err = NewStdConsole(command) + } + if err != nil { + return err + } + command.Terminal = term + return nil +} + +type TtyConsole struct { + command *Command + Master *os.File + Slave *os.File +} + +func NewTtyConsole(command *Command) (*TtyConsole, error) { + ptyMaster, ptySlave, err := pty.Open() + if err != nil { + return nil, err + } + tty := &TtyConsole{ + Master: ptyMaster, + Slave: ptySlave, + command: command, + } + return tty, nil +} + +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +} + +func (t *TtyConsole) Attach(pipes *Pipes) error { + t.command.Stdout = t.Slave + t.command.Stderr = t.Slave + + t.command.Console = t.Slave.Name() + + go func() { + defer pipes.Stdout.Close() + io.Copy(pipes.Stdout, t.Master) + }() + + if pipes.Stdin != nil { + t.command.Stdin = t.Slave + t.command.SysProcAttr.Setctty = true + + go func() { + defer pipes.Stdin.Close() + io.Copy(t.Master, pipes.Stdin) + }() + } + return nil +} + +func (t *TtyConsole) Close() error { + err := t.Slave.Close() + if merr := t.Master.Close(); err == nil { + err = merr + } + return err +} + +type StdConsole struct { + command *Command +} + +func NewStdConsole(command *Command) (*StdConsole, error) { + return &StdConsole{command}, nil +} + +func (s *StdConsole) Attach(pipes *Pipes) error { + s.command.Stdout = pipes.Stdout + s.command.Stderr = pipes.Stderr + + if pipes.Stdin != nil { + stdin, err := s.command.StdinPipe() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, pipes.Stdin) + }() + } + return nil +} + +func (s *StdConsole) Resize(h, w int) error { + // we do not need to reside a non tty + return nil +} + +func (s *StdConsole) Close() error { + // nothing to close here + return nil +} diff --git a/runtime.go b/runtime.go index eed28f92a..a38109cca 100644 --- a/runtime.go +++ b/runtime.go @@ -812,8 +812,8 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { }), nil } -func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { - return runtime.execDriver.Run(c.command, startCallback) +func (runtime *Runtime) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + return runtime.execDriver.Run(c.command, pipes, startCallback) } func (runtime *Runtime) Kill(c *Container, sig int) error { From 592c2f6f9a472bda227a03c819f73b8edc7c3320 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:42:37 -0800 Subject: [PATCH 063/282] Move term creation into driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 3 -- execdriver/lxc/driver.go | 6 ++-- execdriver/term.go | 61 ++++++++++++++++++++-------------------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/container.go b/container.go index 025479f24..733a31d5a 100644 --- a/container.go +++ b/container.go @@ -530,9 +530,6 @@ func (container *Container) Start() (err error) { } populateCommand(container) - if err := execdriver.NewTerminal(container.command); err != nil { - return err - } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index da3bc1ec7..c18b2e6ab 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -77,10 +77,8 @@ func (d *driver) Name() string { } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - if c.Terminal != nil { - if err := c.Terminal.Attach(pipes); err != nil { - return -1, err - } + if err := execdriver.SetTerminal(c, pipes); err != nil { + return -1, err } configPath, err := d.generateLXCConfig(c) if err != nil { diff --git a/execdriver/term.go b/execdriver/term.go index e7ec46653..ab399e833 100644 --- a/execdriver/term.go +++ b/execdriver/term.go @@ -10,7 +10,6 @@ import ( type Term interface { io.Closer Resize(height, width int) error - Attach(pipes *Pipes) error } type Pipes struct { @@ -29,15 +28,15 @@ func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) return p } -func NewTerminal(command *Command) error { +func SetTerminal(command *Command, pipes *Pipes) error { var ( term Term err error ) if command.Tty { - term, err = NewTtyConsole(command) + term, err = NewTtyConsole(command, pipes) } else { - term, err = NewStdConsole(command) + term, err = NewStdConsole(command, pipes) } if err != nil { return err @@ -47,20 +46,22 @@ func NewTerminal(command *Command) error { } type TtyConsole struct { - command *Command - Master *os.File - Slave *os.File + Master *os.File + Slave *os.File } -func NewTtyConsole(command *Command) (*TtyConsole, error) { +func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { return nil, err } tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, - command: command, + Master: ptyMaster, + Slave: ptySlave, + } + if err := tty.attach(command, pipes); err != nil { + tty.Close() + return nil, err } return tty, nil } @@ -69,11 +70,10 @@ func (t *TtyConsole) Resize(h, w int) error { return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) } -func (t *TtyConsole) Attach(pipes *Pipes) error { - t.command.Stdout = t.Slave - t.command.Stderr = t.Slave - - t.command.Console = t.Slave.Name() +func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { + command.Stdout = t.Slave + command.Stderr = t.Slave + command.Console = t.Slave.Name() go func() { defer pipes.Stdout.Close() @@ -81,8 +81,8 @@ func (t *TtyConsole) Attach(pipes *Pipes) error { }() if pipes.Stdin != nil { - t.command.Stdin = t.Slave - t.command.SysProcAttr.Setctty = true + command.Stdin = t.Slave + command.SysProcAttr.Setctty = true go func() { defer pipes.Stdin.Close() @@ -93,27 +93,28 @@ func (t *TtyConsole) Attach(pipes *Pipes) error { } func (t *TtyConsole) Close() error { - err := t.Slave.Close() - if merr := t.Master.Close(); err == nil { - err = merr - } - return err + t.Slave.Close() + return t.Master.Close() } type StdConsole struct { - command *Command } -func NewStdConsole(command *Command) (*StdConsole, error) { - return &StdConsole{command}, nil +func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { + std := &StdConsole{} + + if err := std.attach(command, pipes); err != nil { + return nil, err + } + return std, nil } -func (s *StdConsole) Attach(pipes *Pipes) error { - s.command.Stdout = pipes.Stdout - s.command.Stderr = pipes.Stderr +func (s *StdConsole) attach(command *Command, pipes *Pipes) error { + command.Stdout = pipes.Stdout + command.Stderr = pipes.Stderr if pipes.Stdin != nil { - stdin, err := s.command.StdinPipe() + stdin, err := command.StdinPipe() if err != nil { return err } From 8e2284aaa2364a8e47a8058e65d60813f9cd5089 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:52:18 -0800 Subject: [PATCH 064/282] Add CloseWriters back and do an interface cast Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 4 ++-- execdriver/term.go | 10 +++++++--- utils/utils.go | 2 +- utils/utils_test.go | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/container.go b/container.go index 733a31d5a..218ae1e4a 100644 --- a/container.go +++ b/container.go @@ -829,10 +829,10 @@ func (container *Container) cleanup() { utils.Errorf("%s: Error close stdin: %s", container.ID, err) } } - if err := container.stdout.Close(); err != nil { + if err := container.stdout.CloseWriters(); err != nil { utils.Errorf("%s: Error close stdout: %s", container.ID, err) } - if err := container.stderr.Close(); err != nil { + if err := container.stderr.CloseWriters(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } if container.command.Terminal != nil { diff --git a/execdriver/term.go b/execdriver/term.go index ab399e833..ec3c36801 100644 --- a/execdriver/term.go +++ b/execdriver/term.go @@ -14,10 +14,10 @@ type Term interface { type Pipes struct { Stdin io.ReadCloser - Stdout, Stderr io.WriteCloser + Stdout, Stderr io.Writer } -func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) *Pipes { +func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { p := &Pipes{ Stdout: stdout, Stderr: stderr, @@ -76,7 +76,11 @@ func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { command.Console = t.Slave.Name() go func() { - defer pipes.Stdout.Close() + if wb, ok := pipes.Stdout.(interface { + CloseWriters() error + }); ok { + defer wb.CloseWriters() + } io.Copy(pipes.Stdout, t.Master) }() diff --git a/utils/utils.go b/utils/utils.go index 3a87f76f5..1aba80ff4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -388,7 +388,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { return len(p), nil } -func (w *WriteBroadcaster) Close() error { +func (w *WriteBroadcaster) CloseWriters() error { w.Lock() defer w.Unlock() for sw := range w.writers { diff --git a/utils/utils_test.go b/utils/utils_test.go index f8b392054..7e63a45cf 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -110,7 +110,7 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - writer.Close() + writer.CloseWriters() } type devNullCloser int From aac702727ea02b3974a2848748b0713ac583fc84 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 13:27:15 -0800 Subject: [PATCH 065/282] Move current tty and pipe impl to lxc driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 4 +-- execdriver/driver.go | 18 ++++++++-- execdriver/lxc/driver.go | 2 +- execdriver/{ => lxc}/term.go | 68 ++++++++++++++---------------------- execdriver/pipes.go | 23 ++++++++++++ 5 files changed, 68 insertions(+), 47 deletions(-) rename execdriver/{ => lxc}/term.go (58%) create mode 100644 execdriver/pipes.go diff --git a/container.go b/container.go index 218ae1e4a..ca53bb57c 100644 --- a/container.go +++ b/container.go @@ -1133,9 +1133,9 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - ttyConsole, ok := container.command.Terminal.(*execdriver.TtyConsole) + ttyConsole, ok := container.command.Terminal.(execdriver.TtyTerminal) if !ok { return nil, ErrNoTTY } - return ttyConsole.Master, nil + return ttyConsole.Master(), nil } diff --git a/execdriver/driver.go b/execdriver/driver.go index c9f62f500..a6d865caf 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -2,6 +2,8 @@ package execdriver import ( "errors" + "io" + "os" "os/exec" ) @@ -57,6 +59,18 @@ type Info interface { IsRunning() bool } +// Terminal in an interface for drivers to implement +// if they want to support Close and Resize calls from +// the core +type Terminal interface { + io.Closer + Resize(height, width int) error +} + +type TtyTerminal interface { + Master() *os.File +} + type Driver interface { Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error @@ -99,8 +113,8 @@ type Command struct { Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` - Terminal Term `json:"-"` - Console string `json:"-"` + Terminal Terminal `json:"-"` // standard or tty terminal + Console string `json:"-"` // dev/console path } // Return the pid of the process diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index c18b2e6ab..5be7ad221 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -77,7 +77,7 @@ func (d *driver) Name() string { } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - if err := execdriver.SetTerminal(c, pipes); err != nil { + if err := SetTerminal(c, pipes); err != nil { return -1, err } configPath, err := d.generateLXCConfig(c) diff --git a/execdriver/term.go b/execdriver/lxc/term.go similarity index 58% rename from execdriver/term.go rename to execdriver/lxc/term.go index ec3c36801..d772f6097 100644 --- a/execdriver/term.go +++ b/execdriver/lxc/term.go @@ -1,36 +1,16 @@ -package execdriver +package lxc import ( + "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/pkg/term" "github.com/kr/pty" "io" "os" ) -type Term interface { - io.Closer - Resize(height, width int) error -} - -type Pipes struct { - Stdin io.ReadCloser - Stdout, Stderr io.Writer -} - -func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { - p := &Pipes{ - Stdout: stdout, - Stderr: stderr, - } - if useStdin { - p.Stdin = stdin - } - return p -} - -func SetTerminal(command *Command, pipes *Pipes) error { +func SetTerminal(command *execdriver.Command, pipes *execdriver.Pipes) error { var ( - term Term + term execdriver.Terminal err error ) if command.Tty { @@ -46,18 +26,18 @@ func SetTerminal(command *Command, pipes *Pipes) error { } type TtyConsole struct { - Master *os.File - Slave *os.File + master *os.File + slave *os.File } -func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { +func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { return nil, err } tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, + master: ptyMaster, + slave: ptySlave, } if err := tty.attach(command, pipes); err != nil { tty.Close() @@ -66,14 +46,18 @@ func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { return tty, nil } -func (t *TtyConsole) Resize(h, w int) error { - return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +func (t *TtyConsole) Master() *os.File { + return t.master } -func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { - command.Stdout = t.Slave - command.Stderr = t.Slave - command.Console = t.Slave.Name() +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +} + +func (t *TtyConsole) attach(command *execdriver.Command, pipes *execdriver.Pipes) error { + command.Stdout = t.slave + command.Stderr = t.slave + command.Console = t.slave.Name() go func() { if wb, ok := pipes.Stdout.(interface { @@ -81,30 +65,30 @@ func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { }); ok { defer wb.CloseWriters() } - io.Copy(pipes.Stdout, t.Master) + io.Copy(pipes.Stdout, t.master) }() if pipes.Stdin != nil { - command.Stdin = t.Slave + command.Stdin = t.slave command.SysProcAttr.Setctty = true go func() { defer pipes.Stdin.Close() - io.Copy(t.Master, pipes.Stdin) + io.Copy(t.master, pipes.Stdin) }() } return nil } func (t *TtyConsole) Close() error { - t.Slave.Close() - return t.Master.Close() + t.slave.Close() + return t.master.Close() } type StdConsole struct { } -func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { +func NewStdConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*StdConsole, error) { std := &StdConsole{} if err := std.attach(command, pipes); err != nil { @@ -113,7 +97,7 @@ func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { return std, nil } -func (s *StdConsole) attach(command *Command, pipes *Pipes) error { +func (s *StdConsole) attach(command *execdriver.Command, pipes *execdriver.Pipes) error { command.Stdout = pipes.Stdout command.Stderr = pipes.Stderr diff --git a/execdriver/pipes.go b/execdriver/pipes.go new file mode 100644 index 000000000..158219f0c --- /dev/null +++ b/execdriver/pipes.go @@ -0,0 +1,23 @@ +package execdriver + +import ( + "io" +) + +// Pipes is a wrapper around a containers output for +// stdin, stdout, stderr +type Pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.Writer +} + +func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { + p := &Pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + return p +} From e8abaf217b887fcd6a157b4f905156bd245f8f0a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Feb 2014 16:56:11 -0800 Subject: [PATCH 066/282] Initial commit of libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/MAINTAINERS | 2 + pkg/libcontainer/README.md | 63 +++++ pkg/libcontainer/capabilities/capabilities.go | 49 ++++ pkg/libcontainer/cli/main.go | 171 +++++++++++ pkg/libcontainer/container.go | 27 ++ pkg/libcontainer/container.json | 38 +++ pkg/libcontainer/errors.go | 9 + pkg/libcontainer/namespaces/calls_linux.go | 164 +++++++++++ pkg/libcontainer/namespaces/exec.go | 266 ++++++++++++++++++ pkg/libcontainer/namespaces/linux_x86_64.go | 7 + pkg/libcontainer/namespaces/mount.go | 207 ++++++++++++++ pkg/libcontainer/namespaces/namespaces.go | 70 +++++ pkg/libcontainer/namespaces/ns_linux.go | 35 +++ pkg/libcontainer/namespaces/utils.go | 108 +++++++ pkg/libcontainer/network/network.go | 104 +++++++ pkg/libcontainer/network/veth.go | 85 ++++++ pkg/libcontainer/privileged.json | 22 ++ pkg/libcontainer/types.go | 49 ++++ pkg/libcontainer/ubuntu.json | 22 ++ pkg/libcontainer/utils/utils.go | 33 +++ 20 files changed, 1531 insertions(+) create mode 100644 pkg/libcontainer/MAINTAINERS create mode 100644 pkg/libcontainer/README.md create mode 100644 pkg/libcontainer/capabilities/capabilities.go create mode 100644 pkg/libcontainer/cli/main.go create mode 100644 pkg/libcontainer/container.go create mode 100644 pkg/libcontainer/container.json create mode 100644 pkg/libcontainer/errors.go create mode 100644 pkg/libcontainer/namespaces/calls_linux.go create mode 100644 pkg/libcontainer/namespaces/exec.go create mode 100644 pkg/libcontainer/namespaces/linux_x86_64.go create mode 100644 pkg/libcontainer/namespaces/mount.go create mode 100644 pkg/libcontainer/namespaces/namespaces.go create mode 100644 pkg/libcontainer/namespaces/ns_linux.go create mode 100644 pkg/libcontainer/namespaces/utils.go create mode 100644 pkg/libcontainer/network/network.go create mode 100644 pkg/libcontainer/network/veth.go create mode 100644 pkg/libcontainer/privileged.json create mode 100644 pkg/libcontainer/types.go create mode 100644 pkg/libcontainer/ubuntu.json create mode 100644 pkg/libcontainer/utils/utils.go diff --git a/pkg/libcontainer/MAINTAINERS b/pkg/libcontainer/MAINTAINERS new file mode 100644 index 000000000..e53d933d4 --- /dev/null +++ b/pkg/libcontainer/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Guillaume Charmes (@creack) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md new file mode 100644 index 000000000..91d747863 --- /dev/null +++ b/pkg/libcontainer/README.md @@ -0,0 +1,63 @@ +## libcontainer - reference implementation for containers + +#### playground + + +Use the cli package to test out functionality + +First setup a container configuration. You will need a root fs, better go the path to a +stopped docker container and use that. + + +```json +{ + "id": "koye", + "namespace_pid": 12265, + "command": { + "args": [ + "/bin/bash" + ], + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm" + ] + }, + "rootfs": "/root/development/gocode/src/github.com/docker/libcontainer/namespaces/ubuntu", + "network": null, + "user": "", + "working_dir": "", + "namespaces": [ + "NEWNET", + "NEWIPC", + "NEWNS", + "NEWPID", + "NEWUTS" + ], + "capabilities": [ + "SETPCAP", + "SYS_MODULE", + "SYS_RAWIO", + "SYS_PACCT", + "SYS_ADMIN", + "SYS_NICE", + "SYS_RESOURCE", + "SYS_TIME", + "SYS_TTY_CONFIG", + "MKNOD", + "AUDIT_WRITE", + "AUDIT_CONTROL", + "MAC_OVERRIDE", + "MAC_ADMIN" + ] +} +``` + +After you have a json file and a rootfs path to use just run: +`./cli exec container.json` + + +If you want to attach to an existing namespace just use the same json +file with the container still running and do: +`./cli execin container.json` diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/capabilities/capabilities.go new file mode 100644 index 000000000..3301e10f7 --- /dev/null +++ b/pkg/libcontainer/capabilities/capabilities.go @@ -0,0 +1,49 @@ +package capabilities + +import ( + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/syndtr/gocapability/capability" + "os" +) + +var capMap = map[libcontainer.Capability]capability.Cap{ + libcontainer.CAP_SETPCAP: capability.CAP_SETPCAP, + libcontainer.CAP_SYS_MODULE: capability.CAP_SYS_MODULE, + libcontainer.CAP_SYS_RAWIO: capability.CAP_SYS_RAWIO, + libcontainer.CAP_SYS_PACCT: capability.CAP_SYS_PACCT, + libcontainer.CAP_SYS_ADMIN: capability.CAP_SYS_ADMIN, + libcontainer.CAP_SYS_NICE: capability.CAP_SYS_NICE, + libcontainer.CAP_SYS_RESOURCE: capability.CAP_SYS_RESOURCE, + libcontainer.CAP_SYS_TIME: capability.CAP_SYS_TIME, + libcontainer.CAP_SYS_TTY_CONFIG: capability.CAP_SYS_TTY_CONFIG, + libcontainer.CAP_MKNOD: capability.CAP_MKNOD, + libcontainer.CAP_AUDIT_WRITE: capability.CAP_AUDIT_WRITE, + libcontainer.CAP_AUDIT_CONTROL: capability.CAP_AUDIT_CONTROL, + libcontainer.CAP_MAC_OVERRIDE: capability.CAP_MAC_OVERRIDE, + libcontainer.CAP_MAC_ADMIN: capability.CAP_MAC_ADMIN, +} + +// DropCapabilities drops capabilities for the current process based +// on the container's configuration. +func DropCapabilities(container *libcontainer.Container) error { + if drop := getCapabilities(container); len(drop) > 0 { + c, err := capability.NewPid(os.Getpid()) + if err != nil { + return err + } + c.Unset(capability.CAPS|capability.BOUNDS, drop...) + + if err := c.Apply(capability.CAPS | capability.BOUNDS); err != nil { + return err + } + } + return nil +} + +func getCapabilities(container *libcontainer.Container) []capability.Cap { + drop := []capability.Cap{} + for _, c := range container.Capabilities { + drop = append(drop, capMap[c]) + } + return drop +} diff --git a/pkg/libcontainer/cli/main.go b/pkg/libcontainer/cli/main.go new file mode 100644 index 000000000..490135ef5 --- /dev/null +++ b/pkg/libcontainer/cli/main.go @@ -0,0 +1,171 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/utils" + "os" +) + +var ( + displayPid bool + newCommand string + usrNet bool +) + +func init() { + flag.BoolVar(&displayPid, "pid", false, "display the pid before waiting") + flag.StringVar(&newCommand, "cmd", "/bin/bash", "command to run in the existing namespace") + flag.BoolVar(&usrNet, "net", false, "user a net namespace") + flag.Parse() +} + +func exec(container *libcontainer.Container) error { + var ( + netFile *os.File + err error + ) + container.NetNsFd = 0 + + if usrNet { + netFile, err = os.Open("/root/nsroot/test") + if err != nil { + return err + } + container.NetNsFd = netFile.Fd() + } + + pid, err := namespaces.Exec(container) + if err != nil { + return fmt.Errorf("error exec container %s", err) + } + + if displayPid { + fmt.Println(pid) + } + + exitcode, err := utils.WaitOnPid(pid) + if err != nil { + return fmt.Errorf("error waiting on child %s", err) + } + fmt.Println(exitcode) + if usrNet { + netFile.Close() + if err := network.DeleteNetworkNamespace("/root/nsroot/test"); err != nil { + return err + } + } + os.Exit(exitcode) + return nil +} + +func execIn(container *libcontainer.Container) error { + f, err := os.Open("/root/nsroot/test") + if err != nil { + return err + } + container.NetNsFd = f.Fd() + pid, err := namespaces.ExecIn(container, &libcontainer.Command{ + Env: container.Command.Env, + Args: []string{ + newCommand, + }, + }) + if err != nil { + return fmt.Errorf("error exexin container %s", err) + } + exitcode, err := utils.WaitOnPid(pid) + if err != nil { + return fmt.Errorf("error waiting on child %s", err) + } + os.Exit(exitcode) + return nil +} + +func createNet(config *libcontainer.Network) error { + root := "/root/nsroot" + if err := network.SetupNamespaceMountDir(root); err != nil { + return err + } + + nspath := root + "/test" + if err := network.CreateNetworkNamespace(nspath); err != nil { + return nil + } + if err := network.CreateVethPair("veth0", config.TempVethName); err != nil { + return err + } + if err := network.SetInterfaceMaster("veth0", config.Bridge); err != nil { + return err + } + if err := network.InterfaceUp("veth0"); err != nil { + return err + } + + f, err := os.Open(nspath) + if err != nil { + return err + } + defer f.Close() + + if err := network.SetInterfaceInNamespaceFd("veth1", int(f.Fd())); err != nil { + return err + } + + /* + if err := network.SetupVethInsideNamespace(f.Fd(), config); err != nil { + return err + } + */ + return nil +} + +func printErr(err error) { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} + +func main() { + var ( + err error + cliCmd = flag.Arg(0) + config = flag.Arg(1) + ) + f, err := os.Open(config) + if err != nil { + printErr(err) + } + + dec := json.NewDecoder(f) + var container *libcontainer.Container + + if err := dec.Decode(&container); err != nil { + printErr(err) + } + f.Close() + + switch cliCmd { + case "exec": + err = exec(container) + case "execin": + err = execIn(container) + case "net": + err = createNet(&libcontainer.Network{ + TempVethName: "veth1", + IP: "172.17.0.100/16", + Gateway: "172.17.42.1", + Mtu: 1500, + Bridge: "docker0", + }) + default: + err = fmt.Errorf("command not supported: %s", cliCmd) + } + + if err != nil { + printErr(err) + } +} diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go new file mode 100644 index 000000000..b77890fb5 --- /dev/null +++ b/pkg/libcontainer/container.go @@ -0,0 +1,27 @@ +package libcontainer + +type Container struct { + ID string `json:"id,omitempty"` + NsPid int `json:"namespace_pid,omitempty"` + Command *Command `json:"command,omitempty"` + RootFs string `json:"rootfs,omitempty"` + ReadonlyFs bool `json:"readonly_fs,omitempty"` + NetNsFd uintptr `json:"network_namespace_fd,omitempty"` + User string `json:"user,omitempty"` + WorkingDir string `json:"working_dir,omitempty"` + Namespaces Namespaces `json:"namespaces,omitempty"` + Capabilities Capabilities `json:"capabilities,omitempty"` +} + +type Command struct { + Args []string `json:"args,omitempty"` + Env []string `json:"environment,omitempty"` +} + +type Network struct { + TempVethName string `json:"temp_veth,omitempty"` + IP string `json:"ip,omitempty"` + Gateway string `json:"gateway,omitempty"` + Bridge string `json:"bridge,omitempty"` + Mtu int `json:"mtu,omitempty"` +} diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json new file mode 100644 index 000000000..ed8eb1bd7 --- /dev/null +++ b/pkg/libcontainer/container.json @@ -0,0 +1,38 @@ +{ + "id": "koye", + "namespace_pid": 3117, + "command": { + "args": [ + "/bin/bash" + ], + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm" + ] + }, + "rootfs": "/root/main/mycontainer", + "namespaces": [ + "NEWIPC", + "NEWNS", + "NEWPID", + "NEWUTS" + ], + "capabilities": [ + "SETPCAP", + "SYS_MODULE", + "SYS_RAWIO", + "SYS_PACCT", + "SYS_ADMIN", + "SYS_NICE", + "SYS_RESOURCE", + "SYS_TIME", + "SYS_TTY_CONFIG", + "MKNOD", + "AUDIT_WRITE", + "AUDIT_CONTROL", + "MAC_OVERRIDE", + "MAC_ADMIN" + ] +} diff --git a/pkg/libcontainer/errors.go b/pkg/libcontainer/errors.go new file mode 100644 index 000000000..c6964ee8e --- /dev/null +++ b/pkg/libcontainer/errors.go @@ -0,0 +1,9 @@ +package libcontainer + +import ( + "errors" +) + +var ( + ErrInvalidPid = errors.New("no ns pid found") +) diff --git a/pkg/libcontainer/namespaces/calls_linux.go b/pkg/libcontainer/namespaces/calls_linux.go new file mode 100644 index 000000000..793e940b6 --- /dev/null +++ b/pkg/libcontainer/namespaces/calls_linux.go @@ -0,0 +1,164 @@ +package namespaces + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +const ( + TIOCGPTN = 0x80045430 + TIOCSPTLCK = 0x40045431 +) + +func chroot(dir string) error { + return syscall.Chroot(dir) +} + +func chdir(dir string) error { + return syscall.Chdir(dir) +} + +func exec(cmd string, args []string, env []string) error { + return syscall.Exec(cmd, args, env) +} + +func fork() (int, error) { + syscall.ForkLock.Lock() + pid, _, err := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0) + syscall.ForkLock.Unlock() + if err != 0 { + return -1, err + } + return int(pid), nil +} + +func vfork() (int, error) { + syscall.ForkLock.Lock() + pid, _, err := syscall.Syscall(syscall.SYS_VFORK, 0, 0, 0) + syscall.ForkLock.Unlock() + if err != 0 { + return -1, err + } + return int(pid), nil +} + +func mount(source, target, fstype string, flags uintptr, data string) error { + return syscall.Mount(source, target, fstype, flags, data) +} + +func unmount(target string, flags int) error { + return syscall.Unmount(target, flags) +} + +func pivotroot(newroot, putold string) error { + return syscall.PivotRoot(newroot, putold) +} + +func unshare(flags int) error { + return syscall.Unshare(flags) +} + +func clone(flags uintptr) (int, error) { + syscall.ForkLock.Lock() + pid, _, err := syscall.RawSyscall(syscall.SYS_CLONE, flags, 0, 0) + syscall.ForkLock.Unlock() + if err != 0 { + return -1, err + } + return int(pid), nil +} + +func setns(fd uintptr, flags uintptr) error { + _, _, err := syscall.RawSyscall(SYS_SETNS, fd, flags, 0) + if err != 0 { + return err + } + return nil +} + +func usetCloseOnExec(fd uintptr) error { + if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0); err != 0 { + return err + } + return nil +} + +func setgroups(gids []int) error { + return syscall.Setgroups(gids) +} + +func setresgid(rgid, egid, sgid int) error { + return syscall.Setresgid(rgid, egid, sgid) +} + +func setresuid(ruid, euid, suid int) error { + return syscall.Setresuid(ruid, euid, suid) +} + +func sethostname(name string) error { + return syscall.Sethostname([]byte(name)) +} + +func setsid() (int, error) { + return syscall.Setsid() +} + +func ioctl(fd uintptr, flag, data uintptr) error { + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 { + return err + } + return nil +} + +func openpmtx() (*os.File, error) { + return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) +} + +func unlockpt(f *os.File) error { + var u int + return ioctl(f.Fd(), TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} + +func ptsname(f *os.File) (string, error) { + var n int + if err := ioctl(f.Fd(), TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { + return "", err + } + return fmt.Sprintf("/dev/pts/%d", n), nil +} + +func closefd(fd uintptr) error { + return syscall.Close(int(fd)) +} + +func dup2(fd1, fd2 uintptr) error { + return syscall.Dup2(int(fd1), int(fd2)) +} + +func mknod(path string, mode uint32, dev int) error { + return syscall.Mknod(path, mode, dev) +} + +func parentDeathSignal() error { + if _, _, err := syscall.RawSyscall6(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0, 0, 0, 0); err != 0 { + return err + } + return nil +} + +func setctty() error { + if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { + return err + } + return nil +} + +func mkfifo(name string, mode uint32) error { + return syscall.Mkfifo(name, mode) +} + +func umask(mask int) int { + return syscall.Umask(mask) +} diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go new file mode 100644 index 000000000..893b30288 --- /dev/null +++ b/pkg/libcontainer/namespaces/exec.go @@ -0,0 +1,266 @@ +/* + Higher level convience functions for setting up a container +*/ + +package namespaces + +import ( + "errors" + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/utils" + "io" + "log" + "os" + "path/filepath" + "syscall" +) + +var ( + ErrExistingNetworkNamespace = errors.New("specified both CLONE_NEWNET and an existing network namespace") +) + +// Exec will spawn new namespaces with the specified Container configuration +// in the RootFs path and return the pid of the new containerized process. +// +// If an existing network namespace is specified the container +// will join that namespace. If an existing network namespace is not specified but CLONE_NEWNET is, +// the container will be spawned with a new network namespace with no configuration. Omiting an +// existing network namespace and the CLONE_NEWNET option in the container configuration will allow +// the container to the the host's networking options and configuration. +func Exec(container *libcontainer.Container) (pid int, err error) { + // a user cannot pass CLONE_NEWNET and an existing net namespace fd to join + if container.NetNsFd > 0 && container.Namespaces.Contains(libcontainer.CLONE_NEWNET) { + return -1, ErrExistingNetworkNamespace + } + + rootfs, err := resolveRootfs(container) + if err != nil { + return -1, err + } + + master, console, err := createMasterAndConsole() + if err != nil { + return -1, err + } + + logger, err := os.OpenFile("/root/logs", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) + if err != nil { + return -1, err + } + log.SetOutput(logger) + + // we need CLONE_VFORK so we can wait on the child + flag := getNamespaceFlags(container.Namespaces) | CLONE_VFORK + + if pid, err = clone(uintptr(flag | SIGCHLD)); err != nil { + return -1, fmt.Errorf("error cloning process: %s", err) + } + + if pid == 0 { + // welcome to your new namespace ;) + // + // any errors encoutered inside the namespace we should write + // out to a log or a pipe to our parent and exit(1) + // because writing to stderr will not work after we close + if err := closeMasterAndStd(master); err != nil { + writeError("close master and std %s", err) + } + slave, err := openTerminal(console, syscall.O_RDWR) + if err != nil { + writeError("open terminal %s", err) + } + if err := dupSlave(slave); err != nil { + writeError("dup2 slave %s", err) + } + + if container.NetNsFd > 0 { + if err := JoinExistingNamespace(container.NetNsFd, libcontainer.CLONE_NEWNET); err != nil { + writeError("join existing net namespace %s", err) + } + } + + if _, err := setsid(); err != nil { + writeError("setsid %s", err) + } + if err := setctty(); err != nil { + writeError("setctty %s", err) + } + if err := parentDeathSignal(); err != nil { + writeError("parent deth signal %s", err) + } + if err := SetupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { + writeError("setup mount namespace %s", err) + } + if err := sethostname(container.ID); err != nil { + writeError("sethostname %s", err) + } + if err := capabilities.DropCapabilities(container); err != nil { + writeError("drop capabilities %s", err) + } + if err := setupUser(container); err != nil { + writeError("setup user %s", err) + } + if container.WorkingDir != "" { + if err := chdir(container.WorkingDir); err != nil { + writeError("chdir to %s %s", container.WorkingDir, err) + } + } + if err := exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + writeError("exec %s", err) + } + panic("unreachable") + } + + go func() { + if _, err := io.Copy(os.Stdout, master); err != nil { + log.Println(err) + } + }() + go func() { + if _, err := io.Copy(master, os.Stdin); err != nil { + log.Println(err) + } + }() + return pid, nil +} + +// ExecIn will spawn a new command inside an existing container's namespaces. The existing container's +// pid and namespace configuration is needed along with the specific capabilities that should +// be dropped once inside the namespace. +func ExecIn(container *libcontainer.Container, cmd *libcontainer.Command) (int, error) { + if container.NsPid <= 0 { + return -1, libcontainer.ErrInvalidPid + } + + fds, err := getNsFds(container) + if err != nil { + return -1, err + } + + if container.NetNsFd > 0 { + fds = append(fds, container.NetNsFd) + } + + pid, err := fork() + if err != nil { + for _, fd := range fds { + syscall.Close(int(fd)) + } + return -1, err + } + + if pid == 0 { + for _, fd := range fds { + if fd > 0 { + if err := JoinExistingNamespace(fd, ""); err != nil { + for _, fd := range fds { + syscall.Close(int(fd)) + } + writeError("join existing namespace for %d %s", fd, err) + } + } + syscall.Close(int(fd)) + } + + if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && + container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { + // important: + // + // we need to fork and unshare so that re can remount proc and sys within + // the namespace so the CLONE_NEWPID namespace will take effect + // if we don't fork we would end up unmounting proc and sys for the entire + // namespace + child, err := fork() + if err != nil { + writeError("fork child %s", err) + } + + if child == 0 { + if err := unshare(CLONE_NEWNS); err != nil { + writeError("unshare newns %s", err) + } + if err := remountProc(); err != nil { + writeError("remount proc %s", err) + } + if err := remountSys(); err != nil { + writeError("remount sys %s", err) + } + if err := capabilities.DropCapabilities(container); err != nil { + writeError("drop caps %s", err) + } + if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { + writeError("exec %s", err) + } + panic("unreachable") + } + exit, err := utils.WaitOnPid(child) + if err != nil { + writeError("wait on child %s", err) + } + os.Exit(exit) + } + if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { + writeError("exec %s", err) + } + panic("unreachable") + } + return pid, err +} + +func resolveRootfs(container *libcontainer.Container) (string, error) { + rootfs, err := filepath.Abs(container.RootFs) + if err != nil { + return "", err + } + return filepath.EvalSymlinks(rootfs) +} + +func createMasterAndConsole() (*os.File, string, error) { + master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, "", err + } + + console, err := ptsname(master) + if err != nil { + return nil, "", err + } + + if err := unlockpt(master); err != nil { + return nil, "", err + } + return master, console, nil +} + +func closeMasterAndStd(master *os.File) error { + closefd(master.Fd()) + closefd(0) + closefd(1) + closefd(2) + + return nil +} + +func dupSlave(slave *os.File) error { + // we close Stdin,etc so our pty slave should have fd 0 + if slave.Fd() != 0 { + return fmt.Errorf("slave fd not 0 %d", slave.Fd()) + } + if err := dup2(slave.Fd(), 1); err != nil { + return err + } + if err := dup2(slave.Fd(), 2); err != nil { + return err + } + return nil +} + +func openTerminal(name string, flag int) (*os.File, error) { + r, e := syscall.Open(name, flag, 0) + if e != nil { + return nil, &os.PathError{"open", name, e} + } + return os.NewFile(uintptr(r), name), nil +} diff --git a/pkg/libcontainer/namespaces/linux_x86_64.go b/pkg/libcontainer/namespaces/linux_x86_64.go new file mode 100644 index 000000000..ac9a01476 --- /dev/null +++ b/pkg/libcontainer/namespaces/linux_x86_64.go @@ -0,0 +1,7 @@ +// +build linux,x86_64 +package namespaces + +// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092 +const ( + SYS_SETNS = 308 +) diff --git a/pkg/libcontainer/namespaces/mount.go b/pkg/libcontainer/namespaces/mount.go new file mode 100644 index 000000000..6d867c91e --- /dev/null +++ b/pkg/libcontainer/namespaces/mount.go @@ -0,0 +1,207 @@ +package namespaces + +import ( + "fmt" + "log" + "os" + "path/filepath" + "syscall" +) + +var ( + // default mount point options + defaults = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +) + +func SetupNewMountNamespace(rootfs, console string, readonly bool) error { + if err := mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("mounting / as slave %s", err) + } + + if err := mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("mouting %s as bind %s", rootfs, err) + } + + if readonly { + if err := mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { + return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + } + } + + if err := mountSystem(rootfs); err != nil { + return fmt.Errorf("mount system %s", err) + } + + if err := copyDevNodes(rootfs); err != nil { + return fmt.Errorf("copy dev nodes %s", err) + } + + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink(filepath.Join(rootfs, "pts/ptmx"), ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + + if err := setupDev(rootfs); err != nil { + return err + } + + if err := setupConsole(rootfs, console); err != nil { + return err + } + + if err := chdir(rootfs); err != nil { + return fmt.Errorf("chdir into %s %s", rootfs, err) + } + + if err := mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + return fmt.Errorf("mount move %s into / %s", rootfs, err) + } + + if err := chroot("."); err != nil { + return fmt.Errorf("chroot . %s", err) + } + + if err := chdir("/"); err != nil { + return fmt.Errorf("chdir / %s", err) + } + + umask(0022) + + return nil +} + +func copyDevNodes(rootfs string) error { + umask(0000) + + for _, node := range []string{ + "null", + "zero", + "full", + "random", + "urandom", + "tty", + } { + stat, err := os.Stat(filepath.Join("/dev", node)) + if err != nil { + return err + } + + var ( + dest = filepath.Join(rootfs, "dev", node) + st = stat.Sys().(*syscall.Stat_t) + ) + + log.Printf("copy %s to %s %d\n", node, dest, st.Rdev) + if err := mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + return fmt.Errorf("copy %s %s", node, err) + } + } + return nil +} + +func setupDev(rootfs string) error { + for _, link := range []struct { + from string + to string + }{ + {"/proc/kcore", "/dev/core"}, + {"/proc/self/fd", "/dev/fd"}, + {"/proc/self/fd/0", "/dev/stdin"}, + {"/proc/self/fd/1", "/dev/stdout"}, + {"/proc/self/fd/2", "/dev/stderr"}, + } { + dest := filepath.Join(rootfs, link.to) + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + if err := os.Symlink(link.from, dest); err != nil { + return fmt.Errorf("symlink %s %s", dest, err) + } + } + return nil +} + +func setupConsole(rootfs, console string) error { + umask(0000) + + stat, err := os.Stat(console) + if err != nil { + return fmt.Errorf("stat console %s %s", console, err) + } + st := stat.Sys().(*syscall.Stat_t) + + dest := filepath.Join(rootfs, "dev/console") + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + + if err := os.Chmod(console, 0600); err != nil { + return err + } + if err := os.Chown(console, 0, 0); err != nil { + return err + } + + if err := mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + return fmt.Errorf("mknod %s %s", dest, err) + } + + if err := mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", console, dest, err) + } + return nil +} + +// mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts +// inside the mount namespace +func mountSystem(rootfs string) error { + mounts := []struct { + source string + path string + device string + flags int + data string + }{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaults}, + {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaults}, + {source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaults, data: "mode=1777"}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"}, + {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_STRICTATIME, data: "mode=755"}, + } + for _, m := range mounts { + if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { + return fmt.Errorf("mkdirall %s %s", m.path, err) + } + if err := mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.source, m.path, err) + } + } + return nil +} + +func remountProc() error { + if err := unmount("/proc", syscall.MNT_DETACH); err != nil { + return err + } + if err := mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { + return err + } + return nil +} + +func remountSys() error { + if err := unmount("/sys", syscall.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + return err + } + } else { + if err := mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/namespaces/namespaces.go b/pkg/libcontainer/namespaces/namespaces.go new file mode 100644 index 000000000..2a5084701 --- /dev/null +++ b/pkg/libcontainer/namespaces/namespaces.go @@ -0,0 +1,70 @@ +/* + TODO + pivot root + cgroups + more mount stuff that I probably am forgetting + apparmor +*/ + +package namespaces + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/utils" + "os" + "path/filepath" + "syscall" +) + +// CreateNewNamespace creates a new namespace and binds it's fd to the specified path +func CreateNewNamespace(namespace libcontainer.Namespace, bindTo string) error { + var ( + flag = namespaceMap[namespace] + name = namespaceFileMap[namespace] + nspath = filepath.Join("/proc/self/ns", name) + ) + // TODO: perform validation on name and flag + + pid, err := fork() + if err != nil { + return err + } + + if pid == 0 { + if err := unshare(flag); err != nil { + writeError("unshare %s", err) + } + if err := mount(nspath, bindTo, "none", syscall.MS_BIND, ""); err != nil { + writeError("bind mount %s", err) + } + os.Exit(0) + } + exit, err := utils.WaitOnPid(pid) + if err != nil { + return err + } + if exit != 0 { + return fmt.Errorf("exit status %d", exit) + } + return err +} + +// JoinExistingNamespace uses the fd of an existing linux namespace and +// has the current process join that namespace or the spacespace specified by ns +func JoinExistingNamespace(fd uintptr, ns libcontainer.Namespace) error { + flag := namespaceMap[ns] + if err := setns(fd, uintptr(flag)); err != nil { + return err + } + return nil +} + +// getNamespaceFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare, and setns +func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { + for _, ns := range namespaces { + flag |= namespaceMap[ns] + } + return +} diff --git a/pkg/libcontainer/namespaces/ns_linux.go b/pkg/libcontainer/namespaces/ns_linux.go new file mode 100644 index 000000000..b0e511913 --- /dev/null +++ b/pkg/libcontainer/namespaces/ns_linux.go @@ -0,0 +1,35 @@ +package namespaces + +import ( + "github.com/dotcloud/docker/pkg/libcontainer" +) + +const ( + SIGCHLD = 0x14 + CLONE_VFORK = 0x00004000 + CLONE_NEWNS = 0x00020000 + CLONE_NEWUTS = 0x04000000 + CLONE_NEWIPC = 0x08000000 + CLONE_NEWUSER = 0x10000000 + CLONE_NEWPID = 0x20000000 + CLONE_NEWNET = 0x40000000 +) + +var namespaceMap = map[libcontainer.Namespace]int{ + "": 0, + libcontainer.CLONE_NEWNS: CLONE_NEWNS, + libcontainer.CLONE_NEWUTS: CLONE_NEWUTS, + libcontainer.CLONE_NEWIPC: CLONE_NEWIPC, + libcontainer.CLONE_NEWUSER: CLONE_NEWUSER, + libcontainer.CLONE_NEWPID: CLONE_NEWPID, + libcontainer.CLONE_NEWNET: CLONE_NEWNET, +} + +var namespaceFileMap = map[libcontainer.Namespace]string{ + libcontainer.CLONE_NEWNS: "mnt", + libcontainer.CLONE_NEWUTS: "uts", + libcontainer.CLONE_NEWIPC: "ipc", + libcontainer.CLONE_NEWUSER: "user", + libcontainer.CLONE_NEWPID: "pid", + libcontainer.CLONE_NEWNET: "net", +} diff --git a/pkg/libcontainer/namespaces/utils.go b/pkg/libcontainer/namespaces/utils.go new file mode 100644 index 000000000..438d89648 --- /dev/null +++ b/pkg/libcontainer/namespaces/utils.go @@ -0,0 +1,108 @@ +package namespaces + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" +) + +func addEnvIfNotSet(container *libcontainer.Container, key, value string) { + jv := fmt.Sprintf("%s=%s", key, value) + if len(container.Command.Env) == 0 { + container.Command.Env = []string{jv} + return + } + + for _, v := range container.Command.Env { + parts := strings.Split(v, "=") + if parts[0] == key { + return + } + } + container.Command.Env = append(container.Command.Env, jv) +} + +// print and error to stderr and exit(1) +func writeError(format string, v ...interface{}) { + fmt.Fprintf(os.Stderr, format, v...) + os.Exit(1) +} + +// getNsFds inspects the container's namespace configuration and opens the fds to +// each of the namespaces. +func getNsFds(container *libcontainer.Container) ([]uintptr, error) { + var ( + namespaces = []string{} + fds = []uintptr{} + ) + + for _, ns := range container.Namespaces { + namespaces = append(namespaces, namespaceFileMap[ns]) + } + + for _, ns := range namespaces { + fd, err := getNsFd(container.NsPid, ns) + if err != nil { + for _, fd = range fds { + syscall.Close(int(fd)) + } + return nil, err + } + fds = append(fds, fd) + } + return fds, nil +} + +// getNsFd returns the fd for a specific pid and namespace option +func getNsFd(pid int, ns string) (uintptr, error) { + nspath := filepath.Join("/proc", strconv.Itoa(pid), "ns", ns) + // OpenFile adds closOnExec + f, err := os.OpenFile(nspath, os.O_RDONLY, 0666) + if err != nil { + return 0, err + } + return f.Fd(), nil +} + +// setupEnvironment adds additional environment variables to the container's +// Command such as USER, LOGNAME, container, and TERM +func setupEnvironment(container *libcontainer.Container) { + addEnvIfNotSet(container, "container", "docker") + // TODO: check if pty + addEnvIfNotSet(container, "TERM", "xterm") + // TODO: get username from container + addEnvIfNotSet(container, "USER", "root") + addEnvIfNotSet(container, "LOGNAME", "root") +} + +func setupUser(container *libcontainer.Container) error { + // TODO: honor user passed on container + if err := setgroups(nil); err != nil { + return err + } + if err := setresgid(0, 0, 0); err != nil { + return err + } + if err := setresuid(0, 0, 0); err != nil { + return err + } + return nil +} + +func getMasterAndConsole(container *libcontainer.Container) (string, *os.File, error) { + master, err := openpmtx() + if err != nil { + return "", nil, err + } + + console, err := ptsname(master) + if err != nil { + master.Close() + return "", nil, err + } + return console, master, nil +} diff --git a/pkg/libcontainer/network/network.go b/pkg/libcontainer/network/network.go new file mode 100644 index 000000000..31c5d3249 --- /dev/null +++ b/pkg/libcontainer/network/network.go @@ -0,0 +1,104 @@ +package network + +import ( + "errors" + "github.com/dotcloud/docker/pkg/netlink" + "net" +) + +var ( + ErrNoDefaultRoute = errors.New("no default network route found") +) + +func InterfaceUp(name string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkLinkUp(iface) +} + +func InterfaceDown(name string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkLinkDown(iface) +} + +func ChangeInterfaceName(old, newName string) error { + iface, err := net.InterfaceByName(old) + if err != nil { + return err + } + return netlink.NetworkChangeName(iface, newName) +} + +func CreateVethPair(name1, name2 string) error { + return netlink.NetworkCreateVethPair(name1, name2) +} + +func SetInterfaceInNamespacePid(name string, nsPid int) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkSetNsPid(iface, nsPid) +} + +func SetInterfaceInNamespaceFd(name string, fd int) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkSetNsFd(iface, fd) +} + +func SetInterfaceMaster(name, master string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + masterIface, err := net.InterfaceByName(master) + if err != nil { + return err + } + return netlink.NetworkSetMaster(iface, masterIface) +} + +func SetDefaultGateway(ip string) error { + return netlink.AddDefaultGw(net.ParseIP(ip)) +} + +func SetInterfaceIp(name string, rawIp string) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + ip, ipNet, err := net.ParseCIDR(rawIp) + if err != nil { + return err + } + return netlink.NetworkLinkAddIp(iface, ip, ipNet) +} + +func SetMtu(name string, mtu int) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkSetMTU(iface, mtu) +} + +func GetDefaultMtu() (int, error) { + routes, err := netlink.NetworkGetRoutes() + if err != nil { + return -1, err + } + for _, r := range routes { + if r.Default { + return r.Iface.MTU, nil + } + } + return -1, ErrNoDefaultRoute +} diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go new file mode 100644 index 000000000..dc207b339 --- /dev/null +++ b/pkg/libcontainer/network/veth.go @@ -0,0 +1,85 @@ +package network + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "os" + "syscall" +) + +// SetupVeth sets up an existing network namespace with the specified +// network configuration. +func SetupVeth(config *libcontainer.Network) error { + if err := InterfaceDown(config.TempVethName); err != nil { + return fmt.Errorf("interface down %s %s", config.TempVethName, err) + } + if err := ChangeInterfaceName(config.TempVethName, "eth0"); err != nil { + return fmt.Errorf("change %s to eth0 %s", config.TempVethName, err) + } + if err := SetInterfaceIp("eth0", config.IP); err != nil { + return fmt.Errorf("set eth0 ip %s", err) + } + + if err := SetMtu("eth0", config.Mtu); err != nil { + return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) + } + if err := InterfaceUp("eth0"); err != nil { + return fmt.Errorf("eth0 up %s", err) + } + + if err := SetMtu("lo", config.Mtu); err != nil { + return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) + } + if err := InterfaceUp("lo"); err != nil { + return fmt.Errorf("lo up %s", err) + } + + if config.Gateway != "" { + if err := SetDefaultGateway(config.Gateway); err != nil { + return fmt.Errorf("set gateway to %s %s", config.Gateway, err) + } + } + return nil +} + +// SetupNamespaceMountDir prepares a new root for use as a mount +// source for bind mounting namespace fd to an outside path +func SetupNamespaceMountDir(root string) error { + if err := os.MkdirAll(root, 0666); err != nil { + return err + } + // make sure mounts are not unmounted by other mnt namespaces + if err := syscall.Mount("", root, "none", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil && err != syscall.EINVAL { + return err + } + if err := syscall.Mount(root, root, "none", syscall.MS_BIND, ""); err != nil { + return err + } + return nil +} + +// CreateNetworkNamespace creates a new network namespace and binds it's fd +// at the binding path +func CreateNetworkNamespace(bindingPath string) error { + f, err := os.OpenFile(bindingPath, os.O_RDONLY|os.O_CREATE|os.O_EXCL, 0) + if err != nil { + return err + } + f.Close() + + if err := namespaces.CreateNewNamespace(libcontainer.CLONE_NEWNET, bindingPath); err != nil { + return err + } + return nil +} + +// DeleteNetworkNamespace unmounts the binding path and removes the +// file so that no references to the fd are present and the network +// namespace is automatically cleaned up +func DeleteNetworkNamespace(bindingPath string) error { + if err := syscall.Unmount(bindingPath, 0); err != nil { + return err + } + return os.Remove(bindingPath) +} diff --git a/pkg/libcontainer/privileged.json b/pkg/libcontainer/privileged.json new file mode 100644 index 000000000..be877ad33 --- /dev/null +++ b/pkg/libcontainer/privileged.json @@ -0,0 +1,22 @@ +{ + "id": "koye", + "namespace_pid": 3745, + "command": { + "args": [ + "/usr/lib/systemd/systemd" + ], + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=" + ] + }, + "rootfs": "/root/main/mycontainer", + "namespaces": [ + "NEWIPC", + "NEWNS", + "NEWPID", + "NEWUTS" + ] +} diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go new file mode 100644 index 000000000..db1c3b973 --- /dev/null +++ b/pkg/libcontainer/types.go @@ -0,0 +1,49 @@ +package libcontainer + +type Namespace string +type Namespaces []Namespace + +func (n Namespaces) Contains(ns Namespace) bool { + for _, nns := range n { + if nns == ns { + return true + } + } + return false +} + +type Capability string +type Capabilities []Capability + +func (c Capabilities) Contains(capp Capability) bool { + for _, cc := range c { + if cc == capp { + return true + } + } + return false +} + +const ( + CAP_SETPCAP Capability = "SETPCAP" + CAP_SYS_MODULE Capability = "SYS_MODULE" + CAP_SYS_RAWIO Capability = "SYS_RAWIO" + CAP_SYS_PACCT Capability = "SYS_PACCT" + CAP_SYS_ADMIN Capability = "SYS_ADMIN" + CAP_SYS_NICE Capability = "SYS_NICE" + CAP_SYS_RESOURCE Capability = "SYS_RESOURCE" + CAP_SYS_TIME Capability = "SYS_TIME" + CAP_SYS_TTY_CONFIG Capability = "SYS_TTY_CONFIG" + CAP_MKNOD Capability = "MKNOD" + CAP_AUDIT_WRITE Capability = "AUDIT_WRITE" + CAP_AUDIT_CONTROL Capability = "AUDIT_CONTROL" + CAP_MAC_OVERRIDE Capability = "MAC_OVERRIDE" + CAP_MAC_ADMIN Capability = "MAC_ADMIN" + + CLONE_NEWNS Namespace = "NEWNS" // mount + CLONE_NEWUTS Namespace = "NEWUTS" // utsname + CLONE_NEWIPC Namespace = "NEWIPC" // ipc + CLONE_NEWUSER Namespace = "NEWUSER" // user + CLONE_NEWPID Namespace = "NEWPID" // pid + CLONE_NEWNET Namespace = "NEWNET" // network +) diff --git a/pkg/libcontainer/ubuntu.json b/pkg/libcontainer/ubuntu.json new file mode 100644 index 000000000..0a450ae06 --- /dev/null +++ b/pkg/libcontainer/ubuntu.json @@ -0,0 +1,22 @@ +{ + "id": "koye", + "namespace_pid": 3745, + "command": { + "args": [ + "/sbin/init" + ], + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm" + ] + }, + "rootfs": "/var/lib/docker/btrfs/subvolumes/7c0f15df1ad2e2fe04d7a6e079aec17406e9465a6a37dd16cb0dd754fc0167b3", + "namespaces": [ + "NEWIPC", + "NEWNS", + "NEWPID", + "NEWUTS" + ] +} diff --git a/pkg/libcontainer/utils/utils.go b/pkg/libcontainer/utils/utils.go new file mode 100644 index 000000000..7289fecf2 --- /dev/null +++ b/pkg/libcontainer/utils/utils.go @@ -0,0 +1,33 @@ +package utils + +import ( + "crypto/rand" + "encoding/hex" + "io" + "os" + "syscall" +) + +func WaitOnPid(pid int) (exitcode int, err error) { + child, err := os.FindProcess(pid) + if err != nil { + return -1, err + } + state, err := child.Wait() + if err != nil { + return -1, err + } + return getExitCode(state), nil +} + +func getExitCode(state *os.ProcessState) int { + return state.Sys().(syscall.WaitStatus).ExitStatus() +} + +func GenerateRandomName(size int) (string, error) { + id := make([]byte, size) + if _, err := io.ReadFull(rand.Reader, id); err != nil { + return "", err + } + return hex.EncodeToString(id), nil +} From 68b049aed4663eb5f6f53241390f7602c1b40c12 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Feb 2014 17:52:06 -0800 Subject: [PATCH 067/282] Make separate nsinit pkg for a dockerinit like init Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 2 + pkg/libcontainer/namespaces/calls_linux.go | 80 +++--- pkg/libcontainer/namespaces/exec.go | 270 +++++++-------------- pkg/libcontainer/namespaces/mount.go | 36 +-- pkg/libcontainer/namespaces/namespaces.go | 40 +-- pkg/libcontainer/namespaces/nsinit/init.go | 140 +++++++++++ pkg/libcontainer/namespaces/utils.go | 24 +- 7 files changed, 285 insertions(+), 307 deletions(-) create mode 100644 pkg/libcontainer/namespaces/nsinit/init.go diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index b77890fb5..dd5e728e6 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,6 +11,8 @@ type Container struct { WorkingDir string `json:"working_dir,omitempty"` Namespaces Namespaces `json:"namespaces,omitempty"` Capabilities Capabilities `json:"capabilities,omitempty"` + Master uintptr `json:"master"` + Console string `json:"console"` } type Command struct { diff --git a/pkg/libcontainer/namespaces/calls_linux.go b/pkg/libcontainer/namespaces/calls_linux.go index 793e940b6..f006d56da 100644 --- a/pkg/libcontainer/namespaces/calls_linux.go +++ b/pkg/libcontainer/namespaces/calls_linux.go @@ -12,19 +12,19 @@ const ( TIOCSPTLCK = 0x40045431 ) -func chroot(dir string) error { +func Chroot(dir string) error { return syscall.Chroot(dir) } -func chdir(dir string) error { +func Chdir(dir string) error { return syscall.Chdir(dir) } -func exec(cmd string, args []string, env []string) error { +func Exec(cmd string, args []string, env []string) error { return syscall.Exec(cmd, args, env) } -func fork() (int, error) { +func Fork() (int, error) { syscall.ForkLock.Lock() pid, _, err := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0) syscall.ForkLock.Unlock() @@ -34,33 +34,23 @@ func fork() (int, error) { return int(pid), nil } -func vfork() (int, error) { - syscall.ForkLock.Lock() - pid, _, err := syscall.Syscall(syscall.SYS_VFORK, 0, 0, 0) - syscall.ForkLock.Unlock() - if err != 0 { - return -1, err - } - return int(pid), nil -} - -func mount(source, target, fstype string, flags uintptr, data string) error { +func Mount(source, target, fstype string, flags uintptr, data string) error { return syscall.Mount(source, target, fstype, flags, data) } -func unmount(target string, flags int) error { +func Unmount(target string, flags int) error { return syscall.Unmount(target, flags) } -func pivotroot(newroot, putold string) error { +func Pivotroot(newroot, putold string) error { return syscall.PivotRoot(newroot, putold) } -func unshare(flags int) error { +func Unshare(flags int) error { return syscall.Unshare(flags) } -func clone(flags uintptr) (int, error) { +func Clone(flags uintptr) (int, error) { syscall.ForkLock.Lock() pid, _, err := syscall.RawSyscall(syscall.SYS_CLONE, flags, 0, 0) syscall.ForkLock.Unlock() @@ -70,7 +60,7 @@ func clone(flags uintptr) (int, error) { return int(pid), nil } -func setns(fd uintptr, flags uintptr) error { +func Setns(fd uintptr, flags uintptr) error { _, _, err := syscall.RawSyscall(SYS_SETNS, fd, flags, 0) if err != 0 { return err @@ -78,87 +68,87 @@ func setns(fd uintptr, flags uintptr) error { return nil } -func usetCloseOnExec(fd uintptr) error { +func UsetCloseOnExec(fd uintptr) error { if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0); err != 0 { return err } return nil } -func setgroups(gids []int) error { +func Setgroups(gids []int) error { return syscall.Setgroups(gids) } -func setresgid(rgid, egid, sgid int) error { +func Setresgid(rgid, egid, sgid int) error { return syscall.Setresgid(rgid, egid, sgid) } -func setresuid(ruid, euid, suid int) error { +func Setresuid(ruid, euid, suid int) error { return syscall.Setresuid(ruid, euid, suid) } -func sethostname(name string) error { +func Sethostname(name string) error { return syscall.Sethostname([]byte(name)) } -func setsid() (int, error) { +func Setsid() (int, error) { return syscall.Setsid() } -func ioctl(fd uintptr, flag, data uintptr) error { +func Unlockpt(f *os.File) error { + var u int + return Ioctl(f.Fd(), TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} + +func Ioctl(fd uintptr, flag, data uintptr) error { if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 { return err } return nil } -func openpmtx() (*os.File, error) { - return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) -} - -func unlockpt(f *os.File) error { - var u int - return ioctl(f.Fd(), TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) -} - -func ptsname(f *os.File) (string, error) { +func Ptsname(f *os.File) (string, error) { var n int - if err := ioctl(f.Fd(), TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { + if err := Ioctl(f.Fd(), TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { return "", err } return fmt.Sprintf("/dev/pts/%d", n), nil } -func closefd(fd uintptr) error { +func Openpmtx() (*os.File, error) { + return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) +} + +func Closefd(fd uintptr) error { return syscall.Close(int(fd)) } -func dup2(fd1, fd2 uintptr) error { +func Dup2(fd1, fd2 uintptr) error { return syscall.Dup2(int(fd1), int(fd2)) } -func mknod(path string, mode uint32, dev int) error { +func Mknod(path string, mode uint32, dev int) error { return syscall.Mknod(path, mode, dev) } -func parentDeathSignal() error { +func ParentDeathSignal() error { if _, _, err := syscall.RawSyscall6(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0, 0, 0, 0); err != 0 { return err } return nil } -func setctty() error { +func Setctty() error { if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 { return err } return nil } -func mkfifo(name string, mode uint32) error { +func Mkfifo(name string, mode uint32) error { return syscall.Mkfifo(name, mode) } -func umask(mask int) int { +func Umask(mask int) int { return syscall.Umask(mask) } diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index 893b30288..0077a0b16 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -8,12 +8,10 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/capabilities" - "github.com/dotcloud/docker/pkg/libcontainer/utils" "io" "log" "os" - "path/filepath" + "os/exec" "syscall" ) @@ -29,89 +27,31 @@ var ( // the container will be spawned with a new network namespace with no configuration. Omiting an // existing network namespace and the CLONE_NEWNET option in the container configuration will allow // the container to the the host's networking options and configuration. -func Exec(container *libcontainer.Container) (pid int, err error) { +func ExecContainer(container *libcontainer.Container) (pid int, err error) { // a user cannot pass CLONE_NEWNET and an existing net namespace fd to join if container.NetNsFd > 0 && container.Namespaces.Contains(libcontainer.CLONE_NEWNET) { return -1, ErrExistingNetworkNamespace } - rootfs, err := resolveRootfs(container) - if err != nil { - return -1, err - } - master, console, err := createMasterAndConsole() if err != nil { return -1, err } - - logger, err := os.OpenFile("/root/logs", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) - if err != nil { - return -1, err - } - log.SetOutput(logger) + container.Console = console + container.Master = master.Fd() // we need CLONE_VFORK so we can wait on the child - flag := getNamespaceFlags(container.Namespaces) | CLONE_VFORK + flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - if pid, err = clone(uintptr(flag | SIGCHLD)); err != nil { - return -1, fmt.Errorf("error cloning process: %s", err) - } - - if pid == 0 { - // welcome to your new namespace ;) - // - // any errors encoutered inside the namespace we should write - // out to a log or a pipe to our parent and exit(1) - // because writing to stderr will not work after we close - if err := closeMasterAndStd(master); err != nil { - writeError("close master and std %s", err) - } - slave, err := openTerminal(console, syscall.O_RDWR) - if err != nil { - writeError("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - writeError("dup2 slave %s", err) - } - - if container.NetNsFd > 0 { - if err := JoinExistingNamespace(container.NetNsFd, libcontainer.CLONE_NEWNET); err != nil { - writeError("join existing net namespace %s", err) - } - } - - if _, err := setsid(); err != nil { - writeError("setsid %s", err) - } - if err := setctty(); err != nil { - writeError("setctty %s", err) - } - if err := parentDeathSignal(); err != nil { - writeError("parent deth signal %s", err) - } - if err := SetupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { - writeError("setup mount namespace %s", err) - } - if err := sethostname(container.ID); err != nil { - writeError("sethostname %s", err) - } - if err := capabilities.DropCapabilities(container); err != nil { - writeError("drop capabilities %s", err) - } - if err := setupUser(container); err != nil { - writeError("setup user %s", err) - } - if container.WorkingDir != "" { - if err := chdir(container.WorkingDir); err != nil { - writeError("chdir to %s %s", container.WorkingDir, err) - } - } - if err := exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { - writeError("exec %s", err) - } - panic("unreachable") + command := exec.Command("/.nsinit") + command.SysProcAttr = &syscall.SysProcAttr{} + command.SysProcAttr.Cloneflags = flag + command.SysProcAttr.Setctty = true + + if err := command.Start(); err != nil { + return -1, err } + pid = command.Process.Pid go func() { if _, err := io.Copy(os.Stdout, master); err != nil { @@ -130,91 +70,86 @@ func Exec(container *libcontainer.Container) (pid int, err error) { // pid and namespace configuration is needed along with the specific capabilities that should // be dropped once inside the namespace. func ExecIn(container *libcontainer.Container, cmd *libcontainer.Command) (int, error) { - if container.NsPid <= 0 { - return -1, libcontainer.ErrInvalidPid - } - - fds, err := getNsFds(container) - if err != nil { - return -1, err - } - - if container.NetNsFd > 0 { - fds = append(fds, container.NetNsFd) - } - - pid, err := fork() - if err != nil { - for _, fd := range fds { - syscall.Close(int(fd)) + return -1, fmt.Errorf("not implemented") + /* + if container.NsPid <= 0 { + return -1, libcontainer.ErrInvalidPid } - return -1, err - } - if pid == 0 { - for _, fd := range fds { - if fd > 0 { - if err := JoinExistingNamespace(fd, ""); err != nil { - for _, fd := range fds { - syscall.Close(int(fd)) + fds, err := getNsFds(container) + if err != nil { + return -1, err + } + + if container.NetNsFd > 0 { + fds = append(fds, container.NetNsFd) + } + + pid, err := fork() + if err != nil { + for _, fd := range fds { + syscall.Close(int(fd)) + } + return -1, err + } + + if pid == 0 { + for _, fd := range fds { + if fd > 0 { + if err := JoinExistingNamespace(fd, ""); err != nil { + for _, fd := range fds { + syscall.Close(int(fd)) + } + writeError("join existing namespace for %d %s", fd, err) } - writeError("join existing namespace for %d %s", fd, err) } - } - syscall.Close(int(fd)) - } - - if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && - container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { - // important: - // - // we need to fork and unshare so that re can remount proc and sys within - // the namespace so the CLONE_NEWPID namespace will take effect - // if we don't fork we would end up unmounting proc and sys for the entire - // namespace - child, err := fork() - if err != nil { - writeError("fork child %s", err) + syscall.Close(int(fd)) } - if child == 0 { - if err := unshare(CLONE_NEWNS); err != nil { - writeError("unshare newns %s", err) + if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && + container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { + // important: + // + // we need to fork and unshare so that re can remount proc and sys within + // the namespace so the CLONE_NEWPID namespace will take effect + // if we don't fork we would end up unmounting proc and sys for the entire + // namespace + child, err := fork() + if err != nil { + writeError("fork child %s", err) } - if err := remountProc(); err != nil { - writeError("remount proc %s", err) - } - if err := remountSys(); err != nil { - writeError("remount sys %s", err) - } - if err := capabilities.DropCapabilities(container); err != nil { - writeError("drop caps %s", err) - } - if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { - writeError("exec %s", err) - } - panic("unreachable") - } - exit, err := utils.WaitOnPid(child) - if err != nil { - writeError("wait on child %s", err) - } - os.Exit(exit) - } - if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { - writeError("exec %s", err) - } - panic("unreachable") - } - return pid, err -} -func resolveRootfs(container *libcontainer.Container) (string, error) { - rootfs, err := filepath.Abs(container.RootFs) - if err != nil { - return "", err - } - return filepath.EvalSymlinks(rootfs) + if child == 0 { + if err := unshare(CLONE_NEWNS); err != nil { + writeError("unshare newns %s", err) + } + if err := remountProc(); err != nil { + writeError("remount proc %s", err) + } + if err := remountSys(); err != nil { + writeError("remount sys %s", err) + } + if err := capabilities.DropCapabilities(container); err != nil { + writeError("drop caps %s", err) + } + if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { + writeError("exec %s", err) + } + panic("unreachable") + } + exit, err := utils.WaitOnPid(child) + if err != nil { + writeError("wait on child %s", err) + } + os.Exit(exit) + } + if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { + writeError("exec %s", err) + } + panic("unreachable") + } + return pid, err + */ } func createMasterAndConsole() (*os.File, string, error) { @@ -223,44 +158,13 @@ func createMasterAndConsole() (*os.File, string, error) { return nil, "", err } - console, err := ptsname(master) + console, err := Ptsname(master) if err != nil { return nil, "", err } - if err := unlockpt(master); err != nil { + if err := Unlockpt(master); err != nil { return nil, "", err } return master, console, nil } - -func closeMasterAndStd(master *os.File) error { - closefd(master.Fd()) - closefd(0) - closefd(1) - closefd(2) - - return nil -} - -func dupSlave(slave *os.File) error { - // we close Stdin,etc so our pty slave should have fd 0 - if slave.Fd() != 0 { - return fmt.Errorf("slave fd not 0 %d", slave.Fd()) - } - if err := dup2(slave.Fd(), 1); err != nil { - return err - } - if err := dup2(slave.Fd(), 2); err != nil { - return err - } - return nil -} - -func openTerminal(name string, flag int) (*os.File, error) { - r, e := syscall.Open(name, flag, 0) - if e != nil { - return nil, &os.PathError{"open", name, e} - } - return os.NewFile(uintptr(r), name), nil -} diff --git a/pkg/libcontainer/namespaces/mount.go b/pkg/libcontainer/namespaces/mount.go index 6d867c91e..8e7c54b04 100644 --- a/pkg/libcontainer/namespaces/mount.go +++ b/pkg/libcontainer/namespaces/mount.go @@ -14,16 +14,16 @@ var ( ) func SetupNewMountNamespace(rootfs, console string, readonly bool) error { - if err := mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { + if err := Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting / as slave %s", err) } - if err := mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { + if err := Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } if readonly { - if err := mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { + if err := Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } } @@ -52,29 +52,29 @@ func SetupNewMountNamespace(rootfs, console string, readonly bool) error { return err } - if err := chdir(rootfs); err != nil { + if err := Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if err := mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + if err := Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { return fmt.Errorf("mount move %s into / %s", rootfs, err) } - if err := chroot("."); err != nil { + if err := Chroot("."); err != nil { return fmt.Errorf("chroot . %s", err) } - if err := chdir("/"); err != nil { + if err := Chdir("/"); err != nil { return fmt.Errorf("chdir / %s", err) } - umask(0022) + Umask(0022) return nil } func copyDevNodes(rootfs string) error { - umask(0000) + Umask(0000) for _, node := range []string{ "null", @@ -95,7 +95,7 @@ func copyDevNodes(rootfs string) error { ) log.Printf("copy %s to %s %d\n", node, dest, st.Rdev) - if err := mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + if err := Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { return fmt.Errorf("copy %s %s", node, err) } } @@ -125,7 +125,7 @@ func setupDev(rootfs string) error { } func setupConsole(rootfs, console string) error { - umask(0000) + Umask(0000) stat, err := os.Stat(console) if err != nil { @@ -145,11 +145,11 @@ func setupConsole(rootfs, console string) error { return err } - if err := mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + if err := Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { return fmt.Errorf("mknod %s %s", dest, err) } - if err := mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { + if err := Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind %s to %s %s", console, dest, err) } return nil @@ -176,7 +176,7 @@ func mountSystem(rootfs string) error { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } - if err := mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil { + if err := Mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil { return fmt.Errorf("mounting %s into %s %s", m.source, m.path, err) } } @@ -184,22 +184,22 @@ func mountSystem(rootfs string) error { } func remountProc() error { - if err := unmount("/proc", syscall.MNT_DETACH); err != nil { + if err := Unmount("/proc", syscall.MNT_DETACH); err != nil { return err } - if err := mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { + if err := Mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { return err } return nil } func remountSys() error { - if err := unmount("/sys", syscall.MNT_DETACH); err != nil { + if err := Unmount("/sys", syscall.MNT_DETACH); err != nil { if err != syscall.EINVAL { return err } } else { - if err := mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { + if err := Mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { return err } } diff --git a/pkg/libcontainer/namespaces/namespaces.go b/pkg/libcontainer/namespaces/namespaces.go index 2a5084701..05ef0ac7a 100644 --- a/pkg/libcontainer/namespaces/namespaces.go +++ b/pkg/libcontainer/namespaces/namespaces.go @@ -9,52 +9,14 @@ package namespaces import ( - "fmt" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/utils" - "os" - "path/filepath" - "syscall" ) -// CreateNewNamespace creates a new namespace and binds it's fd to the specified path -func CreateNewNamespace(namespace libcontainer.Namespace, bindTo string) error { - var ( - flag = namespaceMap[namespace] - name = namespaceFileMap[namespace] - nspath = filepath.Join("/proc/self/ns", name) - ) - // TODO: perform validation on name and flag - - pid, err := fork() - if err != nil { - return err - } - - if pid == 0 { - if err := unshare(flag); err != nil { - writeError("unshare %s", err) - } - if err := mount(nspath, bindTo, "none", syscall.MS_BIND, ""); err != nil { - writeError("bind mount %s", err) - } - os.Exit(0) - } - exit, err := utils.WaitOnPid(pid) - if err != nil { - return err - } - if exit != 0 { - return fmt.Errorf("exit status %d", exit) - } - return err -} - // JoinExistingNamespace uses the fd of an existing linux namespace and // has the current process join that namespace or the spacespace specified by ns func JoinExistingNamespace(fd uintptr, ns libcontainer.Namespace) error { flag := namespaceMap[ns] - if err := setns(fd, uintptr(flag)); err != nil { + if err := Setns(fd, uintptr(flag)); err != nil { return err } return nil diff --git a/pkg/libcontainer/namespaces/nsinit/init.go b/pkg/libcontainer/namespaces/nsinit/init.go new file mode 100644 index 000000000..9a7563642 --- /dev/null +++ b/pkg/libcontainer/namespaces/nsinit/init.go @@ -0,0 +1,140 @@ +package nsinit + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "log" + "os" + "path/filepath" + "syscall" +) + +// InitNamespace should be run inside an existing namespace to setup +// common mounts, drop capabilities, and setup network interfaces +func InitNamespace(container *libcontainer.Container) error { + rootfs, err := resolveRootfs(container) + if err != nil { + return err + } + + // any errors encoutered inside the namespace we should write + // out to a log or a pipe to our parent and exit(1) + // because writing to stderr will not work after we close + if err := closeMasterAndStd(container.Master); err != nil { + log.Fatalf("close master and std %s", err) + return err + } + + slave, err := openTerminal(container.Console, syscall.O_RDWR) + if err != nil { + log.Fatalf("open terminal %s", err) + return err + } + if err := dupSlave(slave); err != nil { + log.Fatalf("dup2 slave %s", err) + return err + } + + /* + if container.NetNsFd > 0 { + if err := joinExistingNamespace(container.NetNsFd, libcontainer.CLONE_NEWNET); err != nil { + log.Fatalf("join existing net namespace %s", err) + } + } + */ + + if _, err := namespaces.Setsid(); err != nil { + log.Fatalf("setsid %s", err) + return err + } + if err := namespaces.Setctty(); err != nil { + log.Fatalf("setctty %s", err) + return err + } + if err := namespaces.ParentDeathSignal(); err != nil { + log.Fatalf("parent deth signal %s", err) + return err + } + if err := namespaces.SetupNewMountNamespace(rootfs, container.Console, container.ReadonlyFs); err != nil { + log.Fatalf("setup mount namespace %s", err) + return err + } + if err := namespaces.Sethostname(container.ID); err != nil { + log.Fatalf("sethostname %s", err) + return err + } + if err := capabilities.DropCapabilities(container); err != nil { + log.Fatalf("drop capabilities %s", err) + return err + } + if err := setupUser(container); err != nil { + log.Fatalf("setup user %s", err) + return err + } + if container.WorkingDir != "" { + if err := namespaces.Chdir(container.WorkingDir); err != nil { + log.Fatalf("chdir to %s %s", container.WorkingDir, err) + return err + } + } + if err := namespaces.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + log.Fatalf("exec %s", err) + return err + } + panic("unreachable") +} + +func resolveRootfs(container *libcontainer.Container) (string, error) { + rootfs, err := filepath.Abs(container.RootFs) + if err != nil { + return "", err + } + return filepath.EvalSymlinks(rootfs) +} + +func closeMasterAndStd(master uintptr) error { + namespaces.Closefd(master) + namespaces.Closefd(0) + namespaces.Closefd(1) + namespaces.Closefd(2) + + return nil +} + +func setupUser(container *libcontainer.Container) error { + // TODO: honor user passed on container + if err := namespaces.Setgroups(nil); err != nil { + return err + } + if err := namespaces.Setresgid(0, 0, 0); err != nil { + return err + } + if err := namespaces.Setresuid(0, 0, 0); err != nil { + return err + } + return nil +} + +func dupSlave(slave *os.File) error { + // we close Stdin,etc so our pty slave should have fd 0 + if slave.Fd() != 0 { + return fmt.Errorf("slave fd not 0 %d", slave.Fd()) + } + if err := namespaces.Dup2(slave.Fd(), 1); err != nil { + return err + } + if err := namespaces.Dup2(slave.Fd(), 2); err != nil { + return err + } + return nil +} + +func openTerminal(name string, flag int) (*os.File, error) { + r, e := syscall.Open(name, flag, 0) + if e != nil { + return nil, &os.PathError{"open", name, e} + } + return os.NewFile(uintptr(r), name), nil +} diff --git a/pkg/libcontainer/namespaces/utils.go b/pkg/libcontainer/namespaces/utils.go index 438d89648..fd195c0ad 100644 --- a/pkg/libcontainer/namespaces/utils.go +++ b/pkg/libcontainer/namespaces/utils.go @@ -26,12 +26,6 @@ func addEnvIfNotSet(container *libcontainer.Container, key, value string) { container.Command.Env = append(container.Command.Env, jv) } -// print and error to stderr and exit(1) -func writeError(format string, v ...interface{}) { - fmt.Fprintf(os.Stderr, format, v...) - os.Exit(1) -} - // getNsFds inspects the container's namespace configuration and opens the fds to // each of the namespaces. func getNsFds(container *libcontainer.Container) ([]uintptr, error) { @@ -79,27 +73,13 @@ func setupEnvironment(container *libcontainer.Container) { addEnvIfNotSet(container, "LOGNAME", "root") } -func setupUser(container *libcontainer.Container) error { - // TODO: honor user passed on container - if err := setgroups(nil); err != nil { - return err - } - if err := setresgid(0, 0, 0); err != nil { - return err - } - if err := setresuid(0, 0, 0); err != nil { - return err - } - return nil -} - func getMasterAndConsole(container *libcontainer.Container) (string, *os.File, error) { - master, err := openpmtx() + master, err := Openpmtx() if err != nil { return "", nil, err } - console, err := ptsname(master) + console, err := Ptsname(master) if err != nil { master.Close() return "", nil, err From 72e65b654b75d5087e50cc6366e78bd2f8318bae Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Feb 2014 18:15:41 -0800 Subject: [PATCH 068/282] WIP moving to nsini Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cli/main.go | 78 +++++++++++++++++------------ pkg/libcontainer/namespaces/exec.go | 10 ++-- pkg/libcontainer/network/veth.go | 16 ------ 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/pkg/libcontainer/cli/main.go b/pkg/libcontainer/cli/main.go index 490135ef5..0430e2943 100644 --- a/pkg/libcontainer/cli/main.go +++ b/pkg/libcontainer/cli/main.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "github.com/dotcloud/docker/pkg/libcontainer/namespaces/nsinit" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/utils" "os" @@ -15,15 +16,26 @@ var ( displayPid bool newCommand string usrNet bool + masterFd int + console string ) func init() { flag.BoolVar(&displayPid, "pid", false, "display the pid before waiting") flag.StringVar(&newCommand, "cmd", "/bin/bash", "command to run in the existing namespace") flag.BoolVar(&usrNet, "net", false, "user a net namespace") + flag.IntVar(&masterFd, "master", 0, "master fd") + flag.StringVar(&console, "console", "", "console path") flag.Parse() } +func nsinitFunc(container *libcontainer.Container) error { + container.Master = uintptr(masterFd) + container.Console = console + + return nsinit.InitNamespace(container) +} + func exec(container *libcontainer.Container) error { var ( netFile *os.File @@ -39,7 +51,7 @@ func exec(container *libcontainer.Container) error { container.NetNsFd = netFile.Fd() } - pid, err := namespaces.Exec(container) + pid, err := namespaces.ExecContainer(container) if err != nil { return fmt.Errorf("error exec container %s", err) } @@ -87,39 +99,39 @@ func execIn(container *libcontainer.Container) error { } func createNet(config *libcontainer.Network) error { - root := "/root/nsroot" - if err := network.SetupNamespaceMountDir(root); err != nil { - return err - } - - nspath := root + "/test" - if err := network.CreateNetworkNamespace(nspath); err != nil { - return nil - } - if err := network.CreateVethPair("veth0", config.TempVethName); err != nil { - return err - } - if err := network.SetInterfaceMaster("veth0", config.Bridge); err != nil { - return err - } - if err := network.InterfaceUp("veth0"); err != nil { - return err - } - - f, err := os.Open(nspath) - if err != nil { - return err - } - defer f.Close() - - if err := network.SetInterfaceInNamespaceFd("veth1", int(f.Fd())); err != nil { - return err - } - /* - if err := network.SetupVethInsideNamespace(f.Fd(), config); err != nil { + root := "/root/nsroot" + if err := network.SetupNamespaceMountDir(root); err != nil { return err } + + nspath := root + "/test" + if err := network.CreateNetworkNamespace(nspath); err != nil { + return nil + } + if err := network.CreateVethPair("veth0", config.TempVethName); err != nil { + return err + } + if err := network.SetInterfaceMaster("veth0", config.Bridge); err != nil { + return err + } + if err := network.InterfaceUp("veth0"); err != nil { + return err + } + + f, err := os.Open(nspath) + if err != nil { + return err + } + defer f.Close() + + if err := network.SetInterfaceInNamespaceFd("veth1", int(f.Fd())); err != nil { + return err + } + + if err := network.SetupVethInsideNamespace(f.Fd(), config); err != nil { + return err + } */ return nil } @@ -133,7 +145,7 @@ func main() { var ( err error cliCmd = flag.Arg(0) - config = flag.Arg(1) + config = "/root/development/gocode/src/github.com/dotcloud/docker/pkg/libcontainer/container.json" //flag.Arg(1) ) f, err := os.Open(config) if err != nil { @@ -149,6 +161,8 @@ func main() { f.Close() switch cliCmd { + case "init": + err = nsinitFunc(container) case "exec": err = exec(container) case "execin": diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index 0077a0b16..93b155ba2 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -12,6 +12,8 @@ import ( "log" "os" "os/exec" + "path/filepath" + "strconv" "syscall" ) @@ -37,16 +39,15 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { if err != nil { return -1, err } - container.Console = console - container.Master = master.Fd() + nsinit := filepath.Join(container.RootFs, ".nsinit") // we need CLONE_VFORK so we can wait on the child flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - command := exec.Command("/.nsinit") + command := exec.Command(nsinit, "init", "-master", strconv.Itoa(int(master.Fd())), "-console", console) command.SysProcAttr = &syscall.SysProcAttr{} command.SysProcAttr.Cloneflags = flag - command.SysProcAttr.Setctty = true + // command.SysProcAttr.Setctty = true if err := command.Start(); err != nil { return -1, err @@ -63,6 +64,7 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { log.Println(err) } }() + command.Wait() return pid, nil } diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go index dc207b339..2ecce22c3 100644 --- a/pkg/libcontainer/network/veth.go +++ b/pkg/libcontainer/network/veth.go @@ -3,7 +3,6 @@ package network import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" "os" "syscall" ) @@ -59,21 +58,6 @@ func SetupNamespaceMountDir(root string) error { return nil } -// CreateNetworkNamespace creates a new network namespace and binds it's fd -// at the binding path -func CreateNetworkNamespace(bindingPath string) error { - f, err := os.OpenFile(bindingPath, os.O_RDONLY|os.O_CREATE|os.O_EXCL, 0) - if err != nil { - return err - } - f.Close() - - if err := namespaces.CreateNewNamespace(libcontainer.CLONE_NEWNET, bindingPath); err != nil { - return err - } - return nil -} - // DeleteNetworkNamespace unmounts the binding path and removes the // file so that no references to the fd are present and the network // namespace is automatically cleaned up From 11429457691be3b009c6d9f4cc9fce9150d4e810 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 10:44:29 -0800 Subject: [PATCH 069/282] Use nsinit for setting up namespace Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cli/main.go | 1 + pkg/libcontainer/container.go | 1 + pkg/libcontainer/namespaces/exec.go | 4 ++-- pkg/libcontainer/namespaces/nsinit/init.go | 13 +++++++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/cli/main.go b/pkg/libcontainer/cli/main.go index 0430e2943..ac0ea2992 100644 --- a/pkg/libcontainer/cli/main.go +++ b/pkg/libcontainer/cli/main.go @@ -32,6 +32,7 @@ func init() { func nsinitFunc(container *libcontainer.Container) error { container.Master = uintptr(masterFd) container.Console = console + container.LogFile = "/root/logs" return nsinit.InitNamespace(container) } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index dd5e728e6..c9a3f2e90 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -13,6 +13,7 @@ type Container struct { Capabilities Capabilities `json:"capabilities,omitempty"` Master uintptr `json:"master"` Console string `json:"console"` + LogFile string `json:"log_file"` } type Command struct { diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index 93b155ba2..7f4b4a609 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -44,9 +44,10 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { // we need CLONE_VFORK so we can wait on the child flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - command := exec.Command(nsinit, "init", "-master", strconv.Itoa(int(master.Fd())), "-console", console) + command := exec.Command(nsinit, "-master", strconv.Itoa(int(master.Fd())), "-console", console, "init") command.SysProcAttr = &syscall.SysProcAttr{} command.SysProcAttr.Cloneflags = flag + command.ExtraFiles = []*os.File{master} // command.SysProcAttr.Setctty = true if err := command.Start(); err != nil { @@ -64,7 +65,6 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { log.Println(err) } }() - command.Wait() return pid, nil } diff --git a/pkg/libcontainer/namespaces/nsinit/init.go b/pkg/libcontainer/namespaces/nsinit/init.go index 9a7563642..ae6159b45 100644 --- a/pkg/libcontainer/namespaces/nsinit/init.go +++ b/pkg/libcontainer/namespaces/nsinit/init.go @@ -14,6 +14,10 @@ import ( // InitNamespace should be run inside an existing namespace to setup // common mounts, drop capabilities, and setup network interfaces func InitNamespace(container *libcontainer.Container) error { + if err := setLogFile(container); err != nil { + return err + } + rootfs, err := resolveRootfs(container) if err != nil { return err @@ -138,3 +142,12 @@ func openTerminal(name string, flag int) (*os.File, error) { } return os.NewFile(uintptr(r), name), nil } + +func setLogFile(container *libcontainer.Container) error { + f, err := os.OpenFile(container.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0655) + if err != nil { + return err + } + log.SetOutput(f) + return nil +} From 93d41e53ae64fd77340c31d3bc5531864ed28779 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 18 Feb 2014 23:13:36 -0800 Subject: [PATCH 070/282] Improve general quality of libcontainer Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/cli/main.go | 65 ++++++----- pkg/libcontainer/container.json | 2 +- pkg/libcontainer/namespaces/exec.go | 101 ++---------------- pkg/libcontainer/namespaces/mount.go | 44 ++++---- pkg/libcontainer/namespaces/namespaces.go | 32 ------ pkg/libcontainer/namespaces/ns_linux.go | 9 ++ pkg/libcontainer/namespaces/nsinit/init.go | 41 +++---- pkg/libcontainer/namespaces/utils.go | 14 --- .../namespaces => system}/calls_linux.go | 37 +------ pkg/system/pty_linux.go | 31 ++++++ pkg/system/setns_linux.go | 13 +++ pkg/system/setns_linux_amd64.go | 8 ++ 12 files changed, 159 insertions(+), 238 deletions(-) delete mode 100644 pkg/libcontainer/namespaces/namespaces.go rename pkg/{libcontainer/namespaces => system}/calls_linux.go (74%) create mode 100644 pkg/system/pty_linux.go create mode 100644 pkg/system/setns_linux.go create mode 100644 pkg/system/setns_linux_amd64.go diff --git a/pkg/libcontainer/cli/main.go b/pkg/libcontainer/cli/main.go index ac0ea2992..93bb0399f 100644 --- a/pkg/libcontainer/cli/main.go +++ b/pkg/libcontainer/cli/main.go @@ -10,6 +10,9 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/utils" "os" + exec_ "os/exec" + "path" + "path/filepath" ) var ( @@ -52,6 +55,18 @@ func exec(container *libcontainer.Container) error { container.NetNsFd = netFile.Fd() } + self, err := exec_.LookPath(os.Args[0]) + if err != nil { + return err + } + if output, err := exec_.Command("cp", self, path.Join(container.RootFs, ".nsinit")).CombinedOutput(); err != nil { + return fmt.Errorf("Error exec cp: %s, (%s)", err, output) + } else { + println(self, container.RootFs) + fmt.Printf("-----> %s\n", output) + } + println("----") + pid, err := namespaces.ExecContainer(container) if err != nil { return fmt.Errorf("error exec container %s", err) @@ -77,25 +92,25 @@ func exec(container *libcontainer.Container) error { } func execIn(container *libcontainer.Container) error { - f, err := os.Open("/root/nsroot/test") - if err != nil { - return err - } - container.NetNsFd = f.Fd() - pid, err := namespaces.ExecIn(container, &libcontainer.Command{ - Env: container.Command.Env, - Args: []string{ - newCommand, - }, - }) - if err != nil { - return fmt.Errorf("error exexin container %s", err) - } - exitcode, err := utils.WaitOnPid(pid) - if err != nil { - return fmt.Errorf("error waiting on child %s", err) - } - os.Exit(exitcode) + // f, err := os.Open("/root/nsroot/test") + // if err != nil { + // return err + // } + // container.NetNsFd = f.Fd() + // pid, err := namespaces.ExecIn(container, &libcontainer.Command{ + // Env: container.Command.Env, + // Args: []string{ + // newCommand, + // }, + // }) + // if err != nil { + // return fmt.Errorf("error exexin container %s", err) + // } + // exitcode, err := utils.WaitOnPid(pid) + // if err != nil { + // return fmt.Errorf("error waiting on child %s", err) + // } + // os.Exit(exitcode) return nil } @@ -143,11 +158,13 @@ func printErr(err error) { } func main() { - var ( - err error - cliCmd = flag.Arg(0) - config = "/root/development/gocode/src/github.com/dotcloud/docker/pkg/libcontainer/container.json" //flag.Arg(1) - ) + cliCmd := flag.Arg(0) + + config, err := filepath.Abs(flag.Arg(1)) + if err != nil { + printErr(err) + } + println("cli:", cliCmd, "config:", config) f, err := os.Open(config) if err != nil { printErr(err) diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index ed8eb1bd7..6e4fda54c 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -12,7 +12,7 @@ "TERM=xterm" ] }, - "rootfs": "/root/main/mycontainer", + "rootfs": "/var/lib/docker/containers/ee76122136d691d63e09d24168a91ddb2ef9fdcf210b4de5c50aa76354892f4b/root", "namespaces": [ "NEWIPC", "NEWNS", diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index 7f4b4a609..ea3d2caa7 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -6,8 +6,8 @@ package namespaces import ( "errors" - "fmt" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/system" "io" "log" "os" @@ -44,12 +44,15 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { // we need CLONE_VFORK so we can wait on the child flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - command := exec.Command(nsinit, "-master", strconv.Itoa(int(master.Fd())), "-console", console, "init") + command := exec.Command(nsinit, "-master", strconv.Itoa(int(master.Fd())), "-console", console, "init", "container.json") + // command.Stdin = os.Stdin + // command.Stdout = os.Stdout + // command.Stderr = os.Stderr command.SysProcAttr = &syscall.SysProcAttr{} command.SysProcAttr.Cloneflags = flag - command.ExtraFiles = []*os.File{master} - // command.SysProcAttr.Setctty = true + //command.ExtraFiles = []*os.File{master} + println("vvvvvvvvv") if err := command.Start(); err != nil { return -1, err } @@ -68,104 +71,18 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { return pid, nil } -// ExecIn will spawn a new command inside an existing container's namespaces. The existing container's -// pid and namespace configuration is needed along with the specific capabilities that should -// be dropped once inside the namespace. -func ExecIn(container *libcontainer.Container, cmd *libcontainer.Command) (int, error) { - return -1, fmt.Errorf("not implemented") - /* - if container.NsPid <= 0 { - return -1, libcontainer.ErrInvalidPid - } - - fds, err := getNsFds(container) - if err != nil { - return -1, err - } - - if container.NetNsFd > 0 { - fds = append(fds, container.NetNsFd) - } - - pid, err := fork() - if err != nil { - for _, fd := range fds { - syscall.Close(int(fd)) - } - return -1, err - } - - if pid == 0 { - for _, fd := range fds { - if fd > 0 { - if err := JoinExistingNamespace(fd, ""); err != nil { - for _, fd := range fds { - syscall.Close(int(fd)) - } - writeError("join existing namespace for %d %s", fd, err) - } - } - syscall.Close(int(fd)) - } - - if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && - container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { - // important: - // - // we need to fork and unshare so that re can remount proc and sys within - // the namespace so the CLONE_NEWPID namespace will take effect - // if we don't fork we would end up unmounting proc and sys for the entire - // namespace - child, err := fork() - if err != nil { - writeError("fork child %s", err) - } - - if child == 0 { - if err := unshare(CLONE_NEWNS); err != nil { - writeError("unshare newns %s", err) - } - if err := remountProc(); err != nil { - writeError("remount proc %s", err) - } - if err := remountSys(); err != nil { - writeError("remount sys %s", err) - } - if err := capabilities.DropCapabilities(container); err != nil { - writeError("drop caps %s", err) - } - if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { - writeError("exec %s", err) - } - panic("unreachable") - } - exit, err := utils.WaitOnPid(child) - if err != nil { - writeError("wait on child %s", err) - } - os.Exit(exit) - } - if err := exec(cmd.Args[0], cmd.Args[0:], cmd.Env); err != nil { - writeError("exec %s", err) - } - panic("unreachable") - } - return pid, err - */ -} - func createMasterAndConsole() (*os.File, string, error) { master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) if err != nil { return nil, "", err } - console, err := Ptsname(master) + console, err := system.Ptsname(master) if err != nil { return nil, "", err } - if err := Unlockpt(master); err != nil { + if err := system.Unlockpt(master); err != nil { return nil, "", err } return master, console, nil diff --git a/pkg/libcontainer/namespaces/mount.go b/pkg/libcontainer/namespaces/mount.go index 8e7c54b04..a9b981ecd 100644 --- a/pkg/libcontainer/namespaces/mount.go +++ b/pkg/libcontainer/namespaces/mount.go @@ -2,6 +2,7 @@ package namespaces import ( "fmt" + "github.com/dotcloud/docker/pkg/system" "log" "os" "path/filepath" @@ -14,16 +15,16 @@ var ( ) func SetupNewMountNamespace(rootfs, console string, readonly bool) error { - if err := Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { + if err := system.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting / as slave %s", err) } - if err := Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { + if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } if readonly { - if err := Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { + if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } } @@ -52,29 +53,30 @@ func SetupNewMountNamespace(rootfs, console string, readonly bool) error { return err } - if err := Chdir(rootfs); err != nil { + if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if err := Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { return fmt.Errorf("mount move %s into / %s", rootfs, err) } - if err := Chroot("."); err != nil { + if err := system.Chroot("."); err != nil { return fmt.Errorf("chroot . %s", err) } - if err := Chdir("/"); err != nil { + if err := system.Chdir("/"); err != nil { return fmt.Errorf("chdir / %s", err) } - Umask(0022) + system.Umask(0022) return nil } func copyDevNodes(rootfs string) error { - Umask(0000) + oldMask := system.Umask(0000) + defer system.Umask(oldMask) for _, node := range []string{ "null", @@ -95,7 +97,7 @@ func copyDevNodes(rootfs string) error { ) log.Printf("copy %s to %s %d\n", node, dest, st.Rdev) - if err := Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { return fmt.Errorf("copy %s %s", node, err) } } @@ -125,7 +127,8 @@ func setupDev(rootfs string) error { } func setupConsole(rootfs, console string) error { - Umask(0000) + oldMask := system.Umask(0000) + defer system.Umask(oldMask) stat, err := os.Stat(console) if err != nil { @@ -145,11 +148,11 @@ func setupConsole(rootfs, console string) error { return err } - if err := Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { return fmt.Errorf("mknod %s %s", dest, err) } - if err := Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { + if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind %s to %s %s", console, dest, err) } return nil @@ -158,7 +161,7 @@ func setupConsole(rootfs, console string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string) error { - mounts := []struct { + for _, m := range []struct { source string path string device string @@ -171,12 +174,11 @@ func mountSystem(rootfs string) error { {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaults, data: "mode=1777"}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"}, {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_STRICTATIME, data: "mode=755"}, - } - for _, m := range mounts { + } { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } - if err := Mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil { + if err := system.Mount(m.source, m.path, m.device, uintptr(m.flags), m.data); err != nil { return fmt.Errorf("mounting %s into %s %s", m.source, m.path, err) } } @@ -184,22 +186,22 @@ func mountSystem(rootfs string) error { } func remountProc() error { - if err := Unmount("/proc", syscall.MNT_DETACH); err != nil { + if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { return err } - if err := Mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { + if err := system.Mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { return err } return nil } func remountSys() error { - if err := Unmount("/sys", syscall.MNT_DETACH); err != nil { + if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { if err != syscall.EINVAL { return err } } else { - if err := Mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { return err } } diff --git a/pkg/libcontainer/namespaces/namespaces.go b/pkg/libcontainer/namespaces/namespaces.go deleted file mode 100644 index 05ef0ac7a..000000000 --- a/pkg/libcontainer/namespaces/namespaces.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - TODO - pivot root - cgroups - more mount stuff that I probably am forgetting - apparmor -*/ - -package namespaces - -import ( - "github.com/dotcloud/docker/pkg/libcontainer" -) - -// JoinExistingNamespace uses the fd of an existing linux namespace and -// has the current process join that namespace or the spacespace specified by ns -func JoinExistingNamespace(fd uintptr, ns libcontainer.Namespace) error { - flag := namespaceMap[ns] - if err := Setns(fd, uintptr(flag)); err != nil { - return err - } - return nil -} - -// getNamespaceFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare, and setns -func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { - for _, ns := range namespaces { - flag |= namespaceMap[ns] - } - return -} diff --git a/pkg/libcontainer/namespaces/ns_linux.go b/pkg/libcontainer/namespaces/ns_linux.go index b0e511913..f61279334 100644 --- a/pkg/libcontainer/namespaces/ns_linux.go +++ b/pkg/libcontainer/namespaces/ns_linux.go @@ -33,3 +33,12 @@ var namespaceFileMap = map[libcontainer.Namespace]string{ libcontainer.CLONE_NEWPID: "pid", libcontainer.CLONE_NEWNET: "net", } + +// getNamespaceFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare, and setns +func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { + for _, ns := range namespaces { + flag |= namespaceMap[ns] + } + return +} diff --git a/pkg/libcontainer/namespaces/nsinit/init.go b/pkg/libcontainer/namespaces/nsinit/init.go index ae6159b45..7f85ebacd 100644 --- a/pkg/libcontainer/namespaces/nsinit/init.go +++ b/pkg/libcontainer/namespaces/nsinit/init.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/namespaces" + "github.com/dotcloud/docker/pkg/system" "log" "os" "path/filepath" @@ -14,10 +15,12 @@ import ( // InitNamespace should be run inside an existing namespace to setup // common mounts, drop capabilities, and setup network interfaces func InitNamespace(container *libcontainer.Container) error { + println("|||||||||||||") if err := setLogFile(container); err != nil { return err } - + println(container.LogFile) + log.Printf("--------->") rootfs, err := resolveRootfs(container) if err != nil { return err @@ -26,7 +29,7 @@ func InitNamespace(container *libcontainer.Container) error { // any errors encoutered inside the namespace we should write // out to a log or a pipe to our parent and exit(1) // because writing to stderr will not work after we close - if err := closeMasterAndStd(container.Master); err != nil { + if err := closeMasterAndStd(os.NewFile(container.Master, "/dev/ptmx")); err != nil { log.Fatalf("close master and std %s", err) return err } @@ -49,15 +52,15 @@ func InitNamespace(container *libcontainer.Container) error { } */ - if _, err := namespaces.Setsid(); err != nil { + if _, err := system.Setsid(); err != nil { log.Fatalf("setsid %s", err) return err } - if err := namespaces.Setctty(); err != nil { + if err := system.Setctty(); err != nil { log.Fatalf("setctty %s", err) return err } - if err := namespaces.ParentDeathSignal(); err != nil { + if err := system.ParentDeathSignal(); err != nil { log.Fatalf("parent deth signal %s", err) return err } @@ -65,7 +68,7 @@ func InitNamespace(container *libcontainer.Container) error { log.Fatalf("setup mount namespace %s", err) return err } - if err := namespaces.Sethostname(container.ID); err != nil { + if err := system.Sethostname(container.ID); err != nil { log.Fatalf("sethostname %s", err) return err } @@ -78,12 +81,12 @@ func InitNamespace(container *libcontainer.Container) error { return err } if container.WorkingDir != "" { - if err := namespaces.Chdir(container.WorkingDir); err != nil { + if err := system.Chdir(container.WorkingDir); err != nil { log.Fatalf("chdir to %s %s", container.WorkingDir, err) return err } } - if err := namespaces.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { log.Fatalf("exec %s", err) return err } @@ -98,24 +101,23 @@ func resolveRootfs(container *libcontainer.Container) (string, error) { return filepath.EvalSymlinks(rootfs) } -func closeMasterAndStd(master uintptr) error { - namespaces.Closefd(master) - namespaces.Closefd(0) - namespaces.Closefd(1) - namespaces.Closefd(2) - +func closeMasterAndStd(master *os.File) error { + master.Close() + os.Stdin.Close() + os.Stdout.Close() + os.Stderr.Close() return nil } func setupUser(container *libcontainer.Container) error { // TODO: honor user passed on container - if err := namespaces.Setgroups(nil); err != nil { + if err := system.Setgroups(nil); err != nil { return err } - if err := namespaces.Setresgid(0, 0, 0); err != nil { + if err := system.Setresgid(0, 0, 0); err != nil { return err } - if err := namespaces.Setresuid(0, 0, 0); err != nil { + if err := system.Setresuid(0, 0, 0); err != nil { return err } return nil @@ -126,15 +128,16 @@ func dupSlave(slave *os.File) error { if slave.Fd() != 0 { return fmt.Errorf("slave fd not 0 %d", slave.Fd()) } - if err := namespaces.Dup2(slave.Fd(), 1); err != nil { + if err := system.Dup2(slave.Fd(), 1); err != nil { return err } - if err := namespaces.Dup2(slave.Fd(), 2); err != nil { + if err := system.Dup2(slave.Fd(), 2); err != nil { return err } return nil } +// openTerminal is a clone of os.OpenFile without the O_CLOEXEC addition. func openTerminal(name string, flag int) (*os.File, error) { r, e := syscall.Open(name, flag, 0) if e != nil { diff --git a/pkg/libcontainer/namespaces/utils.go b/pkg/libcontainer/namespaces/utils.go index fd195c0ad..a5d677c7b 100644 --- a/pkg/libcontainer/namespaces/utils.go +++ b/pkg/libcontainer/namespaces/utils.go @@ -72,17 +72,3 @@ func setupEnvironment(container *libcontainer.Container) { addEnvIfNotSet(container, "USER", "root") addEnvIfNotSet(container, "LOGNAME", "root") } - -func getMasterAndConsole(container *libcontainer.Container) (string, *os.File, error) { - master, err := Openpmtx() - if err != nil { - return "", nil, err - } - - console, err := Ptsname(master) - if err != nil { - master.Close() - return "", nil, err - } - return console, master, nil -} diff --git a/pkg/libcontainer/namespaces/calls_linux.go b/pkg/system/calls_linux.go similarity index 74% rename from pkg/libcontainer/namespaces/calls_linux.go rename to pkg/system/calls_linux.go index f006d56da..42afa349c 100644 --- a/pkg/libcontainer/namespaces/calls_linux.go +++ b/pkg/system/calls_linux.go @@ -1,15 +1,7 @@ -package namespaces +package system import ( - "fmt" - "os" "syscall" - "unsafe" -) - -const ( - TIOCGPTN = 0x80045430 - TIOCSPTLCK = 0x40045431 ) func Chroot(dir string) error { @@ -60,14 +52,6 @@ func Clone(flags uintptr) (int, error) { return int(pid), nil } -func Setns(fd uintptr, flags uintptr) error { - _, _, err := syscall.RawSyscall(SYS_SETNS, fd, flags, 0) - if err != 0 { - return err - } - return nil -} - func UsetCloseOnExec(fd uintptr) error { if _, _, err := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0); err != 0 { return err @@ -95,11 +79,6 @@ func Setsid() (int, error) { return syscall.Setsid() } -func Unlockpt(f *os.File) error { - var u int - return Ioctl(f.Fd(), TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) -} - func Ioctl(fd uintptr, flag, data uintptr) error { if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 { return err @@ -107,18 +86,6 @@ func Ioctl(fd uintptr, flag, data uintptr) error { return nil } -func Ptsname(f *os.File) (string, error) { - var n int - if err := Ioctl(f.Fd(), TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { - return "", err - } - return fmt.Sprintf("/dev/pts/%d", n), nil -} - -func Openpmtx() (*os.File, error) { - return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) -} - func Closefd(fd uintptr) error { return syscall.Close(int(fd)) } @@ -132,7 +99,7 @@ func Mknod(path string, mode uint32, dev int) error { } func ParentDeathSignal() error { - if _, _, err := syscall.RawSyscall6(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0, 0, 0, 0); err != 0 { + if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, uintptr(syscall.SIGKILL), 0); err != 0 { return err } return nil diff --git a/pkg/system/pty_linux.go b/pkg/system/pty_linux.go new file mode 100644 index 000000000..b281b719f --- /dev/null +++ b/pkg/system/pty_linux.go @@ -0,0 +1,31 @@ +package system + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +// Unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f. +// Unlockpt should be called before opening the slave side of a pseudoterminal. +func Unlockpt(f *os.File) error { + var u int + return Ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))) +} + +// Ptsname retrieves the name of the first available pts for the given master. +func Ptsname(f *os.File) (string, error) { + var n int + + if err := Ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { + return "", err + } + return fmt.Sprintf("/dev/pts/%d", n), nil +} + +// OpenPtmx opens /dev/ptmx, i.e. the PTY master. +func OpenPtmx() (*os.File, error) { + // O_NOCTTY and O_CLOEXEC are not present in os package so we use the syscall's one for all. + return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) +} diff --git a/pkg/system/setns_linux.go b/pkg/system/setns_linux.go new file mode 100644 index 000000000..be6f3edb3 --- /dev/null +++ b/pkg/system/setns_linux.go @@ -0,0 +1,13 @@ +package system + +import ( + "syscall" +) + +func Setns(fd uintptr, flags uintptr) error { + _, _, err := syscall.RawSyscall(SYS_SETNS, fd, flags, 0) + if err != 0 { + return err + } + return nil +} diff --git a/pkg/system/setns_linux_amd64.go b/pkg/system/setns_linux_amd64.go new file mode 100644 index 000000000..4e306253d --- /dev/null +++ b/pkg/system/setns_linux_amd64.go @@ -0,0 +1,8 @@ +// +build linux,amd64 + +package system + +// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092 +const ( + SYS_SETNS = 308 +) From 18f06b8d16c475568fd023e97eecc138ab052c2d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 19 Feb 2014 12:47:01 -0800 Subject: [PATCH 071/282] Fix ptmx issue on libcontainer Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/namespaces/exec.go | 3 ++- pkg/libcontainer/namespaces/mount.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index ea3d2caa7..77550d608 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -50,7 +50,8 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { // command.Stderr = os.Stderr command.SysProcAttr = &syscall.SysProcAttr{} command.SysProcAttr.Cloneflags = flag - //command.ExtraFiles = []*os.File{master} + + command.ExtraFiles = []*os.File{master} println("vvvvvvvvv") if err := command.Start(); err != nil { diff --git a/pkg/libcontainer/namespaces/mount.go b/pkg/libcontainer/namespaces/mount.go index a9b981ecd..5c0b8ead1 100644 --- a/pkg/libcontainer/namespaces/mount.go +++ b/pkg/libcontainer/namespaces/mount.go @@ -41,7 +41,7 @@ func SetupNewMountNamespace(rootfs, console string, readonly bool) error { if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err } - if err := os.Symlink(filepath.Join(rootfs, "pts/ptmx"), ptmx); err != nil { + if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } From e25065a6b1df09771598d77cc698e4fcf1159bd4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 14:33:25 -0800 Subject: [PATCH 072/282] Use nsinit as app Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 10 +- pkg/libcontainer/namespaces/exec.go | 40 ++----- pkg/libcontainer/namespaces/linux_x86_64.go | 7 -- pkg/libcontainer/namespaces/ns_linux.go | 2 +- pkg/libcontainer/namespaces/nsinit/init.go | 112 ++++++++++-------- .../namespaces/{ => nsinit}/mount.go | 4 +- pkg/libcontainer/namespaces/utils.go | 26 ---- 7 files changed, 82 insertions(+), 119 deletions(-) delete mode 100644 pkg/libcontainer/namespaces/linux_x86_64.go rename pkg/libcontainer/namespaces/{ => nsinit}/mount.go (98%) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index c9a3f2e90..c2885447f 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -2,18 +2,14 @@ package libcontainer type Container struct { ID string `json:"id,omitempty"` - NsPid int `json:"namespace_pid,omitempty"` Command *Command `json:"command,omitempty"` - RootFs string `json:"rootfs,omitempty"` ReadonlyFs bool `json:"readonly_fs,omitempty"` - NetNsFd uintptr `json:"network_namespace_fd,omitempty"` User string `json:"user,omitempty"` WorkingDir string `json:"working_dir,omitempty"` Namespaces Namespaces `json:"namespaces,omitempty"` Capabilities Capabilities `json:"capabilities,omitempty"` - Master uintptr `json:"master"` - Console string `json:"console"` - LogFile string `json:"log_file"` + LogFile string `json:"log_file,omitempty"` + Network *Network `json:"network,omitempty"` } type Command struct { @@ -22,9 +18,9 @@ type Command struct { } type Network struct { - TempVethName string `json:"temp_veth,omitempty"` IP string `json:"ip,omitempty"` Gateway string `json:"gateway,omitempty"` Bridge string `json:"bridge,omitempty"` Mtu int `json:"mtu,omitempty"` + TempVethName string `json:"temp_veth,omitempty"` } diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/namespaces/exec.go index 77550d608..8e5bf68ae 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/namespaces/exec.go @@ -1,27 +1,17 @@ -/* - Higher level convience functions for setting up a container -*/ - package namespaces import ( - "errors" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/pkg/term" "io" "log" "os" "os/exec" - "path/filepath" - "strconv" "syscall" ) -var ( - ErrExistingNetworkNamespace = errors.New("specified both CLONE_NEWNET and an existing network namespace") -) - -// Exec will spawn new namespaces with the specified Container configuration +// ExecContainer will spawn new namespaces with the specified Container configuration // in the RootFs path and return the pid of the new containerized process. // // If an existing network namespace is specified the container @@ -30,30 +20,19 @@ var ( // existing network namespace and the CLONE_NEWNET option in the container configuration will allow // the container to the the host's networking options and configuration. func ExecContainer(container *libcontainer.Container) (pid int, err error) { - // a user cannot pass CLONE_NEWNET and an existing net namespace fd to join - if container.NetNsFd > 0 && container.Namespaces.Contains(libcontainer.CLONE_NEWNET) { - return -1, ErrExistingNetworkNamespace - } - master, console, err := createMasterAndConsole() if err != nil { return -1, err } - nsinit := filepath.Join(container.RootFs, ".nsinit") // we need CLONE_VFORK so we can wait on the child flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - command := exec.Command(nsinit, "-master", strconv.Itoa(int(master.Fd())), "-console", console, "init", "container.json") - // command.Stdin = os.Stdin - // command.Stdout = os.Stdout - // command.Stderr = os.Stderr - command.SysProcAttr = &syscall.SysProcAttr{} - command.SysProcAttr.Cloneflags = flag + command := exec.Command("nsinit", console) + command.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: flag, + } - command.ExtraFiles = []*os.File{master} - - println("vvvvvvvvv") if err := command.Start(); err != nil { return -1, err } @@ -64,11 +43,18 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { log.Println(err) } }() + go func() { if _, err := io.Copy(master, os.Stdin); err != nil { log.Println(err) } }() + + term.SetRawTerminal(os.Stdin.Fd()) + + if err := command.Wait(); err != nil { + return pid, err + } return pid, nil } diff --git a/pkg/libcontainer/namespaces/linux_x86_64.go b/pkg/libcontainer/namespaces/linux_x86_64.go deleted file mode 100644 index ac9a01476..000000000 --- a/pkg/libcontainer/namespaces/linux_x86_64.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build linux,x86_64 -package namespaces - -// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092 -const ( - SYS_SETNS = 308 -) diff --git a/pkg/libcontainer/namespaces/ns_linux.go b/pkg/libcontainer/namespaces/ns_linux.go index f61279334..2c73e08e5 100644 --- a/pkg/libcontainer/namespaces/ns_linux.go +++ b/pkg/libcontainer/namespaces/ns_linux.go @@ -40,5 +40,5 @@ func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { for _, ns := range namespaces { flag |= namespaceMap[ns] } - return + return flag } diff --git a/pkg/libcontainer/namespaces/nsinit/init.go b/pkg/libcontainer/namespaces/nsinit/init.go index 7f85ebacd..523854e5d 100644 --- a/pkg/libcontainer/namespaces/nsinit/init.go +++ b/pkg/libcontainer/namespaces/nsinit/init.go @@ -1,6 +1,7 @@ -package nsinit +package main import ( + "encoding/json" "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" @@ -12,103 +13,112 @@ import ( "syscall" ) -// InitNamespace should be run inside an existing namespace to setup -// common mounts, drop capabilities, and setup network interfaces -func InitNamespace(container *libcontainer.Container) error { - println("|||||||||||||") - if err := setLogFile(container); err != nil { - return err - } - println(container.LogFile) - log.Printf("--------->") - rootfs, err := resolveRootfs(container) +func loadContainer() (*libcontainer.Container, error) { + f, err := os.Open("container.json") if err != nil { - return err + return nil, err + } + defer f.Close() + + var container *libcontainer.Container + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + return container, nil +} + +func main() { + container, err := loadContainer() + if err != nil { + log.Fatal(err) } - // any errors encoutered inside the namespace we should write - // out to a log or a pipe to our parent and exit(1) - // because writing to stderr will not work after we close - if err := closeMasterAndStd(os.NewFile(container.Master, "/dev/ptmx")); err != nil { - log.Fatalf("close master and std %s", err) - return err + if os.Args[1] == "exec" { + _, err := namespaces.ExecContainer(container) + if err != nil { + log.Fatal(err) + } + os.Exit(0) + } + console := os.Args[1] + + if err := setLogFile(container); err != nil { + log.Fatal(err) } - slave, err := openTerminal(container.Console, syscall.O_RDWR) + rootfs, err := resolveRootfs() + if err != nil { + log.Fatal(err) + } + + // close pipes so that we can replace it with the pty + os.Stdin.Close() + os.Stdout.Close() + os.Stderr.Close() + + slave, err := openTerminal(console, syscall.O_RDWR) if err != nil { log.Fatalf("open terminal %s", err) - return err + } + if slave.Fd() != 0 { + log.Fatalf("slave fd should be 0") } if err := dupSlave(slave); err != nil { log.Fatalf("dup2 slave %s", err) - return err } - /* - if container.NetNsFd > 0 { - if err := joinExistingNamespace(container.NetNsFd, libcontainer.CLONE_NEWNET); err != nil { - log.Fatalf("join existing net namespace %s", err) - } - } - */ - if _, err := system.Setsid(); err != nil { log.Fatalf("setsid %s", err) - return err } if err := system.Setctty(); err != nil { log.Fatalf("setctty %s", err) - return err } if err := system.ParentDeathSignal(); err != nil { log.Fatalf("parent deth signal %s", err) - return err } - if err := namespaces.SetupNewMountNamespace(rootfs, container.Console, container.ReadonlyFs); err != nil { + + if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { log.Fatalf("setup mount namespace %s", err) - return err } + + if container.Network != nil { + if err := setupNetworking(container); err != nil { + log.Fatalf("setup networking %s", err) + } + } + if err := system.Sethostname(container.ID); err != nil { log.Fatalf("sethostname %s", err) - return err } if err := capabilities.DropCapabilities(container); err != nil { log.Fatalf("drop capabilities %s", err) - return err } if err := setupUser(container); err != nil { log.Fatalf("setup user %s", err) - return err } if container.WorkingDir != "" { if err := system.Chdir(container.WorkingDir); err != nil { log.Fatalf("chdir to %s %s", container.WorkingDir, err) - return err } } if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { log.Fatalf("exec %s", err) - return err } panic("unreachable") } -func resolveRootfs(container *libcontainer.Container) (string, error) { - rootfs, err := filepath.Abs(container.RootFs) +func resolveRootfs() (string, error) { + cwd, err := os.Getwd() + if err != nil { + return "", err + } + rootfs, err := filepath.Abs(cwd) if err != nil { return "", err } return filepath.EvalSymlinks(rootfs) } -func closeMasterAndStd(master *os.File) error { - master.Close() - os.Stdin.Close() - os.Stdout.Close() - os.Stderr.Close() - return nil -} - func setupUser(container *libcontainer.Container) error { // TODO: honor user passed on container if err := system.Setgroups(nil); err != nil { @@ -154,3 +164,7 @@ func setLogFile(container *libcontainer.Container) error { log.SetOutput(f) return nil } + +func setupNetworking(conatiner *libcontainer.Container) error { + return nil +} diff --git a/pkg/libcontainer/namespaces/mount.go b/pkg/libcontainer/namespaces/nsinit/mount.go similarity index 98% rename from pkg/libcontainer/namespaces/mount.go rename to pkg/libcontainer/namespaces/nsinit/mount.go index 5c0b8ead1..f9ee96963 100644 --- a/pkg/libcontainer/namespaces/mount.go +++ b/pkg/libcontainer/namespaces/nsinit/mount.go @@ -1,4 +1,4 @@ -package namespaces +package main import ( "fmt" @@ -14,7 +14,7 @@ var ( defaults = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV ) -func SetupNewMountNamespace(rootfs, console string, readonly bool) error { +func setupNewMountNamespace(rootfs, console string, readonly bool) error { if err := system.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting / as slave %s", err) } diff --git a/pkg/libcontainer/namespaces/utils.go b/pkg/libcontainer/namespaces/utils.go index a5d677c7b..edc3ab52c 100644 --- a/pkg/libcontainer/namespaces/utils.go +++ b/pkg/libcontainer/namespaces/utils.go @@ -7,7 +7,6 @@ import ( "path/filepath" "strconv" "strings" - "syscall" ) func addEnvIfNotSet(container *libcontainer.Container, key, value string) { @@ -26,31 +25,6 @@ func addEnvIfNotSet(container *libcontainer.Container, key, value string) { container.Command.Env = append(container.Command.Env, jv) } -// getNsFds inspects the container's namespace configuration and opens the fds to -// each of the namespaces. -func getNsFds(container *libcontainer.Container) ([]uintptr, error) { - var ( - namespaces = []string{} - fds = []uintptr{} - ) - - for _, ns := range container.Namespaces { - namespaces = append(namespaces, namespaceFileMap[ns]) - } - - for _, ns := range namespaces { - fd, err := getNsFd(container.NsPid, ns) - if err != nil { - for _, fd = range fds { - syscall.Close(int(fd)) - } - return nil, err - } - fds = append(fds, fd) - } - return fds, nil -} - // getNsFd returns the fd for a specific pid and namespace option func getNsFd(pid int, ns string) (uintptr, error) { nspath := filepath.Join("/proc", strconv.Itoa(pid), "ns", ns) From 7bc3c012507edcfc5e8ab8523b240ac2bb03fe19 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 14:55:34 -0800 Subject: [PATCH 073/282] Simplify namespaces with only nsinit Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/cli/main.go | 203 ------------------ pkg/libcontainer/namespaces/utils.go | 48 ----- .../{namespaces => nsinit}/exec.go | 26 ++- .../{namespaces => }/nsinit/init.go | 13 +- .../{namespaces => }/nsinit/mount.go | 0 .../{namespaces => nsinit}/ns_linux.go | 11 +- 6 files changed, 23 insertions(+), 278 deletions(-) delete mode 100644 pkg/libcontainer/cli/main.go delete mode 100644 pkg/libcontainer/namespaces/utils.go rename pkg/libcontainer/{namespaces => nsinit}/exec.go (63%) rename pkg/libcontainer/{namespaces => }/nsinit/init.go (93%) rename pkg/libcontainer/{namespaces => }/nsinit/mount.go (100%) rename pkg/libcontainer/{namespaces => nsinit}/ns_linux.go (74%) diff --git a/pkg/libcontainer/cli/main.go b/pkg/libcontainer/cli/main.go deleted file mode 100644 index 93bb0399f..000000000 --- a/pkg/libcontainer/cli/main.go +++ /dev/null @@ -1,203 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces/nsinit" - "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/libcontainer/utils" - "os" - exec_ "os/exec" - "path" - "path/filepath" -) - -var ( - displayPid bool - newCommand string - usrNet bool - masterFd int - console string -) - -func init() { - flag.BoolVar(&displayPid, "pid", false, "display the pid before waiting") - flag.StringVar(&newCommand, "cmd", "/bin/bash", "command to run in the existing namespace") - flag.BoolVar(&usrNet, "net", false, "user a net namespace") - flag.IntVar(&masterFd, "master", 0, "master fd") - flag.StringVar(&console, "console", "", "console path") - flag.Parse() -} - -func nsinitFunc(container *libcontainer.Container) error { - container.Master = uintptr(masterFd) - container.Console = console - container.LogFile = "/root/logs" - - return nsinit.InitNamespace(container) -} - -func exec(container *libcontainer.Container) error { - var ( - netFile *os.File - err error - ) - container.NetNsFd = 0 - - if usrNet { - netFile, err = os.Open("/root/nsroot/test") - if err != nil { - return err - } - container.NetNsFd = netFile.Fd() - } - - self, err := exec_.LookPath(os.Args[0]) - if err != nil { - return err - } - if output, err := exec_.Command("cp", self, path.Join(container.RootFs, ".nsinit")).CombinedOutput(); err != nil { - return fmt.Errorf("Error exec cp: %s, (%s)", err, output) - } else { - println(self, container.RootFs) - fmt.Printf("-----> %s\n", output) - } - println("----") - - pid, err := namespaces.ExecContainer(container) - if err != nil { - return fmt.Errorf("error exec container %s", err) - } - - if displayPid { - fmt.Println(pid) - } - - exitcode, err := utils.WaitOnPid(pid) - if err != nil { - return fmt.Errorf("error waiting on child %s", err) - } - fmt.Println(exitcode) - if usrNet { - netFile.Close() - if err := network.DeleteNetworkNamespace("/root/nsroot/test"); err != nil { - return err - } - } - os.Exit(exitcode) - return nil -} - -func execIn(container *libcontainer.Container) error { - // f, err := os.Open("/root/nsroot/test") - // if err != nil { - // return err - // } - // container.NetNsFd = f.Fd() - // pid, err := namespaces.ExecIn(container, &libcontainer.Command{ - // Env: container.Command.Env, - // Args: []string{ - // newCommand, - // }, - // }) - // if err != nil { - // return fmt.Errorf("error exexin container %s", err) - // } - // exitcode, err := utils.WaitOnPid(pid) - // if err != nil { - // return fmt.Errorf("error waiting on child %s", err) - // } - // os.Exit(exitcode) - return nil -} - -func createNet(config *libcontainer.Network) error { - /* - root := "/root/nsroot" - if err := network.SetupNamespaceMountDir(root); err != nil { - return err - } - - nspath := root + "/test" - if err := network.CreateNetworkNamespace(nspath); err != nil { - return nil - } - if err := network.CreateVethPair("veth0", config.TempVethName); err != nil { - return err - } - if err := network.SetInterfaceMaster("veth0", config.Bridge); err != nil { - return err - } - if err := network.InterfaceUp("veth0"); err != nil { - return err - } - - f, err := os.Open(nspath) - if err != nil { - return err - } - defer f.Close() - - if err := network.SetInterfaceInNamespaceFd("veth1", int(f.Fd())); err != nil { - return err - } - - if err := network.SetupVethInsideNamespace(f.Fd(), config); err != nil { - return err - } - */ - return nil -} - -func printErr(err error) { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) -} - -func main() { - cliCmd := flag.Arg(0) - - config, err := filepath.Abs(flag.Arg(1)) - if err != nil { - printErr(err) - } - println("cli:", cliCmd, "config:", config) - f, err := os.Open(config) - if err != nil { - printErr(err) - } - - dec := json.NewDecoder(f) - var container *libcontainer.Container - - if err := dec.Decode(&container); err != nil { - printErr(err) - } - f.Close() - - switch cliCmd { - case "init": - err = nsinitFunc(container) - case "exec": - err = exec(container) - case "execin": - err = execIn(container) - case "net": - err = createNet(&libcontainer.Network{ - TempVethName: "veth1", - IP: "172.17.0.100/16", - Gateway: "172.17.42.1", - Mtu: 1500, - Bridge: "docker0", - }) - default: - err = fmt.Errorf("command not supported: %s", cliCmd) - } - - if err != nil { - printErr(err) - } -} diff --git a/pkg/libcontainer/namespaces/utils.go b/pkg/libcontainer/namespaces/utils.go deleted file mode 100644 index edc3ab52c..000000000 --- a/pkg/libcontainer/namespaces/utils.go +++ /dev/null @@ -1,48 +0,0 @@ -package namespaces - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" - "os" - "path/filepath" - "strconv" - "strings" -) - -func addEnvIfNotSet(container *libcontainer.Container, key, value string) { - jv := fmt.Sprintf("%s=%s", key, value) - if len(container.Command.Env) == 0 { - container.Command.Env = []string{jv} - return - } - - for _, v := range container.Command.Env { - parts := strings.Split(v, "=") - if parts[0] == key { - return - } - } - container.Command.Env = append(container.Command.Env, jv) -} - -// getNsFd returns the fd for a specific pid and namespace option -func getNsFd(pid int, ns string) (uintptr, error) { - nspath := filepath.Join("/proc", strconv.Itoa(pid), "ns", ns) - // OpenFile adds closOnExec - f, err := os.OpenFile(nspath, os.O_RDONLY, 0666) - if err != nil { - return 0, err - } - return f.Fd(), nil -} - -// setupEnvironment adds additional environment variables to the container's -// Command such as USER, LOGNAME, container, and TERM -func setupEnvironment(container *libcontainer.Container) { - addEnvIfNotSet(container, "container", "docker") - // TODO: check if pty - addEnvIfNotSet(container, "TERM", "xterm") - // TODO: get username from container - addEnvIfNotSet(container, "USER", "root") - addEnvIfNotSet(container, "LOGNAME", "root") -} diff --git a/pkg/libcontainer/namespaces/exec.go b/pkg/libcontainer/nsinit/exec.go similarity index 63% rename from pkg/libcontainer/namespaces/exec.go rename to pkg/libcontainer/nsinit/exec.go index 8e5bf68ae..ef81b0ef8 100644 --- a/pkg/libcontainer/namespaces/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -1,4 +1,4 @@ -package namespaces +package main import ( "github.com/dotcloud/docker/pkg/libcontainer" @@ -11,15 +11,7 @@ import ( "syscall" ) -// ExecContainer will spawn new namespaces with the specified Container configuration -// in the RootFs path and return the pid of the new containerized process. -// -// If an existing network namespace is specified the container -// will join that namespace. If an existing network namespace is not specified but CLONE_NEWNET is, -// the container will be spawned with a new network namespace with no configuration. Omiting an -// existing network namespace and the CLONE_NEWNET option in the container configuration will allow -// the container to the the host's networking options and configuration. -func ExecContainer(container *libcontainer.Container) (pid int, err error) { +func execCommand(container *libcontainer.Container) (pid int, err error) { master, console, err := createMasterAndConsole() if err != nil { return -1, err @@ -50,7 +42,19 @@ func ExecContainer(container *libcontainer.Container) (pid int, err error) { } }() - term.SetRawTerminal(os.Stdin.Fd()) + ws, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + return -1, err + } + if err := term.SetWinsize(master.Fd(), ws); err != nil { + return -1, err + } + state, err := term.SetRawTerminal(os.Stdin.Fd()) + if err != nil { + command.Process.Kill() + return -1, err + } + defer term.RestoreTerminal(os.Stdin.Fd(), state) if err := command.Wait(); err != nil { return pid, err diff --git a/pkg/libcontainer/namespaces/nsinit/init.go b/pkg/libcontainer/nsinit/init.go similarity index 93% rename from pkg/libcontainer/namespaces/nsinit/init.go rename to pkg/libcontainer/nsinit/init.go index 523854e5d..b4b7de410 100644 --- a/pkg/libcontainer/namespaces/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" - "github.com/dotcloud/docker/pkg/libcontainer/namespaces" "github.com/dotcloud/docker/pkg/system" "log" "os" @@ -34,7 +33,7 @@ func main() { } if os.Args[1] == "exec" { - _, err := namespaces.ExecContainer(container) + _, err := execCommand(container) if err != nil { log.Fatal(err) } @@ -157,11 +156,13 @@ func openTerminal(name string, flag int) (*os.File, error) { } func setLogFile(container *libcontainer.Container) error { - f, err := os.OpenFile(container.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0655) - if err != nil { - return err + if container.LogFile != "" { + f, err := os.OpenFile(container.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0655) + if err != nil { + return err + } + log.SetOutput(f) } - log.SetOutput(f) return nil } diff --git a/pkg/libcontainer/namespaces/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go similarity index 100% rename from pkg/libcontainer/namespaces/nsinit/mount.go rename to pkg/libcontainer/nsinit/mount.go diff --git a/pkg/libcontainer/namespaces/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go similarity index 74% rename from pkg/libcontainer/namespaces/ns_linux.go rename to pkg/libcontainer/nsinit/ns_linux.go index 2c73e08e5..b54bc2b99 100644 --- a/pkg/libcontainer/namespaces/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -1,4 +1,4 @@ -package namespaces +package main import ( "github.com/dotcloud/docker/pkg/libcontainer" @@ -25,15 +25,6 @@ var namespaceMap = map[libcontainer.Namespace]int{ libcontainer.CLONE_NEWNET: CLONE_NEWNET, } -var namespaceFileMap = map[libcontainer.Namespace]string{ - libcontainer.CLONE_NEWNS: "mnt", - libcontainer.CLONE_NEWUTS: "uts", - libcontainer.CLONE_NEWIPC: "ipc", - libcontainer.CLONE_NEWUSER: "user", - libcontainer.CLONE_NEWPID: "pid", - libcontainer.CLONE_NEWNET: "net", -} - // getNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { From 34671f20103fb975fed31a03705e04bc65aed239 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 15:33:44 -0800 Subject: [PATCH 074/282] Implement init veth creation Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 9 ++++---- pkg/libcontainer/container.json | 14 ++++++++---- pkg/libcontainer/network/veth.go | 38 +++++--------------------------- pkg/libcontainer/nsinit/exec.go | 33 +++++++++++++++++++++++++++ pkg/libcontainer/nsinit/init.go | 14 +++++++++--- pkg/libcontainer/ubuntu.json | 22 ------------------ 6 files changed, 63 insertions(+), 67 deletions(-) delete mode 100644 pkg/libcontainer/ubuntu.json diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index c2885447f..3f3961d49 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -18,9 +18,8 @@ type Command struct { } type Network struct { - IP string `json:"ip,omitempty"` - Gateway string `json:"gateway,omitempty"` - Bridge string `json:"bridge,omitempty"` - Mtu int `json:"mtu,omitempty"` - TempVethName string `json:"temp_veth,omitempty"` + IP string `json:"ip,omitempty"` + Gateway string `json:"gateway,omitempty"` + Bridge string `json:"bridge,omitempty"` + Mtu int `json:"mtu,omitempty"` } diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 6e4fda54c..8731170c2 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,6 +1,6 @@ { "id": "koye", - "namespace_pid": 3117, + "log_file": "/root/logs", "command": { "args": [ "/bin/bash" @@ -12,12 +12,12 @@ "TERM=xterm" ] }, - "rootfs": "/var/lib/docker/containers/ee76122136d691d63e09d24168a91ddb2ef9fdcf210b4de5c50aa76354892f4b/root", "namespaces": [ "NEWIPC", "NEWNS", "NEWPID", - "NEWUTS" + "NEWUTS", + "NEWNET" ], "capabilities": [ "SETPCAP", @@ -34,5 +34,11 @@ "AUDIT_CONTROL", "MAC_OVERRIDE", "MAC_ADMIN" - ] + ], + "network": { + "ip": "172.17.0.100/16", + "gateway": "172.17.42.1", + "bridge": "docker0", + "mtu": 1500 + } } diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go index 2ecce22c3..05512e63c 100644 --- a/pkg/libcontainer/network/veth.go +++ b/pkg/libcontainer/network/veth.go @@ -3,18 +3,16 @@ package network import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" - "os" - "syscall" ) // SetupVeth sets up an existing network namespace with the specified // network configuration. -func SetupVeth(config *libcontainer.Network) error { - if err := InterfaceDown(config.TempVethName); err != nil { - return fmt.Errorf("interface down %s %s", config.TempVethName, err) +func SetupVeth(config *libcontainer.Network, tempVethName string) error { + if err := InterfaceDown(tempVethName); err != nil { + return fmt.Errorf("interface down %s %s", tempVethName, err) } - if err := ChangeInterfaceName(config.TempVethName, "eth0"); err != nil { - return fmt.Errorf("change %s to eth0 %s", config.TempVethName, err) + if err := ChangeInterfaceName(tempVethName, "eth0"); err != nil { + return fmt.Errorf("change %s to eth0 %s", tempVethName, err) } if err := SetInterfaceIp("eth0", config.IP); err != nil { return fmt.Errorf("set eth0 ip %s", err) @@ -41,29 +39,3 @@ func SetupVeth(config *libcontainer.Network) error { } return nil } - -// SetupNamespaceMountDir prepares a new root for use as a mount -// source for bind mounting namespace fd to an outside path -func SetupNamespaceMountDir(root string) error { - if err := os.MkdirAll(root, 0666); err != nil { - return err - } - // make sure mounts are not unmounted by other mnt namespaces - if err := syscall.Mount("", root, "none", syscall.MS_SHARED|syscall.MS_REC, ""); err != nil && err != syscall.EINVAL { - return err - } - if err := syscall.Mount(root, root, "none", syscall.MS_BIND, ""); err != nil { - return err - } - return nil -} - -// DeleteNetworkNamespace unmounts the binding path and removes the -// file so that no references to the fd are present and the network -// namespace is automatically cleaned up -func DeleteNetworkNamespace(bindingPath string) error { - if err := syscall.Unmount(bindingPath, 0); err != nil { - return err - } - return os.Remove(bindingPath) -} diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index ef81b0ef8..9cd174170 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/term" "io" @@ -25,11 +27,34 @@ func execCommand(container *libcontainer.Container) (pid int, err error) { Cloneflags: flag, } + inPipe, err := command.StdinPipe() + if err != nil { + return -1, err + } + if err := command.Start(); err != nil { return -1, err } pid = command.Process.Pid + if container.Network != nil { + name1, name2, err := createVethPair() + if err != nil { + log.Fatal(err) + } + if err := network.SetInterfaceMaster(name1, container.Network.Bridge); err != nil { + log.Fatal(err) + } + if err := network.InterfaceUp(name1); err != nil { + log.Fatal(err) + } + if err := network.SetInterfaceInNamespacePid(name2, pid); err != nil { + log.Fatal(err) + } + fmt.Fprint(inPipe, name2) + inPipe.Close() + } + go func() { if _, err := io.Copy(os.Stdout, master); err != nil { log.Println(err) @@ -78,3 +103,11 @@ func createMasterAndConsole() (*os.File, string, error) { } return master, console, nil } + +func createVethPair() (name1 string, name2 string, err error) { + name1, name2 = "veth001", "veth002" + if err = network.CreateVethPair(name1, name2); err != nil { + return + } + return +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index b4b7de410..2804f01e5 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -5,7 +5,9 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" + "io/ioutil" "log" "os" "path/filepath" @@ -50,6 +52,12 @@ func main() { log.Fatal(err) } + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("error reading from stdin %s", err) + } + tempVethName := string(data) + // close pipes so that we can replace it with the pty os.Stdin.Close() os.Stdout.Close() @@ -81,7 +89,7 @@ func main() { } if container.Network != nil { - if err := setupNetworking(container); err != nil { + if err := setupNetworking(container, tempVethName); err != nil { log.Fatalf("setup networking %s", err) } } @@ -166,6 +174,6 @@ func setLogFile(container *libcontainer.Container) error { return nil } -func setupNetworking(conatiner *libcontainer.Container) error { - return nil +func setupNetworking(container *libcontainer.Container, tempVethName string) error { + return network.SetupVeth(container.Network, tempVethName) } diff --git a/pkg/libcontainer/ubuntu.json b/pkg/libcontainer/ubuntu.json deleted file mode 100644 index 0a450ae06..000000000 --- a/pkg/libcontainer/ubuntu.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": "koye", - "namespace_pid": 3745, - "command": { - "args": [ - "/sbin/init" - ], - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm" - ] - }, - "rootfs": "/var/lib/docker/btrfs/subvolumes/7c0f15df1ad2e2fe04d7a6e079aec17406e9465a6a37dd16cb0dd754fc0167b3", - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS" - ] -} From 5428964400ece4cd79cc5d482307df5e8913469f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 15:54:53 -0800 Subject: [PATCH 075/282] Add dynamic veth name Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/network/veth.go | 41 ---------------------------- pkg/libcontainer/nsinit/exec.go | 10 ++++++- pkg/libcontainer/nsinit/init.go | 46 +++++++++++++++++++++++++------- pkg/libcontainer/utils/utils.go | 24 +++-------------- 4 files changed, 48 insertions(+), 73 deletions(-) delete mode 100644 pkg/libcontainer/network/veth.go diff --git a/pkg/libcontainer/network/veth.go b/pkg/libcontainer/network/veth.go deleted file mode 100644 index 05512e63c..000000000 --- a/pkg/libcontainer/network/veth.go +++ /dev/null @@ -1,41 +0,0 @@ -package network - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/libcontainer" -) - -// SetupVeth sets up an existing network namespace with the specified -// network configuration. -func SetupVeth(config *libcontainer.Network, tempVethName string) error { - if err := InterfaceDown(tempVethName); err != nil { - return fmt.Errorf("interface down %s %s", tempVethName, err) - } - if err := ChangeInterfaceName(tempVethName, "eth0"); err != nil { - return fmt.Errorf("change %s to eth0 %s", tempVethName, err) - } - if err := SetInterfaceIp("eth0", config.IP); err != nil { - return fmt.Errorf("set eth0 ip %s", err) - } - - if err := SetMtu("eth0", config.Mtu); err != nil { - return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) - } - if err := InterfaceUp("eth0"); err != nil { - return fmt.Errorf("eth0 up %s", err) - } - - if err := SetMtu("lo", config.Mtu); err != nil { - return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) - } - if err := InterfaceUp("lo"); err != nil { - return fmt.Errorf("lo up %s", err) - } - - if config.Gateway != "" { - if err := SetDefaultGateway(config.Gateway); err != nil { - return fmt.Errorf("set gateway to %s %s", config.Gateway, err) - } - } - return nil -} diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 9cd174170..e0324074c 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/term" "io" @@ -105,7 +106,14 @@ func createMasterAndConsole() (*os.File, string, error) { } func createVethPair() (name1 string, name2 string, err error) { - name1, name2 = "veth001", "veth002" + name1, err = utils.GenerateRandomName("dock", 4) + if err != nil { + return + } + name2, err = utils.GenerateRandomName("dock", 4) + if err != nil { + return + } if err = network.CreateVethPair(name1, name2); err != nil { return } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 2804f01e5..fe8fd4b4d 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -52,11 +52,14 @@ func main() { log.Fatal(err) } - data, err := ioutil.ReadAll(os.Stdin) - if err != nil { - log.Fatalf("error reading from stdin %s", err) + var tempVethName string + if container.Network != nil { + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalf("error reading from stdin %s", err) + } + tempVethName = string(data) } - tempVethName := string(data) // close pipes so that we can replace it with the pty os.Stdin.Close() @@ -73,7 +76,6 @@ func main() { if err := dupSlave(slave); err != nil { log.Fatalf("dup2 slave %s", err) } - if _, err := system.Setsid(); err != nil { log.Fatalf("setsid %s", err) } @@ -83,13 +85,11 @@ func main() { if err := system.ParentDeathSignal(); err != nil { log.Fatalf("parent deth signal %s", err) } - if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { log.Fatalf("setup mount namespace %s", err) } - if container.Network != nil { - if err := setupNetworking(container, tempVethName); err != nil { + if err := setupNetworking(container.Network, tempVethName); err != nil { log.Fatalf("setup networking %s", err) } } @@ -174,6 +174,32 @@ func setLogFile(container *libcontainer.Container) error { return nil } -func setupNetworking(container *libcontainer.Container, tempVethName string) error { - return network.SetupVeth(container.Network, tempVethName) +func setupNetworking(config *libcontainer.Network, tempVethName string) error { + if err := network.InterfaceDown(tempVethName); err != nil { + return fmt.Errorf("interface down %s %s", tempVethName, err) + } + if err := network.ChangeInterfaceName(tempVethName, "eth0"); err != nil { + return fmt.Errorf("change %s to eth0 %s", tempVethName, err) + } + if err := network.SetInterfaceIp("eth0", config.IP); err != nil { + return fmt.Errorf("set eth0 ip %s", err) + } + if err := network.SetMtu("eth0", config.Mtu); err != nil { + return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) + } + if err := network.InterfaceUp("eth0"); err != nil { + return fmt.Errorf("eth0 up %s", err) + } + if err := network.SetMtu("lo", config.Mtu); err != nil { + return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) + } + if err := network.InterfaceUp("lo"); err != nil { + return fmt.Errorf("lo up %s", err) + } + if config.Gateway != "" { + if err := network.SetDefaultGateway(config.Gateway); err != nil { + return fmt.Errorf("set gateway to %s %s", config.Gateway, err) + } + } + return nil } diff --git a/pkg/libcontainer/utils/utils.go b/pkg/libcontainer/utils/utils.go index 7289fecf2..d3223c3e4 100644 --- a/pkg/libcontainer/utils/utils.go +++ b/pkg/libcontainer/utils/utils.go @@ -4,30 +4,12 @@ import ( "crypto/rand" "encoding/hex" "io" - "os" - "syscall" ) -func WaitOnPid(pid int) (exitcode int, err error) { - child, err := os.FindProcess(pid) - if err != nil { - return -1, err - } - state, err := child.Wait() - if err != nil { - return -1, err - } - return getExitCode(state), nil -} - -func getExitCode(state *os.ProcessState) int { - return state.Sys().(syscall.WaitStatus).ExitStatus() -} - -func GenerateRandomName(size int) (string, error) { - id := make([]byte, size) +func GenerateRandomName(prefix string, size int) (string, error) { + id := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, id); err != nil { return "", err } - return hex.EncodeToString(id), nil + return prefix + hex.EncodeToString(id)[:size], nil } From 61a119220d88d20bb1cca111e9c8ba7cdb45d4f6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 16:40:36 -0800 Subject: [PATCH 076/282] General cleanup of libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/errors.go | 9 ----- pkg/libcontainer/network/network.go | 26 ------------ pkg/libcontainer/nsinit/exec.go | 51 ++++++++++------------- pkg/libcontainer/nsinit/init.go | 63 ++++++++--------------------- pkg/libcontainer/nsinit/main.go | 42 +++++++++++++++++++ pkg/libcontainer/nsinit/mount.go | 61 +++++++++++----------------- pkg/libcontainer/nsinit/ns_linux.go | 25 ++++-------- 7 files changed, 111 insertions(+), 166 deletions(-) delete mode 100644 pkg/libcontainer/errors.go create mode 100644 pkg/libcontainer/nsinit/main.go diff --git a/pkg/libcontainer/errors.go b/pkg/libcontainer/errors.go deleted file mode 100644 index c6964ee8e..000000000 --- a/pkg/libcontainer/errors.go +++ /dev/null @@ -1,9 +0,0 @@ -package libcontainer - -import ( - "errors" -) - -var ( - ErrInvalidPid = errors.New("no ns pid found") -) diff --git a/pkg/libcontainer/network/network.go b/pkg/libcontainer/network/network.go index 31c5d3249..8c7a4b618 100644 --- a/pkg/libcontainer/network/network.go +++ b/pkg/libcontainer/network/network.go @@ -1,15 +1,10 @@ package network import ( - "errors" "github.com/dotcloud/docker/pkg/netlink" "net" ) -var ( - ErrNoDefaultRoute = errors.New("no default network route found") -) - func InterfaceUp(name string) error { iface, err := net.InterfaceByName(name) if err != nil { @@ -46,14 +41,6 @@ func SetInterfaceInNamespacePid(name string, nsPid int) error { return netlink.NetworkSetNsPid(iface, nsPid) } -func SetInterfaceInNamespaceFd(name string, fd int) error { - iface, err := net.InterfaceByName(name) - if err != nil { - return err - } - return netlink.NetworkSetNsFd(iface, fd) -} - func SetInterfaceMaster(name, master string) error { iface, err := net.InterfaceByName(name) if err != nil { @@ -89,16 +76,3 @@ func SetMtu(name string, mtu int) error { } return netlink.NetworkSetMTU(iface, mtu) } - -func GetDefaultMtu() (int, error) { - routes, err := netlink.NetworkGetRoutes() - if err != nil { - return -1, err - } - for _, r := range routes { - if r.Default { - return r.Iface.MTU, nil - } - } - return -1, ErrNoDefaultRoute -} diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index e0324074c..4ac070db0 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -8,65 +8,54 @@ import ( "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/term" "io" - "log" + "io/ioutil" "os" "os/exec" "syscall" ) -func execCommand(container *libcontainer.Container) (pid int, err error) { +func execCommand(container *libcontainer.Container) (int, error) { master, console, err := createMasterAndConsole() if err != nil { return -1, err } - // we need CLONE_VFORK so we can wait on the child - flag := uintptr(getNamespaceFlags(container.Namespaces) | CLONE_VFORK) - - command := exec.Command("nsinit", console) + command := exec.Command("nsinit", "init", console) command.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: flag, + Cloneflags: uintptr(getNamespaceFlags(container.Namespaces) | syscall.CLONE_VFORK), // we need CLONE_VFORK so we can wait on the child } inPipe, err := command.StdinPipe() if err != nil { return -1, err } - if err := command.Start(); err != nil { return -1, err } - pid = command.Process.Pid + if err := writePidFile(command); err != nil { + return -1, err + } if container.Network != nil { name1, name2, err := createVethPair() if err != nil { - log.Fatal(err) + return -1, err } if err := network.SetInterfaceMaster(name1, container.Network.Bridge); err != nil { - log.Fatal(err) + return -1, err } if err := network.InterfaceUp(name1); err != nil { - log.Fatal(err) + return -1, err } - if err := network.SetInterfaceInNamespacePid(name2, pid); err != nil { - log.Fatal(err) + if err := network.SetInterfaceInNamespacePid(name2, command.Process.Pid); err != nil { + return -1, err } fmt.Fprint(inPipe, name2) inPipe.Close() } - go func() { - if _, err := io.Copy(os.Stdout, master); err != nil { - log.Println(err) - } - }() - - go func() { - if _, err := io.Copy(master, os.Stdin); err != nil { - log.Println(err) - } - }() + go io.Copy(os.Stdout, master) + go io.Copy(master, os.Stdin) ws, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { @@ -83,9 +72,11 @@ func execCommand(container *libcontainer.Container) (pid int, err error) { defer term.RestoreTerminal(os.Stdin.Fd(), state) if err := command.Wait(); err != nil { - return pid, err + if _, ok := err.(*exec.ExitError); !ok { + return -1, err + } } - return pid, nil + return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } func createMasterAndConsole() (*os.File, string, error) { @@ -93,12 +84,10 @@ func createMasterAndConsole() (*os.File, string, error) { if err != nil { return nil, "", err } - console, err := system.Ptsname(master) if err != nil { return nil, "", err } - if err := system.Unlockpt(master); err != nil { return nil, "", err } @@ -119,3 +108,7 @@ func createVethPair() (name1 string, name2 string, err error) { } return } + +func writePidFile(command *exec.Cmd) error { + return ioutil.WriteFile(".nspid", []byte(fmt.Sprint(command.Process.Pid)), 0655) +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index fe8fd4b4d..16a30812f 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" @@ -14,49 +13,21 @@ import ( "syscall" ) -func loadContainer() (*libcontainer.Container, error) { - f, err := os.Open("container.json") - if err != nil { - return nil, err - } - defer f.Close() - - var container *libcontainer.Container - if err := json.NewDecoder(f).Decode(&container); err != nil { - return nil, err - } - return container, nil -} - -func main() { - container, err := loadContainer() - if err != nil { - log.Fatal(err) - } - - if os.Args[1] == "exec" { - _, err := execCommand(container) - if err != nil { - log.Fatal(err) - } - os.Exit(0) - } - console := os.Args[1] - +func initCommand(container *libcontainer.Container, console string) error { if err := setLogFile(container); err != nil { - log.Fatal(err) + return err } rootfs, err := resolveRootfs() if err != nil { - log.Fatal(err) + return err } var tempVethName string if container.Network != nil { data, err := ioutil.ReadAll(os.Stdin) if err != nil { - log.Fatalf("error reading from stdin %s", err) + return fmt.Errorf("error reading from stdin %s", err) } tempVethName = string(data) } @@ -68,48 +39,48 @@ func main() { slave, err := openTerminal(console, syscall.O_RDWR) if err != nil { - log.Fatalf("open terminal %s", err) + return fmt.Errorf("open terminal %s", err) } if slave.Fd() != 0 { - log.Fatalf("slave fd should be 0") + return fmt.Errorf("slave fd should be 0") } if err := dupSlave(slave); err != nil { - log.Fatalf("dup2 slave %s", err) + return fmt.Errorf("dup2 slave %s", err) } if _, err := system.Setsid(); err != nil { - log.Fatalf("setsid %s", err) + return fmt.Errorf("setsid %s", err) } if err := system.Setctty(); err != nil { - log.Fatalf("setctty %s", err) + return fmt.Errorf("setctty %s", err) } if err := system.ParentDeathSignal(); err != nil { - log.Fatalf("parent deth signal %s", err) + return fmt.Errorf("parent deth signal %s", err) } if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { - log.Fatalf("setup mount namespace %s", err) + return fmt.Errorf("setup mount namespace %s", err) } if container.Network != nil { if err := setupNetworking(container.Network, tempVethName); err != nil { - log.Fatalf("setup networking %s", err) + return fmt.Errorf("setup networking %s", err) } } if err := system.Sethostname(container.ID); err != nil { - log.Fatalf("sethostname %s", err) + return fmt.Errorf("sethostname %s", err) } if err := capabilities.DropCapabilities(container); err != nil { - log.Fatalf("drop capabilities %s", err) + return fmt.Errorf("drop capabilities %s", err) } if err := setupUser(container); err != nil { - log.Fatalf("setup user %s", err) + return fmt.Errorf("setup user %s", err) } if container.WorkingDir != "" { if err := system.Chdir(container.WorkingDir); err != nil { - log.Fatalf("chdir to %s %s", container.WorkingDir, err) + return fmt.Errorf("chdir to %s %s", container.WorkingDir, err) } } if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { - log.Fatalf("exec %s", err) + return fmt.Errorf("exec %s", err) } panic("unreachable") } diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go new file mode 100644 index 000000000..47abcce0c --- /dev/null +++ b/pkg/libcontainer/nsinit/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "github.com/dotcloud/docker/pkg/libcontainer" + "log" + "os" +) + +func main() { + container, err := loadContainer() + if err != nil { + log.Fatal(err) + } + + switch os.Args[1] { + case "exec": + exitCode, err := execCommand(container) + if err != nil { + log.Fatal(err) + } + os.Exit(exitCode) + case "init": + if err := initCommand(container, os.Args[2]); err != nil { + log.Fatal(err) + } + } +} + +func loadContainer() (*libcontainer.Container, error) { + f, err := os.Open("container.json") + if err != nil { + return nil, err + } + defer f.Close() + + var container *libcontainer.Container + if err := json.NewDecoder(f).Decode(&container); err != nil { + return nil, err + } + return container, nil +} diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index f9ee96963..13ee13e00 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -3,68 +3,47 @@ package main import ( "fmt" "github.com/dotcloud/docker/pkg/system" - "log" "os" "path/filepath" "syscall" ) -var ( - // default mount point options - defaults = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV -) +// default mount point options +const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV func setupNewMountNamespace(rootfs, console string, readonly bool) error { if err := system.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting / as slave %s", err) } - if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if readonly { if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } } - if err := mountSystem(rootfs); err != nil { return fmt.Errorf("mount system %s", err) } - if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - if err := setupDev(rootfs); err != nil { return err } - - if err := setupConsole(rootfs, console); err != nil { + if err := setupPtmx(rootfs, console); err != nil { return err } - if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { return fmt.Errorf("mount move %s into / %s", rootfs, err) } - if err := system.Chroot("."); err != nil { return fmt.Errorf("chroot . %s", err) } - if err := system.Chdir("/"); err != nil { return fmt.Errorf("chdir / %s", err) } @@ -90,13 +69,10 @@ func copyDevNodes(rootfs string) error { if err != nil { return err } - var ( dest = filepath.Join(rootfs, "dev", node) st = stat.Sys().(*syscall.Stat_t) ) - - log.Printf("copy %s to %s %d\n", node, dest, st.Rdev) if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { return fmt.Errorf("copy %s %s", node, err) } @@ -134,24 +110,22 @@ func setupConsole(rootfs, console string) error { if err != nil { return fmt.Errorf("stat console %s %s", console, err) } - st := stat.Sys().(*syscall.Stat_t) - - dest := filepath.Join(rootfs, "dev/console") + var ( + st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, "dev/console") + ) if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { return fmt.Errorf("remove %s %s", dest, err) } - if err := os.Chmod(console, 0600); err != nil { return err } if err := os.Chown(console, 0, 0); err != nil { return err } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { return fmt.Errorf("mknod %s %s", dest, err) } - if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind %s to %s %s", console, dest, err) } @@ -168,10 +142,10 @@ func mountSystem(rootfs string) error { flags int data string }{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaults}, - {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaults}, + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, {source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaults, data: "mode=1777"}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: "mode=1777"}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"}, {source: "tmpfs", path: filepath.Join(rootfs, "run"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_STRICTATIME, data: "mode=755"}, } { @@ -189,7 +163,7 @@ func remountProc() error { if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { return err } - if err := system.Mount("proc", "/proc", "proc", uintptr(defaults), ""); err != nil { + if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { return err } return nil @@ -201,9 +175,20 @@ func remountSys() error { return err } } else { - if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaults), ""); err != nil { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { return err } } return nil } + +func setupPtmx(rootfs, console string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + return setupConsole(rootfs, console) +} diff --git a/pkg/libcontainer/nsinit/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go index b54bc2b99..2392ffd77 100644 --- a/pkg/libcontainer/nsinit/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -2,27 +2,16 @@ package main import ( "github.com/dotcloud/docker/pkg/libcontainer" -) - -const ( - SIGCHLD = 0x14 - CLONE_VFORK = 0x00004000 - CLONE_NEWNS = 0x00020000 - CLONE_NEWUTS = 0x04000000 - CLONE_NEWIPC = 0x08000000 - CLONE_NEWUSER = 0x10000000 - CLONE_NEWPID = 0x20000000 - CLONE_NEWNET = 0x40000000 + "syscall" ) var namespaceMap = map[libcontainer.Namespace]int{ - "": 0, - libcontainer.CLONE_NEWNS: CLONE_NEWNS, - libcontainer.CLONE_NEWUTS: CLONE_NEWUTS, - libcontainer.CLONE_NEWIPC: CLONE_NEWIPC, - libcontainer.CLONE_NEWUSER: CLONE_NEWUSER, - libcontainer.CLONE_NEWPID: CLONE_NEWPID, - libcontainer.CLONE_NEWNET: CLONE_NEWNET, + libcontainer.CLONE_NEWNS: syscall.CLONE_NEWNS, + libcontainer.CLONE_NEWUTS: syscall.CLONE_NEWUTS, + libcontainer.CLONE_NEWIPC: syscall.CLONE_NEWIPC, + libcontainer.CLONE_NEWUSER: syscall.CLONE_NEWUSER, + libcontainer.CLONE_NEWPID: syscall.CLONE_NEWPID, + libcontainer.CLONE_NEWNET: syscall.CLONE_NEWNET, } // getNamespaceFlags parses the container's Namespaces options to set the correct From f3c48ec584707a9acaf9d79c2e161dadf1d2c99b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 19 Feb 2014 16:50:10 -0800 Subject: [PATCH 077/282] OSX compilation Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 2 ++ pkg/libcontainer/nsinit/init.go | 2 ++ pkg/libcontainer/nsinit/main.go | 13 +++++++++++++ pkg/libcontainer/nsinit/mount.go | 2 ++ 4 files changed, 19 insertions(+) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 4ac070db0..5b53be259 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -1,3 +1,5 @@ +// +build linux + package main import ( diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 16a30812f..1c90ecca2 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -1,3 +1,5 @@ +// +build linux + package main import ( diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 47abcce0c..c9f9d7bc8 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -2,17 +2,27 @@ package main import ( "encoding/json" + "errors" "github.com/dotcloud/docker/pkg/libcontainer" "log" "os" ) +var ( + ErrUnsupported = errors.New("Unsupported method") + ErrWrongArguments = errors.New("Wrong argument count") +) + func main() { container, err := loadContainer() if err != nil { log.Fatal(err) } + argc := len(os.Args) + if argc < 2 { + log.Fatal(ErrWrongArguments) + } switch os.Args[1] { case "exec": exitCode, err := execCommand(container) @@ -21,6 +31,9 @@ func main() { } os.Exit(exitCode) case "init": + if argc != 3 { + log.Fatal(ErrWrongArguments) + } if err := initCommand(container, os.Args[2]); err != nil { log.Fatal(err) } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 13ee13e00..baa850f0f 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -1,3 +1,5 @@ +// +build linux + package main import ( From 5d62916c48cb97320b37640592805d97badfd8ff Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 19:14:31 -0800 Subject: [PATCH 078/282] Refactor large funcs Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 5 +- pkg/libcontainer/container.json | 3 +- pkg/libcontainer/nsinit/exec.go | 60 +++++++++++++++-------- pkg/libcontainer/nsinit/init.go | 87 +++++++++++++++------------------ pkg/libcontainer/types.go | 48 +++++++++--------- 5 files changed, 107 insertions(+), 96 deletions(-) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 3f3961d49..c8dbdd668 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -1,14 +1,13 @@ package libcontainer type Container struct { - ID string `json:"id,omitempty"` - Command *Command `json:"command,omitempty"` + Hostname string `json:"hostname,omitempty"` ReadonlyFs bool `json:"readonly_fs,omitempty"` User string `json:"user,omitempty"` WorkingDir string `json:"working_dir,omitempty"` + Command *Command `json:"command,omitempty"` Namespaces Namespaces `json:"namespaces,omitempty"` Capabilities Capabilities `json:"capabilities,omitempty"` - LogFile string `json:"log_file,omitempty"` Network *Network `json:"network,omitempty"` } diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 8731170c2..2abf01adb 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,6 +1,5 @@ { "id": "koye", - "log_file": "/root/logs", "command": { "args": [ "/bin/bash" @@ -9,7 +8,7 @@ "HOME=/", "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", "container=docker", - "TERM=xterm" + "TERM=xterm-256color" ] }, "namespaces": [ diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 5b53be259..4abebd294 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -27,6 +27,8 @@ func execCommand(container *libcontainer.Container) (int, error) { Cloneflags: uintptr(getNamespaceFlags(container.Namespaces) | syscall.CLONE_VFORK), // we need CLONE_VFORK so we can wait on the child } + // create a pipe so that we can syncronize with the namespaced process and + // pass the veth name to the child inPipe, err := command.StdinPipe() if err != nil { return -1, err @@ -39,34 +41,17 @@ func execCommand(container *libcontainer.Container) (int, error) { } if container.Network != nil { - name1, name2, err := createVethPair() + vethPair, err := setupVeth(container.Network.Bridge, command.Process.Pid) if err != nil { return -1, err } - if err := network.SetInterfaceMaster(name1, container.Network.Bridge); err != nil { - return -1, err - } - if err := network.InterfaceUp(name1); err != nil { - return -1, err - } - if err := network.SetInterfaceInNamespacePid(name2, command.Process.Pid); err != nil { - return -1, err - } - fmt.Fprint(inPipe, name2) - inPipe.Close() + sendVethName(vethPair, inPipe) } go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) - ws, err := term.GetWinsize(os.Stdin.Fd()) - if err != nil { - return -1, err - } - if err := term.SetWinsize(master.Fd(), ws); err != nil { - return -1, err - } - state, err := term.SetRawTerminal(os.Stdin.Fd()) + state, err := setupWindow(master) if err != nil { command.Process.Kill() return -1, err @@ -81,6 +66,41 @@ func execCommand(container *libcontainer.Container) (int, error) { return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } +func sendVethName(name string, pipe io.WriteCloser) { + // write the veth pair name to the child's stdin then close the + // pipe so that the child stops waiting + fmt.Fprint(pipe, name) + pipe.Close() +} + +func setupVeth(bridge string, nspid int) (string, error) { + name1, name2, err := createVethPair() + if err != nil { + return "", err + } + if err := network.SetInterfaceMaster(name1, bridge); err != nil { + return "", err + } + if err := network.InterfaceUp(name1); err != nil { + return "", err + } + if err := network.SetInterfaceInNamespacePid(name2, nspid); err != nil { + return "", err + } + return name2, nil +} + +func setupWindow(master *os.File) (*term.State, error) { + ws, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + return nil, err + } + if err := term.SetWinsize(master.Fd(), ws); err != nil { + return nil, err + } + return term.SetRawTerminal(os.Stdin.Fd()) +} + func createMasterAndConsole() (*os.File, string, error) { master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) if err != nil { diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 1c90ecca2..d853a32d0 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -9,17 +9,12 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" "io/ioutil" - "log" "os" "path/filepath" "syscall" ) func initCommand(container *libcontainer.Container, console string) error { - if err := setLogFile(container); err != nil { - return err - } - rootfs, err := resolveRootfs() if err != nil { return err @@ -27,11 +22,10 @@ func initCommand(container *libcontainer.Container, console string) error { var tempVethName string if container.Network != nil { - data, err := ioutil.ReadAll(os.Stdin) + tempVethName, err = getVethName() if err != nil { - return fmt.Errorf("error reading from stdin %s", err) + return err } - tempVethName = string(data) } // close pipes so that we can replace it with the pty @@ -61,13 +55,10 @@ func initCommand(container *libcontainer.Container, console string) error { if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { return fmt.Errorf("setup mount namespace %s", err) } - if container.Network != nil { - if err := setupNetworking(container.Network, tempVethName); err != nil { - return fmt.Errorf("setup networking %s", err) - } + if err := setupNetworking(container.Network, tempVethName); err != nil { + return fmt.Errorf("setup networking %s", err) } - - if err := system.Sethostname(container.ID); err != nil { + if err := system.Sethostname(container.Hostname); err != nil { return fmt.Errorf("sethostname %s", err) } if err := capabilities.DropCapabilities(container); err != nil { @@ -136,43 +127,45 @@ func openTerminal(name string, flag int) (*os.File, error) { return os.NewFile(uintptr(r), name), nil } -func setLogFile(container *libcontainer.Container) error { - if container.LogFile != "" { - f, err := os.OpenFile(container.LogFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0655) - if err != nil { - return err +func setupNetworking(config *libcontainer.Network, tempVethName string) error { + if config != nil { + if err := network.InterfaceDown(tempVethName); err != nil { + return fmt.Errorf("interface down %s %s", tempVethName, err) + } + if err := network.ChangeInterfaceName(tempVethName, "eth0"); err != nil { + return fmt.Errorf("change %s to eth0 %s", tempVethName, err) + } + if err := network.SetInterfaceIp("eth0", config.IP); err != nil { + return fmt.Errorf("set eth0 ip %s", err) + } + if err := network.SetMtu("eth0", config.Mtu); err != nil { + return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) + } + if err := network.InterfaceUp("eth0"); err != nil { + return fmt.Errorf("eth0 up %s", err) + } + if err := network.SetMtu("lo", config.Mtu); err != nil { + return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) + } + if err := network.InterfaceUp("lo"); err != nil { + return fmt.Errorf("lo up %s", err) + } + if config.Gateway != "" { + if err := network.SetDefaultGateway(config.Gateway); err != nil { + return fmt.Errorf("set gateway to %s %s", config.Gateway, err) + } } - log.SetOutput(f) } return nil } -func setupNetworking(config *libcontainer.Network, tempVethName string) error { - if err := network.InterfaceDown(tempVethName); err != nil { - return fmt.Errorf("interface down %s %s", tempVethName, err) +// getVethName reads from Stdin the temp veth name +// sent by the parent processes after the veth pair +// has been created and setup +func getVethName() (string, error) { + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return "", fmt.Errorf("error reading from stdin %s", err) } - if err := network.ChangeInterfaceName(tempVethName, "eth0"); err != nil { - return fmt.Errorf("change %s to eth0 %s", tempVethName, err) - } - if err := network.SetInterfaceIp("eth0", config.IP); err != nil { - return fmt.Errorf("set eth0 ip %s", err) - } - if err := network.SetMtu("eth0", config.Mtu); err != nil { - return fmt.Errorf("set eth0 mtu to %d %s", config.Mtu, err) - } - if err := network.InterfaceUp("eth0"); err != nil { - return fmt.Errorf("eth0 up %s", err) - } - if err := network.SetMtu("lo", config.Mtu); err != nil { - return fmt.Errorf("set lo mtu to %d %s", config.Mtu, err) - } - if err := network.InterfaceUp("lo"); err != nil { - return fmt.Errorf("lo up %s", err) - } - if config.Gateway != "" { - if err := network.SetDefaultGateway(config.Gateway); err != nil { - return fmt.Errorf("set gateway to %s %s", config.Gateway, err) - } - } - return nil + return string(data), nil } diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index db1c3b973..b5d993267 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -1,29 +1,5 @@ package libcontainer -type Namespace string -type Namespaces []Namespace - -func (n Namespaces) Contains(ns Namespace) bool { - for _, nns := range n { - if nns == ns { - return true - } - } - return false -} - -type Capability string -type Capabilities []Capability - -func (c Capabilities) Contains(capp Capability) bool { - for _, cc := range c { - if cc == capp { - return true - } - } - return false -} - const ( CAP_SETPCAP Capability = "SETPCAP" CAP_SYS_MODULE Capability = "SYS_MODULE" @@ -47,3 +23,27 @@ const ( CLONE_NEWPID Namespace = "NEWPID" // pid CLONE_NEWNET Namespace = "NEWNET" // network ) + +type Namespace string +type Namespaces []Namespace + +func (n Namespaces) Contains(ns Namespace) bool { + for _, nns := range n { + if nns == ns { + return true + } + } + return false +} + +type Capability string +type Capabilities []Capability + +func (c Capabilities) Contains(capp Capability) bool { + for _, cc := range c { + if cc == capp { + return true + } + } + return false +} From 420b5eb211f877baac9622e7bedde2948c043619 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 19:53:25 -0800 Subject: [PATCH 079/282] Add execin function to running a process in a namespace Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.json | 2 +- pkg/libcontainer/nsinit/execin.go | 115 ++++++++++++++++++++++++++++ pkg/libcontainer/nsinit/main.go | 8 ++ pkg/libcontainer/nsinit/ns_linux.go | 9 +++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 pkg/libcontainer/nsinit/execin.go diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 2abf01adb..c5807a7b2 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,5 +1,5 @@ { - "id": "koye", + "hostname": "koye", "command": { "args": [ "/bin/bash" diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go new file mode 100644 index 000000000..362cf5afd --- /dev/null +++ b/pkg/libcontainer/nsinit/execin.go @@ -0,0 +1,115 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/system" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "syscall" +) + +func execinCommand(container *libcontainer.Container) (int, error) { + nspid, err := readPid() + if err != nil { + return -1, err + } + + for _, ns := range container.Namespaces { + if err := system.Unshare(namespaceMap[ns]); err != nil { + return -1, err + } + } + fds, err := getNsFds(nspid, container) + closeFds := func() { + for _, f := range fds { + system.Closefd(f) + } + } + if err != nil { + closeFds() + return -1, err + } + + for _, fd := range fds { + if fd > 0 { + if err := system.Setns(fd, 0); err != nil { + closeFds() + return -1, fmt.Errorf("setns %s", err) + } + } + system.Closefd(fd) + } + + // if the container has a new pid and mount namespace we need to + // remount proc and sys to pick up the changes + if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && + container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { + + pid, err := system.Fork() + if err != nil { + return -1, err + } + if pid == 0 { + // TODO: make all raw syscalls to be fork safe + if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { + return -1, err + } + if err := remountProc(); err != nil { + return -1, fmt.Errorf("remount proc %s", err) + } + if err := remountSys(); err != nil { + return -1, fmt.Errorf("remount sys %s", err) + } + if err := capabilities.DropCapabilities(container); err != nil { + return -1, fmt.Errorf("drop capabilities %s", err) + } + if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + return -1, err + } + } + proc, err := os.FindProcess(pid) + if err != nil { + return -1, err + } + state, err := proc.Wait() + if err != nil { + return -1, err + } + os.Exit(state.Sys().(syscall.WaitStatus).ExitStatus()) + } + if err := capabilities.DropCapabilities(container); err != nil { + return -1, fmt.Errorf("drop capabilities %s", err) + } + if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + return -1, err + } + panic("unreachable") +} + +func readPid() (int, error) { + data, err := ioutil.ReadFile(".nspid") + if err != nil { + return -1, err + } + pid, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + return pid, nil +} + +func getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) { + fds := make([]uintptr, len(container.Namespaces)) + for i, ns := range container.Namespaces { + f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", namespaceFileMap[ns]), os.O_RDONLY, 0) + if err != nil { + return fds, err + } + fds[i] = f.Fd() + } + return fds, nil +} diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index c9f9d7bc8..8fe700e06 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -37,6 +37,14 @@ func main() { if err := initCommand(container, os.Args[2]); err != nil { log.Fatal(err) } + case "execin": + exitCode, err := execinCommand(container) + if err != nil { + log.Fatal(err) + } + os.Exit(exitCode) + default: + log.Fatalf("command not supported for nsinit %s", os.Args[1]) } } diff --git a/pkg/libcontainer/nsinit/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go index 2392ffd77..a2809eb19 100644 --- a/pkg/libcontainer/nsinit/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -14,6 +14,15 @@ var namespaceMap = map[libcontainer.Namespace]int{ libcontainer.CLONE_NEWNET: syscall.CLONE_NEWNET, } +var namespaceFileMap = map[libcontainer.Namespace]string{ + libcontainer.CLONE_NEWNS: "mnt", + libcontainer.CLONE_NEWUTS: "uts", + libcontainer.CLONE_NEWIPC: "ipc", + libcontainer.CLONE_NEWUSER: "user", + libcontainer.CLONE_NEWPID: "pid", + libcontainer.CLONE_NEWNET: "net", +} + // getNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { From d84feb8fe5e40838c81321249189f1f0a02825bb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 20:35:04 -0800 Subject: [PATCH 080/282] Refactor to remove cmd from container Pass the container's command via args Remove execin function and just look for an existing nspid file to join the namespace Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 7 +----- pkg/libcontainer/container.json | 17 +++++--------- pkg/libcontainer/nsinit/exec.go | 21 +++++++++++++----- pkg/libcontainer/nsinit/execin.go | 24 +++----------------- pkg/libcontainer/nsinit/init.go | 4 ++-- pkg/libcontainer/nsinit/main.go | 37 +++++++++++++++++++++++-------- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index c8dbdd668..763526f66 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -5,17 +5,12 @@ type Container struct { ReadonlyFs bool `json:"readonly_fs,omitempty"` User string `json:"user,omitempty"` WorkingDir string `json:"working_dir,omitempty"` - Command *Command `json:"command,omitempty"` + Env []string `json:"environment,omitempty"` Namespaces Namespaces `json:"namespaces,omitempty"` Capabilities Capabilities `json:"capabilities,omitempty"` Network *Network `json:"network,omitempty"` } -type Command struct { - Args []string `json:"args,omitempty"` - Env []string `json:"environment,omitempty"` -} - type Network struct { IP string `json:"ip,omitempty"` Gateway string `json:"gateway,omitempty"` diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index c5807a7b2..ccc9abb04 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,16 +1,11 @@ { "hostname": "koye", - "command": { - "args": [ - "/bin/bash" - ], - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ] - }, + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "namespaces": [ "NEWIPC", "NEWNS", diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 4abebd294..67f907af5 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -16,17 +16,13 @@ import ( "syscall" ) -func execCommand(container *libcontainer.Container) (int, error) { +func execCommand(container *libcontainer.Container, args []string) (int, error) { master, console, err := createMasterAndConsole() if err != nil { return -1, err } - command := exec.Command("nsinit", "init", console) - command.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: uintptr(getNamespaceFlags(container.Namespaces) | syscall.CLONE_VFORK), // we need CLONE_VFORK so we can wait on the child - } - + command := createCommand(container, console, args) // create a pipe so that we can syncronize with the namespaced process and // pass the veth name to the child inPipe, err := command.StdinPipe() @@ -39,6 +35,7 @@ func execCommand(container *libcontainer.Container) (int, error) { if err := writePidFile(command); err != nil { return -1, err } + defer deletePidFile() if container.Network != nil { vethPair, err := setupVeth(container.Network.Bridge, command.Process.Pid) @@ -134,3 +131,15 @@ func createVethPair() (name1 string, name2 string, err error) { func writePidFile(command *exec.Cmd) error { return ioutil.WriteFile(".nspid", []byte(fmt.Sprint(command.Process.Pid)), 0655) } + +func deletePidFile() error { + return os.Remove(".nspid") +} + +func createCommand(container *libcontainer.Container, console string, args []string) *exec.Cmd { + command := exec.Command("nsinit", append([]string{"init", console}, args...)...) + command.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: uintptr(getNamespaceFlags(container.Namespaces) | syscall.CLONE_VFORK), // we need CLONE_VFORK so we can wait on the child + } + return command +} diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 362cf5afd..7f32620cb 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -5,19 +5,13 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/system" - "io/ioutil" "os" "path/filepath" "strconv" "syscall" ) -func execinCommand(container *libcontainer.Container) (int, error) { - nspid, err := readPid() - if err != nil { - return -1, err - } - +func execinCommand(container *libcontainer.Container, nspid int, args []string) (int, error) { for _, ns := range container.Namespaces { if err := system.Unshare(namespaceMap[ns]); err != nil { return -1, err @@ -67,7 +61,7 @@ func execinCommand(container *libcontainer.Container) (int, error) { if err := capabilities.DropCapabilities(container); err != nil { return -1, fmt.Errorf("drop capabilities %s", err) } - if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + if err := system.Exec(args[0], args[0:], container.Env); err != nil { return -1, err } } @@ -84,24 +78,12 @@ func execinCommand(container *libcontainer.Container) (int, error) { if err := capabilities.DropCapabilities(container); err != nil { return -1, fmt.Errorf("drop capabilities %s", err) } - if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + if err := system.Exec(args[0], args[0:], container.Env); err != nil { return -1, err } panic("unreachable") } -func readPid() (int, error) { - data, err := ioutil.ReadFile(".nspid") - if err != nil { - return -1, err - } - pid, err := strconv.Atoi(string(data)) - if err != nil { - return -1, err - } - return pid, nil -} - func getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) { fds := make([]uintptr, len(container.Namespaces)) for i, ns := range container.Namespaces { diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index d853a32d0..82706fdad 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -14,7 +14,7 @@ import ( "syscall" ) -func initCommand(container *libcontainer.Container, console string) error { +func initCommand(container *libcontainer.Container, console string, args []string) error { rootfs, err := resolveRootfs() if err != nil { return err @@ -72,7 +72,7 @@ func initCommand(container *libcontainer.Container, console string) error { return fmt.Errorf("chdir to %s %s", container.WorkingDir, err) } } - if err := system.Exec(container.Command.Args[0], container.Command.Args[0:], container.Command.Env); err != nil { + if err := system.Exec(args[0], args[0:], container.Env); err != nil { return fmt.Errorf("exec %s", err) } panic("unreachable") diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 8fe700e06..30c8b064e 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -4,8 +4,10 @@ import ( "encoding/json" "errors" "github.com/dotcloud/docker/pkg/libcontainer" + "io/ioutil" "log" "os" + "strconv" ) var ( @@ -25,24 +27,29 @@ func main() { } switch os.Args[1] { case "exec": - exitCode, err := execCommand(container) + var exitCode int + nspid, err := readPid() + if err != nil { + if !os.IsNotExist(err) { + log.Fatal(err) + } + } + if nspid > 0 { + exitCode, err = execinCommand(container, nspid, os.Args[2:]) + } else { + exitCode, err = execCommand(container, os.Args[2:]) + } if err != nil { log.Fatal(err) } os.Exit(exitCode) case "init": - if argc != 3 { + if argc < 3 { log.Fatal(ErrWrongArguments) } - if err := initCommand(container, os.Args[2]); err != nil { + if err := initCommand(container, os.Args[2], os.Args[3:]); err != nil { log.Fatal(err) } - case "execin": - exitCode, err := execinCommand(container) - if err != nil { - log.Fatal(err) - } - os.Exit(exitCode) default: log.Fatalf("command not supported for nsinit %s", os.Args[1]) } @@ -61,3 +68,15 @@ func loadContainer() (*libcontainer.Container, error) { } return container, nil } + +func readPid() (int, error) { + data, err := ioutil.ReadFile(".nspid") + if err != nil { + return -1, err + } + pid, err := strconv.Atoi(string(data)) + if err != nil { + return -1, err + } + return pid, nil +} From 3a97fe27d8f5a9bbcf4992cc9efe33880e73f274 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 21:15:44 -0800 Subject: [PATCH 081/282] Update readme and add TODO Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 75 ++++++++++++++++++++++---------------- pkg/libcontainer/TODO.md | 17 +++++++++ 2 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 pkg/libcontainer/TODO.md diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 91d747863..07fe4f7b2 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -1,39 +1,34 @@ ## libcontainer - reference implementation for containers -#### playground +#### background + +libcontainer specifies configuration options for what a container is. It provides a native Go implementation +for using linux namespaces with no external dependencies. libcontainer provides many convience functions for working with namespaces, networking, and management. -Use the cli package to test out functionality - -First setup a container configuration. You will need a root fs, better go the path to a -stopped docker container and use that. - +#### container +A container is a self contained directory that is able to run one or more processes inside without +affecting the host system. The directory is usually a full system tree. Inside the directory +a `container.json` file just be placed with the runtime configuration for how the process +should be contained and run. Environment, networking, and different capabilities for the +process are specified in this file. +Sample `container.json` file: ```json { - "id": "koye", - "namespace_pid": 12265, - "command": { - "args": [ - "/bin/bash" - ], - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm" - ] - }, - "rootfs": "/root/development/gocode/src/github.com/docker/libcontainer/namespaces/ubuntu", - "network": null, - "user": "", - "working_dir": "", + "hostname": "koye", + "environment": [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "namespaces": [ - "NEWNET", "NEWIPC", "NEWNS", "NEWPID", - "NEWUTS" + "NEWUTS", + "NEWNET" ], "capabilities": [ "SETPCAP", @@ -50,14 +45,32 @@ stopped docker container and use that. "AUDIT_CONTROL", "MAC_OVERRIDE", "MAC_ADMIN" - ] + ], + "network": { + "ip": "172.17.0.100/16", + "gateway": "172.17.42.1", + "bridge": "docker0", + "mtu": 1500 + } } ``` -After you have a json file and a rootfs path to use just run: -`./cli exec container.json` +Using this configuration and the current directory holding the rootfs for a process to live, one can se libcontainer to exec the container. Running the life of the namespace a `.nspid` file +is written to the current directory with the pid of the namespace'd process to the external word. A client can use this pid to wait, kill, or perform other operation with the container. If a user tries to run an new process inside an existing container with a live namespace with namespace will be joined by the new process. -If you want to attach to an existing namespace just use the same json -file with the container still running and do: -`./cli execin container.json` +#### nsinit + +`nsinit` is a cli application used as the reference implementation of libcontainer. It is able to +spawn or join new containers giving the current directory. To use `nsinit` cd into a linux +rootfs and copy a `container.json` file into the directory with your specified configuration. + +To execution `/bin/bash` in the current directory as a container just run: +```bash +nsinit exec /bin/bash +``` + +If you wish to spawn another process inside the container while your current bash session is +running just run the exact same command again to get another bash shell or change the command. If the original process dies, PID 1, all other processes spawned inside the container will also be killed and the namespace will be removed. + +You can identify if a process is running in a container by looking to see if `.nspid` is in the root of the directory. diff --git a/pkg/libcontainer/TODO.md b/pkg/libcontainer/TODO.md new file mode 100644 index 000000000..f18c0b4c5 --- /dev/null +++ b/pkg/libcontainer/TODO.md @@ -0,0 +1,17 @@ +#### goals +* small and simple - line count is not everything but less code is better +* clean lines between what we do in the pkg +* provide primitives for working with namespaces not cater to every option +* extend via configuration not by features - host networking, no networking, veth network can be accomplished via adjusting the container.json, nothing to do with code + +#### tasks +* proper tty for a new process in an existing container +* use exec or raw syscalls for new process in existing container +* setup proper user in namespace if specified +* implement hook or clean interface for cgroups +* example configs for different setups (host networking, boot init) +* improve pkg documentation with comments +* testing - this is hard in a low level pkg but we could do some, maybe +* pivot root +* selinux +* apparmor From e0ff0f4dd6612e331459a2dec69adc728bc360fe Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 21:21:49 -0800 Subject: [PATCH 082/282] Add CAP_NET_ADMIN Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/capabilities/capabilities.go | 1 + pkg/libcontainer/types.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/capabilities/capabilities.go index 3301e10f7..c19b71956 100644 --- a/pkg/libcontainer/capabilities/capabilities.go +++ b/pkg/libcontainer/capabilities/capabilities.go @@ -21,6 +21,7 @@ var capMap = map[libcontainer.Capability]capability.Cap{ libcontainer.CAP_AUDIT_CONTROL: capability.CAP_AUDIT_CONTROL, libcontainer.CAP_MAC_OVERRIDE: capability.CAP_MAC_OVERRIDE, libcontainer.CAP_MAC_ADMIN: capability.CAP_MAC_ADMIN, + libcontainer.CAP_NET_ADMIN: capability.CAP_NET_ADMIN, } // DropCapabilities drops capabilities for the current process based diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index b5d993267..fcd00fd4f 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -15,6 +15,7 @@ const ( CAP_AUDIT_CONTROL Capability = "AUDIT_CONTROL" CAP_MAC_OVERRIDE Capability = "MAC_OVERRIDE" CAP_MAC_ADMIN Capability = "MAC_ADMIN" + CAP_NET_ADMIN Capability = "NET_ADMIN" CLONE_NEWNS Namespace = "NEWNS" // mount CLONE_NEWUTS Namespace = "NEWUTS" // utsname From 70593be139647cdedca0130250626ea6e0b8a277 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 22:43:40 -0800 Subject: [PATCH 083/282] Add comments to many functions Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/capabilities/capabilities.go | 1 + pkg/libcontainer/container.go | 22 ++++++---- pkg/libcontainer/nsinit/exec.go | 23 +++++++++-- pkg/libcontainer/nsinit/execin.go | 10 ++--- pkg/libcontainer/nsinit/init.go | 18 ++++---- pkg/libcontainer/nsinit/main.go | 4 +- pkg/libcontainer/nsinit/mount.go | 41 +++++++++++++------ pkg/libcontainer/nsinit/ns_linux.go | 3 ++ pkg/libcontainer/types.go | 18 +++++--- pkg/libcontainer/utils/utils.go | 2 + 10 files changed, 97 insertions(+), 45 deletions(-) diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/capabilities/capabilities.go index c19b71956..65fd455c2 100644 --- a/pkg/libcontainer/capabilities/capabilities.go +++ b/pkg/libcontainer/capabilities/capabilities.go @@ -41,6 +41,7 @@ func DropCapabilities(container *libcontainer.Container) error { return nil } +// getCapabilities returns the specific cap values for the libcontainer types func getCapabilities(container *libcontainer.Container) []capability.Cap { drop := []capability.Cap{} for _, c := range container.Capabilities { diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 763526f66..a6a57dab7 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -1,16 +1,22 @@ package libcontainer +// Container defines configuration options for how a +// container is setup inside a directory and how a process should be executed type Container struct { - Hostname string `json:"hostname,omitempty"` - ReadonlyFs bool `json:"readonly_fs,omitempty"` - User string `json:"user,omitempty"` - WorkingDir string `json:"working_dir,omitempty"` - Env []string `json:"environment,omitempty"` - Namespaces Namespaces `json:"namespaces,omitempty"` - Capabilities Capabilities `json:"capabilities,omitempty"` - Network *Network `json:"network,omitempty"` + Hostname string `json:"hostname,omitempty"` // hostname + ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly + User string `json:"user,omitempty"` // user to execute the process as + WorkingDir string `json:"working_dir,omitempty"` // current working directory + Env []string `json:"environment,omitempty"` // environment to set + Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply + Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop + Network *Network `json:"network,omitempty"` // nil for host's network stack } +// Network defines configuration for a container's networking stack +// +// The network configuration can be omited from a container causing the +// container to be setup with the host's networking stack type Network struct { IP string `json:"ip,omitempty"` Gateway string `json:"gateway,omitempty"` diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 67f907af5..202cfcab5 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -38,7 +38,7 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) defer deletePidFile() if container.Network != nil { - vethPair, err := setupVeth(container.Network.Bridge, command.Process.Pid) + vethPair, err := initializeContainerVeth(container.Network.Bridge, command.Process.Pid) if err != nil { return -1, err } @@ -63,14 +63,21 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } +// sendVethName writes the veth pair name to the child's stdin then closes the +// pipe so that the child stops waiting for more data func sendVethName(name string, pipe io.WriteCloser) { - // write the veth pair name to the child's stdin then close the - // pipe so that the child stops waiting fmt.Fprint(pipe, name) pipe.Close() } -func setupVeth(bridge string, nspid int) (string, error) { +// initializeContainerVeth will create a veth pair and setup the host's +// side of the pair by setting the specified bridge as the master and bringing +// up the interface. +// +// Then will with set the other side of the veth pair into the container's namespaced +// using the pid and returns the veth's interface name to provide to the container to +// finish setting up the interface inside the namespace +func initializeContainerVeth(bridge string, nspid int) (string, error) { name1, name2, err := createVethPair() if err != nil { return "", err @@ -98,6 +105,8 @@ func setupWindow(master *os.File) (*term.State, error) { return term.SetRawTerminal(os.Stdin.Fd()) } +// createMasterAndConsole will open /dev/ptmx on the host and retreive the +// pts name for use as the pty slave inside the container func createMasterAndConsole() (*os.File, string, error) { master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) if err != nil { @@ -113,6 +122,8 @@ func createMasterAndConsole() (*os.File, string, error) { return master, console, nil } +// createVethPair will automatically generage two random names for +// the veth pair and ensure that they have been created func createVethPair() (name1 string, name2 string, err error) { name1, err = utils.GenerateRandomName("dock", 4) if err != nil { @@ -128,6 +139,7 @@ func createVethPair() (name1 string, name2 string, err error) { return } +// writePidFile writes the namespaced processes pid to .nspid in the rootfs for the container func writePidFile(command *exec.Cmd) error { return ioutil.WriteFile(".nspid", []byte(fmt.Sprint(command.Process.Pid)), 0655) } @@ -136,6 +148,9 @@ func deletePidFile() error { return os.Remove(".nspid") } +// createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces +// defined on the container's configuration and use the current binary as the init with the +// args provided func createCommand(container *libcontainer.Container, console string, args []string) *exec.Cmd { command := exec.Command("nsinit", append([]string{"init", console}, args...)...) command.SysProcAttr = &syscall.SysProcAttr{ diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 7f32620cb..d6224f95e 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -28,6 +28,7 @@ func execinCommand(container *libcontainer.Container, nspid int, args []string) return -1, err } + // foreach namespace fd, use setns to join an existing container's namespaces for _, fd := range fds { if fd > 0 { if err := system.Setns(fd, 0); err != nil { @@ -42,7 +43,6 @@ func execinCommand(container *libcontainer.Container, nspid int, args []string) // remount proc and sys to pick up the changes if container.Namespaces.Contains(libcontainer.CLONE_NEWNS) && container.Namespaces.Contains(libcontainer.CLONE_NEWPID) { - pid, err := system.Fork() if err != nil { return -1, err @@ -58,12 +58,7 @@ func execinCommand(container *libcontainer.Container, nspid int, args []string) if err := remountSys(); err != nil { return -1, fmt.Errorf("remount sys %s", err) } - if err := capabilities.DropCapabilities(container); err != nil { - return -1, fmt.Errorf("drop capabilities %s", err) - } - if err := system.Exec(args[0], args[0:], container.Env); err != nil { - return -1, err - } + goto dropAndExec } proc, err := os.FindProcess(pid) if err != nil { @@ -75,6 +70,7 @@ func execinCommand(container *libcontainer.Container, nspid int, args []string) } os.Exit(state.Sys().(syscall.WaitStatus).ExitStatus()) } +dropAndExec: if err := capabilities.DropCapabilities(container); err != nil { return -1, fmt.Errorf("drop capabilities %s", err) } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 82706fdad..c77fd9044 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -37,9 +37,6 @@ func initCommand(container *libcontainer.Container, console string, args []strin if err != nil { return fmt.Errorf("open terminal %s", err) } - if slave.Fd() != 0 { - return fmt.Errorf("slave fd should be 0") - } if err := dupSlave(slave); err != nil { return fmt.Errorf("dup2 slave %s", err) } @@ -55,7 +52,7 @@ func initCommand(container *libcontainer.Container, console string, args []strin if err := setupNewMountNamespace(rootfs, console, container.ReadonlyFs); err != nil { return fmt.Errorf("setup mount namespace %s", err) } - if err := setupNetworking(container.Network, tempVethName); err != nil { + if err := setupVethNetwork(container.Network, tempVethName); err != nil { return fmt.Errorf("setup networking %s", err) } if err := system.Sethostname(container.Hostname); err != nil { @@ -78,6 +75,8 @@ func initCommand(container *libcontainer.Container, console string, args []strin panic("unreachable") } +// resolveRootfs ensures that the current working directory is +// not a symlink and returns the absolute path to the rootfs func resolveRootfs() (string, error) { cwd, err := os.Getwd() if err != nil { @@ -104,8 +103,9 @@ func setupUser(container *libcontainer.Container) error { return nil } +// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that +// the slave's fd is 0, or stdin func dupSlave(slave *os.File) error { - // we close Stdin,etc so our pty slave should have fd 0 if slave.Fd() != 0 { return fmt.Errorf("slave fd not 0 %d", slave.Fd()) } @@ -118,7 +118,8 @@ func dupSlave(slave *os.File) error { return nil } -// openTerminal is a clone of os.OpenFile without the O_CLOEXEC addition. +// openTerminal is a clone of os.OpenFile without the O_CLOEXEC +// used to open the pty slave inside the container namespace func openTerminal(name string, flag int) (*os.File, error) { r, e := syscall.Open(name, flag, 0) if e != nil { @@ -127,7 +128,10 @@ func openTerminal(name string, flag int) (*os.File, error) { return os.NewFile(uintptr(r), name), nil } -func setupNetworking(config *libcontainer.Network, tempVethName string) error { +// setupVethNetwork uses the Network config if it is not nil to initialize +// the new veth interface inside the container for use by changing the name to eth0 +// setting the MTU and IP address along with the default gateway +func setupVethNetwork(config *libcontainer.Network, tempVethName string) error { if config != nil { if err := network.InterfaceDown(tempVethName); err != nil { return fmt.Errorf("interface down %s %s", tempVethName, err) diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 30c8b064e..f45fe5568 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -26,7 +26,7 @@ func main() { log.Fatal(ErrWrongArguments) } switch os.Args[1] { - case "exec": + case "exec": // this is executed outside of the namespace in the cwd var exitCode int nspid, err := readPid() if err != nil { @@ -43,7 +43,7 @@ func main() { log.Fatal(err) } os.Exit(exitCode) - case "init": + case "init": // this is executed inside of the namespace to setup the container if argc < 3 { log.Fatal(ErrWrongArguments) } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index baa850f0f..6eb2e0906 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -10,10 +10,16 @@ import ( "syscall" ) -// default mount point options +// default mount point flags const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +// setupNewMountNamespace is used to initialize a new mount namespace for an new +// container in the rootfs that is specified. +// +// There is no need to unmount the new mounts because as soon as the mount namespace +// is no longer in use, the mounts will be removed automatically func setupNewMountNamespace(rootfs, console string, readonly bool) error { + // mount as slave so that the new mounts do not propagate to the host if err := system.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting / as slave %s", err) } @@ -55,6 +61,7 @@ func setupNewMountNamespace(rootfs, console string, readonly bool) error { return nil } +// copyDevNodes mknods the hosts devices so the new container has access to them func copyDevNodes(rootfs string) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) @@ -82,6 +89,8 @@ func copyDevNodes(rootfs string) error { return nil } +// setupDev symlinks the current processes pipes into the +// appropriate destination on the containers rootfs func setupDev(rootfs string) error { for _, link := range []struct { from string @@ -104,6 +113,7 @@ func setupDev(rootfs string) error { return nil } +// setupConsole ensures that the container has a proper /dev/console setup func setupConsole(rootfs, console string) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) @@ -161,6 +171,24 @@ func mountSystem(rootfs string) error { return nil } +// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and +// finishes setting up /dev/console +func setupPtmx(rootfs, console string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + if err := setupConsole(rootfs, console); err != nil { + return err + } + return nil +} + +// remountProc is used to detach and remount the proc filesystem +// commonly needed with running a new process inside an existing container func remountProc() error { if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { return err @@ -183,14 +211,3 @@ func remountSys() error { } return nil } - -func setupPtmx(rootfs, console string) error { - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - return setupConsole(rootfs, console) -} diff --git a/pkg/libcontainer/nsinit/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go index a2809eb19..481bdf79d 100644 --- a/pkg/libcontainer/nsinit/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -14,6 +14,9 @@ var namespaceMap = map[libcontainer.Namespace]int{ libcontainer.CLONE_NEWNET: syscall.CLONE_NEWNET, } +// namespaceFileMap is used to convert the libcontainer types +// into the names of the files located in /proc//ns/* for +// each namespace var namespaceFileMap = map[libcontainer.Namespace]string{ libcontainer.CLONE_NEWNS: "mnt", libcontainer.CLONE_NEWUTS: "uts", diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index fcd00fd4f..bb54ff513 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -1,5 +1,8 @@ package libcontainer +// These constants are defined as string types so that +// it is clear when adding the configuration in config files +// instead of using ints or other types const ( CAP_SETPCAP Capability = "SETPCAP" CAP_SYS_MODULE Capability = "SYS_MODULE" @@ -25,9 +28,15 @@ const ( CLONE_NEWNET Namespace = "NEWNET" // network ) -type Namespace string -type Namespaces []Namespace +type ( + Namespace string + Namespaces []Namespace + Capability string + Capabilities []Capability +) +// Contains returns true if the specified Namespace is +// in the slice func (n Namespaces) Contains(ns Namespace) bool { for _, nns := range n { if nns == ns { @@ -37,9 +46,8 @@ func (n Namespaces) Contains(ns Namespace) bool { return false } -type Capability string -type Capabilities []Capability - +// Contains returns true if the specified Capability is +// in the slice func (c Capabilities) Contains(capp Capability) bool { for _, cc := range c { if cc == capp { diff --git a/pkg/libcontainer/utils/utils.go b/pkg/libcontainer/utils/utils.go index d3223c3e4..5050997ff 100644 --- a/pkg/libcontainer/utils/utils.go +++ b/pkg/libcontainer/utils/utils.go @@ -6,6 +6,8 @@ import ( "io" ) +// GenerateRandomName returns a new name joined with a prefix. This size +// specified is used to truncate the randomly generated value func GenerateRandomName(prefix string, size int) (string, error) { id := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, id); err != nil { From e133d895a6934e650f64f391f9f26b29b0379457 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 19 Feb 2014 22:46:02 -0800 Subject: [PATCH 084/282] Remove privileged.json config Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/privileged.json | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 pkg/libcontainer/privileged.json diff --git a/pkg/libcontainer/privileged.json b/pkg/libcontainer/privileged.json deleted file mode 100644 index be877ad33..000000000 --- a/pkg/libcontainer/privileged.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": "koye", - "namespace_pid": 3745, - "command": { - "args": [ - "/usr/lib/systemd/systemd" - ], - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=" - ] - }, - "rootfs": "/root/main/mycontainer", - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS" - ] -} From f0b4dd6e5883a65dc23121934b6eed7e70ac2515 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 12:00:54 -0800 Subject: [PATCH 085/282] WIP for setup kmsg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/mount.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 6eb2e0906..67f902035 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -43,6 +43,9 @@ func setupNewMountNamespace(rootfs, console string, readonly bool) error { if err := setupPtmx(rootfs, console); err != nil { return err } + if err := setupKmsg(rootfs); err != nil { + return err + } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } @@ -211,3 +214,32 @@ func remountSys() error { } return nil } + +func setupKmsg(rootfs string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + var ( + source = filepath.Join(rootfs, "dev/kmsg") + dest = filepath.Join(rootfs, "proc/kmsg") + ) + + if err := system.Mkfifo(source, 0600); err != nil { + return err + } + + os.Chmod(source, 0600) + os.Chown(source, 0, 0) + + if err := system.Mount(source, dest, "bind", syscall.MS_BIND, ""); err != nil { + return err + } + _, err := os.OpenFile(source, syscall.O_RDWR|syscall.O_NDELAY|syscall.O_CLOEXEC, 0) + if err != nil { + return err + } + if err := syscall.Unlink(source); err != nil { + return err + } + return nil +} From 664fc54e65ebc14ca9dd5bfc55e3dfe1796e51c8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 20 Feb 2014 23:12:08 +0100 Subject: [PATCH 086/282] libcontainer: Initial version of cgroups support This is a minimal version of raw cgroup support for libcontainer. It has only enough for what docker needs, and it has no support for systemd yet. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/cgroups.go | 16 ++- pkg/libcontainer/cgroup/cgroup.go | 177 ++++++++++++++++++++++++++++++ pkg/libcontainer/container.go | 7 ++ pkg/libcontainer/container.json | 5 +- pkg/libcontainer/nsinit/exec.go | 13 ++- pkg/libcontainer/nsinit/init.go | 10 +- 6 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 pkg/libcontainer/cgroup/cgroup.go diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 91ac3842a..b9318f99e 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -40,6 +40,16 @@ func GetThisCgroupDir(subsystem string) (string, error) { return parseCgroupFile(subsystem, f) } +func GetInitCgroupDir(subsystem string) (string, error) { + f, err := os.Open("/proc/1/cgroup") + if err != nil { + return "", err + } + defer f.Close() + + return parseCgroupFile(subsystem, f) +} + func parseCgroupFile(subsystem string, r io.Reader) (string, error) { s := bufio.NewScanner(r) @@ -49,8 +59,10 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) { } text := s.Text() parts := strings.Split(text, ":") - if parts[1] == subsystem { - return parts[2], nil + for _, subs := range strings.Split(parts[1], ",") { + if subs == subsystem { + return parts[2], nil + } } } return "", fmt.Errorf("cgroup '%s' not found in /proc/self/cgroup", subsystem) diff --git a/pkg/libcontainer/cgroup/cgroup.go b/pkg/libcontainer/cgroup/cgroup.go new file mode 100644 index 000000000..e30262ca5 --- /dev/null +++ b/pkg/libcontainer/cgroup/cgroup.go @@ -0,0 +1,177 @@ +package cgroup + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer" + "io/ioutil" + "os" + "path/filepath" + "strconv" +) + +// We have two implementation of cgroups support, one is based on +// systemd and the dbus api, and one is based on raw cgroup fs operations +// following the pre-single-writer model docs at: +// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ +const ( + cgroupRoot = "/sys/fs/cgroup" +) + +func useSystemd() bool { + return false +} + +func applyCgroupSystemd(container *libcontainer.Container, pid int) error { + return fmt.Errorf("not supported yet") +} + +func writeFile(dir, file, data string) error { + return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) +} + +func getCgroup(subsystem string, container *libcontainer.Container) (string, error) { + cgroup := container.CgroupName + if container.CgroupParent != "" { + cgroup = filepath.Join(container.CgroupParent, cgroup) + } + + initPath, err := cgroups.GetInitCgroupDir(subsystem) + if err != nil { + return "", err + } + + path := filepath.Join(cgroupRoot, subsystem, initPath, cgroup) + + return path, nil +} + +func joinCgroup(subsystem string, container *libcontainer.Container, pid int) (string, error) { + path, err := getCgroup(subsystem, container) + if err != nil { + return "", err + } + + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + + if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { + return "", err + } + + return path, nil +} + +func applyCgroupRaw(container *libcontainer.Container, pid int) (retErr error) { + if _, err := os.Stat(cgroupRoot); err != nil { + return fmt.Errorf("cgroups fs not found") + } + + if !container.DeviceAccess { + dir, err := joinCgroup("devices", container, pid) + if err != nil { + return err + } + defer func() { + if retErr != nil { + os.RemoveAll(dir) + } + }() + + if err := writeFile(dir, "devices.deny", "a"); err != nil { + return err + } + + allow := []string{ + // /dev/null, zero, full + "c 1:3 rwm", + "c 1:5 rwm", + "c 1:7 rwm", + + // consoles + "c 5:1 rwm", + "c 5:0 rwm", + "c 4:0 rwm", + "c 4:1 rwm", + + // /dev/urandom,/dev/random + "c 1:9 rwm", + "c 1:8 rwm", + + // /dev/pts/ - pts namespaces are "coming soon" + "c 136:* rwm", + "c 5:2 rwm", + + // tuntap + "c 10:200 rwm", + } + + for _, val := range allow { + if err := writeFile(dir, "devices.allow", val); err != nil { + return err + } + } + } + + if container.Memory != 0 || container.MemorySwap != 0 { + dir, err := joinCgroup("memory", container, pid) + if err != nil { + return err + } + defer func() { + if retErr != nil { + os.RemoveAll(dir) + } + }() + + if container.Memory != 0 { + if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(container.Memory, 10)); err != nil { + return err + } + if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(container.Memory, 10)); err != nil { + return err + } + } + if container.MemorySwap != 0 { + if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(container.MemorySwap, 10)); err != nil { + return err + } + } + } + + // We always want to join the cpu group, to allow fair cpu scheduling + // on a container basis + dir, err := joinCgroup("cpu", container, pid) + if err != nil { + return err + } + if container.CpuShares != 0 { + if err := writeFile(dir, "cpu.shares", strconv.FormatInt(container.CpuShares, 10)); err != nil { + return err + } + } + return nil +} + +func CleanupCgroup(container *libcontainer.Container) error { + path, _ := getCgroup("memory", container) + os.RemoveAll(path) + path, _ = getCgroup("devices", container) + os.RemoveAll(path) + path, _ = getCgroup("cpu", container) + os.RemoveAll(path) + return nil +} + +func ApplyCgroup(container *libcontainer.Container, pid int) error { + if container.CgroupName == "" { + return nil + } + + if useSystemd() { + return applyCgroupSystemd(container, pid) + } else { + return applyCgroupRaw(container, pid) + } +} diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index a6a57dab7..b34ac8b35 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -11,6 +11,13 @@ type Container struct { Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop Network *Network `json:"network,omitempty"` // nil for host's network stack + + CgroupName string `json:"cgroup_name,omitempty"` // name of cgroup + CgroupParent string `json:"cgroup_parent,omitempty"` // name of parent cgroup or slice + DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice + Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) + MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index ccc9abb04..3e2360063 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -34,5 +34,8 @@ "gateway": "172.17.42.1", "bridge": "docker0", "mtu": 1500 - } + }, + "cgroup_name": "docker-koye", + "cgroup_parent": "docker", + "memory": 524800 } diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 202cfcab5..acff647c6 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -5,6 +5,7 @@ package main import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/cgroup" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" @@ -33,10 +34,18 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) return -1, err } if err := writePidFile(command); err != nil { + command.Process.Kill() return -1, err } defer deletePidFile() + // Do this before syncing with child so that no children + // can escape the cgroup + if err := cgroup.ApplyCgroup(container, command.Process.Pid); err != nil { + command.Process.Kill() + return -1, err + } + if container.Network != nil { vethPair, err := initializeContainerVeth(container.Network.Bridge, command.Process.Pid) if err != nil { @@ -45,6 +54,9 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) sendVethName(vethPair, inPipe) } + // Sync with child + inPipe.Close() + go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) @@ -67,7 +79,6 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) // pipe so that the child stops waiting for more data func sendVethName(name string, pipe io.WriteCloser) { fmt.Fprint(pipe, name) - pipe.Close() } // initializeContainerVeth will create a veth pair and setup the host's diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index c77fd9044..f619276e6 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -20,12 +20,10 @@ func initCommand(container *libcontainer.Container, console string, args []strin return err } - var tempVethName string - if container.Network != nil { - tempVethName, err = getVethName() - if err != nil { - return err - } + // We always read this as it is a way to sync with the parent as well + tempVethName, err := getVethName() + if err != nil { + return err } // close pipes so that we can replace it with the pty From 5f84738ef139f696e339afb8280eb74917f2167c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 14:40:00 -0800 Subject: [PATCH 087/282] Revert "WIP for setup kmsg" This reverts commit 80db9a918337c4ae80ffa9a001da13bd24e848c8. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/mount.go | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 67f902035..6eb2e0906 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -43,9 +43,6 @@ func setupNewMountNamespace(rootfs, console string, readonly bool) error { if err := setupPtmx(rootfs, console); err != nil { return err } - if err := setupKmsg(rootfs); err != nil { - return err - } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } @@ -214,32 +211,3 @@ func remountSys() error { } return nil } - -func setupKmsg(rootfs string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - var ( - source = filepath.Join(rootfs, "dev/kmsg") - dest = filepath.Join(rootfs, "proc/kmsg") - ) - - if err := system.Mkfifo(source, 0600); err != nil { - return err - } - - os.Chmod(source, 0600) - os.Chown(source, 0, 0) - - if err := system.Mount(source, dest, "bind", syscall.MS_BIND, ""); err != nil { - return err - } - _, err := os.OpenFile(source, syscall.O_RDWR|syscall.O_NDELAY|syscall.O_CLOEXEC, 0) - if err != nil { - return err - } - if err := syscall.Unlink(source); err != nil { - return err - } - return nil -} From f00f37413826e31e9eb87096b67c609fdfa457b9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 14:40:36 -0800 Subject: [PATCH 088/282] Remove clone_vfork Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index acff647c6..f73ad3281 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -165,7 +165,7 @@ func deletePidFile() error { func createCommand(container *libcontainer.Container, console string, args []string) *exec.Cmd { command := exec.Command("nsinit", append([]string{"init", console}, args...)...) command.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: uintptr(getNamespaceFlags(container.Namespaces) | syscall.CLONE_VFORK), // we need CLONE_VFORK so we can wait on the child + Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), } return command } From c44258630575f70231b11fb55bc4edc3fb677cab Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 15:48:48 -0800 Subject: [PATCH 089/282] Refactory cgroups into general pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/cgroups/cgroups.go | 61 +++++++++++++- pkg/libcontainer/cgroup/cgroup.go | 131 +++++++++++------------------- pkg/libcontainer/container.go | 28 +++---- pkg/libcontainer/container.json | 8 +- 4 files changed, 124 insertions(+), 104 deletions(-) diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index b9318f99e..1e96caa7e 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -5,10 +5,23 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/mount" "io" + "io/ioutil" "os" + "path/filepath" + "strconv" "strings" ) +type Cgroup struct { + Name string `json:"name,omitempty"` + Parent string `json:"parent,omitempty"` + + DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice + Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) + MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap + CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) +} + // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mounts, err := mount.GetMounts() @@ -25,7 +38,6 @@ func FindCgroupMountpoint(subsystem string) (string, error) { } } } - return "", fmt.Errorf("cgroup mountpoint not found for %s", subsystem) } @@ -50,9 +62,50 @@ func GetInitCgroupDir(subsystem string) (string, error) { return parseCgroupFile(subsystem, f) } +func (c *Cgroup) Path(root, subsystem string) (string, error) { + cgroup := c.Name + if c.Parent != "" { + cgroup = filepath.Join(c.Parent, cgroup) + } + initPath, err := GetInitCgroupDir(subsystem) + if err != nil { + return "", err + } + return filepath.Join(root, subsystem, initPath, cgroup), nil +} + +func (c *Cgroup) Join(root, subsystem string, pid int) (string, error) { + path, err := c.Path(root, subsystem) + if err != nil { + return "", err + } + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { + return "", err + } + return path, nil +} + +func (c *Cgroup) Cleanup(root string) error { + get := func(subsystem string) string { + path, _ := c.Path(root, subsystem) + return path + } + + for _, path := range []string{ + get("memory"), + get("devices"), + get("cpu"), + } { + os.RemoveAll(path) + } + return nil +} + func parseCgroupFile(subsystem string, r io.Reader) (string, error) { s := bufio.NewScanner(r) - for s.Scan() { if err := s.Err(); err != nil { return "", err @@ -67,3 +120,7 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) { } return "", fmt.Errorf("cgroup '%s' not found in /proc/self/cgroup", subsystem) } + +func writeFile(dir, file, data string) error { + return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) +} diff --git a/pkg/libcontainer/cgroup/cgroup.go b/pkg/libcontainer/cgroup/cgroup.go index e30262ca5..5f27ac3ff 100644 --- a/pkg/libcontainer/cgroup/cgroup.go +++ b/pkg/libcontainer/cgroup/cgroup.go @@ -10,71 +10,46 @@ import ( "strconv" ) -// We have two implementation of cgroups support, one is based on -// systemd and the dbus api, and one is based on raw cgroup fs operations -// following the pre-single-writer model docs at: -// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ -const ( - cgroupRoot = "/sys/fs/cgroup" -) - -func useSystemd() bool { - return false -} - -func applyCgroupSystemd(container *libcontainer.Container, pid int) error { - return fmt.Errorf("not supported yet") -} - -func writeFile(dir, file, data string) error { - return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) -} - -func getCgroup(subsystem string, container *libcontainer.Container) (string, error) { - cgroup := container.CgroupName - if container.CgroupParent != "" { - cgroup = filepath.Join(container.CgroupParent, cgroup) +func ApplyCgroup(container *libcontainer.Container, pid int) (err error) { + if container.Cgroups == nil { + return nil } - initPath, err := cgroups.GetInitCgroupDir(subsystem) + // We have two implementation of cgroups support, one is based on + // systemd and the dbus api, and one is based on raw cgroup fs operations + // following the pre-single-writer model docs at: + // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ + // + // we can pick any subsystem to find the root + cgroupRoot, err := cgroups.FindCgroupMountpoint("memory") if err != nil { - return "", err + return err } - - path := filepath.Join(cgroupRoot, subsystem, initPath, cgroup) - - return path, nil -} - -func joinCgroup(subsystem string, container *libcontainer.Container, pid int) (string, error) { - path, err := getCgroup(subsystem, container) - if err != nil { - return "", err - } - - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return "", err - } - - if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { - return "", err - } - - return path, nil -} - -func applyCgroupRaw(container *libcontainer.Container, pid int) (retErr error) { + cgroupRoot = filepath.Dir(cgroupRoot) if _, err := os.Stat(cgroupRoot); err != nil { return fmt.Errorf("cgroups fs not found") } + if err := setupDevices(container, cgroupRoot, pid); err != nil { + return err + } + if err := setupMemory(container, cgroupRoot, pid); err != nil { + return err + } + if err := setupCpu(container, cgroupRoot, pid); err != nil { + return err + } + return nil +} - if !container.DeviceAccess { - dir, err := joinCgroup("devices", container, pid) +func setupDevices(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { + if !container.Cgroups.DeviceAccess { + dir, err := container.Cgroups.Join(cgroupRoot, "devices", pid) if err != nil { return err } + defer func() { - if retErr != nil { + if err != nil { os.RemoveAll(dir) } }() @@ -113,65 +88,53 @@ func applyCgroupRaw(container *libcontainer.Container, pid int) (retErr error) { } } } + return nil +} - if container.Memory != 0 || container.MemorySwap != 0 { - dir, err := joinCgroup("memory", container, pid) +func setupMemory(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { + if container.Cgroups.Memory != 0 || container.Cgroups.MemorySwap != 0 { + dir, err := container.Cgroups.Join(cgroupRoot, "memory", pid) if err != nil { return err } defer func() { - if retErr != nil { + if err != nil { os.RemoveAll(dir) } }() - if container.Memory != 0 { - if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(container.Memory, 10)); err != nil { + if container.Cgroups.Memory != 0 { + if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(container.Cgroups.Memory, 10)); err != nil { return err } - if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(container.Memory, 10)); err != nil { + if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(container.Cgroups.Memory, 10)); err != nil { return err } } - if container.MemorySwap != 0 { - if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(container.MemorySwap, 10)); err != nil { + if container.Cgroups.MemorySwap != 0 { + if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(container.Cgroups.MemorySwap, 10)); err != nil { return err } } } + return nil +} +func setupCpu(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { // We always want to join the cpu group, to allow fair cpu scheduling // on a container basis - dir, err := joinCgroup("cpu", container, pid) + dir, err := container.Cgroups.Join(cgroupRoot, "cpu", pid) if err != nil { return err } - if container.CpuShares != 0 { - if err := writeFile(dir, "cpu.shares", strconv.FormatInt(container.CpuShares, 10)); err != nil { + if container.Cgroups.CpuShares != 0 { + if err := writeFile(dir, "cpu.shares", strconv.FormatInt(container.Cgroups.CpuShares, 10)); err != nil { return err } } return nil } -func CleanupCgroup(container *libcontainer.Container) error { - path, _ := getCgroup("memory", container) - os.RemoveAll(path) - path, _ = getCgroup("devices", container) - os.RemoveAll(path) - path, _ = getCgroup("cpu", container) - os.RemoveAll(path) - return nil -} - -func ApplyCgroup(container *libcontainer.Container, pid int) error { - if container.CgroupName == "" { - return nil - } - - if useSystemd() { - return applyCgroupSystemd(container, pid) - } else { - return applyCgroupRaw(container, pid) - } +func writeFile(dir, file, data string) error { + return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index b34ac8b35..4c0e39a79 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -1,23 +1,21 @@ package libcontainer +import ( + "github.com/dotcloud/docker/pkg/cgroups" +) + // Container defines configuration options for how a // container is setup inside a directory and how a process should be executed type Container struct { - Hostname string `json:"hostname,omitempty"` // hostname - ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly - User string `json:"user,omitempty"` // user to execute the process as - WorkingDir string `json:"working_dir,omitempty"` // current working directory - Env []string `json:"environment,omitempty"` // environment to set - Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply - Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop - Network *Network `json:"network,omitempty"` // nil for host's network stack - - CgroupName string `json:"cgroup_name,omitempty"` // name of cgroup - CgroupParent string `json:"cgroup_parent,omitempty"` // name of parent cgroup or slice - DeviceAccess bool `json:"device_access,omitempty"` // name of parent cgroup or slice - Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) - MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap - CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) + Hostname string `json:"hostname,omitempty"` // hostname + ReadonlyFs bool `json:"readonly_fs,omitempty"` // set the containers rootfs as readonly + User string `json:"user,omitempty"` // user to execute the process as + WorkingDir string `json:"working_dir,omitempty"` // current working directory + Env []string `json:"environment,omitempty"` // environment to set + Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply + Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop + Network *Network `json:"network,omitempty"` // nil for host's network stack + Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` } // Network defines configuration for a container's networking stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 3e2360063..2207543bd 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -35,7 +35,9 @@ "bridge": "docker0", "mtu": 1500 }, - "cgroup_name": "docker-koye", - "cgroup_parent": "docker", - "memory": 524800 + "cgroups": { + "name": "docker-koye", + "parent": "docker", + "memory": 524800 + } } From 3cb698125da7b55a7d7ec43b33858f35844a6143 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 15:50:55 -0800 Subject: [PATCH 090/282] Change IP to address because it includes the subnet Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 7 ++++++- pkg/libcontainer/container.go | 2 +- pkg/libcontainer/container.json | 2 +- pkg/libcontainer/nsinit/init.go | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 07fe4f7b2..163161c17 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -47,10 +47,15 @@ Sample `container.json` file: "MAC_ADMIN" ], "network": { - "ip": "172.17.0.100/16", + "address": "172.17.0.100/16", "gateway": "172.17.42.1", "bridge": "docker0", "mtu": 1500 + }, + "cgroups": { + "name": "docker-koye", + "parent": "docker", + "memory": 524800 } } ``` diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 4c0e39a79..e6e4b4747 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -23,7 +23,7 @@ type Container struct { // The network configuration can be omited from a container causing the // container to be setup with the host's networking stack type Network struct { - IP string `json:"ip,omitempty"` + Address string `json:"address,omitempty"` Gateway string `json:"gateway,omitempty"` Bridge string `json:"bridge,omitempty"` Mtu int `json:"mtu,omitempty"` diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index 2207543bd..c1a07dc55 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -30,7 +30,7 @@ "MAC_ADMIN" ], "network": { - "ip": "172.17.0.100/16", + "address": "172.17.0.100/16", "gateway": "172.17.42.1", "bridge": "docker0", "mtu": 1500 diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index f619276e6..f89e53982 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -137,7 +137,7 @@ func setupVethNetwork(config *libcontainer.Network, tempVethName string) error { if err := network.ChangeInterfaceName(tempVethName, "eth0"); err != nil { return fmt.Errorf("change %s to eth0 %s", tempVethName, err) } - if err := network.SetInterfaceIp("eth0", config.IP); err != nil { + if err := network.SetInterfaceIp("eth0", config.Address); err != nil { return fmt.Errorf("set eth0 ip %s", err) } if err := network.SetMtu("eth0", config.Mtu); err != nil { From 7020e208c70dfca5ebc97d699553e4bf1c6ab0bb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 16:11:22 -0800 Subject: [PATCH 091/282] Move rest of cgroups functions into cgroups pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/cgroups/cgroups.go | 122 ++++++++++++++++++++++++++ pkg/libcontainer/cgroup/cgroup.go | 140 ------------------------------ pkg/libcontainer/nsinit/exec.go | 9 +- 3 files changed, 127 insertions(+), 144 deletions(-) delete mode 100644 pkg/libcontainer/cgroup/cgroup.go diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 1e96caa7e..96002f0af 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -124,3 +124,125 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) { func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } + +func (c *Cgroup) Apply(pid int) error { + // We have two implementation of cgroups support, one is based on + // systemd and the dbus api, and one is based on raw cgroup fs operations + // following the pre-single-writer model docs at: + // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ + // + // we can pick any subsystem to find the root + cgroupRoot, err := FindCgroupMountpoint("memory") + if err != nil { + return err + } + cgroupRoot = filepath.Dir(cgroupRoot) + + if _, err := os.Stat(cgroupRoot); err != nil { + return fmt.Errorf("cgroups fs not found") + } + if err := c.setupDevices(cgroupRoot, pid); err != nil { + return err + } + if err := c.setupMemory(cgroupRoot, pid); err != nil { + return err + } + if err := c.setupCpu(cgroupRoot, pid); err != nil { + return err + } + return nil +} + +func (c *Cgroup) setupDevices(cgroupRoot string, pid int) (err error) { + if !c.DeviceAccess { + dir, err := c.Join(cgroupRoot, "devices", pid) + if err != nil { + return err + } + + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if err := writeFile(dir, "devices.deny", "a"); err != nil { + return err + } + + allow := []string{ + // /dev/null, zero, full + "c 1:3 rwm", + "c 1:5 rwm", + "c 1:7 rwm", + + // consoles + "c 5:1 rwm", + "c 5:0 rwm", + "c 4:0 rwm", + "c 4:1 rwm", + + // /dev/urandom,/dev/random + "c 1:9 rwm", + "c 1:8 rwm", + + // /dev/pts/ - pts namespaces are "coming soon" + "c 136:* rwm", + "c 5:2 rwm", + + // tuntap + "c 10:200 rwm", + } + + for _, val := range allow { + if err := writeFile(dir, "devices.allow", val); err != nil { + return err + } + } + } + return nil +} + +func (c *Cgroup) setupMemory(cgroupRoot string, pid int) (err error) { + if c.Memory != 0 || c.MemorySwap != 0 { + dir, err := c.Join(cgroupRoot, "memory", pid) + if err != nil { + return err + } + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if c.Memory != 0 { + if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { + return err + } + if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { + return err + } + } + if c.MemorySwap != 0 { + if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.MemorySwap, 10)); err != nil { + return err + } + } + } + return nil +} + +func (c *Cgroup) setupCpu(cgroupRoot string, pid int) (err error) { + // We always want to join the cpu group, to allow fair cpu scheduling + // on a container basis + dir, err := c.Join(cgroupRoot, "cpu", pid) + if err != nil { + return err + } + if c.CpuShares != 0 { + if err := writeFile(dir, "cpu.shares", strconv.FormatInt(c.CpuShares, 10)); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/cgroup/cgroup.go b/pkg/libcontainer/cgroup/cgroup.go deleted file mode 100644 index 5f27ac3ff..000000000 --- a/pkg/libcontainer/cgroup/cgroup.go +++ /dev/null @@ -1,140 +0,0 @@ -package cgroup - -import ( - "fmt" - "github.com/dotcloud/docker/pkg/cgroups" - "github.com/dotcloud/docker/pkg/libcontainer" - "io/ioutil" - "os" - "path/filepath" - "strconv" -) - -func ApplyCgroup(container *libcontainer.Container, pid int) (err error) { - if container.Cgroups == nil { - return nil - } - - // We have two implementation of cgroups support, one is based on - // systemd and the dbus api, and one is based on raw cgroup fs operations - // following the pre-single-writer model docs at: - // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ - // - // we can pick any subsystem to find the root - cgroupRoot, err := cgroups.FindCgroupMountpoint("memory") - if err != nil { - return err - } - cgroupRoot = filepath.Dir(cgroupRoot) - if _, err := os.Stat(cgroupRoot); err != nil { - return fmt.Errorf("cgroups fs not found") - } - if err := setupDevices(container, cgroupRoot, pid); err != nil { - return err - } - if err := setupMemory(container, cgroupRoot, pid); err != nil { - return err - } - if err := setupCpu(container, cgroupRoot, pid); err != nil { - return err - } - return nil -} - -func setupDevices(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { - if !container.Cgroups.DeviceAccess { - dir, err := container.Cgroups.Join(cgroupRoot, "devices", pid) - if err != nil { - return err - } - - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() - - if err := writeFile(dir, "devices.deny", "a"); err != nil { - return err - } - - allow := []string{ - // /dev/null, zero, full - "c 1:3 rwm", - "c 1:5 rwm", - "c 1:7 rwm", - - // consoles - "c 5:1 rwm", - "c 5:0 rwm", - "c 4:0 rwm", - "c 4:1 rwm", - - // /dev/urandom,/dev/random - "c 1:9 rwm", - "c 1:8 rwm", - - // /dev/pts/ - pts namespaces are "coming soon" - "c 136:* rwm", - "c 5:2 rwm", - - // tuntap - "c 10:200 rwm", - } - - for _, val := range allow { - if err := writeFile(dir, "devices.allow", val); err != nil { - return err - } - } - } - return nil -} - -func setupMemory(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { - if container.Cgroups.Memory != 0 || container.Cgroups.MemorySwap != 0 { - dir, err := container.Cgroups.Join(cgroupRoot, "memory", pid) - if err != nil { - return err - } - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() - - if container.Cgroups.Memory != 0 { - if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(container.Cgroups.Memory, 10)); err != nil { - return err - } - if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(container.Cgroups.Memory, 10)); err != nil { - return err - } - } - if container.Cgroups.MemorySwap != 0 { - if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(container.Cgroups.MemorySwap, 10)); err != nil { - return err - } - } - } - return nil -} - -func setupCpu(container *libcontainer.Container, cgroupRoot string, pid int) (err error) { - // We always want to join the cpu group, to allow fair cpu scheduling - // on a container basis - dir, err := container.Cgroups.Join(cgroupRoot, "cpu", pid) - if err != nil { - return err - } - if container.Cgroups.CpuShares != 0 { - if err := writeFile(dir, "cpu.shares", strconv.FormatInt(container.Cgroups.CpuShares, 10)); err != nil { - return err - } - } - return nil -} - -func writeFile(dir, file, data string) error { - return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) -} diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index f73ad3281..f04e9bee2 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -5,7 +5,6 @@ package main import ( "fmt" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/cgroup" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" @@ -41,9 +40,11 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) // Do this before syncing with child so that no children // can escape the cgroup - if err := cgroup.ApplyCgroup(container, command.Process.Pid); err != nil { - command.Process.Kill() - return -1, err + if container.Cgroups != nil { + if err := container.Cgroups.Apply(command.Process.Pid); err != nil { + command.Process.Kill() + return -1, err + } } if container.Network != nil { From b519d3ea5a50ad7c15d576a89ec9846c4fc123fa Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Feb 2014 17:53:50 -0800 Subject: [PATCH 092/282] Use flag for init Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 2 +- pkg/libcontainer/nsinit/main.go | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index f04e9bee2..6d87f3b66 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -164,7 +164,7 @@ func deletePidFile() error { // defined on the container's configuration and use the current binary as the init with the // args provided func createCommand(container *libcontainer.Container, console string, args []string) *exec.Cmd { - command := exec.Command("nsinit", append([]string{"init", console}, args...)...) + command := exec.Command("nsinit", append([]string{"-console", console, "init"}, args...)...) command.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), } diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index f45fe5568..e7240df04 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "errors" + "flag" "github.com/dotcloud/docker/pkg/libcontainer" "io/ioutil" "log" @@ -16,16 +17,18 @@ var ( ) func main() { + console := flag.String("console", "", "Console (pty slave) name") + flag.Parse() + container, err := loadContainer() if err != nil { log.Fatal(err) } - argc := len(os.Args) - if argc < 2 { + if flag.NArg() < 1 { log.Fatal(ErrWrongArguments) } - switch os.Args[1] { + switch flag.Arg(0) { case "exec": // this is executed outside of the namespace in the cwd var exitCode int nspid, err := readPid() @@ -35,23 +38,23 @@ func main() { } } if nspid > 0 { - exitCode, err = execinCommand(container, nspid, os.Args[2:]) + exitCode, err = execinCommand(container, nspid, flag.Args()[1:]) } else { - exitCode, err = execCommand(container, os.Args[2:]) + exitCode, err = execCommand(container, flag.Args()[1:]) } if err != nil { log.Fatal(err) } os.Exit(exitCode) case "init": // this is executed inside of the namespace to setup the container - if argc < 3 { + if flag.NArg() < 2 { log.Fatal(ErrWrongArguments) } - if err := initCommand(container, os.Args[2], os.Args[3:]); err != nil { + if err := initCommand(container, *console, flag.Args()[1:]); err != nil { log.Fatal(err) } default: - log.Fatalf("command not supported for nsinit %s", os.Args[1]) + log.Fatalf("command not supported for nsinit %s", flag.Arg(0)) } } From 8dec4adcb3fd905eb05f07678fa7f5bb47d8242f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Feb 2014 17:58:13 -0800 Subject: [PATCH 093/282] Use a custom pipe instead of stdin for sync net namespace Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 16 ++++++++++------ pkg/libcontainer/nsinit/init.go | 11 +++++++---- pkg/libcontainer/nsinit/main.go | 3 ++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 6d87f3b66..8007ed469 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -22,16 +22,20 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) return -1, err } - command := createCommand(container, console, args) // create a pipe so that we can syncronize with the namespaced process and // pass the veth name to the child - inPipe, err := command.StdinPipe() + r, w, err := os.Pipe() if err != nil { return -1, err } + system.UsetCloseOnExec(r.Fd()) + + command := createCommand(container, console, r.Fd(), args) + if err := command.Start(); err != nil { return -1, err } + if err := writePidFile(command); err != nil { command.Process.Kill() return -1, err @@ -52,11 +56,11 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) if err != nil { return -1, err } - sendVethName(vethPair, inPipe) + sendVethName(vethPair, w) } // Sync with child - inPipe.Close() + w.Close() go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) @@ -163,8 +167,8 @@ func deletePidFile() error { // createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces // defined on the container's configuration and use the current binary as the init with the // args provided -func createCommand(container *libcontainer.Container, console string, args []string) *exec.Cmd { - command := exec.Command("nsinit", append([]string{"-console", console, "init"}, args...)...) +func createCommand(container *libcontainer.Container, console string, pipe uintptr, args []string) *exec.Cmd { + command := exec.Command("nsinit", append([]string{"-console", console, "-pipe", fmt.Sprint(pipe), "init"}, args...)...) command.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index f89e53982..a0815eef1 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -8,20 +8,21 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" + "io" "io/ioutil" "os" "path/filepath" "syscall" ) -func initCommand(container *libcontainer.Container, console string, args []string) error { +func initCommand(container *libcontainer.Container, console string, pipe io.ReadCloser, args []string) error { rootfs, err := resolveRootfs() if err != nil { return err } // We always read this as it is a way to sync with the parent as well - tempVethName, err := getVethName() + tempVethName, err := getVethName(pipe) if err != nil { return err } @@ -164,8 +165,10 @@ func setupVethNetwork(config *libcontainer.Network, tempVethName string) error { // getVethName reads from Stdin the temp veth name // sent by the parent processes after the veth pair // has been created and setup -func getVethName() (string, error) { - data, err := ioutil.ReadAll(os.Stdin) +func getVethName(pipe io.ReadCloser) (string, error) { + defer pipe.Close() + + data, err := ioutil.ReadAll(pipe) if err != nil { return "", fmt.Errorf("error reading from stdin %s", err) } diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index e7240df04..6f2825b25 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -18,6 +18,7 @@ var ( func main() { console := flag.String("console", "", "Console (pty slave) name") + pipeFd := flag.Int("pipe", 0, "sync pipe fd") flag.Parse() container, err := loadContainer() @@ -50,7 +51,7 @@ func main() { if flag.NArg() < 2 { log.Fatal(ErrWrongArguments) } - if err := initCommand(container, *console, flag.Args()[1:]); err != nil { + if err := initCommand(container, *console, os.NewFile(uintptr(*pipeFd), "pipe"), flag.Args()[1:]); err != nil { log.Fatal(err) } default: From 83dfdd1d9587a7335bbf3a4656572baefae4f28d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Feb 2014 17:59:08 -0800 Subject: [PATCH 094/282] Minor cleanup Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 8007ed469..7f552c296 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -56,11 +56,12 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) if err != nil { return -1, err } - sendVethName(vethPair, w) + sendVethName(w, vethPair) } // Sync with child w.Close() + r.Close() go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) @@ -82,7 +83,7 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) // sendVethName writes the veth pair name to the child's stdin then closes the // pipe so that the child stops waiting for more data -func sendVethName(name string, pipe io.WriteCloser) { +func sendVethName(pipe io.Writer, name string) { fmt.Fprint(pipe, name) } From 1a4fb0921919720ab379bc82b7508580057770ee Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Feb 2014 18:05:40 -0800 Subject: [PATCH 095/282] Handle non-tty mode Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 55 +++++++++++++++++++++++++------- pkg/libcontainer/nsinit/init.go | 30 +++++++++-------- pkg/libcontainer/nsinit/main.go | 9 ++++-- pkg/libcontainer/nsinit/mount.go | 6 ++-- 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 7f552c296..b290ace3c 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -16,10 +16,21 @@ import ( "syscall" ) -func execCommand(container *libcontainer.Container, args []string) (int, error) { - master, console, err := createMasterAndConsole() - if err != nil { - return -1, err +func execCommand(container *libcontainer.Container, tty bool, args []string) (int, error) { + var ( + master *os.File + console string + err error + + inPipe io.WriteCloser + outPipe, errPipe io.ReadCloser + ) + + if tty { + master, console, err = createMasterAndConsole() + if err != nil { + return -1, err + } } // create a pipe so that we can syncronize with the namespaced process and @@ -32,6 +43,21 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) command := createCommand(container, console, r.Fd(), args) + if !tty { + inPipe, err = command.StdinPipe() + if err != nil { + return -1, err + } + outPipe, err = command.StdoutPipe() + if err != nil { + return -1, err + } + errPipe, err = command.StderrPipe() + if err != nil { + return -1, err + } + } + if err := command.Start(); err != nil { return -1, err } @@ -63,15 +89,20 @@ func execCommand(container *libcontainer.Container, args []string) (int, error) w.Close() r.Close() - go io.Copy(os.Stdout, master) - go io.Copy(master, os.Stdin) - - state, err := setupWindow(master) - if err != nil { - command.Process.Kill() - return -1, err + if tty { + go io.Copy(os.Stdout, master) + go io.Copy(master, os.Stdin) + state, err := setupWindow(master) + if err != nil { + command.Process.Kill() + return -1, err + } + defer term.RestoreTerminal(os.Stdin.Fd(), state) + } else { + go io.Copy(inPipe, os.Stdin) + go io.Copy(os.Stdout, outPipe) + go io.Copy(os.Stderr, errPipe) } - defer term.RestoreTerminal(os.Stdin.Fd(), state) if err := command.Wait(); err != nil { if _, ok := err.(*exec.ExitError); !ok { diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index a0815eef1..ef7fc4e44 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -27,23 +27,27 @@ func initCommand(container *libcontainer.Container, console string, pipe io.Read return err } - // close pipes so that we can replace it with the pty - os.Stdin.Close() - os.Stdout.Close() - os.Stderr.Close() + if console != "" { + // close pipes so that we can replace it with the pty + os.Stdin.Close() + os.Stdout.Close() + os.Stderr.Close() + slave, err := openTerminal(console, syscall.O_RDWR) + if err != nil { + return fmt.Errorf("open terminal %s", err) + } + if err := dupSlave(slave); err != nil { + return fmt.Errorf("dup2 slave %s", err) + } + } - slave, err := openTerminal(console, syscall.O_RDWR) - if err != nil { - return fmt.Errorf("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - return fmt.Errorf("dup2 slave %s", err) - } if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if err := system.Setctty(); err != nil { - return fmt.Errorf("setctty %s", err) + if console != "" { + if err := system.Setctty(); err != nil { + return fmt.Errorf("setctty %s", err) + } } if err := system.ParentDeathSignal(); err != nil { return fmt.Errorf("parent deth signal %s", err) diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/main.go index 6f2825b25..f66ff0d85 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/main.go @@ -17,8 +17,11 @@ var ( ) func main() { - console := flag.String("console", "", "Console (pty slave) name") - pipeFd := flag.Int("pipe", 0, "sync pipe fd") + var ( + console = flag.String("console", "", "Console (pty slave) name") + tty = flag.Bool("tty", false, "Create a tty") + pipeFd = flag.Int("pipe", 0, "sync pipe fd") + ) flag.Parse() container, err := loadContainer() @@ -41,7 +44,7 @@ func main() { if nspid > 0 { exitCode, err = execinCommand(container, nspid, flag.Args()[1:]) } else { - exitCode, err = execCommand(container, flag.Args()[1:]) + exitCode, err = execCommand(container, *tty, flag.Args()[1:]) } if err != nil { log.Fatal(err) diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 6eb2e0906..9cf69f418 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -40,8 +40,10 @@ func setupNewMountNamespace(rootfs, console string, readonly bool) error { if err := setupDev(rootfs); err != nil { return err } - if err := setupPtmx(rootfs, console); err != nil { - return err + if console != "" { + if err := setupPtmx(rootfs, console); err != nil { + return err + } } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) From 66baa0653b636180b8b5c57c58f4bbc805aca8c5 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 20 Feb 2014 18:10:30 -0800 Subject: [PATCH 096/282] Make sure to close the pipe upon ctrl-d Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/libcontainer/nsinit/exec.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index b290ace3c..44d9aff5f 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -99,7 +99,10 @@ func execCommand(container *libcontainer.Container, tty bool, args []string) (in } defer term.RestoreTerminal(os.Stdin.Fd(), state) } else { - go io.Copy(inPipe, os.Stdin) + go func() { + defer inPipe.Close() + io.Copy(inPipe, os.Stdin) + }() go io.Copy(os.Stdout, outPipe) go io.Copy(os.Stderr, errPipe) } @@ -109,6 +112,7 @@ func execCommand(container *libcontainer.Container, tty bool, args []string) (in return -1, err } } + return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } From 1316007e54e0c5a25f0d67675df7dec40286f5e8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 18:27:42 -0800 Subject: [PATCH 097/282] Make nsinit a proper go pkg and add the main in another dir Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 4 +++- pkg/libcontainer/nsinit/exec.go | 6 ++++-- pkg/libcontainer/nsinit/execin.go | 5 +++-- pkg/libcontainer/nsinit/init.go | 6 ++++-- pkg/libcontainer/nsinit/mount.go | 2 +- pkg/libcontainer/nsinit/ns_linux.go | 2 +- pkg/libcontainer/nsinit/{ => nsinit}/main.go | 7 ++++--- 7 files changed, 20 insertions(+), 12 deletions(-) rename pkg/libcontainer/nsinit/{ => nsinit}/main.go (87%) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 163161c17..3a2a843b6 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -72,9 +72,11 @@ rootfs and copy a `container.json` file into the directory with your specified c To execution `/bin/bash` in the current directory as a container just run: ```bash -nsinit exec /bin/bash +nsinit -tty exec /bin/bash ``` +If you want a proper tty setup inside the new container you must use the `-tty` flag when running nsinit. + If you wish to spawn another process inside the container while your current bash session is running just run the exact same command again to get another bash shell or change the command. If the original process dies, PID 1, all other processes spawned inside the container will also be killed and the namespace will be removed. diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 44d9aff5f..9d0f7fff4 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -1,6 +1,6 @@ // +build linux -package main +package nsinit import ( "fmt" @@ -16,7 +16,9 @@ import ( "syscall" ) -func execCommand(container *libcontainer.Container, tty bool, args []string) (int, error) { +// Exec performes setup outside of a namespace so that a container can be +// executed. Exec is a high level function for working with container namespaces. +func Exec(container *libcontainer.Container, tty bool, args []string) (int, error) { var ( master *os.File console string diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index d6224f95e..85a89905c 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -1,4 +1,4 @@ -package main +package nsinit import ( "fmt" @@ -11,7 +11,8 @@ import ( "syscall" ) -func execinCommand(container *libcontainer.Container, nspid int, args []string) (int, error) { +// ExecIn uses an existing pid and joins the pid's namespaces with the new command. +func ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error) { for _, ns := range container.Namespaces { if err := system.Unshare(namespaceMap[ns]); err != nil { return -1, err diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index ef7fc4e44..f80d785bc 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -1,6 +1,6 @@ // +build linux -package main +package nsinit import ( "fmt" @@ -15,7 +15,9 @@ import ( "syscall" ) -func initCommand(container *libcontainer.Container, console string, pipe io.ReadCloser, args []string) error { +// Init is the init process that first runs inside a new namespace to setup mounts, users, networking, +// and other options required for the new container. +func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, args []string) error { rootfs, err := resolveRootfs() if err != nil { return err diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 9cf69f418..a73e97e37 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -1,6 +1,6 @@ // +build linux -package main +package nsinit import ( "fmt" diff --git a/pkg/libcontainer/nsinit/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go index 481bdf79d..e42d4b88d 100644 --- a/pkg/libcontainer/nsinit/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -1,4 +1,4 @@ -package main +package nsinit import ( "github.com/dotcloud/docker/pkg/libcontainer" diff --git a/pkg/libcontainer/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go similarity index 87% rename from pkg/libcontainer/nsinit/main.go rename to pkg/libcontainer/nsinit/nsinit/main.go index f66ff0d85..9d3c201aa 100644 --- a/pkg/libcontainer/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/nsinit" "io/ioutil" "log" "os" @@ -42,9 +43,9 @@ func main() { } } if nspid > 0 { - exitCode, err = execinCommand(container, nspid, flag.Args()[1:]) + exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { - exitCode, err = execCommand(container, *tty, flag.Args()[1:]) + exitCode, err = nsinit.Exec(container, *tty, flag.Args()[1:]) } if err != nil { log.Fatal(err) @@ -54,7 +55,7 @@ func main() { if flag.NArg() < 2 { log.Fatal(ErrWrongArguments) } - if err := initCommand(container, *console, os.NewFile(uintptr(*pipeFd), "pipe"), flag.Args()[1:]); err != nil { + if err := nsinit.Init(container, *console, os.NewFile(uintptr(*pipeFd), "pipe"), flag.Args()[1:]); err != nil { log.Fatal(err) } default: From 6b2e963ce0aef802e60eafe0e895f24abb294a07 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 20 Feb 2014 18:38:28 -0800 Subject: [PATCH 098/282] Refactor the flag management for main Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 13 ++++--------- pkg/libcontainer/nsinit/init.go | 1 - pkg/libcontainer/nsinit/nsinit/main.go | 27 ++++++++++++++++---------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 9d0f7fff4..d2b87b66b 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -44,18 +44,14 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro system.UsetCloseOnExec(r.Fd()) command := createCommand(container, console, r.Fd(), args) - if !tty { - inPipe, err = command.StdinPipe() - if err != nil { + if inPipe, err = command.StdinPipe(); err != nil { return -1, err } - outPipe, err = command.StdoutPipe() - if err != nil { + if outPipe, err = command.StdoutPipe(); err != nil { return -1, err } - errPipe, err = command.StderrPipe() - if err != nil { + if errPipe, err = command.StderrPipe(); err != nil { return -1, err } } @@ -63,7 +59,6 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro if err := command.Start(); err != nil { return -1, err } - if err := writePidFile(command); err != nil { command.Process.Kill() return -1, err @@ -94,6 +89,7 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro if tty { go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) + state, err := setupWindow(master) if err != nil { command.Process.Kill() @@ -114,7 +110,6 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro return -1, err } } - return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index f80d785bc..88a5c3c5d 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -173,7 +173,6 @@ func setupVethNetwork(config *libcontainer.Network, tempVethName string) error { // has been created and setup func getVethName(pipe io.ReadCloser) (string, error) { defer pipe.Close() - data, err := ioutil.ReadAll(pipe) if err != nil { return "", fmt.Errorf("error reading from stdin %s", err) diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 9d3c201aa..33a774759 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -12,27 +12,34 @@ import ( "strconv" ) +var ( + console string + tty bool + pipeFd int +) + var ( ErrUnsupported = errors.New("Unsupported method") ErrWrongArguments = errors.New("Wrong argument count") ) -func main() { - var ( - console = flag.String("console", "", "Console (pty slave) name") - tty = flag.Bool("tty", false, "Create a tty") - pipeFd = flag.Int("pipe", 0, "sync pipe fd") - ) - flag.Parse() +func init() { + flag.StringVar(&console, "console", "", "console (pty slave) path") + flag.BoolVar(&tty, "tty", false, "create a tty") + flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd") + flag.Parse() +} + +func main() { container, err := loadContainer() if err != nil { log.Fatal(err) } - if flag.NArg() < 1 { log.Fatal(ErrWrongArguments) } + switch flag.Arg(0) { case "exec": // this is executed outside of the namespace in the cwd var exitCode int @@ -45,7 +52,7 @@ func main() { if nspid > 0 { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { - exitCode, err = nsinit.Exec(container, *tty, flag.Args()[1:]) + exitCode, err = nsinit.Exec(container, tty, flag.Args()[1:]) } if err != nil { log.Fatal(err) @@ -55,7 +62,7 @@ func main() { if flag.NArg() < 2 { log.Fatal(ErrWrongArguments) } - if err := nsinit.Init(container, *console, os.NewFile(uintptr(*pipeFd), "pipe"), flag.Args()[1:]); err != nil { + if err := nsinit.Init(container, console, os.NewFile(uintptr(pipeFd), "pipe"), flag.Args()[1:]); err != nil { log.Fatal(err) } default: From 7f247e7006761ac8922a58651a76b194a4655ffa Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 13:53:11 -0800 Subject: [PATCH 099/282] Move tty into container.json Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 7 +++---- pkg/libcontainer/container.go | 1 + pkg/libcontainer/container.json | 3 ++- pkg/libcontainer/nsinit/exec.go | 8 ++++---- pkg/libcontainer/nsinit/nsinit/main.go | 4 +--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index 3a2a843b6..89a4ec0c4 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -17,6 +17,7 @@ Sample `container.json` file: ```json { "hostname": "koye", + "tty": true, "environment": [ "HOME=/", "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", @@ -55,7 +56,7 @@ Sample `container.json` file: "cgroups": { "name": "docker-koye", "parent": "docker", - "memory": 524800 + "memory": 5248000 } } ``` @@ -72,11 +73,9 @@ rootfs and copy a `container.json` file into the directory with your specified c To execution `/bin/bash` in the current directory as a container just run: ```bash -nsinit -tty exec /bin/bash +nsinit exec /bin/bash ``` -If you want a proper tty setup inside the new container you must use the `-tty` flag when running nsinit. - If you wish to spawn another process inside the container while your current bash session is running just run the exact same command again to get another bash shell or change the command. If the original process dies, PID 1, all other processes spawned inside the container will also be killed and the namespace will be removed. diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index e6e4b4747..3c1b62b65 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -12,6 +12,7 @@ type Container struct { User string `json:"user,omitempty"` // user to execute the process as WorkingDir string `json:"working_dir,omitempty"` // current working directory Env []string `json:"environment,omitempty"` // environment to set + Tty bool `json:"tty,omitempty"` // setup a proper tty or not Namespaces Namespaces `json:"namespaces,omitempty"` // namespaces to apply Capabilities Capabilities `json:"capabilities,omitempty"` // capabilities to drop Network *Network `json:"network,omitempty"` // nil for host's network stack diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index c1a07dc55..07e52df42 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,5 +1,6 @@ { "hostname": "koye", + "tty": true, "environment": [ "HOME=/", "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", @@ -38,6 +39,6 @@ "cgroups": { "name": "docker-koye", "parent": "docker", - "memory": 524800 + "memory": 5248000 } } diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index d2b87b66b..e2adf3d4f 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -18,7 +18,7 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Container, tty bool, args []string) (int, error) { +func Exec(container *libcontainer.Container, args []string) (int, error) { var ( master *os.File console string @@ -28,7 +28,7 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro outPipe, errPipe io.ReadCloser ) - if tty { + if container.Tty { master, console, err = createMasterAndConsole() if err != nil { return -1, err @@ -44,7 +44,7 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro system.UsetCloseOnExec(r.Fd()) command := createCommand(container, console, r.Fd(), args) - if !tty { + if !container.Tty { if inPipe, err = command.StdinPipe(); err != nil { return -1, err } @@ -86,7 +86,7 @@ func Exec(container *libcontainer.Container, tty bool, args []string) (int, erro w.Close() r.Close() - if tty { + if container.Tty { go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 33a774759..6508a3e9d 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -14,7 +14,6 @@ import ( var ( console string - tty bool pipeFd int ) @@ -25,7 +24,6 @@ var ( func init() { flag.StringVar(&console, "console", "", "console (pty slave) path") - flag.BoolVar(&tty, "tty", false, "create a tty") flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd") flag.Parse() @@ -52,7 +50,7 @@ func main() { if nspid > 0 { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { - exitCode, err = nsinit.Exec(container, tty, flag.Args()[1:]) + exitCode, err = nsinit.Exec(container, flag.Args()[1:]) } if err != nil { log.Fatal(err) From 50c752fcb06497e9e597049a1007c53d77032d17 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 14:49:55 -0800 Subject: [PATCH 100/282] Add good logging support to both sides Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 35 +++++++++++++++++++---- pkg/libcontainer/nsinit/init.go | 23 ++++++++------- pkg/libcontainer/nsinit/nsinit/main.go | 39 ++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 21 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index e2adf3d4f..24e722a22 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/pkg/term" "io" "io/ioutil" + "log" "os" "os/exec" "syscall" @@ -18,7 +19,7 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Container, args []string) (int, error) { +func Exec(container *libcontainer.Container, logFile string, args []string) (int, error) { var ( master *os.File console string @@ -29,6 +30,7 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { ) if container.Tty { + log.Printf("setting up master and console") master, console, err = createMasterAndConsole() if err != nil { return -1, err @@ -43,8 +45,9 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { } system.UsetCloseOnExec(r.Fd()) - command := createCommand(container, console, r.Fd(), args) + command := createCommand(container, console, logFile, r.Fd(), args) if !container.Tty { + log.Printf("opening pipes on command") if inPipe, err = command.StdinPipe(); err != nil { return -1, err } @@ -56,9 +59,11 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { } } + log.Printf("staring init") if err := command.Start(); err != nil { return -1, err } + log.Printf("writting state file") if err := writePidFile(command); err != nil { command.Process.Kill() return -1, err @@ -68,6 +73,7 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { // Do this before syncing with child so that no children // can escape the cgroup if container.Cgroups != nil { + log.Printf("setting up cgroups") if err := container.Cgroups.Apply(command.Process.Pid); err != nil { command.Process.Kill() return -1, err @@ -75,18 +81,22 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { } if container.Network != nil { - vethPair, err := initializeContainerVeth(container.Network.Bridge, command.Process.Pid) + log.Printf("creating veth pair") + vethPair, err := initializeContainerVeth(container.Network.Bridge, container.Network.Mtu, command.Process.Pid) if err != nil { return -1, err } + log.Printf("sending %s as veth pair name", vethPair) sendVethName(w, vethPair) } // Sync with child + log.Printf("closing sync pipes") w.Close() r.Close() if container.Tty { + log.Printf("starting copy for tty") go io.Copy(os.Stdout, master) go io.Copy(master, os.Stdin) @@ -97,6 +107,7 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { } defer term.RestoreTerminal(os.Stdin.Fd(), state) } else { + log.Printf("starting copy for std pipes") go func() { defer inPipe.Close() io.Copy(inPipe, os.Stdin) @@ -105,11 +116,13 @@ func Exec(container *libcontainer.Container, args []string) (int, error) { go io.Copy(os.Stderr, errPipe) } + log.Printf("waiting on process") if err := command.Wait(); err != nil { if _, ok := err.(*exec.ExitError); !ok { return -1, err } } + log.Printf("process ended") return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } @@ -126,17 +139,22 @@ func sendVethName(pipe io.Writer, name string) { // Then will with set the other side of the veth pair into the container's namespaced // using the pid and returns the veth's interface name to provide to the container to // finish setting up the interface inside the namespace -func initializeContainerVeth(bridge string, nspid int) (string, error) { +func initializeContainerVeth(bridge string, mtu, nspid int) (string, error) { name1, name2, err := createVethPair() if err != nil { return "", err } + log.Printf("veth pair created %s <> %s", name1, name2) if err := network.SetInterfaceMaster(name1, bridge); err != nil { return "", err } + if err := network.SetMtu(name1, mtu); err != nil { + return "", err + } if err := network.InterfaceUp(name1); err != nil { return "", err } + log.Printf("setting %s inside %d namespace", name2, nspid) if err := network.SetInterfaceInNamespacePid(name2, nspid); err != nil { return "", err } @@ -200,8 +218,13 @@ func deletePidFile() error { // createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces // defined on the container's configuration and use the current binary as the init with the // args provided -func createCommand(container *libcontainer.Container, console string, pipe uintptr, args []string) *exec.Cmd { - command := exec.Command("nsinit", append([]string{"-console", console, "-pipe", fmt.Sprint(pipe), "init"}, args...)...) +func createCommand(container *libcontainer.Container, console, logFile string, pipe uintptr, args []string) *exec.Cmd { + command := exec.Command("nsinit", append([]string{ + "-console", console, + "-pipe", fmt.Sprint(pipe), + "-log", logFile, + "init"}, args...)...) + command.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 88a5c3c5d..8fc5f3d05 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/pkg/system" "io" "io/ioutil" + "log" "os" "path/filepath" "syscall" @@ -17,19 +18,23 @@ import ( // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. -func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, args []string) error { - rootfs, err := resolveRootfs() +func Init(container *libcontainer.Container, uncleanRootfs, console string, pipe io.ReadCloser, args []string) error { + rootfs, err := resolveRootfs(uncleanRootfs) if err != nil { return err } + log.Printf("initializing namespace at %s", rootfs) // We always read this as it is a way to sync with the parent as well tempVethName, err := getVethName(pipe) if err != nil { return err } - + if tempVethName != "" { + log.Printf("received veth name %s", tempVethName) + } if console != "" { + log.Printf("setting up console for %s", console) // close pipes so that we can replace it with the pty os.Stdin.Close() os.Stdout.Close() @@ -42,7 +47,6 @@ func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, return fmt.Errorf("dup2 slave %s", err) } } - if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } @@ -63,9 +67,11 @@ func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, if err := system.Sethostname(container.Hostname); err != nil { return fmt.Errorf("sethostname %s", err) } + log.Printf("dropping capabilities") if err := capabilities.DropCapabilities(container); err != nil { return fmt.Errorf("drop capabilities %s", err) } + log.Printf("setting user in namespace") if err := setupUser(container); err != nil { return fmt.Errorf("setup user %s", err) } @@ -74,6 +80,7 @@ func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, return fmt.Errorf("chdir to %s %s", container.WorkingDir, err) } } + log.Printf("execing %s goodbye", args[0]) if err := system.Exec(args[0], args[0:], container.Env); err != nil { return fmt.Errorf("exec %s", err) } @@ -82,12 +89,8 @@ func Init(container *libcontainer.Container, console string, pipe io.ReadCloser, // resolveRootfs ensures that the current working directory is // not a symlink and returns the absolute path to the rootfs -func resolveRootfs() (string, error) { - cwd, err := os.Getwd() - if err != nil { - return "", err - } - rootfs, err := filepath.Abs(cwd) +func resolveRootfs(uncleanRootfs string) (string, error) { + rootfs, err := filepath.Abs(uncleanRootfs) if err != nil { return "", err } diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 6508a3e9d..0873c09fe 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -6,6 +6,7 @@ import ( "flag" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + "io" "io/ioutil" "log" "os" @@ -15,6 +16,7 @@ import ( var ( console string pipeFd int + logFile string ) var ( @@ -24,22 +26,27 @@ var ( func init() { flag.StringVar(&console, "console", "", "console (pty slave) path") + flag.StringVar(&logFile, "log", "none", "log options (none, stderr, or a file path)") flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd") flag.Parse() } func main() { + if flag.NArg() < 1 { + log.Fatal(ErrWrongArguments) + } container, err := loadContainer() if err != nil { log.Fatal(err) } - if flag.NArg() < 1 { - log.Fatal(ErrWrongArguments) + if err := setupLogging(); err != nil { + log.Fatal(err) } - switch flag.Arg(0) { case "exec": // this is executed outside of the namespace in the cwd + log.SetPrefix("[nsinit exec] ") + var exitCode int nspid, err := readPid() if err != nil { @@ -50,17 +57,22 @@ func main() { if nspid > 0 { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { - exitCode, err = nsinit.Exec(container, flag.Args()[1:]) + exitCode, err = nsinit.Exec(container, logFile, flag.Args()[1:]) } if err != nil { log.Fatal(err) } os.Exit(exitCode) case "init": // this is executed inside of the namespace to setup the container + log.SetPrefix("[nsinit init] ") + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } if flag.NArg() < 2 { log.Fatal(ErrWrongArguments) } - if err := nsinit.Init(container, console, os.NewFile(uintptr(pipeFd), "pipe"), flag.Args()[1:]); err != nil { + if err := nsinit.Init(container, cwd, console, os.NewFile(uintptr(pipeFd), "pipe"), flag.Args()[1:]); err != nil { log.Fatal(err) } default: @@ -93,3 +105,20 @@ func readPid() (int, error) { } return pid, nil } + +func setupLogging() (err error) { + var writer io.Writer + switch logFile { + case "stderr": + writer = os.Stderr + case "none", "": + writer = ioutil.Discard + default: + writer, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755) + if err != nil { + return err + } + } + log.SetOutput(writer) + return nil +} From ba025cb75cceaa8536d0d512889ea86f13349950 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 15:32:50 -0800 Subject: [PATCH 101/282] User os.Args[0] as name to reexec Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 24e722a22..ba548a2bd 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -219,7 +219,9 @@ func deletePidFile() error { // defined on the container's configuration and use the current binary as the init with the // args provided func createCommand(container *libcontainer.Container, console, logFile string, pipe uintptr, args []string) *exec.Cmd { - command := exec.Command("nsinit", append([]string{ + // get our binary name so we can always reexec ourself + name := os.Args[0] + command := exec.Command(name, append([]string{ "-console", console, "-pipe", fmt.Sprint(pipe), "-log", logFile, From a352ecb01a788eff3446fe12191ca0434fce1eed Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 16:17:18 -0800 Subject: [PATCH 102/282] Use lookup path for init Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 1 + pkg/libcontainer/nsinit/init.go | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index ba548a2bd..80fe8495f 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -230,5 +230,6 @@ func createCommand(container *libcontainer.Container, console, logFile string, p command.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), } + command.Env = container.Env return command } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 8fc5f3d05..04716ba64 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "log" "os" + "os/exec" "path/filepath" "syscall" ) @@ -80,8 +81,13 @@ func Init(container *libcontainer.Container, uncleanRootfs, console string, pipe return fmt.Errorf("chdir to %s %s", container.WorkingDir, err) } } - log.Printf("execing %s goodbye", args[0]) - if err := system.Exec(args[0], args[0:], container.Env); err != nil { + name, err := exec.LookPath(args[0]) + if err != nil { + return err + } + + log.Printf("execing %s goodbye", name) + if err := system.Exec(name, args[0:], container.Env); err != nil { return fmt.Errorf("exec %s", err) } panic("unreachable") From c8fd81c27821576f339ccf4fd85c47375ba34042 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 16:28:43 -0800 Subject: [PATCH 103/282] Pass pipes into Exec function Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 14 +++++++------- pkg/libcontainer/nsinit/nsinit/main.go | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 80fe8495f..98f5209f0 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -19,7 +19,7 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Container, logFile string, args []string) (int, error) { +func Exec(container *libcontainer.Container, stdin io.Reader, stdout, stderr io.Writer, logFile string, args []string) (int, error) { var ( master *os.File console string @@ -97,23 +97,23 @@ func Exec(container *libcontainer.Container, logFile string, args []string) (int if container.Tty { log.Printf("starting copy for tty") - go io.Copy(os.Stdout, master) - go io.Copy(master, os.Stdin) + go io.Copy(stdout, master) + go io.Copy(master, stdin) state, err := setupWindow(master) if err != nil { command.Process.Kill() return -1, err } - defer term.RestoreTerminal(os.Stdin.Fd(), state) + defer term.RestoreTerminal(uintptr(syscall.Stdin), state) } else { log.Printf("starting copy for std pipes") go func() { defer inPipe.Close() - io.Copy(inPipe, os.Stdin) + io.Copy(inPipe, stdin) }() - go io.Copy(os.Stdout, outPipe) - go io.Copy(os.Stderr, errPipe) + go io.Copy(stdout, outPipe) + go io.Copy(stderr, errPipe) } log.Printf("waiting on process") diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index 0873c09fe..e6e382771 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -57,7 +57,9 @@ func main() { if nspid > 0 { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { - exitCode, err = nsinit.Exec(container, logFile, flag.Args()[1:]) + exitCode, err = nsinit.Exec(container, + os.Stdin, os.Stdout, os.Stderr, + logFile, flag.Args()[1:]) } if err != nil { log.Fatal(err) From 332755b99d345a8ffbf4fb636ca8fed604a233c0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 16:40:32 -0800 Subject: [PATCH 104/282] Pass tty master to Exec Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/exec.go | 3 +-- pkg/libcontainer/nsinit/nsinit/main.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 98f5209f0..3622196b7 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -19,9 +19,8 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Container, stdin io.Reader, stdout, stderr io.Writer, logFile string, args []string) (int, error) { +func Exec(container *libcontainer.Container, stdin io.Reader, stdout, stderr io.Writer, master *os.File, logFile string, args []string) (int, error) { var ( - master *os.File console string err error diff --git a/pkg/libcontainer/nsinit/nsinit/main.go b/pkg/libcontainer/nsinit/nsinit/main.go index e6e382771..28d42d464 100644 --- a/pkg/libcontainer/nsinit/nsinit/main.go +++ b/pkg/libcontainer/nsinit/nsinit/main.go @@ -58,7 +58,7 @@ func main() { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { exitCode, err = nsinit.Exec(container, - os.Stdin, os.Stdout, os.Stderr, + os.Stdin, os.Stdout, os.Stderr, nil, logFile, flag.Args()[1:]) } if err != nil { From 2419e63d243255ef38f16799ffdc64084aa18fe4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 17:11:57 -0800 Subject: [PATCH 105/282] Initial commit of libcontainer running docker Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 1 + execdriver/namespaces/default_template.go | 41 +++ execdriver/namespaces/driver.go | 349 ++++++++++++++++++++++ execdriver/namespaces/term.go | 26 ++ pkg/libcontainer/nsinit/exec.go | 2 +- pkg/libcontainer/nsinit/ns_linux.go | 2 +- runtime.go | 5 +- 7 files changed, 422 insertions(+), 4 deletions(-) create mode 100644 execdriver/namespaces/default_template.go create mode 100644 execdriver/namespaces/driver.go create mode 100644 execdriver/namespaces/term.go diff --git a/container.go b/container.go index ca53bb57c..76e51cdad 100644 --- a/container.go +++ b/container.go @@ -530,6 +530,7 @@ func (container *Container) Start() (err error) { } populateCommand(container) + container.command.Env = env // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/execdriver/namespaces/default_template.go b/execdriver/namespaces/default_template.go new file mode 100644 index 000000000..79b6ac1c1 --- /dev/null +++ b/execdriver/namespaces/default_template.go @@ -0,0 +1,41 @@ +package namespaces + +import ( + "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/pkg/libcontainer" +) + +// getDefaultTemplate returns the docker default for +// the libcontainer configuration file +func getDefaultTemplate() *libcontainer.Container { + return &libcontainer.Container{ + Capabilities: libcontainer.Capabilities{ + libcontainer.CAP_SETPCAP, + libcontainer.CAP_SYS_MODULE, + libcontainer.CAP_SYS_RAWIO, + libcontainer.CAP_SYS_PACCT, + libcontainer.CAP_SYS_ADMIN, + libcontainer.CAP_SYS_NICE, + libcontainer.CAP_SYS_RESOURCE, + libcontainer.CAP_SYS_TIME, + libcontainer.CAP_SYS_TTY_CONFIG, + libcontainer.CAP_MKNOD, + libcontainer.CAP_AUDIT_WRITE, + libcontainer.CAP_AUDIT_CONTROL, + libcontainer.CAP_MAC_ADMIN, + libcontainer.CAP_MAC_OVERRIDE, + libcontainer.CAP_NET_ADMIN, + }, + Namespaces: libcontainer.Namespaces{ + libcontainer.CLONE_NEWIPC, + libcontainer.CLONE_NEWNET, + libcontainer.CLONE_NEWNS, + libcontainer.CLONE_NEWPID, + libcontainer.CLONE_NEWUTS, + }, + Cgroups: &cgroups.Cgroup{ + Name: "docker", + DeviceAccess: false, + }, + } +} diff --git a/execdriver/namespaces/driver.go b/execdriver/namespaces/driver.go new file mode 100644 index 000000000..e243c6470 --- /dev/null +++ b/execdriver/namespaces/driver.go @@ -0,0 +1,349 @@ +package namespaces + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/nsinit" + "github.com/dotcloud/docker/pkg/libcontainer/utils" + "github.com/dotcloud/docker/pkg/system" + "github.com/dotcloud/docker/pkg/term" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" +) + +const ( + DriverName = "namespaces" + Version = "0.1" +) + +var ( + ErrNotSupported = errors.New("not supported") +) + +func init() { + execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { + return nil + }) +} + +type driver struct { +} + +func NewDriver() (*driver, error) { + return &driver{}, nil +} + +func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + container := createContainer(c) + if err := writeContainerFile(container, c.Rootfs); err != nil { + return -1, err + } + + var ( + console string + master *os.File + err error + + inPipe io.WriteCloser + outPipe, errPipe io.ReadCloser + ) + + if container.Tty { + log.Printf("setting up master and console") + master, console, err = createMasterAndConsole() + if err != nil { + return -1, err + } + } + c.Terminal = NewTerm(pipes, master) + + // create a pipe so that we can syncronize with the namespaced process and + // pass the veth name to the child + r, w, err := os.Pipe() + if err != nil { + return -1, err + } + system.UsetCloseOnExec(r.Fd()) + + args := append([]string{c.Entrypoint}, c.Arguments...) + createCommand(c, container, console, "/nsinit.logs", r.Fd(), args) + command := c + + if !container.Tty { + log.Printf("opening pipes on command") + if inPipe, err = command.StdinPipe(); err != nil { + return -1, err + } + if outPipe, err = command.StdoutPipe(); err != nil { + return -1, err + } + if errPipe, err = command.StderrPipe(); err != nil { + return -1, err + } + } + + log.Printf("staring init") + if err := command.Start(); err != nil { + return -1, err + } + log.Printf("writting state file") + if err := writePidFile(c.Rootfs, command.Process.Pid); err != nil { + command.Process.Kill() + return -1, err + } + defer deletePidFile(c.Rootfs) + + // Do this before syncing with child so that no children + // can escape the cgroup + if container.Cgroups != nil { + log.Printf("setting up cgroups") + if err := container.Cgroups.Apply(command.Process.Pid); err != nil { + command.Process.Kill() + return -1, err + } + } + + if container.Network != nil { + log.Printf("creating veth pair") + vethPair, err := initializeContainerVeth(container.Network.Bridge, container.Network.Mtu, command.Process.Pid) + if err != nil { + return -1, err + } + log.Printf("sending %s as veth pair name", vethPair) + sendVethName(w, vethPair) + } + + // Sync with child + log.Printf("closing sync pipes") + w.Close() + r.Close() + + if container.Tty { + log.Printf("starting copy for tty") + go io.Copy(pipes.Stdout, master) + if pipes.Stdin != nil { + go io.Copy(master, pipes.Stdin) + } + + /* + state, err := setupWindow(master) + if err != nil { + command.Process.Kill() + return -1, err + } + defer term.RestoreTerminal(uintptr(syscall.Stdin), state) + */ + } else { + log.Printf("starting copy for std pipes") + if pipes.Stdin != nil { + go func() { + defer inPipe.Close() + io.Copy(inPipe, pipes.Stdin) + }() + } + go io.Copy(pipes.Stdout, outPipe) + go io.Copy(pipes.Stderr, errPipe) + } + + if startCallback != nil { + startCallback(c) + } + + log.Printf("waiting on process") + if err := command.Wait(); err != nil { + if _, ok := err.(*exec.ExitError); !ok { + return -1, err + } + } + log.Printf("process ended") + return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil +} + +func (d *driver) Kill(p *execdriver.Command, sig int) error { + return p.Process.Kill() +} + +func (d *driver) Restore(c *execdriver.Command) error { + return ErrNotSupported +} + +func (d *driver) Info(id string) execdriver.Info { + return nil +} + +func (d *driver) Name() string { + return fmt.Sprintf("%s-%s", DriverName, Version) +} + +func (d *driver) GetPidsForContainer(id string) ([]int, error) { + return nil, ErrNotSupported +} + +func writeContainerFile(container *libcontainer.Container, rootfs string) error { + data, err := json.Marshal(container) + if err != nil { + return err + } + return ioutil.WriteFile(filepath.Join(rootfs, "container.json"), data, 0755) +} + +func getEnv(key string, env []string) string { + for _, pair := range env { + parts := strings.Split(pair, "=") + if parts[0] == key { + return parts[1] + } + } + return "" +} + +// sendVethName writes the veth pair name to the child's stdin then closes the +// pipe so that the child stops waiting for more data +func sendVethName(pipe io.Writer, name string) { + fmt.Fprint(pipe, name) +} + +// initializeContainerVeth will create a veth pair and setup the host's +// side of the pair by setting the specified bridge as the master and bringing +// up the interface. +// +// Then will with set the other side of the veth pair into the container's namespaced +// using the pid and returns the veth's interface name to provide to the container to +// finish setting up the interface inside the namespace +func initializeContainerVeth(bridge string, mtu, nspid int) (string, error) { + name1, name2, err := createVethPair() + if err != nil { + return "", err + } + log.Printf("veth pair created %s <> %s", name1, name2) + if err := network.SetInterfaceMaster(name1, bridge); err != nil { + return "", err + } + if err := network.SetMtu(name1, mtu); err != nil { + return "", err + } + if err := network.InterfaceUp(name1); err != nil { + return "", err + } + log.Printf("setting %s inside %d namespace", name2, nspid) + if err := network.SetInterfaceInNamespacePid(name2, nspid); err != nil { + return "", err + } + return name2, nil +} + +func setupWindow(master *os.File) (*term.State, error) { + ws, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + return nil, err + } + if err := term.SetWinsize(master.Fd(), ws); err != nil { + return nil, err + } + return term.SetRawTerminal(os.Stdin.Fd()) +} + +// createMasterAndConsole will open /dev/ptmx on the host and retreive the +// pts name for use as the pty slave inside the container +func createMasterAndConsole() (*os.File, string, error) { + master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0) + if err != nil { + return nil, "", err + } + console, err := system.Ptsname(master) + if err != nil { + return nil, "", err + } + if err := system.Unlockpt(master); err != nil { + return nil, "", err + } + return master, console, nil +} + +// createVethPair will automatically generage two random names for +// the veth pair and ensure that they have been created +func createVethPair() (name1 string, name2 string, err error) { + name1, err = utils.GenerateRandomName("dock", 4) + if err != nil { + return + } + name2, err = utils.GenerateRandomName("dock", 4) + if err != nil { + return + } + if err = network.CreateVethPair(name1, name2); err != nil { + return + } + return +} + +// writePidFile writes the namespaced processes pid to .nspid in the rootfs for the container +func writePidFile(rootfs string, pid int) error { + return ioutil.WriteFile(filepath.Join(rootfs, ".nspid"), []byte(fmt.Sprint(pid)), 0655) +} + +func deletePidFile(rootfs string) error { + return os.Remove(filepath.Join(rootfs, ".nspid")) +} + +// createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces +// defined on the container's configuration and use the current binary as the init with the +// args provided +func createCommand(c *execdriver.Command, container *libcontainer.Container, + console, logFile string, pipe uintptr, args []string) { + + aname, _ := exec.LookPath("nsinit") + c.Path = aname + c.Args = append([]string{ + aname, + "-console", console, + "-pipe", fmt.Sprint(pipe), + "-log", logFile, + "init", + }, args...) + c.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: uintptr(nsinit.GetNamespaceFlags(container.Namespaces)), + } + c.Env = container.Env + c.Dir = c.Rootfs +} + +func createContainer(c *execdriver.Command) *libcontainer.Container { + container := getDefaultTemplate() + + container.Hostname = getEnv("HOSTNAME", c.Env) + container.Tty = c.Tty + container.User = c.User + container.WorkingDir = c.WorkingDir + container.Env = c.Env + + container.Env = append(container.Env, "container=docker") + + if c.Network != nil { + container.Network = &libcontainer.Network{ + Mtu: c.Network.Mtu, + Address: fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), + Gateway: c.Network.Gateway, + Bridge: c.Network.Bridge, + } + } + if c.Privileged { + container.Capabilities = nil + } + if c.Resources != nil { + container.Cgroups.CpuShares = c.Resources.CpuShares + container.Cgroups.Memory = c.Resources.Memory + container.Cgroups.MemorySwap = c.Resources.MemorySwap + } + return container +} diff --git a/execdriver/namespaces/term.go b/execdriver/namespaces/term.go new file mode 100644 index 000000000..682c6a27b --- /dev/null +++ b/execdriver/namespaces/term.go @@ -0,0 +1,26 @@ +package namespaces + +import ( + "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/pkg/term" + "os" +) + +type NsinitTerm struct { + master *os.File +} + +func NewTerm(pipes *execdriver.Pipes, master *os.File) *NsinitTerm { + return &NsinitTerm{master} +} + +func (t *NsinitTerm) Close() error { + return t.master.Close() +} + +func (t *NsinitTerm) Resize(h, w int) error { + if t.master != nil { + return term.SetWinsize(t.master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) + } + return nil +} diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 3622196b7..6671ebe12 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -227,7 +227,7 @@ func createCommand(container *libcontainer.Container, console, logFile string, p "init"}, args...)...) command.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: uintptr(getNamespaceFlags(container.Namespaces)), + Cloneflags: uintptr(GetNamespaceFlags(container.Namespaces)), } command.Env = container.Env return command diff --git a/pkg/libcontainer/nsinit/ns_linux.go b/pkg/libcontainer/nsinit/ns_linux.go index e42d4b88d..58af24798 100644 --- a/pkg/libcontainer/nsinit/ns_linux.go +++ b/pkg/libcontainer/nsinit/ns_linux.go @@ -28,7 +28,7 @@ var namespaceFileMap = map[libcontainer.Namespace]string{ // getNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns -func getNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { +func GetNamespaceFlags(namespaces libcontainer.Namespaces) (flag int) { for _, ns := range namespaces { flag |= namespaceMap[ns] } diff --git a/runtime.go b/runtime.go index a38109cca..9f16d6213 100644 --- a/runtime.go +++ b/runtime.go @@ -7,7 +7,8 @@ import ( "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/execdriver/lxc" + _ "github.com/dotcloud/docker/execdriver/lxc" + "github.com/dotcloud/docker/execdriver/namespaces" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/btrfs" @@ -703,7 +704,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime sysInfo := sysinfo.New(false) - ed, err := lxc.NewDriver(config.Root, sysInfo.AppArmor) + ed, err := namespaces.NewDriver() if err != nil { return nil, err } From 67d55860a52bec8b1a1327355b4f27674ec912aa Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 21 Feb 2014 18:07:51 +0200 Subject: [PATCH 106/282] Remove Vagrantfile and remove it from all docs This removes the Vagrantfile and updates the documentation to remove the steps which explain how to install Docker in a VM via Vagrant. Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- .gitignore | 1 + MAINTAINERS | 1 - Vagrantfile | 206 ------------------ docs/sources/contributing/devenvironment.rst | 8 - docs/sources/faq.rst | 6 +- docs/sources/index.rst | 2 +- docs/sources/installation/amazon.rst | 111 +--------- docs/sources/installation/windows.rst | 213 +++---------------- 8 files changed, 37 insertions(+), 511 deletions(-) delete mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index a40d9e067..0087b4730 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ bundles/ .git/ vendor/pkg/ pyenv +Vagrantfile diff --git a/MAINTAINERS b/MAINTAINERS index 895fba563..a08f8f314 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6,4 +6,3 @@ Michael Crosby (@crosbymichael) api.go: Victor Vieux (@vieux) Dockerfile: Tianon Gravi (@tianon) Makefile: Tianon Gravi (@tianon) -Vagrantfile: Cristian Staretu (@unclejack) diff --git a/Vagrantfile b/Vagrantfile deleted file mode 100644 index 23f262020..000000000 --- a/Vagrantfile +++ /dev/null @@ -1,206 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -BOX_NAME = ENV['BOX_NAME'] || "ubuntu" -BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box" -VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box" -AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box" -AWS_REGION = ENV['AWS_REGION'] || "us-east-1" -AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900" -AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro' -SSH_PRIVKEY_PATH = ENV['SSH_PRIVKEY_PATH'] -PRIVATE_NETWORK = ENV['PRIVATE_NETWORK'] - -# Boolean that forwards the Docker dynamic ports 49000-49900 -# See http://docs.docker.io/en/latest/use/port_redirection/ for more -# $ FORWARD_DOCKER_PORTS=1 vagrant [up|reload] -FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS'] -VAGRANT_RAM = ENV['VAGRANT_RAM'] || 512 -VAGRANT_CORES = ENV['VAGRANT_CORES'] || 1 - -# You may also provide a comma-separated list of ports -# for Vagrant to forward. For example: -# $ FORWARD_PORTS=8080,27017 vagrant [up|reload] -FORWARD_PORTS = ENV['FORWARD_PORTS'] - -# A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8) -# and install docker. -$script = <