github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/k8smeta/k8s_meta_http_server.go (about) 1 package k8smeta 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "os" 8 "strconv" 9 "strings" 10 "time" 11 12 app "k8s.io/api/apps/v1" 13 v1 "k8s.io/api/core/v1" 14 "k8s.io/apimachinery/pkg/labels" 15 16 "github.com/alibaba/ilogtail/pkg/logger" 17 ) 18 19 type requestBody struct { 20 Keys []string `json:"keys"` 21 } 22 23 type metadataHandler struct { 24 metaManager *MetaManager 25 } 26 27 func newMetadataHandler(metaManager *MetaManager) *metadataHandler { 28 metadataHandler := &metadataHandler{ 29 metaManager: metaManager, 30 } 31 return metadataHandler 32 } 33 34 func (m *metadataHandler) K8sServerRun(stopCh <-chan struct{}) error { 35 defer panicRecover() 36 portEnv := os.Getenv("KUBERNETES_METADATA_PORT") 37 if len(portEnv) == 0 { 38 portEnv = "9000" 39 } 40 port, err := strconv.Atoi(portEnv) 41 if err != nil { 42 port = 9000 43 } 44 server := &http.Server{ //nolint:gosec 45 Addr: ":" + strconv.Itoa(port), 46 } 47 mux := http.NewServeMux() 48 49 mux.HandleFunc("/metadata/ipport", m.handler(m.handlePodMetaByIPPort)) 50 mux.HandleFunc("/metadata/containerid", m.handler(m.handlePodMetaByContainerID)) 51 mux.HandleFunc("/metadata/host", m.handler(m.handlePodMetaByHostIP)) 52 server.Handler = mux 53 logger.Info(context.Background(), "k8s meta server", "started", "port", port) 54 go func() { 55 defer panicRecover() 56 _ = server.ListenAndServe() 57 }() 58 <-stopCh 59 return nil 60 } 61 62 func (m *metadataHandler) handler(handleFunc func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc { 63 return func(w http.ResponseWriter, r *http.Request) { 64 defer panicRecover() 65 if !m.metaManager.IsReady() { 66 w.WriteHeader(http.StatusServiceUnavailable) 67 return 68 } 69 startTime := time.Now() 70 m.metaManager.httpRequestCount.Add(1) 71 handleFunc(w, r) 72 latency := time.Since(startTime).Milliseconds() 73 m.metaManager.httpAvgDelayMs.Add(latency) 74 m.metaManager.httpMaxDelayMs.Set(float64(latency)) 75 } 76 } 77 78 func (m *metadataHandler) handlePodMetaByIPPort(w http.ResponseWriter, r *http.Request) { 79 defer r.Body.Close() 80 var rBody requestBody 81 // Decode the JSON data into the struct 82 err := json.NewDecoder(r.Body).Decode(&rBody) 83 if err != nil { 84 http.Error(w, "Error parsing JSON: "+err.Error(), http.StatusBadRequest) 85 return 86 } 87 88 // Get the metadata 89 metadata := make(map[string]*PodMetadata) 90 for _, key := range rBody.Keys { 91 ipPort := strings.Split(key, ":") 92 if len(ipPort) == 0 { 93 continue 94 } 95 ip := ipPort[0] 96 port := int32(0) 97 if len(ipPort) > 1 { 98 tmp, _ := strconv.ParseInt(ipPort[1], 10, 32) 99 port = int32(tmp) 100 } 101 objs := m.metaManager.cacheMap[POD].Get([]string{ip}) 102 if len(objs) == 0 { 103 podMetadata := m.findPodByServiceIPPort(ip, port) 104 if podMetadata != nil { 105 metadata[key] = podMetadata 106 } 107 } else { 108 podMetadata := m.findPodByPodIPPort(ip, port, objs) 109 if podMetadata != nil { 110 metadata[key] = podMetadata 111 } 112 } 113 } 114 wrapperResponse(w, metadata) 115 } 116 117 func (m *metadataHandler) findPodByServiceIPPort(ip string, port int32) *PodMetadata { 118 // try service IP 119 svcObjs := m.metaManager.cacheMap[SERVICE].Get([]string{ip}) 120 if len(svcObjs) == 0 { 121 return nil 122 } 123 var service *v1.Service 124 if port != 0 { 125 for _, obj := range svcObjs[ip] { 126 svc, ok := obj.Raw.(*v1.Service) 127 if !ok { 128 continue 129 } 130 portMatch := false 131 for _, realPort := range svc.Spec.Ports { 132 if realPort.Port == port { 133 portMatch = true 134 break 135 } 136 } 137 if !portMatch { 138 continue 139 } 140 service = svc 141 break 142 } 143 if service == nil { 144 return nil 145 } 146 } else { 147 for _, obj := range svcObjs[ip] { 148 // if no port specified, use the first service 149 svc, ok := obj.Raw.(*v1.Service) 150 if !ok { 151 continue 152 } 153 service = svc 154 break 155 } 156 } 157 158 // find pod by service 159 lm := newLabelMatcher(service, labels.SelectorFromSet(service.Spec.Selector)) 160 podObjs := m.metaManager.cacheMap[POD].Filter(func(ow *ObjectWrapper) bool { 161 pod, ok := ow.Raw.(*v1.Pod) 162 if !ok { 163 return false 164 } 165 if pod.Namespace != service.Namespace { 166 return false 167 } 168 return lm.selector.Matches(labels.Set(pod.Labels)) 169 }, 1) 170 if len(podObjs) != 0 { 171 podMetadata := m.convertObj2PodResponse(podObjs[0]) 172 if podMetadata == nil { 173 return nil 174 } 175 podMetadata.ServiceName = service.Name 176 return podMetadata 177 } 178 return nil 179 } 180 181 func (m *metadataHandler) findPodByPodIPPort(ip string, port int32, objs map[string][]*ObjectWrapper) *PodMetadata { 182 if port != 0 { 183 for _, obj := range objs[ip] { 184 pod, ok := obj.Raw.(*v1.Pod) 185 if !ok { 186 continue 187 } 188 for _, container := range pod.Spec.Containers { 189 portMatch := false 190 for _, realPort := range container.Ports { 191 if realPort.ContainerPort == port { 192 portMatch = true 193 break 194 } 195 } 196 if !portMatch { 197 continue 198 } 199 podMetadata := m.convertObj2PodResponse(obj) 200 return podMetadata 201 } 202 } 203 } else { 204 // without port 205 if objs[ip] == nil || len(objs[ip]) == 0 { 206 return nil 207 } 208 podMetadata := m.convertObj2PodResponse(objs[ip][0]) 209 return podMetadata 210 } 211 return nil 212 } 213 214 func (m *metadataHandler) convertObj2PodResponse(obj *ObjectWrapper) *PodMetadata { 215 pod, ok := obj.Raw.(*v1.Pod) 216 if !ok { 217 return nil 218 } 219 podMetadata := m.getCommonPodMetadata(pod) 220 containerIDs := make([]string, 0) 221 for _, container := range pod.Status.ContainerStatuses { 222 containerIDs = append(containerIDs, truncateContainerID(container.ContainerID)) 223 } 224 podMetadata.ContainerIDs = containerIDs 225 podMetadata.PodIP = pod.Status.PodIP 226 return podMetadata 227 } 228 229 func (m *metadataHandler) handlePodMetaByContainerID(w http.ResponseWriter, r *http.Request) { 230 defer r.Body.Close() 231 var rBody requestBody 232 // Decode the JSON data into the struct 233 err := json.NewDecoder(r.Body).Decode(&rBody) 234 if err != nil { 235 http.Error(w, "Error parsing JSON: "+err.Error(), http.StatusBadRequest) 236 return 237 } 238 239 // Get the metadata 240 metadata := make(map[string]*PodMetadata) 241 objs := m.metaManager.cacheMap[POD].Get(rBody.Keys) 242 for key, obj := range objs { 243 podMetadata := m.convertObjs2ContainerResponse(obj) 244 if len(podMetadata) > 1 { 245 logger.Warning(context.Background(), "Multiple pods found for unique container ID", key) 246 } 247 if len(podMetadata) > 0 { 248 metadata[key] = podMetadata[0] 249 } 250 } 251 wrapperResponse(w, metadata) 252 } 253 254 func (m *metadataHandler) convertObjs2ContainerResponse(objs []*ObjectWrapper) []*PodMetadata { 255 metadatas := make([]*PodMetadata, 0) 256 for _, obj := range objs { 257 pod, ok := obj.Raw.(*v1.Pod) 258 if !ok { 259 continue 260 } 261 podMetadata := m.getCommonPodMetadata(pod) 262 podMetadata.PodIP = pod.Status.PodIP 263 metadatas = append(metadatas, podMetadata) 264 } 265 return metadatas 266 } 267 268 func (m *metadataHandler) handlePodMetaByHostIP(w http.ResponseWriter, r *http.Request) { 269 defer r.Body.Close() 270 var rBody requestBody 271 // Decode the JSON data into the struct 272 err := json.NewDecoder(r.Body).Decode(&rBody) 273 if err != nil { 274 http.Error(w, "Error parsing JSON: "+err.Error(), http.StatusBadRequest) 275 return 276 } 277 278 // Get the metadata 279 metadata := make(map[string]*PodMetadata) 280 queryKeys := make([]string, len(rBody.Keys)) 281 for _, key := range rBody.Keys { 282 queryKeys = append(queryKeys, addHostIPIndexPrefex(key)) 283 } 284 objs := m.metaManager.cacheMap[POD].Get(queryKeys) 285 for _, obj := range objs { 286 podMetadata := m.convertObjs2HostResponse(obj) 287 for i, meta := range podMetadata { 288 pod, ok := obj[i].Raw.(*v1.Pod) 289 if !ok { 290 continue 291 } 292 metadata[string(pod.UID)] = meta 293 } 294 } 295 wrapperResponse(w, metadata) 296 } 297 298 func (m *metadataHandler) convertObjs2HostResponse(objs []*ObjectWrapper) []*PodMetadata { 299 metadatas := make([]*PodMetadata, 0) 300 for _, obj := range objs { 301 pod, ok := obj.Raw.(*v1.Pod) 302 if !ok { 303 continue 304 } 305 podMetadata := m.getCommonPodMetadata(pod) 306 containerIDs := make([]string, 0) 307 for _, container := range pod.Status.ContainerStatuses { 308 containerIDs = append(containerIDs, truncateContainerID(container.ContainerID)) 309 } 310 podMetadata.ContainerIDs = containerIDs 311 podMetadata.PodIP = pod.Status.PodIP 312 metadatas = append(metadatas, podMetadata) 313 } 314 return metadatas 315 } 316 317 func (m *metadataHandler) getCommonPodMetadata(pod *v1.Pod) *PodMetadata { 318 images := make(map[string]string) 319 envs := make(map[string]string) 320 for _, container := range pod.Spec.Containers { 321 images[container.Name] = container.Image 322 for _, env := range container.Env { 323 envs[env.Name] = env.Value 324 } 325 } 326 podMetadata := &PodMetadata{ 327 PodName: pod.Name, 328 StartTime: pod.CreationTimestamp.Time.Unix(), 329 Namespace: pod.Namespace, 330 Labels: pod.Labels, 331 Images: images, 332 Envs: envs, 333 IsDeleted: false, 334 } 335 if len(pod.GetOwnerReferences()) == 0 { 336 podMetadata.WorkloadName = "" 337 podMetadata.WorkloadKind = "" 338 logger.Warning(context.Background(), "Pod has no owner", pod.Name) 339 } else { 340 reference := pod.GetOwnerReferences()[0] 341 podMetadata.WorkloadName = reference.Name 342 podMetadata.WorkloadKind = strings.ToLower(reference.Kind) 343 if podMetadata.WorkloadKind == REPLICASET { 344 // replicaset -> deployment 345 replicasetKey := generateNameWithNamespaceKey(pod.Namespace, podMetadata.WorkloadName) 346 replicasets := m.metaManager.cacheMap[REPLICASET].Get([]string{replicasetKey}) 347 for _, replicaset := range replicasets[replicasetKey] { 348 replicaset, ok := replicaset.Raw.(*app.ReplicaSet) 349 if !ok { 350 continue 351 } 352 if len(replicaset.OwnerReferences) > 0 { 353 podMetadata.WorkloadName = replicaset.OwnerReferences[0].Name 354 podMetadata.WorkloadKind = strings.ToLower(replicaset.OwnerReferences[0].Kind) 355 break 356 } 357 } 358 if podMetadata.WorkloadKind == "replicaset" { 359 logger.Warning(context.Background(), "ReplicaSet has no owner", podMetadata.WorkloadName) 360 } 361 } 362 } 363 return podMetadata 364 } 365 366 func truncateContainerID(containerID string) string { 367 sep := "://" 368 separated := strings.SplitN(containerID, sep, 2) 369 if len(separated) < 2 { 370 return "" 371 } 372 return separated[1] 373 } 374 375 func wrapperResponse(w http.ResponseWriter, metadata map[string]*PodMetadata) { 376 // Convert metadata to JSON 377 metadataJSON, err := json.Marshal(metadata) 378 if err != nil { 379 http.Error(w, "Error converting metadata to JSON: "+err.Error(), http.StatusInternalServerError) 380 return 381 } 382 // Set the response content type to application/json 383 w.Header().Set("Content-Type", "application/json") 384 // Write the metadata JSON to the response body 385 _, err = w.Write(metadataJSON) 386 if err != nil { 387 http.Error(w, "Error writing response: "+err.Error(), http.StatusInternalServerError) 388 return 389 } 390 }