From ef406c07e8a71f36cd45b77b68dbb6d6516b7c7a Mon Sep 17 00:00:00 2001 From: Renuka Fernando Date: Wed, 18 Feb 2026 14:25:25 +0530 Subject: [PATCH 1/5] Make control plane host optional --- gateway/DEBUG_GUIDE.md | 31 +++++++++++------ gateway/docker-compose.yaml | 16 +++++---- .../gateway-controller/pkg/config/config.go | 33 ++++++++++--------- .../pkg/config/config_test.go | 15 +++++++-- 4 files changed, 61 insertions(+), 34 deletions(-) diff --git a/gateway/DEBUG_GUIDE.md b/gateway/DEBUG_GUIDE.md index 3638fae65..25c34752a 100644 --- a/gateway/DEBUG_GUIDE.md +++ b/gateway/DEBUG_GUIDE.md @@ -48,22 +48,33 @@ Update `.vscode/launch.json` in the **Gateway Controller** configuration with yo ### Step 2: Update Docker Compose Configuration -Edit `gateway/docker-compose.yml` and comment out the policy engine port mappings in the `gateway-runtime` service, keeping only the router ports: +In `gateway/docker-compose.yaml`, make two changes to the `gateway-runtime` service: + +1. Set `GATEWAY_CONTROLLER_HOST` to `host.docker.internal` so the runtime reaches the locally-running controller: + +```yaml +services: + gateway-runtime: + environment: + GATEWAY_CONTROLLER_HOST: host.docker.internal +``` + +2. Comment out the **Policy Engine** port block: ```yaml services: gateway-runtime: ports: - # Router ports (keep these) - - "8080:8080" # HTTP - - "8443:8443" # HTTPS - - "9901:9901" # Admin - - # Policy engine ports (comment these out - running locally) - # - "9001:9001" # ext_proc - # - "9002:9002" # Policy engine admin + # Router (Envoy) - keep these + - "8080:8080" # HTTP ingress + - "8443:8443" # HTTPS ingress + - "8081:8081" # xDS-managed API listener + - "8082:8082" # WebSub Hub dynamic forward proxy + - "8083:8083" # WebSub Hub internal listener + - "9901:9901" # Envoy admin + # Policy Engine - comment these out + # - "9002:9002" # Admin API # - "9003:9003" # Metrics - # - "18090:18090" # ALS ``` ### Step 3: Start Gateway Controller diff --git a/gateway/docker-compose.yaml b/gateway/docker-compose.yaml index e3f0a2373..9a8a0c7d0 100644 --- a/gateway/docker-compose.yaml +++ b/gateway/docker-compose.yaml @@ -53,14 +53,16 @@ services: cpus: 0.175 command: ["--pol.config", "/etc/policy-engine/config.toml"] ports: - - "8080:8080" # HTTP traffic (Envoy) - - "8443:8443" # HTTPS traffic (Envoy) + # Router (Envoy) + - "8080:8080" # HTTP ingress + - "8443:8443" # HTTPS ingress + - "8081:8081" # xDS-managed API listener + - "8082:8082" # WebSub Hub dynamic forward proxy + - "8083:8083" # WebSub Hub internal listener - "9901:9901" # Envoy admin - - "8081:8081" - - "8082:8082" - - "8083:8083" - - "9002:9002" # Policy Engine admin - - "9003:9003" # Policy Engine metrics + # Policy Engine + - "9002:9002" # Admin API + - "9003:9003" # Metrics environment: - GATEWAY_CONTROLLER_HOST=gateway-controller - MOESIF_KEY=${MOESIF_KEY:-} diff --git a/gateway/gateway-controller/pkg/config/config.go b/gateway/gateway-controller/pkg/config/config.go index e1baf73d1..f58656824 100644 --- a/gateway/gateway-controller/pkg/config/config.go +++ b/gateway/gateway-controller/pkg/config/config.go @@ -517,7 +517,7 @@ func defaultConfig() *Config { Port: 9091, }, ControlPlane: ControlPlaneConfig{ - Host: "localhost:9243", + Host: "", Token: "", ReconnectInitial: 1 * time.Second, ReconnectMax: 5 * time.Minute, @@ -934,31 +934,34 @@ func (c *Config) validateEventGatewayConfig() error { // validateControlPlaneConfig validates the control plane configuration func (c *Config) validateControlPlaneConfig() error { - // Host validation - required if control plane is configured - if c.Controller.ControlPlane.Host == "" { - return fmt.Errorf("controlplane.host is required") - } + cp := &c.Controller.ControlPlane - // Token is optional - gateway can run without control plane connection - // If token is empty, connection will not be established + // If no host is set, the gateway runs in standalone mode — skip all CP validation. + if cp.Host == "" { + // A token without a host is a misconfiguration. + if cp.Token != "" { + return fmt.Errorf("controlplane.host is required when controlplane.token is set") + } + return nil + } // Validate reconnection intervals - if c.Controller.ControlPlane.ReconnectInitial <= 0 { - return fmt.Errorf("controlplane.reconnect_initial must be positive, got: %s", c.Controller.ControlPlane.ReconnectInitial) + if cp.ReconnectInitial <= 0 { + return fmt.Errorf("controlplane.reconnect_initial must be positive, got: %s", cp.ReconnectInitial) } - if c.Controller.ControlPlane.ReconnectMax <= 0 { - return fmt.Errorf("controlplane.reconnect_max must be positive, got: %s", c.Controller.ControlPlane.ReconnectMax) + if cp.ReconnectMax <= 0 { + return fmt.Errorf("controlplane.reconnect_max must be positive, got: %s", cp.ReconnectMax) } - if c.Controller.ControlPlane.ReconnectInitial > c.Controller.ControlPlane.ReconnectMax { + if cp.ReconnectInitial > cp.ReconnectMax { return fmt.Errorf("controlplane.reconnect_initial (%s) must be <= controlplane.reconnect_max (%s)", - c.Controller.ControlPlane.ReconnectInitial, c.Controller.ControlPlane.ReconnectMax) + cp.ReconnectInitial, cp.ReconnectMax) } // Validate polling interval - if c.Controller.ControlPlane.PollingInterval <= 0 { - return fmt.Errorf("controlplane.polling_interval must be positive, got: %s", c.Controller.ControlPlane.PollingInterval) + if cp.PollingInterval <= 0 { + return fmt.Errorf("controlplane.polling_interval must be positive, got: %s", cp.PollingInterval) } return nil diff --git a/gateway/gateway-controller/pkg/config/config_test.go b/gateway/gateway-controller/pkg/config/config_test.go index 106d085ab..2d605f6f1 100644 --- a/gateway/gateway-controller/pkg/config/config_test.go +++ b/gateway/gateway-controller/pkg/config/config_test.go @@ -633,6 +633,7 @@ func TestConfig_ValidateControlPlaneConfig(t *testing.T) { tests := []struct { name string host string + token string reconnectInitial time.Duration reconnectMax time.Duration pollingInterval time.Duration @@ -648,10 +649,19 @@ func TestConfig_ValidateControlPlaneConfig(t *testing.T) { wantErr: false, }, { - name: "Missing host", + name: "Missing host (standalone mode)", + host: "", + reconnectInitial: 1 * time.Second, + reconnectMax: 30 * time.Second, + pollingInterval: 5 * time.Second, + wantErr: false, + }, + { + name: "Token set without host", host: "", + token: "some-token", wantErr: true, - errContains: "controlplane.host is required", + errContains: "controlplane.host is required when controlplane.token is set", }, { name: "Non-positive reconnect initial", @@ -695,6 +705,7 @@ func TestConfig_ValidateControlPlaneConfig(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := validConfig() cfg.Controller.ControlPlane.Host = tt.host + cfg.Controller.ControlPlane.Token = tt.token cfg.Controller.ControlPlane.ReconnectInitial = tt.reconnectInitial cfg.Controller.ControlPlane.ReconnectMax = tt.reconnectMax cfg.Controller.ControlPlane.PollingInterval = tt.pollingInterval From 9f7e1b53c19890117e499a9b09f3d09e92ff6143 Mon Sep 17 00:00:00 2001 From: Renuka Fernando Date: Wed, 18 Feb 2026 15:16:32 +0530 Subject: [PATCH 2/5] build(gateway): remove build-local targets from all gateway Makefiles The build-local targets used `DOCKER_BUILDKIT=1 docker build` with the claim of being faster due to "no buildx", but both invocations use the same underlying BuildKit daemon. The distinction was misleading and the targets were redundant with the standard `build` targets. Removes build-local, build-local-controller, build-local-gateway-builder, and build-local-gateway-runtime from the root gateway Makefile and from the gateway-controller, gateway-builder, and gateway-runtime component Makefiles. Updates the gateway/it/Makefile error message to reference `make build` instead of `make build-local`. --- gateway/Makefile | 16 ---------------- gateway/gateway-builder/Makefile | 14 -------------- gateway/gateway-controller/Makefile | 12 +----------- gateway/gateway-runtime/Makefile | 19 +------------------ gateway/it/Makefile | 4 ++-- 5 files changed, 4 insertions(+), 61 deletions(-) diff --git a/gateway/Makefile b/gateway/Makefile index 261ecc5f9..1d6e6f82a 100644 --- a/gateway/Makefile +++ b/gateway/Makefile @@ -39,11 +39,9 @@ help: ## Show this help message @echo '' @echo 'Build Targets:' @echo ' make build - Build all gateway component Docker images (buildx)' - @echo ' make build-local - Build all gateway component Docker images locally (faster)' @echo ' make build-controller - Build gateway-controller Docker image' @echo ' make build-gateway-builder - Build gateway-builder Docker image' @echo ' make build-gateway-runtime - Build gateway-runtime Docker image (Router + Policy Engine)' - @echo ' make build-local-gateway-runtime - Build gateway-runtime Docker image locally (faster)' @echo '' @echo 'Multi-arch Build and Push Targets:' @echo ' make build-and-push-multiarch - Build and push all gateway components for multiple architectures' @@ -79,8 +77,6 @@ help: ## Show this help message .PHONY: build build: build-controller build-gateway-builder build-gateway-runtime ## Build all gateway components -.PHONY: build-local -build-local: build-local-controller build-local-gateway-builder build-local-gateway-runtime ## Build all gateway components locally (faster) .PHONY: build-coverage build-coverage: build-gateway-builder build-controller-coverage build-gateway-runtime-coverage ## Build all coverage-instrumented gateway components @@ -90,10 +86,6 @@ build-controller: ## Build gateway-controller Docker image @echo "Building controller Docker image ($(VERSION))..." $(MAKE) -C gateway-controller build VERSION=$(VERSION) -.PHONY: build-local-controller -build-local-controller: ## Build gateway-controller Docker image locally (faster) - @echo "Building controller Docker image locally ($(VERSION))..." - $(MAKE) -C gateway-controller build-local VERSION=$(VERSION) .PHONY: build-controller-coverage build-controller-coverage: ## Build coverage-instrumented gateway-controller Docker image @@ -105,20 +97,12 @@ build-gateway-builder: ## Build gateway-builder Docker image @echo "Building gateway-builder Docker image ($(VERSION))..." $(MAKE) -C gateway-builder build VERSION=$(VERSION) -.PHONY: build-local-gateway-builder -build-local-gateway-builder: ## Build gateway-builder Docker image locally (faster) - @echo "Building gateway-builder Docker image locally ($(VERSION))..." - $(MAKE) -C gateway-builder build-local VERSION=$(VERSION) .PHONY: build-gateway-runtime build-gateway-runtime: ## Build gateway-runtime Docker image (Router + Policy Engine) @echo "Building gateway-runtime Docker image ($(VERSION))..." $(MAKE) -C gateway-runtime build VERSION=$(VERSION) -.PHONY: build-local-gateway-runtime -build-local-gateway-runtime: ## Build gateway-runtime Docker image locally (faster) - @echo "Building gateway-runtime Docker image locally ($(VERSION))..." - $(MAKE) -C gateway-runtime build-local VERSION=$(VERSION) .PHONY: build-gateway-runtime-coverage build-gateway-runtime-coverage: ## Build coverage-instrumented gateway-runtime Docker image diff --git a/gateway/gateway-builder/Makefile b/gateway/gateway-builder/Makefile index 7a4912a2d..e08a4e598 100644 --- a/gateway/gateway-builder/Makefile +++ b/gateway/gateway-builder/Makefile @@ -39,7 +39,6 @@ help: ## Show this help message @echo '' @echo 'Build Targets:' @echo ' make build - Build Docker image using buildx' - @echo ' make build-local - Build Docker image locally (faster)' @echo ' make push - Push Docker image to registry' @echo ' make build-and-push-multiarch - Build and push multi-arch Docker image' @echo '' @@ -69,19 +68,6 @@ build: ## Build Docker image using buildx --load \ . -.PHONY: build-local -build-local: ## Build Docker image locally (faster, no buildx) - @echo "Building Docker image locally ($(IMAGE_NAME):$(VERSION))..." - @DOCKER_BUILDKIT=1 docker build -f Dockerfile \ - --build-context policy-engine=../gateway-runtime/policy-engine \ - --build-context system-policies=../system-policies \ - --build-context sdk=../../sdk \ - --build-context common=../../common \ - --build-arg VERSION=$(VERSION) \ - --build-arg GIT_COMMIT=$(GIT_COMMIT) \ - -t $(IMAGE_NAME):$(VERSION) \ - -t $(IMAGE_NAME):latest \ - . .PHONY: push push: ## Push Docker image to registry diff --git a/gateway/gateway-controller/Makefile b/gateway/gateway-controller/Makefile index 1a36821c1..b7d94fb8c 100644 --- a/gateway/gateway-controller/Makefile +++ b/gateway/gateway-controller/Makefile @@ -28,7 +28,7 @@ LDFLAGS := -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT) -X main.Bu DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform IMAGE_NAME := $(DOCKER_REGISTRY)/gateway-controller -.PHONY: help generate build build-local test generate-listener-certs push build-coverage-image +.PHONY: help generate build test generate-listener-certs push build-coverage-image help: ## Show this help message @echo 'Usage: make [target]' @@ -57,16 +57,6 @@ build: generate test ## Build Docker image using buildx --load \ . -build-local: generate ## Build Docker image locally (faster, no buildx) - @echo "Building Docker image locally ($(IMAGE_NAME):$(VERSION))..." - DOCKER_BUILDKIT=1 docker build -f Dockerfile \ - --build-context sdk=../../sdk \ - --build-context common=../../common \ - --build-arg VERSION=$(VERSION) \ - --build-arg GIT_COMMIT=$(GIT_COMMIT) \ - -t $(IMAGE_NAME):$(VERSION) \ - -t $(IMAGE_NAME):latest \ - . push: ## Push Docker image to registry @echo "Pushing Docker images..." diff --git a/gateway/gateway-runtime/Makefile b/gateway/gateway-runtime/Makefile index e2ea59198..9f5d11ffa 100644 --- a/gateway/gateway-runtime/Makefile +++ b/gateway/gateway-runtime/Makefile @@ -26,7 +26,7 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform IMAGE_NAME := $(DOCKER_REGISTRY)/gateway-runtime -.PHONY: help build build-local build-coverage-image push clean build-and-push-multiarch test +.PHONY: help build build-coverage-image push clean build-and-push-multiarch test help: ## Show this help message @echo 'Gateway Runtime Build (Version: $(VERSION))' @@ -53,23 +53,6 @@ build: ## Build Gateway Runtime Docker image using buildx @rm -rf target @echo "Docker image built successfully: $(IMAGE_NAME):$(VERSION)" -build-local: ## Build Gateway Runtime Docker image locally (faster, no buildx) - @echo "Building Gateway Runtime Docker image locally ($(IMAGE_NAME):$(VERSION))..." - @mkdir -p target && cp ../build.yaml target/ - DOCKER_BUILDKIT=1 docker build -f Dockerfile \ - --build-context sdk=../../sdk \ - --build-context common=../../common \ - --build-context gateway-builder=../gateway-builder \ - --build-context system-policies=../system-policies \ - --build-context dev-policies=../dev-policies \ - --build-context target=target \ - --build-arg VERSION=$(VERSION) \ - --build-arg GIT_COMMIT=$(GIT_COMMIT) \ - -t $(IMAGE_NAME):$(VERSION) \ - -t $(IMAGE_NAME):latest \ - . - @rm -rf target - @echo "Docker image built successfully: $(IMAGE_NAME):$(VERSION)" build-coverage-image: test ## Build Gateway Runtime Docker image with coverage instrumentation @echo "Building Gateway Runtime coverage image ($(IMAGE_NAME)-coverage:$(VERSION))..." diff --git a/gateway/it/Makefile b/gateway/it/Makefile index a180937fb..48f921411 100644 --- a/gateway/it/Makefile +++ b/gateway/it/Makefile @@ -27,7 +27,7 @@ COMPOSE_FILE ?= docker-compose.test.yaml # Refresh :test tags from :VERSION images when available. # If :VERSION is unavailable but :test already exists, keep using :test. # Tries coverage image first (from make build-coverage), then falls back to -# non-coverage image (from make build-local) for local development convenience. +# non-coverage image (from make build) for local development convenience. ensure-test-tags: @for img in $(COVERAGE_IMAGES); do \ base=$${img%-coverage}; \ @@ -40,7 +40,7 @@ ensure-test-tags: elif docker image inspect $(DOCKER_REGISTRY)/$$img:test >/dev/null 2>&1; then \ echo "Using existing $(DOCKER_REGISTRY)/$$img:test (no :$(VERSION) image found)"; \ else \ - echo "Error: No image found for $$img (:$(VERSION) or :test). Run 'make build-coverage' or 'make build-local' in gateway/ first." && exit 1; \ + echo "Error: No image found for $$img (:$(VERSION) or :test). Run 'make build-coverage' or 'make build' in gateway/ first." && exit 1; \ fi; \ done From ae3b074d617024425e74d1d9ab8853316ffbb631 Mon Sep 17 00:00:00 2001 From: Renuka Fernando Date: Wed, 18 Feb 2026 17:38:44 +0530 Subject: [PATCH 3/5] Build gateway remote debug images --- .vscode/launch.json | 32 +++ gateway/Makefile | 16 ++ gateway/docker-compose.debug.yaml | 95 ++++++++ .../internal/compilation/compiler.go | 8 + .../internal/compilation/options.go | 23 +- .../internal/compilation/options_test.go | 6 +- gateway/gateway-builder/pkg/types/policy.go | 1 + gateway/gateway-controller/Dockerfile | 50 +++- gateway/gateway-controller/Makefile | 15 +- gateway/gateway-runtime/Dockerfile | 47 +++- gateway/gateway-runtime/Makefile | 23 +- .../docker-entrypoint-debug.sh | 222 ++++++++++++++++++ 12 files changed, 514 insertions(+), 24 deletions(-) create mode 100644 gateway/docker-compose.debug.yaml create mode 100644 gateway/gateway-runtime/docker-entrypoint-debug.sh diff --git a/.vscode/launch.json b/.vscode/launch.json index cf83d8b22..5ee35321c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -83,5 +83,37 @@ "APIP_GW_ANALYTICS_ACCESS__LOGS__SERVICE_MODE": "tcp", }, }, + { + "name": "Gateway Controller (Remote)", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 2345, + "host": "127.0.0.1", + "apiVersion": 2, + "trace": "log", + "substitutePath": [ + { + "from": "${workspaceFolder}/gateway/gateway-controller", + "to": "/build" + } + ] + }, + { + "name": "Policy Engine (Remote)", + "type": "go", + "request": "attach", + "mode": "remote", + "port": 2346, + "host": "127.0.0.1", + "apiVersion": 2, + "trace": "log", + "substitutePath": [ + { + "from": "${workspaceFolder}/gateway/gateway-runtime/policy-engine", + "to": "/api-platform/gateway/gateway-runtime/policy-engine" + } + ] + }, ] } diff --git a/gateway/Makefile b/gateway/Makefile index 1d6e6f82a..884cd9fe8 100644 --- a/gateway/Makefile +++ b/gateway/Makefile @@ -40,8 +40,11 @@ help: ## Show this help message @echo 'Build Targets:' @echo ' make build - Build all gateway component Docker images (buildx)' @echo ' make build-controller - Build gateway-controller Docker image' + @echo ' make build-controller-debug - Build debug gateway-controller image for remote debugging (dlv on port 2345)' @echo ' make build-gateway-builder - Build gateway-builder Docker image' @echo ' make build-gateway-runtime - Build gateway-runtime Docker image (Router + Policy Engine)' + @echo ' make build-debug - Build all debug images (controller + runtime) for remote debugging' + @echo ' make build-gateway-runtime-debug - Build debug gateway-runtime image for remote debugging (dlv on port 2346)' @echo '' @echo 'Multi-arch Build and Push Targets:' @echo ' make build-and-push-multiarch - Build and push all gateway components for multiple architectures' @@ -78,6 +81,9 @@ help: ## Show this help message build: build-controller build-gateway-builder build-gateway-runtime ## Build all gateway components +.PHONY: build-debug +build-debug: build-controller-debug build-gateway-runtime-debug ## Build all debug images (controller + runtime) for remote debugging + .PHONY: build-coverage build-coverage: build-gateway-builder build-controller-coverage build-gateway-runtime-coverage ## Build all coverage-instrumented gateway components @@ -92,6 +98,11 @@ build-controller-coverage: ## Build coverage-instrumented gateway-controller Doc @echo "Building coverage-instrumented controller Docker image ($(VERSION))..." $(MAKE) -C gateway-controller build-coverage-image VERSION=$(VERSION) +.PHONY: build-controller-debug +build-controller-debug: ## Build debug gateway-controller image for remote debugging + @echo "Building debug controller Docker image ($(VERSION))..." + $(MAKE) -C gateway-controller build-debug VERSION=$(VERSION) + .PHONY: build-gateway-builder build-gateway-builder: ## Build gateway-builder Docker image @echo "Building gateway-builder Docker image ($(VERSION))..." @@ -109,6 +120,11 @@ build-gateway-runtime-coverage: ## Build coverage-instrumented gateway-runtime D @echo "Building coverage-instrumented gateway-runtime Docker image ($(VERSION))..." $(MAKE) -C gateway-runtime build-coverage-image VERSION=$(VERSION) +.PHONY: build-gateway-runtime-debug +build-gateway-runtime-debug: ## Build debug gateway-runtime image for remote debugging + @echo "Building debug gateway-runtime Docker image ($(VERSION))..." + $(MAKE) -C gateway-runtime build-debug VERSION=$(VERSION) + # Multi-arch Build and Push Targets .PHONY: build-and-push-multiarch build-and-push-multiarch: build-and-push-multiarch-controller build-and-push-multiarch-gateway-builder build-and-push-multiarch-gateway-runtime ## Build and push all gateway components for multiple architectures diff --git a/gateway/docker-compose.debug.yaml b/gateway/docker-compose.debug.yaml new file mode 100644 index 000000000..cc4690fb6 --- /dev/null +++ b/gateway/docker-compose.debug.yaml @@ -0,0 +1,95 @@ +# -------------------------------------------------------------------- +# Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# -------------------------------------------------------------------- + +# Compose overlay for remote debugging gateway components with dlv. +# Usage: docker compose -f docker-compose.yaml -f docker-compose.debug.yaml up +# +# Prerequisites: +# 1. Build debug images: +# cd gateway-controller && make build-debug +# cd gateway-runtime && make build-debug +# 2. In VS Code Run & Debug: +# - "Gateway Controller (Remote)" (port 2345) +# - "Policy Engine (Remote)" (port 2346) +services: + gateway-controller: + image: ghcr.io/wso2/api-platform/gateway-controller-debug:latest + command: ["-config", "/etc/gateway-controller/config.toml"] + ports: + - "9090:9090" # REST API + - "9094:9092" # Admin API + - "18000:18000" # xDS gRPC (Router) + - "18001:18001" # xDS gRPC (Policy Engine) + - "9011:9091" # Metrics + - "2345:2345" # dlv remote debug port + environment: + - APIP_GW_CONTROLLER_STORAGE_TYPE=sqlite + - APIP_GW_CONTROLLER_STORAGE_SQLITE_PATH=./data/gateway.db + - APIP_GW_CONTROLLER_LOGGING_LEVEL=info + - APIP_GW_CONTROLLER_METRICS_PORT=9091 + - APIP_GW_GATEWAY_REGISTRATION_TOKEN=${GATEWAY_REGISTRATION_TOKEN:-} + - APIP_GW_CONTROLPLANE_HOST=${GATEWAY_CONTROLPLANE_HOST:-host.docker.internal:9243} + volumes: + - controller-data:/app/data + - ./configs/config.toml:/etc/gateway-controller/config.toml:ro + - ./gateway-controller/certificates:/app/certificates + - ./gateway-controller/listener-certs:/app/listener-certs:ro + extra_hosts: + - "host.docker.internal:host-gateway" + networks: + - gateway-network + cap_add: + - SYS_PTRACE # required by dlv for process introspection + security_opt: + - seccomp:unconfined # dlv uses syscalls blocked by the default seccomp profile + + gateway-runtime: + image: ghcr.io/wso2/api-platform/gateway-runtime-debug:latest + command: ["--pol.config", "/etc/policy-engine/config.toml"] + ports: + # Router (Envoy) + - "8080:8080" # HTTP ingress + - "8443:8443" # HTTPS ingress + - "8081:8081" # xDS-managed API listener + - "8082:8082" # WebSub Hub dynamic forward proxy + - "8083:8083" # WebSub Hub internal listener + - "9901:9901" # Envoy admin + # Policy Engine + - "9002:9002" # Admin API + - "9003:9003" # Metrics + - "2346:2346" # dlv remote debug port + environment: + - GATEWAY_CONTROLLER_HOST=gateway-controller + - MOESIF_KEY=${MOESIF_KEY:-} + - LOG_LEVEL=info + volumes: + - ./configs/config.toml:/etc/policy-engine/config.toml:ro + networks: + - gateway-network + cap_add: + - SYS_PTRACE # required by dlv for process introspection + security_opt: + - seccomp:unconfined # dlv uses syscalls blocked by the default seccomp profile + +volumes: + controller-data: + driver: local + +networks: + gateway-network: + driver: bridge diff --git a/gateway/gateway-builder/internal/compilation/compiler.go b/gateway/gateway-builder/internal/compilation/compiler.go index b0c1273fe..fe23cd032 100644 --- a/gateway/gateway-builder/internal/compilation/compiler.go +++ b/gateway/gateway-builder/internal/compilation/compiler.go @@ -127,6 +127,14 @@ func runGoBuild(srcDir string, options *types.CompilationOptions) error { args = append(args, "-cover") } + // Add debug flags if enabled (no optimizations/inlining for dlv) + if options.EnableDebug { + slog.Info("Building with debug flags (no optimizations/inlining)", + "step", "build", + "phase", "compilation") + args = append(args, "-gcflags", "all=-N -l") + } + // Add build tags if len(options.BuildTags) > 0 { tags := "" diff --git a/gateway/gateway-builder/internal/compilation/options.go b/gateway/gateway-builder/internal/compilation/options.go index 99500fbcd..6881aaa13 100644 --- a/gateway/gateway-builder/internal/compilation/options.go +++ b/gateway/gateway-builder/internal/compilation/options.go @@ -36,6 +36,12 @@ func BuildOptions(outputPath string, buildMetadata *types.BuildMetadata) *types. enableCoverage = true } + // Check for debug mode from environment variable + enableDebug := false + if debugEnv := os.Getenv("DEBUG"); strings.EqualFold(debugEnv, "true") { + enableDebug = true + } + // Determine target architecture: // 1. Use TARGETARCH env var if set (Docker buildx cross-compilation) // 2. Fall back to runtime.GOARCH (native build) @@ -45,8 +51,8 @@ func BuildOptions(outputPath string, buildMetadata *types.BuildMetadata) *types. } // Generate ldflags for build metadata injection - // Pass enableCoverage to avoid stripping debug info needed for coverage - ldflags := generateLDFlags(buildMetadata, enableCoverage) + // Pass enableCoverage/enableDebug to avoid stripping debug info when needed + ldflags := generateLDFlags(buildMetadata, enableCoverage, enableDebug) return &types.CompilationOptions{ OutputPath: outputPath, @@ -56,17 +62,18 @@ func BuildOptions(outputPath string, buildMetadata *types.BuildMetadata) *types. TargetOS: "linux", TargetArch: targetArch, EnableCoverage: enableCoverage, + EnableDebug: enableDebug, } } // generateLDFlags creates ldflags string for embedding build metadata -// enableCoverage determines if coverage is enabled (skip -s -w flags if so) -func generateLDFlags(metadata *types.BuildMetadata, enableCoverage bool) string { +// enableCoverage/enableDebug determine if debug info should be preserved +func generateLDFlags(metadata *types.BuildMetadata, enableCoverage bool, enableDebug bool) string { var ldflags string - - // Only strip debug info if coverage is NOT enabled - // -s and -w interfere with Go coverage instrumentation - if !enableCoverage { + + // Only strip debug info if neither coverage nor debug mode is enabled + // -s and -w interfere with Go coverage instrumentation and dlv debugging + if !enableCoverage && !enableDebug { ldflags = "-s -w" // Strip debug info and symbol table } diff --git a/gateway/gateway-builder/internal/compilation/options_test.go b/gateway/gateway-builder/internal/compilation/options_test.go index 136be011e..a000693bd 100644 --- a/gateway/gateway-builder/internal/compilation/options_test.go +++ b/gateway/gateway-builder/internal/compilation/options_test.go @@ -127,7 +127,7 @@ func TestGenerateLDFlags_WithoutCoverage(t *testing.T) { Timestamp: time.Date(2025, 6, 15, 14, 30, 0, 0, time.UTC), } - ldflags := generateLDFlags(metadata, false) + ldflags := generateLDFlags(metadata, false, false) assert.Contains(t, ldflags, "-s -w") assert.Contains(t, ldflags, "-X main.Version=v1.2.3") @@ -142,7 +142,7 @@ func TestGenerateLDFlags_WithCoverage(t *testing.T) { Timestamp: time.Date(2025, 12, 25, 8, 0, 0, 0, time.UTC), } - ldflags := generateLDFlags(metadata, true) + ldflags := generateLDFlags(metadata, true, false) // Should NOT have -s -w when coverage is enabled assert.NotContains(t, ldflags, "-s -w") @@ -158,7 +158,7 @@ func TestGenerateLDFlags_EmptyMetadata(t *testing.T) { Timestamp: time.Time{}, } - ldflags := generateLDFlags(metadata, false) + ldflags := generateLDFlags(metadata, false, false) assert.Contains(t, ldflags, "-s -w") assert.Contains(t, ldflags, "-X main.Version=") diff --git a/gateway/gateway-builder/pkg/types/policy.go b/gateway/gateway-builder/pkg/types/policy.go index 83d514235..ca6fa79d7 100644 --- a/gateway/gateway-builder/pkg/types/policy.go +++ b/gateway/gateway-builder/pkg/types/policy.go @@ -98,6 +98,7 @@ type CompilationOptions struct { TargetOS string TargetArch string EnableCoverage bool // Enable coverage instrumentation for integration tests + EnableDebug bool // Disable optimizations/inlining for dlv remote debugging } // PackagingMetadata contains Docker image metadata diff --git a/gateway/gateway-controller/Dockerfile b/gateway/gateway-controller/Dockerfile index 5c862652e..a203c2973 100644 --- a/gateway/gateway-controller/Dockerfile +++ b/gateway/gateway-controller/Dockerfile @@ -8,6 +8,9 @@ ARG GIT_COMMIT=unknown # Coverage build argument - set to "true" for coverage-instrumented builds ARG ENABLE_COVERAGE=false +# Debug build argument - set to "true" for debug builds with dlv support (disables optimizations) +ARG ENABLE_DEBUG=false + WORKDIR /build # Install build dependencies for SQLite (CGO) and cross-compilation toolchains @@ -37,7 +40,7 @@ COPY --from=sdk . /sdk COPY --from=common . /common COPY . ./ -# Build with CGO (required for SQLite), set CC for cross-compilation, add -cover flag if enabled +# Build with CGO (required for SQLite), set CC for cross-compilation, add -cover/-gcflags if enabled RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ if [ "$TARGETARCH" = "amd64" ]; then \ @@ -52,14 +55,43 @@ RUN --mount=type=cache,target=/go/pkg/mod \ else \ COVER_FLAG=""; \ fi && \ - CGO_ENABLED=1 \ - GOOS=linux \ - GOARCH=${TARGETARCH} \ - go build $COVER_FLAG \ - -ldflags "-X main.Version=${VERSION} \ - -X main.GitCommit=${GIT_COMMIT} \ - -X main.BuildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ - -o controller cmd/controller/main.go + export CGO_ENABLED=1 && \ + export GOOS=linux && \ + export GOARCH=${TARGETARCH} && \ + BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) && \ + if [ "$ENABLE_DEBUG" = "true" ]; then \ + go build $COVER_FLAG \ + -gcflags "all=-N -l" \ + -ldflags "-X main.Version=${VERSION} -X main.GitCommit=${GIT_COMMIT} -X main.BuildDate=${BUILD_DATE}" \ + -o controller cmd/controller/main.go; \ + else \ + go build $COVER_FLAG \ + -ldflags "-X main.Version=${VERSION} -X main.GitCommit=${GIT_COMMIT} -X main.BuildDate=${BUILD_DATE}" \ + -o controller cmd/controller/main.go; \ + fi + +# Stage: debug (select with --target debug; NOT part of the default build) +# Full Go image needed to install dlv via go install; acceptable for dev-only use. +# Runs as root — dlv requires ptrace; container security settings handle isolation. +FROM golang:1.25.7-bookworm AS debug +WORKDIR /app + +# Install dlv (Delve debugger) +RUN go install github.com/go-delve/delve/cmd/dlv@latest + +# Copy debug-compiled binary and required runtime assets +COPY --from=builder /build/controller . +COPY default-policies /app/default-policies +COPY lua /app/lua +COPY default-llm-provider-templates /app/default-llm-provider-templates +RUN mkdir -p /app/data + +EXPOSE 2345 9090 18000 + +# Run controller under dlv in headless mode, waiting for debugger attach on :2345 +ENTRYPOINT ["/go/bin/dlv", "exec", "/app/controller", \ + "--listen=:2345", "--headless=true", \ + "--api-version=2", "--accept-multiclient", "--"] # Stage 2: Runtime image (Debian slim for glibc compatibility) FROM debian:bookworm-slim diff --git a/gateway/gateway-controller/Makefile b/gateway/gateway-controller/Makefile index b7d94fb8c..69b85ad48 100644 --- a/gateway/gateway-controller/Makefile +++ b/gateway/gateway-controller/Makefile @@ -28,7 +28,7 @@ LDFLAGS := -X main.Version=$(VERSION) -X main.GitCommit=$(GIT_COMMIT) -X main.Bu DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform IMAGE_NAME := $(DOCKER_REGISTRY)/gateway-controller -.PHONY: help generate build test generate-listener-certs push build-coverage-image +.PHONY: help generate build test generate-listener-certs push build-coverage-image build-debug help: ## Show this help message @echo 'Usage: make [target]' @@ -87,6 +87,19 @@ build-coverage-image: test ## Build coverage-instrumented gateway-controller ima --load \ . +build-debug: ## Build debug image for remote debugging with dlv (VS Code attach on port 2345) + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context common=../../common \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --build-arg ENABLE_DEBUG=true \ + --target debug \ + -t $(IMAGE_NAME)-debug:$(VERSION) \ + -t $(IMAGE_NAME)-debug:latest \ + --load \ + . + generate-listener-certs: ## Generate listener certificates @echo "Generating listener certificates..." @mkdir -p listener-certs diff --git a/gateway/gateway-runtime/Dockerfile b/gateway/gateway-runtime/Dockerfile index b7515715a..e4c235f72 100644 --- a/gateway/gateway-runtime/Dockerfile +++ b/gateway/gateway-runtime/Dockerfile @@ -72,11 +72,13 @@ ARG VERSION ARG GIT_COMMIT ARG BUILD_DATE ARG ENABLE_COVERAGE +ARG ENABLE_DEBUG=false ARG TARGETARCH ENV VERSION=${VERSION} ENV GIT_COMMIT=${GIT_COMMIT} ENV BUILD_DATE=${BUILD_DATE} ENV TARGETARCH=${TARGETARCH} +ENV ENABLE_DEBUG=${ENABLE_DEBUG} # Download SDK dependencies (cached by go.mod/go.sum changes) COPY --from=sdk go.mod go.sum /api-platform/sdk/ @@ -108,18 +110,59 @@ RUN mkdir -p /workspace/output WORKDIR /workspace -# Run gateway builder with build file, conditionally enable coverage +# Run gateway builder with build file, conditionally enable coverage or debug # Cache mounts let the builder's internal go build reuse compiled packages RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ [ "$ENABLE_COVERAGE" = "true" ] && export COVERAGE=true || true; \ + [ "$ENABLE_DEBUG" = "true" ] && export DEBUG=true || true; \ /usr/local/bin/gateway-builder \ -build-file /workspace/policies/build.yaml \ -system-build-lock /workspace/system-policies/system-build-lock.yaml \ -policy-engine-src /api-platform/gateway/gateway-runtime/policy-engine \ -out-dir /workspace/output -# Stage 3: Runtime +# Stage 3a: dlv installer (builds dlv for the debug stage) +FROM golang:1.25.7-bookworm AS debug-dlv-builder +RUN go install github.com/go-delve/delve/cmd/dlv@latest + +# Stage 3b: Debug Runtime (policy-engine wrapped in dlv, port 2346) +FROM envoyproxy/envoy:v1.35.3 AS debug + +USER root +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + tini \ + gettext-base \ + ca-certificates \ + curl \ + netcat-openbsd \ + dnsutils \ + iproute2 \ + iputils-ping \ + net-tools + +RUN mkdir -p /app /var/run/api-platform /coverage + +COPY --from=debug-dlv-builder /go/bin/dlv /usr/local/bin/dlv +COPY --from=policy-compiler /workspace/output/gateway-runtime/policy-engine /app/policy-engine +COPY router/config/envoy-bootstrap.yaml /etc/envoy/envoy.yaml +COPY router/config/config-override.yaml /etc/envoy/config-override.yaml +COPY docker-entrypoint-debug.sh /usr/local/bin/docker-entrypoint.sh +COPY health-check.sh /usr/local/bin/health-check.sh + +RUN chmod +x /app/policy-engine /usr/local/bin/dlv /usr/local/bin/docker-entrypoint.sh /usr/local/bin/health-check.sh + +ENV GATEWAY_CONTROLLER_HOST=gateway-controller \ + LOG_LEVEL=info + +# dlv requires elevated privileges (no USER wso2) +EXPOSE 2346 8080 8443 9901 9002 9003 + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] + +# Stage 4: Runtime (production — must remain last so default builds are unaffected) FROM envoyproxy/envoy:v1.35.3 ARG VERSION=unknown diff --git a/gateway/gateway-runtime/Makefile b/gateway/gateway-runtime/Makefile index 9f5d11ffa..71912e7b0 100644 --- a/gateway/gateway-runtime/Makefile +++ b/gateway/gateway-runtime/Makefile @@ -26,7 +26,7 @@ GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") DOCKER_REGISTRY ?= ghcr.io/wso2/api-platform IMAGE_NAME := $(DOCKER_REGISTRY)/gateway-runtime -.PHONY: help build build-coverage-image push clean build-and-push-multiarch test +.PHONY: help build build-coverage-image build-debug push clean build-and-push-multiarch test help: ## Show this help message @echo 'Gateway Runtime Build (Version: $(VERSION))' @@ -54,6 +54,27 @@ build: ## Build Gateway Runtime Docker image using buildx @echo "Docker image built successfully: $(IMAGE_NAME):$(VERSION)" +build-debug: ## Build debug Gateway Runtime image for remote debugging with dlv (VS Code attach on port 2346) + @echo "Building Gateway Runtime debug image ($(IMAGE_NAME)-debug:$(VERSION))..." + @mkdir -p target && cp ../build.yaml target/ + docker buildx build -f Dockerfile \ + --build-context sdk=../../sdk \ + --build-context common=../../common \ + --build-context gateway-builder=../gateway-builder \ + --build-context system-policies=../system-policies \ + --build-context dev-policies=../dev-policies \ + --build-context target=target \ + --build-arg VERSION=$(VERSION) \ + --build-arg GIT_COMMIT=$(GIT_COMMIT) \ + --build-arg ENABLE_DEBUG=true \ + --target debug \ + -t $(IMAGE_NAME)-debug:$(VERSION) \ + -t $(IMAGE_NAME)-debug:latest \ + --load \ + . + @rm -rf target + @echo "Debug image built successfully: $(IMAGE_NAME)-debug:$(VERSION)" + build-coverage-image: test ## Build Gateway Runtime Docker image with coverage instrumentation @echo "Building Gateway Runtime coverage image ($(IMAGE_NAME)-coverage:$(VERSION))..." @mkdir -p target && cp ../build.yaml target/ diff --git a/gateway/gateway-runtime/docker-entrypoint-debug.sh b/gateway/gateway-runtime/docker-entrypoint-debug.sh new file mode 100644 index 000000000..1eb863f8d --- /dev/null +++ b/gateway/gateway-runtime/docker-entrypoint-debug.sh @@ -0,0 +1,222 @@ +#!/bin/bash + +# -------------------------------------------------------------------- +# Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). +# +# WSO2 LLC. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# -------------------------------------------------------------------- + +# Gateway Runtime Debug Entrypoint Script +# Variant of docker-entrypoint.sh that wraps the policy engine in dlv for +# remote debugging via VS Code (port 2346). +# +# Process-specific args can be passed using prefixed flags: +# --rtr. → forwarded to Router (Envoy) +# --pol. → forwarded to Policy Engine +# +# Examples: +# docker run gateway-runtime-debug --rtr.component-log-level upstream:debug --pol.log-format text + +set -e + +# Logging function for entrypoint messages +log() { + echo "[ent] $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +# Parse process-specific args from command line. +# Uses dot (.) as the prefix separator (e.g. --rtr.flag, --pol.flag) because no +# standard CLI flag contains a dot, making prefix detection unambiguous. +# --rtr.X → ROUTER_ARGS, --pol.X → PE_ARGS, unrecognized → warning +ROUTER_ARGS=() +PE_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --rtr.*) + ROUTER_ARGS+=("--${1#--rtr.}") + shift + # Consume the value if next arg is not a flag + if [[ $# -gt 0 && "$1" != --* ]]; then + ROUTER_ARGS+=("$1") + shift + fi + ;; + --pol.*) + PE_ARGS+=("--${1#--pol.}") + shift + if [[ $# -gt 0 && "$1" != --* ]]; then + PE_ARGS+=("$1") + shift + fi + ;; + *) + log "ERROR: Unrecognized arg '$1' (use --rtr. or --pol. prefix)" + exit 1 + ;; + esac +done + +# Default configuration +# GATEWAY_CONTROLLER_HOST is the primary user-facing env var to configure connectivity +# to the gateway controller. The xDS ports default to well-known values: +# - ROUTER_XDS_PORT (18000): Router (Envoy) route/cluster/listener configs +# - POLICY_ENGINE_XDS_PORT (18001): Policy Engine policy chain configs +export GATEWAY_CONTROLLER_HOST="${GATEWAY_CONTROLLER_HOST:-gateway-controller}" +export ROUTER_XDS_PORT="${ROUTER_XDS_PORT:-18000}" +export POLICY_ENGINE_XDS_PORT="${POLICY_ENGINE_XDS_PORT:-18001}" +export LOG_LEVEL="${LOG_LEVEL:-info}" + +# Performance tuning configuration +# GOMAXPROCS limits Go's CPU usage - set to leave cores for Envoy (default: 2) +# ROUTER_CONCURRENCY sets Envoy's worker thread count (default: auto-detect, 0 means use all cores) +# APIP_GW_POLICY_ENGINE_METRICS_ENABLED controls metrics collection (default: true, set false for high-load) +export GOMAXPROCS="${GOMAXPROCS:-2}" +export ROUTER_CONCURRENCY="${ROUTER_CONCURRENCY:-0}" +export APIP_GW_POLICY_ENGINE_METRICS_ENABLED="${APIP_GW_POLICY_ENGINE_METRICS_ENABLED:-true}" + +# Derive Router (Envoy) xDS config — used by envsubst on config-override.yaml +export XDS_SERVER_HOST="${GATEWAY_CONTROLLER_HOST}" +export XDS_SERVER_PORT="${ROUTER_XDS_PORT}" + +# Policy Engine xDS address +PE_XDS_SERVER="${GATEWAY_CONTROLLER_HOST}:${POLICY_ENGINE_XDS_PORT}" + +POLICY_ENGINE_SOCKET="/var/run/api-platform/policy-engine.sock" + +log "Starting Gateway Runtime (DEBUG mode — dlv on port 2346)" +log " Gateway Controller: ${GATEWAY_CONTROLLER_HOST}" +log " Router xDS: ${GATEWAY_CONTROLLER_HOST}:${ROUTER_XDS_PORT}" +log " Policy Engine xDS: ${PE_XDS_SERVER}" +log " Log Level: ${LOG_LEVEL}" +log " Policy Engine Socket: ${POLICY_ENGINE_SOCKET}" +log " GOMAXPROCS: ${GOMAXPROCS}" +log " Router Concurrency: ${ROUTER_CONCURRENCY}" +log " Policy Engine Metrics: ${APIP_GW_POLICY_ENGINE_METRICS_ENABLED}" +[[ ${#ROUTER_ARGS[@]} -gt 0 ]] && log " Router extra args: ${ROUTER_ARGS[*]}" +[[ ${#PE_ARGS[@]} -gt 0 ]] && log " Policy Engine extra args: ${PE_ARGS[*]}" + +# Cleanup stale socket from previous runs +rm -f "${POLICY_ENGINE_SOCKET}" + +# Generate Envoy config override by substituting environment variables +CONFIG_OVERRIDE=$(envsubst < /etc/envoy/config-override.yaml) + +# Track child PIDs +PE_PID="" +ENVOY_PID="" + +# Shutdown handler - gracefully terminate both processes +shutdown() { + log "Received shutdown signal, terminating processes..." + + # Send SIGTERM to both processes + if [ -n "$PE_PID" ] && kill -0 "$PE_PID" 2>/dev/null; then + log "Stopping Policy Engine / dlv (PID $PE_PID)..." + kill -TERM "$PE_PID" 2>/dev/null || true + fi + + if [ -n "$ENVOY_PID" ] && kill -0 "$ENVOY_PID" 2>/dev/null; then + log "Stopping Envoy (PID $ENVOY_PID)..." + kill -TERM "$ENVOY_PID" 2>/dev/null || true + fi + + # Wait for processes to exit + wait + + # Cleanup socket + rm -f "${POLICY_ENGINE_SOCKET}" + + log "Shutdown complete" + exit 0 +} + +# Set up signal handlers +trap shutdown SIGTERM SIGINT SIGQUIT + +# Start Policy Engine under dlv for remote debugging (port 2346) +log "Starting Policy Engine under dlv (listening on :2346, headless)..." +/usr/local/bin/dlv exec /app/policy-engine \ + --listen=:2346 --headless=true \ + --api-version=2 --accept-multiclient -- \ + -xds-server "${PE_XDS_SERVER}" "${PE_ARGS[@]}" \ + > >(while IFS= read -r line; do echo "[pol] $line"; done) \ + 2> >(while IFS= read -r line; do echo "[pol] $line" >&2; done) & +PE_PID=$! +log "Policy Engine (dlv) started (PID $PE_PID)" + +# Wait for Policy Engine to create the socket (with timeout) +# Increased to 30s to account for dlv startup overhead before policy-engine initialises +SOCKET_WAIT_TIMEOUT=30 +SOCKET_WAIT_COUNT=0 +while [ ! -S "${POLICY_ENGINE_SOCKET}" ]; do + if [ $SOCKET_WAIT_COUNT -ge $SOCKET_WAIT_TIMEOUT ]; then + log "ERROR: Policy Engine socket not created within ${SOCKET_WAIT_TIMEOUT}s" + log "Checking if Policy Engine is still running..." + if ! kill -0 "$PE_PID" 2>/dev/null; then + log "ERROR: Policy Engine process has exited" + fi + exit 1 + fi + + # Check if Policy Engine is still running + if ! kill -0 "$PE_PID" 2>/dev/null; then + log "ERROR: Policy Engine exited before creating socket" + exit 1 + fi + + sleep 1 + SOCKET_WAIT_COUNT=$((SOCKET_WAIT_COUNT + 1)) +done +log "Policy Engine socket ready: ${POLICY_ENGINE_SOCKET}" + +# Start Envoy (Router) with [rtr] log prefix +log "Starting Envoy..." +/usr/local/bin/envoy \ + -c /etc/envoy/envoy.yaml \ + --config-yaml "${CONFIG_OVERRIDE}" \ + --log-level "${LOG_LEVEL}" \ + --concurrency "${ROUTER_CONCURRENCY}" \ + "${ROUTER_ARGS[@]}" \ + > >(while IFS= read -r line; do echo "[rtr] $line"; done) \ + 2> >(while IFS= read -r line; do echo "[rtr] $line" >&2; done) & +ENVOY_PID=$! +log "Envoy started (PID $ENVOY_PID)" + +log "Gateway Runtime running (DEBUG) - Policy Engine/dlv (PID $PE_PID), Envoy (PID $ENVOY_PID)" + +# Monitor both processes - exit if either dies +wait -n "$PE_PID" "$ENVOY_PID" +EXIT_CODE=$? + +# Determine which process exited and clean up the other +if ! kill -0 "$PE_PID" 2>/dev/null; then + log "Policy Engine exited with code $EXIT_CODE" + if kill -0 "$ENVOY_PID" 2>/dev/null; then + log "Terminating Envoy due to Policy Engine exit..." + kill -TERM "$ENVOY_PID" 2>/dev/null || true + wait "$ENVOY_PID" 2>/dev/null || true + fi +else + log "Envoy exited with code $EXIT_CODE" + if kill -0 "$PE_PID" 2>/dev/null; then + log "Terminating Policy Engine due to Envoy exit..." + kill -TERM "$PE_PID" 2>/dev/null || true + wait "$PE_PID" 2>/dev/null || true + fi +fi + +rm -f "${POLICY_ENGINE_SOCKET}" +exit $EXIT_CODE From 65826aa3e60fa080f5000cadc8d59ac71a3cac6d Mon Sep 17 00:00:00 2001 From: Renuka Fernando Date: Wed, 18 Feb 2026 17:47:32 +0530 Subject: [PATCH 4/5] Update gateway debug guide with remote debug --- gateway/DEBUG_GUIDE.md | 82 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/gateway/DEBUG_GUIDE.md b/gateway/DEBUG_GUIDE.md index 25c34752a..8a1fd5848 100644 --- a/gateway/DEBUG_GUIDE.md +++ b/gateway/DEBUG_GUIDE.md @@ -1,8 +1,80 @@ -# Gateway Local Debug Guide +# Gateway Debug Guide -This guide explains how to debug the gateway with **Gateway Controller** and **Policy Engine** running locally in VS Code, and the **Gateway Runtime (Router)** running in Docker Compose. +Two debug options are available. **Option 1 (Remote Debug)** is the recommended approach — everything runs in Docker and VS Code attaches via dlv. **Option 2 (Local Process)** runs the controller and policy engine as local VS Code processes with only the router in Docker. -## Architecture Overview +--- + +## Option 1 (Recommended): Remote Debug — All Components in Docker + +Gateway Controller and Policy Engine run inside Docker containers with dlv in server mode. VS Code attaches remotely. + +### Step 1: Build Debug Images + +```bash +cd gateway +make build-debug +``` + +This builds both `gateway-controller-debug:latest` and `gateway-runtime-debug:latest`. + +### Step 2: Start the Full Stack + +```bash +cd gateway +docker compose -f docker-compose.debug.yaml up +``` + +Wait until you see both containers are ready. The policy engine waits up to 30 seconds for dlv startup before the socket becomes available. + +### Step 3: Set Breakpoints + +Open the relevant source files in VS Code and set breakpoints: + +- **Gateway Controller**: files under `gateway/gateway-controller/` +- **Policy Engine**: files under `gateway/gateway-runtime/policy-engine/` + +### Step 4: Attach VS Code Debugger + +In the VS Code **Run & Debug** panel, launch: + +- **"Gateway Controller (Remote)"** — attaches to `localhost:2345` +- **"Policy Engine (Remote)"** — attaches to `localhost:2346` + +Both can be attached simultaneously. Source path substitution is configured automatically in `.vscode/launch.json`: + +| Component | Local path | Container path | +|---|---|---| +| Gateway Controller | `gateway/gateway-controller` | `/build` | +| Policy Engine | `gateway/gateway-runtime/policy-engine` | `/api-platform/gateway/gateway-runtime/policy-engine` | + +### Step 5: Deploy an API and Trigger Breakpoints + +```bash +# Deploy a test API +curl -X POST http://localhost:9090/apis \ + -H "Content-Type: application/yaml" \ + --data-binary @path/to/api.yaml + +# Send a request through the router +curl http://localhost:8080/petstore/v1/pets +``` + +### Notes + +- dlv runs with `--accept-multiclient` — you can detach and re-attach without restarting containers. +- Containers run as root (required by dlv for ptrace); resource limits are removed for debug headroom. +- Policy Engine socket wait timeout is 30s (vs 10s in production) to account for dlv startup overhead. +- All ports remain accessible: `9090` (Controller REST), `8080`/`8443` (Router), `9002` (PE admin), `18000`/`18001` (xDS). + +--- + +## Option 2 (Alternative): Local Process Debug — Controller + Policy Engine in VS Code + +Gateway Controller and Policy Engine run as local VS Code processes. Only the Envoy Router runs in Docker Compose. + +> **Warning:** Processes run directly on the host, so Go resolves modules via `go.work`. Local versions of `sdk` and other workspace modules are used instead of the published Go module versions — including any uncommitted or untagged changes. Behavior may differ from a production build. + +### Architecture ```mermaid graph TB @@ -21,14 +93,12 @@ graph TB GC -->|localhost:18001| PE ``` -## Prerequisites +### Prerequisites - VS Code with Go extension installed - Docker and Docker Compose - Control plane host and registration token (optional, for gateway registration) -## Step-by-Step Instructions - ### Step 1: Configure Control Plane Connection Update `.vscode/launch.json` in the **Gateway Controller** configuration with your control plane details: From 305af97398f21c3bd8558816d2d275a7de157ffa Mon Sep 17 00:00:00 2001 From: Renuka Fernando Date: Wed, 18 Feb 2026 18:25:44 +0530 Subject: [PATCH 5/5] Add a note to sync --- gateway/docker-compose.debug.yaml | 3 +++ gateway/docker-compose.yaml | 2 ++ gateway/gateway-runtime/docker-entrypoint-debug.sh | 4 +++- gateway/gateway-runtime/docker-entrypoint.sh | 4 +++- gateway/it/docker-compose.test.postgres.yaml | 12 ++++++++---- gateway/it/docker-compose.test.yaml | 12 ++++++++---- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/gateway/docker-compose.debug.yaml b/gateway/docker-compose.debug.yaml index cc4690fb6..8f3b0d19c 100644 --- a/gateway/docker-compose.debug.yaml +++ b/gateway/docker-compose.debug.yaml @@ -26,6 +26,9 @@ # 2. In VS Code Run & Debug: # - "Gateway Controller (Remote)" (port 2345) # - "Policy Engine (Remote)" (port 2346) + +# NOTE: gateway-controller and gateway-runtime here duplicate docker-compose.yaml — keep in sync. + services: gateway-controller: image: ghcr.io/wso2/api-platform/gateway-controller-debug:latest diff --git a/gateway/docker-compose.yaml b/gateway/docker-compose.yaml index 9a8a0c7d0..d3afcc640 100644 --- a/gateway/docker-compose.yaml +++ b/gateway/docker-compose.yaml @@ -16,6 +16,8 @@ # under the License. # -------------------------------------------------------------------- +# NOTE: gateway-controller and gateway-runtime are duplicated in docker-compose.debug.yaml — keep in sync. + services: gateway-controller: image: ghcr.io/wso2/api-platform/gateway-controller:0.8.8-SNAPSHOT diff --git a/gateway/gateway-runtime/docker-entrypoint-debug.sh b/gateway/gateway-runtime/docker-entrypoint-debug.sh index 1eb863f8d..f31b6729b 100644 --- a/gateway/gateway-runtime/docker-entrypoint-debug.sh +++ b/gateway/gateway-runtime/docker-entrypoint-debug.sh @@ -21,7 +21,9 @@ # Gateway Runtime Debug Entrypoint Script # Variant of docker-entrypoint.sh that wraps the policy engine in dlv for # remote debugging via VS Code (port 2346). -# + +# NOTE: Mostly duplicates docker-entrypoint.sh — keep in sync. + # Process-specific args can be passed using prefixed flags: # --rtr. → forwarded to Router (Envoy) # --pol. → forwarded to Policy Engine diff --git a/gateway/gateway-runtime/docker-entrypoint.sh b/gateway/gateway-runtime/docker-entrypoint.sh index 795a573c9..b6791ecc0 100644 --- a/gateway/gateway-runtime/docker-entrypoint.sh +++ b/gateway/gateway-runtime/docker-entrypoint.sh @@ -20,7 +20,9 @@ # Gateway Runtime Entrypoint Script # Manages both Policy Engine and Envoy processes -# + +# NOTE: docker-entrypoint-debug.sh is a near-duplicate of this file — keep in sync. + # Process-specific args can be passed using prefixed flags: # --rtr. → forwarded to Router (Envoy) # --pol. → forwarded to Policy Engine diff --git a/gateway/it/docker-compose.test.postgres.yaml b/gateway/it/docker-compose.test.postgres.yaml index 8e2a96b93..8729b3ed8 100644 --- a/gateway/it/docker-compose.test.postgres.yaml +++ b/gateway/it/docker-compose.test.postgres.yaml @@ -20,6 +20,8 @@ # Uses the gateway-runtime container (Router + Policy Engine combined) # Build coverage images with: make build-gateway-runtime-coverage +# NOTE: gateway-controller and gateway-runtime are duplicated in docker-compose.test.yaml — keep in sync. + services: postgres: container_name: it-postgres @@ -86,11 +88,13 @@ services: cpus: 1 command: ["--pol.config", "/etc/policy-engine/config.toml"] ports: - - "8080:8080" # HTTP traffic (Envoy) - - "8443:8443" # HTTPS traffic (Envoy) + # Router (Envoy) + - "8080:8080" # HTTP ingress + - "8443:8443" # HTTPS ingress - "9901:9901" # Envoy admin - - "9002:9002" # Policy Engine admin - - "9003:9003" # Policy Engine metrics + # Policy Engine + - "9002:9002" # Admin API + - "9003:9003" # Metrics environment: - GATEWAY_CONTROLLER_HOST=it-gateway-controller - LOG_LEVEL=info diff --git a/gateway/it/docker-compose.test.yaml b/gateway/it/docker-compose.test.yaml index 035cf6a77..4d77108bd 100644 --- a/gateway/it/docker-compose.test.yaml +++ b/gateway/it/docker-compose.test.yaml @@ -20,6 +20,8 @@ # Uses the gateway-runtime container (Router + Policy Engine combined) # Build coverage images with: make build-gateway-runtime-coverage +# NOTE: gateway-controller and gateway-runtime are duplicated in docker-compose.test.postgres.yaml — keep in sync. + services: gateway-controller: container_name: it-gateway-controller @@ -62,11 +64,13 @@ services: cpus: 1 command: ["--pol.config", "/etc/policy-engine/config.toml"] ports: - - "8080:8080" # HTTP traffic (Envoy) - - "8443:8443" # HTTPS traffic (Envoy) + # Router (Envoy) + - "8080:8080" # HTTP ingress + - "8443:8443" # HTTPS ingress - "9901:9901" # Envoy admin - - "9002:9002" # Policy Engine admin - - "9003:9003" # Policy Engine metrics + # Policy Engine + - "9002:9002" # Admin API + - "9003:9003" # Metrics environment: - GATEWAY_CONTROLLER_HOST=it-gateway-controller - LOG_LEVEL=info