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 }