Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions .github/.copilot/breadcrumbs/2026-08-17-2356-gep-1748-gateway-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# GEP-1748 Gateway API Design

## Requirements

- Produce a fresh design focused on Gateway API and the GEP-1748 interaction model.
- Keep `networking.fleet.azure.com/v1alpha1 ServiceImport` unchanged.
- Use optional annotations for Azure Front Door, WAF, and Private Link configuration.
- Identify feasibility and repository ownership.
- Produce a detailed, tests-first implementation plan.
- Commit the documentation on a completely new pull request branch using Conventional Commits.

## Additional comments from user

- The design must not migrate to `multicluster.x-k8s.io/ServiceImport`.
- The public ingress API must not depend on the prior `FrontDoor*` CRD proposal.
- The working branch is `rchinchani/gep-1748-gateway-api`, created from the latest `origin/main`.
- The architecture should align with GKE's multi-cluster Gateway requirements where the concepts
apply to Fleet and Azure.
- The user approved proceeding with implementation after the design commit.

## Plan

### Phase 1: Verify current contracts

- [x] **Task 1.1: Review repository instructions and documentation conventions.**
- Success criteria: repository-specific design, planning, and breadcrumb requirements are
reflected in the documentation.
- [x] **Task 1.2: Verify ServiceImport and internal export behavior.**
- Success criteria: the design accurately preserves the existing ServiceImport group, version,
schema, status, and aggregation behavior.

### Phase 2: Create design documentation

- [x] **Task 2.1: Write the architecture design.**
- Success criteria: the document covers API boundaries, GEP compatibility, annotations,
controllers, AFD/WAF/PLS behavior, status, ownership, security, feasibility, and repository
placement.
- [x] **Task 2.2: Write the implementation plan.**
- Success criteria: the plan is phased, tests-first, independently reviewable, and includes
measurable completion criteria.

### Phase 3: Validate and commit

- [x] **Task 3.1: Review documentation consistency and git diff.**
- Success criteria: links, examples, terminology, and referenced repository paths are coherent,
with no unrelated changes.
- [x] **Task 3.2: Commit the design package.**
- Success criteria: the design, plan, and breadcrumb are committed with a detailed Conventional
Commit message and required trailers.

### Phase 4: Implement the Gateway foundation

- [x] **Task 4.1: Add Gateway API dependency and scheme tests.**
- Success criteria: Gateway, HTTPRoute, ReferenceGrant, and the unchanged Fleet ServiceImport GVK
are registered.
- [x] **Task 4.2: Add typed AFD annotation parsing.**
- Success criteria: defaults, supported values, misspellings, malformed values, reserved keys,
and cross-resource compatibility are tested.
- [x] **Task 4.3: Add normalized model primitives.**
- Success criteria: listeners, routes, filters, Fleet ServiceImport backends, origins, probes,
WAF, deterministic ordering, and weight validation are represented.
- [x] **Task 4.4: Add the separate hub Gateway manager skeleton.**
- Success criteria: the binary registers schemes, validates controller-wide Azure configuration,
exposes health/readiness, uses repository-standard structured logging, and registers no
reconcilers while the feature is disabled.
- [x] **Task 4.5: Add public and private configuration documentation.**
- Success criteria: separate subsections identify actors and placement for public AFD origins,
optional WAF, internal load balancers, PLS, approval, and cleanup.
- [x] **Task 4.6: Run focused validation.**
- Success criteria: unit tests, build, vet, dependency tidy, and diff checks pass. Race validation
is delegated to CI because this Windows host does not have a C compiler.

### Detailed checklist

- [x] Phase 1 / Task 1.1 completed.
- [x] Phase 1 / Task 1.2 completed.
- [x] Phase 2 / Task 2.1 completed.
- [x] Phase 2 / Task 2.2 completed.
- [x] Phase 3 / Task 3.1 completed.
- [x] Phase 3 / Task 3.2 completed.
- [x] Phase 4 / Task 4.1 completed.
- [x] Phase 4 / Task 4.2 completed.
- [x] Phase 4 / Task 4.3 completed.
- [x] Phase 4 / Task 4.4 completed.
- [x] Phase 4 / Task 4.5 completed.
- [x] Phase 4 / Task 4.6 completed.

