From 8b4086c3c659f6a592f54ed79ea2611ea7b6c430 Mon Sep 17 00:00:00 2001 From: Lucie Milan Date: Thu, 11 Jun 2026 11:37:12 +0200 Subject: [PATCH 1/6] multi-tenancy draft --- app/_data/series.yml | 3 + .../operator-multi-tenancy-1-setup.md | 105 ++++++++++ ...operator-multi-tenancy-2-public-gateway.md | 125 ++++++++++++ ...perator-multi-tenancy-3-private-gateway.md | 121 +++++++++++ .../operator-multi-tenancy-4-test-services.md | 147 ++++++++++++++ app/_indices/operator.yaml | 3 + .../control-plane-watch-namespaces.md | 8 + app/operator/reference/multi-tenancy.md | 190 ++++++++++++++++++ 8 files changed, 702 insertions(+) create mode 100644 app/_how-tos/operator/operator-multi-tenancy-1-setup.md create mode 100644 app/_how-tos/operator/operator-multi-tenancy-2-public-gateway.md create mode 100644 app/_how-tos/operator/operator-multi-tenancy-3-private-gateway.md create mode 100644 app/_how-tos/operator/operator-multi-tenancy-4-test-services.md create mode 100644 app/operator/reference/multi-tenancy.md diff --git a/app/_data/series.yml b/app/_data/series.yml index f926772d6c..9e18c98eba 100644 --- a/app/_data/series.yml +++ b/app/_data/series.yml @@ -41,3 +41,6 @@ mcp-acls: mesh-get-started-universal: title: Get started with {{site.mesh_product_name}} on Universal url: /mesh/get-started/universal/install/ +operator-multi-tenancy: + title: Deploy multiple isolated gateways on the same cluster + url: /operator/dataplanes/how-to/multi-tenancy/setup/ diff --git a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md new file mode 100644 index 0000000000..489e7f0fbf --- /dev/null +++ b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md @@ -0,0 +1,105 @@ +--- +title: Install Kong Operator for multi-tenancy +description: "Create tenant namespaces, install {{ site.operator_product_name }} scoped to those namespaces, and apply a KongLicense." +content_type: how_to + +permalink: /operator/dataplanes/how-to/multi-tenancy/setup/ +series: + id: operator-multi-tenancy + position: 1 + +breadcrumbs: + - /operator/ + - index: operator + group: Gateway Deployment + - index: operator + group: Gateway Deployment + section: "How-To" + +products: + - operator + +works_on: + - on-prem + +min_version: + operator: '2.0' + +related_resources: + - text: "Multi-tenancy reference" + url: /operator/reference/multi-tenancy/ + - text: "Limiting namespaces watched by ControlPlane" + url: /operator/reference/control-plane-watch-namespaces/ + +tldr: + q: How do I set up {{ site.operator_product_name }} for multi-tenancy? + a: | + Install {{ site.operator_product_name }} with `env.watch_namespace` scoped to your + tenant namespaces, then apply a single `KongLicense` in `kong-system`. + +prereqs: + skip_product: true +--- + + + +This guide deploys two independent {{ site.base_gateway }} instances — one public-facing, one private — on the same cluster using a single {{ site.operator_product_name }} installation. Each gateway is scoped to its own namespace so that its in-memory KIC only processes routes from that namespace. + +## Create namespaces + +Create the operator namespace and the two tenant namespaces: + +```bash +kubectl create namespace kong-system +kubectl create namespace kong-gw-public +kubectl create namespace kong-gw-private +``` + +## Install {{ site.operator_product_name }} + + + +Add the Kong Helm chart repository: + +```bash +helm repo add kong https://charts.konghq.com +helm repo update +``` + +Install {{ site.operator_product_name }} scoped to the two tenant namespaces. The `watch_namespace` value prevents the operator from reconciling resources in any other namespace. + +```bash +helm upgrade --install kong-operator kong/kong-operator \ + -n kong-system \ + --create-namespace \ + --set image.tag={{ site.data.operator_latest.release }} \ + --values - < + + +Apply the license once in `kong-system`. It is shared by all gateways managed by this operator installation. This assumes your license file is at `./license.json`. + +```bash +kubectl -n kong-system apply -f - < + +## Create a GatewayConfiguration + +The `controlPlaneOptions.watchNamespaces.type: own` field restricts the in-memory KIC for this gateway to watch only the `kong-gw-public` namespace. Without this, it would watch all namespaces and process routes belonging to other tenants. + +```bash +kubectl apply -f - < + +Create a `GatewayClass` that references the `GatewayConfiguration` above: + +```bash +kubectl apply -f - < + +## Create a GatewayConfiguration + +As with the public gateway, `controlPlaneOptions.watchNamespaces.type: own` restricts the in-memory KIC to watch only the `kong-gw-private` namespace. + +```bash +kubectl apply -f - < + +## Deploy a test service in each namespace + +Deploy the echo service in both tenant namespaces: + +```bash +kubectl apply -f {{site.links.web}}/manifests/kic/echo-service.yaml -n kong-gw-public +kubectl apply -f {{site.links.web}}/manifests/kic/echo-service.yaml -n kong-gw-private +``` + +## Create an HTTPRoute for the public gateway + +Create an `HTTPRoute` in the `kong-gw-public` namespace pointing to the echo service: + +```bash +kubectl apply -f - < diff --git a/app/_indices/operator.yaml b/app/_indices/operator.yaml index 1e1d19837b..432d156d4d 100644 --- a/app/_indices/operator.yaml +++ b/app/_indices/operator.yaml @@ -21,6 +21,8 @@ groups: - title: Reference sections: - items: + - path: /operator/reference/architecture/ + - path: /operator/reference/multi-tenancy/ - path: /operator/reference/configuration-options/ - path: /operator/reference/custom-resources/ - path: /operator/reference/version-compatibility/ @@ -63,6 +65,7 @@ groups: - path: /operator/dataplanes/how-to/use-custom-ca-certificate/ - path: /operator/dataplanes/how-to/deploy-custom-plugins/ - path: /operator/dataplanes/how-to/handle-ingress/ + - path: /operator/dataplanes/how-to/multi-tenancy/setup/ - path: /operator/dataplanes/how-to/configure-plugins-for-httproute/ - title: Observability items: diff --git a/app/operator/reference/control-plane-watch-namespaces.md b/app/operator/reference/control-plane-watch-namespaces.md index 8e9e122941..f27ef83aaa 100644 --- a/app/operator/reference/control-plane-watch-namespaces.md +++ b/app/operator/reference/control-plane-watch-namespaces.md @@ -14,6 +14,14 @@ products: min_version: operator: '1.6' + +related_resources: + - text: "Multi-tenancy" + url: /operator/reference/multi-tenancy/ + - text: "Deploy multiple isolated gateways" + url: /operator/dataplanes/how-to/multi-tenancy/setup/ + - text: "{{site.operator_product_name}} architecture" + url: /operator/reference/architecture/ --- By default, {{ site.operator_product_name }}'s `ControlPlane` watches all namespaces. diff --git a/app/operator/reference/multi-tenancy.md b/app/operator/reference/multi-tenancy.md new file mode 100644 index 0000000000..bd1a079e6e --- /dev/null +++ b/app/operator/reference/multi-tenancy.md @@ -0,0 +1,190 @@ +--- +title: "Multi-tenancy" +description: "Understand how to run multiple isolated {{ site.base_gateway }} instances on the same Kubernetes cluster using {{ site.operator_product_name }}." +content_type: reference +layout: reference + +breadcrumbs: + - /operator/ + - index: operator + group: Reference + +products: + - operator + +works_on: + - on-prem + +min_version: + operator: '1.6' + +related_resources: + - text: "Deploy multiple isolated gateways on the same cluster" + url: /operator/dataplanes/how-to/multi-tenancy/setup/ + - text: "{{site.operator_product_name}} architecture" + url: /operator/reference/architecture/ + - text: "Limiting namespaces watched by ControlPlane" + url: /operator/reference/control-plane-watch-namespaces/ + - text: "Gateway configuration" + url: /operator/dataplanes/gateway-configuration/ + - text: "Managed Gateways" + url: /operator/dataplanes/managed-gateways/ + - text: "Custom Resources" + url: /operator/reference/custom-resources/ +--- + + + +Multi-tenancy in {{ site.operator_product_name }} means running multiple isolated {{ site.base_gateway }} instances — each with their own routing configuration, data plane, and namespace scope — on the same Kubernetes cluster, managed by a single {{ site.operator_product_name }} installation. + +Common use cases include separating a public-facing API gateway from an internal one, or giving different teams independent gateway instances without requiring separate clusters. + +## How it works + + + +Each tenant is represented by a **Gateway**, provisioned using a triptych of three resources: + +``` +GatewayConfiguration ──(parametersRef)──▶ GatewayClass ──(gatewayClassName)──▶ Gateway +``` + +For each `Gateway`, {{ site.operator_product_name }} creates: + +- One **in-memory KIC instance** embedded inside the {{ site.operator_product_name }} pod (not a separately deployed pod), which watches Gateway API resources and translates them into Kong configuration. +- One **DataPlane deployment** running {{ site.base_gateway }} in DB-less mode. + +Multiple `Gateway` resources can coexist in the same cluster. The resulting in-memory KIC instances and DataPlane pods are independent of each other. A single {{ site.operator_product_name }} installation manages them all. + +## Namespace isolation + + + +By default, each in-memory KIC instance watches **all namespaces** for Gateway API resources (`HTTPRoute`, `GRPCRoute`, etc.). This means, without additional configuration, one tenant's KIC would also process another tenant's routes. Namespace isolation is therefore required for multi-tenancy. + +The `watchNamespaces` field on `GatewayConfiguration.spec.controlPlaneOptions` controls which namespaces the in-memory KIC for each `Gateway` will watch. + + + +### watchNamespaces types + + + +The `watchNamespaces.type` field accepts three values: + +{% table %} +columns: + - title: Type + key: type + - title: Behavior + key: behavior + - title: Use when + key: use +rows: + - type: "`all`" + behavior: Watches all namespaces (default) + use: Single-tenant or dev environments + - type: "`own`" + behavior: Watches only the `ControlPlane`'s own namespace + use: One namespace per tenant (recommended) + - type: "`list`" + behavior: Watches own namespace plus a specified list + use: Routes for this tenant span multiple namespaces +{% endtable %} + +When using `list`, the ControlPlane's own namespace is automatically included. Each additional namespace in the list requires a `WatchNamespaceGrant` resource in that namespace (see [WatchNamespaceGrant](#watchnamespacegrant)). + +{:.info} +> Setting `watchNamespaces` via `GatewayConfiguration.spec.controlPlaneOptions` configures the `CONTROLLER_WATCH_NAMESPACE` environment variable in the managed KIC. If you set this variable manually through `podTemplateSpec`, it will override the `watchNamespaces` field. + +## Operator-level namespace scoping {% new_in 2.0 %} + + + +From v2.0, you can scope {{ site.operator_product_name }} itself to watch only specific namespaces. This is separate from the per-`ControlPlane` `watchNamespaces` configuration and is useful when running multiple {{ site.operator_product_name }} instances on the same cluster with strictly disjoint namespace assignments. + +Configure operator-level watch namespaces using the `watch_namespace` Helm value: + +```yaml +# values.yaml +env: + watch_namespace: namespace-a,namespace-b +``` + +Or via environment variable: + +```sh +KONG_OPERATOR_WATCH_NAMESPACES='namespace-a,namespace-b' +``` + +{:.warning} +> If both operator-level and `ControlPlane`-level watch namespaces are configured, they must not conflict. For example, if {{ site.operator_product_name }} watches namespaces A and B, a `ControlPlane` may only define watch namespaces A or B. Using namespace C would cause the `ControlPlane` to receive a failure status condition and stop reconciling. + +## WatchNamespaceGrant + + + +When `watchNamespaces.type` is `list`, a `WatchNamespaceGrant` resource must be created in each additional namespace (beyond the ControlPlane's own). This resource explicitly grants the named `ControlPlane` permission to watch resources in that namespace. + +```yaml +apiVersion: gateway-operator.konghq.com/v1alpha1 +kind: WatchNamespaceGrant +metadata: + name: watch-namespace-grant + namespace: target-namespace # the namespace being granted access to +spec: + from: + - group: gateway-operator.konghq.com + kind: ControlPlane + namespace: control-plane-namespace # the namespace where the ControlPlane lives +``` + +For the full field reference, see [WatchNamespaceGrant](/operator/reference/custom-resources/#watchnamespacegrant). + +## KongLicense + + + + +A single `KongLicense` applied in the `kong-system` namespace (where {{ site.operator_product_name }} is installed) is shared by all `Gateway` instances managed by that operator. You do not need to create a license per tenant. + +```yaml +apiVersion: configuration.konghq.com/v1alpha1 +kind: KongLicense +metadata: + name: kong-license + namespace: kong-system +rawLicenseString: '' +``` + +## Recommended namespace layout + + + +The recommended pattern for namespace-per-tenant isolation: + +{% table %} +columns: + - title: Namespace + key: namespace + - title: Contents + key: contents +rows: + - namespace: "`kong-system`" + contents: "{{ site.operator_product_name }} pod, `KongLicense`" + - namespace: "`kong-gw-public`" + contents: "`GatewayConfiguration`, `GatewayClass`, `Gateway`, `DataPlane` pod, `HTTPRoute`s, upstream services for the public gateway" + - namespace: "`kong-gw-private`" + contents: Same triptych for the private gateway, in isolation +{% endtable %} + +Each namespace is an independent routing domain. The in-memory KIC for `kong-gw-public` only sees routes in `kong-gw-public`; the KIC for `kong-gw-private` only sees routes in `kong-gw-private`. + + From 293df6592899bc91335a96ea5f4ba1b055af00f1 Mon Sep 17 00:00:00 2001 From: Lucie Milan Date: Thu, 11 Jun 2026 12:50:31 +0200 Subject: [PATCH 2/6] add wait and diagram --- .../operator-multi-tenancy-1-setup.md | 78 ++++++++--- ...operator-multi-tenancy-2-public-gateway.md | 40 +++--- ...perator-multi-tenancy-3-private-gateway.md | 40 +++--- .../operator-multi-tenancy-4-test-services.md | 125 ++++++++++-------- 4 files changed, 175 insertions(+), 108 deletions(-) diff --git a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md index 489e7f0fbf..09b42b4757 100644 --- a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md +++ b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md @@ -1,5 +1,5 @@ --- -title: Install Kong Operator for multi-tenancy +title: Install {{ site.operator_product_name }} for multi-tenancy description: "Create tenant namespaces, install {{ site.operator_product_name }} scoped to those namespaces, and apply a KongLicense." content_type: how_to @@ -46,9 +46,45 @@ prereqs: This guide deploys two independent {{ site.base_gateway }} instances — one public-facing, one private — on the same cluster using a single {{ site.operator_product_name }} installation. Each gateway is scoped to its own namespace so that its in-memory KIC only processes routes from that namespace. +The following diagram shows the end state you'll build across this series: + + +{% mermaid %} +flowchart TB + subgraph cluster["Kubernetes Cluster"] + subgraph sys["kong-system"] + KO["{{ site.operator_product_name }}\n(KongLicense)"] + end + + subgraph pub["kong-gw-public"] + ConfigPub["GatewayConfiguration\nwatchNamespaces: own"] + GWPub["Gateway: gw-public"] + DPPub["Data plane Pod"] + SvcPub["echo service\nHTTPRoute /echo"] + end + + subgraph priv["kong-gw-private"] + ConfigPriv["GatewayConfiguration\nwatchNamespaces: own"] + GWPriv["Gateway: gw-private"] + DPPriv["Data plane Pod"] + SvcPriv["echo service\nHTTPRoute /echo"] + end + + KO -->|manages| GWPub + KO -->|manages| GWPriv + ConfigPub -.->|configures| GWPub + ConfigPriv -.->|configures| GWPriv + GWPub -->|provisions| DPPub + GWPriv -->|provisions| DPPriv + DPPub -->|routes traffic to| SvcPub + DPPriv -->|routes traffic to| SvcPriv + end +{% endmermaid %} + + ## Create namespaces -Create the operator namespace and the two tenant namespaces: +Create the system namespace and the two tenant namespaces: ```bash kubectl create namespace kong-system @@ -60,25 +96,25 @@ kubectl create namespace kong-gw-private -Add the Kong Helm chart repository: - -```bash -helm repo add kong https://charts.konghq.com -helm repo update -``` - -Install {{ site.operator_product_name }} scoped to the two tenant namespaces. The `watch_namespace` value prevents the operator from reconciling resources in any other namespace. - -```bash -helm upgrade --install kong-operator kong/kong-operator \ - -n kong-system \ - --create-namespace \ - --set image.tag={{ site.data.operator_latest.release }} \ - --values - < -Create a `GatewayClass` that references the `GatewayConfiguration` above: - -```bash -kubectl apply -f - < Date: Thu, 11 Jun 2026 15:26:17 +0200 Subject: [PATCH 3/6] debug --- ...perator-multi-tenancy-3-private-gateway.md | 4 +- .../operator-multi-tenancy-4-test-services.md | 160 +++++++++++------- 2 files changed, 97 insertions(+), 67 deletions(-) diff --git a/app/_how-tos/operator/operator-multi-tenancy-3-private-gateway.md b/app/_how-tos/operator/operator-multi-tenancy-3-private-gateway.md index ada79fb82d..af6a280daa 100644 --- a/app/_how-tos/operator/operator-multi-tenancy-3-private-gateway.md +++ b/app/_how-tos/operator/operator-multi-tenancy-3-private-gateway.md @@ -98,7 +98,7 @@ EOF ## Create a Gateway -Create the `Gateway` resource in the `kong-gw-private` namespace, referencing the `GatewayClass` above: +Create the `Gateway` resource in the `kong-gw-private` namespace. The private gateway uses port 8080 to avoid a host-port conflict with the public gateway on single-node clusters (such as OrbStack, k3s, or kind) where each LoadBalancer service binds a host port. ```bash kubectl apply -f - < +## Test isolation + +Both gateways responding on `/echo` confirms they are running, but it does not prove isolation — both routes happen to use the same path. The following test deploys a route that only exists in `kong-gw-private` and verifies that the public gateway has no knowledge of it. + +Create an `HTTPRoute` for a `/private` path in `kong-gw-private` only: + +```bash +kubectl apply -f - < Date: Thu, 11 Jun 2026 17:04:39 +0200 Subject: [PATCH 4/6] fixes --- .../operator-multi-tenancy-1-setup.md | 50 +++--- ...operator-multi-tenancy-2-public-gateway.md | 56 +++---- ...perator-multi-tenancy-3-private-gateway.md | 52 +++--- .../operator-multi-tenancy-4-test-services.md | 121 +++++++------- .../kubernetes-resource/snippet.md | 1 + app/operator/dataplanes/faq/license.md | 2 + .../control-plane-watch-namespaces.md | 51 +++--- app/operator/reference/multi-tenancy.md | 151 ++---------------- 8 files changed, 181 insertions(+), 303 deletions(-) diff --git a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md index 09b42b4757..b2546f2cff 100644 --- a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md +++ b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md @@ -39,12 +39,14 @@ tldr: prereqs: skip_product: true + inline: + - title: Kong Enterprise license + icon_url: /assets/icons/key.svg + content: | + Save your {{site.ee_product_name}} license as `license.json` in your current working directory. If you don't have a license, contact your Kong representative. --- - - -This guide deploys two independent {{ site.base_gateway }} instances — one public-facing, one private — on the same cluster using a single {{ site.operator_product_name }} installation. Each gateway is scoped to its own namespace so that its in-memory KIC only processes routes from that namespace. +This series deploys two independent {{ site.base_gateway }} instances — one public-facing, one private — on the same cluster using a single {{ site.operator_product_name }} installation. Each gateway is scoped to its own namespace so that its in-memory {{ site.kic_product_name_short }} only processes routes from that namespace. The following diagram shows the end state you'll build across this series: @@ -94,8 +96,6 @@ kubectl create namespace kong-gw-private ## Install {{ site.operator_product_name }} - - 1. Add the Kong Helm chart repository: ```bash @@ -116,26 +116,32 @@ kubectl create namespace kong-gw-private EOF ``` -## Validate - -Wait for {{ site.operator_product_name }} to be ready: +1. Wait for {{ site.operator_product_name }} to be ready: +{% capture validate %} {% include prereqs/products/operator-validate-deployment.md %} +{% endcapture %} + +{{validate | indent}} ## Apply a KongLicense - - +Apply the license once in `kong-system`. It's shared by all gateways managed by this operator installation. -Apply the license once in `kong-system`. It is shared by all gateways managed by this operator installation. This assumes your license file is at `./license.json`. +1. Apply the `KongLicense`: -```bash -kubectl -n kong-system apply -f - < - ## Create a GatewayConfiguration -The `controlPlaneOptions.watchNamespaces.type: own` field restricts the in-memory KIC for this gateway to watch only the `kong-gw-public` namespace. Without this, it would watch all namespaces and process routes belonging to other tenants. +The `controlPlaneOptions.watchNamespaces.type: own` field restricts the in-memory {{ site.kic_product_name_short }} for this gateway to watch only the `kong-gw-public` namespace. Without this, it would watch all namespaces and process routes belonging to other tenants. ```bash -kubectl apply -f - < - 1. Create a `GatewayClass` that references the `GatewayConfiguration` above: ```bash - kubectl apply -f - < - ## Create a GatewayConfiguration -As with the public gateway, `controlPlaneOptions.watchNamespaces.type: own` restricts the in-memory KIC to watch only the `kong-gw-private` namespace. +As with the public gateway, `controlPlaneOptions.watchNamespaces.type: own` restricts the in-memory {{ site.kic_product_name_short }} to watch only the `kong-gw-private` namespace. ```bash -kubectl apply -f - < - -## Deploy a test service in each namespace +## Deploy test services 1. Deploy the echo service in both tenant namespaces: @@ -72,7 +69,7 @@ prereqs: Create an `HTTPRoute` in the `kong-gw-public` namespace pointing to the echo service: ```bash -kubectl apply -f - < **Note:** Setting this field in `ControlPlane` will configure the `CONTROLLER_WATCH_NAMESPACE` environment variable in the managed {{ site.kic_product_name }}. -> If you manually set the `CONTROLLER_WATCH_NAMESPACE` environment variable through `podTemplateSpec`, it will **override** this configuration. +> The `watchNamespaces` setting configures the `CONTROLLER_WATCH_NAMESPACE` environment variable in the managed {{ site.kic_product_name_short }}. If you set this variable manually through `podTemplateSpec`, it will override the `watchNamespaces` field. The `all` and `own` types don't require any further changes or additional resources. The `list` type requires further configuration. @@ -49,17 +48,18 @@ The `all` and `own` types don't require any further changes or additional resour The `list` type requires two additional steps: -1. Specify the namespaces to watch in the `spec.watchNamespaces.list` field. +1. Specify the namespaces to watch in the `spec.watchNamespaces.list` field: + ```yaml spec: watchNamespaces: type: list - list: - - namespace-a - - namespace-b + list: + - namespace-a + - namespace-b ``` -1. Create a `WatchNamespaceGrant` resource in each of the specified namespaces. This resource grants the `ControlPlane` permission to watch resources in the specified namespace. It can be defined as: +1. Create a `WatchNamespaceGrant` resource in each of the specified namespaces. This resource grants the `ControlPlane` permission to watch resources in that namespace: ```yaml apiVersion: gateway-operator.konghq.com/v1alpha1 @@ -76,20 +76,18 @@ The `list` type requires two additional steps: For more information on the `WatchNamespaceGrant` CRD, see the [CRD reference](/operator/reference/custom-resources/#watchnamespacegrant). -## Multi-tenancy using watch namespaces {% new_in 2.0 %} +## Operator-level namespace scoping {% new_in 2.0 %} -Multi-tenancy, in the context of {{ site.operator_product_name }}, is an approach that allows multiple instances of the {{ site.operator_product_name }} to share the same underlying infrastructure while keeping their data isolated and more specifically to watch disjoint namespaces. - -This allows you to configure {{ site.operator_product_name }} itself to watch namespaces instead of always specifying them in the `ControlPlane` resources. +From v2.0, you can scope {{ site.operator_product_name }} itself to watch only specific namespaces. This is separate from the per-`ControlPlane` `watchNamespaces` configuration and is useful when running multiple {{ site.operator_product_name }} instances on the same cluster with strictly disjoint namespace assignments. {:.warning} -> **Important:** If you configure watch namespaces on both {{ site.operator_product_name }} and `ControlPlane` resources, they must be configured so that they don't conflict. For example, if the {{ site.operator_product_name }} watches namespaces A and B, the `ControlPlane` resource can only define watch namespaces A and B. If you use other watch namespaces, such as namespace C, the `ControlPlane` object will receive an appropriate status condition and won't reconcile your configuration. +> If you configure watch namespaces on both {{ site.operator_product_name }} and `ControlPlane` resources, they must not conflict. For example, if {{ site.operator_product_name }} watches namespaces A and B, the `ControlPlane` can only define watch namespaces A or B. Using namespace C would cause the `ControlPlane` to receive a failure status condition and stop reconciling. You can set watch namespaces for {{ site.operator_product_name }} using several methods: {% navtabs "multi-tenant-namespaces" %} {% navtab "Helm chart" %} -When using the `kong-operator` Helm chart, you can use the `env` top level configuration in your `values.yaml`: +When using the `kong-operator` Helm chart, use the `env` top-level configuration in your `values.yaml`: ```yaml env: @@ -102,15 +100,10 @@ KONG_OPERATOR_WATCH_NAMESPACES='namespace-a,namespace-b' ``` {% endnavtab %} {% navtab "CLI" %} -To specify the comma separated list of namespaces to watch you can use the `--watch-namespaces` flag: +Use the `--watch-namespaces` flag with a comma-separated list of namespaces: ```bash ... --watch-namespaces namespace-a,namespace-b ... ``` {% endnavtab %} {% endnavtabs %} - - - - - diff --git a/app/operator/reference/multi-tenancy.md b/app/operator/reference/multi-tenancy.md index bd1a079e6e..9a5b5d2775 100644 --- a/app/operator/reference/multi-tenancy.md +++ b/app/operator/reference/multi-tenancy.md @@ -33,158 +33,27 @@ related_resources: url: /operator/reference/custom-resources/ --- - - Multi-tenancy in {{ site.operator_product_name }} means running multiple isolated {{ site.base_gateway }} instances — each with their own routing configuration, data plane, and namespace scope — on the same Kubernetes cluster, managed by a single {{ site.operator_product_name }} installation. -Common use cases include separating a public-facing API gateway from an internal one, or giving different teams independent gateway instances without requiring separate clusters. +Common use cases include separating a public-facing API gateway from an internal one, or giving different teams independent gateway instances without requiring separate clusters. For a step-by-step walkthrough, see [Deploy multiple isolated gateways](/operator/dataplanes/how-to/multi-tenancy/setup/). ## How it works - - -Each tenant is represented by a **Gateway**, provisioned using a triptych of three resources: +Each tenant is represented by a `Gateway`, provisioned using three resources: -``` -GatewayConfiguration ──(parametersRef)──▶ GatewayClass ──(gatewayClassName)──▶ Gateway -``` +* `GatewayConfiguration`: A Kong-specific resource that configures the control plane and data plane options for a gateway, such as the proxy image, environment variables, and namespace watch scope. +* `GatewayClass`: A cluster-scoped Gateway API resource that registers {{ site.operator_product_name }} as the controller for gateways of this class. It references the `GatewayConfiguration` via `parametersRef`. +* `Gateway`: A namespaced Gateway API resource that declares a gateway instance. It references the `GatewayClass` via `gatewayClassName`, which is how {{ site.operator_product_name }} associates it with the correct `GatewayConfiguration`. For each `Gateway`, {{ site.operator_product_name }} creates: -- One **in-memory KIC instance** embedded inside the {{ site.operator_product_name }} pod (not a separately deployed pod), which watches Gateway API resources and translates them into Kong configuration. -- One **DataPlane deployment** running {{ site.base_gateway }} in DB-less mode. +* One in-memory {{ site.kic_product_name_short }} instance embedded inside the {{ site.operator_product_name }} Pod, which watches Gateway API resources and translates them into Kong configuration. +* One*data plane deployment running {{ site.base_gateway }} in DB-less mode. -Multiple `Gateway` resources can coexist in the same cluster. The resulting in-memory KIC instances and DataPlane pods are independent of each other. A single {{ site.operator_product_name }} installation manages them all. +Multiple `Gateway` resources can coexist in the same cluster. The resulting in-memory {{ site.kic_product_name_short }} instances and data plane Pods are independent of each other. A single {{ site.operator_product_name }} installation manages them all. ## Namespace isolation - - -By default, each in-memory KIC instance watches **all namespaces** for Gateway API resources (`HTTPRoute`, `GRPCRoute`, etc.). This means, without additional configuration, one tenant's KIC would also process another tenant's routes. Namespace isolation is therefore required for multi-tenancy. - -The `watchNamespaces` field on `GatewayConfiguration.spec.controlPlaneOptions` controls which namespaces the in-memory KIC for each `Gateway` will watch. - - - -### watchNamespaces types - - - -The `watchNamespaces.type` field accepts three values: - -{% table %} -columns: - - title: Type - key: type - - title: Behavior - key: behavior - - title: Use when - key: use -rows: - - type: "`all`" - behavior: Watches all namespaces (default) - use: Single-tenant or dev environments - - type: "`own`" - behavior: Watches only the `ControlPlane`'s own namespace - use: One namespace per tenant (recommended) - - type: "`list`" - behavior: Watches own namespace plus a specified list - use: Routes for this tenant span multiple namespaces -{% endtable %} - -When using `list`, the ControlPlane's own namespace is automatically included. Each additional namespace in the list requires a `WatchNamespaceGrant` resource in that namespace (see [WatchNamespaceGrant](#watchnamespacegrant)). - -{:.info} -> Setting `watchNamespaces` via `GatewayConfiguration.spec.controlPlaneOptions` configures the `CONTROLLER_WATCH_NAMESPACE` environment variable in the managed KIC. If you set this variable manually through `podTemplateSpec`, it will override the `watchNamespaces` field. - -## Operator-level namespace scoping {% new_in 2.0 %} - - - -From v2.0, you can scope {{ site.operator_product_name }} itself to watch only specific namespaces. This is separate from the per-`ControlPlane` `watchNamespaces` configuration and is useful when running multiple {{ site.operator_product_name }} instances on the same cluster with strictly disjoint namespace assignments. - -Configure operator-level watch namespaces using the `watch_namespace` Helm value: - -```yaml -# values.yaml -env: - watch_namespace: namespace-a,namespace-b -``` - -Or via environment variable: - -```sh -KONG_OPERATOR_WATCH_NAMESPACES='namespace-a,namespace-b' -``` - -{:.warning} -> If both operator-level and `ControlPlane`-level watch namespaces are configured, they must not conflict. For example, if {{ site.operator_product_name }} watches namespaces A and B, a `ControlPlane` may only define watch namespaces A or B. Using namespace C would cause the `ControlPlane` to receive a failure status condition and stop reconciling. - -## WatchNamespaceGrant - - - -When `watchNamespaces.type` is `list`, a `WatchNamespaceGrant` resource must be created in each additional namespace (beyond the ControlPlane's own). This resource explicitly grants the named `ControlPlane` permission to watch resources in that namespace. - -```yaml -apiVersion: gateway-operator.konghq.com/v1alpha1 -kind: WatchNamespaceGrant -metadata: - name: watch-namespace-grant - namespace: target-namespace # the namespace being granted access to -spec: - from: - - group: gateway-operator.konghq.com - kind: ControlPlane - namespace: control-plane-namespace # the namespace where the ControlPlane lives -``` - -For the full field reference, see [WatchNamespaceGrant](/operator/reference/custom-resources/#watchnamespacegrant). - -## KongLicense - - - - -A single `KongLicense` applied in the `kong-system` namespace (where {{ site.operator_product_name }} is installed) is shared by all `Gateway` instances managed by that operator. You do not need to create a license per tenant. - -```yaml -apiVersion: configuration.konghq.com/v1alpha1 -kind: KongLicense -metadata: - name: kong-license - namespace: kong-system -rawLicenseString: '' -``` - -## Recommended namespace layout - - +By default, each in-memory {{ site.kic_product_name_short }} instance watches all namespaces for Gateway API resources (`HTTPRoute`, `GRPCRoute`, etc.). Without additional configuration, one tenant's {{ site.kic_product_name_short }} would also process another tenant's routes, so namespace isolation is required for multi-tenancy. -The recommended pattern for namespace-per-tenant isolation: - -{% table %} -columns: - - title: Namespace - key: namespace - - title: Contents - key: contents -rows: - - namespace: "`kong-system`" - contents: "{{ site.operator_product_name }} pod, `KongLicense`" - - namespace: "`kong-gw-public`" - contents: "`GatewayConfiguration`, `GatewayClass`, `Gateway`, `DataPlane` pod, `HTTPRoute`s, upstream services for the public gateway" - - namespace: "`kong-gw-private`" - contents: Same triptych for the private gateway, in isolation -{% endtable %} - -Each namespace is an independent routing domain. The in-memory KIC for `kong-gw-public` only sees routes in `kong-gw-public`; the KIC for `kong-gw-private` only sees routes in `kong-gw-private`. - - +Set `watchNamespaces` on `GatewayConfiguration.spec.controlPlaneOptions` to restrict each gateway's in-memory {{ site.kic_product_name_short }} to its own namespace. For the full field reference, type options, `WatchNamespaceGrant` configuration, and operator-level scoping, see [Limiting namespaces watched by ControlPlane](/operator/reference/control-plane-watch-namespaces/). \ No newline at end of file From 57b77bcbc530346ae855625a59cffa97189d8f8f Mon Sep 17 00:00:00 2001 From: Lucie Milan Date: Thu, 11 Jun 2026 17:08:20 +0200 Subject: [PATCH 5/6] fixes --- app/operator/dataplanes/faq/license.md | 2 +- app/operator/reference/control-plane-watch-namespaces.md | 4 ++-- app/operator/reference/multi-tenancy.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/operator/dataplanes/faq/license.md b/app/operator/dataplanes/faq/license.md index 93efe4d7df..dd9217edeb 100644 --- a/app/operator/dataplanes/faq/license.md +++ b/app/operator/dataplanes/faq/license.md @@ -75,7 +75,7 @@ breadcrumbs: kubectl create secret generic kong-enterprise-license --from-file=license=./license.json -n default ``` -1. Specify the `KONG_LICENSE_DATA` environment variable for your DataPlane pods. This can be provided on the `DataPlane` or `GatewayConfiguration` +1. Specify the `KONG_LICENSE_DATA` environment variable for your DataPlane pods. This can be provided on the `DataPlane` or `GatewayConfiguration`. ### DataPlane diff --git a/app/operator/reference/control-plane-watch-namespaces.md b/app/operator/reference/control-plane-watch-namespaces.md index 92269c4d13..f4c964e32b 100644 --- a/app/operator/reference/control-plane-watch-namespaces.md +++ b/app/operator/reference/control-plane-watch-namespaces.md @@ -28,10 +28,10 @@ By default, {{ site.operator_product_name }}'s `ControlPlane` watches all namesp You can restrict namespace watching in two ways depending on how you manage your gateways: -* *Managed Gateways (Gateway API flow): Set `watchNamespaces` via `GatewayConfiguration.spec.controlPlaneOptions`. You do not create a `ControlPlane` directly. See [Multi-tenancy](/operator/reference/multi-tenancy/) for the full use case. +* Managed Gateways (Gateway API flow): Set `watchNamespaces` via `GatewayConfiguration.spec.controlPlaneOptions`. You do not create a `ControlPlane` directly. See [Multi-tenancy](/operator/reference/multi-tenancy/) for the full use case. * Direct `ControlPlane` management: Set `watchNamespaces` directly in the `ControlPlane`'s `spec`. -## watchNamespaces types +## `watchNamespaces` types The `watchNamespaces.type` field accepts three values: diff --git a/app/operator/reference/multi-tenancy.md b/app/operator/reference/multi-tenancy.md index 9a5b5d2775..8774022971 100644 --- a/app/operator/reference/multi-tenancy.md +++ b/app/operator/reference/multi-tenancy.md @@ -48,7 +48,7 @@ Each tenant is represented by a `Gateway`, provisioned using three resources: For each `Gateway`, {{ site.operator_product_name }} creates: * One in-memory {{ site.kic_product_name_short }} instance embedded inside the {{ site.operator_product_name }} Pod, which watches Gateway API resources and translates them into Kong configuration. -* One*data plane deployment running {{ site.base_gateway }} in DB-less mode. +* One data plane deployment running {{ site.base_gateway }} in DB-less mode. Multiple `Gateway` resources can coexist in the same cluster. The resulting in-memory {{ site.kic_product_name_short }} instances and data plane Pods are independent of each other. A single {{ site.operator_product_name }} installation manages them all. From e18aa272a12f53c64410a42d4a03c44461d8db45 Mon Sep 17 00:00:00 2001 From: Lucie Milan Date: Thu, 11 Jun 2026 17:09:44 +0200 Subject: [PATCH 6/6] Update operator-multi-tenancy-1-setup.md --- app/_how-tos/operator/operator-multi-tenancy-1-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md index b2546f2cff..3892ef6406 100644 --- a/app/_how-tos/operator/operator-multi-tenancy-1-setup.md +++ b/app/_how-tos/operator/operator-multi-tenancy-1-setup.md @@ -40,7 +40,7 @@ tldr: prereqs: skip_product: true inline: - - title: Kong Enterprise license + - title: "{{site.ee_product_name}} license" icon_url: /assets/icons/key.svg content: | Save your {{site.ee_product_name}} license as `license.json` in your current working directory. If you don't have a license, contact your Kong representative.