github.com/cilium/cilium@v1.16.2/operator/pkg/gateway-api/cell.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package gateway_api 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 11 "github.com/cilium/hive/cell" 12 "github.com/sirupsen/logrus" 13 "github.com/spf13/pflag" 14 corev1 "k8s.io/api/core/v1" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 "k8s.io/apimachinery/pkg/runtime" 17 "k8s.io/apimachinery/pkg/runtime/schema" 18 ctrlRuntime "sigs.k8s.io/controller-runtime" 19 gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" 20 gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" 21 gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" 22 mcsapiv1alpha1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" 23 24 operatorOption "github.com/cilium/cilium/operator/option" 25 "github.com/cilium/cilium/operator/pkg/model/translation" 26 gatewayApiTranslation "github.com/cilium/cilium/operator/pkg/model/translation/gateway-api" 27 "github.com/cilium/cilium/operator/pkg/secretsync" 28 k8sClient "github.com/cilium/cilium/pkg/k8s/client" 29 "github.com/cilium/cilium/pkg/option" 30 ) 31 32 // Cell manages the Gateway API related controllers. 33 var Cell = cell.Module( 34 "gateway-api", 35 "Manages the Gateway API controllers", 36 37 cell.Config(gatewayApiConfig{ 38 EnableGatewayAPISecretsSync: true, 39 EnableGatewayAPIProxyProtocol: false, 40 EnableGatewayAPIAppProtocol: false, 41 EnableGatewayAPIAlpn: false, 42 GatewayAPIServiceExternalTrafficPolicy: "Cluster", 43 GatewayAPISecretsNamespace: "cilium-secrets", 44 GatewayAPIXffNumTrustedHops: 0, 45 46 GatewayAPIHostnetworkEnabled: false, 47 GatewayAPIHostnetworkNodelabelselector: "", 48 }), 49 cell.Invoke(initGatewayAPIController), 50 cell.Provide(registerSecretSync), 51 ) 52 53 var requiredGVK = []schema.GroupVersionKind{ 54 gatewayv1.SchemeGroupVersion.WithKind("gatewayclasses"), 55 gatewayv1.SchemeGroupVersion.WithKind("gateways"), 56 gatewayv1.SchemeGroupVersion.WithKind("httproutes"), 57 gatewayv1.SchemeGroupVersion.WithKind("grpcroutes"), 58 gatewayv1beta1.SchemeGroupVersion.WithKind("referencegrants"), 59 gatewayv1alpha2.SchemeGroupVersion.WithKind("tlsroutes"), 60 } 61 62 type gatewayApiConfig struct { 63 KubeProxyReplacement string 64 EnableNodePort bool 65 66 EnableGatewayAPISecretsSync bool 67 EnableGatewayAPIProxyProtocol bool 68 EnableGatewayAPIAppProtocol bool 69 EnableGatewayAPIAlpn bool 70 GatewayAPIServiceExternalTrafficPolicy string 71 GatewayAPISecretsNamespace string 72 GatewayAPIXffNumTrustedHops uint32 73 74 GatewayAPIHostnetworkEnabled bool 75 GatewayAPIHostnetworkNodelabelselector string 76 } 77 78 func (r gatewayApiConfig) Flags(flags *pflag.FlagSet) { 79 flags.String("kube-proxy-replacement", r.KubeProxyReplacement, "Enable only selected features (will panic if any selected feature cannot be enabled) (\"false\"), or enable all features (will panic if any feature cannot be enabled) (\"true\") (default \"false\")") 80 flags.Bool("enable-node-port", r.EnableNodePort, "Enable NodePort type services by Cilium") 81 82 flags.Bool("enable-gateway-api-secrets-sync", r.EnableGatewayAPISecretsSync, "Enables fan-in TLS secrets sync from multiple namespaces to singular namespace (specified by gateway-api-secrets-namespace flag)") 83 flags.Bool("enable-gateway-api-proxy-protocol", r.EnableGatewayAPIProxyProtocol, "Enable proxy protocol for all GatewayAPI listeners. Note that _only_ Proxy protocol traffic will be accepted once this is enabled.") 84 flags.Bool("enable-gateway-api-app-protocol", r.EnableGatewayAPIAppProtocol, "Enables Backend Protocol selection (GEP-1911) for Gateway API via appProtocol") 85 flags.Bool("enable-gateway-api-alpn", r.EnableGatewayAPIAlpn, "Enables exposing ALPN with HTTP2 and HTTP/1.1 support for Gateway API") 86 flags.Uint32("gateway-api-xff-num-trusted-hops", r.GatewayAPIXffNumTrustedHops, "The number of additional GatewayAPI proxy hops from the right side of the HTTP header to trust when determining the origin client's IP address.") 87 flags.String("gateway-api-service-externaltrafficpolicy", r.GatewayAPIServiceExternalTrafficPolicy, "Kubernetes LoadBalancer Service externalTrafficPolicy for all Gateway instances.") 88 flags.String("gateway-api-secrets-namespace", r.GatewayAPISecretsNamespace, "Namespace having tls secrets used by CEC for Gateway API") 89 flags.Bool("gateway-api-hostnetwork-enabled", r.GatewayAPIHostnetworkEnabled, "Exposes Gateway listeners on the host network.") 90 flags.String("gateway-api-hostnetwork-nodelabelselector", r.GatewayAPIHostnetworkNodelabelselector, "Label selector that matches the nodes where the gateway listeners should be exposed. It's a list of comma-separated key-value label pairs. e.g. 'kubernetes.io/os=linux,kubernetes.io/hostname=kind-worker'") 91 } 92 93 type gatewayAPIParams struct { 94 cell.In 95 96 Logger logrus.FieldLogger 97 K8sClient k8sClient.Clientset 98 CtrlRuntimeManager ctrlRuntime.Manager 99 Scheme *runtime.Scheme 100 101 AgentConfig *option.DaemonConfig 102 OperatorConfig *operatorOption.OperatorConfig 103 GatewayApiConfig gatewayApiConfig 104 } 105 106 func initGatewayAPIController(params gatewayAPIParams) error { 107 if !operatorOption.Config.EnableGatewayAPI { 108 return nil 109 } 110 111 if params.GatewayApiConfig.KubeProxyReplacement != option.KubeProxyReplacementTrue && 112 !params.GatewayApiConfig.EnableNodePort { 113 params.Logger.Warn("Gateway API support requires either kube-proxy-replacement or enable-node-port enabled") 114 return nil 115 } 116 117 if err := validateExternalTrafficPolicy(params); err != nil { 118 return err 119 } 120 121 params.Logger.WithField("requiredGVK", requiredGVK).Info("Checking for required GatewayAPI resources") 122 if err := checkRequiredCRDs(context.Background(), params.K8sClient); err != nil { 123 params.Logger.WithError(err).Error("Required GatewayAPI resources are not found, please refer to docs for installation instructions") 124 return nil 125 } 126 127 if err := registerGatewayAPITypesToScheme(params.Scheme); err != nil { 128 return err 129 } 130 131 if err := registerMCSAPITypesToScheme(params.K8sClient, params.Scheme); err != nil { 132 return err 133 } 134 135 cecTranslator := translation.NewCECTranslator( 136 params.GatewayApiConfig.GatewayAPISecretsNamespace, 137 params.GatewayApiConfig.EnableGatewayAPIProxyProtocol, 138 params.GatewayApiConfig.EnableGatewayAPIAppProtocol, 139 true, // hostNameSuffixMatch 140 params.OperatorConfig.ProxyIdleTimeoutSeconds, 141 params.GatewayApiConfig.GatewayAPIHostnetworkEnabled, 142 translation.ParseNodeLabelSelector(params.GatewayApiConfig.GatewayAPIHostnetworkNodelabelselector), 143 params.AgentConfig.EnableIPv4, 144 params.AgentConfig.EnableIPv6, 145 params.GatewayApiConfig.GatewayAPIXffNumTrustedHops, 146 ) 147 148 cecTranslator.WithUseAlpn(params.GatewayApiConfig.EnableGatewayAPIAlpn) 149 150 gatewayAPITranslator := gatewayApiTranslation.NewTranslator( 151 cecTranslator, 152 params.GatewayApiConfig.GatewayAPIHostnetworkEnabled, 153 params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy, 154 ) 155 156 if err := registerReconcilers( 157 params.CtrlRuntimeManager, 158 gatewayAPITranslator, 159 ); err != nil { 160 return fmt.Errorf("failed to create gateway controller: %w", err) 161 } 162 163 return nil 164 } 165 166 // registerSecretSync registers the Gateway API for secret synchronization based on TLS secrets referenced 167 // by a Cilium Gateway resource. 168 func registerSecretSync(params gatewayAPIParams) secretsync.SecretSyncRegistrationOut { 169 if err := checkRequiredCRDs(context.Background(), params.K8sClient); err != nil { 170 return secretsync.SecretSyncRegistrationOut{} 171 } 172 173 if !operatorOption.Config.EnableGatewayAPI || !params.GatewayApiConfig.EnableGatewayAPISecretsSync { 174 return secretsync.SecretSyncRegistrationOut{} 175 } 176 177 return secretsync.SecretSyncRegistrationOut{ 178 SecretSyncRegistration: &secretsync.SecretSyncRegistration{ 179 RefObject: &gatewayv1.Gateway{}, 180 RefObjectEnqueueFunc: EnqueueTLSSecrets(params.CtrlRuntimeManager.GetClient(), params.Logger), 181 RefObjectCheckFunc: IsReferencedByCiliumGateway, 182 SecretsNamespace: params.GatewayApiConfig.GatewayAPISecretsNamespace, 183 }, 184 } 185 } 186 187 func validateExternalTrafficPolicy(params gatewayAPIParams) error { 188 if params.GatewayApiConfig.GatewayAPIHostnetworkEnabled && params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy != "" { 189 log.Warn("Gateway API host networking is enabled, externalTrafficPolicy will be ignored.") 190 return nil 191 } else if params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy == string(corev1.ServiceExternalTrafficPolicyCluster) || 192 params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy == string(corev1.ServiceExternalTrafficPolicyLocal) { 193 return nil 194 } 195 return fmt.Errorf("invalid externalTrafficPolicy: %s", params.GatewayApiConfig.GatewayAPIServiceExternalTrafficPolicy) 196 } 197 198 func checkCRD(ctx context.Context, clientset k8sClient.Clientset, gvk schema.GroupVersionKind) error { 199 if !clientset.IsEnabled() { 200 return nil 201 } 202 203 crd, err := clientset.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, gvk.GroupKind().String(), metav1.GetOptions{}) 204 if err != nil { 205 return err 206 } 207 208 found := false 209 for _, v := range crd.Spec.Versions { 210 if v.Name == gvk.Version { 211 found = true 212 break 213 } 214 } 215 if !found { 216 return fmt.Errorf("CRD %q does not have version %q", gvk.GroupKind().String(), gvk.Version) 217 } 218 219 return nil 220 } 221 222 func checkRequiredCRDs(ctx context.Context, clientset k8sClient.Clientset) error { 223 var res error 224 for _, gvk := range requiredGVK { 225 if err := checkCRD(ctx, clientset, gvk); err != nil { 226 res = errors.Join(res, err) 227 } 228 } 229 return res 230 } 231 232 // registerReconcilers registers the Gateway API reconcilers to the controller-runtime library manager. 233 func registerReconcilers(mgr ctrlRuntime.Manager, translator translation.Translator) error { 234 reconcilers := []interface { 235 SetupWithManager(mgr ctrlRuntime.Manager) error 236 }{ 237 newGatewayClassReconciler(mgr), 238 newGatewayReconciler(mgr, translator), 239 newReferenceGrantReconciler(mgr), 240 newHTTPRouteReconciler(mgr), 241 newGammaHttpRouteReconciler(mgr, translator), 242 newGRPCRouteReconciler(mgr), 243 newTLSRouteReconciler(mgr), 244 } 245 246 for _, r := range reconcilers { 247 if err := r.SetupWithManager(mgr); err != nil { 248 return fmt.Errorf("failed to setup reconciler: %w", err) 249 } 250 } 251 252 return nil 253 } 254 255 func registerGatewayAPITypesToScheme(scheme *runtime.Scheme) error { 256 for gv, f := range map[fmt.Stringer]func(s *runtime.Scheme) error{ 257 gatewayv1.GroupVersion: gatewayv1.AddToScheme, 258 gatewayv1beta1.GroupVersion: gatewayv1beta1.AddToScheme, 259 gatewayv1alpha2.GroupVersion: gatewayv1alpha2.AddToScheme, 260 } { 261 if err := f(scheme); err != nil { 262 return fmt.Errorf("failed to add types from %s to scheme: %w", gv, err) 263 } 264 } 265 266 return nil 267 } 268 269 func registerMCSAPITypesToScheme(clientset k8sClient.Clientset, scheme *runtime.Scheme) error { 270 serviceImportSupport := checkCRD(context.Background(), clientset, mcsapiv1alpha1.SchemeGroupVersion.WithKind("serviceimports")) == nil 271 log.WithField("enabled", serviceImportSupport). 272 Info("Multi-cluster Service API ServiceImport GatewayAPI integration") 273 if serviceImportSupport { 274 return mcsapiv1alpha1.AddToScheme(scheme) 275 } 276 277 return nil 278 }