Skip to content

feat: multi cluster support #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 73 commits into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
5bfcce5
feat: multi cluster support for the listener
vertex451 May 30, 2025
dbade00
cleanup
vertex451 May 30, 2025
5ef479d
merged main
vertex451 Jun 5, 2025
208d253
feat: gateway
vertex451 Jun 5, 2025
679681f
listener and gateway
vertex451 Jun 9, 2025
65a4195
Merge branch 'main' of github.com:openmfp/kubernetes-graphql-gateway …
vertex451 Jun 9, 2025
04d95c2
fix config storing
vertex451 Jun 9, 2025
cbe76e4
fixed certificate issue
vertex451 Jun 10, 2025
95cbd65
returned kcp direct approach to the listener
vertex451 Jun 10, 2025
757ffa0
testing
vertex451 Jun 10, 2025
afdef55
script
vertex451 Jun 10, 2025
a766071
cleanup
vertex451 Jun 10, 2025
77f8e9e
cleanup
vertex451 Jun 10, 2025
d6268cb
generate
vertex451 Jun 10, 2025
27e566c
genereted fresh ClusterAccess
vertex451 Jun 10, 2025
bc874c1
cleanup
vertex451 Jun 11, 2025
05f2b10
strategy pattern in reconciling
vertex451 Jun 11, 2025
eb67fcd
strategy pattern in reconciling
vertex451 Jun 11, 2025
6765e2e
fix conenction target
vertex451 Jun 11, 2025
8caffa0
moved handler to router package to cluster
vertex451 Jun 11, 2025
b8c411b
merged clusterRegistry and clusterRouter
vertex451 Jun 11, 2025
5147ca1
removed redundant code
vertex451 Jun 11, 2025
df79f7f
cleanup
vertex451 Jun 11, 2025
4c53899
simplify kcp part
vertex451 Jun 11, 2025
8506a97
fixed unit tests
vertex451 Jun 11, 2025
5d8fc12
feat: use golang-commons lifecycle
vertex451 Jun 18, 2025
7c43577
Merge branch 'main' of github.com:openmfp/kubernetes-graphql-gateway …
vertex451 Jun 18, 2025
96b3c03
cleanup
vertex451 Jun 18, 2025
9fe7c6b
skip failing tests to pass task test
vertex451 Jun 19, 2025
2bc0e72
removed kcp package, since its logic should live in reconciler packages
vertex451 Jun 19, 2025
7b085a3
removed reconciler factory
vertex451 Jun 19, 2025
2c245d6
incapsulated mgr in the reconcilers
vertex451 Jun 19, 2025
5af53f5
moved discovery client to its the its caller
vertex451 Jun 19, 2025
bc31964
optimized params for reconciler constructors
vertex451 Jun 20, 2025
6078a49
simplified parsing kubeconfig from spec files
vertex451 Jun 20, 2025
c20c560
simplify connection even more
vertex451 Jun 20, 2025
40076ce
fixed code generation
vertex451 Jun 20, 2025
d95f6de
removed unused controller package and moved other package closer to i…
vertex451 Jun 20, 2025
aa5525c
moved types to the reconciler
vertex451 Jun 20, 2025
855b4a0
removed redundant code from reconcilers
vertex451 Jun 20, 2025
fdb5965
gateway doesn't need kubeconfig in multicluster mode
vertex451 Jun 20, 2025
5469aa8
renamed standard to singlecluster
vertex451 Jun 20, 2025
60841ff
used RoundTripperFactory
vertex451 Jun 20, 2025
9b3e78b
moved RoundTripperFactory to the targetcluster to elimitane reverse dep
vertex451 Jun 20, 2025
79e2490
fixed task test
vertex451 Jun 23, 2025
d2a4fd9
added tests for manager
vertex451 Jun 23, 2025
d0f983a
fix subscriptions
vertex451 Jun 23, 2025
cbe38e9
tests: coverage
Jun 26, 2025
b6ee1dd
fix kcp connection
vertex451 Jun 30, 2025
64082af
pulled main
vertex451 Jun 30, 2025
7355b0a
cleanup
vertex451 Jun 30, 2025
ec593b1
add test for listener/reconciler/clusteraccess package
vertex451 Jul 1, 2025
d2cde3f
Merge branch 'main' of github.com:openmfp/kubernetes-graphql-gateway …
vertex451 Jul 1, 2025
8ceb9a5
reconciler/clusteraccess integration tests for subroutines
vertex451 Jul 2, 2025
436d06c
Merge branch 'main' of github.com:openmfp/kubernetes-graphql-gateway …
vertex451 Jul 2, 2025
4dc9df8
fix: task cover
vertex451 Jul 2, 2025
4218cd5
returned default gateway port
vertex451 Jul 2, 2025
7af15ee
replace interface with any
vertex451 Jul 2, 2025
e790cbd
reduced nesting
vertex451 Jul 2, 2025
32eed65
removed buildkubernetesconfig func
vertex451 Jul 2, 2025
78641d3
fix: tests
vertex451 Jul 2, 2025
fa75b07
tests: listener/reconciler/kcp
vertex451 Jul 2, 2025
e69ea05
added subroutines.go file to testignore
vertex451 Jul 3, 2025
c368c4f
linter
vertex451 Jul 3, 2025
865c82c
correct name for testignore
vertex451 Jul 3, 2025
03033a4
Merge branch 'main' of github.com:openmfp/kubernetes-graphql-gateway …
vertex451 Jul 8, 2025
e12f68b
fix: auth issues (#274)
vertex451 Jul 9, 2025
96be88b
Merge branch 'feat/mutli-cluster' of github.com:openmfp/kubernetes-gr…
vertex451 Jul 9, 2025
e5937b4
removed listener/reconciler/kcp from testignore
vertex451 Jul 9, 2025
369e747
remove single cluster
vertex451 Jul 9, 2025
653de3d
removed singlenode mode
vertex451 Jul 9, 2025
7867374
moved rest Config builder to common package
vertex451 Jul 10, 2025
b02ad95
feat: condition management
vertex451 Jul 10, 2025
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ coverage.html
*.swp
*.swo
*~
*.log

# MacOS
**/.DS_Store
Expand All @@ -40,4 +41,5 @@ coverage.html
go.work

# binary files
main
main
kubernetes-graphql-gateway
37 changes: 19 additions & 18 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ with-expecter: true
packages:
k8s.io/client-go/discovery:
config:
dir: listener/kcp/mocks
dir: listener/reconciler/kcp/mocks
outpkg: mocks
interfaces:
DiscoveryInterface:

k8s.io/apimachinery/pkg/api/meta:
config:
dir: listener/kcp/mocks
dir: listener/reconciler/kcp/mocks
outpkg: mocks
interfaces:
RESTMapper:

sigs.k8s.io/controller-runtime/pkg/client:
config:
dir: gateway/resolver/mocks
dir: common/mocks
outpkg: mocks
interfaces:
WithWatch:
Expand All @@ -31,45 +31,46 @@ packages:
interfaces:
RoundTripper:

github.com/openmfp/kubernetes-graphql-gateway/listener/workspacefile:
github.com/openmfp/kubernetes-graphql-gateway/listener/pkg/workspacefile:
config:
dir: listener/workspacefile/mocks
dir: listener/pkg/workspacefile/mocks
outpkg: mocks
interfaces:
IOHandler:

github.com/openmfp/kubernetes-graphql-gateway/listener/discoveryclient:
github.com/openmfp/kubernetes-graphql-gateway/listener/reconciler/kcp/discoveryclient:
config:
dir: listener/discoveryclient/mocks
dir: listener/reconciler/kcp/discoveryclient/mocks
outpkg: mocks
interfaces:
Factory:

github.com/openmfp/kubernetes-graphql-gateway/listener/apischema:
github.com/openmfp/kubernetes-graphql-gateway/listener/pkg/apischema:
config:
dir: listener/apischema/mocks
dir: listener/pkg/apischema/mocks
outpkg: mocks
interfaces:
Resolver:

github.com/openmfp/kubernetes-graphql-gateway/listener/clusterpath:
github.com/openmfp/kubernetes-graphql-gateway/listener/reconciler/kcp/clusterpath:
config:
dir: listener/clusterpath/mocks
dir: listener/reconciler/kcp/clusterpath/mocks
outpkg: mocks
interfaces:
Resolver:

github.com/openmfp/kubernetes-graphql-gateway/listener/controller:
k8s.io/client-go/openapi:
config:
dir: listener/controller/mocks
dir: listener/pkg/apischema/mocks
outpkg: mocks
interfaces:
CRDResolver:
GroupVersion:
Client:

k8s.io/client-go/openapi:
github.com/openmfp/kubernetes-graphql-gateway/gateway/manager:
config:
dir: listener/apischema/mocks
dir: gateway/manager/mocks
outpkg: mocks
interfaces:
GroupVersion:
Client:
ClusterManager:
SchemaWatcher:
6 changes: 5 additions & 1 deletion .testcoverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ exclude:
- cmd
- tests
- common/config/config.go
- mocks
- mocks
- common/apis/*
# remove it later:
- listener/reconciler/clusteraccess/subroutines.go
- listener/reconciler/singlecluster
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,38 @@ This repository contains two main components:
- [Listener](./docs/listener.md): watches a cluster and stores its openAPI spec in a directory.
- [Gateway](./docs/gateway.md): exposes the openAPI spec as a GraphQL endpoints.

## MultiCluster Support

The system supports three modes of operation:

1. **Single Cluster** (`ENABLE_KCP=false`, `MULTICLUSTER=false`): Gateway connects to the same cluster as the listener
2. **KCP Mode** (`ENABLE_KCP=true`): Designed for KCP-based multi-cluster scenarios
3. **MultiCluster Mode** (`ENABLE_KCP=false`, `MULTICLUSTER=true`): Gateway connects to multiple external clusters via ClusterAccess resources

### MultiCluster with ClusterAccess

In MultiCluster mode, the system uses ClusterAccess resources to store kubeconfig data and connection information. The listener processes these resources and embeds connection metadata into schema files, which the gateway then uses to establish cluster-specific connections.

For complete setup instructions, see:
- [ClusterAccess documentation](./docs/clusteraccess.md) - Manual setup
- [MultiCluster Kubeconfig Flow](./docs/multicluster-kubeconfig-flow.md) - Detailed flow explanation

### Quick Setup Scripts

```bash
# Create ClusterAccess with secure token authentication
./scripts/create-clusteraccess.sh --target-kubeconfig ~/.kube/prod-config

# Test end-to-end integration
./scripts/test-clusteraccess-integration.sh
```

### Gateway Requirements

- **Single Cluster Mode**: Requires KUBECONFIG to connect to the local cluster
- **KCP Mode**: Requires KUBECONFIG to connect to KCP management cluster
- **MultiCluster Mode**: Does NOT require KUBECONFIG - gets all connection info from schema files

## Authorization

All information about authorization can be found in the [authorization](./docs/authorization.md) section.
Expand Down
50 changes: 47 additions & 3 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ vars:
ENVTEST_K8S_VERSION: "1.30.0" # to get latest version run $(pwd)/bin/setup-envtest list
ENVTEST_VERSION: "release-0.20" # https://github.com/kubernetes-sigs/controller-runtime/releases
MOCKERY_VERSION: v2.52.3 # https://github.com/vektra/mockery/releases
CONTROLLER_GEN_VERSION: v0.18.0 # https://github.com/kubernetes-sigs/controller-tools/releases
tasks:
## Setup
setup:mockery:
Expand All @@ -16,6 +17,10 @@ tasks:
internal: true
cmds:
- test -s {{.LOCAL_BIN}}/setup-envtest || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install sigs.k8s.io/controller-runtime/tools/setup-envtest@{{.ENVTEST_VERSION}}
setup:controller-gen:
internal: true
cmds:
- test -s {{.LOCAL_BIN}}/controller-gen || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install sigs.k8s.io/controller-tools/cmd/controller-gen@{{.CONTROLLER_GEN_VERSION}}
update:crd:
desc: "Download the latest CRD from OpenMFP"
cmds:
Expand All @@ -31,6 +36,25 @@ tasks:
cmds:
- test -s {{.LOCAL_BIN}}/go-test-coverage || GOBIN=$(pwd)/{{.LOCAL_BIN}} go install github.com/vladopajic/go-test-coverage/v2@latest

## Code Generation
generate:crd:
desc: "Generate CRD manifests from Go types"
deps: [setup:controller-gen]
cmds:
- "{{.LOCAL_BIN}}/controller-gen crd:crdVersions=v1 paths=./common/apis/v1alpha1 output:crd:artifacts:config=config/crd"
- echo "CRD manifests generated successfully in config/crd/"
generate:deepcopy:
desc: "Generate deepcopy methods for API types"
deps: [setup:controller-gen]
cmds:
- "{{.LOCAL_BIN}}/controller-gen object paths=./common/apis/v1alpha1"
- echo "Deepcopy methods generated successfully"
generate:
desc: "Generate all CRD-related files (manifests + deepcopy methods)"
deps: [generate:crd, generate:deepcopy]
cmds:
- echo "All CRD generation completed successfully!"

## Development
mockery:
deps: [ setup:mockery ]
Expand Down Expand Up @@ -62,7 +86,7 @@ tasks:
vars:
ADDITIONAL_COMMAND_ARGS: -coverprofile=./cover.out -covermode=atomic -coverpkg=./...
cover:
deps: [ setup:envtest, setup:go-test-coverage ]
deps: [ setup:envtest, update:crd, setup:go-test-coverage ]
cmds:
- task: envtest
vars:
Expand All @@ -75,16 +99,36 @@ tasks:
- go tool cover -html=cover.out -o coverage.html
- open coverage.html || xdg-open coverage.html || start coverage.html
validate:
desc: "Run all validation checks including code generation, linting, and testing"
cmds:
- task: generate
- task: mockery
- task: lint
- task: test


gateway:
cmds:
desc: "Start the GraphQL gateway server (kills existing process on port 8080 if needed)"
cmds:
- |
# Check if port 8080 is in use and kill the process if found
PID=$(lsof -ti:8080 2>/dev/null || echo "")
if [ ! -z "$PID" ]; then
echo "Found existing process $PID on port 8080, killing it..."
kill $PID 2>/dev/null || true
sleep 2
fi
- go run main.go gateway

listener:
cmds:
desc: "Start the listener server (kills existing process on port 8090 if needed)"
cmds:
- |
# Check if port 8090 is in use and kill the process if found
PID=$(lsof -ti:8090 2>/dev/null || echo "")
if [ ! -z "$PID" ]; then
echo "Found existing process $PID on port 8090, killing it..."
kill $PID 2>/dev/null || true
sleep 2
fi
- go run main.go listener
27 changes: 14 additions & 13 deletions cmd/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
ctrl "sigs.k8s.io/controller-runtime"
restCfg "sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

"github.com/openmfp/golang-commons/logger"

Expand Down Expand Up @@ -48,12 +46,12 @@ var gatewayCmd = &cobra.Command{
defer openmfpcontext.Recover(log)
}

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

// Get Kubernetes restCfg
restCfg, err := restCfg.GetConfig()
gatewayInstance, err := manager.NewGateway(log, appCfg)
if err != nil {
log.Fatal().Err(err).Msg("Error getting Kubernetes restCfg, exiting")
log.Error().Err(err).Msg("Error creating gateway")
return fmt.Errorf("failed to create gateway: %w", err)
}

// Initialize tracing provider
Expand All @@ -76,15 +74,14 @@ var gatewayCmd = &cobra.Command{
}
}()

// Initialize Manager
managerInstance, err := manager.NewManager(log, restCfg, appCfg)
if err != nil {
log.Error().Err(err).Msg("Error creating manager")
return fmt.Errorf("failed to create manager: %w", err)
}
defer func() {
if err := providerShutdown(ctx); err != nil {
log.Fatal().Err(err).Msg("failed to shutdown TracerProvider")
}
}()

// Set up HTTP handler
http.Handle("/", managerInstance)
http.Handle("/", gatewayInstance)

// Replace the /metrics endpoint handler
http.Handle("/metrics", promhttp.Handler())
Expand Down Expand Up @@ -113,6 +110,10 @@ var gatewayCmd = &cobra.Command{
log.Fatal().Err(err).Msg("HTTP server shutdown failed")
}

if err := gatewayInstance.Close(); err != nil {
log.Error().Err(err).Msg("Error closing gateway services")
}

// Call the shutdown cleanup
shutdown()

Expand Down
Loading