From 12add722ae598a913b6a751eb912abfaf9cdeb49 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Mon, 31 Jul 2017 18:14:56 +0100 Subject: [PATCH] synthesise k8s service network from service IPs This prevents cluttering host.LocalNetworks with lots of /32 addresses. These were unsightly and rather distracting in the UI. They also bloated the report and slowed down server-side rendering. Fixes #2748. --- probe/kubernetes/reporter.go | 38 +++++++++++++++++++++++------------- report/networks.go | 29 +++++++++++++++++++++++++++ report/networks_test.go | 16 +++++++++++++++ 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index 519c5ba115..bda02378dc 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -2,6 +2,7 @@ package kubernetes import ( "fmt" + "net" "strings" "k8s.io/apimachinery/pkg/labels" @@ -294,23 +295,32 @@ func (r *Reporter) serviceTopology() (report.Topology, []Service, error) { return result, services, err } -// FIXME: Hideous hack to remove persistent-connection edges to virtual service -// IPs attributed to the internet. We add each service IP as a /32 network -// (the global service-cluster-ip-range is not exposed by the API -// server so we treat each IP as a /32 network see -// https://github.com/kubernetes/kubernetes/issues/25533). -// The right way of fixing this is performing DNAT mapping on persistent -// connections for which we don't have a robust solution -// (see https://github.com/weaveworks/scope/issues/1491) +// FIXME: Hideous hack to remove persistent-connection edges to +// virtual service IPs attributed to the internet. The global +// service-cluster-ip-range is not exposed by the API server (see +// https://github.com/kubernetes/kubernetes/issues/25533), so instead +// we synthesise it by computing the smallest network that contains +// all service IPs. That network may be smaller than the actual range +// but that is ok, since in the end all we care about is that it +// contains all the service IPs. +// +// The right way of fixing this is performing DNAT mapping on +// persistent connections for which we don't have a robust solution +// (see https://github.com/weaveworks/scope/issues/1491). func (r *Reporter) hostTopology(services []Service) report.Topology { - localNetworks := report.MakeStringSet() + serviceIPs := make([]net.IP, 0, len(services)) for _, service := range services { - localNetworks = localNetworks.Add(service.ClusterIP() + "/32") + if ip := net.ParseIP(service.ClusterIP()).To4(); ip != nil { + serviceIPs = append(serviceIPs, ip) + } + } + serviceNetwork := report.ContainingIPv4Network(serviceIPs) + if serviceNetwork == nil { + return report.MakeTopology() } - node := report.MakeNode(report.MakeHostNodeID(r.hostID)) - node = node.WithSets(report.MakeSets(). - Add(host.LocalNetworks, localNetworks)) - return report.MakeTopology().AddNode(node) + return report.MakeTopology().AddNode( + report.MakeNode(report.MakeHostNodeID(r.hostID)). + WithSets(report.MakeSets().Add(host.LocalNetworks, report.MakeStringSet(serviceNetwork.String())))) } func (r *Reporter) deploymentTopology(probeID string) (report.Topology, []Deployment, error) { diff --git a/report/networks.go b/report/networks.go index 4cd9d4a9b0..6feb1ceb4c 100644 --- a/report/networks.go +++ b/report/networks.go @@ -1,6 +1,7 @@ package report import ( + "encoding/binary" "net" "strings" @@ -105,3 +106,31 @@ func ipv4Nets(addrs []net.Addr) []*net.IPNet { } return nets } + +// ContainingIPv4Network determines the smallest network containing +// the given IPv4 addresses. When no addresses are specified, nil is +// returned. +func ContainingIPv4Network(ips []net.IP) *net.IPNet { + if len(ips) == 0 { + return nil + } + network := net.IPNet{ + IP: ips[0], + Mask: net.CIDRMask(net.IPv4len*8, net.IPv4len*8), + } + for _, ip := range ips[1:] { + network.Mask = net.CIDRMask(commonIPv4PrefixLen(network.IP, ip), net.IPv4len*8) + network.IP = network.IP.Mask(network.Mask) + } + return &network +} + +func commonIPv4PrefixLen(a, b net.IP) (cpl int) { + x := binary.BigEndian.Uint32(a) + y := binary.BigEndian.Uint32(b) + for cpl = 32; x != y; cpl-- { + x >>= 1 + y >>= 1 + } + return +} diff --git a/report/networks_test.go b/report/networks_test.go index 531a5ff0e3..82af2bcd6c 100644 --- a/report/networks_test.go +++ b/report/networks_test.go @@ -4,6 +4,7 @@ import ( "net" "testing" + "github.com/stretchr/testify/assert" "github.com/weaveworks/scope/report" ) @@ -23,3 +24,18 @@ func TestContains(t *testing.T) { t.Errorf("10.0.0.1 in %v", networks) } } + +func TestContainingIPv4Network(t *testing.T) { + assert.Nil(t, containingIPv4Networks([]string{})) + assert.Equal(t, "10.0.0.1/32", containingIPv4Networks([]string{"10.0.0.1"}).String()) + assert.Equal(t, "10.0.0.0/17", containingIPv4Networks([]string{"10.0.0.1", "10.0.2.55", "10.0.106.48"}).String()) + assert.Equal(t, "0.0.0.0/0", containingIPv4Networks([]string{"10.0.0.1", "192.168.0.1"}).String()) +} + +func containingIPv4Networks(ipstrings []string) *net.IPNet { + ips := make([]net.IP, len(ipstrings)) + for i, ip := range ipstrings { + ips[i] = net.ParseIP(ip).To4() + } + return report.ContainingIPv4Network(ips) +}