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 }