k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/container/helpers.go (about)

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package container
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"hash/fnv"
    24  	"strings"
    25  
    26  	"k8s.io/klog/v2"
    27  
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/client-go/tools/record"
    33  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    34  	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
    35  	sc "k8s.io/kubernetes/pkg/securitycontext"
    36  	hashutil "k8s.io/kubernetes/pkg/util/hash"
    37  	"k8s.io/kubernetes/third_party/forked/golang/expansion"
    38  	utilsnet "k8s.io/utils/net"
    39  )
    40  
    41  // HandlerRunner runs a lifecycle handler for a container.
    42  type HandlerRunner interface {
    43  	Run(ctx context.Context, containerID ContainerID, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler) (string, error)
    44  }
    45  
    46  // RuntimeHelper wraps kubelet to make container runtime
    47  // able to get necessary informations like the RunContainerOptions, DNS settings, Host IP.
    48  type RuntimeHelper interface {
    49  	GenerateRunContainerOptions(ctx context.Context, pod *v1.Pod, container *v1.Container, podIP string, podIPs []string) (contOpts *RunContainerOptions, cleanupAction func(), err error)
    50  	GetPodDNS(pod *v1.Pod) (dnsConfig *runtimeapi.DNSConfig, err error)
    51  	// GetPodCgroupParent returns the CgroupName identifier, and its literal cgroupfs form on the host
    52  	// of a pod.
    53  	GetPodCgroupParent(pod *v1.Pod) string
    54  	GetPodDir(podUID types.UID) string
    55  	GeneratePodHostNameAndDomain(pod *v1.Pod) (hostname string, hostDomain string, err error)
    56  	// GetExtraSupplementalGroupsForPod returns a list of the extra
    57  	// supplemental groups for the Pod. These extra supplemental groups come
    58  	// from annotations on persistent volumes that the pod depends on.
    59  	GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64
    60  
    61  	// GetOrCreateUserNamespaceMappings returns the configuration for the sandbox user namespace
    62  	GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error)
    63  
    64  	// PrepareDynamicResources prepares resources for a pod.
    65  	PrepareDynamicResources(pod *v1.Pod) error
    66  
    67  	// UnprepareDynamicResources unprepares resources for a a pod.
    68  	UnprepareDynamicResources(pod *v1.Pod) error
    69  }
    70  
    71  // ShouldContainerBeRestarted checks whether a container needs to be restarted.
    72  // TODO(yifan): Think about how to refactor this.
    73  func ShouldContainerBeRestarted(container *v1.Container, pod *v1.Pod, podStatus *PodStatus) bool {
    74  	// Once a pod has been marked deleted, it should not be restarted
    75  	if pod.DeletionTimestamp != nil {
    76  		return false
    77  	}
    78  	// Get latest container status.
    79  	status := podStatus.FindContainerStatusByName(container.Name)
    80  	// If the container was never started before, we should start it.
    81  	// NOTE(random-liu): If all historical containers were GC'd, we'll also return true here.
    82  	if status == nil {
    83  		return true
    84  	}
    85  	// Check whether container is running
    86  	if status.State == ContainerStateRunning {
    87  		return false
    88  	}
    89  	// Always restart container in the unknown, or in the created state.
    90  	if status.State == ContainerStateUnknown || status.State == ContainerStateCreated {
    91  		return true
    92  	}
    93  	// Check RestartPolicy for dead container
    94  	if pod.Spec.RestartPolicy == v1.RestartPolicyNever {
    95  		klog.V(4).InfoS("Already ran container, do nothing", "pod", klog.KObj(pod), "containerName", container.Name)
    96  		return false
    97  	}
    98  	if pod.Spec.RestartPolicy == v1.RestartPolicyOnFailure {
    99  		// Check the exit code.
   100  		if status.ExitCode == 0 {
   101  			klog.V(4).InfoS("Already successfully ran container, do nothing", "pod", klog.KObj(pod), "containerName", container.Name)
   102  			return false
   103  		}
   104  	}
   105  	return true
   106  }
   107  
   108  // HashContainer returns the hash of the container. It is used to compare
   109  // the running container with its desired spec.
   110  // Note: remember to update hashValues in container_hash_test.go as well.
   111  func HashContainer(container *v1.Container) uint64 {
   112  	hash := fnv.New32a()
   113  	// Omit nil or empty field when calculating hash value
   114  	// Please see https://github.com/kubernetes/kubernetes/issues/53644
   115  	containerJSON, _ := json.Marshal(container)
   116  	hashutil.DeepHashObject(hash, containerJSON)
   117  	return uint64(hash.Sum32())
   118  }
   119  
   120  // HashContainerWithoutResources returns the hash of the container with Resources field zero'd out.
   121  func HashContainerWithoutResources(container *v1.Container) uint64 {
   122  	// InPlacePodVerticalScaling enables mutable Resources field.
   123  	// Changes to this field may not require container restart depending on policy.
   124  	// Compute hash over fields besides the Resources field
   125  	// NOTE: This is needed during alpha and beta so that containers using Resources but
   126  	//       not subject to In-place resize are not unexpectedly restarted when
   127  	//       InPlacePodVerticalScaling feature-gate is toggled.
   128  	//TODO(vinaykul,InPlacePodVerticalScaling): Remove this in GA+1 and make HashContainerWithoutResources to become Hash.
   129  	hashWithoutResources := fnv.New32a()
   130  	containerCopy := container.DeepCopy()
   131  	containerCopy.Resources = v1.ResourceRequirements{}
   132  	containerJSON, _ := json.Marshal(containerCopy)
   133  	hashutil.DeepHashObject(hashWithoutResources, containerJSON)
   134  	return uint64(hashWithoutResources.Sum32())
   135  }
   136  
   137  // envVarsToMap constructs a map of environment name to value from a slice
   138  // of env vars.
   139  func envVarsToMap(envs []EnvVar) map[string]string {
   140  	result := map[string]string{}
   141  	for _, env := range envs {
   142  		result[env.Name] = env.Value
   143  	}
   144  	return result
   145  }
   146  
   147  // v1EnvVarsToMap constructs a map of environment name to value from a slice
   148  // of env vars.
   149  func v1EnvVarsToMap(envs []v1.EnvVar) map[string]string {
   150  	result := map[string]string{}
   151  	for _, env := range envs {
   152  		result[env.Name] = env.Value
   153  	}
   154  
   155  	return result
   156  }
   157  
   158  // ExpandContainerCommandOnlyStatic substitutes only static environment variable values from the
   159  // container environment definitions. This does *not* include valueFrom substitutions.
   160  // TODO: callers should use ExpandContainerCommandAndArgs with a fully resolved list of environment.
   161  func ExpandContainerCommandOnlyStatic(containerCommand []string, envs []v1.EnvVar) (command []string) {
   162  	mapping := expansion.MappingFuncFor(v1EnvVarsToMap(envs))
   163  	if len(containerCommand) != 0 {
   164  		for _, cmd := range containerCommand {
   165  			command = append(command, expansion.Expand(cmd, mapping))
   166  		}
   167  	}
   168  	return command
   169  }
   170  
   171  // ExpandContainerVolumeMounts expands the subpath of the given VolumeMount by replacing variable references with the values of given EnvVar.
   172  func ExpandContainerVolumeMounts(mount v1.VolumeMount, envs []EnvVar) (string, error) {
   173  
   174  	envmap := envVarsToMap(envs)
   175  	missingKeys := sets.NewString()
   176  	expanded := expansion.Expand(mount.SubPathExpr, func(key string) string {
   177  		value, ok := envmap[key]
   178  		if !ok || len(value) == 0 {
   179  			missingKeys.Insert(key)
   180  		}
   181  		return value
   182  	})
   183  
   184  	if len(missingKeys) > 0 {
   185  		return "", fmt.Errorf("missing value for %s", strings.Join(missingKeys.List(), ", "))
   186  	}
   187  	return expanded, nil
   188  }
   189  
   190  // ExpandContainerCommandAndArgs expands the given Container's command by replacing variable references `with the values of given EnvVar.
   191  func ExpandContainerCommandAndArgs(container *v1.Container, envs []EnvVar) (command []string, args []string) {
   192  	mapping := expansion.MappingFuncFor(envVarsToMap(envs))
   193  
   194  	if len(container.Command) != 0 {
   195  		for _, cmd := range container.Command {
   196  			command = append(command, expansion.Expand(cmd, mapping))
   197  		}
   198  	}
   199  
   200  	if len(container.Args) != 0 {
   201  		for _, arg := range container.Args {
   202  			args = append(args, expansion.Expand(arg, mapping))
   203  		}
   204  	}
   205  
   206  	return command, args
   207  }
   208  
   209  // FilterEventRecorder creates an event recorder to record object's event except implicitly required container's, like infra container.
   210  func FilterEventRecorder(recorder record.EventRecorder) record.EventRecorder {
   211  	return &innerEventRecorder{
   212  		recorder: recorder,
   213  	}
   214  }
   215  
   216  type innerEventRecorder struct {
   217  	recorder record.EventRecorder
   218  }
   219  
   220  func (irecorder *innerEventRecorder) shouldRecordEvent(object runtime.Object) (*v1.ObjectReference, bool) {
   221  	if ref, ok := object.(*v1.ObjectReference); ok {
   222  		// this check is needed AFTER the cast. See https://github.com/kubernetes/kubernetes/issues/95552
   223  		if ref == nil {
   224  			return nil, false
   225  		}
   226  		if !strings.HasPrefix(ref.FieldPath, ImplicitContainerPrefix) {
   227  			return ref, true
   228  		}
   229  	}
   230  	return nil, false
   231  }
   232  
   233  func (irecorder *innerEventRecorder) Event(object runtime.Object, eventtype, reason, message string) {
   234  	if ref, ok := irecorder.shouldRecordEvent(object); ok {
   235  		irecorder.recorder.Event(ref, eventtype, reason, message)
   236  	}
   237  }
   238  
   239  func (irecorder *innerEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
   240  	if ref, ok := irecorder.shouldRecordEvent(object); ok {
   241  		irecorder.recorder.Eventf(ref, eventtype, reason, messageFmt, args...)
   242  	}
   243  
   244  }
   245  
   246  func (irecorder *innerEventRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
   247  	if ref, ok := irecorder.shouldRecordEvent(object); ok {
   248  		irecorder.recorder.AnnotatedEventf(ref, annotations, eventtype, reason, messageFmt, args...)
   249  	}
   250  
   251  }
   252  
   253  // IsHostNetworkPod returns whether the host networking requested for the given Pod.
   254  // Pod must not be nil.
   255  func IsHostNetworkPod(pod *v1.Pod) bool {
   256  	return pod.Spec.HostNetwork
   257  }
   258  
   259  // ConvertPodStatusToRunningPod returns Pod given PodStatus and container runtime string.
   260  // TODO(random-liu): Convert PodStatus to running Pod, should be deprecated soon
   261  func ConvertPodStatusToRunningPod(runtimeName string, podStatus *PodStatus) Pod {
   262  	runningPod := Pod{
   263  		ID:        podStatus.ID,
   264  		Name:      podStatus.Name,
   265  		Namespace: podStatus.Namespace,
   266  	}
   267  	for _, containerStatus := range podStatus.ContainerStatuses {
   268  		if containerStatus.State != ContainerStateRunning {
   269  			continue
   270  		}
   271  		container := &Container{
   272  			ID:                   containerStatus.ID,
   273  			Name:                 containerStatus.Name,
   274  			Image:                containerStatus.Image,
   275  			ImageID:              containerStatus.ImageID,
   276  			ImageRef:             containerStatus.ImageRef,
   277  			ImageRuntimeHandler:  containerStatus.ImageRuntimeHandler,
   278  			Hash:                 containerStatus.Hash,
   279  			HashWithoutResources: containerStatus.HashWithoutResources,
   280  			State:                containerStatus.State,
   281  		}
   282  		runningPod.Containers = append(runningPod.Containers, container)
   283  	}
   284  
   285  	// Populate sandboxes in kubecontainer.Pod
   286  	for _, sandbox := range podStatus.SandboxStatuses {
   287  		runningPod.Sandboxes = append(runningPod.Sandboxes, &Container{
   288  			ID:    ContainerID{Type: runtimeName, ID: sandbox.Id},
   289  			State: SandboxToContainerState(sandbox.State),
   290  		})
   291  	}
   292  	return runningPod
   293  }
   294  
   295  // SandboxToContainerState converts runtimeapi.PodSandboxState to
   296  // kubecontainer.State.
   297  // This is only needed because we need to return sandboxes as if they were
   298  // kubecontainer.Containers to avoid substantial changes to PLEG.
   299  // TODO: Remove this once it becomes obsolete.
   300  func SandboxToContainerState(state runtimeapi.PodSandboxState) State {
   301  	switch state {
   302  	case runtimeapi.PodSandboxState_SANDBOX_READY:
   303  		return ContainerStateRunning
   304  	case runtimeapi.PodSandboxState_SANDBOX_NOTREADY:
   305  		return ContainerStateExited
   306  	}
   307  	return ContainerStateUnknown
   308  }
   309  
   310  // GetContainerSpec gets the container spec by containerName.
   311  func GetContainerSpec(pod *v1.Pod, containerName string) *v1.Container {
   312  	var containerSpec *v1.Container
   313  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   314  		if containerName == c.Name {
   315  			containerSpec = c
   316  			return false
   317  		}
   318  		return true
   319  	})
   320  	return containerSpec
   321  }
   322  
   323  // HasPrivilegedContainer returns true if any of the containers in the pod are privileged.
   324  func HasPrivilegedContainer(pod *v1.Pod) bool {
   325  	var hasPrivileged bool
   326  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   327  		if c.SecurityContext != nil && c.SecurityContext.Privileged != nil && *c.SecurityContext.Privileged {
   328  			hasPrivileged = true
   329  			return false
   330  		}
   331  		return true
   332  	})
   333  	return hasPrivileged
   334  }
   335  
   336  // HasWindowsHostProcessContainer returns true if any of the containers in a pod are HostProcess containers.
   337  func HasWindowsHostProcessContainer(pod *v1.Pod) bool {
   338  	var hasHostProcess bool
   339  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   340  		if sc.HasWindowsHostProcessRequest(pod, c) {
   341  			hasHostProcess = true
   342  			return false
   343  		}
   344  		return true
   345  	})
   346  
   347  	return hasHostProcess
   348  }
   349  
   350  // AllContainersAreWindowsHostProcess returns true if all containers in a pod are HostProcess containers.
   351  func AllContainersAreWindowsHostProcess(pod *v1.Pod) bool {
   352  	allHostProcess := true
   353  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   354  		if !sc.HasWindowsHostProcessRequest(pod, c) {
   355  			allHostProcess = false
   356  			return false
   357  		}
   358  		return true
   359  	})
   360  
   361  	return allHostProcess
   362  }
   363  
   364  // MakePortMappings creates internal port mapping from api port mapping.
   365  func MakePortMappings(container *v1.Container) (ports []PortMapping) {
   366  	names := make(map[string]struct{})
   367  	for _, p := range container.Ports {
   368  		pm := PortMapping{
   369  			HostPort:      int(p.HostPort),
   370  			ContainerPort: int(p.ContainerPort),
   371  			Protocol:      p.Protocol,
   372  			HostIP:        p.HostIP,
   373  		}
   374  
   375  		// We need to determine the address family this entry applies to. We do this to ensure
   376  		// duplicate containerPort / protocol rules work across different address families.
   377  		// https://github.com/kubernetes/kubernetes/issues/82373
   378  		family := "any"
   379  		if p.HostIP != "" {
   380  			if utilsnet.IsIPv6String(p.HostIP) {
   381  				family = "v6"
   382  			} else {
   383  				family = "v4"
   384  			}
   385  		}
   386  
   387  		var name = p.Name
   388  		if name == "" {
   389  			name = fmt.Sprintf("%s-%s-%s:%d:%d", family, p.Protocol, p.HostIP, p.ContainerPort, p.HostPort)
   390  		}
   391  
   392  		// Protect against a port name being used more than once in a container.
   393  		if _, ok := names[name]; ok {
   394  			klog.InfoS("Port name conflicted, it is defined more than once", "portName", name)
   395  			continue
   396  		}
   397  		ports = append(ports, pm)
   398  		names[name] = struct{}{}
   399  	}
   400  	return
   401  }
   402  
   403  // HasAnyRegularContainerStarted returns true if any regular container has
   404  // started, which indicates all init containers have been initialized.
   405  func HasAnyRegularContainerStarted(spec *v1.PodSpec, statuses []v1.ContainerStatus) bool {
   406  	if len(statuses) == 0 {
   407  		return false
   408  	}
   409  
   410  	containerNames := make(map[string]struct{})
   411  	for _, c := range spec.Containers {
   412  		containerNames[c.Name] = struct{}{}
   413  	}
   414  
   415  	for _, status := range statuses {
   416  		if _, ok := containerNames[status.Name]; !ok {
   417  			continue
   418  		}
   419  		if status.State.Running != nil || status.State.Terminated != nil {
   420  			return true
   421  		}
   422  	}
   423  
   424  	return false
   425  }