Merge pull request #2030 from weaveworks/issues/1992-cni-plugin
Implement CNI plugin
This commit is contained in:
commit
3123418022
17 files changed
+669
-70
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+74
-20
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+10
-11
@@ -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
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
+39
-30
@@ -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)
|
||||
|
||||
|
||||
+19
-2
@@ -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())
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
###<a name="cniplugin"></a>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.
|
||||
|
||||
|
||||
###<a name="addressing"></a>IP Address Management (IPAM)
|
||||
|
||||
Containers are automatically allocated a unique IP address. To view the addresses allocated by Weave run, `weave ps`.
|
||||
|
||||
@@ -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 <peer hosts>
|
||||
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.
|
||||
@@ -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
|
||||
|
||||
Executable
+35
@@ -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 <<EOF
|
||||
{
|
||||
"name": "weave",
|
||||
"type": "weave-net"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
weave_on $HOST1 launch
|
||||
weave_on $HOST1 expose
|
||||
|
||||
C1=$(docker_on $HOST1 run --net=none --name=c1 -dt $SMALL_IMAGE /bin/sh)
|
||||
C2=$(docker_on $HOST1 run --net=none --name=c2 -dt $SMALL_IMAGE /bin/sh)
|
||||
|
||||
cni_connect $HOST1 c1 >/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
|
||||
@@ -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 <host> <container> <name> [<ip> ...]
|
||||
assert_dns_record() {
|
||||
local host=$1
|
||||
|
||||
+1
Submodule vendor/github.com/appc/cni added at cde7088cbe.
+1
Submodule vendor/github.com/coreos/go-iptables added at 90456be57f.
@@ -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" <<EOF
|
||||
#!/bin/sh
|
||||
docker run --rm --privileged --net=host --pid=host -i \\
|
||||
-e CNI_VERSION -e CNI_COMMAND -e CNI_CONTAINERID -e CNI_NETNS \\
|
||||
-e CNI_IFNAME -e CNI_ARGS -e CNI_PATH $(cni_volume_options setup) \\
|
||||
$PLUGIN_IMAGE "$2"
|
||||
EOF
|
||||
chmod a+x "$1"
|
||||
}
|
||||
|
||||
create_cni_config() {
|
||||
cat >"$1" <<EOF
|
||||
{
|
||||
"name": "weave",
|
||||
"type": "weave-net"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
exec_remote() {
|
||||
docker $DOCKER_CLIENT_ARGS run --rm \
|
||||
$(docker_run_options) \
|
||||
--pid=host \
|
||||
$(cni_volume_options "$@") \
|
||||
-e DOCKERHUB_USER="$DOCKERHUB_USER" \
|
||||
-e WEAVE_VERSION \
|
||||
-e WEAVE_DEBUG \
|
||||
@@ -1837,6 +1862,13 @@ case "$COMMAND" in
|
||||
for img in $IMAGE $EXEC_IMAGE $PLUGIN_IMAGE ; do
|
||||
docker pull $img
|
||||
done
|
||||
if [ -d /opt/cni/bin ] ; then
|
||||
create_cni_script /opt/cni/bin/weave-net --cni-net
|
||||
create_cni_script /opt/cni/bin/weave-ipam --cni-ipam
|
||||
fi
|
||||
if [ -d /etc/cni/net.d -a ! -f /etc/cni/net.d/10-weave.conf ] ; then
|
||||
create_cni_config /etc/cni/net.d/10-weave.conf
|
||||
fi
|
||||
;;
|
||||
version)
|
||||
[ $# -eq 0 ] || usage
|
||||
|
||||
Reference in new issue
Block a user