Files
PandaX/apps/devops/services/k8s/pods/pod_detail.go
2022-01-26 14:13:23 +08:00

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{}
}