### Overall success criteria

- The design is independent of prior AFD CRD branches.
- Fleet ServiceImport remains unchanged.
- Gateway API is the only public AFD routing API.
- Azure-specific options are optional annotations.
- Formal upstream GEP conformance limitations are explicit.
- The implementation plan can be delivered as focused follow-up pull requests.

## Decisions

- Keep the Fleet ServiceImport API because existing controllers, users, and status consumers already
depend on its group and cluster list.
- Follow GEP-1748 semantics without claiming upstream Extended conformance, because the GEP names
the upstream MCS API group.
- Use annotations rather than Azure policy CRDs, accepting weaker API-server validation in exchange
for a smaller and more portable public API.
- Compensate for annotation limitations with typed parsing, admission validation, status
conditions, and warning events.
- Put all controller implementation in `fleet-networking`; `fleet` needs only optional examples or
placement documentation.
- Use a separate hub Gateway controller manager to isolate Azure permissions, rollout, failures,
and ARM throttling.
- Start with direct per-Service member origins; shared per-cluster ingress gateways are deferred.
- Treat the Fleet hub as the configuration cluster and install the multi-cluster GatewayClass there.
- Accept only Fleet ServiceImport backends for the multi-cluster GatewayClass, matching GKE's
separation between single-cluster and multi-cluster GatewayClasses.
- Require explicit feature enablement, Fleet/MCS health, managed identity, Azure provider
registration, quota readiness, and controller readiness.
- Fail static during hub/configuration-plane outages or migrations when Azure resource ownership
cannot be proven.

## Implementation Details

- `docs/design/gep-1748-gateway-api.md` defines the architecture and compatibility contract.
- `docs/design/gep-1748-implementation-plan.md` defines the tests-first delivery phases and pull
request sequence.
- Gateway API `v1.2.1` is the selected dependency because it matches Kubernetes `v0.31.1`; newer
Gateway API releases require a repository-wide Kubernetes/controller-runtime upgrade.
- The first implementation slice covers dependency and scheme registration, a separate manager
skeleton, typed annotations, and normalized model primitives.
- The design uses `networking.fleet.azure.com/afd-*` annotations on Gateway and ServiceImport
resources.
- `HTTPRoute.backendRefs` targets the unchanged Fleet ServiceImport group.
- Per-logical-backend weight uses `HTTPRoute.backendRefs.weight`; per-member-cluster weight retains
the existing ServiceExport weight annotation.
- PLS discovery is transported through internal resources rather than public ServiceImport status.
- `cmd/hub-gateway-controller-manager` is a separate process with health, readiness, leader
election, Gateway API schemes, feature gating, and controller-wide Azure configuration.
- `pkg/annotations` rejects unknown reserved AFD annotations and invalid explicit values rather
than silently applying defaults.
- `pkg/controllers/hub/gatewaymodel` provides a provider-neutral, deeply copied, deterministic
model with distinct Gateway API backend weights and Fleet ServiceExport origin weights.

## Changes Made

- Added a new design directory for the GEP-1748 Gateway API proposal.
- Added the architecture design.
- Added the detailed implementation plan.
- Added a configuration guide with separate public-origin and private
internal-load-balancer/PLS subsections, each including optional WAF.
- Added an actor-and-placement matrix and labeled each configuration step with
who applies it and whether it belongs in the Fleet hub, member clusters, or Azure.
- Documented the private AFD + optional WAF + PLS topology's SFI-NS253 alignment and clarified that
public member load balancers are not the SFI-NS253 topology. The foundation PR does not claim
compliance until Private Link reconciliation, enforcement, and end-to-end evidence are complete.
- Added the Gateway API dependency, scheme registration tests, manager skeleton, annotation parser,
normalized model, Makefile build target, and focused unit tests.
- Focused tests pass with 81.8% coverage for the manager, 95.1% for annotation parsing, and 89.7%
for the normalized model. Race mode could not run locally because `gcc` is not installed; CI
will provide race validation.
- Added this breadcrumb as the decision and documentation trail.

## Before/After Comparison

