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 }