istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/inject/app_probe.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package inject implements kube-inject or webhoook autoinject feature to inject sidecar.
    16  // This file is focused on rewriting Kubernetes app probers to support mutual TLS.
    17  package inject
    18  
    19  import (
    20  	"encoding/json"
    21  	"strconv"
    22  
    23  	corev1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/util/intstr"
    25  
    26  	"istio.io/api/annotation"
    27  	"istio.io/istio/pilot/cmd/pilot-agent/status"
    28  	"istio.io/istio/pkg/log"
    29  	"istio.io/istio/pkg/slices"
    30  )
    31  
    32  // ShouldRewriteAppHTTPProbers returns if we should rewrite apps' probers config.
    33  func ShouldRewriteAppHTTPProbers(annotations map[string]string, specSetting bool) bool {
    34  	if annotations != nil {
    35  		if value, ok := annotations[annotation.SidecarRewriteAppHTTPProbers.Name]; ok {
    36  			if isSetInAnnotation, err := strconv.ParseBool(value); err == nil {
    37  				return isSetInAnnotation
    38  			}
    39  		}
    40  	}
    41  	return specSetting
    42  }
    43  
    44  // FindSidecar returns the pointer to the first container whose name matches the "istio-proxy".
    45  func FindSidecar(pod *corev1.Pod) *corev1.Container {
    46  	return FindContainerFromPod(ProxyContainerName, pod)
    47  }
    48  
    49  // FindContainerFromPod returns the pointer to the first container whose name matches in init containers or regular containers
    50  func FindContainerFromPod(name string, pod *corev1.Pod) *corev1.Container {
    51  	if c := FindContainer(name, pod.Spec.Containers); c != nil {
    52  		return c
    53  	}
    54  	return FindContainer(name, pod.Spec.InitContainers)
    55  }
    56  
    57  // FindContainer returns the pointer to the first container whose name matches.
    58  func FindContainer(name string, containers []corev1.Container) *corev1.Container {
    59  	for i := range containers {
    60  		if containers[i].Name == name {
    61  			return &containers[i]
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  // convertAppProber returns an overwritten `Probe` for pilot agent to take over.
    68  func convertAppProber(probe *corev1.Probe, newURL string, statusPort int) *corev1.Probe {
    69  	if probe == nil {
    70  		return nil
    71  	}
    72  	if probe.HTTPGet != nil {
    73  		return convertAppProberHTTPGet(probe, newURL, statusPort)
    74  	} else if probe.TCPSocket != nil {
    75  		return convertAppProberTCPSocket(probe, newURL, statusPort)
    76  	} else if probe.GRPC != nil {
    77  		return convertAppProberGRPC(probe, newURL, statusPort)
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  // convertAppProberHTTPGet returns an overwritten `Probe` (HttpGet) for pilot agent to take over.
    84  func convertAppProberHTTPGet(probe *corev1.Probe, newURL string, statusPort int) *corev1.Probe {
    85  	p := probe.DeepCopy()
    86  	// Change the application container prober config.
    87  	p.HTTPGet.Port = intstr.FromInt32(int32(statusPort))
    88  	p.HTTPGet.Path = newURL
    89  	// For HTTPS prober, we change to HTTP,
    90  	// and pilot agent uses https to request application prober endpoint.
    91  	// Kubelet -> HTTP -> Pilot Agent -> HTTPS -> Application
    92  	if p.HTTPGet.Scheme == corev1.URISchemeHTTPS {
    93  		p.HTTPGet.Scheme = corev1.URISchemeHTTP
    94  	}
    95  	return p
    96  }
    97  
    98  // convertAppProberTCPSocket returns an overwritten `Probe` (TcpSocket) for pilot agent to take over.
    99  func convertAppProberTCPSocket(probe *corev1.Probe, newURL string, statusPort int) *corev1.Probe {
   100  	p := probe.DeepCopy()
   101  	// the sidecar intercepts all tcp connections, so we change it to a HTTP probe and the sidecar will check tcp
   102  	p.HTTPGet = &corev1.HTTPGetAction{}
   103  	p.HTTPGet.Port = intstr.FromInt32(int32(statusPort))
   104  	p.HTTPGet.Path = newURL
   105  
   106  	p.TCPSocket = nil
   107  	return p
   108  }
   109  
   110  // convertAppProberGRPC returns an overwritten `Probe` (gRPC) for pilot agent to take over.
   111  func convertAppProberGRPC(probe *corev1.Probe, newURL string, statusPort int) *corev1.Probe {
   112  	p := probe.DeepCopy()
   113  	// the sidecar intercepts all gRPC connections, so we change it to a HTTP probe and the sidecar will check gRPC
   114  	p.HTTPGet = &corev1.HTTPGetAction{}
   115  	p.HTTPGet.Port = intstr.FromInt32(int32(statusPort))
   116  	p.HTTPGet.Path = newURL
   117  	// For gRPC prober, we change to HTTP,
   118  	// and pilot agent uses gRPC to request application prober endpoint.
   119  	// Kubelet -> HTTP -> Pilot Agent -> gRPC -> Application
   120  	p.GRPC = nil
   121  	return p
   122  }
   123  
   124  type KubeAppProbers map[string]*Prober
   125  
   126  // Prober represents a single container prober
   127  type Prober struct {
   128  	HTTPGet        *corev1.HTTPGetAction   `json:"httpGet,omitempty"`
   129  	TCPSocket      *corev1.TCPSocketAction `json:"tcpSocket,omitempty"`
   130  	GRPC           *corev1.GRPCAction      `json:"grpc,omitempty"`
   131  	TimeoutSeconds int32                   `json:"timeoutSeconds,omitempty"`
   132  }
   133  
   134  // DumpAppProbers returns a json encoded string as `status.KubeAppProbers`.
   135  // Also update the probers so that all usages of named port will be resolved to integer.
   136  func DumpAppProbers(pod *corev1.Pod, targetPort int32) string {
   137  	out := KubeAppProbers{}
   138  	updateNamedPort := func(p *Prober, portMap map[string]int32) *Prober {
   139  		if p == nil {
   140  			return nil
   141  		}
   142  		if p.GRPC != nil {
   143  			// don't need to update for gRPC probe port as it only supports integer
   144  			return p
   145  		}
   146  		if p.HTTPGet == nil && p.TCPSocket == nil {
   147  			return nil
   148  		}
   149  
   150  		var probePort *intstr.IntOrString
   151  		if p.HTTPGet != nil {
   152  			probePort = &p.HTTPGet.Port
   153  		} else {
   154  			probePort = &p.TCPSocket.Port
   155  		}
   156  
   157  		if probePort.Type == intstr.String {
   158  			port, exists := portMap[probePort.StrVal]
   159  			if !exists {
   160  				return nil
   161  			}
   162  			*probePort = intstr.FromInt32(port)
   163  		} else if probePort.IntVal == targetPort {
   164  			// Already is rewritten
   165  			return nil
   166  		}
   167  		return p
   168  	}
   169  	for _, c := range allContainers(pod) {
   170  		if c.Name == ProxyContainerName {
   171  			continue
   172  		}
   173  		readyz, livez, startupz := status.FormatProberURL(c.Name)
   174  		portMap := map[string]int32{}
   175  		for _, p := range c.Ports {
   176  			if p.Name != "" {
   177  				portMap[p.Name] = p.ContainerPort
   178  			}
   179  		}
   180  		if h := updateNamedPort(kubeProbeToInternalProber(c.ReadinessProbe), portMap); h != nil {
   181  			out[readyz] = h
   182  		}
   183  		if h := updateNamedPort(kubeProbeToInternalProber(c.LivenessProbe), portMap); h != nil {
   184  			out[livez] = h
   185  		}
   186  		if h := updateNamedPort(kubeProbeToInternalProber(c.StartupProbe), portMap); h != nil {
   187  			out[startupz] = h
   188  		}
   189  
   190  	}
   191  	// prevent generate '{}'
   192  	if len(out) == 0 {
   193  		return ""
   194  	}
   195  	b, err := json.Marshal(out)
   196  	if err != nil {
   197  		log.Errorf("failed to serialize the app prober config %v", err)
   198  		return ""
   199  	}
   200  	return string(b)
   201  }
   202  
   203  func allContainers(pod *corev1.Pod) []corev1.Container {
   204  	return append(slices.Clone(pod.Spec.InitContainers), pod.Spec.Containers...)
   205  }
   206  
   207  // patchRewriteProbe generates the patch for webhook.
   208  func patchRewriteProbe(annotations map[string]string, pod *corev1.Pod, defaultPort int32) {
   209  	statusPort := int(defaultPort)
   210  	if v, f := annotations[annotation.SidecarStatusPort.Name]; f {
   211  		p, err := strconv.Atoi(v)
   212  		if err != nil {
   213  			log.Errorf("Invalid annotation %v=%v: %v", annotation.SidecarStatusPort.Name, v, err)
   214  		}
   215  		statusPort = p
   216  	}
   217  	for i, c := range pod.Spec.Containers {
   218  		// Skip sidecar container.
   219  		if c.Name == ProxyContainerName {
   220  			continue
   221  		}
   222  		convertProbe(&c, statusPort)
   223  		pod.Spec.Containers[i] = c
   224  	}
   225  	for i, c := range pod.Spec.InitContainers {
   226  		// Skip sidecar container.
   227  		if c.Name == ProxyContainerName {
   228  			continue
   229  		}
   230  		convertProbe(&c, statusPort)
   231  		pod.Spec.InitContainers[i] = c
   232  	}
   233  }
   234  
   235  func convertProbe(c *corev1.Container, statusPort int) {
   236  	readyz, livez, startupz := status.FormatProberURL(c.Name)
   237  	if probePatch := convertAppProber(c.ReadinessProbe, readyz, statusPort); probePatch != nil {
   238  		c.ReadinessProbe = probePatch
   239  	}
   240  	if probePatch := convertAppProber(c.LivenessProbe, livez, statusPort); probePatch != nil {
   241  		c.LivenessProbe = probePatch
   242  	}
   243  	if probePatch := convertAppProber(c.StartupProbe, startupz, statusPort); probePatch != nil {
   244  		c.StartupProbe = probePatch
   245  	}
   246  }
   247  
   248  // kubeProbeToInternalProber converts a Kubernetes Probe to an Istio internal Prober
   249  func kubeProbeToInternalProber(probe *corev1.Probe) *Prober {
   250  	if probe == nil {
   251  		return nil
   252  	}
   253  
   254  	if probe.HTTPGet != nil {
   255  		return &Prober{
   256  			HTTPGet:        probe.HTTPGet,
   257  			TimeoutSeconds: probe.TimeoutSeconds,
   258  		}
   259  	}
   260  
   261  	if probe.TCPSocket != nil {
   262  		return &Prober{
   263  			TCPSocket:      probe.TCPSocket,
   264  			TimeoutSeconds: probe.TimeoutSeconds,
   265  		}
   266  	}
   267  
   268  	if probe.GRPC != nil {
   269  		return &Prober{
   270  			GRPC:           probe.GRPC,
   271  			TimeoutSeconds: probe.TimeoutSeconds,
   272  		}
   273  	}
   274  
   275  	return nil
   276  }