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  }