github.com/cilium/cilium@v1.16.2/pkg/k8s/utils/utils.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package utils 5 6 import ( 7 "net" 8 "strings" 9 10 v1 "k8s.io/api/core/v1" 11 discoveryv1 "k8s.io/api/discovery/v1" 12 v1meta "k8s.io/apimachinery/pkg/apis/meta/v1" 13 14 "github.com/cilium/cilium/pkg/ip" 15 k8sconst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io" 16 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 17 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels" 18 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/selection" 19 labelsPkg "github.com/cilium/cilium/pkg/labels" 20 "github.com/cilium/cilium/pkg/slices" 21 ) 22 23 const ( 24 // ServiceProxyNameLabel is the label for service proxy name in k8s service related 25 // objects. 26 serviceProxyNameLabel = "service.kubernetes.io/service-proxy-name" 27 // EndpointSliceMeshControllerName is a unique value used with LabelManagedBy to indicate 28 // the component managing an EndpointSlice. 29 EndpointSliceMeshControllerName = "endpointslice-mesh-controller.cilium.io" 30 ) 31 32 type NamespaceNameGetter interface { 33 GetNamespace() string 34 GetName() string 35 } 36 37 // ExtractNamespace extracts the namespace of ObjectMeta. 38 // For cluster scoped objects the Namespace field is empty and this function 39 // assumes that the object is returned from kubernetes itself implying that 40 // the namespace is empty only and only when the Object is cluster scoped 41 // and thus returns empty namespace for such objects. 42 func ExtractNamespace(np NamespaceNameGetter) string { 43 return np.GetNamespace() 44 } 45 46 // ExtractNamespaceOrDefault extracts the namespace of ObjectMeta, it returns default 47 // namespace if the namespace field in the ObjectMeta is empty. 48 func ExtractNamespaceOrDefault(np NamespaceNameGetter) string { 49 ns := np.GetNamespace() 50 if ns == "" { 51 return v1.NamespaceDefault 52 } 53 54 return ns 55 } 56 57 // GetObjNamespaceName returns the object's namespace and name. 58 // If the object is cluster scoped then the function returns only the object name 59 // without any namespace prefix. 60 func GetObjNamespaceName(obj NamespaceNameGetter) string { 61 ns := ExtractNamespace(obj) 62 if ns == "" { 63 return obj.GetName() 64 } 65 66 return ns + "/" + obj.GetName() 67 } 68 69 // GetEndpointSliceListOptionsModifier returns the options modifier for endpointSlice object list. 70 // This methods returns a ListOptions modifier which adds a label selector to 71 // select all endpointSlice objects they are not from remote clusters in Cilium cluster mesh. 72 // This is mostly the same behavior as kube-proxy except the cluster mesh behavior which is 73 // tied to how Cilium internally works with clustermesh endpoints and that this function also doesn't ignore headless Services. 74 // Given label mirroring from the service objects to endpoint slice objects were introduced in Kubernetes PR 94443, 75 // and released as part of Kubernetes v1.20; we can start using GetServiceAndEndpointListOptionsModifier for 76 // endpoint slices when dropping support for Kubernetes v1.19 and older. We can do that since the 77 // serviceProxyNameLabel label will then be mirrored to endpoint slices for services with that label. 78 // We also ignore Kubernetes endpoints coming from other clusters in the Cilium clustermesh here as 79 // Cilium does not rely on mirrored Kubernetes EndpointSlice for any of its functionalities. 80 func GetEndpointSliceListOptionsModifier() (func(options *v1meta.ListOptions), error) { 81 nonRemoteEndpointSelector, err := labels.NewRequirement(discoveryv1.LabelManagedBy, selection.NotEquals, []string{EndpointSliceMeshControllerName}) 82 if err != nil { 83 return nil, err 84 } 85 86 labelSelector := labels.NewSelector() 87 labelSelector = labelSelector.Add(*nonRemoteEndpointSelector) 88 89 return func(options *v1meta.ListOptions) { 90 options.LabelSelector = labelSelector.String() 91 }, nil 92 } 93 94 // GetServiceAndEndpointListOptionsModifier returns the options modifier for service and endpoint object lists. 95 // This methods returns a ListOptions modifier which adds a label selector to only 96 // select services that are in context of Cilium. 97 // Unlike kube-proxy Cilium does not select services/endpoints containing k8s headless service label. 98 // We honor service.kubernetes.io/service-proxy-name label in the service object and only 99 // handle services that match our service proxy name. If the service proxy name for Cilium 100 // is an empty string, we assume that Cilium is the default service handler in which case 101 // we select all services that don't have the above mentioned label. 102 func GetServiceAndEndpointListOptionsModifier(k8sServiceProxy string) (func(options *v1meta.ListOptions), error) { 103 var ( 104 serviceNameSelector *labels.Requirement 105 err error 106 ) 107 108 if k8sServiceProxy == "" { 109 serviceNameSelector, err = labels.NewRequirement( 110 serviceProxyNameLabel, selection.DoesNotExist, nil) 111 } else { 112 serviceNameSelector, err = labels.NewRequirement( 113 serviceProxyNameLabel, selection.DoubleEquals, []string{k8sServiceProxy}) 114 } 115 116 if err != nil { 117 return nil, err 118 } 119 120 labelSelector := labels.NewSelector() 121 labelSelector = labelSelector.Add(*serviceNameSelector) 122 123 return func(options *v1meta.ListOptions) { 124 options.LabelSelector = labelSelector.String() 125 }, nil 126 } 127 128 // GetLatestPodReadiness returns the lastest podReady condition on a given pod. 129 func GetLatestPodReadiness(podStatus slim_corev1.PodStatus) slim_corev1.ConditionStatus { 130 for _, cond := range podStatus.Conditions { 131 if cond.Type == slim_corev1.PodReady { 132 return cond.Status 133 } 134 } 135 return slim_corev1.ConditionUnknown 136 } 137 138 // ValidIPs return a sorted slice of unique IP addresses retrieved from the given PodStatus. 139 // Returns an error when no IPs are found. 140 func ValidIPs(podStatus slim_corev1.PodStatus) []string { 141 if len(podStatus.PodIPs) == 0 && len(podStatus.PodIP) == 0 { 142 return nil 143 } 144 145 // make it a set first to avoid repeated IP addresses 146 ips := []string{} 147 if podStatus.PodIP != "" { 148 ips = append(ips, podStatus.PodIP) 149 } 150 151 for _, podIP := range podStatus.PodIPs { 152 if podIP.IP != "" { 153 ips = append(ips, podIP.IP) 154 } 155 } 156 157 return slices.SortedUnique(ips) 158 } 159 160 // IsPodRunning returns true if the pod is considered to be in running state. 161 // We consider a Running pod a pod that does not report a Failed nor a Succeeded 162 // pod Phase. 163 func IsPodRunning(status slim_corev1.PodStatus) bool { 164 switch status.Phase { 165 case slim_corev1.PodFailed, slim_corev1.PodSucceeded: 166 return false 167 } 168 return true 169 } 170 171 // GetClusterIPByFamily returns a service clusterip by family. 172 // From - https://github.com/kubernetes/kubernetes/blob/release-1.20/pkg/proxy/util/utils.go#L386-L411 173 func GetClusterIPByFamily(ipFamily slim_corev1.IPFamily, service *slim_corev1.Service) string { 174 // allowing skew 175 if len(service.Spec.IPFamilies) == 0 { 176 if len(service.Spec.ClusterIP) == 0 || service.Spec.ClusterIP == v1.ClusterIPNone { 177 return "" 178 } 179 180 IsIPv6Family := (ipFamily == slim_corev1.IPv6Protocol) 181 if IsIPv6Family == ip.IsIPv6(net.ParseIP(service.Spec.ClusterIP)) { 182 return service.Spec.ClusterIP 183 } 184 185 return "" 186 } 187 188 for idx, family := range service.Spec.IPFamilies { 189 if family == ipFamily { 190 if idx < len(service.Spec.ClusterIPs) { 191 return service.Spec.ClusterIPs[idx] 192 } 193 } 194 } 195 196 return "" 197 } 198 199 // nameLabelsGetter is an interface that returns the name and the labels for 200 // the namespace. 201 type nameLabelsGetter interface { 202 GetName() string 203 GetLabels() map[string]string 204 } 205 206 // filterPodLabels returns a copy of the given labels map, without the labels owned by Cilium. 207 func filterPodLabels(labels map[string]string) map[string]string { 208 res := map[string]string{} 209 for k, v := range labels { 210 if strings.HasPrefix(k, k8sconst.LabelPrefix) { 211 continue 212 } 213 res[k] = v 214 } 215 return res 216 } 217 218 // SanitizePodLabels makes sure that no important pod labels were overridden manually on k8s pod 219 // object creation. 220 func SanitizePodLabels(podLabels map[string]string, namespace nameLabelsGetter, serviceAccount, clusterName string) map[string]string { 221 sanitizedLabels := filterPodLabels(podLabels) 222 223 // Sanitize namespace labels 224 for k, v := range namespace.GetLabels() { 225 sanitizedLabels[joinPath(k8sconst.PodNamespaceMetaLabels, k)] = v 226 } 227 // Sanitize namespace name label 228 sanitizedLabels[k8sconst.PodNamespaceLabel] = namespace.GetName() 229 // Sanitize service account name 230 if serviceAccount != "" { 231 sanitizedLabels[k8sconst.PolicyLabelServiceAccount] = serviceAccount 232 } else { 233 delete(sanitizedLabels, k8sconst.PolicyLabelServiceAccount) 234 } 235 // Sanitize cluster name 236 sanitizedLabels[k8sconst.PolicyLabelCluster] = clusterName 237 238 return sanitizedLabels 239 } 240 241 // StripPodSpecialLabels strips labels that are not supposed to be coming from a k8s pod object update. 242 func StripPodSpecialLabels(labels map[string]string) map[string]string { 243 sanitizedLabels := make(map[string]string) 244 for k, v := range filterPodLabels(labels) { 245 // If the key contains the prefix for namespace labels then we will 246 // ignore it. 247 if strings.HasPrefix(k, k8sconst.PodNamespaceMetaLabels) { 248 continue 249 } 250 // Also ignore it if the key is a kubernetes namespace label. 251 if k == k8sconst.PodNamespaceLabel { 252 continue 253 } 254 sanitizedLabels[k] = v 255 } 256 return sanitizedLabels 257 } 258 259 // joinPath mimics JoinPath from pkg/policy/utils, which could not be imported here due to circular dependency 260 func joinPath(a, b string) string { 261 return a + labelsPkg.PathDelimiter + b 262 }