123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- /*
- 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"
- "k8s.io/klog"
- "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)
- // 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
- request.Request.ParseForm()
- if onlyCluAndMemoryParam, found := request.Request.Form["only_cpu_and_memory"]; found &&
- len(onlyCluAndMemoryParam) == 1 && onlyCluAndMemoryParam[0] == "true" {
- onlyCPUAndMemory = true
- }
- var summary *statsapi.Summary
- var err error
- 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)
- }
- }
|