Merge pull request #2030 from weaveworks/issues/1992-cni-plugin

Implement CNI plugin
This commit is contained in:
Adam Harrison committed 2016-03-21 10:51:19 +00:00
commit 3123418022
17 files changed
+669 -70

No files matched your search

+6
View File
@@ -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
+1 -1
View File
@@ -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
+43
View File
@@ -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
View File
@@ -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
})
}
+77
View File
@@ -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
View File
@@ -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
}
+240
View File
@@ -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
View File
@@ -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
View File
@@ -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())
+8
View File
@@ -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`.
+77
View File
@@ -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.
+2 -6
View File
@@ -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
+35
View File
@@ -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
+4
View File
@@ -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
Generated Vendored Submodule
+1
+32
View File
@@ -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