k8s.io/kubernetes@v1.29.3/pkg/kubelet/prober/prober.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 prober
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/client-go/tools/record"
    27  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    28  	"k8s.io/kubernetes/pkg/kubelet/events"
    29  	"k8s.io/kubernetes/pkg/kubelet/prober/results"
    30  	"k8s.io/kubernetes/pkg/kubelet/util/format"
    31  	"k8s.io/kubernetes/pkg/probe"
    32  	execprobe "k8s.io/kubernetes/pkg/probe/exec"
    33  	grpcprobe "k8s.io/kubernetes/pkg/probe/grpc"
    34  	httpprobe "k8s.io/kubernetes/pkg/probe/http"
    35  	tcpprobe "k8s.io/kubernetes/pkg/probe/tcp"
    36  	"k8s.io/utils/exec"
    37  
    38  	"k8s.io/klog/v2"
    39  )
    40  
    41  const maxProbeRetries = 3
    42  
    43  // Prober helps to check the liveness/readiness/startup of a container.
    44  type prober struct {
    45  	exec   execprobe.Prober
    46  	http   httpprobe.Prober
    47  	tcp    tcpprobe.Prober
    48  	grpc   grpcprobe.Prober
    49  	runner kubecontainer.CommandRunner
    50  
    51  	recorder record.EventRecorder
    52  }
    53  
    54  // NewProber creates a Prober, it takes a command runner and
    55  // several container info managers.
    56  func newProber(
    57  	runner kubecontainer.CommandRunner,
    58  	recorder record.EventRecorder) *prober {
    59  
    60  	const followNonLocalRedirects = false
    61  	return &prober{
    62  		exec:     execprobe.New(),
    63  		http:     httpprobe.New(followNonLocalRedirects),
    64  		tcp:      tcpprobe.New(),
    65  		grpc:     grpcprobe.New(),
    66  		runner:   runner,
    67  		recorder: recorder,
    68  	}
    69  }
    70  
    71  // recordContainerEvent should be used by the prober for all container related events.
    72  func (pb *prober) recordContainerEvent(pod *v1.Pod, container *v1.Container, eventType, reason, message string, args ...interface{}) {
    73  	ref, err := kubecontainer.GenerateContainerRef(pod, container)
    74  	if err != nil {
    75  		klog.ErrorS(err, "Can't make a ref to pod and container", "pod", klog.KObj(pod), "containerName", container.Name)
    76  		return
    77  	}
    78  	pb.recorder.Eventf(ref, eventType, reason, message, args...)
    79  }
    80  
    81  // probe probes the container.
    82  func (pb *prober) probe(ctx context.Context, probeType probeType, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (results.Result, error) {
    83  	var probeSpec *v1.Probe
    84  	switch probeType {
    85  	case readiness:
    86  		probeSpec = container.ReadinessProbe
    87  	case liveness:
    88  		probeSpec = container.LivenessProbe
    89  	case startup:
    90  		probeSpec = container.StartupProbe
    91  	default:
    92  		return results.Failure, fmt.Errorf("unknown probe type: %q", probeType)
    93  	}
    94  
    95  	if probeSpec == nil {
    96  		klog.InfoS("Probe is nil", "probeType", probeType, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name)
    97  		return results.Success, nil
    98  	}
    99  
   100  	result, output, err := pb.runProbeWithRetries(ctx, probeType, probeSpec, pod, status, container, containerID, maxProbeRetries)
   101  	if err != nil || (result != probe.Success && result != probe.Warning) {
   102  		// Probe failed in one way or another.
   103  		if err != nil {
   104  			klog.V(1).ErrorS(err, "Probe errored", "probeType", probeType, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name)
   105  			pb.recordContainerEvent(pod, &container, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe errored: %v", probeType, err)
   106  		} else { // result != probe.Success
   107  			klog.V(1).InfoS("Probe failed", "probeType", probeType, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name, "probeResult", result, "output", output)
   108  			pb.recordContainerEvent(pod, &container, v1.EventTypeWarning, events.ContainerUnhealthy, "%s probe failed: %s", probeType, output)
   109  		}
   110  		return results.Failure, err
   111  	}
   112  	if result == probe.Warning {
   113  		pb.recordContainerEvent(pod, &container, v1.EventTypeWarning, events.ContainerProbeWarning, "%s probe warning: %s", probeType, output)
   114  		klog.V(3).InfoS("Probe succeeded with a warning", "probeType", probeType, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name, "output", output)
   115  	} else {
   116  		klog.V(3).InfoS("Probe succeeded", "probeType", probeType, "pod", klog.KObj(pod), "podUID", pod.UID, "containerName", container.Name)
   117  	}
   118  	return results.Success, nil
   119  }
   120  
   121  // runProbeWithRetries tries to probe the container in a finite loop, it returns the last result
   122  // if it never succeeds.
   123  func (pb *prober) runProbeWithRetries(ctx context.Context, probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID, retries int) (probe.Result, string, error) {
   124  	var err error
   125  	var result probe.Result
   126  	var output string
   127  	for i := 0; i < retries; i++ {
   128  		result, output, err = pb.runProbe(ctx, probeType, p, pod, status, container, containerID)
   129  		if err == nil {
   130  			return result, output, nil
   131  		}
   132  	}
   133  	return result, output, err
   134  }
   135  
   136  func (pb *prober) runProbe(ctx context.Context, probeType probeType, p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) {
   137  	timeout := time.Duration(p.TimeoutSeconds) * time.Second
   138  	if p.Exec != nil {
   139  		klog.V(4).InfoS("Exec-Probe runProbe", "pod", klog.KObj(pod), "containerName", container.Name, "execCommand", p.Exec.Command)
   140  		command := kubecontainer.ExpandContainerCommandOnlyStatic(p.Exec.Command, container.Env)
   141  		return pb.exec.Probe(pb.newExecInContainer(ctx, container, containerID, command, timeout))
   142  	}
   143  	if p.HTTPGet != nil {
   144  		req, err := httpprobe.NewRequestForHTTPGetAction(p.HTTPGet, &container, status.PodIP, "probe")
   145  		if err != nil {
   146  			return probe.Unknown, "", err
   147  		}
   148  		if klogV4 := klog.V(4); klogV4.Enabled() {
   149  			port := req.URL.Port()
   150  			host := req.URL.Hostname()
   151  			path := req.URL.Path
   152  			scheme := req.URL.Scheme
   153  			headers := p.HTTPGet.HTTPHeaders
   154  			klogV4.InfoS("HTTP-Probe", "scheme", scheme, "host", host, "port", port, "path", path, "timeout", timeout, "headers", headers)
   155  		}
   156  		return pb.http.Probe(req, timeout)
   157  	}
   158  	if p.TCPSocket != nil {
   159  		port, err := probe.ResolveContainerPort(p.TCPSocket.Port, &container)
   160  		if err != nil {
   161  			return probe.Unknown, "", err
   162  		}
   163  		host := p.TCPSocket.Host
   164  		if host == "" {
   165  			host = status.PodIP
   166  		}
   167  		klog.V(4).InfoS("TCP-Probe", "host", host, "port", port, "timeout", timeout)
   168  		return pb.tcp.Probe(host, port, timeout)
   169  	}
   170  
   171  	if p.GRPC != nil {
   172  		host := status.PodIP
   173  		service := ""
   174  		if p.GRPC.Service != nil {
   175  			service = *p.GRPC.Service
   176  		}
   177  		klog.V(4).InfoS("GRPC-Probe", "host", host, "service", service, "port", p.GRPC.Port, "timeout", timeout)
   178  		return pb.grpc.Probe(host, service, int(p.GRPC.Port), timeout)
   179  	}
   180  
   181  	klog.InfoS("Failed to find probe builder for container", "containerName", container.Name)
   182  	return probe.Unknown, "", fmt.Errorf("missing probe handler for %s:%s", format.Pod(pod), container.Name)
   183  }
   184  
   185  type execInContainer struct {
   186  	// run executes a command in a container. Combined stdout and stderr output is always returned. An
   187  	// error is returned if one occurred.
   188  	run    func() ([]byte, error)
   189  	writer io.Writer
   190  }
   191  
   192  func (pb *prober) newExecInContainer(ctx context.Context, container v1.Container, containerID kubecontainer.ContainerID, cmd []string, timeout time.Duration) exec.Cmd {
   193  	return &execInContainer{run: func() ([]byte, error) {
   194  		return pb.runner.RunInContainer(ctx, containerID, cmd, timeout)
   195  	}}
   196  }
   197  
   198  func (eic *execInContainer) Run() error {
   199  	return nil
   200  }
   201  
   202  func (eic *execInContainer) CombinedOutput() ([]byte, error) {
   203  	return eic.run()
   204  }
   205  
   206  func (eic *execInContainer) Output() ([]byte, error) {
   207  	return nil, fmt.Errorf("unimplemented")
   208  }
   209  
   210  func (eic *execInContainer) SetDir(dir string) {
   211  	// unimplemented
   212  }
   213  
   214  func (eic *execInContainer) SetStdin(in io.Reader) {
   215  	// unimplemented
   216  }
   217  
   218  func (eic *execInContainer) SetStdout(out io.Writer) {
   219  	eic.writer = out
   220  }
   221  
   222  func (eic *execInContainer) SetStderr(out io.Writer) {
   223  	eic.writer = out
   224  }
   225  
   226  func (eic *execInContainer) SetEnv(env []string) {
   227  	// unimplemented
   228  }
   229  
   230  func (eic *execInContainer) Stop() {
   231  	// unimplemented
   232  }
   233  
   234  func (eic *execInContainer) Start() error {
   235  	data, err := eic.run()
   236  	if eic.writer != nil {
   237  		// only record the write error, do not cover the command run error
   238  		if p, err := eic.writer.Write(data); err != nil {
   239  			klog.ErrorS(err, "Unable to write all bytes from execInContainer", "expectedBytes", len(data), "actualBytes", p)
   240  		}
   241  	}
   242  	return err
   243  }
   244  
   245  func (eic *execInContainer) Wait() error {
   246  	return nil
   247  }
   248  
   249  func (eic *execInContainer) StdoutPipe() (io.ReadCloser, error) {
   250  	return nil, fmt.Errorf("unimplemented")
   251  }
   252  
   253  func (eic *execInContainer) StderrPipe() (io.ReadCloser, error) {
   254  	return nil, fmt.Errorf("unimplemented")
   255  }