...

Source file src/k8s.io/kubernetes/pkg/kubelet/lifecycle/handlers.go

Documentation: k8s.io/kubernetes/pkg/kubelet/lifecycle

     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  }
   242  

View as plain text