github.com/kubewharf/katalyst-core@v0.5.3/pkg/metaserver/agent/pod/pod.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package pod
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/fsnotify/fsnotify"
    26  	"golang.org/x/time/rate"
    27  	v1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	"k8s.io/klog/v2"
    30  
    31  	"github.com/kubewharf/katalyst-core/pkg/config/agent/global"
    32  	"github.com/kubewharf/katalyst-core/pkg/config/agent/metaserver"
    33  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    34  	"github.com/kubewharf/katalyst-core/pkg/util/cgroup/common"
    35  	"github.com/kubewharf/katalyst-core/pkg/util/general"
    36  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    37  )
    38  
    39  const (
    40  	metricsNamePodCacheSync       = "pod_cache_sync"
    41  	metricsNamePodCacheTotalCount = "pod_cache_total_count"
    42  	metricsNamePodCacheNotFound   = "pod_cache_not_found"
    43  	metricsNamePodFetcherHealth   = "pod_fetcher_health"
    44  )
    45  
    46  type ContextKey string
    47  
    48  const (
    49  	BypassCacheKey  ContextKey = "bypass_cache"
    50  	BypassCacheTrue ContextKey = "true"
    51  
    52  	podFetcherKubeletHealthCheckName = "pod_fetcher_kubelet"
    53  	podFetcherRuntimeHealthCheckName = "pod_fetcher_runtime"
    54  	tolerationTurns                  = 3
    55  )
    56  
    57  type PodFetcher interface {
    58  	KubeletPodFetcher
    59  
    60  	// Run starts the preparing logic to collect pod metadata.
    61  	Run(ctx context.Context)
    62  
    63  	// GetContainerID & GetContainerSpec are used to parse running container info
    64  	GetContainerID(podUID, containerName string) (string, error)
    65  	GetContainerSpec(podUID, containerName string) (*v1.Container, error)
    66  	// GetPod returns Pod by UID
    67  	GetPod(ctx context.Context, podUID string) (*v1.Pod, error)
    68  }
    69  
    70  type podFetcherImpl struct {
    71  	kubeletPodFetcher KubeletPodFetcher
    72  	runtimePodFetcher RuntimePodFetcher
    73  
    74  	kubeletPodsCache     map[string]*v1.Pod
    75  	kubeletPodsCacheLock sync.RWMutex
    76  	runtimePodsCache     map[string]*RuntimePod
    77  	runtimePodsCacheLock sync.RWMutex
    78  
    79  	emitter metrics.MetricEmitter
    80  
    81  	baseConf        *global.BaseConfiguration
    82  	podConf         *metaserver.PodConfiguration
    83  	cgroupRootPaths []string
    84  }
    85  
    86  func NewPodFetcher(baseConf *global.BaseConfiguration, podConf *metaserver.PodConfiguration,
    87  	emitter metrics.MetricEmitter,
    88  ) (PodFetcher, error) {
    89  	runtimePodFetcher, err := NewRuntimePodFetcher(baseConf)
    90  	if err != nil {
    91  		klog.Errorf("init runtime pod fetcher failed: %v", err)
    92  		runtimePodFetcher = nil
    93  	}
    94  
    95  	return &podFetcherImpl{
    96  		kubeletPodFetcher: NewKubeletPodFetcher(baseConf),
    97  		runtimePodFetcher: runtimePodFetcher,
    98  		emitter:           emitter,
    99  		baseConf:          baseConf,
   100  		podConf:           podConf,
   101  		cgroupRootPaths:   common.GetKubernetesCgroupRootPathWithSubSys(common.DefaultSelectedSubsys),
   102  	}, nil
   103  }
   104  
   105  func (w *podFetcherImpl) GetContainerSpec(podUID, containerName string) (*v1.Container, error) {
   106  	if w == nil {
   107  		return nil, fmt.Errorf("get container spec from nil pod fetcher")
   108  	}
   109  
   110  	kubeletPodsCache, err := w.getKubeletPodsCache(context.Background())
   111  	if err != nil {
   112  		return nil, fmt.Errorf("getKubeletPodsCache failed with error: %v", err)
   113  	}
   114  
   115  	if kubeletPodsCache[podUID] == nil {
   116  		return nil, fmt.Errorf("pod of uid: %s isn't found", podUID)
   117  	}
   118  
   119  	for i := range kubeletPodsCache[podUID].Spec.Containers {
   120  		if kubeletPodsCache[podUID].Spec.Containers[i].Name == containerName {
   121  			return kubeletPodsCache[podUID].Spec.Containers[i].DeepCopy(), nil
   122  		}
   123  	}
   124  
   125  	return nil, fmt.Errorf("container: %s isn't found in pod: %s spec", containerName, podUID)
   126  }
   127  
   128  func (w *podFetcherImpl) GetContainerID(podUID, containerName string) (string, error) {
   129  	if w == nil {
   130  		return "", fmt.Errorf("get container id from nil pod fetcher")
   131  	}
   132  
   133  	kubeletPodsCache, err := w.getKubeletPodsCache(context.Background())
   134  	if err != nil {
   135  		return "", fmt.Errorf("getKubeletPodsCache failed with error: %v", err)
   136  	}
   137  
   138  	pod := kubeletPodsCache[podUID]
   139  	if pod == nil {
   140  		return "", fmt.Errorf("pod of uid: %s isn't found", podUID)
   141  	}
   142  
   143  	return native.GetContainerID(pod, containerName)
   144  }
   145  
   146  func (w *podFetcherImpl) Run(ctx context.Context) {
   147  	watcherInfo := general.FileWatcherInfo{
   148  		Path:     w.cgroupRootPaths,
   149  		Filename: "",
   150  		Op:       fsnotify.Create,
   151  	}
   152  
   153  	general.RegisterHeartbeatCheck(podFetcherKubeletHealthCheckName, tolerationTurns*w.podConf.KubeletPodCacheSyncPeriod,
   154  		general.HealthzCheckStateNotReady, tolerationTurns*w.podConf.KubeletPodCacheSyncPeriod)
   155  	general.RegisterHeartbeatCheck(podFetcherRuntimeHealthCheckName, tolerationTurns*w.podConf.RuntimePodCacheSyncPeriod,
   156  		general.HealthzCheckStateNotReady, tolerationTurns*w.podConf.RuntimePodCacheSyncPeriod)
   157  
   158  	watcherCh, err := general.RegisterFileEventWatcher(ctx.Done(), watcherInfo)
   159  	if err != nil {
   160  		klog.Fatalf("register file event watcher failed: %s", err)
   161  	}
   162  
   163  	timer := time.NewTimer(w.podConf.KubeletPodCacheSyncPeriod)
   164  	rateLimiter := rate.NewLimiter(w.podConf.KubeletPodCacheSyncMaxRate, w.podConf.KubeletPodCacheSyncBurstBulk)
   165  
   166  	go func() {
   167  		for {
   168  			select {
   169  			case <-watcherCh:
   170  				if rateLimiter.Allow() {
   171  					w.syncKubeletPod(ctx)
   172  					timer.Reset(w.podConf.KubeletPodCacheSyncPeriod)
   173  				}
   174  			case <-timer.C:
   175  				w.syncKubeletPod(ctx)
   176  				timer.Reset(w.podConf.KubeletPodCacheSyncPeriod)
   177  			case <-ctx.Done():
   178  				klog.Infof("file event watcher stopped")
   179  				klog.Infof("stop timer channel when ctx.Done() has been received")
   180  				timer.Stop()
   181  				return
   182  			}
   183  		}
   184  	}()
   185  
   186  	go wait.UntilWithContext(ctx, w.syncRuntimePod, w.podConf.RuntimePodCacheSyncPeriod)
   187  	go wait.Until(w.checkPodCache, 30*time.Second, ctx.Done())
   188  	<-ctx.Done()
   189  }
   190  
   191  func (w *podFetcherImpl) GetPodList(ctx context.Context, podFilter func(*v1.Pod) bool) ([]*v1.Pod, error) {
   192  	kubeletPodsCache, err := w.getKubeletPodsCache(ctx)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("getKubeletPodsCache failed with error: %v", err)
   195  	}
   196  
   197  	res := make([]*v1.Pod, 0, len(kubeletPodsCache))
   198  	for _, p := range kubeletPodsCache {
   199  		if podFilter != nil && !podFilter(p) {
   200  			continue
   201  		}
   202  		res = append(res, p.DeepCopy())
   203  	}
   204  
   205  	return res, nil
   206  }
   207  
   208  func (w *podFetcherImpl) GetPod(ctx context.Context, podUID string) (*v1.Pod, error) {
   209  	kubeletPodsCache, err := w.getKubeletPodsCache(ctx)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("getKubeletPodsCache failed with error: %v", err)
   212  	}
   213  	if pod, ok := kubeletPodsCache[podUID]; ok {
   214  		return pod, nil
   215  	}
   216  	return nil, fmt.Errorf("failed to find pod by uid %v", podUID)
   217  }
   218  
   219  func (w *podFetcherImpl) getKubeletPodsCache(ctx context.Context) (map[string]*v1.Pod, error) {
   220  	// if current kubelet pod cache is nil or enforce bypass, we sync cache first
   221  	w.kubeletPodsCacheLock.RLock()
   222  	if w.kubeletPodsCache == nil || len(w.kubeletPodsCache) == 0 || ctx.Value(BypassCacheKey) == BypassCacheTrue {
   223  		w.kubeletPodsCacheLock.RUnlock()
   224  		w.syncKubeletPod(ctx)
   225  	} else {
   226  		w.kubeletPodsCacheLock.RUnlock()
   227  	}
   228  
   229  	// the second time checks if the kubelet pod cache is nil, if it is,
   230  	// it means the first sync of the kubelet pod failed and returns an error
   231  	w.kubeletPodsCacheLock.RLock()
   232  	defer w.kubeletPodsCacheLock.RUnlock()
   233  	if w.kubeletPodsCache == nil || len(w.kubeletPodsCache) == 0 {
   234  		return nil, fmt.Errorf("first sync kubelet pod cache failed")
   235  	}
   236  
   237  	return w.kubeletPodsCache, nil
   238  }
   239  
   240  // syncRuntimePod sync local runtime pod cache from runtime pod fetcher.
   241  func (w *podFetcherImpl) syncRuntimePod(_ context.Context) {
   242  	if w.runtimePodFetcher == nil {
   243  		klog.Error("runtime pod fetcher init not success")
   244  		_ = w.emitter.StoreInt64("pod_cache_runtime_init_failed", 1, metrics.MetricTypeNameRaw)
   245  		return
   246  	}
   247  
   248  	runtimePods, err := w.runtimePodFetcher.GetPods(false)
   249  	_ = general.UpdateHealthzStateByError(podFetcherRuntimeHealthCheckName, err)
   250  	if err != nil {
   251  		klog.Errorf("sync runtime pod failed: %s", err)
   252  		_ = w.emitter.StoreInt64(metricsNamePodCacheSync, 1, metrics.MetricTypeNameCount,
   253  			metrics.ConvertMapToTags(map[string]string{
   254  				"source":  "runtime",
   255  				"success": "false",
   256  			})...)
   257  		return
   258  	}
   259  
   260  	_ = w.emitter.StoreInt64(metricsNamePodCacheSync, 1, metrics.MetricTypeNameCount,
   261  		metrics.ConvertMapToTags(map[string]string{
   262  			"source":  "runtime",
   263  			"success": "true",
   264  		})...)
   265  
   266  	runtimePodsCache := make(map[string]*RuntimePod, len(runtimePods))
   267  
   268  	for _, p := range runtimePods {
   269  		runtimePodsCache[string(p.UID)] = p
   270  	}
   271  
   272  	w.runtimePodsCacheLock.Lock()
   273  	w.runtimePodsCache = runtimePodsCache
   274  	w.runtimePodsCacheLock.Unlock()
   275  }
   276  
   277  // syncKubeletPod sync local kubelet pod cache from kubelet pod fetcher.
   278  func (w *podFetcherImpl) syncKubeletPod(ctx context.Context) {
   279  	kubeletPods, err := w.kubeletPodFetcher.GetPodList(ctx, nil)
   280  	_ = general.UpdateHealthzStateByError(podFetcherKubeletHealthCheckName, err)
   281  	if err != nil {
   282  		klog.Errorf("sync kubelet pod failed: %s", err)
   283  		_ = w.emitter.StoreInt64(metricsNamePodCacheSync, 1, metrics.MetricTypeNameCount,
   284  			metrics.ConvertMapToTags(map[string]string{
   285  				"source":  "kubelet",
   286  				"success": "false",
   287  				"reason":  "error",
   288  			})...)
   289  		return
   290  	} else if len(kubeletPods) == 0 {
   291  		klog.Error("kubelet pod is empty")
   292  		_ = w.emitter.StoreInt64(metricsNamePodCacheSync, 1, metrics.MetricTypeNameCount,
   293  			metrics.ConvertMapToTags(map[string]string{
   294  				"source":  "kubelet",
   295  				"success": "false",
   296  				"reason":  "empty",
   297  			})...)
   298  		return
   299  	}
   300  
   301  	_ = w.emitter.StoreInt64(metricsNamePodCacheSync, 1, metrics.MetricTypeNameCount,
   302  		metrics.ConvertMapToTags(map[string]string{
   303  			"source":  "kubelet",
   304  			"success": "true",
   305  		})...)
   306  
   307  	kubeletPodsCache := make(map[string]*v1.Pod, len(kubeletPods))
   308  
   309  	for _, p := range kubeletPods {
   310  		kubeletPodsCache[string(p.GetUID())] = p
   311  	}
   312  
   313  	w.kubeletPodsCacheLock.Lock()
   314  	w.kubeletPodsCache = kubeletPodsCache
   315  	w.kubeletPodsCacheLock.Unlock()
   316  }
   317  
   318  // checkPodCache if the runtime pod and kubelet pod match, and send a metric alert if they don't.
   319  func (w *podFetcherImpl) checkPodCache() {
   320  	w.kubeletPodsCacheLock.RLock()
   321  	kubeletPodsCache := w.kubeletPodsCache
   322  	w.kubeletPodsCacheLock.RUnlock()
   323  
   324  	w.runtimePodsCacheLock.RLock()
   325  	runtimePodsCache := w.runtimePodsCache
   326  	w.runtimePodsCacheLock.RUnlock()
   327  
   328  	_ = w.emitter.StoreInt64(metricsNamePodFetcherHealth, 1, metrics.MetricTypeNameRaw)
   329  
   330  	klog.Infof("total kubelet pod count is %d", len(kubeletPodsCache))
   331  	_ = w.emitter.StoreInt64(metricsNamePodCacheTotalCount, int64(len(kubeletPodsCache)), metrics.MetricTypeNameRaw,
   332  		metrics.ConvertMapToTags(map[string]string{
   333  			"source": "kubelet",
   334  		})...)
   335  
   336  	klog.Infof("total runtime pod count is %d", len(runtimePodsCache))
   337  	_ = w.emitter.StoreInt64(metricsNamePodCacheTotalCount, int64(len(runtimePodsCache)), metrics.MetricTypeNameRaw,
   338  		metrics.ConvertMapToTags(map[string]string{
   339  			"source": "runtime",
   340  		})...)
   341  
   342  	runtimeNotFoundPodCount := 0
   343  	for id, p := range kubeletPodsCache {
   344  		// we only care about running kubelet pods here, because pods in other stages may not exist in runtime
   345  		if _, ok := runtimePodsCache[id]; !ok && p.Status.Phase == v1.PodRunning {
   346  			klog.Warningf("running kubelet pod %s/%s with uid %s runtime not found", p.Namespace, p.Name, p.UID)
   347  			runtimeNotFoundPodCount += 1
   348  		}
   349  	}
   350  	_ = w.emitter.StoreInt64(metricsNamePodCacheNotFound, int64(runtimeNotFoundPodCount), metrics.MetricTypeNameRaw,
   351  		metrics.ConvertMapToTags(map[string]string{
   352  			"source": "runtime",
   353  		})...)
   354  
   355  	kubeletNotFoundPodCount := 0
   356  	for id, p := range runtimePodsCache {
   357  		if _, ok := kubeletPodsCache[id]; !ok {
   358  			klog.Warningf("runtime pod %s/%s with uid %s kubelet not found", p.Namespace, p.Name, p.UID)
   359  			kubeletNotFoundPodCount += 1
   360  		}
   361  	}
   362  	_ = w.emitter.StoreInt64(metricsNamePodCacheNotFound, int64(kubeletNotFoundPodCount), metrics.MetricTypeNameRaw,
   363  		metrics.ConvertMapToTags(map[string]string{
   364  			"source": "kubelet",
   365  		})...)
   366  }