mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			333 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| Copyright 2016 The Kubernetes Authors.
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
|     http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| */
 | |
| 
 | |
| package stats
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"net/http"
 | |
| 	"path"
 | |
| 	"time"
 | |
| 
 | |
| 	restful "github.com/emicklei/go-restful"
 | |
| 	cadvisorapi "github.com/google/cadvisor/info/v1"
 | |
| 	cadvisorv2 "github.com/google/cadvisor/info/v2"
 | |
| 	"github.com/pkg/errors"
 | |
| 	"k8s.io/klog/v2"
 | |
| 
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/types"
 | |
| 	statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
 | |
| 	"k8s.io/kubernetes/pkg/kubelet/cm"
 | |
| 	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
 | |
| 	"k8s.io/kubernetes/pkg/volume"
 | |
| )
 | |
| 
 | |
| // Provider hosts methods required by stats handlers.
 | |
| type Provider interface {
 | |
| 	// The following stats are provided by either CRI or cAdvisor.
 | |
| 	//
 | |
| 	// ListPodStats returns the stats of all the containers managed by pods.
 | |
| 	ListPodStats() ([]statsapi.PodStats, error)
 | |
| 	// ListPodStatsAndUpdateCPUNanoCoreUsage updates the cpu nano core usage for
 | |
| 	// the containers and returns the stats for all the pod-managed containers.
 | |
| 	ListPodCPUAndMemoryStats() ([]statsapi.PodStats, error)
 | |
| 	// ListPodStatsAndUpdateCPUNanoCoreUsage returns the stats of all the
 | |
| 	// containers managed by pods and force update the cpu usageNanoCores.
 | |
| 	// This is a workaround for CRI runtimes that do not integrate with
 | |
| 	// cadvisor. See https://github.com/kubernetes/kubernetes/issues/72788
 | |
| 	// for more details.
 | |
| 	ListPodStatsAndUpdateCPUNanoCoreUsage() ([]statsapi.PodStats, error)
 | |
| 	// ImageFsStats returns the stats of the image filesystem.
 | |
| 	ImageFsStats() (*statsapi.FsStats, error)
 | |
| 
 | |
| 	// The following stats are provided by cAdvisor.
 | |
| 	//
 | |
| 	// GetCgroupStats returns the stats and the networking usage of the cgroup
 | |
| 	// with the specified cgroupName.
 | |
| 	GetCgroupStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, *statsapi.NetworkStats, error)
 | |
| 	// GetCgroupCPUAndMemoryStats returns the CPU and memory stats of the cgroup with the specified cgroupName.
 | |
| 	GetCgroupCPUAndMemoryStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, error)
 | |
| 
 | |
| 	// RootFsStats returns the stats of the node root filesystem.
 | |
| 	RootFsStats() (*statsapi.FsStats, error)
 | |
| 
 | |
| 	// The following stats are provided by cAdvisor for legacy usage.
 | |
| 	//
 | |
| 	// GetContainerInfo returns the information of the container with the
 | |
| 	// containerName managed by the pod with the uid.
 | |
| 	GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error)
 | |
| 	// GetRawContainerInfo returns the information of the container with the
 | |
| 	// containerName. If subcontainers is true, this function will return the
 | |
| 	// information of all the sub-containers as well.
 | |
| 	GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error)
 | |
| 	// GetRequestedContainersInfo returns the information of the container with
 | |
| 	// the containerName, and with the specified cAdvisor options.
 | |
| 	GetRequestedContainersInfo(containerName string, options cadvisorv2.RequestOptions) (map[string]*cadvisorapi.ContainerInfo, error)
 | |
| 
 | |
| 	// The following information is provided by Kubelet.
 | |
| 	//
 | |
| 	// GetPodByName returns the spec of the pod with the name in the specified
 | |
| 	// namespace.
 | |
| 	GetPodByName(namespace, name string) (*v1.Pod, bool)
 | |
| 	// GetNode returns the spec of the local node.
 | |
| 	GetNode() (*v1.Node, error)
 | |
| 	// GetNodeConfig returns the configuration of the local node.
 | |
| 	GetNodeConfig() cm.NodeConfig
 | |
| 	// ListVolumesForPod returns the stats of the volume used by the pod with
 | |
| 	// the podUID.
 | |
| 	ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool)
 | |
| 	// GetPods returns the specs of all the pods running on this node.
 | |
| 	GetPods() []*v1.Pod
 | |
| 
 | |
| 	// RlimitStats returns the rlimit stats of system.
 | |
| 	RlimitStats() (*statsapi.RlimitStats, error)
 | |
| 
 | |
| 	// GetPodCgroupRoot returns the literal cgroupfs value for the cgroup containing all pods
 | |
| 	GetPodCgroupRoot() string
 | |
| 
 | |
| 	// GetPodByCgroupfs provides the pod that maps to the specified cgroup literal, as well
 | |
