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