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 }