github.com/cilium/cilium@v1.16.2/operator/pkg/ingress/ingress.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package ingress 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/sirupsen/logrus" 11 corev1 "k8s.io/api/core/v1" 12 networkingv1 "k8s.io/api/networking/v1" 13 "k8s.io/apimachinery/pkg/types" 14 ctrl "sigs.k8s.io/controller-runtime" 15 "sigs.k8s.io/controller-runtime/pkg/builder" 16 "sigs.k8s.io/controller-runtime/pkg/client" 17 "sigs.k8s.io/controller-runtime/pkg/event" 18 "sigs.k8s.io/controller-runtime/pkg/handler" 19 "sigs.k8s.io/controller-runtime/pkg/predicate" 20 "sigs.k8s.io/controller-runtime/pkg/reconcile" 21 22 "github.com/cilium/cilium/operator/pkg/model/translation" 23 ciliumv2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 24 ) 25 26 const ( 27 ciliumIngressPrefix = "cilium-ingress" 28 ciliumIngressClassName = "cilium" 29 ) 30 31 // ingressReconciler reconciles a Ingress object 32 type ingressReconciler struct { 33 logger logrus.FieldLogger 34 client client.Client 35 36 lbAnnotationPrefixes []string 37 sharedResourcesName string 38 ciliumNamespace string 39 defaultLoadbalancerMode string 40 defaultSecretNamespace string 41 defaultSecretName string 42 enforcedHTTPS bool 43 defaultRequestTimeout time.Duration 44 45 hostNetworkEnabled bool 46 hostNetworkSharedPort uint32 47 48 cecTranslator translation.CECTranslator 49 dedicatedTranslator translation.Translator 50 } 51 52 func newIngressReconciler( 53 logger logrus.FieldLogger, 54 c client.Client, 55 cecTranslator translation.CECTranslator, 56 dedicatedIngressTranslator translation.Translator, 57 ciliumNamespace string, 58 lbAnnotationPrefixes []string, 59 sharedResourcesName string, 60 defaultLoadbalancerMode string, 61 defaultSecretNamespace string, 62 defaultSecretName string, 63 enforcedHTTPS bool, 64 defaultRequestTimeout time.Duration, 65 hostNetworkEnabled bool, 66 hostNetworkSharedPort uint32, 67 ) *ingressReconciler { 68 return &ingressReconciler{ 69 logger: logger, 70 client: c, 71 72 cecTranslator: cecTranslator, 73 dedicatedTranslator: dedicatedIngressTranslator, 74 75 lbAnnotationPrefixes: lbAnnotationPrefixes, 76 sharedResourcesName: sharedResourcesName, 77 ciliumNamespace: ciliumNamespace, 78 defaultLoadbalancerMode: defaultLoadbalancerMode, 79 defaultSecretNamespace: defaultSecretNamespace, 80 defaultSecretName: defaultSecretName, 81 enforcedHTTPS: enforcedHTTPS, 82 defaultRequestTimeout: defaultRequestTimeout, 83 84 hostNetworkEnabled: hostNetworkEnabled, 85 hostNetworkSharedPort: hostNetworkSharedPort, 86 } 87 } 88 89 // SetupWithManager sets up the controller with the Manager. 90 func (r *ingressReconciler) SetupWithManager(mgr ctrl.Manager) error { 91 return ctrl.NewControllerManagedBy(mgr). 92 // Watch for changed Ingresses that are managed by the Cilium Ingress controller 93 // It's important that this includes cases where the IngressClassName gets changed from `cilium` 94 // to something else. In these cases the controller has to cleanup the resources. 95 For(&networkingv1.Ingress{}, r.forCiliumManagedIngress()). 96 // (LoadBalancer) Service resource with OwnerReference to the Ingress with dedicated loadbalancing mode 97 Owns(&corev1.Service{}). 98 // Endpoints resource with OwnerReference to the Ingress with dedicated loadbalancing mode 99 Owns(&corev1.Endpoints{}). 100 // CiliumEnvoyConfig resource with OwnerReference to the Ingress with dedicated loadbalancing mode 101 Owns(&ciliumv2.CiliumEnvoyConfig{}). 102 // Watching shared loadbalancer Service and reconcile all shared Cilium Ingresses. 103 // It's necessary to reconcile all shared Cilium Ingresses as they all have to potentially update their LoadBalancer status. 104 Watches(&corev1.Service{}, r.enqueueSharedCiliumIngresses(), r.forSharedLoadbalancerService()). 105 // Watching shared CiliumEnvoyConfig and reconcile a non-existing pseudo Cilium Ingress. 106 // Its not necessary to reconcile all shared Cilium Ingresses as they all will update the 107 // shared CEC with the complete model that includes all shared Cilium Ingresses. 108 // This will cover the following cases 109 // - Manual deletion of shared CEC 110 // -> Pseudo Cilium Ingress reconciliation will re-create it 111 // - Manual update of shared CEC 112 // -> Pseudo Cilium Ingress reconciliation will enforce an update of the shared CEC 113 // - Deletion of shared Cilium Ingresses during downtime of the Cilium Operator 114 // -> pseudo Cilium Ingress reconciliation will enforce an update of the shared CEC after the restart 115 Watches(&ciliumv2.CiliumEnvoyConfig{}, r.enqueuePseudoIngress(), r.forSharedCiliumEnvoyConfig()). 116 // Watching Cilium IngressClass for changes being the default Ingress controller 117 // (changed annotation `ingressclass.kubernetes.io/is-default-class`) 118 Watches(&networkingv1.IngressClass{}, r.enqueueIngressesWithoutExplicitClass(), r.forCiliumIngressClass(), withDefaultIngressClassAnnotation()). 119 Complete(r) 120 } 121 122 func (r *ingressReconciler) enqueueSharedCiliumIngresses() handler.EventHandler { 123 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, _ client.Object) []reconcile.Request { 124 ingressList := networkingv1.IngressList{} 125 if err := r.client.List(ctx, &ingressList); err != nil { 126 r.logger.WithError(err).Warn("Failed to list Ingresses") 127 return nil 128 } 129 130 result := []reconcile.Request{} 131 132 for _, i := range ingressList.Items { 133 // Skip Ingresses that aren't managed by Cilium 134 if !isCiliumManagedIngress(ctx, r.client, r.logger, i) { 135 continue 136 } 137 138 // Skip Ingresses with dedicated loadbalancer mode 139 if r.isEffectiveLoadbalancerModeDedicated(&i) { 140 continue 141 } 142 143 result = append(result, reconcile.Request{ 144 NamespacedName: types.NamespacedName{ 145 Namespace: i.Namespace, 146 Name: i.Name, 147 }, 148 }) 149 } 150 151 return result 152 }) 153 } 154 155 func (r *ingressReconciler) enqueueIngressesWithoutExplicitClass() handler.EventHandler { 156 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, _ client.Object) []reconcile.Request { 157 ingressList := networkingv1.IngressList{} 158 if err := r.client.List(ctx, &ingressList); err != nil { 159 r.logger.WithError(err).Warn("Failed to list Ingresses") 160 return nil 161 } 162 163 result := []reconcile.Request{} 164 165 for _, i := range ingressList.Items { 166 if i.Spec.IngressClassName != nil { 167 continue 168 } 169 result = append(result, reconcile.Request{ 170 NamespacedName: types.NamespacedName{ 171 Namespace: i.Namespace, 172 Name: i.Name, 173 }, 174 }) 175 } 176 177 return result 178 }) 179 } 180 181 func (r *ingressReconciler) enqueuePseudoIngress() handler.EventHandler { 182 return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, _ client.Object) []reconcile.Request { 183 return []reconcile.Request{ 184 { 185 NamespacedName: types.NamespacedName{ 186 Namespace: r.ciliumNamespace, 187 Name: "pseudo-ingress", 188 }, 189 }, 190 } 191 }) 192 } 193 194 func (r *ingressReconciler) forSharedLoadbalancerService() builder.WatchesOption { 195 return builder.WithPredicates(&matchesInstancePredicate{namespace: r.ciliumNamespace, name: r.sharedResourcesName}) 196 } 197 198 func (r *ingressReconciler) forSharedCiliumEnvoyConfig() builder.WatchesOption { 199 return builder.WithPredicates(&matchesInstancePredicate{namespace: r.ciliumNamespace, name: r.sharedResourcesName}) 200 } 201 202 func (r *ingressReconciler) forCiliumIngressClass() builder.WatchesOption { 203 return builder.WithPredicates(&matchesInstancePredicate{namespace: "", name: ciliumIngressClassName}) 204 } 205 206 func withDefaultIngressClassAnnotation() builder.WatchesOption { 207 return builder.WithPredicates(&defaultIngressClassPredicate{}) 208 } 209 210 func (r *ingressReconciler) forCiliumManagedIngress() builder.ForOption { 211 return builder.WithPredicates(&matchesCiliumRelevantIngressPredicate{client: r.client, logger: r.logger}) 212 } 213 214 var _ predicate.Predicate = &matchesInstancePredicate{} 215 216 type matchesInstancePredicate struct { 217 namespace string 218 name string 219 } 220 221 func (r *matchesInstancePredicate) Create(event event.CreateEvent) bool { 222 return event.Object.GetNamespace() == r.namespace && event.Object.GetName() == r.name 223 } 224 225 func (r *matchesInstancePredicate) Update(event event.UpdateEvent) bool { 226 return event.ObjectNew.GetNamespace() == r.namespace && event.ObjectNew.GetName() == r.name 227 } 228 229 func (r *matchesInstancePredicate) Delete(event event.DeleteEvent) bool { 230 return event.Object.GetNamespace() == r.namespace && event.Object.GetName() == r.name 231 } 232 233 func (r *matchesInstancePredicate) Generic(event event.GenericEvent) bool { 234 return event.Object.GetNamespace() == r.namespace && event.Object.GetName() == r.name 235 } 236 237 var _ predicate.Predicate = &defaultIngressClassPredicate{} 238 239 type defaultIngressClassPredicate struct{} 240 241 func (r *defaultIngressClassPredicate) Create(event event.CreateEvent) bool { 242 return r.isIngressClassMarkedAsDefault(event.Object) 243 } 244 245 func (r *defaultIngressClassPredicate) Update(event event.UpdateEvent) bool { 246 oldIngressClassDefault := r.isIngressClassMarkedAsDefault(event.ObjectOld) 247 newIngressClassDefault := r.isIngressClassMarkedAsDefault(event.ObjectNew) 248 249 return (oldIngressClassDefault || newIngressClassDefault) && 250 // Only in case of a change 251 oldIngressClassDefault != newIngressClassDefault 252 } 253 254 func (r *defaultIngressClassPredicate) Delete(event event.DeleteEvent) bool { 255 return r.isIngressClassMarkedAsDefault(event.Object) 256 } 257 258 func (r *defaultIngressClassPredicate) Generic(event event.GenericEvent) bool { 259 return r.isIngressClassMarkedAsDefault(event.Object) 260 } 261 262 func (r *defaultIngressClassPredicate) isIngressClassMarkedAsDefault(o client.Object) bool { 263 ingressClass, ok := o.(*networkingv1.IngressClass) 264 if !ok { 265 return false 266 } 267 268 isDefault, err := isIngressClassMarkedAsDefault(*ingressClass) 269 if err != nil { 270 return false 271 } 272 273 return isDefault 274 } 275 276 var _ predicate.Predicate = &matchesCiliumRelevantIngressPredicate{} 277 278 type matchesCiliumRelevantIngressPredicate struct { 279 client client.Client 280 logger logrus.FieldLogger 281 } 282 283 func (r *matchesCiliumRelevantIngressPredicate) Create(event event.CreateEvent) bool { 284 return r.isCiliumManagedIngress(event.Object) 285 } 286 287 func (r *matchesCiliumRelevantIngressPredicate) Update(event event.UpdateEvent) bool { 288 return r.isCiliumManagedIngress(event.ObjectOld) || r.isCiliumManagedIngress(event.ObjectNew) 289 } 290 291 func (r *matchesCiliumRelevantIngressPredicate) Delete(event event.DeleteEvent) bool { 292 return r.isCiliumManagedIngress(event.Object) 293 } 294 295 func (r *matchesCiliumRelevantIngressPredicate) Generic(event event.GenericEvent) bool { 296 return r.isCiliumManagedIngress(event.Object) 297 } 298 299 func (r *matchesCiliumRelevantIngressPredicate) isCiliumManagedIngress(o client.Object) bool { 300 ingress, ok := o.(*networkingv1.Ingress) 301 if !ok { 302 return false 303 } 304 305 return isCiliumManagedIngress(context.Background(), r.client, r.logger, *ingress) 306 }