* Module name changed to `github.com/rajch/weave` * Added environment variable `CHECKPOINT_DISABLE=1` to default manifests, to bypass weaveworks telemetry
413 lines
14 KiB
Go
413 lines
14 KiB
Go
/*
|
|
Package main deals with weave-net peers on the cluster.
|
|
|
|
This involves peer management, such as getting the latest peers or removing defunct peers from the cluster
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/vishvananda/netlink"
|
|
"golang.org/x/sys/unix"
|
|
api "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/client-go/informers"
|
|
"k8s.io/client-go/kubernetes"
|
|
"k8s.io/client-go/rest"
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
weaveapi "github.com/rajch/weave/api"
|
|
"github.com/rajch/weave/common"
|
|
)
|
|
|
|
type nodeInfo struct {
|
|
name string
|
|
addr string
|
|
}
|
|
|
|
// return the IP addresses of all nodes in the cluster
|
|
func getKubePeers(c kubernetes.Interface, includeWithNoIPAddr bool) ([]nodeInfo, error) {
|
|
ctx := context.Background()
|
|
nodeList, err := c.CoreV1().Nodes().List(ctx, api.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addresses := make([]nodeInfo, 0, len(nodeList.Items))
|
|
for _, peer := range nodeList.Items {
|
|
var internalIP, externalIP string
|
|
for _, addr := range peer.Status.Addresses {
|
|
// Check it's a valid ipv4 address
|
|
ip := net.ParseIP(addr.Address)
|
|
if ip == nil || ip.To4() == nil {
|
|
continue
|
|
}
|
|
if addr.Type == "InternalIP" {
|
|
internalIP = ip.To4().String()
|
|
} else if addr.Type == "ExternalIP" {
|
|
externalIP = ip.To4().String()
|
|
}
|
|
}
|
|
|
|
// Fallback for cases where a Node has an ExternalIP but no InternalIP
|
|
if internalIP != "" {
|
|
// exclude self from the list of peers this node will peer with
|
|
if isLocalNodeIP(internalIP) {
|
|
continue
|
|
}
|
|
addresses = append(addresses, nodeInfo{name: peer.Name, addr: internalIP})
|
|
} else if externalIP != "" {
|
|
addresses = append(addresses, nodeInfo{name: peer.Name, addr: externalIP})
|
|
} else if includeWithNoIPAddr {
|
|
addresses = append(addresses, nodeInfo{name: peer.Name, addr: ""})
|
|
}
|
|
}
|
|
return addresses, nil
|
|
}
|
|
|
|
// returns true if given IP matches with one of the local IP's
|
|
func isLocalNodeIP(ip string) bool {
|
|
addrs, err := netlink.AddrList(nil, unix.AF_INET)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, addr := range addrs {
|
|
if addr.Peer != nil && addr.Peer.IP.String() == ip {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// (minimal, incomplete) interface so weaver can be mocked for testing.
|
|
type weaveClient interface {
|
|
RmPeer(peerName string) (string, error)
|
|
}
|
|
|
|
// For each of those peers that is no longer listed as a node by
|
|
// Kubernetes, remove it from Weave IPAM
|
|
func reclaimRemovedPeers(kube kubernetes.Interface, cml *configMapAnnotations, myPeerName, myNodeName string) error {
|
|
weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), common.Log)
|
|
for loopsWhenNothingChanged := 0; loopsWhenNothingChanged < 3; loopsWhenNothingChanged++ {
|
|
// 1. Compare peers stored in the peerList against all peers reported by k8s now.
|
|
storedPeerList, err := getRefreshedPeerList(cml)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodes, err := getKubePeers(kube, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
nodeSet := make(map[string]struct{}, len(nodes))
|
|
for _, node := range nodes {
|
|
nodeSet[node.name] = struct{}{}
|
|
}
|
|
peerMap := make(map[string]peerInfo, len(storedPeerList.Peers))
|
|
for _, peer := range storedPeerList.Peers {
|
|
if peer.PeerName != myPeerName {
|
|
peerMap[peer.PeerName] = peer
|
|
}
|
|
}
|
|
// remove entries from the peer map that are current nodes
|
|
for key, peer := range peerMap {
|
|
if _, found := nodeSet[peer.NodeName]; found {
|
|
// unless they have a duplicate of my NodeName but are not me
|
|
if peer.NodeName == myNodeName && peer.PeerName != myPeerName {
|
|
continue
|
|
}
|
|
delete(peerMap, key)
|
|
}
|
|
}
|
|
// so the remainder is everything we want to clean up
|
|
common.Log.Debugln("[kube-peers] Nodes that have disappeared:", peerMap)
|
|
if len(peerMap) == 0 {
|
|
break
|
|
}
|
|
// 2. Loop for each X in the first set and not in the second - we wish to remove X from our data structures
|
|
for _, peer := range peerMap {
|
|
if peer.PeerName == myPeerName { // Don't remove myself.
|
|
common.Log.Warnln("[kube-peers] not removing myself", peer)
|
|
continue
|
|
}
|
|
changed, err := reclaimPeer(weave, cml, peer.PeerName, myPeerName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if changed {
|
|
loopsWhenNothingChanged = 0
|
|
}
|
|
}
|
|
|
|
// 9. Go back to step 1 until there is no difference between the two sets
|
|
// (or we hit the counter that says we've been round the loop 3 times and nothing happened)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Attempt to reclaim the IP addresses owned by peerName, using the
|
|
// Kubernetes api-server as a point of consensus so that only one peer
|
|
// actions the reclaim.
|
|
// Return a bool to show whether we attempted to change anything,
|
|
// and an error if something went wrong.
|
|
func reclaimPeer(weave weaveClient, cml *configMapAnnotations, peerName string, myPeerName string) (changed bool, err error) {
|
|
ctx := context.Background()
|
|
common.Log.Debugln("[kube-peers] Preparing to remove disappeared peer", peerName)
|
|
okToRemove := false
|
|
nonExistentPeer := false
|
|
|
|
// Re-read status from Kubernetes; speculative updates may leave it un-set
|
|
if err := cml.Init(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// 3. Check if there is an existing annotation with key X
|
|
existingAnnotation, found := cml.GetAnnotation(KubePeersPrefix + peerName)
|
|
if found {
|
|
common.Log.Debugln("[kube-peers] Existing annotation", existingAnnotation)
|
|
// 4. If annotation already contains my identity, ok;
|
|
if existingAnnotation == myPeerName {
|
|
okToRemove = true
|
|
} else {
|
|
storedPeerList, err := cml.GetPeerList()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// handle an edge case where peer claimed to own the action to reclaim but no longer
|
|
// exists hence lock persists foever
|
|
if !storedPeerList.contains(existingAnnotation) {
|
|
nonExistentPeer = true
|
|
common.Log.Debugln("[kube-peers] Existing annotation", existingAnnotation, " has a non-existent peer so owning the reclaim action")
|
|
}
|
|
}
|
|
}
|
|
if !found || nonExistentPeer {
|
|
// 5. If non-existent, write an annotation with key X and contents "my identity"
|
|
common.Log.Debugln("[kube-peers] Noting I plan to remove ", peerName)
|
|
if err := cml.UpdateAnnotation(ctx, KubePeersPrefix+peerName, myPeerName); err == nil {
|
|
okToRemove = true
|
|
} else {
|
|
common.Log.Errorln("[kube-peers] error from UpdateAnnotation: ", err)
|
|
}
|
|
}
|
|
if !okToRemove {
|
|
return false, nil
|
|
}
|
|
// 6. If step 4 or 5 succeeded, rmpeer X
|
|
// we will try once more to see if the peerName has been removed from the annotation
|
|
// by another weave pod
|
|
storedPeerList, err := getRefreshedPeerList(cml)
|
|
if storedPeerList.contains(peerName) {
|
|
result, err := weave.RmPeer(peerName)
|
|
common.Log.Infof("[kube-peers] rmpeer of %s: %s", peerName, result)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
err = cml.LoopUpdate(func() error {
|
|
// 7aa. Remove any annotations Z* that have contents X
|
|
if err := cml.RemoveAnnotationsWithValue(ctx, peerName); err != nil {
|
|
return err
|
|
}
|
|
// 7a. Remove X from peerList
|
|
storedPeerList, err := cml.GetPeerList()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if storedPeerList.contains(peerName) {
|
|
storedPeerList.remove(peerName)
|
|
if err := cml.UpdatePeerList(ctx, *storedPeerList); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
common.Log.Infof("[kube-peers] annotation was removed already for %s", peerName)
|
|
}
|
|
// 7b. Remove annotation with key X
|
|
return cml.RemoveAnnotation(ctx, KubePeersPrefix+peerName)
|
|
})
|
|
// 8. If step 5 failed due to optimistic lock conflict, stop: someone else is handling X
|
|
|
|
// Step 3-5 is to protect against two simultaneous rmpeers of X
|
|
// Step 4 is to pick up again after a restart between step 5 and step 7b
|
|
// If the peer doing the reclaim disappears between steps 5 and 7a, then someone will clean it up in step 7aa
|
|
// If peer doing the reclaim disappears forever between 7a and 7b then we get a dangling annotation
|
|
// This should be sufficiently rare that we don't care.
|
|
|
|
// Question: Should we narrow step 2 by checking against Weave Net IPAM?
|
|
// i.e. If peer X owns any address space and is marked unreachable, we want to rmpeer X
|
|
return true, err
|
|
}
|
|
|
|
// resetPeers replaces the peers list with current set of peers
|
|
func resetPeers(kube kubernetes.Interface) error {
|
|
nodes, err := getKubePeers(kube, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
peerList := make([]string, 0)
|
|
for _, node := range nodes {
|
|
peerList = append(peerList, node.addr)
|
|
}
|
|
weave := weaveapi.NewClient(os.Getenv("WEAVE_HTTP_ADDR"), common.Log)
|
|
err = weave.ReplacePeers(peerList)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// regiesters with Kubernetes API server for node delete events. Node delete event handler
|
|
// invokes reclaimRemovedPeers to remove it from IPAM so that IP space is reclaimed
|
|
func registerForNodeUpdates(client *kubernetes.Clientset, stopCh <-chan struct{}, nodeName, peerName string) {
|
|
informerFactory := informers.NewSharedInformerFactory(client, 0)
|
|
nodeInformer := informerFactory.Core().V1().Nodes().Informer()
|
|
common.Log.Debugln("registering for updates for node delete events")
|
|
nodeInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
|
DeleteFunc: func(obj interface{}) {
|
|
var name string
|
|
if apiObj, ok := obj.(api.Object); ok {
|
|
name = apiObj.GetName()
|
|
} else {
|
|
name = fmt.Sprintf("%#v", obj)
|
|
}
|
|
common.Log.Debugln("Delete event for", name)
|
|
// add random delay to avoid all nodes acting on node delete event at the same
|
|
// time leading to contention to use `weave-net` configmap
|
|
r := rand.Intn(5000)
|
|
time.Sleep(time.Duration(r) * time.Millisecond)
|
|
|
|
cml := newConfigMapAnnotations(configMapNamespace, configMapName, client)
|
|
err := reclaimRemovedPeers(client, cml, peerName, nodeName)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Error while reclaiming space: %v", err)
|
|
}
|
|
err = resetPeers(client)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Error resetting peer list: %v", err)
|
|
}
|
|
},
|
|
})
|
|
informerFactory.WaitForCacheSync(stopCh)
|
|
informerFactory.Start(stopCh)
|
|
}
|
|
|
|
// getRefreshedPeerList will fetch the latest peer list from the config map annotation
|
|
func getRefreshedPeerList(cml *configMapAnnotations) (*peerList, error) {
|
|
if err := cml.Init(); err != nil {
|
|
return nil, err
|
|
}
|
|
storedPeerList, err := cml.GetPeerList()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return storedPeerList, nil
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
justReclaim bool
|
|
justCheck bool
|
|
justSetNodeStatus bool
|
|
justVersion bool
|
|
justUID bool
|
|
peerName string
|
|
nodeName string
|
|
logLevel string
|
|
runReclaimDaemon bool
|
|
)
|
|
flag.BoolVar(&justReclaim, "reclaim", false, "reclaim IP space from dead peers")
|
|
flag.BoolVar(&runReclaimDaemon, "run-reclaim-daemon", false, "run background process that reclaim IP space from dead peers ")
|
|
flag.BoolVar(&justCheck, "check-peer-new", false, "return success if peer name is not stored in annotation")
|
|
flag.BoolVar(&justSetNodeStatus, "set-node-status", false, "set NodeNetworkUnavailable to false")
|
|
flag.BoolVar(&justVersion, "print-k8s-version", false, "print the Kubernetes version and exit")
|
|
flag.BoolVar(&justUID, "print-uid", false, "print a UID for this installation and exit")
|
|
flag.StringVar(&peerName, "peer-name", "unknown", "name of this Weave Net peer")
|
|
flag.StringVar(&nodeName, "node-name", "unknown", "name of this Kubernetes node")
|
|
flag.StringVar(&logLevel, "log-level", "info", "logging level (debug, info, warning, error)")
|
|
flag.Parse()
|
|
|
|
common.SetLogLevel(logLevel)
|
|
config, err := rest.InClusterConfig()
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not get cluster config: %v", err)
|
|
}
|
|
c, err := kubernetes.NewForConfig(config)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not make Kubernetes connection: %v", err)
|
|
}
|
|
if justCheck {
|
|
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
|
|
exists, err := checkIamInPeerList(cml, c, peerName)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not check peer list: %v", err)
|
|
}
|
|
if exists {
|
|
os.Exit(9)
|
|
} else {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
if justSetNodeStatus {
|
|
err := setNodeNetworkUnavailableFalse(c, nodeName)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] could not set node status: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if justReclaim {
|
|
cml := newConfigMapAnnotations(configMapNamespace, configMapName, c)
|
|
|
|
list, err := addMyselfToPeerList(cml, c, peerName, nodeName)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not update peer list: %v", err)
|
|
}
|
|
common.Log.Infoln("[kube-peers] Added myself to peer list", list)
|
|
|
|
err = reclaimRemovedPeers(c, cml, peerName, nodeName)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Error while reclaiming space: %v", err)
|
|
}
|
|
return
|
|
}
|
|
if justVersion {
|
|
k8sVersion, err := c.Discovery().ServerVersion()
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not get Kubernetes version: %v", err)
|
|
}
|
|
fmt.Println(k8sVersion.String())
|
|
}
|
|
if justUID {
|
|
// use UID of `weave-net` configmap as unique ID of the Kubernetes cluster
|
|
cm, err := c.CoreV1().ConfigMaps(configMapNamespace).Get(context.Background(), configMapName, api.GetOptions{})
|
|
if err != nil {
|
|
common.Log.Fatalf("Unable to fetch ConfigMap %s/%s to infer unique cluster ID", configMapNamespace, configMapName)
|
|
}
|
|
fmt.Println(cm.ObjectMeta.UID)
|
|
}
|
|
if runReclaimDaemon {
|
|
// Handle SIGINT and SIGTERM
|
|
ch := make(chan os.Signal, 1)
|
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
|
stopCh := make(chan struct{})
|
|
rand.Seed(time.Now().UnixNano())
|
|
registerForNodeUpdates(c, stopCh, nodeName, peerName)
|
|
<-ch
|
|
close(stopCh)
|
|
return
|
|
}
|
|
|
|
peers, err := getKubePeers(c, false)
|
|
if err != nil {
|
|
common.Log.Fatalf("[kube-peers] Could not get peers: %v", err)
|
|
}
|
|
for _, node := range peers {
|
|
fmt.Println(node.addr)
|
|
}
|
|
}
|