k8s.io/kubernetes@v1.29.3/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) (*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  			ImageRuntimeHandler:  containerStatus.ImageRuntimeHandler,
   277  			Hash:                 containerStatus.Hash,
   278  			HashWithoutResources: containerStatus.HashWithoutResources,
   279  			State:                containerStatus.State,
   280  		}
   281  		runningPod.Containers = append(runningPod.Containers, container)
   282  	}
   283  
   284  	// Populate sandboxes in kubecontainer.Pod
   285  	for _, sandbox := range podStatus.SandboxStatuses {
   286  		runningPod.Sandboxes = append(runningPod.Sandboxes, &Container{
   287  			ID:    ContainerID{Type: runtimeName, ID: sandbox.Id},
   288  			State: SandboxToContainerState(sandbox.State),
   289  		})
   290  	}
   291  	return runningPod
   292  }
   293  
   294  // SandboxToContainerState converts runtimeapi.PodSandboxState to
   295  // kubecontainer.State.
   296  // This is only needed because we need to return sandboxes as if they were
   297  // kubecontainer.Containers to avoid substantial changes to PLEG.
   298  // TODO: Remove this once it becomes obsolete.
   299  func SandboxToContainerState(state runtimeapi.PodSandboxState) State {
   300  	switch state {
   301  	case runtimeapi.PodSandboxState_SANDBOX_READY:
   302  		return ContainerStateRunning
   303  	case runtimeapi.PodSandboxState_SANDBOX_NOTREADY:
   304  		return ContainerStateExited
   305  	}
   306  	return ContainerStateUnknown
   307  }
   308  
   309  // GetContainerSpec gets the container spec by containerName.
   310  func GetContainerSpec(pod *v1.Pod, containerName string) *v1.Container {
   311  	var containerSpec *v1.Container
   312  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   313  		if containerName == c.Name {
   314  			containerSpec = c
   315  			return false
   316  		}
   317  		return true
   318  	})
   319  	return containerSpec
   320  }
   321  
   322  // HasPrivilegedContainer returns true if any of the containers in the pod are privileged.
   323  func HasPrivilegedContainer(pod *v1.Pod) bool {
   324  	var hasPrivileged bool
   325  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   326  		if c.SecurityContext != nil && c.SecurityContext.Privileged != nil && *c.SecurityContext.Privileged {
   327  			hasPrivileged = true
   328  			return false
   329  		}
   330  		return true
   331  	})
   332  	return hasPrivileged
   333  }
   334  
   335  // HasWindowsHostProcessContainer returns true if any of the containers in a pod are HostProcess containers.
   336  func HasWindowsHostProcessContainer(pod *v1.Pod) bool {
   337  	var hasHostProcess bool
   338  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   339  		if sc.HasWindowsHostProcessRequest(pod, c) {
   340  			hasHostProcess = true
   341  			return false
   342  		}
   343  		return true
   344  	})
   345  
   346  	return hasHostProcess
   347  }
   348  
   349  // AllContainersAreWindowsHostProcess returns true if all containers in a pod are HostProcess containers.
   350  func AllContainersAreWindowsHostProcess(pod *v1.Pod) bool {
   351  	allHostProcess := true
   352  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *v1.Container, containerType podutil.ContainerType) bool {
   353  		if !sc.HasWindowsHostProcessRequest(pod, c) {
   354  			allHostProcess = false
   355  			return false
   356  		}
   357  		return true
   358  	})
   359  
   360  	return allHostProcess
   361  }
   362  
   363  // MakePortMappings creates internal port mapping from api port mapping.
   364  func MakePortMappings(container *v1.Container) (ports []PortMapping) {
   365  	names := make(map[string]struct{})
   366  	for _, p := range container.Ports {
   367  		pm := PortMapping{
   368  			HostPort:      int(p.HostPort),
   369  			ContainerPort: int(p.ContainerPort),
   370  			Protocol:      p.Protocol,
   371  			HostIP:        p.HostIP,
   372  		}
   373  
   374  		// We need to determine the address family this entry applies to. We do this to ensure
   375  		// duplicate containerPort / protocol rules work across different address families.
   376  		// https://github.com/kubernetes/kubernetes/issues/82373
   377  		family := "any"
   378  		if p.HostIP != "" {
   379  			if utilsnet.IsIPv6String(p.HostIP) {
   380  				family = "v6"
   381  			} else {
   382  				family = "v4"
   383  			}
   384  		}
   385  
   386  		var name = p.Name
   387  		if name == "" {
   388  			name = fmt.Sprintf("%s-%s-%s:%d:%d", family, p.Protocol, p.HostIP, p.ContainerPort, p.HostPort)
   389  		}
   390  
   391  		// Protect against a port name being used more than once in a container.
   392  		if _, ok := names[name]; ok {
   393  			klog.InfoS("Port name conflicted, it is defined more than once", "portName", name)
   394  			continue
   395  		}
   396  		ports = append(ports, pm)
   397  		names[name] = struct{}{}
   398  	}
   399  	return
   400  }
   401  
   402  // HasAnyRegularContainerStarted returns true if any regular container has
   403  // started, which indicates all init containers have been initialized.
   404  func HasAnyRegularContainerStarted(spec *v1.PodSpec, statuses []v1.ContainerStatus) bool {
   405  	if len(statuses) == 0 {
   406  		return false
   407  	}
   408  
   409  	containerNames := make(map[string]struct{})
   410  	for _, c := range spec.Containers {
   411  		containerNames[c.Name] = struct{}{}
   412  	}
   413  
   414  	for _, status := range statuses {
   415  		if _, ok := containerNames[status.Name]; !ok {
   416  			continue
   417  		}
   418  		if status.State.Running != nil || status.State.Terminated != nil {
   419  			return true
   420  		}
   421  	}
   422  
   423  	return false
   424  }