| 	// as whether the pod was found.
 | |
| 	GetPodByCgroupfs(cgroupfs string) (*v1.Pod, bool)
 | |
| }
 | |
| 
 | |
| type handler struct {
 | |
| 	provider        Provider
 | |
| 	summaryProvider SummaryProvider
 | |
| }
 | |
| 
 | |
| // CreateHandlers creates the REST handlers for the stats.
 | |
| func CreateHandlers(rootPath string, provider Provider, summaryProvider SummaryProvider, enableCAdvisorJSONEndpoints bool) *restful.WebService {
 | |
| 	h := &handler{provider, summaryProvider}
 | |
| 
 | |
| 	ws := &restful.WebService{}
 | |
| 	ws.Path(rootPath).
 | |
| 		Produces(restful.MIME_JSON)
 | |
| 
 | |
| 	type endpoint struct {
 | |
| 		path    string
 | |
| 		handler restful.RouteFunction
 | |
| 	}
 | |
| 
 | |
| 	endpoints := []endpoint{
 | |
| 		{"/summary", h.handleSummary},
 | |
| 	}
 | |
| 
 | |
| 	if enableCAdvisorJSONEndpoints {
 | |
| 		endpoints = append(endpoints,
 | |
| 			endpoint{"", h.handleStats},
 | |
| 			endpoint{"/container", h.handleSystemContainer},
 | |
| 			endpoint{"/{podName}/{containerName}", h.handlePodContainer},
 | |
| 			endpoint{"/{namespace}/{podName}/{uid}/{containerName}", h.handlePodContainer},
 | |
| 		)
 | |
| 	}
 | |
| 
 | |
| 	for _, e := range endpoints {
 | |
| 		for _, method := range []string{"GET", "POST"} {
 | |
| 			ws.Route(ws.
 | |
| 				Method(method).
 | |
| 				Path(e.path).
 | |
| 				To(e.handler))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return ws
 | |
| }
 | |
| 
 | |
| type statsRequest struct {
 | |
| 	// The name of the container for which to request stats.
 | |
| 	// Default: /
 | |
| 	// +optional
 | |
| 	ContainerName string `json:"containerName,omitempty"`
 | |
| 
 | |
| 	// Max number of stats to return.
 | |
| 	// If start and end time are specified this limit is ignored.
 | |
| 	// Default: 60
 | |
| 	// +optional
 | |
| 	NumStats int `json:"num_stats,omitempty"`
 | |
| 
 | |
| 	// Start time for which to query information.
 | |
| 	// If omitted, the beginning of time is assumed.
 | |
| 	// +optional
 | |
| 	Start time.Time `json:"start,omitempty"`
 | |
| 
 | |
| 	// End time for which to query information.
 | |
| 	// If omitted, current time is assumed.
 | |
| 	// +optional
 | |
| 	End time.Time `json:"end,omitempty"`
 | |
| 
 | |
| 	// Whether to also include information from subcontainers.
 | |
| 	// Default: false.
 | |
| 	// +optional
 | |
| 	Subcontainers bool `json:"subcontainers,omitempty"`
 | |
| }
 | |
| 
 | |
| func (r *statsRequest) cadvisorRequest() *cadvisorapi.ContainerInfoRequest {
 | |
| 	return &cadvisorapi.ContainerInfoRequest{
 | |
| 		NumStats: r.NumStats,
 | |
| 		Start:    r.Start,
 | |
| 		End:      r.End,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func parseStatsRequest(request *restful.Request) (statsRequest, error) {
 | |
| 	// Default request.
 | |
| 	query := statsRequest{
 | |
| 		NumStats: 60,
 | |
| 	}
 | |
| 
 | |
| 	err := json.NewDecoder(request.Request.Body).Decode(&query)
 | |
| 	if err != nil && err != io.EOF {
 | |
| 		return query, err
 | |
| 	}
 | |
| 	return query, nil
 | |
| }
 | |
| 
 | |
| // Handles root container stats requests to /stats
 | |
| func (h *handler) handleStats(request *restful.Request, response *restful.Response) {
 | |
| 	query, err := parseStatsRequest(request)
 | |
| 	if err != nil {
 | |
| 		handleError(response, "/stats", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Root container stats.
 | |
| 	statsMap, err := h.provider.GetRawContainerInfo("/", query.cadvisorRequest(), false)
 | |
| 	if err != nil {
 | |
| 		handleError(response, fmt.Sprintf("/stats %v", query), err)
 | |
| 		return
 | |
| 	}
 | |
| 	writeResponse(response, statsMap["/"])
 | |
| }
 | |
| 
 | |
| // Handles stats summary requests to /stats/summary
 | |
| // If "only_cpu_and_memory" GET param is true then only cpu and memory is returned in response.
 | |
| func (h *handler) handleSummary(request *restful.Request, response *restful.Response) {
 | |
| 	onlyCPUAndMemory := false
 | |
| 	err := request.Request.ParseForm()
 | |
| 	if err != nil {
 | |
| 		handleError(response, "/stats/summary", errors.Wrapf(err, "parse form failed"))
 | |
| 		return
 | |
| 	}
 | |
| 	if onlyCluAndMemoryParam, found := request.Request.Form["only_cpu_and_memory"]; found &&
 | |
| 		len(onlyCluAndMemoryParam) == 1 && onlyCluAndMemoryParam[0] == "true" {
 | |
| 		onlyCPUAndMemory = true
 | |
| 	}
 | |
| 	var summary *statsapi.Summary
 | |
| 	if onlyCPUAndMemory {
 | |
| 		summary, err = h.summaryProvider.GetCPUAndMemoryStats()
 | |
| 	} else {
 | |
| 		// external calls to the summary API use cached stats
 | |
| 		forceStatsUpdate := false
 | |
| 		summary, err = h.summaryProvider.Get(forceStatsUpdate)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		handleError(response, "/stats/summary", err)
 | |
| 	} else {
 | |
| 		writeResponse(response, summary)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Handles non-kubernetes container stats requests to /stats/container/
 | |
| func (h *handler) handleSystemContainer(request *restful.Request, response *restful.Response) {
 | |
| 	query, err := parseStatsRequest(request)
 | |
| 	if err != nil {
 | |
| 		handleError(response, "/stats/container", err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Non-Kubernetes container stats.
 | |
| 	containerName := path.Join("/", query.ContainerName)
 | |
| 	stats, err := h.provider.GetRawContainerInfo(
 | |
| 		containerName, query.cadvisorRequest(), query.Subcontainers)
 | |
| 	if err != nil {
 | |
| 		if _, ok := stats[containerName]; ok {
 | |
| 			// If the failure is partial, log it and return a best-effort response.
 | |
| 			klog.Errorf("Partial failure issuing GetRawContainerInfo(%v): %v", query, err)
 | |
| 		} else {
 | |
| 			handleError(response, fmt.Sprintf("/stats/container %v", query), err)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| 	writeResponse(response, stats)
 | |
| }
 | |
| 
 | |
| // Handles kubernetes pod/container stats requests to:
 | |
| // /stats/<pod name>/<container name>
 | |
| // /stats/<namespace>/<pod name>/<uid>/<container name>
 | |
| func (h *handler) handlePodContainer(request *restful.Request, response *restful.Response) {
 | |
| 	query, err := parseStatsRequest(request)
 | |
| 	if err != nil {
 | |
| 		handleError(response, request.Request.URL.String(), err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// Default parameters.
 | |
| 	params := map[string]string{
 | |
| 		"namespace": metav1.NamespaceDefault,
 | |
| 		"uid":       "",
 | |
| 	}
 | |
| 	for k, v := range request.PathParameters() {
 | |
| 		params[k] = v
 | |
| 	}
 | |
| 
 | |
| 	if params["podName"] == "" || params["containerName"] == "" {
 | |
| 		response.WriteErrorString(http.StatusBadRequest,
 | |
| 			fmt.Sprintf("Invalid pod container request: %v", params))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	pod, ok := h.provider.GetPodByName(params["namespace"], params["podName"])
 | |
| 	if !ok {
 | |
| 		klog.V(4).Infof("Container not found: %v", params)
 | |
| 		response.WriteError(http.StatusNotFound, kubecontainer.ErrContainerNotFound)
 | |
| 		return
 | |
| 	}
 | |
| 	stats, err := h.provider.GetContainerInfo(
 | |
| 		kubecontainer.GetPodFullName(pod),
 | |
| 		types.UID(params["uid"]),
 | |
| 		params["containerName"],
 | |
| 		query.cadvisorRequest())
 | |
| 
 | |
| 	if err != nil {
 | |
| 		handleError(response, fmt.Sprintf("%s %v", request.Request.URL.String(), query), err)
 | |
| 		return
 | |
| 	}
 | |
| 	writeResponse(response, stats)
 | |
| }
 | |
| 
 | |
| func writeResponse(response *restful.Response, stats interface{}) {
 | |
| 	if err := response.WriteAsJson(stats); err != nil {
 | |
| 		klog.Errorf("Error writing response: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // handleError serializes an error object into an HTTP response.
 | |
| // request is provided for logging.
 | |
| func handleError(response *restful.Response, request string, err error) {
 | |
| 	switch err {
 | |
| 	case kubecontainer.ErrContainerNotFound:
 | |
| 		response.WriteError(http.StatusNotFound, err)
 | |
| 	default:
 | |
| 		msg := fmt.Sprintf("Internal Error: %v", err)
 | |
| 		klog.Errorf("HTTP InternalServerError serving %s: %s", request, msg)
 | |
| 		response.WriteErrorString(http.StatusInternalServerError, msg)
 | |
| 	}
 | |
| }
 |