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 }