package node import ( "context" "fmt" "pandax/apps/devops/services/k8s/event" "pandax/base/global" "pandax/apps/devops/entity/k8s" k8scommon "pandax/apps/devops/services/k8s/common" //"github.com/gin-gonic/gin" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/kubernetes" ) // NodeAllocatedResources describes node allocated resources. type NodeAllocatedResources struct { // CPURequests is number of allocated milicores. CPURequests int64 `json:"cpuRequests"` // CPURequestsFraction is a fraction of CPU, that is allocated. CPURequestsFraction float64 `json:"cpuRequestsFraction"` // CPULimits is defined CPU limit. CPULimits int64 `json:"cpuLimits"` // CPULimitsFraction is a fraction of defined CPU limit, can be over 100%, i.e. // overcommitted. CPULimitsFraction float64 `json:"cpuLimitsFraction"` // CPUCapacity is specified node CPU capacity in milicores. CPUCapacity int64 `json:"cpuCapacity"` // MemoryRequests is a fraction of memory, that is allocated. MemoryRequests int64 `json:"memoryRequests"` // MemoryRequestsFraction is a fraction of memory, that is allocated. MemoryRequestsFraction float64 `json:"memoryRequestsFraction"` // MemoryLimits is defined memory limit. MemoryLimits int64 `json:"memoryLimits"` // MemoryLimitsFraction is a fraction of defined memory limit, can be over 100%, i.e. // overcommitted. MemoryLimitsFraction float64 `json:"memoryLimitsFraction"` // MemoryCapacity is specified node memory capacity in bytes. MemoryCapacity int64 `json:"memoryCapacity"` // AllocatedPods in number of currently allocated pods on the node. AllocatedPods int `json:"allocatedPods"` // PodCapacity is maximum number of pods, that can be allocated on the node. PodCapacity int64 `json:"podCapacity"` // PodFraction is a fraction of pods, that can be allocated on given node. PodFraction float64 `json:"podFraction"` } // NodeDetail is a presentation layer view of Kubernetes Node resource. This means it is Node plus // additional augmented data we can get from other sources. type NodeDetail struct { // Extends list item structure. Node `json:",inline"` // NodePhase is the current lifecycle phase of the node. Phase v1.NodePhase `json:"phase"` // PodCIDR represents the pod IP range assigned to the node. PodCIDR string `json:"podCIDR"` // ID of the node assigned by the cloud provider. ProviderID string `json:"providerID"` // Unschedulable controls node schedulability of new pods. By default node is schedulable. Unschedulable bool `json:"unschedulable"` // Set of ids/uuids to uniquely identify the node. NodeInfo v1.NodeSystemInfo `json:"nodeInfo"` //// Conditions is an array of current node conditions. Conditions []k8scommon.Condition `json:"conditions"` // Container images of the node. ContainerImages []string `json:"containerImages"` // PodListComponent contains information about pods belonging to this node. PodList v1.PodList `json:"podList"` // Events is list of events associated to the node. EventList v1.EventList `json:"eventList"` // Taints Taints []v1.Taint `json:"taints,omitempty"` // Addresses is a list of addresses reachable to the node. Queried from cloud provider, if available. Addresses []v1.NodeAddress `json:"addresses,omitempty"` Ready v1.ConditionStatus `json:"ready"` NodeIP k8s.NodeIP `json:"nodeIP"` UID k8s.UID `json:"uid"` } func GetNodeDetail(client *kubernetes.Clientset, name string) (*NodeDetail, error) { /* 获取节点详细信息 */ global.Log.Info(fmt.Sprintf("Getting details of %s node", name)) node, err := client.CoreV1().Nodes().Get(context.TODO(), name, metaV1.GetOptions{}) if err != nil { return nil, err } pods, err := getNodePods(client, *node) if err != nil { return nil, err } eventList, err := event.GetNodeEvents(client, node.Name) if err != nil { return nil, err } allocatedResources, err := getNodeAllocatedResources(*node, pods) if err != nil { return nil, err } nodeDetails := toNodeDetail(*node, pods, eventList, NodeAllocatedResources(allocatedResources)) return &nodeDetails, nil } func getNodePods(client *kubernetes.Clientset, node v1.Node) (*v1.PodList, error) { fieldSelector, err := fields.ParseSelector("spec.nodeName=" + node.Name + ",status.phase!=" + string(v1.PodSucceeded) + ",status.phase!=" + string(v1.PodFailed)) if err != nil { return nil, err } return client.CoreV1().Pods(v1.NamespaceAll).List(context.TODO(), metaV1.ListOptions{ FieldSelector: fieldSelector.String(), }) } func getNodeAllocatedResources(node v1.Node, podList *v1.PodList) (k8s.NodeAllocatedResources, error) { reqs, limits := map[v1.ResourceName]resource.Quantity{}, map[v1.ResourceName]resource.Quantity{} for _, pod := range podList.Items { podReqs, podLimits, err := PodRequestsAndLimits(&pod) if err != nil { return k8s.NodeAllocatedResources{}, err } for podReqName, podReqValue := range podReqs { if value, ok := reqs[podReqName]; !ok { reqs[podReqName] = podReqValue.DeepCopy() } else { value.Add(podReqValue) reqs[podReqName] = value } } for podLimitName, podLimitValue := range podLimits { if value, ok := limits[podLimitName]; !ok { limits[podLimitName] = podLimitValue.DeepCopy() } else { value.Add(podLimitValue) limits[podLimitName] = value } } } cpuRequests, cpuLimits, memoryRequests, memoryLimits := reqs[v1.ResourceCPU], limits[v1.ResourceCPU], reqs[v1.ResourceMemory], limits[v1.ResourceMemory] var cpuRequestsFraction, cpuLimitsFraction float64 = 0, 0 if capacity := float64(node.Status.Allocatable.Cpu().MilliValue()); capacity > 0 { cpuRequestsFraction = float64(cpuRequests.MilliValue()) / capacity * 100 cpuLimitsFraction = float64(cpuLimits.MilliValue()) / capacity * 100 } var memoryRequestsFraction, memoryLimitsFraction float64 = 0, 0 if capacity := float64(node.Status.Allocatable.Memory().MilliValue()); capacity > 0 { memoryRequestsFraction = float64(memoryRequests.MilliValue()) / capacity * 100 memoryLimitsFraction = float64(memoryLimits.MilliValue()) / capacity * 100 } var podFraction float64 = 0 var podCapacity int64 = node.Status.Capacity.Pods().Value() if podCapacity > 0 { podFraction = float64(len(podList.Items)) / float64(podCapacity) * 100 } return k8s.NodeAllocatedResources{ CPURequests: cpuRequests.MilliValue(), CPURequestsFraction: cpuRequestsFraction, CPULimits: cpuLimits.MilliValue(), CPULimitsFraction: cpuLimitsFraction, CPUCapacity: node.Status.Allocatable.Cpu().MilliValue(), MemoryRequests: memoryRequests.Value(), MemoryRequestsFraction: memoryRequestsFraction, MemoryLimits: memoryLimits.Value(), MemoryLimitsFraction: memoryLimitsFraction, MemoryCapacity: node.Status.Allocatable.Memory().Value(), AllocatedPods: len(podList.Items), PodCapacity: podCapacity, PodFraction: podFraction, }, nil } // PodRequestsAndLimits returns a dictionary of all defined resources summed up for all // containers of the pod. If pod overhead is non-nil, the pod overhead is added to the // total container resource requests and to the total container limits which have a // non-zero quantity. func PodRequestsAndLimits(pod *v1.Pod) (reqs, limits v1.ResourceList, err error) { reqs, limits = v1.ResourceList{}, v1.ResourceList{} for _, container := range pod.Spec.Containers { addResourceList(reqs, container.Resources.Requests) addResourceList(limits, container.Resources.Limits) } // Init containers define the minimum of any resource for _, container := range pod.Spec.InitContainers { maxResourceList(reqs, container.Resources.Requests) maxResourceList(limits, container.Resources.Limits) } // Add overhead for running a pod to the sum of requests and to non-zero limits: if pod.Spec.Overhead != nil { addResourceList(reqs, pod.Spec.Overhead) for name, quantity := range pod.Spec.Overhead { if value, ok := limits[name]; ok && !value.IsZero() { value.Add(quantity) limits[name] = value } } } return } // addResourceList adds the resources in newList to list func addResourceList(list, new v1.ResourceList) { for name, quantity := range new { if value, ok := list[name]; !ok { list[name] = quantity.DeepCopy() } else { value.Add(quantity) list[name] = value } } } // maxResourceList sets list to the greater of list/newList for every resource // either list func maxResourceList(list, new v1.ResourceList) { for name, quantity := range new { if value, ok := list[name]; !ok { list[name] = quantity.DeepCopy() continue } else { if quantity.Cmp(value) > 0 { list[name] = quantity.DeepCopy() } } } } func toNodeDetail(node v1.Node, pods *v1.PodList, eventList *v1.EventList, allocatedResources NodeAllocatedResources) NodeDetail { return NodeDetail{ Node: Node{ ObjectMeta: k8s.NewObjectMeta(node.ObjectMeta), TypeMeta: k8s.NewTypeMeta("node"), AllocatedResources: k8s.NodeAllocatedResources(allocatedResources), }, Phase: node.Status.Phase, ProviderID: node.Spec.ProviderID, PodCIDR: node.Spec.PodCIDR, Unschedulable: node.Spec.Unschedulable, NodeInfo: node.Status.NodeInfo, Conditions: getNodeConditions(node), ContainerImages: getContainerImages(node), PodList: *pods, EventList: *eventList, Taints: node.Spec.Taints, Addresses: node.Status.Addresses, Ready: getNodeConditionStatus(node, v1.NodeReady), NodeIP: k8s.NodeIP(getNodeIP(node)), UID: k8s.UID(node.UID), } }