diff --git a/README.md b/README.md index ad1f773c..7d25e474 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,35 @@ each other but not the containers of our first application... This isolation-through-subnets scheme is an example of carrying over a well-known technique from the 'on metal' days to containers. +### Dynamic network attachment + +In some scenarios containers are started independently, e.g. via some +existing tool chain, or require more complex startup sequences than +provided by `weave run`. And sometimes the decision which application +network a container should be part of is made post startup. For these +situations, weave allows an existing, running container to be attached +to the weave network. To illustrate, we can achieve the same effect as +the first example with + + host1# C=$(docker run -d -t -i ubuntu) + host1# weave attach 10.0.1.1/24 $C + +There is a matching `weave detach` command: + + host1# weave detach 10.0.1.1/24 $C + +You can detach a container from one application network and attach it +to another: + + host1# weave detach 10.0.1.1/24 $C + host1# weave attach 10.0.2.1/24 $C + +or attach a container to multiple application networks, effectively +sharing it between applications: + + host1# weave attach 10.0.1.1/24 $C + host1# weave attach 10.0.2.1/24 $C + ### Security In order to connect containers across untrusted networks, weave peers @@ -435,6 +464,21 @@ Any running application containers will permanently lose connectivity with the weave network and have to be restarted in order to re-connect. +### Reboots + +When a host reboots, docker's default behaviour is to restart any +containers that were running. Since weave relies on special network +configuration outside of the containers, the weave network will not +function in this state. + +To remedy this, stop and re-launch the weave container, and re-attach +the application containers with `weave attach`. + +For a more permanent solution, +[disable Docker's auto-restart feature](https://docs.docker.com/articles/host_integration/) +and create appropriate startup scripts to launch weave and run +application containers from your favourite process manager. + ## Installation with Boot2Docker If you are running Docker inside the Boot2Docker VM, e.g. because you diff --git a/connection_maker.go b/connection_maker.go index 94064646..718b2fdc 100644 --- a/connection_maker.go +++ b/connection_maker.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "log" - "math" "math/rand" "net" "time" @@ -59,7 +58,7 @@ func (cm *ConnectionMaker) String() string { } func (cm *ConnectionMaker) queryLoop(queryChan <-chan *ConnectionMakerInteraction) { - timer := time.NewTimer(math.MaxInt64) + timer := time.NewTimer(MaxDuration) run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) } for { select { @@ -131,7 +130,7 @@ func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration { }) now := time.Now() // make sure we catch items just added - after := time.Duration(math.MaxInt64) + after := MaxDuration for address, target := range cm.targets { if our_connected_targets[address] { delete(cm.targets, address) diff --git a/consts.go b/consts.go index e90a27ad..06add6f9 100644 --- a/consts.go +++ b/consts.go @@ -2,6 +2,7 @@ package weave import ( "time" + "math" ) const ( @@ -23,6 +24,7 @@ const ( FragTestInterval = 5 * time.Minute PMTUVerifyAttempts = 8 PMTUVerifyTimeout = 10 * time.Millisecond // gets doubled with every attempt + MaxDuration = time.Duration(math.MaxInt64) ) const ( diff --git a/pcap.go b/pcap.go index 354ec818..bcca3edd 100644 --- a/pcap.go +++ b/pcap.go @@ -40,7 +40,7 @@ func newPcapIO(ifName string, promisc bool, snaplen int, bufSz int) (handle *Pca if err = inactive.SetSnapLen(snaplen); err != nil { return } - if err = inactive.SetTimeout(-1); err != nil { + if err = inactive.SetTimeout(MaxDuration); err != nil { return } if err = inactive.SetImmediateMode(true); err != nil { @@ -60,7 +60,12 @@ func newPcapIO(ifName string, promisc bool, snaplen int, bufSz int) (handle *Pca } func (pi *PcapIO) ReadPacket() (data []byte, err error) { - data, _, err = pi.handle.ZeroCopyReadPacketData() + for { + data, _, err = pi.handle.ZeroCopyReadPacketData() + if err == nil || err != pcap.NextErrorTimeoutExpired { + break + } + } return } diff --git a/weaver/Dockerfile b/weaver/Dockerfile index adf86d35..9baf50bb 100644 --- a/weaver/Dockerfile +++ b/weaver/Dockerfile @@ -3,4 +3,4 @@ MAINTAINER Zettio weave@zett.io WORKDIR /home/weave ADD ./weaver /home/weave/ CMD [] -ENTRYPOINT ["/home/weave/weaver", "-iface", "ethwe", "-wait"] +ENTRYPOINT ["/home/weave/weaver", "-iface", "ethwe", "-wait", "5"] diff --git a/weaver/main.go b/weaver/main.go index 63bb60d1..3ca078db 100644 --- a/weaver/main.go +++ b/weaver/main.go @@ -18,17 +18,19 @@ import ( "time" ) -func ensureInterface(ifaceName string, wait bool) (iface *net.Interface, err error) { +func ensureInterface(ifaceName string, wait int) (iface *net.Interface, err error) { iface, err = findInterface(ifaceName) - if err == nil || !wait { + if err == nil || wait == 0 { return } log.Println("Waiting for interface", ifaceName, "to come up") - for err != nil { + for ; err != nil && wait > 0; wait -= 1 { time.Sleep(1 * time.Second) iface, err = findInterface(ifaceName) } - log.Println("Interface", ifaceName, "is up") + if err == nil { + log.Println("Interface", ifaceName, "is up") + } return } @@ -47,6 +49,7 @@ func main() { log.SetPrefix(weave.Protocol + " ") log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds) + log.Println(os.Args) procs := runtime.NumCPU() // packet sniffing can block an OS thread, so we need one thread @@ -60,7 +63,7 @@ func main() { ifaceName string routerName string password string - wait bool + wait int debug bool prof string peers []string @@ -71,7 +74,7 @@ func main() { flag.StringVar(&ifaceName, "iface", "", "name of interface to read from") flag.StringVar(&routerName, "name", "", "name of router (defaults to MAC)") flag.StringVar(&password, "password", "", "network password") - flag.BoolVar(&wait, "wait", false, "wait for interface to be created and come up") + flag.IntVar(&wait, "wait", 0, "number of seconds to wait for interface to be created and come up (defaults to 0, i.e. don't wait)") flag.BoolVar(&debug, "debug", false, "enable debug logging") flag.StringVar(&prof, "profile", "", "enable profiling and write profiles to given path") flag.IntVar(&connLimit, "connlimit", 10, "connection limit (defaults to 10, set to 0 for unlimited)") diff --git a/weaver/weave b/weaver/weave index e673f9f9..7c04fdd0 100755 --- a/weaver/weave +++ b/weaver/weave @@ -3,8 +3,10 @@ set -e usage() { echo "Usage:" - echo "weave launch / [-passwd ] ..." + echo "weave launch / [-password ] ..." echo "weave run / ..." + echo "weave attach / " + echo "weave detach / " echo "weave expose /" echo "weave hide /" echo "weave status" @@ -101,7 +103,7 @@ stop_masquerading() { } # the following borrows from https://github.com/jpetazzo/pipework -configure_container_networking() { +container_netns_preamble() { DOCKERPID=$(docker inspect --format='{{ .State.Pid }}' $1) [ "$DOCKERPID" = 0 ] && { echo "Container $1 not running." >&2 @@ -122,7 +124,41 @@ configure_container_networking() { GUEST_IFNAME="v${CONTAINER_IFNAME}pg${NSPID}" IP_TMPFILE=/tmp/weave_ip_output_$$ rm -f $IP_TMPFILE - if ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU >$IP_TMPFILE 2>&1 ; then + + return 0 +} + +container_netns_postamble() { + if [ "$EXITCODE" -ne 0 ] ; then + if ! ps -p $DOCKERPID >/dev/null 2>&1 ; then + echo "Container $1 died" >&2 + else + echo "Failure during network configuration for container $1:" >&2 + cat $IP_TMPFILE >&2 + fi + fi + + rm -f $IP_TMPFILE + rm -f /var/run/netns/$NSPID +} + +# Attach Container-ID $1 to the weave network at CIDR address $2, +# creating a virtual ethernet device if necessary. +# Also turn off tx offload on device $3 inside the namespace. +attach() { + container_netns_preamble $1 + + # If veth name already exists then assume we set it up already; + # just add the IP address, if it's not already there + if ip link show $LOCAL_IFNAME >/dev/null 2>&1 ; then + if ! (ip netns exec $NSPID ip addr list dev $CONTAINER_IFNAME | grep -qF $2) 2>$IP_TMPFILE ; then + if ip netns exec $NSPID ip addr add $2 dev $CONTAINER_IFNAME 2>$IP_TMPFILE ; then + EXITCODE=0 # succeeded in adding the address + fi + else + EXITCODE=0 # address was there already + fi + elif ip link add name $LOCAL_IFNAME mtu $MTU type veth peer name $GUEST_IFNAME mtu $MTU >$IP_TMPFILE 2>&1 ; then if (ip link set $LOCAL_IFNAME master $BRIDGE && ip link set $LOCAL_IFNAME up && ip link set $GUEST_IFNAME netns $NSPID) >$IP_TMPFILE 2>&1 @@ -138,18 +174,32 @@ configure_container_networking() { fi fi - if [ "$EXITCODE" -ne 0 ] ; then - if ! ps -p $DOCKERPID >/dev/null 2>&1 ; then - echo "Container $1 died" >&2 - else - echo "Failure during network configuration for container $1:" >&2 - cat $IP_TMPFILE >&2 + container_netns_postamble $1 + + return $EXITCODE +} + +# Detach Container-ID $1 at CIDR address $2 from the weave network +# removing the virtual ethernet device if there are no more addresses +detach() { + container_netns_preamble $1 + + if (ip netns exec $NSPID ip addr list dev $CONTAINER_IFNAME | grep -qF $2) 2>$IP_TMPFILE ; then + if ip netns exec $NSPID ip addr del $2 dev $CONTAINER_IFNAME 2>$IP_TMPFILE ; then + # if no inet addresses remain then delete the veth + if [ -z "$(ip netns exec $NSPID ip -f inet addr list dev ethwe 2>$IP_TMPFILE)" ] ; then + if ip link del $LOCAL_IFNAME type veth 2>$IP_TMPFILE ; then + EXITCODE=0 + fi + else + EXITCODE=0 + fi fi + else + EXITCODE=0 fi - rm -f $IP_TMPFILE - rm -f /var/run/netns/$NSPID - + container_netns_postamble $1 return $EXITCODE } @@ -186,7 +236,7 @@ case "$COMMAND" in # restarts. MACADDR=`cat /sys/class/net/$BRIDGE/address` CONTAINER=$(docker run --privileged -d --name=weave -p 6783:6783/tcp -p 6783:6783/udp $WEAVE_DOCKER_ARGS zettio/weave -name $MACADDR "$@") - configure_container_networking $CONTAINER $IPADDR eth0 + attach $CONTAINER $IPADDR eth0 echo $CONTAINER ;; status) @@ -223,9 +273,18 @@ case "$COMMAND" in shift 1 create_bridge CONTAINER=$(docker run -d "$@") - configure_container_networking $CONTAINER $IPADDR $CONTAINER_IFNAME + attach $CONTAINER $IPADDR $CONTAINER_IFNAME echo $CONTAINER ;; + attach) + [ $# -gt 1 ] || usage + create_bridge + attach $2 $1 $CONTAINER_IFNAME + ;; + detach) + [ $# -gt 1 ] || usage + detach $2 $1 + ;; expose) [ $# -gt 0 ] || usage IPADDR=$1