- **Before:** The main branch had Service export/import and Traffic Manager documentation but no
Gateway API global-ingress design.
- **After:** The branch has a standalone Gateway API design and phased plan for AFD, optional WAF,
and optional PLS while preserving Fleet ServiceImport.

## References

- `api/v1alpha1/serviceimport_types.go`: Existing ServiceImport contract.
- `api/v1alpha1/serviceexport_types.go`: Existing ServiceExport and cluster weight annotation.
- `api/v1alpha1/internalserviceexport_types.go`: Existing member-to-hub transport.
- `pkg/controllers/hub/serviceimport/controller.go`: ServiceImport aggregation behavior.
- `pkg/controllers/hub/trafficmanagerbackend/controller.go`: Existing Azure reconciliation
patterns.
- `cmd/hub-net-controller-manager/main.go`: Existing hub controller process boundary.
- `docs/concepts/ExportingService/README.md`: Service export/import user model.
- `docs/concepts/DNSBasedGlobalLoadBalancing/README.md`: Existing global-routing ownership model.
- `docs/howtos/gateway-api-afd-configuration.md`: Proposed public and Private Link configuration
workflow.
- GEP-1748: <https://gateway-api.sigs.k8s.io/geps/gep-1748/>
- Gateway API v1.2.1 type definitions:
<https://github.com/kubernetes-sigs/gateway-api/blob/v1.2.1/apis/v1/gateway_types.go>
- GKE multi-cluster Gateway requirements:
<https://docs.cloud.google.com/kubernetes-engine/docs/how-to/prepare-environment-multi-cluster-gateways#requirements>
- Azure Front Door Private Link: <https://learn.microsoft.com/azure/frontdoor/private-link>
- Azure Front Door WAF: <https://learn.microsoft.com/azure/web-application-firewall/afds/afds-overview>
- Repository domain knowledge: no files were present under `.github/.copilot/domain_knowledge`.
- Repository specifications: no files were present under `.github/.copilot/specifications`.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,14 @@ generate: $(CONTROLLER_GEN)
.PHONY: build
build: generate fmt vet ## Build binaries.
go build -o bin/hub-net-controller-manager cmd/hub-net-controller-manager/main.go
go build -o bin/hub-gateway-controller-manager cmd/hub-gateway-controller-manager/main.go
go build -o bin/member-net-controller-manager cmd/member-net-controller-manager/main.go
go build -o bin/mcs-controller-manager cmd/mcs-controller-manager/main.go

.PHONY: run-hub-gateway-controller-manager
run-hub-gateway-controller-manager: manifests generate fmt vet ## Run the hub Gateway controller from your host.
go run ./cmd/hub-gateway-controller-manager/main.go

.PHONY: run-hub-net-controller-manager
run-hub-net-controller-manager: manifests generate fmt vet ## Run a controllers from your host.
go run ./cmd/hub-net-controller-manager/main.go
Expand Down
187 changes: 187 additions & 0 deletions cmd/hub-gateway-controller-manager/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

// Binary hub-gateway-controller-manager watches Gateway API and Fleet
// ServiceImport resources in the hub cluster.
package main

import (
"context"
"errors"
"flag"
"fmt"
"os"

"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"go.goms.io/fleet/pkg/utils/cloudconfig/azure"

fleetnetv1alpha1 "go.goms.io/fleet-networking/api/v1alpha1"
)

var (
metricsAddr = flag.String("metrics-bind-address", ":8082", "The address the metrics endpoint binds to.")
probeAddr = flag.String("health-probe-bind-address", ":8083", "The address the health probe endpoint binds to.")

enableLeaderElection = flag.Bool("leader-elect", true, "Enable leader election for the controller manager.")
leaderElectionNamespace = flag.String("leader-election-namespace", "fleet-system", "The namespace used for leader election.")

enableAFD = flag.Bool("enable-afd", false, "Enable Azure Front Door reconciliation.")
cloudConfigFile = flag.String("cloud-config", "/etc/kubernetes/provider/azure.json", "The Azure cloud configuration file.")
afdResourceGroup = flag.String("afd-resource-group", "", "The resource group in which the controller manages Azure Front Door resources.")
)

func init() {
// Register klog flags before flag.Parse so production verbosity and output
// settings are available consistently with the existing controller binaries.
klog.InitFlags(nil)
}

