k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/lifecycle/handlers.go (about) 1 /* 2 Copyright 2014 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 lifecycle 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "net/http" 25 "net/url" 26 "strconv" 27 "strings" 28 "time" 29 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/intstr" 33 utilfeature "k8s.io/apiserver/pkg/util/feature" 34 "k8s.io/client-go/tools/record" 35 "k8s.io/klog/v2" 36 "k8s.io/kubernetes/pkg/features" 37 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 38 "k8s.io/kubernetes/pkg/kubelet/metrics" 39 kubetypes "k8s.io/kubernetes/pkg/kubelet/types" 40 "k8s.io/kubernetes/pkg/kubelet/util/format" 41 httpprobe "k8s.io/kubernetes/pkg/probe/http" 42 "k8s.io/kubernetes/pkg/security/apparmor" 43 ) 44 45 const ( 46 maxRespBodyLength = 10 * 1 << 10 // 10KB 47 ) 48 49 type handlerRunner struct { 50 httpDoer kubetypes.HTTPDoer 51 commandRunner kubecontainer.CommandRunner 52 containerManager podStatusProvider 53 eventRecorder record.EventRecorder 54 } 55 56 type podStatusProvider interface { 57 GetPodStatus(ctx context.Context, uid types.UID, name, namespace string) (*kubecontainer.PodStatus, error) 58 } 59 60 // NewHandlerRunner returns a configured lifecycle handler for a container. 61 func NewHandlerRunner(httpDoer kubetypes.HTTPDoer, commandRunner kubecontainer.CommandRunner, containerManager podStatusProvider, eventRecorder record.EventRecorder) kubecontainer.HandlerRunner { 62 return &handlerRunner{ 63 httpDoer: httpDoer, 64 commandRunner: commandRunner, 65 containerManager: containerManager, 66 eventRecorder: eventRecorder, 67 } 68 } 69 70 func (hr *handlerRunner) Run(ctx context.Context, containerID kubecontainer.ContainerID, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler) (string, error) { 71 switch { 72 case handler.Exec != nil: 73 var msg string 74 // TODO(tallclair): Pass a proper timeout value. 75 output, err := hr.commandRunner.RunInContainer(ctx, containerID, handler.Exec.Command, 0) 76 if err != nil { 77 msg = fmt.Sprintf("Exec lifecycle hook (%v) for Container %q in Pod %q failed - error: %v, message: %q", handler.Exec.Command, container.Name, format.Pod(pod), err, string(output)) 78 klog.V(1).ErrorS(err, "Exec lifecycle hook for Container in Pod failed", "execCommand", handler.Exec.Command, "containerName", container.Name, "pod", klog.KObj(pod), "message", string(output)) 79 } 80 return msg, err 81 case handler.HTTPGet != nil: 82 err := hr.runHTTPHandler(ctx, pod, container, handler, hr.eventRecorder) 83 var msg string 84 if err != nil { 85 msg = fmt.Sprintf("HTTP lifecycle hook (%s) for Container %q in Pod %q failed - error: %v", handler.HTTPGet.Path, container.Name, format.Pod(pod), err) 86 klog.V(1).ErrorS(err, "HTTP lifecycle hook for Container in Pod failed", "path", handler.HTTPGet.Path, "containerName", container.Name, "pod", klog.KObj(pod)) 87 } 88 return msg, err 89 case handler.Sleep != nil: 90 err := hr.runSleepHandler(ctx, handler.Sleep.Seconds) 91 var msg string 92 if err != nil { 93 msg = fmt.Sprintf("Sleep lifecycle hook (%d) for Container %q in Pod %q failed - error: %v", handler.Sleep.Seconds, container.Name, format.Pod(pod), err) 94 klog.V(1).ErrorS(err, "Sleep lifecycle hook for Container in Pod failed", "sleepSeconds", handler.Sleep.Seconds, "containerName", container.Name, "pod", klog.KObj(pod)) 95 } 96 return msg, err 97 default: 98 err := fmt.Errorf("invalid handler: %v", handler) 99 msg := fmt.Sprintf("Cannot run handler: %v", err) 100 klog.ErrorS(err, "Cannot run handler") 101 return msg, err 102 } 103 } 104 105 // resolvePort attempts to turn an IntOrString port reference into a concrete port number. 106 // If portReference has an int value, it is treated as a literal, and simply returns that value. 107 // If portReference is a string, an attempt is first made to parse it as an integer. If that fails, 108 // an attempt is made to find a port with the same name in the container spec. 109 // If a port with the same name is found, it's ContainerPort value is returned. If no matching 110 // port is found, an error is returned. 111 func resolvePort(portReference intstr.IntOrString, container *v1.Container) (int, error) { 112 if portReference.Type == intstr.Int { 113 return portReference.IntValue(), nil 114 } 115 portName := portReference.StrVal 116 port, err := strconv.Atoi(portName) 117 if err == nil { 118 return port, nil 119 } 120 for _, portSpec := range container.Ports { 121 if portSpec.Name == portName { 122 return int(portSpec.ContainerPort), nil 123 } 124 } 125 return -1, fmt.Errorf("couldn't find port: %v in %v", portReference, container) 126 } 127 128 func (hr *handlerRunner) runSleepHandler(ctx context.Context, seconds int64) error { 129 if !utilfeature.DefaultFeatureGate.Enabled(features.PodLifecycleSleepAction) { 130 return nil 131 } 132 c := time.After(time.Duration(seconds) * time.Second) 133 select { 134 case <-ctx.Done(): 135 // unexpected termination 136 metrics.LifecycleHandlerSleepTerminated.Inc() 137 return fmt.Errorf("container terminated before sleep hook finished") 138 case <-c: 139 return nil 140 } 141 } 142 143 func (hr *handlerRunner) runHTTPHandler(ctx context.Context, pod *v1.Pod, container *v1.Container, handler *v1.LifecycleHandler, eventRecorder record.EventRecorder) error { 144 host := handler.HTTPGet.Host 145 podIP := host 146 if len(host) == 0 { 147 status, err := hr.containerManager.GetPodStatus(ctx, pod.UID, pod.Name, pod.Namespace) 148 if err != nil { 149 klog.ErrorS(err, "Unable to get pod info, event handlers may be invalid.", "pod", klog.KObj(pod)) 150 return err 151 } 152 if len(status.IPs) == 0 { 153 return fmt.Errorf("failed to find networking container: %v", status) 154 } 155 host = status.IPs[0] 156 podIP = host 157 } 158 159 req, err := httpprobe.NewRequestForHTTPGetAction(handler.HTTPGet, container, podIP, "lifecycle") 160 if err != nil { 161 return err 162 } 163 resp, err := hr.httpDoer.Do(req) 164 discardHTTPRespBody(resp) 165 166 if isHTTPResponseError(err) { 167 klog.V(1).ErrorS(err, "HTTPS request to lifecycle hook got HTTP response, retrying with HTTP.", "pod", klog.KObj(pod), "host", req.URL.Host) 168 169 req := req.Clone(context.Background()) 170 req.URL.Scheme = "http" 171 req.Header.Del("Authorization") 172 resp, httpErr := hr.httpDoer.Do(req) 173 174 // clear err since the fallback succeeded 175 if httpErr == nil { 176 metrics.LifecycleHandlerHTTPFallbacks.Inc() 177 if eventRecorder != nil { 178 // report the fallback with an event 179 eventRecorder.Event(pod, v1.EventTypeWarning, "LifecycleHTTPFallback", fmt.Sprintf("request to HTTPS lifecycle hook %s got HTTP response, retry with HTTP succeeded", req.URL.Host)) 180 } 181 err = nil 182 } 183 discardHTTPRespBody(resp) 184 } 185 return err 186 } 187 188 func discardHTTPRespBody(resp *http.Response) { 189 if resp == nil { 190 return 191 } 192 193 // Ensure the response body is fully read and closed 194 // before we reconnect, so that we reuse the same TCP 195 // connection. 196 defer resp.Body.Close() 197 198 if resp.ContentLength <= maxRespBodyLength { 199 io.Copy(io.Discard, &io.LimitedReader{R: resp.Body, N: maxRespBodyLength}) 200 } 201 } 202 203 // NewAppArmorAdmitHandler returns a PodAdmitHandler which is used to evaluate 204 // if a pod can be admitted from the perspective of AppArmor. 205 func NewAppArmorAdmitHandler(validator apparmor.Validator) PodAdmitHandler { 206 return &appArmorAdmitHandler{ 207 Validator: validator, 208 } 209 } 210 211 type appArmorAdmitHandler struct { 212 apparmor.Validator 213 } 214 215 func (a *appArmorAdmitHandler) Admit(attrs *PodAdmitAttributes) PodAdmitResult { 216 // If the pod is already running or terminated, no need to recheck AppArmor. 217 if attrs.Pod.Status.Phase != v1.PodPending { 218 return PodAdmitResult{Admit: true} 219 } 220 221 err := a.Validate(attrs.Pod) 222 if err == nil { 223 return PodAdmitResult{Admit: true} 224 } 225 return PodAdmitResult{ 226 Admit: false, 227 Reason: "AppArmor", 228 Message: fmt.Sprintf("Cannot enforce AppArmor: %v", err), 229 } 230 } 231 232 func isHTTPResponseError(err error) bool { 233 if err == nil { 234 return false 235 } 236 urlErr := &url.Error{} 237 if !errors.As(err, &urlErr) { 238 return false 239 } 240 return strings.Contains(urlErr.Err.Error(), "server gave HTTP response to HTTPS client") 241 }