mirror of
https://gitee.com/XM-GO/PandaX.git
synced 2026-04-25 14:08:34 +08:00
421 lines
14 KiB
Go
421 lines
14 KiB
Go
package pods
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"pandax/base/global"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
res "k8s.io/apimachinery/pkg/api/resource"
|
|
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes"
|
|
"math"
|
|
"pandax/apps/devops/entity/k8s"
|
|
k8scommon "pandax/apps/devops/services/k8s/common"
|
|
"pandax/apps/devops/services/k8s/controller"
|
|
"pandax/apps/devops/services/k8s/dataselect"
|
|
"pandax/apps/devops/services/k8s/pvc"
|
|
"strconv"
|
|
)
|
|
|
|
// PodDetail is a presentation layer view of Kubernetes Pod resource.
|
|
type PodDetail struct {
|
|
ObjectMeta k8s.ObjectMeta `json:"objectMeta"`
|
|
TypeMeta k8s.TypeMeta `json:"typeMeta"`
|
|
PodPhase string `json:"podPhase"`
|
|
PodIP string `json:"podIP"`
|
|
NodeName string `json:"nodeName"`
|
|
ServiceAccountName string `json:"serviceAccountName"`
|
|
RestartCount int32 `json:"restartCount"`
|
|
QOSClass string `json:"qosClass"`
|
|
Controller *controller.ResourceOwner `json:"controller,omitempty"`
|
|
Containers []Container `json:"containers"`
|
|
InitContainers []Container `json:"initContainers"`
|
|
Conditions []k8scommon.Condition `json:"conditions"`
|
|
ImagePullSecrets []v1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
|
EventList k8scommon.EventList `json:"eventList"`
|
|
PersistentvolumeclaimList pvc.PersistentVolumeClaimList `json:"persistentVolumeClaimList"`
|
|
SecurityContext *v1.PodSecurityContext `json:"securityContext"`
|
|
}
|
|
|
|
// Container represents a docker/rkt/etc. container that lives in a pod.
|
|
type Container struct {
|
|
// Name of the container.
|
|
Name string `json:"name"`
|
|
|
|
// Image URI of the container.
|
|
Image string `json:"image"`
|
|
|
|
// Ports of the container
|
|
Ports []v1.ContainerPort `json:"ports"`
|
|
|
|
// List of environment variables.
|
|
Env []EnvVar `json:"env"`
|
|
|
|
// Commands of the container
|
|
Commands []string `json:"commands"`
|
|
|
|
// Command arguments
|
|
Args []string `json:"args"`
|
|
|
|
// Information about mounted volumes
|
|
VolumeMounts []VolumeMount `json:"volumeMounts"`
|
|
|
|
// Security configuration that will be applied to a container.
|
|
SecurityContext *v1.SecurityContext `json:"securityContext"`
|
|
|
|
// Status of a pod container
|
|
Status *v1.ContainerStatus `json:"status"`
|
|
|
|
// Resource of a pod limit requests cpu mem
|
|
Resources v1.ResourceRequirements `json:"resource"`
|
|
// Probes
|
|
LivenessProbe *v1.Probe `json:"livenessProbe"`
|
|
ReadinessProbe *v1.Probe `json:"readinessProbe"`
|
|
StartupProbe *v1.Probe `json:"startupProbe"`
|
|
Lifecycle *v1.Lifecycle `json:"lifecycle"`
|
|
// ImagePullPolicy of a pod
|
|
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy"`
|
|
}
|
|
|
|
// EnvVar represents an environment variable of a container.
|
|
type EnvVar struct {
|
|
// Name of the variable.
|
|
Name string `json:"name"`
|
|
|
|
// Value of the variable. May be empty if value from is defined.
|
|
Value string `json:"value"`
|
|
|
|
// Defined for derived variables. If non-null, the value is get from the reference.
|
|
// Note that this is an API struct. This is intentional, as EnvVarSources are plain struct
|
|
// references.
|
|
ValueFrom *v1.EnvVarSource `json:"valueFrom"`
|
|
}
|
|
|
|
type VolumeMount struct {
|
|
// Name of the variable.
|
|
Name string `json:"name"`
|
|
|
|
// Is the volume read only ?
|
|
ReadOnly bool `json:"readOnly"`
|
|
|
|
// Path within the container at which the volume should be mounted. Must not contain ':'.
|
|
MountPath string `json:"mountPath"`
|
|
|
|
// Path within the volume from which the container's volume should be mounted. Defaults to "" (volume's root).
|
|
SubPath string `json:"subPath"`
|
|
|
|
// Information about the Volume itself
|
|
Volume v1.Volume `json:"volume"`
|
|
}
|
|
|
|
// GetPodDetail returns the details of a named Pod from a particular namespace.
|
|
func GetPodDetail(client *kubernetes.Clientset, namespace, name string) (*PodDetail, error) {
|
|
global.Log.Info(fmt.Sprintf("Getting details of %s pod in %s namespace", name, namespace))
|
|
|
|
channels := &k8scommon.ResourceChannels{
|
|
ConfigMapList: k8scommon.GetConfigMapListChannel(client, k8scommon.NewSameNamespaceQuery(namespace), 1),
|
|
SecretList: k8scommon.GetSecretListChannel(client, k8scommon.NewSameNamespaceQuery(namespace), 1),
|
|
}
|
|
|
|
pod, err := client.CoreV1().Pods(namespace).Get(context.TODO(), name, metaV1.GetOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
podController, err := getPodController(client, k8scommon.NewSameNamespaceQuery(namespace), pod)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configMapList := <-channels.ConfigMapList.List
|
|
err = <-channels.ConfigMapList.Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
secretList := <-channels.SecretList.List
|
|
err = <-channels.SecretList.Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
eventList, err := GetEventsForPod(client, dataselect.DefaultDataSelect, pod.Namespace, pod.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
persistentVolumeClaimList, err := pvc.GetPodPersistentVolumeClaims(client, namespace, name, dataselect.DefaultDataSelect)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
podDetail := toPodDetail(pod, configMapList, secretList, podController, eventList, persistentVolumeClaimList)
|
|
return &podDetail, nil
|
|
}
|
|
|
|
func getPodController(client *kubernetes.Clientset, nsQuery *k8scommon.NamespaceQuery, pod *v1.Pod) (*controller.ResourceOwner, error) {
|
|
channels := &k8scommon.ResourceChannels{
|
|
PodList: k8scommon.GetPodListChannel(client, nsQuery, 1),
|
|
EventList: k8scommon.GetEventListChannel(client, nsQuery, 1),
|
|
}
|
|
|
|
pods := <-channels.PodList.List
|
|
err := <-channels.PodList.Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
events := <-channels.EventList.List
|
|
if err := <-channels.EventList.Error; err != nil {
|
|
events = &v1.EventList{}
|
|
}
|
|
|
|
var ctrl controller.ResourceOwner
|
|
ownerRef := metaV1.GetControllerOf(pod)
|
|
if ownerRef != nil {
|
|
var rc controller.ResourceController
|
|
rc, err = controller.NewResourceController(*ownerRef, pod.Namespace, client)
|
|
if err == nil {
|
|
ctrl = rc.Get(pods.Items, events.Items)
|
|
}
|
|
}
|
|
|
|
return &ctrl, nil
|
|
}
|
|
|
|
func toPodDetail(pod *v1.Pod, configMaps *v1.ConfigMapList, secrets *v1.SecretList, controller *controller.ResourceOwner,
|
|
events *k8scommon.EventList, persistentVolumeClaimList *pvc.PersistentVolumeClaimList) PodDetail {
|
|
return PodDetail{
|
|
ObjectMeta: k8s.NewObjectMeta(pod.ObjectMeta),
|
|
TypeMeta: k8s.NewTypeMeta(k8s.ResourceKindPod),
|
|
PodPhase: getPodStatus(*pod),
|
|
PodIP: pod.Status.PodIP,
|
|
RestartCount: getRestartCount(*pod),
|
|
QOSClass: string(pod.Status.QOSClass),
|
|
NodeName: pod.Spec.NodeName,
|
|
ServiceAccountName: pod.Spec.ServiceAccountName,
|
|
Controller: controller,
|
|
Containers: extractContainerInfo(pod.Spec.Containers, pod, configMaps, secrets),
|
|
InitContainers: extractContainerInfo(pod.Spec.InitContainers, pod, configMaps, secrets),
|
|
Conditions: getPodConditions(*pod),
|
|
ImagePullSecrets: pod.Spec.ImagePullSecrets,
|
|
EventList: *events,
|
|
PersistentvolumeclaimList: *persistentVolumeClaimList,
|
|
SecurityContext: pod.Spec.SecurityContext,
|
|
}
|
|
}
|
|
|
|
func extractContainerInfo(containerList []v1.Container, pod *v1.Pod, configMaps *v1.ConfigMapList, secrets *v1.SecretList) []Container {
|
|
containers := make([]Container, 0)
|
|
for _, container := range containerList {
|
|
vars := make([]EnvVar, 0)
|
|
for _, envVar := range container.Env {
|
|
variable := EnvVar{
|
|
Name: envVar.Name,
|
|
Value: envVar.Value,
|
|
ValueFrom: envVar.ValueFrom,
|
|
}
|
|
if variable.ValueFrom != nil {
|
|
variable.Value = evalValueFrom(variable.ValueFrom, &container, pod,
|
|
configMaps, secrets)
|
|
}
|
|
vars = append(vars, variable)
|
|
}
|
|
vars = append(vars, evalEnvFrom(container, configMaps, secrets)...)
|
|
|
|
volume_mounts := extractContainerMounts(container, pod)
|
|
|
|
containers = append(containers, Container{
|
|
Name: container.Name,
|
|
Image: container.Image,
|
|
Ports: container.Ports,
|
|
Resources: container.Resources,
|
|
Env: vars,
|
|
Commands: container.Command,
|
|
Args: container.Args,
|
|
VolumeMounts: volume_mounts,
|
|
SecurityContext: container.SecurityContext,
|
|
Status: extractContainerStatus(pod, &container),
|
|
LivenessProbe: container.LivenessProbe,
|
|
ReadinessProbe: container.ReadinessProbe,
|
|
StartupProbe: container.StartupProbe,
|
|
Lifecycle: container.Lifecycle,
|
|
ImagePullPolicy: container.ImagePullPolicy,
|
|
})
|
|
}
|
|
return containers
|
|
}
|
|
|
|
func evalEnvFrom(container v1.Container, configMaps *v1.ConfigMapList, secrets *v1.SecretList) []EnvVar {
|
|
vars := make([]EnvVar, 0)
|
|
for _, envFromVar := range container.EnvFrom {
|
|
switch {
|
|
case envFromVar.ConfigMapRef != nil:
|
|
name := envFromVar.ConfigMapRef.LocalObjectReference.Name
|
|
for _, configMap := range configMaps.Items {
|
|
if configMap.ObjectMeta.Name == name {
|
|
for key, value := range configMap.Data {
|
|
valueFrom := &v1.EnvVarSource{
|
|
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: name,
|
|
},
|
|
Key: key,
|
|
},
|
|
}
|
|
variable := EnvVar{
|
|
Name: envFromVar.Prefix + key,
|
|
Value: value,
|
|
ValueFrom: valueFrom,
|
|
}
|
|
vars = append(vars, variable)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
case envFromVar.SecretRef != nil:
|
|
name := envFromVar.SecretRef.LocalObjectReference.Name
|
|
for _, secret := range secrets.Items {
|
|
if secret.ObjectMeta.Name == name {
|
|
for key, value := range secret.Data {
|
|
valueFrom := &v1.EnvVarSource{
|
|
SecretKeyRef: &v1.SecretKeySelector{
|
|
LocalObjectReference: v1.LocalObjectReference{
|
|
Name: name,
|
|
},
|
|
Key: key,
|
|
},
|
|
}
|
|
variable := EnvVar{
|
|
Name: envFromVar.Prefix + key,
|
|
Value: base64.StdEncoding.EncodeToString(value),
|
|
ValueFrom: valueFrom,
|
|
}
|
|
vars = append(vars, variable)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return vars
|
|
}
|
|
|
|
// evalValueFrom evaluates environment value from given source. For more details check:
|
|
// https://github.com/kubernetes/kubernetes/blob/d82e51edc5f02bff39661203c9b503d054c3493b/pkg/kubectl/describe.go#L1056
|
|
func evalValueFrom(src *v1.EnvVarSource, container *v1.Container, pod *v1.Pod, configMaps *v1.ConfigMapList, secrets *v1.SecretList) string {
|
|
switch {
|
|
case src.ConfigMapKeyRef != nil:
|
|
name := src.ConfigMapKeyRef.LocalObjectReference.Name
|
|
for _, configMap := range configMaps.Items {
|
|
if configMap.ObjectMeta.Name == name {
|
|
return configMap.Data[src.ConfigMapKeyRef.Key]
|
|
}
|
|
}
|
|
case src.SecretKeyRef != nil:
|
|
name := src.SecretKeyRef.LocalObjectReference.Name
|
|
for _, secret := range secrets.Items {
|
|
if secret.ObjectMeta.Name == name {
|
|
return base64.StdEncoding.EncodeToString([]byte(
|
|
secret.Data[src.SecretKeyRef.Key]))
|
|
}
|
|
}
|
|
case src.ResourceFieldRef != nil:
|
|
valueFrom, err := extractContainerResourceValue(src.ResourceFieldRef, container)
|
|
if err != nil {
|
|
valueFrom = ""
|
|
}
|
|
resource := src.ResourceFieldRef.Resource
|
|
if valueFrom == "0" && (resource == "limits.cpu" || resource == "limits.memory") {
|
|
valueFrom = "node allocatable"
|
|
}
|
|
return valueFrom
|
|
case src.FieldRef != nil:
|
|
gv, err := schema.ParseGroupVersion(src.FieldRef.APIVersion)
|
|
if err != nil {
|
|
global.Log.Warn(err.Error())
|
|
return ""
|
|
}
|
|
gvk := gv.WithKind("Pod")
|
|
internalFieldPath, _, err := runtime.NewScheme().ConvertFieldLabel(gvk, src.FieldRef.FieldPath, "")
|
|
if err != nil {
|
|
global.Log.Warn(err.Error())
|
|
return ""
|
|
}
|
|
valueFrom, err := ExtractFieldPathAsString(pod, internalFieldPath)
|
|
if err != nil {
|
|
global.Log.Warn(err.Error())
|
|
return ""
|
|
}
|
|
return valueFrom
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func extractContainerMounts(container v1.Container, pod *v1.Pod) []VolumeMount {
|
|
volume_mounts := make([]VolumeMount, 0)
|
|
for _, a_volume_mount := range container.VolumeMounts {
|
|
volume_mount := VolumeMount{
|
|
Name: a_volume_mount.Name,
|
|
ReadOnly: a_volume_mount.ReadOnly,
|
|
MountPath: a_volume_mount.MountPath,
|
|
SubPath: a_volume_mount.SubPath,
|
|
Volume: getVolume(pod.Spec.Volumes, a_volume_mount.Name),
|
|
}
|
|
volume_mounts = append(volume_mounts, volume_mount)
|
|
}
|
|
return volume_mounts
|
|
}
|
|
|
|
func extractContainerStatus(pod *v1.Pod, container *v1.Container) *v1.ContainerStatus {
|
|
for _, status := range pod.Status.ContainerStatuses {
|
|
if status.Name == container.Name {
|
|
return &status
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// extractContainerResourceValue extracts the value of a resource in an already known container.
|
|
func extractContainerResourceValue(fs *v1.ResourceFieldSelector, container *v1.Container) (string,
|
|
error) {
|
|
divisor := res.Quantity{}
|
|
if divisor.Cmp(fs.Divisor) == 0 {
|
|
divisor = res.MustParse("1")
|
|
} else {
|
|
divisor = fs.Divisor
|
|
}
|
|
|
|
switch fs.Resource {
|
|
case "limits.cpu":
|
|
return strconv.FormatInt(int64(math.Ceil(float64(container.Resources.Limits.
|
|
Cpu().MilliValue())/float64(divisor.MilliValue()))), 10), nil
|
|
case "limits.memory":
|
|
return strconv.FormatInt(int64(math.Ceil(float64(container.Resources.Limits.
|
|
Memory().Value())/float64(divisor.Value()))), 10), nil
|
|
case "requests.cpu":
|
|
return strconv.FormatInt(int64(math.Ceil(float64(container.Resources.Requests.
|
|
Cpu().MilliValue())/float64(divisor.MilliValue()))), 10), nil
|
|
case "requests.memory":
|
|
return strconv.FormatInt(int64(math.Ceil(float64(container.Resources.Requests.
|
|
Memory().Value())/float64(divisor.Value()))), 10), nil
|
|
}
|
|
|
|
return "", fmt.Errorf("Unsupported container resource : %v", fs.Resource)
|
|
}
|
|
|
|
func getVolume(volumes []v1.Volume, volumeName string) v1.Volume {
|
|
for _, volume := range volumes {
|
|
if volume.Name == volumeName {
|
|
// yes, this is exponential, but N is VERY small, so the malloc for creating a named dictionary would probably take longer
|
|
return volume
|
|
}
|
|
}
|
|
return v1.Volume{}
|
|
}
|