k8s.io/kubernetes@v1.29.3/pkg/kubelet/server/server.go (about) 1 /* 2 Copyright 2014 The Kubernetes 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 server 18 19 import ( 20 "context" 21 "crypto/tls" 22 "fmt" 23 "io" 24 "net" 25 "net/http" 26 "net/http/pprof" 27 "net/url" 28 "os" 29 "reflect" 30 goruntime "runtime" 31 "strconv" 32 "strings" 33 "time" 34 35 "github.com/emicklei/go-restful/v3" 36 cadvisormetrics "github.com/google/cadvisor/container" 37 cadvisorapi "github.com/google/cadvisor/info/v1" 38 cadvisorv2 "github.com/google/cadvisor/info/v2" 39 "github.com/google/cadvisor/metrics" 40 "go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful" 41 oteltrace "go.opentelemetry.io/otel/trace" 42 "google.golang.org/grpc" 43 "k8s.io/klog/v2" 44 "k8s.io/kubernetes/pkg/kubelet/metrics/collectors" 45 "k8s.io/utils/clock" 46 netutils "k8s.io/utils/net" 47 48 v1 "k8s.io/api/core/v1" 49 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 50 "k8s.io/apimachinery/pkg/runtime" 51 "k8s.io/apimachinery/pkg/runtime/schema" 52 "k8s.io/apimachinery/pkg/types" 53 "k8s.io/apimachinery/pkg/util/proxy" 54 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 55 "k8s.io/apimachinery/pkg/util/sets" 56 "k8s.io/apiserver/pkg/authentication/authenticator" 57 "k8s.io/apiserver/pkg/authorization/authorizer" 58 "k8s.io/apiserver/pkg/server/healthz" 59 "k8s.io/apiserver/pkg/server/httplog" 60 "k8s.io/apiserver/pkg/server/routes" 61 utilfeature "k8s.io/apiserver/pkg/util/feature" 62 "k8s.io/apiserver/pkg/util/flushwriter" 63 "k8s.io/component-base/configz" 64 "k8s.io/component-base/logs" 65 compbasemetrics "k8s.io/component-base/metrics" 66 metricsfeatures "k8s.io/component-base/metrics/features" 67 "k8s.io/component-base/metrics/legacyregistry" 68 "k8s.io/component-base/metrics/prometheus/slis" 69 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 70 podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1" 71 podresourcesapiv1alpha1 "k8s.io/kubelet/pkg/apis/podresources/v1alpha1" 72 "k8s.io/kubelet/pkg/cri/streaming" 73 "k8s.io/kubelet/pkg/cri/streaming/portforward" 74 remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand" 75 kubelettypes "k8s.io/kubelet/pkg/types" 76 "k8s.io/kubernetes/pkg/api/legacyscheme" 77 api "k8s.io/kubernetes/pkg/apis/core" 78 "k8s.io/kubernetes/pkg/apis/core/v1/validation" 79 "k8s.io/kubernetes/pkg/features" 80 kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config" 81 apisgrpc "k8s.io/kubernetes/pkg/kubelet/apis/grpc" 82 "k8s.io/kubernetes/pkg/kubelet/apis/podresources" 83 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 84 "k8s.io/kubernetes/pkg/kubelet/prober" 85 servermetrics "k8s.io/kubernetes/pkg/kubelet/server/metrics" 86 "k8s.io/kubernetes/pkg/kubelet/server/stats" 87 "k8s.io/kubernetes/pkg/kubelet/util" 88 ) 89 90 func init() { 91 utilruntime.Must(metricsfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) 92 } 93 94 const ( 95 metricsPath = "/metrics" 96 cadvisorMetricsPath = "/metrics/cadvisor" 97 resourceMetricsPath = "/metrics/resource" 98 proberMetricsPath = "/metrics/probes" 99 statsPath = "/stats/" 100 logsPath = "/logs/" 101 pprofBasePath = "/debug/pprof/" 102 debugFlagPath = "/debug/flags/v" 103 ) 104 105 // Server is a http.Handler which exposes kubelet functionality over HTTP. 106 type Server struct { 107 auth AuthInterface 108 host HostInterface 109 restfulCont containerInterface 110 metricsBuckets sets.String 111 metricsMethodBuckets sets.String 112 resourceAnalyzer stats.ResourceAnalyzer 113 } 114 115 // TLSOptions holds the TLS options. 116 type TLSOptions struct { 117 Config *tls.Config 118 CertFile string 119 KeyFile string 120 } 121 122 // containerInterface defines the restful.Container functions used on the root container 123 type containerInterface interface { 124 Add(service *restful.WebService) *restful.Container 125 Handle(path string, handler http.Handler) 126 Filter(filter restful.FilterFunction) 127 ServeHTTP(w http.ResponseWriter, r *http.Request) 128 RegisteredWebServices() []*restful.WebService 129 130 // RegisteredHandlePaths returns the paths of handlers registered directly with the container (non-web-services) 131 // Used to test filters are being applied on non-web-service handlers 132 RegisteredHandlePaths() []string 133 } 134 135 // filteringContainer delegates all Handle(...) calls to Container.HandleWithFilter(...), 136 // so we can ensure restful.FilterFunctions are used for all handlers 137 type filteringContainer struct { 138 *restful.Container 139 140 registeredHandlePaths []string 141 } 142 143 func (a *filteringContainer) Handle(path string, handler http.Handler) { 144 a.HandleWithFilter(path, handler) 145 a.registeredHandlePaths = append(a.registeredHandlePaths, path) 146 } 147 func (a *filteringContainer) RegisteredHandlePaths() []string { 148 return a.registeredHandlePaths 149 } 150 151 // ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet. 152 func ListenAndServeKubeletServer( 153 host HostInterface, 154 resourceAnalyzer stats.ResourceAnalyzer, 155 kubeCfg *kubeletconfiginternal.KubeletConfiguration, 156 tlsOptions *TLSOptions, 157 auth AuthInterface, 158 tp oteltrace.TracerProvider) { 159 160 address := netutils.ParseIPSloppy(kubeCfg.Address) 161 port := uint(kubeCfg.Port) 162 klog.InfoS("Starting to listen", "address", address, "port", port) 163 handler := NewServer(host, resourceAnalyzer, auth, tp, kubeCfg) 164 s := &http.Server{ 165 Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), 166 Handler: &handler, 167 IdleTimeout: 90 * time.Second, // matches http.DefaultTransport keep-alive timeout 168 ReadTimeout: 4 * 60 * time.Minute, 169 WriteTimeout: 4 * 60 * time.Minute, 170 MaxHeaderBytes: 1 << 20, 171 } 172 173 if tlsOptions != nil { 174 s.TLSConfig = tlsOptions.Config 175 // Passing empty strings as the cert and key files means no 176 // cert/keys are specified and GetCertificate in the TLSConfig 177 // should be called instead. 178 if err := s.ListenAndServeTLS(tlsOptions.CertFile, tlsOptions.KeyFile); err != nil { 179 klog.ErrorS(err, "Failed to listen and serve") 180 os.Exit(1) 181 } 182 } else if err := s.ListenAndServe(); err != nil { 183 klog.ErrorS(err, "Failed to listen and serve") 184 os.Exit(1) 185 } 186 } 187 188 // ListenAndServeKubeletReadOnlyServer initializes a server to respond to HTTP network requests on the Kubelet. 189 func ListenAndServeKubeletReadOnlyServer( 190 host HostInterface, 191 resourceAnalyzer stats.ResourceAnalyzer, 192 address net.IP, 193 port uint) { 194 klog.InfoS("Starting to listen read-only", "address", address, "port", port) 195 // TODO: https://github.com/kubernetes/kubernetes/issues/109829 tracer should use WithPublicEndpoint 196 s := NewServer(host, resourceAnalyzer, nil, oteltrace.NewNoopTracerProvider(), nil) 197 198 server := &http.Server{ 199 Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), 200 Handler: &s, 201 IdleTimeout: 90 * time.Second, // matches http.DefaultTransport keep-alive timeout 202 ReadTimeout: 4 * 60 * time.Minute, 203 WriteTimeout: 4 * 60 * time.Minute, 204 MaxHeaderBytes: 1 << 20, 205 } 206 207 if err := server.ListenAndServe(); err != nil { 208 klog.ErrorS(err, "Failed to listen and serve") 209 os.Exit(1) 210 } 211 } 212 213 type PodResourcesProviders struct { 214 Pods podresources.PodsProvider 215 Devices podresources.DevicesProvider 216 Cpus podresources.CPUsProvider 217 Memory podresources.MemoryProvider 218 } 219 220 // ListenAndServePodResources initializes a gRPC server to serve the PodResources service 221 func ListenAndServePodResources(endpoint string, providers podresources.PodResourcesProviders) { 222 server := grpc.NewServer(apisgrpc.WithRateLimiter("podresources", podresources.DefaultQPS, podresources.DefaultBurstTokens)) 223 224 podresourcesapiv1alpha1.RegisterPodResourcesListerServer(server, podresources.NewV1alpha1PodResourcesServer(providers)) 225 podresourcesapi.RegisterPodResourcesListerServer(server, podresources.NewV1PodResourcesServer(providers)) 226 227 l, err := util.CreateListener(endpoint) 228 if err != nil { 229 klog.ErrorS(err, "Failed to create listener for podResources endpoint") 230 os.Exit(1) 231 } 232 233 klog.InfoS("Starting to serve the podresources API", "endpoint", endpoint) 234 if err := server.Serve(l); err != nil { 235 klog.ErrorS(err, "Failed to serve") 236 os.Exit(1) 237 } 238 } 239 240 // AuthInterface contains all methods required by the auth filters 241 type AuthInterface interface { 242 authenticator.Request 243 authorizer.RequestAttributesGetter 244 authorizer.Authorizer 245 } 246 247 // HostInterface contains all the kubelet methods required by the server. 248 // For testability. 249 type HostInterface interface { 250 stats.Provider 251 GetVersionInfo() (*cadvisorapi.VersionInfo, error) 252 GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) 253 GetRunningPods(ctx context.Context) ([]*v1.Pod, error) 254 RunInContainer(ctx context.Context, name string, uid types.UID, container string, cmd []string) ([]byte, error) 255 CheckpointContainer(ctx context.Context, podUID types.UID, podFullName, containerName string, options *runtimeapi.CheckpointContainerRequest) error 256 GetKubeletContainerLogs(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error 257 ServeLogs(w http.ResponseWriter, req *http.Request) 258 ResyncInterval() time.Duration 259 GetHostname() string 260 LatestLoopEntryTime() time.Time 261 GetExec(ctx context.Context, podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) 262 GetAttach(ctx context.Context, podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) 263 GetPortForward(ctx context.Context, podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error) 264 ListMetricDescriptors(ctx context.Context) ([]*runtimeapi.MetricDescriptor, error) 265 ListPodSandboxMetrics(ctx context.Context) ([]*runtimeapi.PodSandboxMetrics, error) 266 } 267 268 // NewServer initializes and configures a kubelet.Server object to handle HTTP requests. 269 func NewServer( 270 host HostInterface, 271 resourceAnalyzer stats.ResourceAnalyzer, 272 auth AuthInterface, 273 tp oteltrace.TracerProvider, 274 kubeCfg *kubeletconfiginternal.KubeletConfiguration) Server { 275 276 server := Server{ 277 host: host, 278 resourceAnalyzer: resourceAnalyzer, 279 auth: auth, 280 restfulCont: &filteringContainer{Container: restful.NewContainer()}, 281 metricsBuckets: sets.NewString(), 282 metricsMethodBuckets: sets.NewString("OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT"), 283 } 284 if auth != nil { 285 server.InstallAuthFilter() 286 } 287 if utilfeature.DefaultFeatureGate.Enabled(features.KubeletTracing) { 288 server.InstallTracingFilter(tp) 289 } 290 server.InstallDefaultHandlers() 291 if kubeCfg != nil && kubeCfg.EnableDebuggingHandlers { 292 server.InstallDebuggingHandlers() 293 // To maintain backward compatibility serve logs and pprof only when enableDebuggingHandlers is also enabled 294 // see https://github.com/kubernetes/kubernetes/pull/87273 295 server.InstallSystemLogHandler(kubeCfg.EnableSystemLogHandler, kubeCfg.EnableSystemLogQuery) 296 server.InstallProfilingHandler(kubeCfg.EnableProfilingHandler, kubeCfg.EnableContentionProfiling) 297 server.InstallDebugFlagsHandler(kubeCfg.EnableDebugFlagsHandler) 298 } else { 299 server.InstallDebuggingDisabledHandlers() 300 } 301 return server 302 } 303 304 // InstallAuthFilter installs authentication filters with the restful Container. 305 func (s *Server) InstallAuthFilter() { 306 s.restfulCont.Filter(func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { 307 // Authenticate 308 info, ok, err := s.auth.AuthenticateRequest(req.Request) 309 if err != nil { 310 klog.ErrorS(err, "Unable to authenticate the request due to an error") 311 resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") 312 return 313 } 314 if !ok { 315 resp.WriteErrorString(http.StatusUnauthorized, "Unauthorized") 316 return 317 } 318 319 // Get authorization attributes 320 attrs := s.auth.GetRequestAttributes(info.User, req.Request) 321 322 // Authorize 323 decision, _, err := s.auth.Authorize(req.Request.Context(), attrs) 324 if err != nil { 325 klog.ErrorS(err, "Authorization error", "user", attrs.GetUser().GetName(), "verb", attrs.GetVerb(), "resource", attrs.GetResource(), "subresource", attrs.GetSubresource()) 326 msg := fmt.Sprintf("Authorization error (user=%s, verb=%s, resource=%s, subresource=%s)", attrs.GetUser().GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) 327 resp.WriteErrorString(http.StatusInternalServerError, msg) 328 return 329 } 330 if decision != authorizer.DecisionAllow { 331 klog.V(2).InfoS("Forbidden", "user", attrs.GetUser().GetName(), "verb", attrs.GetVerb(), "resource", attrs.GetResource(), "subresource", attrs.GetSubresource()) 332 msg := fmt.Sprintf("Forbidden (user=%s, verb=%s, resource=%s, subresource=%s)", attrs.GetUser().GetName(), attrs.GetVerb(), attrs.GetResource(), attrs.GetSubresource()) 333 resp.WriteErrorString(http.StatusForbidden, msg) 334 return 335 } 336 337 // Continue 338 chain.ProcessFilter(req, resp) 339 }) 340 } 341 342 // InstallTracingFilter installs OpenTelemetry tracing filter with the restful Container. 343 func (s *Server) InstallTracingFilter(tp oteltrace.TracerProvider) { 344 s.restfulCont.Filter(otelrestful.OTelFilter("kubelet", otelrestful.WithTracerProvider(tp))) 345 } 346 347 // addMetricsBucketMatcher adds a regexp matcher and the relevant bucket to use when 348 // it matches. Please be aware this is not thread safe and should not be used dynamically 349 func (s *Server) addMetricsBucketMatcher(bucket string) { 350 s.metricsBuckets.Insert(bucket) 351 } 352 353 // getMetricBucket find the appropriate metrics reporting bucket for the given path 354 func (s *Server) getMetricBucket(path string) string { 355 root := getURLRootPath(path) 356 if s.metricsBuckets.Has(root) { 357 return root 358 } 359 return "other" 360 } 361 362 // getMetricMethodBucket checks for unknown or invalid HTTP verbs 363 func (s *Server) getMetricMethodBucket(method string) string { 364 if s.metricsMethodBuckets.Has(method) { 365 return method 366 } 367 return "other" 368 } 369 370 // InstallDefaultHandlers registers the default set of supported HTTP request 371 // patterns with the restful Container. 372 func (s *Server) InstallDefaultHandlers() { 373 s.addMetricsBucketMatcher("healthz") 374 healthz.InstallHandler(s.restfulCont, 375 healthz.PingHealthz, 376 healthz.LogHealthz, 377 healthz.NamedCheck("syncloop", s.syncLoopHealthCheck), 378 ) 379 380 slis.SLIMetricsWithReset{}.Install(s.restfulCont) 381 382 s.addMetricsBucketMatcher("pods") 383 ws := new(restful.WebService) 384 ws. 385 Path("/pods"). 386 Produces(restful.MIME_JSON) 387 ws.Route(ws.GET(""). 388 To(s.getPods). 389 Operation("getPods")) 390 s.restfulCont.Add(ws) 391 392 s.addMetricsBucketMatcher("stats") 393 s.restfulCont.Add(stats.CreateHandlers(statsPath, s.host, s.resourceAnalyzer)) 394 395 s.addMetricsBucketMatcher("metrics") 396 s.addMetricsBucketMatcher("metrics/cadvisor") 397 s.addMetricsBucketMatcher("metrics/probes") 398 s.addMetricsBucketMatcher("metrics/resource") 399 s.restfulCont.Handle(metricsPath, legacyregistry.Handler()) 400 401 includedMetrics := cadvisormetrics.MetricSet{ 402 cadvisormetrics.CpuUsageMetrics: struct{}{}, 403 cadvisormetrics.MemoryUsageMetrics: struct{}{}, 404 cadvisormetrics.CpuLoadMetrics: struct{}{}, 405 cadvisormetrics.DiskIOMetrics: struct{}{}, 406 cadvisormetrics.DiskUsageMetrics: struct{}{}, 407 cadvisormetrics.NetworkUsageMetrics: struct{}{}, 408 cadvisormetrics.AppMetrics: struct{}{}, 409 cadvisormetrics.ProcessMetrics: struct{}{}, 410 cadvisormetrics.OOMMetrics: struct{}{}, 411 } 412 // cAdvisor metrics are exposed under the secured handler as well 413 r := compbasemetrics.NewKubeRegistry() 414 r.RawMustRegister(metrics.NewPrometheusMachineCollector(prometheusHostAdapter{s.host}, includedMetrics)) 415 if utilfeature.DefaultFeatureGate.Enabled(features.PodAndContainerStatsFromCRI) { 416 r.CustomRegister(collectors.NewCRIMetricsCollector(context.TODO(), s.host.ListPodSandboxMetrics, s.host.ListMetricDescriptors)) 417 } else { 418 cadvisorOpts := cadvisorv2.RequestOptions{ 419 IdType: cadvisorv2.TypeName, 420 Count: 1, 421 Recursive: true, 422 } 423 r.RawMustRegister(metrics.NewPrometheusCollector(prometheusHostAdapter{s.host}, containerPrometheusLabelsFunc(s.host), includedMetrics, clock.RealClock{}, cadvisorOpts)) 424 } 425 s.restfulCont.Handle(cadvisorMetricsPath, 426 compbasemetrics.HandlerFor(r, compbasemetrics.HandlerOpts{ErrorHandling: compbasemetrics.ContinueOnError}), 427 ) 428 429 s.addMetricsBucketMatcher("metrics/resource") 430 resourceRegistry := compbasemetrics.NewKubeRegistry() 431 resourceRegistry.CustomMustRegister(collectors.NewResourceMetricsCollector(s.resourceAnalyzer)) 432 s.restfulCont.Handle(resourceMetricsPath, 433 compbasemetrics.HandlerFor(resourceRegistry, compbasemetrics.HandlerOpts{ErrorHandling: compbasemetrics.ContinueOnError}), 434 ) 435 436 // prober metrics are exposed under a different endpoint 437 438 s.addMetricsBucketMatcher("metrics/probes") 439 p := compbasemetrics.NewKubeRegistry() 440 _ = compbasemetrics.RegisterProcessStartTime(p.Register) 441 p.MustRegister(prober.ProberResults) 442 p.MustRegister(prober.ProberDuration) 443 s.restfulCont.Handle(proberMetricsPath, 444 compbasemetrics.HandlerFor(p, compbasemetrics.HandlerOpts{ErrorHandling: compbasemetrics.ContinueOnError}), 445 ) 446 447 // Only enable checkpoint API if the feature is enabled 448 if utilfeature.DefaultFeatureGate.Enabled(features.ContainerCheckpoint) { 449 s.addMetricsBucketMatcher("checkpoint") 450 ws = &restful.WebService{} 451 ws.Path("/checkpoint").Produces(restful.MIME_JSON) 452 ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). 453 To(s.checkpoint). 454 Operation("checkpoint")) 455 s.restfulCont.Add(ws) 456 } 457 } 458 459 // InstallDebuggingHandlers registers the HTTP request patterns that serve logs or run commands/containers 460 func (s *Server) InstallDebuggingHandlers() { 461 klog.InfoS("Adding debug handlers to kubelet server") 462 463 s.addMetricsBucketMatcher("run") 464 ws := new(restful.WebService) 465 ws. 466 Path("/run") 467 ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). 468 To(s.getRun). 469 Operation("getRun")) 470 ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). 471 To(s.getRun). 472 Operation("getRun")) 473 s.restfulCont.Add(ws) 474 475 s.addMetricsBucketMatcher("exec") 476 ws = new(restful.WebService) 477 ws. 478 Path("/exec") 479 ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). 480 To(s.getExec). 481 Operation("getExec")) 482 ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). 483 To(s.getExec). 484 Operation("getExec")) 485 ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}"). 486 To(s.getExec). 487 Operation("getExec")) 488 ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). 489 To(s.getExec). 490 Operation("getExec")) 491 s.restfulCont.Add(ws) 492 493 s.addMetricsBucketMatcher("attach") 494 ws = new(restful.WebService) 495 ws. 496 Path("/attach") 497 ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). 498 To(s.getAttach). 499 Operation("getAttach")) 500 ws.Route(ws.POST("/{podNamespace}/{podID}/{containerName}"). 501 To(s.getAttach). 502 Operation("getAttach")) 503 ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}/{containerName}"). 504 To(s.getAttach). 505 Operation("getAttach")) 506 ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}/{containerName}"). 507 To(s.getAttach). 508 Operation("getAttach")) 509 s.restfulCont.Add(ws) 510 511 s.addMetricsBucketMatcher("portForward") 512 ws = new(restful.WebService) 513 ws. 514 Path("/portForward") 515 ws.Route(ws.GET("/{podNamespace}/{podID}"). 516 To(s.getPortForward). 517 Operation("getPortForward")) 518 ws.Route(ws.POST("/{podNamespace}/{podID}"). 519 To(s.getPortForward). 520 Operation("getPortForward")) 521 ws.Route(ws.GET("/{podNamespace}/{podID}/{uid}"). 522 To(s.getPortForward). 523 Operation("getPortForward")) 524 ws.Route(ws.POST("/{podNamespace}/{podID}/{uid}"). 525 To(s.getPortForward). 526 Operation("getPortForward")) 527 s.restfulCont.Add(ws) 528 529 s.addMetricsBucketMatcher("containerLogs") 530 ws = new(restful.WebService) 531 ws. 532 Path("/containerLogs") 533 ws.Route(ws.GET("/{podNamespace}/{podID}/{containerName}"). 534 To(s.getContainerLogs). 535 Operation("getContainerLogs")) 536 s.restfulCont.Add(ws) 537 538 s.addMetricsBucketMatcher("configz") 539 configz.InstallHandler(s.restfulCont) 540 541 // The /runningpods endpoint is used for testing only. 542 s.addMetricsBucketMatcher("runningpods") 543 ws = new(restful.WebService) 544 ws. 545 Path("/runningpods/"). 546 Produces(restful.MIME_JSON) 547 ws.Route(ws.GET(""). 548 To(s.getRunningPods). 549 Operation("getRunningPods")) 550 s.restfulCont.Add(ws) 551 } 552 553 // InstallDebuggingDisabledHandlers registers the HTTP request patterns that provide better error message 554 func (s *Server) InstallDebuggingDisabledHandlers() { 555 h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 556 http.Error(w, "Debug endpoints are disabled.", http.StatusMethodNotAllowed) 557 }) 558 559 s.addMetricsBucketMatcher("run") 560 s.addMetricsBucketMatcher("exec") 561 s.addMetricsBucketMatcher("attach") 562 s.addMetricsBucketMatcher("portForward") 563 s.addMetricsBucketMatcher("containerLogs") 564 s.addMetricsBucketMatcher("runningpods") 565 s.addMetricsBucketMatcher("pprof") 566 s.addMetricsBucketMatcher("logs") 567 paths := []string{ 568 "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", 569 "/runningpods/", pprofBasePath, logsPath} 570 for _, p := range paths { 571 s.restfulCont.Handle(p, h) 572 } 573 } 574 575 // InstallSystemLogHandler registers the HTTP request patterns for logs endpoint. 576 func (s *Server) InstallSystemLogHandler(enableSystemLogHandler bool, enableSystemLogQuery bool) { 577 s.addMetricsBucketMatcher("logs") 578 if enableSystemLogHandler { 579 ws := new(restful.WebService) 580 ws.Path(logsPath) 581 ws.Route(ws.GET(""). 582 To(s.getLogs). 583 Operation("getLogs")) 584 if !enableSystemLogQuery { 585 ws.Route(ws.GET("/{logpath:*}"). 586 To(s.getLogs). 587 Operation("getLogs"). 588 Param(ws.PathParameter("logpath", "path to the log").DataType("string"))) 589 } else { 590 ws.Route(ws.GET("/{logpath:*}"). 591 To(s.getLogs). 592 Operation("getLogs"). 593 Param(ws.PathParameter("logpath", "path to the log").DataType("string")). 594 Param(ws.QueryParameter("query", "query specifies services(s) or files from which to return logs").DataType("string")). 595 Param(ws.QueryParameter("sinceTime", "sinceTime is an RFC3339 timestamp from which to show logs").DataType("string")). 596 Param(ws.QueryParameter("untilTime", "untilTime is an RFC3339 timestamp until which to show logs").DataType("string")). 597 Param(ws.QueryParameter("tailLines", "tailLines is used to retrieve the specified number of lines from the end of the log").DataType("string")). 598 Param(ws.QueryParameter("pattern", "pattern filters log entries by the provided regex pattern").DataType("string")). 599 Param(ws.QueryParameter("boot", "boot show messages from a specific system boot").DataType("string"))) 600 } 601 s.restfulCont.Add(ws) 602 } else { 603 s.restfulCont.Handle(logsPath, getHandlerForDisabledEndpoint("logs endpoint is disabled.")) 604 } 605 } 606 607 func getHandlerForDisabledEndpoint(errorMessage string) http.HandlerFunc { 608 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 609 http.Error(w, errorMessage, http.StatusMethodNotAllowed) 610 }) 611 } 612 613 // InstallDebugFlagsHandler registers the HTTP request patterns for /debug/flags/v endpoint. 614 func (s *Server) InstallDebugFlagsHandler(enableDebugFlagsHandler bool) { 615 if enableDebugFlagsHandler { 616 // Setup flags handlers. 617 // so far, only logging related endpoints are considered valid to add for these debug flags. 618 s.restfulCont.Handle(debugFlagPath, routes.StringFlagPutHandler(logs.GlogSetter)) 619 } else { 620 s.restfulCont.Handle(debugFlagPath, getHandlerForDisabledEndpoint("flags endpoint is disabled.")) 621 return 622 } 623 } 624 625 // InstallProfilingHandler registers the HTTP request patterns for /debug/pprof endpoint. 626 func (s *Server) InstallProfilingHandler(enableProfilingLogHandler bool, enableContentionProfiling bool) { 627 s.addMetricsBucketMatcher("debug") 628 if !enableProfilingLogHandler { 629 s.restfulCont.Handle(pprofBasePath, getHandlerForDisabledEndpoint("profiling endpoint is disabled.")) 630 return 631 } 632 633 handlePprofEndpoint := func(req *restful.Request, resp *restful.Response) { 634 name := strings.TrimPrefix(req.Request.URL.Path, pprofBasePath) 635 switch name { 636 case "profile": 637 pprof.Profile(resp, req.Request) 638 case "symbol": 639 pprof.Symbol(resp, req.Request) 640 case "cmdline": 641 pprof.Cmdline(resp, req.Request) 642 case "trace": 643 pprof.Trace(resp, req.Request) 644 default: 645 pprof.Index(resp, req.Request) 646 } 647 } 648 649 // Setup pprof handlers. 650 ws := new(restful.WebService).Path(pprofBasePath) 651 ws.Route(ws.GET("/{subpath:*}").To(handlePprofEndpoint)).Doc("pprof endpoint") 652 s.restfulCont.Add(ws) 653 654 if enableContentionProfiling { 655 goruntime.SetBlockProfileRate(1) 656 } 657 } 658 659 // Checks if kubelet's sync loop that updates containers is working. 660 func (s *Server) syncLoopHealthCheck(req *http.Request) error { 661 duration := s.host.ResyncInterval() * 2 662 minDuration := time.Minute * 5 663 if duration < minDuration { 664 duration = minDuration 665 } 666 enterLoopTime := s.host.LatestLoopEntryTime() 667 if !enterLoopTime.IsZero() && time.Now().After(enterLoopTime.Add(duration)) { 668 return fmt.Errorf("sync Loop took longer than expected") 669 } 670 return nil 671 } 672 673 // getContainerLogs handles containerLogs request against the Kubelet 674 func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { 675 podNamespace := request.PathParameter("podNamespace") 676 podID := request.PathParameter("podID") 677 containerName := request.PathParameter("containerName") 678 ctx := request.Request.Context() 679 680 if len(podID) == 0 { 681 // TODO: Why return JSON when the rest return plaintext errors? 682 // TODO: Why return plaintext errors? 683 response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) 684 return 685 } 686 if len(containerName) == 0 { 687 // TODO: Why return JSON when the rest return plaintext errors? 688 response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) 689 return 690 } 691 if len(podNamespace) == 0 { 692 // TODO: Why return JSON when the rest return plaintext errors? 693 response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) 694 return 695 } 696 697 query := request.Request.URL.Query() 698 // backwards compatibility for the "tail" query parameter 699 if tail := request.QueryParameter("tail"); len(tail) > 0 { 700 query["tailLines"] = []string{tail} 701 // "all" is the same as omitting tail 702 if tail == "all" { 703 delete(query, "tailLines") 704 } 705 } 706 // container logs on the kubelet are locked to the v1 API version of PodLogOptions 707 logOptions := &v1.PodLogOptions{} 708 if err := legacyscheme.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { 709 response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) 710 return 711 } 712 logOptions.TypeMeta = metav1.TypeMeta{} 713 if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { 714 response.WriteError(http.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) 715 return 716 } 717 718 pod, ok := s.host.GetPodByName(podNamespace, podID) 719 if !ok { 720 response.WriteError(http.StatusNotFound, fmt.Errorf("pod %q does not exist", podID)) 721 return 722 } 723 // Check if containerName is valid. 724 if kubecontainer.GetContainerSpec(pod, containerName) == nil { 725 response.WriteError(http.StatusNotFound, fmt.Errorf("container %q not found in pod %q", containerName, podID)) 726 return 727 } 728 729 if _, ok := response.ResponseWriter.(http.Flusher); !ok { 730 response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher, cannot show logs", reflect.TypeOf(response))) 731 return 732 } 733 fw := flushwriter.Wrap(response.ResponseWriter) 734 response.Header().Set("Transfer-Encoding", "chunked") 735 if err := s.host.GetKubeletContainerLogs(ctx, kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { 736 response.WriteError(http.StatusBadRequest, err) 737 return 738 } 739 } 740 741 // encodePods creates an v1.PodList object from pods and returns the encoded 742 // PodList. 743 func encodePods(pods []*v1.Pod) (data []byte, err error) { 744 podList := new(v1.PodList) 745 for _, pod := range pods { 746 podList.Items = append(podList.Items, *pod) 747 } 748 // TODO: this needs to be parameterized to the kubelet, not hardcoded. Depends on Kubelet 749 // as API server refactor. 750 // TODO: Locked to v1, needs to be made generic 751 codec := legacyscheme.Codecs.LegacyCodec(schema.GroupVersion{Group: v1.GroupName, Version: "v1"}) 752 return runtime.Encode(codec, podList) 753 } 754 755 // getPods returns a list of pods bound to the Kubelet and their spec. 756 func (s *Server) getPods(request *restful.Request, response *restful.Response) { 757 pods := s.host.GetPods() 758 data, err := encodePods(pods) 759 if err != nil { 760 response.WriteError(http.StatusInternalServerError, err) 761 return 762 } 763 writeJSONResponse(response, data) 764 } 765 766 // getRunningPods returns a list of pods running on Kubelet. The list is 767 // provided by the container runtime, and is different from the list returned 768 // by getPods, which is a set of desired pods to run. 769 func (s *Server) getRunningPods(request *restful.Request, response *restful.Response) { 770 ctx := request.Request.Context() 771 pods, err := s.host.GetRunningPods(ctx) 772 if err != nil { 773 response.WriteError(http.StatusInternalServerError, err) 774 return 775 } 776 data, err := encodePods(pods) 777 if err != nil { 778 response.WriteError(http.StatusInternalServerError, err) 779 return 780 } 781 writeJSONResponse(response, data) 782 } 783 784 // getLogs handles logs requests against the Kubelet. 785 func (s *Server) getLogs(request *restful.Request, response *restful.Response) { 786 s.host.ServeLogs(response, request.Request) 787 } 788 789 type execRequestParams struct { 790 podNamespace string 791 podName string 792 podUID types.UID 793 containerName string 794 cmd []string 795 } 796 797 func getExecRequestParams(req *restful.Request) execRequestParams { 798 return execRequestParams{ 799 podNamespace: req.PathParameter("podNamespace"), 800 podName: req.PathParameter("podID"), 801 podUID: types.UID(req.PathParameter("uid")), 802 containerName: req.PathParameter("containerName"), 803 cmd: req.Request.URL.Query()[api.ExecCommandParam], 804 } 805 } 806 807 type portForwardRequestParams struct { 808 podNamespace string 809 podName string 810 podUID types.UID 811 } 812 813 func getPortForwardRequestParams(req *restful.Request) portForwardRequestParams { 814 return portForwardRequestParams{ 815 podNamespace: req.PathParameter("podNamespace"), 816 podName: req.PathParameter("podID"), 817 podUID: types.UID(req.PathParameter("uid")), 818 } 819 } 820 821 type responder struct{} 822 823 func (r *responder) Error(w http.ResponseWriter, req *http.Request, err error) { 824 klog.ErrorS(err, "Error while proxying request") 825 http.Error(w, err.Error(), http.StatusInternalServerError) 826 } 827 828 // proxyStream proxies stream to url. 829 func proxyStream(w http.ResponseWriter, r *http.Request, url *url.URL) { 830 // TODO(random-liu): Set MaxBytesPerSec to throttle the stream. 831 handler := proxy.NewUpgradeAwareHandler(url, nil /*transport*/, false /*wrapTransport*/, true /*upgradeRequired*/, &responder{}) 832 handler.ServeHTTP(w, r) 833 } 834 835 // getAttach handles requests to attach to a container. 836 func (s *Server) getAttach(request *restful.Request, response *restful.Response) { 837 params := getExecRequestParams(request) 838 streamOpts, err := remotecommandserver.NewOptions(request.Request) 839 if err != nil { 840 utilruntime.HandleError(err) 841 response.WriteError(http.StatusBadRequest, err) 842 return 843 } 844 pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) 845 if !ok { 846 response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) 847 return 848 } 849 850 podFullName := kubecontainer.GetPodFullName(pod) 851 url, err := s.host.GetAttach(request.Request.Context(), podFullName, params.podUID, params.containerName, *streamOpts) 852 if err != nil { 853 streaming.WriteError(err, response.ResponseWriter) 854 return 855 } 856 857 proxyStream(response.ResponseWriter, request.Request, url) 858 } 859 860 // getExec handles requests to run a command inside a container. 861 func (s *Server) getExec(request *restful.Request, response *restful.Response) { 862 params := getExecRequestParams(request) 863 streamOpts, err := remotecommandserver.NewOptions(request.Request) 864 if err != nil { 865 utilruntime.HandleError(err) 866 response.WriteError(http.StatusBadRequest, err) 867 return 868 } 869 pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) 870 if !ok { 871 response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) 872 return 873 } 874 875 podFullName := kubecontainer.GetPodFullName(pod) 876 url, err := s.host.GetExec(request.Request.Context(), podFullName, params.podUID, params.containerName, params.cmd, *streamOpts) 877 if err != nil { 878 streaming.WriteError(err, response.ResponseWriter) 879 return 880 } 881 proxyStream(response.ResponseWriter, request.Request, url) 882 } 883 884 // getRun handles requests to run a command inside a container. 885 func (s *Server) getRun(request *restful.Request, response *restful.Response) { 886 params := getExecRequestParams(request) 887 pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) 888 if !ok { 889 response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) 890 return 891 } 892 893 // For legacy reasons, run uses different query param than exec. 894 params.cmd = strings.Split(request.QueryParameter("cmd"), " ") 895 data, err := s.host.RunInContainer(request.Request.Context(), kubecontainer.GetPodFullName(pod), params.podUID, params.containerName, params.cmd) 896 if err != nil { 897 response.WriteError(http.StatusInternalServerError, err) 898 return 899 } 900 writeJSONResponse(response, data) 901 } 902 903 // Derived from go-restful writeJSON. 904 func writeJSONResponse(response *restful.Response, data []byte) { 905 if data == nil { 906 response.WriteHeader(http.StatusOK) 907 // do not write a nil representation 908 return 909 } 910 response.Header().Set(restful.HEADER_ContentType, restful.MIME_JSON) 911 response.WriteHeader(http.StatusOK) 912 if _, err := response.Write(data); err != nil { 913 klog.ErrorS(err, "Error writing response") 914 } 915 } 916 917 // getPortForward handles a new restful port forward request. It determines the 918 // pod name and uid and then calls ServePortForward. 919 func (s *Server) getPortForward(request *restful.Request, response *restful.Response) { 920 params := getPortForwardRequestParams(request) 921 922 portForwardOptions, err := portforward.NewV4Options(request.Request) 923 if err != nil { 924 utilruntime.HandleError(err) 925 response.WriteError(http.StatusBadRequest, err) 926 return 927 } 928 pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) 929 if !ok { 930 response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) 931 return 932 } 933 if len(params.podUID) > 0 && pod.UID != params.podUID { 934 response.WriteError(http.StatusNotFound, fmt.Errorf("pod not found")) 935 return 936 } 937 938 url, err := s.host.GetPortForward(request.Request.Context(), pod.Name, pod.Namespace, pod.UID, *portForwardOptions) 939 if err != nil { 940 streaming.WriteError(err, response.ResponseWriter) 941 return 942 } 943 proxyStream(response.ResponseWriter, request.Request, url) 944 } 945 946 // checkpoint handles the checkpoint API request. It checks if the requested 947 // podNamespace, pod and container actually exist and only then calls out 948 // to the runtime to actually checkpoint the container. 949 func (s *Server) checkpoint(request *restful.Request, response *restful.Response) { 950 ctx := request.Request.Context() 951 pod, ok := s.host.GetPodByName(request.PathParameter("podNamespace"), request.PathParameter("podID")) 952 if !ok { 953 response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) 954 return 955 } 956 957 containerName := request.PathParameter("containerName") 958 959 found := false 960 for _, container := range pod.Spec.Containers { 961 if container.Name == containerName { 962 found = true 963 break 964 } 965 } 966 if !found { 967 for _, container := range pod.Spec.InitContainers { 968 if container.Name == containerName { 969 found = true 970 break 971 } 972 } 973 } 974 if !found { 975 for _, container := range pod.Spec.EphemeralContainers { 976 if container.Name == containerName { 977 found = true 978 break 979 } 980 } 981 } 982 if !found { 983 response.WriteError( 984 http.StatusNotFound, 985 fmt.Errorf("container %v does not exist", containerName), 986 ) 987 return 988 } 989 990 options := &runtimeapi.CheckpointContainerRequest{} 991 // Query parameter to select an optional timeout. Without the timeout parameter 992 // the checkpoint command will use the default CRI timeout. 993 timeouts := request.Request.URL.Query()["timeout"] 994 if len(timeouts) > 0 { 995 // If the user specified one or multiple values for timeouts we 996 // are using the last available value. 997 timeout, err := strconv.ParseInt(timeouts[len(timeouts)-1], 10, 64) 998 if err != nil { 999 response.WriteError( 1000 http.StatusNotFound, 1001 fmt.Errorf("cannot parse value of timeout parameter"), 1002 ) 1003 return 1004 } 1005 options.Timeout = timeout 1006 } 1007 1008 if err := s.host.CheckpointContainer(ctx, pod.UID, kubecontainer.GetPodFullName(pod), containerName, options); err != nil { 1009 response.WriteError( 1010 http.StatusInternalServerError, 1011 fmt.Errorf( 1012 "checkpointing of %v/%v/%v failed (%v)", 1013 request.PathParameter("podNamespace"), 1014 request.PathParameter("podID"), 1015 containerName, 1016 err, 1017 ), 1018 ) 1019 return 1020 } 1021 writeJSONResponse( 1022 response, 1023 []byte(fmt.Sprintf("{\"items\":[\"%s\"]}", options.Location)), 1024 ) 1025 } 1026 1027 // getURLRootPath trims a URL path. 1028 // For paths in the format of "/metrics/xxx", "metrics/xxx" is returned; 1029 // For all other paths, the first part of the path is returned. 1030 func getURLRootPath(path string) string { 1031 parts := strings.SplitN(strings.TrimPrefix(path, "/"), "/", 3) 1032 if len(parts) == 0 { 1033 return path 1034 } 1035 1036 if parts[0] == "metrics" && len(parts) > 1 { 1037 return fmt.Sprintf("%s/%s", parts[0], parts[1]) 1038 1039 } 1040 return parts[0] 1041 } 1042 1043 var longRunningRequestPathMap = map[string]bool{ 1044 "exec": true, 1045 "attach": true, 1046 "portforward": true, 1047 "debug": true, 1048 } 1049 1050 // isLongRunningRequest determines whether the request is long-running or not. 1051 func isLongRunningRequest(path string) bool { 1052 _, ok := longRunningRequestPathMap[path] 1053 return ok 1054 } 1055 1056 var statusesNoTracePred = httplog.StatusIsNot( 1057 http.StatusOK, 1058 http.StatusFound, 1059 http.StatusMovedPermanently, 1060 http.StatusTemporaryRedirect, 1061 http.StatusBadRequest, 1062 http.StatusNotFound, 1063 http.StatusSwitchingProtocols, 1064 ) 1065 1066 // ServeHTTP responds to HTTP requests on the Kubelet. 1067 func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { 1068 handler := httplog.WithLogging(s.restfulCont, statusesNoTracePred) 1069 1070 // monitor http requests 1071 var serverType string 1072 if s.auth == nil { 1073 serverType = "readonly" 1074 } else { 1075 serverType = "readwrite" 1076 } 1077 1078 method, path := s.getMetricMethodBucket(req.Method), s.getMetricBucket(req.URL.Path) 1079 1080 longRunning := strconv.FormatBool(isLongRunningRequest(path)) 1081 1082 servermetrics.HTTPRequests.WithLabelValues(method, path, serverType, longRunning).Inc() 1083 1084 servermetrics.HTTPInflightRequests.WithLabelValues(method, path, serverType, longRunning).Inc() 1085 defer servermetrics.HTTPInflightRequests.WithLabelValues(method, path, serverType, longRunning).Dec() 1086 1087 startTime := time.Now() 1088 defer servermetrics.HTTPRequestsDuration.WithLabelValues(method, path, serverType, longRunning).Observe(servermetrics.SinceInSeconds(startTime)) 1089 1090 handler.ServeHTTP(w, req) 1091 } 1092 1093 // prometheusHostAdapter adapts the HostInterface to the interface expected by the 1094 // cAdvisor prometheus collector. 1095 type prometheusHostAdapter struct { 1096 host HostInterface 1097 } 1098 1099 func (a prometheusHostAdapter) GetRequestedContainersInfo(containerName string, options cadvisorv2.RequestOptions) (map[string]*cadvisorapi.ContainerInfo, error) { 1100 return a.host.GetRequestedContainersInfo(containerName, options) 1101 } 1102 func (a prometheusHostAdapter) GetVersionInfo() (*cadvisorapi.VersionInfo, error) { 1103 return a.host.GetVersionInfo() 1104 } 1105 func (a prometheusHostAdapter) GetMachineInfo() (*cadvisorapi.MachineInfo, error) { 1106 return a.host.GetCachedMachineInfo() 1107 } 1108 1109 func containerPrometheusLabelsFunc(s stats.Provider) metrics.ContainerLabelsFunc { 1110 // containerPrometheusLabels maps cAdvisor labels to prometheus labels. 1111 return func(c *cadvisorapi.ContainerInfo) map[string]string { 1112 // Prometheus requires that all metrics in the same family have the same labels, 1113 // so we arrange to supply blank strings for missing labels 1114 var name, image, podName, namespace, containerName string 1115 if len(c.Aliases) > 0 { 1116 name = c.Aliases[0] 1117 } 1118 image = c.Spec.Image 1119 if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNameLabel]; ok { 1120 podName = v 1121 } 1122 if v, ok := c.Spec.Labels[kubelettypes.KubernetesPodNamespaceLabel]; ok { 1123 namespace = v 1124 } 1125 if v, ok := c.Spec.Labels[kubelettypes.KubernetesContainerNameLabel]; ok { 1126 containerName = v 1127 } 1128 // Associate pod cgroup with pod so we have an accurate accounting of sandbox 1129 if podName == "" && namespace == "" { 1130 if pod, found := s.GetPodByCgroupfs(c.Name); found { 1131 podName = pod.Name 1132 namespace = pod.Namespace 1133 } 1134 } 1135 set := map[string]string{ 1136 metrics.LabelID: c.Name, 1137 metrics.LabelName: name, 1138 metrics.LabelImage: image, 1139 "pod": podName, 1140 "namespace": namespace, 1141 "container": containerName, 1142 } 1143 return set 1144 } 1145 }