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  }