github.com/grafana/pyroscope@v1.18.0/pkg/metastore/discovery/kuberesolver.go (about)

     1  package discovery
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/url"
     7  	"strconv"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/go-kit/log"
    12  	"github.com/go-kit/log/level"
    13  	"github.com/hashicorp/raft"
    14  	"google.golang.org/grpc/resolver"
    15  
    16  	"github.com/grafana/pyroscope/pkg/metastore/discovery/kuberesolver"
    17  )
    18  
    19  type KubeDiscovery struct {
    20  	l        log.Logger
    21  	resolver *kuberesolver.KResolver
    22  	ti       targetInfo
    23  
    24  	servers []Server
    25  	updLock sync.Mutex
    26  	upd     Updates
    27  }
    28  
    29  func (g *KubeDiscovery) Rediscover() {
    30  
    31  }
    32  
    33  func NewKubeResolverDiscovery(l log.Logger, target string, client kuberesolver.K8sClient) (*KubeDiscovery, error) {
    34  	var err error
    35  	l = log.With(l, "target", target, "component", "kuberesolver-discovery")
    36  	u, err := url.Parse(target)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	gt := resolver.Target{URL: *u}
    41  	ti, err := parseResolverTarget(gt)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	level.Info(l).Log("msg", "parsed target", "target_namespace", ti.namespace, "target_service", ti.service, "target_port", ti.port)
    46  
    47  	res := &KubeDiscovery{
    48  		l:  l,
    49  		ti: ti,
    50  	}
    51  	ku := kuberesolver.ResolveUpdatesFunc(func(e kuberesolver.Endpoints) {
    52  		res.resolved(e)
    53  	})
    54  
    55  	r, err := kuberesolver.Build(l, client, ku, kuberesolver.TargetInfo{
    56  		ServiceName:      ti.service,
    57  		ServiceNamespace: ti.namespace,
    58  	})
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	res.resolver = r
    64  
    65  	return res, nil
    66  }
    67  
    68  func (g *KubeDiscovery) Subscribe(upd Updates) {
    69  	g.updLock.Lock()
    70  	defer g.updLock.Unlock()
    71  	g.upd = upd
    72  	g.upd.Servers(g.servers)
    73  }
    74  
    75  func (g *KubeDiscovery) Close() {
    76  	g.updLock.Lock()
    77  	defer g.updLock.Unlock()
    78  	g.upd = nil
    79  	g.resolver.Close()
    80  }
    81  
    82  func (g *KubeDiscovery) resolved(e kuberesolver.Endpoints) {
    83  	for _, subset := range e.Subsets {
    84  		for _, addr := range subset.Addresses {
    85  			level.Debug(g.l).Log("msg", "resolved", "ip", addr.IP, "targetRef", fmt.Sprintf("%+v", addr.TargetRef))
    86  		}
    87  	}
    88  	g.updLock.Lock()
    89  	defer g.updLock.Unlock()
    90  	g.servers = convertEndpoints(e, g.ti)
    91  	if g.upd != nil {
    92  		g.upd.Servers(g.servers)
    93  	}
    94  }
    95  
    96  func convertEndpoints(e kuberesolver.Endpoints, ti targetInfo) []Server {
    97  	var servers []Server
    98  	for _, ep := range e.Subsets {
    99  		for _, addr := range ep.Addresses {
   100  			for _, port := range ep.Ports {
   101  				if fmt.Sprintf("%d", port.Port) != ti.port {
   102  					continue
   103  				}
   104  				podName := addr.TargetRef.Name
   105  				raftServerId := fmt.Sprintf("%s.%s.%s.svc.cluster.local.:%d", podName, ti.service, ti.namespace, port.Port)
   106  
   107  				servers = append(servers, Server{
   108  					ResolvedAddress: net.JoinHostPort(addr.IP, strconv.Itoa(port.Port)),
   109  					Raft: raft.Server{
   110  						ID:      raft.ServerID(raftServerId),
   111  						Address: raft.ServerAddress(raftServerId),
   112  					},
   113  				})
   114  
   115  			}
   116  		}
   117  	}
   118  	return servers
   119  }
   120  
   121  type targetInfo struct {
   122  	namespace, service, port string
   123  }
   124  
   125  func parseResolverTarget(target resolver.Target) (targetInfo, error) {
   126  	var service, port, namespace string
   127  	if target.URL.Host == "" {
   128  		// kubernetes:///service.namespace:port
   129  		service, port, namespace = splitServicePortNamespace(target.Endpoint())
   130  	} else if target.URL.Port() == "" && target.Endpoint() != "" {
   131  		// kubernetes://namespace/service:port
   132  		service, port, _ = splitServicePortNamespace(target.Endpoint())
   133  		namespace = target.URL.Hostname()
   134  	} else {
   135  		// kubernetes://service.namespace:port
   136  		service, port, namespace = splitServicePortNamespace(target.URL.Host)
   137  	}
   138  
   139  	if service == "" {
   140  		return targetInfo{}, fmt.Errorf("target %s must specify a service", &target.URL)
   141  	}
   142  	if namespace == "" {
   143  		return targetInfo{}, fmt.Errorf("target %s must specify a namespace", &target.URL)
   144  	}
   145  	if port == "" {
   146  		return targetInfo{}, fmt.Errorf("target %s must specify a port", &target.URL)
   147  	}
   148  	return targetInfo{
   149  		service:   service,
   150  		namespace: namespace,
   151  		port:      port,
   152  	}, nil
   153  }
   154  
   155  func splitServicePortNamespace(hpn string) (service, port, namespace string) {
   156  	service = hpn
   157  
   158  	colon := strings.LastIndexByte(service, ':')
   159  	if colon != -1 {
   160  		service, port = service[:colon], service[colon+1:]
   161  	}
   162  
   163  	// we want to split into the service name, namespace, and whatever else is left
   164  	// this will support fully qualified service names, e.g. {service-name}.<namespace>.svc.<cluster-domain-name>.
   165  	// Note that since we lookup the endpoints by service name and namespace, we don't care about the
   166  	// cluster-domain-name, only that we can parse out the service name and namespace properly.
   167  	parts := strings.SplitN(service, ".", 3)
   168  	if len(parts) >= 2 {
   169  		service, namespace = parts[0], parts[1]
   170  	}
   171  
   172  	return
   173  }