func main() {
flag.Parse()
defer klog.Flush()

ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

if err := run(currentOptions(), productionDependencies()); err != nil {
klog.ErrorS(err, "Problem running hub Gateway controller manager")
os.Exit(1)
}
}

func currentOptions() managerOptions {
return managerOptions{
metricsAddress: *metricsAddr,
probeAddress: *probeAddr,
leaderElection: *enableLeaderElection,
leaderElectionNamespace: *leaderElectionNamespace,
enableAFD: *enableAFD,
cloudConfigFile: *cloudConfigFile,
afdResourceGroup: *afdResourceGroup,
}
}

type managerOptions struct {
metricsAddress string
probeAddress string
leaderElection bool
leaderElectionNamespace string
enableAFD bool
cloudConfigFile string
afdResourceGroup string
}

type controllerManager interface {
AddHealthzCheck(string, healthz.Checker) error
AddReadyzCheck(string, healthz.Checker) error
Start(context.Context) error
}

type dependencies struct {
getConfig func() *rest.Config
newManager func(*rest.Config, ctrl.Options) (controllerManager, error)
signalHandler func() context.Context
loadAFDConfig func(string, string) (*azure.CloudConfig, error)
newScheme func() (*runtime.Scheme, error)
}

func productionDependencies() dependencies {
return dependencies{
getConfig: ctrl.GetConfigOrDie,
newManager: func(config *rest.Config, options ctrl.Options) (controllerManager, error) {
return ctrl.NewManager(config, options)
},
signalHandler: ctrl.SetupSignalHandler,
loadAFDConfig: loadAFDConfiguration,
newScheme: newScheme,
}
}

func run(options managerOptions, deps dependencies) error {
scheme, err := deps.newScheme()
if err != nil {
return fmt.Errorf("register controller schemes: %w", err)
}

if options.enableAFD {
if _, err := deps.loadAFDConfig(options.cloudConfigFile, options.afdResourceGroup); err != nil {
return fmt.Errorf("load Azure Front Door configuration: %w", err)
}
// The feature flag intentionally performs configuration validation only
// until the Gateway reconcilers are introduced in the next slice.
klog.InfoS("Azure Front Door configuration is valid; no reconcilers are registered in the foundation release")
} else {
klog.InfoS("Azure Front Door reconciliation is disabled")
}

mgr, err := deps.newManager(deps.getConfig(), ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: options.metricsAddress,
},
HealthProbeBindAddress: options.probeAddress,
LeaderElection: options.leaderElection,
LeaderElectionNamespace: options.leaderElectionNamespace,
LeaderElectionID: "hub-gateway-controller-manager.networking.fleet.azure.com",
})
if err != nil {
return fmt.Errorf("create hub Gateway controller manager: %w", err)
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
return fmt.Errorf("set up health check: %w", err)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
return fmt.Errorf("set up readiness check: %w", err)
}

klog.InfoS("Starting hub Gateway controller manager")
if err := mgr.Start(deps.signalHandler()); err != nil {
return fmt.Errorf("start hub Gateway controller manager: %w", err)
}
return nil
}

func newScheme() (*runtime.Scheme, error) {
scheme := runtime.NewScheme()
installers := []func(*runtime.Scheme) error{
clientgoscheme.AddToScheme,
gatewayv1.Install,
// ReferenceGrant remains v1beta1 in Gateway API v1.2.1.
gatewayv1beta1.Install,
fleetnetv1alpha1.AddToScheme,
}
for _, install := range installers {
if err := install(scheme); err != nil {
return nil, err
}
}
return scheme, nil
}

func loadAFDConfiguration(cloudConfigPath, resourceGroup string) (*azure.CloudConfig, error) {
if resourceGroup == "" {
return nil, errors.New("afd-resource-group must be configured when Azure Front Door reconciliation is enabled")
}
cloudConfig, err := azure.NewCloudConfigFromFile(cloudConfigPath)
if err != nil {
return nil, err
}
if cloudConfig.SubscriptionID == "" {
return nil, errors.New("Azure subscription ID must be configured")
}
cloudConfig.SetUserAgent("fleet-hub-gateway-controller-manager")
return cloudConfig, nil
}
Loading
Loading