diff --git a/.gitmodules b/.gitmodules index adec6564..58ec72d5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,3 +64,9 @@ [submodule "vendor/github.com/hashicorp/go-cleanhttp"] path = vendor/github.com/hashicorp/go-cleanhttp url = http://github.com/hashicorp/go-cleanhttp +[submodule "vendor/github.com/appc/cni"] + path = vendor/github.com/appc/cni + url = https://github.com/appc/cni +[submodule "vendor/github.com/coreos/go-iptables"] + path = vendor/github.com/coreos/go-iptables + url = https://github.com/coreos/go-iptables diff --git a/Makefile b/Makefile index 73072389..b181c05c 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ $(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/we $(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/*.go $(WEAVEUTIL_EXE): prog/weaveutil/*.go $(SIGPROXY_EXE): prog/sigproxy/*.go -$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/docker/*.go +$(DOCKERPLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go $(TEST_TLS_EXE): test/tls/*.go $(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go $(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go diff --git a/common/arp.go b/common/arp.go new file mode 100644 index 00000000..094d6b16 --- /dev/null +++ b/common/arp.go @@ -0,0 +1,43 @@ +package common + +import "fmt" +import "io" +import "os" + +// Configure the ARP cache parameters for the given interface. This +// makes containers react more quickly to a change in the MAC address +// associated with an IP address. +func ConfigureARPCache(name string) error { + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil { + return err + } + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil { + return err + } + if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1"); err != nil { + return err + } + return nil +} + +func sysctl(variable, value string) error { + f, err := os.OpenFile(fmt.Sprintf("/proc/sys/%s", variable), os.O_WRONLY, 0) + if err != nil { + return err + } + + n, err := io.WriteString(f, value) + if err != nil { + return err + } + if n < len(value) { + return io.ErrShortWrite + } + + err = f.Close() + if err != nil { + return err + } + + return nil +} diff --git a/common/utils.go b/common/utils.go index 9c348902..19b736d2 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,7 @@ package common import ( + "fmt" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" "net" @@ -49,7 +50,7 @@ type NetDev struct { } // Search the network namespace of a process for interfaces matching a predicate -func FindNetDevs(processID int, match func(string) bool) ([]NetDev, error) { +func FindNetDevs(processID int, match func(link netlink.Link) bool) ([]NetDev, error) { var netDevs []NetDev ns, err := netns.GetFromPid(processID) @@ -62,40 +63,93 @@ func FindNetDevs(processID int, match func(string) bool) ([]NetDev, error) { defer ns.Close() err = WithNetNS(ns, func() error { - links, err := netlink.LinkList() - if err != nil { - return err - } - for _, link := range links { - if match(link.Attrs().Name) { - addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + return forEachLink(func(link netlink.Link) error { + if match(link) { + netDev, err := linkToNetDev(link) if err != nil { return err } - - netDev := NetDev{MAC: link.Attrs().HardwareAddr} - for _, addr := range addrs { - netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) - } - netDevs = append(netDevs, netDev) + netDevs = append(netDevs, *netDev) } - } - return nil + return nil + }) }) return netDevs, err } +func forEachLink(f func(netlink.Link) error) error { + links, err := netlink.LinkList() + if err != nil { + return err + } + for _, link := range links { + if err := f(link); err != nil { + return err + } + } + return nil +} + +func linkToNetDev(link netlink.Link) (*NetDev, error) { + addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) + if err != nil { + return nil, err + } + + netDev := &NetDev{MAC: link.Attrs().HardwareAddr} + for _, addr := range addrs { + netDev.CIDRs = append(netDev.CIDRs, addr.IPNet) + } + return netDev, nil +} + // Lookup the weave interface of a container func GetWeaveNetDevs(processID int) ([]NetDev, error) { - return FindNetDevs(processID, func(name string) bool { - return strings.HasPrefix(name, "ethwe") + // Bail out if this container is running in the root namespace + nsToplevel, err := netns.GetFromPid(1) + if err != nil { + return nil, fmt.Errorf("unable to open root namespace: %s", err) + } + nsContainr, err := netns.GetFromPid(processID) + if err != nil { + return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err) + } + if nsToplevel.Equal(nsContainr) { + return nil, nil + } + + weaveBridge, err := netlink.LinkByName("weave") + if err != nil { + return nil, fmt.Errorf("Cannot find weave bridge: %s", err) + } + // Scan devices in root namespace to find those attached to weave bridge + indexes := make(map[int]struct{}) + err = forEachLink(func(link netlink.Link) error { + if link.Attrs().MasterIndex == weaveBridge.Attrs().Index { + peerIndex := link.Attrs().ParentIndex + if peerIndex == 0 { + // perhaps running on an older kernel where ParentIndex doesn't work. + // as fall-back, assume the indexes are consecutive + peerIndex = link.Attrs().Index - 1 + } + indexes[peerIndex] = struct{}{} + } + return nil + }) + if err != nil { + return nil, err + } + return FindNetDevs(processID, func(link netlink.Link) bool { + _, isveth := link.(*netlink.Veth) + _, found := indexes[link.Attrs().Index] + return isveth && found }) } // Get the weave bridge interface func GetBridgeNetDev(bridgeName string) ([]NetDev, error) { - return FindNetDevs(1, func(name string) bool { - return name == bridgeName + return FindNetDevs(1, func(link netlink.Link) bool { + return link.Attrs().Name == bridgeName }) } diff --git a/plugin/ipam/cni.go b/plugin/ipam/cni.go new file mode 100644 index 00000000..a5d407b8 --- /dev/null +++ b/plugin/ipam/cni.go @@ -0,0 +1,77 @@ +package ipamplugin + +import ( + "encoding/json" + "fmt" + "net" + + "github.com/appc/cni/pkg/skel" + "github.com/appc/cni/pkg/types" +) + +func (i *Ipam) CmdAdd(args *skel.CmdArgs) error { + result, err := i.Allocate(args) + if err != nil { + return err + } + return result.Print() +} + +func (i *Ipam) Allocate(args *skel.CmdArgs) (*types.Result, error) { + // extract the things we care about + conf, err := loadIPAMConf(args.StdinData) + if err != nil { + return nil, err + } + if conf == nil { + conf = &ipamConf{} + } + containerID := args.ContainerID + var ipnet *net.IPNet + + if conf.Subnet == "" { + ipnet, err = i.weave.AllocateIP(containerID) + } else { + var subnet *net.IPNet + subnet, err = types.ParseCIDR(conf.Subnet) + if err != nil { + return nil, fmt.Errorf("subnet given in config, but not parseable: %s", err) + } + ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet) + } + + if err != nil { + return nil, err + } + result := &types.Result{ + IP4: &types.IPConfig{ + IP: *ipnet, + Gateway: conf.Gateway, + Routes: conf.Routes, + }, + } + return result, nil +} + +func (i *Ipam) CmdDel(args *skel.CmdArgs) error { + return i.Release(args) +} + +func (i *Ipam) Release(args *skel.CmdArgs) error { + return i.weave.ReleaseIPsFor(args.ContainerID) +} + +type ipamConf struct { + Subnet string `json:"subnet,omitempty"` + Gateway net.IP `json:"gateway,omitempty"` + Routes []types.Route `json:"routes"` +} + +type netConf struct { + IPAM *ipamConf `json:"ipam"` +} + +func loadIPAMConf(stdinData []byte) (*ipamConf, error) { + var conf netConf + return conf.IPAM, json.Unmarshal(stdinData, &conf) +} diff --git a/plugin/ipam/driver.go b/plugin/ipam/driver.go index cd37e8d6..79ab1077 100644 --- a/plugin/ipam/driver.go +++ b/plugin/ipam/driver.go @@ -6,26 +6,25 @@ import ( "strings" "github.com/docker/libnetwork/discoverapi" - "github.com/docker/libnetwork/ipamapi" "github.com/docker/libnetwork/netlabel" "github.com/weaveworks/weave/api" . "github.com/weaveworks/weave/common" ) -type ipam struct { +type Ipam struct { weave *api.Client } -func NewIpam(weave *api.Client) ipamapi.Ipam { - return &ipam{weave: weave} +func NewIpam(weave *api.Client) *Ipam { + return &Ipam{weave: weave} } -func (i *ipam) GetDefaultAddressSpaces() (string, string, error) { +func (i *Ipam) GetDefaultAddressSpaces() (string, string, error) { Log.Debugln("GetDefaultAddressSpaces") return "weavelocal", "weaveglobal", nil } -func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (poolname string, subnet *net.IPNet, data map[string]string, err error) { +func (i *Ipam) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (poolname string, subnet *net.IPNet, data map[string]string, err error) { Log.Debugln("RequestPool", addressSpace, pool, subPool, options) defer func() { Log.Debugln("RequestPool returning", poolname, subnet, data, err) }() if pool == "" { @@ -50,12 +49,12 @@ func (i *ipam) RequestPool(addressSpace, pool, subPool string, options map[strin return } -func (i *ipam) ReleasePool(poolID string) error { +func (i *Ipam) ReleasePool(poolID string) error { Log.Debugln("ReleasePool", poolID) return nil } -func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) { +func (i *Ipam) RequestAddress(poolID string, address net.IP, options map[string]string) (ip *net.IPNet, _ map[string]string, err error) { Log.Debugln("RequestAddress", poolID, address, options) defer func() { Log.Debugln("allocateIP returned", ip, err) }() // If we pass magic string "_" to weave IPAM it stores the address under its own string @@ -90,17 +89,17 @@ func (i *ipam) RequestAddress(poolID string, address net.IP, options map[string] return } -func (i *ipam) ReleaseAddress(poolID string, address net.IP) error { +func (i *Ipam) ReleaseAddress(poolID string, address net.IP) error { Log.Debugln("ReleaseAddress", poolID, address) return i.weave.ReleaseIPsFor(address.String()) } // Functions required by ipamapi "contract" but not actually used. -func (i *ipam) DiscoverNew(discoverapi.DiscoveryType, interface{}) error { +func (i *Ipam) DiscoverNew(discoverapi.DiscoveryType, interface{}) error { return nil } -func (i *ipam) DiscoverDelete(discoverapi.DiscoveryType, interface{}) error { +func (i *Ipam) DiscoverDelete(discoverapi.DiscoveryType, interface{}) error { return nil } diff --git a/plugin/net/cni.go b/plugin/net/cni.go new file mode 100644 index 00000000..95977e10 --- /dev/null +++ b/plugin/net/cni.go @@ -0,0 +1,240 @@ +package plugin + +import ( + "crypto/rand" + "encoding/json" + "fmt" + "net" + "os" + + "github.com/appc/cni/pkg/ipam" + "github.com/appc/cni/pkg/skel" + "github.com/appc/cni/pkg/types" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netns" + weaveapi "github.com/weaveworks/weave/api" + "github.com/weaveworks/weave/common" + ipamplugin "github.com/weaveworks/weave/plugin/ipam" +) + +var ( + zeroNetwork = net.IPNet{IP: net.IPv4zero, Mask: net.IPv4Mask(0, 0, 0, 0)} + mask32 = net.IPv4Mask(0xff, 0xff, 0xff, 0xff) +) + +type CNIPlugin struct { + weave *weaveapi.Client +} + +func NewCNIPlugin(weave *weaveapi.Client) *CNIPlugin { + return &CNIPlugin{weave: weave} +} + +func loadNetConf(bytes []byte) (*NetConf, error) { + n := &NetConf{ + BrName: "weave", + } + if err := json.Unmarshal(bytes, n); err != nil { + return nil, errorf("failed to load netconf: %v", err) + } + return n, nil +} + +func (c *CNIPlugin) CmdAdd(args *skel.CmdArgs) error { + conf, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + if conf.IsGW { + return errorf("Gateway functionality not supported") + } + if conf.IPMasq { + return errorf("IP Masquerading functionality not supported") + } + + ns, err := netns.GetFromPath(args.Netns) + if err != nil { + return err + } + defer ns.Close() + + id := args.ContainerID + if len(id) < 5 { + data := make([]byte, 5) + _, err := rand.Reader.Read(data) + if err != nil { + return err + } + id = fmt.Sprintf("%x", data) + } + + local, err := createAndAttach(id, conf.BrName, conf.MTU) + if err != nil { + return err + } + + cleanup := func(err error) error { + netlink.LinkDel(local) + return err + } + guest, err := netlink.LinkByName(local.PeerName) + if err != nil { + return cleanup(err) + } + if err = netlink.LinkSetNsFd(guest, int(ns)); err != nil { + return cleanup(errorf("failed to move veth to container netns: %s", err)) + } + + if err := netlink.LinkSetUp(local); err != nil { + return cleanup(errorf("unable to bring veth up: %s", err)) + } + + var result *types.Result + // Default IPAM is Weave's own + if conf.IPAM.Type == "" { + result, err = ipamplugin.NewIpam(c.weave).Allocate(args) + } else { + result, err = ipam.ExecAdd(conf.IPAM.Type, args.StdinData) + } + if err != nil { + return cleanup(errorf("unable to allocate IP address: %s", err)) + } + if result.IP4 == nil { + return cleanup(errorf("IPAM plugin failed to allocate IP address")) + } + + // If config says nothing about routes or gateway, default one will be via the bridge + if result.IP4.Routes == nil && result.IP4.Gateway == nil { + bridgeIP, err := findBridgeIP(conf.BrName, result.IP4.IP) + if err != nil { + return cleanup(err) + } + result.IP4.Gateway = bridgeIP + } + + err = common.WithNetNS(ns, func() error { + return setupGuestIP4(local.PeerName, args.IfName, result.IP4.IP, result.IP4.Gateway, result.IP4.Routes) + }) + if err != nil { + return cleanup(errorf("error setting up interface: %s", err)) + } + + result.DNS = conf.DNS + return result.Print() +} + +func setupGuestIP4(origName, name string, ipnet net.IPNet, gw net.IP, routes []types.Route) error { + guest, err := netlink.LinkByName(origName) + if err != nil { + return err + } + if err = netlink.LinkSetName(guest, name); err != nil { + return err + } + if err = netlink.LinkSetUp(guest); err != nil { + return err + } + if err = common.ConfigureARPCache(name); err != nil { + return err + } + if routes == nil { // If config says nothing about routes, add a default one + if !ipnet.Contains(gw) { + // The bridge IP is not on the same subnet; add a specific route to it + gw32 := &net.IPNet{IP: gw, Mask: mask32} + if err = addRoute(guest, netlink.SCOPE_LINK, gw32, nil); err != nil { + return err + } + } + routes = []types.Route{{Dst: zeroNetwork}} + } + if err = netlink.AddrAdd(guest, &netlink.Addr{IPNet: &ipnet}); err != nil { + return fmt.Errorf("failed to add IP address to %q: %v", name, err) + } + for _, r := range routes { + if r.GW != nil { + err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, r.GW) + } else { + err = addRoute(guest, netlink.SCOPE_UNIVERSE, &r.Dst, gw) + } + if err != nil { + return fmt.Errorf("failed to add route '%v via %v dev %v': %v", r.Dst, gw, name, err) + } + } + return nil +} + +func addRoute(link netlink.Link, scope netlink.Scope, dst *net.IPNet, gw net.IP) error { + err := netlink.RouteAdd(&netlink.Route{ + LinkIndex: link.Attrs().Index, + Scope: scope, + Dst: dst, + Gw: gw, + }) + if os.IsExist(err) { // squash duplicate route errors + err = nil + } + return err +} + +func findBridgeIP(bridgeName string, subnet net.IPNet) (net.IP, error) { + netdevs, err := common.GetBridgeNetDev(bridgeName) + if err != nil { + return nil, fmt.Errorf("Failed to get netdev for %q bridge: %s", bridgeName, err) + } + if len(netdevs) == 0 { + return nil, fmt.Errorf("Could not find %q bridge", bridgeName) + } + if len(netdevs[0].CIDRs) == 0 { + return nil, fmt.Errorf("Bridge %q has no IP addresses", bridgeName) + } + for _, cidr := range netdevs[0].CIDRs { + if subnet.Contains(cidr.IP) { + return cidr.IP, nil + } + } + // None in the required subnet; just return the first one + return netdevs[0].CIDRs[0].IP, nil +} + +func (c *CNIPlugin) CmdDel(args *skel.CmdArgs) error { + conf, err := loadNetConf(args.StdinData) + if err != nil { + return err + } + + ns, err := netns.GetFromPath(args.Netns) + if err != nil { + return err + } + defer ns.Close() + err = common.WithNetNS(ns, func() error { + link, err := netlink.LinkByName(args.IfName) + if err != nil { + return err + } + return netlink.LinkDel(link) + }) + if err != nil { + return errorf("error removing interface: %s", err) + } + + // Default IPAM is Weave's own + if conf.IPAM.Type == "" { + err = ipamplugin.NewIpam(c.weave).Release(args) + } else { + err = ipam.ExecDel(conf.IPAM.Type, args.StdinData) + } + if err != nil { + return errorf("unable to release IP address: %s", err) + } + return nil +} + +type NetConf struct { + types.NetConf + BrName string `json:"bridge"` + IsGW bool `json:"isGateway"` + IPMasq bool `json:"ipMasq"` + MTU int `json:"mtu"` +} diff --git a/plugin/net/driver.go b/plugin/net/driver.go index 3c0f76b0..034721c8 100644 --- a/plugin/net/driver.go +++ b/plugin/net/driver.go @@ -107,39 +107,11 @@ func (driver *driver) EndpointInfo(req *api.EndpointInfoRequest) (*api.EndpointI } func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error) { - endID := j.EndpointID - - maybeBridge, err := netlink.LinkByName(WeaveBridge) + local, err := createAndAttach(j.EndpointID, WeaveBridge, 0) if err != nil { - return nil, errorf(`bridge "%s" not present; did you launch weave?`, WeaveBridge) + return nil, err } - // create and attach local name to the bridge - local := vethPair(endID[:5]) - local.Attrs().MTU = maybeBridge.Attrs().MTU - if err := netlink.LinkAdd(local); err != nil { - return nil, errorf("could not create veth pair: %s", err) - } - - switch maybeBridge.(type) { - case *netlink.Bridge: - if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil { - return nil, errorf(`unable to set master: %s`, err) - } - case *netlink.GenericLink: - if maybeBridge.Type() != "openvswitch" { - Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge) - return nil, errorf(`device "%s" is of type "%s"`, WeaveBridge, maybeBridge.Type()) - } - odp.AddDatapathInterface(WeaveBridge, local.Name) - case *netlink.Device: - Log.Warnf("kernel does not report what kind of device %s is, just %+v", WeaveBridge, maybeBridge) - // Assume it's our openvswitch device, and the kernel has not been updated to report the kind. - odp.AddDatapathInterface(WeaveBridge, local.Name) - default: - Log.Errorf("device %s is %+v", WeaveBridge, maybeBridge) - return nil, errorf(`device "%s" not a bridge`, WeaveBridge) - } if err := netlink.LinkSetUp(local); err != nil { return nil, errorf(`unable to bring veth up: %s`, err) } @@ -163,6 +135,43 @@ func (driver *driver) JoinEndpoint(j *api.JoinRequest) (*api.JoinResponse, error return response, nil } +// create and attach local name to the Weave bridge +func createAndAttach(id, bridgeName string, mtu int) (*netlink.Veth, error) { + maybeBridge, err := netlink.LinkByName(bridgeName) + if err != nil { + return nil, errorf(`bridge "%s" not present; did you launch weave?`, bridgeName) + } + + local := vethPair(id[:5]) + if mtu == 0 { + local.Attrs().MTU = maybeBridge.Attrs().MTU + } else { + local.Attrs().MTU = mtu + } + if err := netlink.LinkAdd(local); err != nil { + return nil, errorf("could not create veth pair: %s", err) + } + + switch maybeBridge.(type) { + case *netlink.Bridge: + if err := netlink.LinkSetMasterByIndex(local, maybeBridge.Attrs().Index); err != nil { + return nil, errorf(`unable to set master: %s`, err) + } + case *netlink.GenericLink: + if maybeBridge.Type() != "openvswitch" { + return nil, errorf(`device "%s" is of type "%s"`, bridgeName, maybeBridge.Type()) + } + odp.AddDatapathInterface(bridgeName, local.Name) + case *netlink.Device: + Log.Warnf("kernel does not report what kind of device %s is, just %+v", bridgeName, maybeBridge) + // Assume it's our openvswitch device, and the kernel has not been updated to report the kind. + odp.AddDatapathInterface(bridgeName, local.Name) + default: + return nil, errorf(`device "%s" not a bridge`, bridgeName) + } + return local, nil +} + func (driver *driver) LeaveEndpoint(leave *api.LeaveRequest) error { Log.Debugf("Leave request: %+v", leave) diff --git a/prog/plugin/main.go b/prog/plugin/main.go index 82a594e7..3eb12a55 100644 --- a/prog/plugin/main.go +++ b/prog/plugin/main.go @@ -7,8 +7,10 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" + cni "github.com/appc/cni/pkg/skel" "github.com/docker/libnetwork/ipamapi" weaveapi "github.com/weaveworks/weave/api" "github.com/weaveworks/weave/common" @@ -26,6 +28,8 @@ var Log = common.Log func main() { var ( justVersion bool + cniNet bool + cniIpam bool address string meshAddress string logLevel string @@ -33,6 +37,8 @@ func main() { ) flag.BoolVar(&justVersion, "version", false, "print version and exit") + flag.BoolVar(&cniNet, "cni-net", false, "act as a CNI network plugin") + flag.BoolVar(&cniIpam, "cni-ipam", false, "act as a CNI IPAM plugin") flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)") flag.StringVar(&address, "socket", "/run/docker/plugins/weave.sock", "socket on which to listen") flag.StringVar(&meshAddress, "meshsocket", "/run/docker/plugins/weavemesh.sock", "socket on which to listen in mesh mode") @@ -47,14 +53,25 @@ func main() { common.SetLogLevel(logLevel) + weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) + + switch { + case cniIpam || strings.HasSuffix(os.Args[0], "weave-ipam"): + i := ipamplugin.NewIpam(weave) + cni.PluginMain(i.CmdAdd, i.CmdDel) + os.Exit(0) + case cniNet || strings.HasSuffix(os.Args[0], "weave-net"): + n := netplugin.NewCNIPlugin(weave) + cni.PluginMain(n.CmdAdd, n.CmdDel) + os.Exit(0) + } + // API 1.21 is the first version that supports docker network commands dockerClient, err := docker.NewVersionedClientFromEnv("1.21") if err != nil { Log.Fatalf("unable to connect to docker: %s", err) } - weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), Log) - Log.Println("Weave plugin", version, "Command line options:", os.Args[1:]) Log.Info(dockerClient.Info()) diff --git a/site/features.md b/site/features.md index 79bad582..57e55add 100644 --- a/site/features.md +++ b/site/features.md @@ -10,6 +10,7 @@ Net: * [Fast Data Path](#fast-data-path) * [Seamless Docker Integration](#docker) * [Docker Network Plugin](#plugin) + * [CNI Plugin](#cniplugin) * [Address Allocation (IPAM)](#addressing) * [Naming and Discovery](#naming-and-discovery) * [Application Isolation](#application-isolation) @@ -100,6 +101,13 @@ when there are network connectivity problems. See [Using the Weave Docker Network Plugin](/site/plugin/weave-plugin-how-to.md) for more details. +###Weave Network CNI Plugin + +Weave can be used as a plugin to systems that support the [Container Network Interface](https://github.com/appc/cni), such as Kubernetes. + +See [Using the Weave CNI Plugin](/site/plugin/cni-plugin-how-to.md) for more details. + + ###IP Address Management (IPAM) Containers are automatically allocated a unique IP address. To view the addresses allocated by Weave run, `weave ps`. diff --git a/site/plugin/cni-plugin-how-to.md b/site/plugin/cni-plugin-how-to.md new file mode 100644 index 00000000..8384358f --- /dev/null +++ b/site/plugin/cni-plugin-how-to.md @@ -0,0 +1,77 @@ +--- +title: Using the Weave Net CNI Plugin +layout: default +--- + +CNI, the [_Container Network Interface_](https://github.com/appc/cni#cni---the-container-network-interface), +is a proposed standard for configuring network interfaces for Linux +application containers. CNI is supported by +[Kubernetes](http://kubernetes.io/), an open source container cluster +manager built by Google. + +###Installing the Weave Net CNI plugin + +The Weave Net CNI plugin is installed when you run `weave setup`, if +your machine has the directories normally used to host CNI plugins. +To create those directories, run (as root): + + mkdir -p /opt/cni/bin + mkdir -p /etc/cni/net.d + +Then run: + + weave setup + +###Launching Weave Net + +To create a network which can span multiple hosts, the Weave peers must be connected to each other, by specifying the other hosts during `weave launch` or via +[`weave connect`](/site/using-weave/finding-adding-hosts-dynamically.md). + +See [Deploying Applications to Weave Net](/site/using-weave/deploying-applications.md#peer-connections) for a discussion on peer connections. + +As well as launching Weave Net, you have to run an extra command to +perform some additional configuration of the Weave bridge: + + weave launch + weave expose + +###Configuring Kubernetes to use the CNI Plugin + +After you've launched Weave and peered your hosts, you can configure +Kubernetes to use Weave, by adding the following options to the +`kubelet` command: + + --network-plugin=cni --network-plugin-dir=/etc/cni/net.d + +See the [`kubelet` documentation](http://kubernetes.io/v1.1/docs/admin/kubelet.html) +for more details. + +Now, whenever Kubernetes starts a pod, it will be attached to the Weave network. + +###The CNI network configuration file + +All CNI plugins are configured by a JSON file in the directory +`/etc/cni/net.d/`. `weave setup` installs a minimal configuration +file named `10-weave.conf`, which you can alter to suit your needs. + +See the [CNI Spec]](https://github.com/appc/cni/blob/master/SPEC.md#network-configuration) +for details on the format and contents of this file. + +By default, the Weave CNI plugin will add a default route out via the Weave bridge, so your containers can access resources on the internet. If you do not want this, add a section to the config file specifying no routes: + +``` + "ipam": { + "routes": [ ] + } +``` + +The following other fields in the spec are supported: + +- `ipam / type` - default is to use Weave's own IPAM +- `ipam / subnet` - default is to use Weave's IPAM default subnet +- `ipam / gateway` - default is to use the Weave bridge IP address (allocated by `weave expose`) + +###Caveats + +- The Weave router container must be running for CNI to allocate addresses +- The CNI plugin does not add entries to Weave DNS. diff --git a/test/090_docker_restart_policy_2_test.sh b/test/090_docker_restart_policy_2_test.sh index b8b9a5a7..dba02595 100755 --- a/test/090_docker_restart_policy_2_test.sh +++ b/test/090_docker_restart_policy_2_test.sh @@ -4,17 +4,13 @@ start_suite "Test docker restart policy" -pidof() { - docker_on $1 inspect -f '{{.State.Pid}}' $2 -} - check_restart() { - OLD_PID=$(pidof $1 $2) + OLD_PID=$(container_pid $1 $2) run_on $1 sudo kill $OLD_PID for i in $(seq 1 30); do - NEW_PID=$(pidof $1 $2) + NEW_PID=$(container_pid $1 $2) if [ $NEW_PID != 0 -a $NEW_PID != $OLD_PID ] ; then return 0 diff --git a/test/830_cni_plugin_test.sh b/test/830_cni_plugin_test.sh new file mode 100755 index 00000000..63b07778 --- /dev/null +++ b/test/830_cni_plugin_test.sh @@ -0,0 +1,35 @@ +#! /bin/bash + +. ./config.sh + +start_suite "Test CNI plugin" + +cni_connect() { + pid=$(container_pid $1 $2) + docker_on $1 run --rm --privileged --net=host --pid=host -i \ + -e CNI_VERSION=1 -e CNI_COMMAND=ADD -e CNI_CONTAINERID=c1 \ + -e CNI_NETNS=/proc/$pid/ns/net -e CNI_IFNAME=eth0 -e CNI_PATH=/opt/cni/bin \ + weaveworks/plugin:latest --cni-net </dev/null +cni_connect $HOST1 c2 >/dev/null + +C1IP=$(container_ip $HOST1 c1) +C2IP=$(container_ip $HOST1 c2) + +assert_raises "exec_on $HOST1 c1 $PING $C2IP" +assert_raises "exec_on $HOST1 c2 $PING $C1IP" + +end_suite diff --git a/test/config.sh b/test/config.sh index 3d23562f..aefdd6b7 100644 --- a/test/config.sh +++ b/test/config.sh @@ -183,6 +183,10 @@ container_ip() { weave_on $1 ps $2 | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' } +container_pid() { + docker_on $1 inspect -f '{{.State.Pid}}' $2 +} + # assert_dns_record [ ...] assert_dns_record() { local host=$1 diff --git a/vendor/github.com/appc/cni b/vendor/github.com/appc/cni new file mode 160000 index 00000000..cde7088c --- /dev/null +++ b/vendor/github.com/appc/cni @@ -0,0 +1 @@ +Subproject commit cde7088cbe539013c2efd994d07bdaac0f5a59b0 diff --git a/vendor/github.com/coreos/go-iptables b/vendor/github.com/coreos/go-iptables new file mode 160000 index 00000000..90456be5 --- /dev/null +++ b/vendor/github.com/coreos/go-iptables @@ -0,0 +1 @@ +Subproject commit 90456be57fcb8185b264b77ce42a9539df42df25 diff --git a/weave b/weave index c3df0664..07f2375c 100755 --- a/weave +++ b/weave @@ -115,10 +115,35 @@ docker_run_options() { echo --privileged --net=host $(docker_sock_options) } +cni_volume_options() { + [ "$1" = "setup" ] && echo -v /etc/cni:/etc/cni -v /opt/cni:/opt/cni +} + +create_cni_script() { + cat >"$1" <"$1" <