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