From cc2950874026f226047703ebcd8a64706b07099f Mon Sep 17 00:00:00 2001 From: hc-github-team-consul-core Date: Thu, 23 Feb 2023 10:52:26 -0800 Subject: [PATCH] Backport of Docs/cluster peering 1.15 updates into release/1.14.x (#16397) * backport of commit e878d2d3e435a724e26789ab6fda84d009961495 * backport of commit 5a378f5794f5f4c9b2b697014c4168b6ab2bdef7 * backport of commit 1f7e812d856960985351d70306ef3527531f2d6d * Docs/cluster peering 1.15 updates (#16291) * initial commit * initial commit * Overview updates * Overview page improvements * More Overview improvements * improvements * Small fixes/updates * Updates * Overview updates * Nav data * More nav updates * Fix * updates * Updates + tip test * Directory test * refining * Create restructure w/ k8s * Single usage page * Technical Specification * k8s pages * typo * L7 traffic management * Manage connections * k8s page fix * Create page tab corrections * link to k8s * intentions * corrections * Add-on intention descriptions * adjustments * Missing * Diagram improvements * Final diagram update * Apply suggestions from code review Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Co-authored-by: Tu Nguyen Co-authored-by: David Yu * diagram name fix * Fixes * Updates to index.mdx * Tech specs page corrections * Tech specs page rename * update link to tech specs * K8s - new pages + tech specs * k8s - manage peering connections * k8s L7 traffic management * Separated establish connection pages * Directory fixes * Usage clean up * k8s docs edits * Updated nav data * CodeBlock Component fix * filename * CodeBlockConfig removal * Redirects * Update k8s filenames * Reshuffle k8s tech specs for clarity, fmt yaml files * Update general cluster peering docs, reorder CLI > API > UI, cross link to kubernetes * Fix config rendering in k8s usage docs, cross link to general usage from k8s docs * fix legacy link * update k8s docs * fix nested list rendering * redirect fix * page error --------- Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Co-authored-by: Tu Nguyen Co-authored-by: David Yu Co-authored-by: Tu Nguyen --------- Co-authored-by: boruszak Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Co-authored-by: Tu Nguyen Co-authored-by: David Yu Co-authored-by: Tu Nguyen --- .../configuration.mdx} | 18 +- .../cluster-peering/create-manage-peering.mdx | 537 ---------------- .../docs/connect/cluster-peering/index.mdx | 72 ++- .../docs/connect/cluster-peering/k8s.mdx | 593 ------------------ .../connect/cluster-peering/tech-specs.mdx | 69 ++ .../usage/create-cluster-peering.mdx | 207 ++++++ .../usage/establish-cluster-peering.mdx | 271 ++++++++ .../usage/manage-connections.mdx | 137 ++++ .../usage/peering-traffic-management.mdx | 168 +++++ .../config-entries/service-intentions.mdx | 51 ++ .../connect/cluster-peering/tech-specs.mdx | 180 ++++++ .../usage/establish-peering.mdx | 453 +++++++++++++ .../cluster-peering/usage/l7-traffic.mdx | 75 +++ .../cluster-peering/usage/manage-peering.mdx | 121 ++++ website/data/docs-nav-data.json | 66 +- .../public/img/cluster-peering-diagram.png | Bin 0 -> 129219 bytes website/redirects.js | 14 +- 17 files changed, 1857 insertions(+), 1175 deletions(-) rename website/content/docs/connect/{gateways/mesh-gateway/service-to-service-traffic-peers.mdx => cluster-peering/configuration.mdx} (69%) delete mode 100644 website/content/docs/connect/cluster-peering/create-manage-peering.mdx delete mode 100644 website/content/docs/connect/cluster-peering/k8s.mdx create mode 100644 website/content/docs/connect/cluster-peering/tech-specs.mdx create mode 100644 website/content/docs/connect/cluster-peering/usage/create-cluster-peering.mdx create mode 100644 website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx create mode 100644 website/content/docs/connect/cluster-peering/usage/manage-connections.mdx create mode 100644 website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx create mode 100644 website/content/docs/k8s/connect/cluster-peering/tech-specs.mdx create mode 100644 website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx create mode 100644 website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx create mode 100644 website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx create mode 100644 website/public/img/cluster-peering-diagram.png diff --git a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx b/website/content/docs/connect/cluster-peering/configuration.mdx similarity index 69% rename from website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx rename to website/content/docs/connect/cluster-peering/configuration.mdx index 4adeecaf1a..53fab696c0 100644 --- a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx +++ b/website/content/docs/connect/cluster-peering/configuration.mdx @@ -1,19 +1,13 @@ --- layout: docs -page_title: Enabling Service-to-service Traffic Across Peered Clusters +page_title: Cluster Peering Configuration description: >- - Mesh gateways are specialized proxies that route data between services that cannot communicate directly. Learn how to enable service-to-service traffic across clusters in different datacenters or admin partitions that have an established peering connection. + --- # Enabling Service-to-service Traffic Across Peered Clusters -Mesh gateways are required for you to route service mesh traffic between peered Consul clusters. Clusters can reside in different clouds or runtime environments where general interconnectivity between all services in all clusters is not feasible. - -At a minimum, a peered cluster exporting a service must have a mesh gateway registered. -For Enterprise, this mesh gateway must also be registered in the same partition as the exported service(s). -To use the `local` mesh gateway mode, there must also be a mesh gateway regsitered in the importing cluster. - -Unlike mesh gateways for WAN-federated datacenters and partitions, mesh gateways between peers terminate mTLS sessions to decrypt data to HTTP services and then re-encrypt traffic to send to services. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. +The topic provides an overview of the configuration options and process for cluster peering. ## Prerequisites @@ -47,14 +41,16 @@ Alternatively, you can also use the CLI to spin up and register a gateway in Con ### ACL configuration -If ACLs are enabled, you must add a token granting `service:write` for the gateway's service name and `service:read` for all services in the Enterprise admin partition or OSS datacenter to the gateway's service definition. +If ACLs are enabled, you must add a token granting `service:write` for the gateway's service name and `service:read` for all services in the Enterprise admin partition or OSS datacenter to the gateway's service definition. + These permissions authorize the token to route communications for other Consul service mesh services. You must also grant `mesh:write` to mesh gateways routing peering traffic in the data plane. + This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. ### Modes Modes are configurable as either `remote` or `local` for mesh gateways that connect peered clusters. The `none` setting is invalid for mesh gateways in peered clusters and will be ignored by the gateway. -By default, all proxies connecting to peered clusters use mesh gateways in [remote mode](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). +By default, all proxies connecting to peered clusters use mesh gateways in [remote mode](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx deleted file mode 100644 index 71b3f50d2e..0000000000 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ /dev/null @@ -1,537 +0,0 @@ ---- -layout: docs -page_title: Cluster Peering - Create and Manage Connections -description: >- - Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to create, list, read, check, and delete peering connections. ---- - - -# Create and Manage Cluster Peering Connections - -A peering token enables cluster peering between different datacenters. Once you generate a peering token, you can use it to establish a connection between clusters. Then you can export services and create intentions so that peered clusters can call those services. - -## Create a peering connection - -Cluster peering is enabled by default on Consul servers as of v1.14. For additional information, including options to disable cluster peering, refer to [Configuration Files](/docs/agent/config/config-files). - -The process to create a peering connection is a sequence with multiple steps: - -1. Create a peering token -1. Establish a connection between clusters -1. Export services between clusters -1. Authorize services for peers - -You can generate peering tokens and initiate connections on any available agent using either the API, CLI, or the Consul UI. If you use the API or CLI, we recommend performing these operations through a client agent in the partition you want to connect. - -The UI does not currently support exporting services between clusters or authorizing services for peers. - -### Create a peering token - -To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. - -Every time you generate a peering token, a single-use establishment secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. - - - - -In `cluster-01`, use the [`/peering/token` endpoint](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. - -```shell-session -$ curl --request POST --data '{"Peer":"cluster-02"}' --url http://localhost:8500/v1/peering/token -``` - -The CLI outputs the peering token, which is a base64-encoded string containing the token details. - -Create a JSON file that contains the first cluster's name and the peering token. - - - -```json -{ - "Peer": "cluster-01", - "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" -} -``` - - - - - - -In `cluster-01`, use the [`consul peering generate-token` command](/commands/peering/generate-token) to issue a request for a peering token. - -```shell-session -$ consul peering generate-token -name cluster-02 -``` - -The CLI outputs the peering token, which is a base64-encoded string containing the token details. -Save this value to a file or clipboard to be used in the next step on `cluster-02`. - - - - - -1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. -1. Click **Add peer connection**. -1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. -1. Click the **Generate token** button. -1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. - - - - -### Establish a connection between clusters - -Next, use the peering token to establish a secure connection between the clusters. - - - - -In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. - -```shell-session -$ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish -``` - -When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/api-docs/peering). - -You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. - - - - - -In one of the client agents in "cluster-02," issue the [`consul peering establish` command](/commands/peering/establish) and specify the token generated in the previous step. The command establishes the peering connection. -The commands prints "Successfully established peering connection with cluster-01" after the connection is established. - -```shell-session -$ consul peering establish -name cluster-01 -peering-token token-from-generate -``` - -When you connect server agents through cluster peering, they peer their default partitions. -To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer. -For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) . - -You can run the `peering establish` command once per peering token. -Peering tokens cannot be reused after being used to establish a connection. -If you need to re-establish a connection, you must generate a new peering token. - - - - - -1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. -1. Click **Establish peering**. -1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. -1. Click **Add peer**. - - - - -### Export services between clusters - -After you establish a connection between the clusters, you need to create a configuration entry that defines the services that are available for other clusters. Consul uses this configuration entry to advertise service information and support service mesh connections across clusters. - -First, create a configuration entry and specify the `Kind` as `"exported-services"`. - - - -```hcl -Kind = "exported-services" -Name = "default" -Services = [ - { - ## The name and namespace of the service to export. - Name = "service-name" - Namespace = "default" - - ## The list of peer clusters to export the service to. - Consumers = [ - { - ## The peer name to reference in config is the one set - ## during the peering process. - Peer = "cluster-02" - } - ] - } -] -``` - - - -Then, add the configuration entry to your cluster. - -```shell-session -$ consul config write peering-config.hcl -``` - -Before you proceed, wait for the clusters to sync and make services available to their peers. You can issue an endpoint query to [check the peered cluster status](#check-peered-cluster-status). - -### Authorize services for peers - -Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. - -First, create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." The following example sets service intentions so that "frontend-service" can access "backend-service." - - - -```hcl -Kind = "service-intentions" -Name = "backend-service" - -Sources = [ - { - Name = "frontend-service" - Peer = "cluster-02" - Action = "allow" - } -] -``` - - - -If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. - -Then, add the configuration entry to your cluster. - -```shell-session -$ consul config write peering-intentions.hcl -``` - -### Authorize Service Reads with ACLs - -If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. -Read access to all imported services is granted using either of the following rules associated with an ACL token: -- `service:write` permissions for any service in the sidecar's partition. -- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. -For Consul Enterprise, access is granted to all imported services in the service's partition. -These permissions are satisfied when using a [service identity](/docs/security/acl/acl-roles#service-identities). - -Example rule files can be found in [Reading Servers](/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` config entry documentation. - -Refer to [ACLs System Overview](/docs/security/acl) for more information on ACLs and their setup. - -## Manage peering connections - -After you establish a peering connection, you can get a list of all active peering connections, read a specific peering connection's information, check peering connection health, and delete peering connections. - -### List all peering connections - -You can list all active peering connections in a cluster. - - - - -After you establish a peering connection, [query the `/peerings/` endpoint](/api-docs/peering#list-all-peerings) to get a list of all peering connections. For example, the following command requests a list of all peering connections on `localhost` and returns the information as a series of JSON objects: - -```shell-session -$ curl http://127.0.0.1:8500/v1/peerings - -[ - { - "ID": "462c45e8-018e-f19d-85eb-1fc1bcc2ef12", - "Name": "cluster-02", - "State": "ACTIVE", - "Partition": "default", - "PeerID": "e83a315c-027e-bcb1-7c0c-a46650904a05", - "PeerServerName": "server.dc1.consul", - "PeerServerAddresses": [ - "10.0.0.1:8300" - ], - "CreateIndex": 89, - "ModifyIndex": 89 - }, - { - "ID": "1460ada9-26d2-f30d-3359-2968aa7dc47d", - "Name": "cluster-03", - "State": "INITIAL", - "Partition": "default", - "Meta": { - "env": "production" - }, - "CreateIndex": 109, - "ModifyIndex": 119 - }, -] -``` - - - - -After you establish a peering connection, run the [`consul peering list`](/commands/peering/list) command to get a list of all peering connections. -For example, the following command requests a list of all peering connections and returns the information in a table: - -```shell-session -$ consul peering list - -Name State Imported Svcs Exported Svcs Meta -cluster-02 ACTIVE 0 2 env=production -cluster-03 PENDING 0 0 - ``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter. - -The name that appears in the list is the name of the cluster in a different datacenter with an established peering connection. - - - -### Read a peering connection - -You can get information about individual peering connections between clusters. - - - - -After you establish a peering connection, [query the `/peering/` endpoint](/api-docs/peering#read-a-peering-connection) to get peering information about for a specific cluster. For example, the following command requests peering connection information for "cluster-02" and returns the info as a JSON object: - -```shell-session -$ curl http://127.0.0.1:8500/v1/peering/cluster-02 - -{ - "ID": "462c45e8-018e-f19d-85eb-1fc1bcc2ef12", - "Name": "cluster-02", - "State": "INITIAL", - "PeerID": "e83a315c-027e-bcb1-7c0c-a46650904a05", - "PeerServerName": "server.dc1.consul", - "PeerServerAddresses": [ - "10.0.0.1:8300" - ], - "CreateIndex": 89, - "ModifyIndex": 89 -} -``` - - - - -After you establish a peering connection, run the [`consul peering read`](/commands/peering/list) command to get peering information about for a specific cluster. -For example, the following command requests peering connection information for "cluster-02": - -```shell-session -$ consul peering read -name cluster-02 - -Name: cluster-02 -ID: 3b001063-8079-b1a6-764c-738af5a39a97 -State: ACTIVE -Meta: - env=production - -Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 -Peer Server Name: server.dc1.consul -Peer CA Pems: 0 -Peer Server Addresses: - 10.0.0.1:8300 - -Imported Services: 0 -Exported Services: 2 - -Create Index: 89 -Modify Index: 89 - -``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. Click the name of a peered cluster to view additional details about the peering connection. - - - -### Check peering connection health - -You can check the status of your peering connection to perform health checks. - -To confirm that the peering connection between your clusters remains healthy, query the [`health/service` endpoint](/api-docs/health) of one cluster from the other cluster. For example, in "cluster-02," query the endpoint and add the `peer=cluster-01` query parameter to the end of the URL. - -```shell-session -$ curl \ - "http://127.0.0.1:8500/v1/health/service/?peer=cluster-01" -``` - -A successful query includes service information in the output. - -### Delete peering connections - -You can disconnect the peered clusters by deleting their connection. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. - - - - -In "cluster-01," request the deletion through the [`/peering/ endpoint`](/api-docs/peering#delete-a-peering-connection). - -```shell-session -$ curl --request DELETE http://127.0.0.1:8500/v1/peering/cluster-02 -``` - - - - -In "cluster-01," request the deletion through the [`consul peering delete`](/commands/peering/list) command. - -```shell-session -$ consul peering delete -name cluster-02 - -Successfully submitted peering connection, cluster-02, for deletion -``` - - - - -In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. - -Next to the name of the peer, click **More** (three horizontal dots) and then **Delete**. Click **Delete** to confirm and remove the peering connection. - - - - -## L7 traffic management between peers - -The following sections describe how to enable L7 traffic management features between peered clusters. - -### Service resolvers for redirects and failover - -As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. The following examples update the [`service-resolver` config entry](/docs/connect/config-entries/) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. - - - -```hcl -Kind = "service-resolver" -Name = "frontend" -ConnectTimeout = "15s" -Failover = { - "*" = { - Targets = [ - {Peer = "cluster-02"} - ] - } -} -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend -spec: - connectTimeout: 15s - failover: - '*': - targets: - - peer: 'cluster-02' - service: 'frontend' - namespace: 'default' -``` - -```json -{ - "ConnectTimeout": "15s", - "Kind": "service-resolver", - "Name": "frontend", - "Failover": { - "*": { - "Targets": [ - { - "Peer": "cluster-02" - } - ] - } - }, - "CreateIndex": 250, - "ModifyIndex": 250 -} -``` - - - -### Service splitters and custom routes - -The `service-splitter` and `service-router` configuration entry kinds do not support directly targeting a service instance hosted on a peer. To split or route traffic to a service on a peer, you must combine the definition with a `service-resolver` configuration entry that defines the service hosted on the peer as an upstream service. For example, to split traffic evenly between `frontend` services hosted on peers, first define the desired behavior locally: - - - -```hcl -Kind = "service-splitter" -Name = "frontend" -Splits = [ - { - Weight = 50 - ## defaults to service with same name as configuration entry ("frontend") - }, - { - Weight = 50 - Service = "frontend-peer" - }, -] -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceSplitter -metadata: - name: frontend -spec: - splits: - - weight: 50 - ## defaults to service with same name as configuration entry ("web") - - weight: 50 - service: frontend-peer -``` - -```json -{ - "Kind": "service-splitter", - "Name": "frontend", - "Splits": [ - { - "Weight": 50 - }, - { - "Weight": 50, - "Service": "frontend-peer" - } - ] -} -``` - - - -Then, create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: - - - -```hcl -Kind = "service-resolver" -Name = "frontend-peer" -Redirect { - Service = frontend - Peer = "cluster-02" -} -``` - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend-peer -spec: - redirect: - peer: 'cluster-02' - service: 'frontend' -``` - -```json -{ - "Kind": "service-resolver", - "Name": "frontend-peer", - "Redirect": { - "Service": "frontend", - "Peer": "cluster-02" - } -} -``` - - - diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 184598c546..f983a23c53 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -1,32 +1,37 @@ --- layout: docs -page_title: Service Mesh - What is Cluster Peering? +page_title: Cluster Peering Overview description: >- - Cluster peering establishes communication between independent clusters in Consul, allowing services to interact across datacenters. Learn about the cluster peering process, differences with WAN federation for multi-datacenter deployments, and technical constraints. + Cluster peering establishes communication between independent clusters in Consul, allowing services to interact across datacenters. Learn how cluster peering works, its differences with WAN federation for multi-datacenter deployments, and how to troubleshoot common issues. --- -# What is Cluster Peering? +# Cluster peering overview -You can create peering connections between two or more independent clusters so that services deployed to different partitions or datacenters can communicate. +This topic provides an overview of cluster peering, which lets you connect two or more independent Consul clusters so that services deployed to different partitions or datacenters can communicate. +Cluster peering is enabled in Consul by default. For specific information about cluster peering configuration and usage, refer to following pages. -## Overview +## What is cluster peering? -Cluster peering is a process that allows Consul clusters to communicate with each other. The cluster peering process consists of the following steps: +Consul supports cluster peering connections between two [admin partitions](/consul/docs/enterprise/admin-partitions) _in different datacenters_. Deployments without an Enterprise license can still use cluster peering because every datacenter automatically includes a default partition. Meanwhile, admin partitions _in the same datacenter_ do not require cluster peering connections because you can export services between them without generating or exchanging a peering token. -1. Create a peering token in one cluster. -1. Use the peering token to establish peering with a second cluster. -1. Export services between clusters. -1. Create intentions to authorize services for peers. +The following diagram describes Consul's cluster peering architecture. -This process establishes cluster peering between two [admin partitions](/docs/enterprise/admin-partitions). Deployments without an Enterprise license can still use cluster peering because every datacenter automatically includes a `default` partition. +![Diagram of cluster peering with admin partitions](/img/cluster-peering-diagram.png) -For detailed instructions on establishing cluster peering connections, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). +In this diagram, the `default` partition in Consul DC 1 has a cluster peering connection with the `web` partition in Consul DC 2. Enforced by their respective mesh gateways, this cluster peering connection enables `Service B` to communicate with `Service C` as a service upstream. -> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](https://learn.hashicorp.com/tutorials/consul/cluster-peering-aws?utm_source=docs). +Cluster peering leverages several components of Consul's architecture to enforce secure communication between services: -### Differences between WAN federation and cluster peering +- A _peering token_ contains an embedded secret that securely establishes communication when shared symetrically between datacenters. Sharing this token enables each datacenter's server agents to recognize requests from authorized peers, similar to how the [gossip encryption key secures agent LAN gossip](/consul/docs/security/encryption#gossip-encryption). +- A _mesh gateway_ encrypts outgoing traffic, decrypts incoming traffic, and directs traffic to healthy services. Consul's service mesh features must be enabled in order to use mesh gateways. Mesh gateways support the specific admin partitions they are deployed on. Refer to [Mesh gateways](/consul/docs/connect/gateways/mesh-gateway) for more information. +- An _exported service_ communicates with downstreams deployed in other admin partitions. They are explicitly defined in an [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services). +- A _service intention_ secures [service-to-service communication in a service mesh](/consul/docs/connect/intentions). Intentions enable identity-based access between services by exchanging TLS certificates, which the service's sidecar proxy verifies upon each request. -WAN federation and cluster peering are different ways to connect Consul deployments. WAN federation connects multiple datacenters to make them function as if they were a single cluster, while cluster peering treats each datacenter as a separate cluster. As a result, WAN federation requires a primary datacenter to maintain and replicate global states such as ACLs and configuration entries, but cluster peering does not. +### Compared with WAN federation + +WAN federation and cluster peering are different ways to connect services through mesh gateways so that they can communicate across datacenters. WAN federation connects multiple datacenters to make them function as if they were a single cluster, while cluster peering treats each datacenter as a separate cluster. As a result, WAN federation requires a primary datacenter to maintain and replicate global states such as ACLs and configuration entries, but cluster peering does not. + +WAN federation and cluster peering also treat encrypted traffic differently. While mesh gateways between WAN federated datacenters use mTLS to keep data encrypted, mesh gateways between peers terminate mTLS sessions, decrypt data to HTTP services, and then re-encrypt traffic to send to services. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. Regardless of whether you connect your clusters through WAN federation or cluster peering, human and machine users can use either method to discover services in other clusters or dial them through the service mesh. @@ -42,11 +47,40 @@ Regardless of whether you connect your clusters through WAN federation or cluste | Shares key/value stores | ✅ | ❌ | | Can replicate ACL tokens, policies, and roles | ✅ | ❌ | -## Important Cluster Peering Constraints +## Guidance -Consider the following technical constraints: +The following resources are available to help you use Consul's cluster peering features. +**Tutorials:** + +- To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) and Google Kubernetes Engine (GKE) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](/consul/tutorials/developer-mesh/cluster-peering). + +**Usage documentation:** + +- [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/establish-peering) +- [Manage cluster peering connections](/consul/docs/connect/cluster-peering/usage/manage-connections) +- [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management) + +**Kubernetes-specific documentation:** + +- [Cluster peering on Kubernetes technical specifications](/consul/docs/k8s/connect/cluster-peering/tech-specs) +- [Establish cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering) +- [Manage cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/manage-peering) +- [Manage L7 traffic with cluster peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/l7-traffic) + +**Reference documentation:** + +- [Cluster peering technical specifications](/consul/docs/connect/cluster-peering/tech-specs) +- [HTTP API reference: `/peering/` endpoint](/consul/api-docs/peering) +- [CLI reference: `peering` command](/consul/commands/peering). + +## Basic troubleshooting + +If you experience errors when using Consul's cluster peering features, refer to the following list of technical constraints. + +- Peer names can only contain lowercase characters. - Services with node, instance, and check definitions totaling more than 8MB cannot be exported to a peer. -- Two admin partitions in the same datacenter cannot be peered. Use [`exported-services`](/docs/connect/config-entries/exported-services#exporting-services-to-peered-clusters) directly. -- The `consul intention` CLI command is not supported. To manage intentions that specify services in peered clusters, use [configuration entries](/docs/connect/config-entries/service-intentions). +- Two admin partitions in the same datacenter cannot be peered. Use the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services#exporting-services-to-peered-clusters) instead. +- To manage intentions that specify services in peered clusters, use [configuration entries](/consul/docs/connect/config-entries/service-intentions). The `consul intention` CLI command is not supported. +- The Consul UI does not support exporting services between clusters or creating service intentions. Use either the API or the CLI to complete these required steps when establishing new cluster peering connections. - Accessing key/value stores across peers is not supported. diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx deleted file mode 100644 index 3a16b6dfc7..0000000000 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ /dev/null @@ -1,593 +0,0 @@ ---- -layout: docs -page_title: Cluster Peering on Kubernetes -description: >- - If you use Consul on Kubernetes, learn how to enable cluster peering, create peering CRDs, and then manage peering connections in consul-k8s. ---- - -# Cluster Peering on Kubernetes - -To establish a cluster peering connection on Kubernetes, you need to enable several pre-requisite values in the Helm chart and create custom resource definitions (CRDs) for each side of the peering. - -The following Helm values are mandatory for cluster peering: -- [`global.tls.enabled = true`](/docs/k8s/helm#v-global-tls-enabled) -- [`meshGateway.enabled = true`](/docs/k8s/helm#v-meshgateway-enabled) - -The following CRDs are used to create and manage a peering connection: - -- `PeeringAcceptor`: Generates a peering token and accepts an incoming peering connection. -- `PeeringDialer`: Uses a peering token to make an outbound peering connection with the cluster that generated the token. - -Peering connections, including both data-plane and control-plane traffic, will be routed over mesh gateways. -As of Consul v1.14, you can also [implement service failovers and redirects to control traffic](/consul/docs/connect/l7-traffic) between peers. - -> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](https://learn.hashicorp.com/tutorials/consul/cluster-peering-aws?utm_source=docs). - -## Prerequisites - -You must implement the following requirements to create and use cluster peering connections with Kubernetes: - -- Consul v1.14.0 or later -- At least two Kubernetes clusters -- The installation must be running on Consul on Kubernetes version 1.0.0 or later - -### Prepare for installation - -Complete the following procedure after you have provisioned a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters. - -1. Use the `kubectl` command to export the Kubernetes context names and then set them to variables for future use. For more information on how to use kubeconfig and contexts, refer to the [Kubernetes docs on configuring access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). - - You can use the following methods to get the context names for your clusters: - - - Use the `kubectl config current-context` command to get the context for the cluster you are currently in. - - Use the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. - - ```shell-session - $ export CLUSTER1_CONTEXT= - $ export CLUSTER2_CONTEXT= - ``` - -1. To establish cluster peering through Kubernetes, create a `values.yaml` file with the following Helm values. **NOTE:** Mesh Gateway replicas are defaulted to 1 replica, and could be adjusted using the `meshGateway.replicas` value for higher availability. - - - - ```yaml - global: - name: consul - image: "hashicorp/consul:1.14.1" - peering: - enabled: true - tls: - enabled: true - meshGateway: - enabled: true - ``` - - - -### Install Consul on Kubernetes - -Install Consul on Kubernetes by using the CLI to apply `values.yaml` to each cluster. - - 1. In `cluster-01`, run the following commands: - - ```shell-session - $ export HELM_RELEASE_NAME=cluster-01 - ``` - - ```shell-session - $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc1 --kube-context $CLUSTER1_CONTEXT - ``` - - 1. In `cluster-02`, run the following commands: - - ```shell-session - $ export HELM_RELEASE_NAME=cluster-02 - ``` - - ```shell-session - $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc2 --kube-context $CLUSTER2_CONTEXT - ``` - -## Create a peering connection for Consul on Kubernetes - -To peer Kubernetes clusters running Consul, you need to create a peering token on one cluster (`cluster-01`) and share -it with the other cluster (`cluster-02`). The generated peering token from `cluster-01` will include the addresses of -the servers for that cluster. The servers for `cluster-02` will use that information to dial the servers in -`cluster-01`. Complete the following steps to create the peer connection. - -### Using mesh gateways for the peering connection -If the servers in `cluster-01` are not directly routable from the dialing cluster `cluster-02`, then you'll need to set up peering through mesh gateways. - -1. In `cluster-01` apply the `Mesh` custom resource so the generated token will have the mesh gateway addresses which will be routable from the other cluster. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: Mesh - metadata: - name: mesh - spec: - peering: - peerThroughMeshGateways: true - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply -f mesh.yaml - ``` - -1. In `cluster-02` apply the `Mesh` custom resource so that the servers for `cluster-02` will use their local mesh gateway to dial the servers for `cluster-01`. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: Mesh - metadata: - name: mesh - spec: - peering: - peerThroughMeshGateways: true - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply -f mesh.yaml - ``` - -### Create a peering token - -Peers identify each other using the `metadata.name` values you establish when creating the `PeeringAcceptor` and `PeeringDialer` CRDs. - -1. In `cluster-01`, create the `PeeringAcceptor` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringAcceptor - metadata: - name: cluster-02 ## The name of the peer you want to connect to - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. Apply the `PeeringAcceptor` resource to the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply --filename acceptor.yaml - ```` - -1. Save your peering token so that you can export it to the other cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT get secret peering-token --output yaml > peering-token.yaml - ``` - -### Establish a peering connection between clusters - -1. Apply the peering token to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename peering-token.yaml - ``` - -1. In `cluster-02`, create the `PeeringDialer` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringDialer - metadata: - name: cluster-01 ## The name of the peer you want to connect to - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. Apply the `PeeringDialer` resource to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename dialer.yaml - ``` - -### Configure the mesh gateway mode for traffic between services -Mesh gateways are required for service-to-service traffic between peered clusters. By default, this will mean that a -service dialing another service in a remote peer will dial the remote mesh gateway to reach that service. If you would -like to configure the mesh gateway mode such that this traffic always leaves through the local mesh gateway, you can use the `ProxyDefaults` CRD. - -1. In `cluster-01` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ProxyDefaults - metadata: - name: global - spec: - meshGateway: - mode: local - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply -f proxy-defaults.yaml - ``` - -1. In `cluster-02` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ProxyDefaults - metadata: - name: global - spec: - meshGateway: - mode: local - ``` - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply -f proxy-defaults.yaml - ``` - -### Export services between clusters - -The examples described in this section demonstrate how to export a service named `backend`. You should change instances of `backend` in the example code to the name of the service you want to export. - -1. For the service in `cluster-02` that you want to export, add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods prior to deploying. The annotation allows the workload to join the mesh. It is highlighted in the following example: - - - - ```yaml - # Service to expose backend - apiVersion: v1 - kind: Service - metadata: - name: backend - spec: - selector: - app: backend - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 9090 - --- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: backend - --- - # Deployment for backend - apiVersion: apps/v1 - kind: Deployment - metadata: - name: backend - labels: - app: backend - spec: - replicas: 1 - selector: - matchLabels: - app: backend - template: - metadata: - labels: - app: backend - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - serviceAccountName: backend - containers: - - name: backend - image: nicholasjackson/fake-service:v0.22.4 - ports: - - containerPort: 9090 - env: - - name: "LISTEN_ADDR" - value: "0.0.0.0:9090" - - name: "NAME" - value: "backend" - - name: "MESSAGE" - value: "Response from backend" - ``` - - - -1. Deploy the `backend` service to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename backend.yaml - ``` - -1. In `cluster-02`, create an `ExportedServices` custom resource. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ExportedServices - metadata: - name: default ## The name of the partition containing the service - spec: - services: - - name: backend ## The name of the service you want to export - consumers: - - peer: cluster-01 ## The name of the peer that receives the service - ``` - - - -1. Apply the `ExportedServices` resource to the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename exported-service.yaml - ``` - -### Authorize services for peers - -1. Create service intentions for the second cluster. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: ServiceIntentions - metadata: - name: backend-deny - spec: - destination: - name: backend - sources: - - name: "*" - action: deny - - name: frontend - action: allow - peer: cluster-01 ## The peer of the source service - ``` - - - -1. Apply the intentions to the second cluster. - - - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT apply --filename intention.yaml - ``` - - - -1. Add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods before deploying the workload so that the services in `cluster-01` can dial `backend` in `cluster-02`. To dial the upstream service from an application, configure the application so that that requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/docs/discovery/dns#service-virtual-ip-lookups). In the following example, the annotation that allows the workload to join the mesh and the configuration provided to the workload that enables the workload to dial the upstream service using the correct DNS name is highlighted. [Service Virtual IP Lookups for Consul Enterprise](/docs/discovery/dns#service-virtual-ip-lookups-for-consul-enterprise) details how you would similarly format a DNS name including partitions and namespaces. - - - - ```yaml - # Service to expose frontend - apiVersion: v1 - kind: Service - metadata: - name: frontend - spec: - selector: - app: frontend - ports: - - name: http - protocol: TCP - port: 9090 - targetPort: 9090 - --- - apiVersion: v1 - kind: ServiceAccount - metadata: - name: frontend - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - name: frontend - labels: - app: frontend - spec: - replicas: 1 - selector: - matchLabels: - app: frontend - template: - metadata: - labels: - app: frontend - annotations: - "consul.hashicorp.com/connect-inject": "true" - spec: - serviceAccountName: frontend - containers: - - name: frontend - image: nicholasjackson/fake-service:v0.22.4 - securityContext: - capabilities: - add: ["NET_ADMIN"] - ports: - - containerPort: 9090 - env: - - name: "LISTEN_ADDR" - value: "0.0.0.0:9090" - - name: "UPSTREAM_URIS" - value: "http://backend.virtual.cluster-02.consul" - - name: "NAME" - value: "frontend" - - name: "MESSAGE" - value: "Hello World" - - name: "HTTP_CLIENT_KEEP_ALIVES" - value: "false" - ``` - - - -1. Apply the service file to the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT apply --filename frontend.yaml - ``` - -1. Run the following command in `frontend` and then check the output to confirm that you peered your clusters successfully. - - - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT exec -it $(kubectl --context $CLUSTER1_CONTEXT get pod -l app=frontend -o name) -- curl localhost:9090 - - { - "name": "frontend", - "uri": "/", - "type": "HTTP", - "ip_addresses": [ - "10.16.2.11" - ], - "start_time": "2022-08-26T23:40:01.167199", - "end_time": "2022-08-26T23:40:01.226951", - "duration": "59.752279ms", - "body": "Hello World", - "upstream_calls": { - "http://backend.virtual.cluster-02.consul": { - "name": "backend", - "uri": "http://backend.virtual.cluster-02.consul", - "type": "HTTP", - "ip_addresses": [ - "10.32.2.10" - ], - "start_time": "2022-08-26T23:40:01.223503", - "end_time": "2022-08-26T23:40:01.224653", - "duration": "1.149666ms", - "headers": { - "Content-Length": "266", - "Content-Type": "text/plain; charset=utf-8", - "Date": "Fri, 26 Aug 2022 23:40:01 GMT" - }, - "body": "Response from backend", - "code": 200 - } - }, - "code": 200 - } - ``` - - - -## End a peering connection - -To end a peering connection, delete both the `PeeringAcceptor` and `PeeringDialer` resources. - -1. Delete the `PeeringDialer` resource from the second cluster. - - ```shell-session - $ kubectl --context $CLUSTER2_CONTEXT delete --filename dialer.yaml - ``` - -1. Delete the `PeeringAcceptor` resource from the first cluster. - - ```shell-session - $ kubectl --context $CLUSTER1_CONTEXT delete --filename acceptor.yaml - ```` - -1. Confirm that you deleted your peering connection in `cluster-01` by querying the the `/health` HTTP endpoint. The peered services should no longer appear. - - 1. Exec into the server pod for the first cluster. - - ```shell-session - $ kubectl exec -it consul-server-0 --context $CLUSTER1_CONTEXT -- /bin/sh - ``` - - 1. If you've enabled ACLs, export an ACL token to access the `/health` HTP endpoint for services. The bootstrap token may be used if an ACL token is not already provisioned. - - ```shell-session - $ export CONSUL_HTTP_TOKEN= - ``` - - 1. Query the the `/health` HTTP endpoint. The peered services should no longer appear. - - ```shell-session - $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" - ``` - -## Recreate or reset a peering connection - -To recreate or reset the peering connection, you need to generate a new peering token from the cluster where you created the `PeeringAcceptor`. - -1. In the `PeeringAcceptor` CRD, add the annotation `consul.hashicorp.com/peering-version`. If the annotation already exists, update its value to a higher version. - - - - ```yaml - apiVersion: consul.hashicorp.com/v1alpha1 - kind: PeeringAcceptor - metadata: - name: cluster-02 - annotations: - consul.hashicorp.com/peering-version: "1" ## The peering version you want to set, must be in quotes - spec: - peer: - secret: - name: "peering-token" - key: "data" - backend: "kubernetes" - ``` - - - -1. After updating `PeeringAcceptor`, repeat the following steps to create a peering connection: - 1. [Create a peering token](#create-a-peering-token) - 1. [Establish a peering connection between clusters](#establish-a-peering-connection-between-clusters) - 1. [Export services between clusters](#export-services-between-clusters) - 1. [Authorize services for peers](#authorize-services-for-peers) - - Your peering connection is re-established with the updated token. - -~> **Note:** The only way to create or set a new peering token is to manually adjust the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token causes the previous token to expire. - -## Traffic management between peers - -As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. - -To configure automatic service failovers and redirect, edit the `ServiceResolver` CRD so that traffic resolves to a backup service instance on a peer. The following example updates the `ServiceResolver` CRD in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in `cluster-02` when it detects multiple connection failures to the primary instance. - - - -```yaml -apiVersion: consul.hashicorp.com/v1alpha1 -kind: ServiceResolver -metadata: - name: frontend -spec: - connectTimeout: 15s - failover: - '*': - targets: - - peer: 'cluster-02' - service: 'backup' - namespace: 'default' -``` - - diff --git a/website/content/docs/connect/cluster-peering/tech-specs.mdx b/website/content/docs/connect/cluster-peering/tech-specs.mdx new file mode 100644 index 0000000000..3e00d6a48b --- /dev/null +++ b/website/content/docs/connect/cluster-peering/tech-specs.mdx @@ -0,0 +1,69 @@ +--- +layout: docs +page_title: Cluster Peering Technical Specifications +description: >- + Cluster peering connections in Consul interact with mesh gateways, sidecar proxies, exported services, and ACLs. Learn about the configuration requirements for these components. +--- + +# Cluster peering technical specifications + +This reference topic describes the technical specifications associated with using cluster peering in your deployments. These specifications include required Consul components and their configurations. + +## Requirements + +To use cluster peering features, make sure your Consul environment meets the following prerequisites: + +- Consul v1.14 or higher. +- A local Consul agent is required to manage mesh gateway configuration. +- Use [Envoy proxies](/consul/docs/connect/proxies/envoy). Envoy is the only proxy with mesh gateway capabilities in Consul. + +In addition, the following service mesh components are required in order to establish cluster peering connections: + +- [Mesh gateways](#mesh-gateway-requirements) +- [Sidecar proxies](#sidecar-proxy-requirements) +- [Exported services](#exported-service-requirements) +- [ACLs](#acl-requirements) + +### Mesh gateway requirements + +Mesh gateways are required for routing service mesh traffic between partitions with cluster peering connections. Consider the following general requirements for mesh gateways when using cluster peering: + +- A cluster requires a registered mesh gateway in order to export services to peers. +- For Enterprise, this mesh gateway must also be registered in the same partition as the exported services and their `exported-services` configuration entry. +- To use the `local` mesh gateway mode, you must register a mesh gateway in the importing cluster. + +In addition, you must define the `Proxy.Config` settings using opaque parameters compatible with your proxy. Refer to the [Gateway options](/consul/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch Overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) documentation for additional Envoy proxy configuration information. + +#### Mesh gateway modes + +By default, all cluster peering connections use mesh gateways in [remote mode](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). Be aware of these additional requirements when changing a mesh gateway's mode. + +- For mesh gateways that connect peered clusters, you can set the `mode` as either `remote` or `local`. +- The `none` mode is invalid for mesh gateways with cluster peering connections. + +Refer to [mesh gateway modes](/consul/docs/connect/gateways/mesh-gateway#modes) for more information. + +## Sidecar proxy requirements + +The Envoy proxies that function as sidecars in your service mesh require configuration in order to properly route traffic to peers. Sidecar proxies are defined in the [service definition](/consul/docs/discovery/services). + +- Configure the `proxy.upstreams` parameters to route traffic to the correct service, namespace, and peer. Refer to the [`upstreams`](/consul/docs/connect/registration/service-registration#upstream-configuration-reference) documentation for details. +- The `proxy.upstreams.destination_name` parameter is always required. +- The `proxy.upstreams.destination_peer` parameter must be configured to enable cross-cluster traffic. +- The `proxy.upstream/destination_namespace` configuration is only necessary if the destination service is in a non-default namespace. + +## Exported service requirements + +The `exported-services` configuration entry is required in order for services to communicate across partitions with cluster peering connections. + +Basic guidance on using the `exported-services` configuration entry is included in [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/create-cluster-peering). + +Refer to the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) reference for more information. + +## ACL requirements + +If ACLs are enabled, you must add tokens to grant the following permissions: + +- Grant `service:write` permissions to services that define mesh gateways in their server definition. +- Grant `service:read` permissions for all services on the partition. +- Grant `mesh:write` permissions to the mesh gateways that participate in cluster peering connections. This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/usage/create-cluster-peering.mdx b/website/content/docs/connect/cluster-peering/usage/create-cluster-peering.mdx new file mode 100644 index 0000000000..56fbe48559 --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/create-cluster-peering.mdx @@ -0,0 +1,207 @@ +--- +layout: docs +page_title: Cluster Peering - Create and Manage Connections +description: >- + Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to create, list, read, check, and delete peering connections. +--- + +# Establish Cluster Peering Connections + +A peering token enables cluster peering between different datacenters. Once you generate a peering token, you can use it to establish a connection between clusters. Then you can export services and create intentions so that peered clusters can call those services. + +The process to create a peering connection is a sequence with multiple steps: + +1. Create a peering token +1. Establish a connection between clusters +1. Export services between clusters +1. Authorize services for peers + +You can generate peering tokens and initiate connections on any available agent using either the API, CLI, or the Consul UI. If you use the API or CLI, we recommend performing these operations through a client agent in the partition you want to connect. + +The UI does not currently support exporting services between clusters or authorizing services for peers. + +## Create a peering token + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use establishment secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + + + + +In `cluster-01`, use the [`/peering/token` endpoint](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. + +```shell-session +$ curl --request POST --data '{"Peer":"cluster-02"}' --url http://localhost:8500/v1/peering/token +``` + +The CLI outputs the peering token, which is a base64-encoded string containing the token details. + +Create a JSON file that contains the first cluster's name and the peering token. + + + +```json +{ + "Peer": "cluster-01", + "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" +} +``` + + + + + + +In `cluster-01`, use the [`consul peering generate-token` command](/commands/peering/generate-token) to issue a request for a peering token. + +```shell-session +$ consul peering generate-token -name cluster-02 +``` + +The CLI outputs the peering token, which is a base64-encoded string containing the token details. +Save this value to a file or clipboard to be used in the next step on `cluster-02`. + + + + + +1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. +1. Click **Add peer connection**. +1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. +1. Click the **Generate token** button. +1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. + + + + +### Establish a connection between clusters + +Next, use the peering token to establish a secure connection between the clusters. + + + + +In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. + +```shell-session +$ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish +``` + +When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/api-docs/peering). + +You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. + + + + + +In one of the client agents in "cluster-02," issue the [`consul peering establish` command](/commands/peering/establish) and specify the token generated in the previous step. The command establishes the peering connection. +The commands prints "Successfully established peering connection with cluster-01" after the connection is established. + +```shell-session +$ consul peering establish -name cluster-01 -peering-token token-from-generate +``` + +When you connect server agents through cluster peering, they peer their default partitions. +To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer. +For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) . + +You can run the `peering establish` command once per peering token. +Peering tokens cannot be reused after being used to establish a connection. +If you need to re-establish a connection, you must generate a new peering token. + + + + + +1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. +1. Click **Establish peering**. +1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +1. Click **Add peer**. + + + + +### Export services between clusters + +After you establish a connection between the clusters, you need to create a configuration entry that defines the services that are available for other clusters. Consul uses this configuration entry to advertise service information and support service mesh connections across clusters. + +First, create a configuration entry and specify the `Kind` as `"exported-services"`. + + + +```hcl +Kind = "exported-services" +Name = "default" +Services = [ + { + ## The name and namespace of the service to export. + Name = "service-name" + Namespace = "default" + + ## The list of peer clusters to export the service to. + Consumers = [ + { + ## The peer name to reference in config is the one set + ## during the peering process. + Peer = "cluster-02" + } + ] + } +] +``` + + + +Then, add the configuration entry to your cluster. + +```shell-session +$ consul config write peering-config.hcl +``` + +Before you proceed, wait for the clusters to sync and make services available to their peers. You can issue an endpoint query to [check the peered cluster status](#check-peered-cluster-status). + +### Authorize services for peers + +Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. + +First, create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." The following example sets service intentions so that "frontend-service" can access "backend-service." + + + +```hcl +Kind = "service-intentions" +Name = "backend-service" + +Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } +] +``` + + + +If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. + +Then, add the configuration entry to your cluster. + +```shell-session +$ consul config write peering-intentions.hcl +``` + +### Authorize Service Reads with ACLs + +If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. +Read access to all imported services is granted using either of the following rules associated with an ACL token: +- `service:write` permissions for any service in the sidecar's partition. +- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. +For Consul Enterprise, access is granted to all imported services in the service's partition. +These permissions are satisfied when using a [service identity](/docs/security/acl/acl-roles#service-identities). + +Example rule files can be found in [Reading Servers](/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` config entry documentation. + +Refer to [ACLs System Overview](/docs/security/acl) for more information on ACLs and their setup. \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx b/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx new file mode 100644 index 0000000000..29259ccf2e --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx @@ -0,0 +1,271 @@ +--- +layout: docs +page_title: Establish Cluster Peering Connections +description: >- + Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to establish peering connections with Consul's HTTP API, CLI or UI. +--- + +# Establish cluster peering connections + +This page details the process for establishing a cluster peering connection between services deployed to different datacenters. You can interact with Consul's cluster peering features using the CLI, the HTTP API, or the UI. The overall process for establishing a cluster peering connection consists of the following steps: + +1. Create a peering token in one cluster. +1. Use the peering token to establish peering with a second cluster. +1. Export services between clusters. +1. Create intentions to authorize services for peers. + +Cluster peering between services cannot be established until all four steps are complete. + +For Kubernetes-specific guidance for establishing cluster peering connections, refer to [Establish cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). + +## Requirements + +You must meet the following requirements to use cluster peering: + +- Consul v1.14.1 or higher +- Services hosted in admin partitions on separate datacenters + +If you need to make services available to an admin partition in the same datacenter, do not use cluster peering. Instead, use the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) to make service upstreams available to other admin partitions in a single datacenter. + +### Mesh gateway requirements + +Mesh gateways are required for all cluster peering connections. Consider the following architectural requirements when creating mesh gateways: + +- A registered mesh gateway is required in order to export services to peers. +- For Enterprise, the mesh gateway that exports services must be registered in the same partition as the exported services and their `exported-services` configuration entry. +- To use the `local` mesh gateway mode, you must register a mesh gateway in the importing cluster. + +## Create a peering token + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + + + + +1. In `cluster-01`, use the [`consul peering generate-token` command](/consul/commands/peering/generate-token) to issue a request for a peering token. + + ```shell-session + $ consul peering generate-token -name cluster-02 + ``` + + The CLI outputs the peering token, which is a base64-encoded string containing the token details. + +1. Save this value to a file or clipboard to use in the next step on `cluster-02`. + + + + +1. In `cluster-01`, use the [`/peering/token` endpoint](/consul/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. + + ```shell-session + $ curl --request POST --data '{"Peer":"cluster-02"}' --url http://localhost:8500/v1/peering/token + ``` + + The CLI outputs the peering token, which is a base64-encoded string containing the token details. + +1. Create a JSON file that contains the first cluster's name and the peering token. + + + + ```json + { + "Peer": "cluster-01", + "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" + } + ``` + + + + + + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + +1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. +1. Click **Add peer connection**. +1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. +1. Click the **Generate token** button. +1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. + + + + +## Establish a connection between clusters + +Next, use the peering token to establish a secure connection between the clusters. + + + + +1. In one of the client agents deployed to "cluster-02," issue the [`consul peering establish` command](/consul/commands/peering/establish) and specify the token generated in the previous step. + + ```shell-session + $ consul peering establish -name cluster-01 -peering-token token-from-generate + "Successfully established peering connection with cluster-01" + ``` + +When you connect server agents through cluster peering, they peer their default partitions. To establish peering connections for other partitions through server agents, you must add the `-partition` flag to the `establish` command and specify the partitions you want to peer. For additional configuration information, refer to [`consul peering establish` command](/consul/commands/peering/establish). + +You can run the `peering establish` command once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. + + + + +1. In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/consul/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. + + ```shell-session + $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish + ``` + +When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/consul/api-docs/peering). + +You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. + + + + + +1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. +1. Click **Establish peering**. +1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +1. Click **Add peer**. + + + + +## Export services between clusters + +After you establish a connection between the clusters, you need to create an `exported-services` configuration entry that defines the services that are available for other clusters. Consul uses this configuration entry to advertise service information and support service mesh connections across clusters. + +An `exported-services` configuration entry makes services available to another admin partition. While it can target admin partitions either locally or remotely. Clusters peers always export services to remote partitions. Refer to [exported service consumers](/consul/docs/connect/config-entries/exported-services#consumers-1) for more information. + +You must use the Consul CLI to complete this step. The HTTP API and the Consul UI do not support `exported-services` configuration entries. + + + + +1. Create a configuration entry and specify the `Kind` as `"exported-services"`. + + + + ```hcl + Kind = "exported-services" + Name = "default" + Services = [ + { + ## The name and namespace of the service to export. + Name = "service-name" + Namespace = "default" + + ## The list of peer clusters to export the service to. + Consumers = [ + { + ## The peer name to reference in config is the one set + ## during the peering process. + Peer = "cluster-02" + } + ] + } + ] + ``` + + + +1. Add the configuration entry to your cluster. + + ```shell-session + $ consul config write peering-config.hcl + ``` + +Before you proceed, wait for the clusters to sync and make services available to their peers. To check the peered cluster status, [read the cluster peering connection](/consul/docs/connect/cluster-peering/usage/manage-connections#read-a-peering-connection). + + + + +## Authorize services for peers + +Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. + +You must use the HTTP API or the Consul CLI to complete this step. The Consul UI supports intentions for local clusters only. + + + + +1. Create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: + + + + ```hcl + Kind = "service-intentions" + Name = "backend-service" + + Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` + + + + If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. + +1. Add the configuration entry to your cluster. + + ```shell-session + $ consul config write peering-intentions.hcl + ``` + + + + +1. Create a configuration entry and specify the `Kind` as `"service-intentions"`. Declare the service on "cluster-02" that can access the service in "cluster-01." In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: + + + + ```hcl + Kind = "service-intentions" + Name = "backend-service" + + Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` + + + + If the peer's name is not specified in `Peer`, then Consul assumes that the service is in the local cluster. + +1. Add the configuration entry to your cluster. + + ```shell-session + $ curl --request PUT --data @peering-intentions.hcl http://127.0.0.1:8500/v1/config + ``` + + + + +### Authorize service reads with ACLs + +If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. + +Read access to all imported services is granted using either of the following rules associated with an ACL token: + +- `service:write` permissions for any service in the sidecar's partition. +- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. + +For Consul Enterprise, the permissions apply to all imported services in the service's partition. These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). + +Refer to [Reading servers](/consul/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` configuration entry documentation for example rules. + +For additional information about how to configure and use ACLs, refer to [ACLs system overview](/consul/docs/security/acl). \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx b/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx new file mode 100644 index 0000000000..c3072e3efe --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/manage-connections.mdx @@ -0,0 +1,137 @@ +--- +layout: docs +page_title: Manage Cluster Peering Connections +description: >- + Learn how to list, read, and delete cluster peering connections using Consul. You can use the HTTP API, the CLI, or the Consul UI to manage cluster peering connections. +--- + +# Manage cluster peering connections + +This usage topic describes how to manage cluster peering connections using the CLI, the HTTP API, and the UI. + +After you establish a cluster peering connection, you can get a list of all active peering connections, read a specific peering connection's information, and delete peering connections. + +For Kubernetes-specific guidance for managing cluster peering connections, refer to [Manage cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/manage-peering). + +## List all peering connections + +You can list all active peering connections in a cluster. + + + + + ```shell-session + $ consul peering list + Name State Imported Svcs Exported Svcs Meta + cluster-02 ACTIVE 0 2 env=production + cluster-03 PENDING 0 0 + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering list` CLI command reference](/consul/commands/peering/list). + + + + +The following example shows how to format an API request to list peering connections: + + ```shell-session + $ curl --header "X-Consul-Token: 0137db51-5895-4c25-b6cd-d9ed992f4a52" http://127.0.0.1:8500/v1/peerings + ``` + +For more information, including optional parameters and sample responses, refer to the [`/peering` endpoint reference](/consul/api-docs/peering#list-all-peerings). + + + + +In the Consul UI, click **Peers**. + +The UI lists peering connections you created for clusters in a datacenter. The name that appears in the list is the name of the cluster in a different datacenter with an established peering connection. + + + + +## Read a peering connection + +You can get information about individual peering connections between clusters. + + + + + +The following example outputs information about a peering connection locally referred to as "cluster-02": + + ```shell-session + $ consul peering read -name cluster-02 + Name: cluster-02 + ID: 3b001063-8079-b1a6-764c-738af5a39a97 + State: ACTIVE + Meta: + env=production + + Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 + Peer Server Name: server.dc1.consul + Peer CA Pems: 0 + Peer Server Addresses: + 10.0.0.1:8300 + + Imported Services: 0 + Exported Services: 2 + + Create Index: 89 + Modify Index: 89 + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering read` CLI command reference](/consul/commands/peering/read). + + + + + ```shell-session + $ curl --header "X-Consul-Token: b23b3cad-5ea1-4413-919e-c76884b9ad60" http://127.0.0.1:8500/v1/peering/cluster-02 + ``` + +For more information, including optional parameters and sample responses, refer to the [`/peering` endpoint reference](/consul/api-docs/peering#read-a-peering-connection). + + + + +1. In the Consul UI, click **Peers**. + +1. Click the name of a peered cluster to view additional details about the peering connection. + + + + +## Delete peering connections + +You can disconnect the peered clusters by deleting their connection. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. + + + + + The following examples deletes a peering connection to a cluster locally referred to as "cluster-02": + + ```shell-session + $ consul peering delete -name cluster-02 + Successfully submitted peering connection, cluster-02, for deletion + ``` + +For more information, including optional flags and parameters, refer to the [`consul peering delete` CLI command reference](/consul/commands/peering/delete). + + + + + ```shell-session + $ curl --request DELETE --header "X-Consul-Token: b23b3cad-5ea1-4413-919e-c76884b9ad60" http://127.0.0.1:8500/v1/peering/cluster-02 + ``` + +This endpoint does not return a response. For more information, including optional parameters, refer to the [`/peering` endpoint reference](/consul/api-docs/peering/consul/api-docs/peering#delete-a-peering-connection). + + + +1. In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. +1. Next to the name of the peer, click **More** (three horizontal dots) and then **Delete**. +1. Click **Delete** to confirm and remove the peering connection. + + + diff --git a/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx b/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx new file mode 100644 index 0000000000..64183c9810 --- /dev/null +++ b/website/content/docs/connect/cluster-peering/usage/peering-traffic-management.mdx @@ -0,0 +1,168 @@ +--- +layout: docs +page_title: Cluster Peering L7 Traffic Management +description: >- + Combine service resolver configurations with splitter and router configurations to manage L7 traffic in Consul deployments with cluster peering connections. Learn how to define dynamic traffic rules to target peers for redirects and failover. +--- + +# Manage L7 traffic with cluster peering + +This usage topic describes how to configure and apply the [`service-resolver` configuration entry](/consul/docs/connect/config-entries/service-resolver) to set up redirects and failovers between services that have an existing cluster peering connection. + +For Kubernetes-specific guidance for managing L7 traffic with cluster peering, refer to [Manage L7 traffic with cluster peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/l7-traffic). + +## Service resolvers for redirects and failover + +When you use cluster peering to connect datacenters through their admin partitions, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically forward traffic to services hosted on peer clusters. + +However, the `service-splitter` and `service-router` configuration entry kinds do not natively support directly targeting a service instance hosted on a peer. Before you can split or route traffic to a service on a peer, you must define the service hosted on the peer as an upstream service by configuring a failover in the `service-resolver` configuration entry. Then, you can set up a redirect in a second service resolver to interact with the peer service by name. + +For more information about formatting, updating, and managing configuration entries in Consul, refer to [How to use configuration entries](/consul/docs/agent/config-entries). + +## Configure dynamic traffic between peers + +To configure L7 traffic management behavior in deployments with cluster peering connections, complete the following steps in order: + +1. Define the peer cluster as a failover target in the service resolver configuration. + + The following examples update the [`service-resolver` configuration entry](/consul/docs/connect/config-entries/service-resolver) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. + + + + ```hcl + Kind = "service-resolver" + Name = "frontend" + ConnectTimeout = "15s" + Failover = { + "*" = { + Targets = [ + {Peer = "cluster-02"} + ] + } + } + ``` + + ```json + { + "ConnectTimeout": "15s", + "Kind": "service-resolver", + "Name": "frontend", + "Failover": { + "*": { + "Targets": [ + { + "Peer": "cluster-02" + } + ] + } + }, + "CreateIndex": 250, + "ModifyIndex": 250 + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend + spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'frontend' + namespace: 'default' + ``` + + + +1. Define the desired behavior in `service-splitter` or `service-router` configuration entries. + + The following example splits traffic evenly between `frontend` services hosted on peers by defining the desired behavior locally: + + + + ```hcl + Kind = "service-splitter" + Name = "frontend" + Splits = [ + { + Weight = 50 + ## defaults to service with same name as configuration entry ("frontend") + }, + { + Weight = 50 + Service = "frontend-peer" + }, + ] + ``` + + ```json + { + "Kind": "service-splitter", + "Name": "frontend", + "Splits": [ + { + "Weight": 50 + }, + { + "Weight": 50, + "Service": "frontend-peer" + } + ] + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceSplitter + metadata: + name: frontend + spec: + splits: + - weight: 50 + ## defaults to service with same name as configuration entry ("frontend") + - weight: 50 + service: frontend-peer + ``` + + + +1. Create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: + + + + ```hcl + Kind = "service-resolver" + Name = "frontend-peer" + Redirect { + Service = frontend + Peer = "cluster-02" + } + ``` + + ```json + { + "Kind": "service-resolver", + "Name": "frontend-peer", + "Redirect": { + "Service": "frontend", + "Peer": "cluster-02" + } + } + ``` + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend-peer + spec: + redirect: + peer: 'cluster-02' + service: 'frontend' + ``` + + diff --git a/website/content/docs/connect/config-entries/service-intentions.mdx b/website/content/docs/connect/config-entries/service-intentions.mdx index 1087628939..656045b47b 100644 --- a/website/content/docs/connect/config-entries/service-intentions.mdx +++ b/website/content/docs/connect/config-entries/service-intentions.mdx @@ -335,6 +335,57 @@ spec: ``` +### Cluster peering + +When using cluster peering connections, intentions secure your deployments with authorized service-to-service communication between remote datacenters. In the following example, the service intentions configuration entry authorizes the `backend-service` to communicate with the `frontend-service` that is hosted on remote peer `cluster-02`: + + + + ```hcl + Kind = "service-intentions" + Name = "backend-service" + + Sources = [ + { + Name = "frontend-service" + Peer = "cluster-02" + Action = "allow" + } + ] + ``` + + ```yaml + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceIntentions + metadata: + name: backend-deny + spec: + destination: + name: backend + sources: + - name: "*" + action: deny + - name: frontend + action: allow + peer: cluster-01 ## The peer of the source service + ``` + + ```json + { + "Kind": "service-intentions", + "Name": "backend-service", + "Sources": [ + { + "Name": "frontend-service", + "Peer": "cluster-02", + "Action": "allow" + } + ] + } + ``` + + ## Available Fields - + In Kubernetes deployments, cluster peering connections interact with mesh gateways, sidecar proxies, exported services, and ACLs. Learn about requirements specific to k8s, including required Helm values and CRDs. +--- + +# Cluster peering on Kubernetes technical specifications + +This reference topic describes the technical specifications associated with using cluster peering in your Kubernetes deployments. These specifications include [required Helm values](#helm-requirements) and [required custom resource definitions (CRDs)](#crd-requirements), as well as required Consul components and their configurations. + +For cluster peering requirements in non-Kubernetes deployments, refer to [cluster peering technical specifications](/consul/docs/connect/cluster-peering/tech-specs). + +## General Requirements + +To use cluster peering features, make sure your Consul environment meets the following prerequisites: + +- Consul v1.14 or higher +- Consul on Kubernetes v1.0.0 or higher +- At least two Kubernetes clusters + +In addition, the following service mesh components are required in order to establish cluster peering connections: + +- [Helm](#helm-requirements) +- [Custom resource definitions (CRD)](#crd-requirements) +- [Mesh gateways](#mesh-gateway-requirements) +- [Exported services](#exported-service-requirements) +- [ACLs](#acl-requirements) + +## Helm requirements + + Mesh gateways are required when establishing cluster peering connections. The following values must be set in the Helm chart to enable mesh gateways: + +- [`global.tls.enabled = true`](/consul/docs/k8s/helm#v-global-tls-enabled) +- [`meshGateway.enabled = true`](/consul/docs/k8s/helm#v-meshgateway-enabled) + +Refer to the following example Helm configuration: + + + +```yaml +global: + name: consul + image: "hashicorp/consul:1.14.1" + peering: + enabled: true + tls: + enabled: true +meshGateway: + enabled: true +``` + + + +After mesh gateways are enabled in the Helm chart, you can separately [configure Mesh CRDs](#mesh-gateway-configuration-for-kubernetes). + +## CRD requirements + +You must create the following CRDs in order to establish a peering connection: + +- `PeeringAcceptor`: Generates a peering token and accepts an incoming peering connection. +- `PeeringDialer`: Uses a peering token to make an outbound peering connection with the cluster that generated the token. + +Refer to the following example CRDs: + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringAcceptor +metadata: + name: cluster-02 ## The name of the peer you want to connect to +spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" +``` + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: PeeringDialer +metadata: + name: cluster-01 ## The name of the peer you want to connect to +spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" +``` + + + + +## Mesh gateway requirements + +Mesh gateways are required for routing service mesh traffic between partitions with cluster peering connections. Consider the following general requirements for mesh gateways when using cluster peering: + +- A cluster requires a registered mesh gateway in order to export services to peers. +- For Enterprise, this mesh gateway must also be registered in the same partition as the exported services and their `exported-services` configuration entry. +- To use the `local` mesh gateway mode, you must register a mesh gateway in the importing cluster. + +In addition, you must define the `Proxy.Config` settings using opaque parameters compatible with your proxy. Refer to the [Gateway options](/consul/docs/connect/proxies/envoy#gateway-options) and [Escape-hatch Overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) documentation for additional Envoy proxy configuration information. + +### Mesh gateway modes + +By default, all cluster peering connections use mesh gateways in [remote mode](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters#remote). Be aware of these additional requirements when changing a mesh gateway's mode. + +- For mesh gateways that connect peered clusters, you can set the `mode` as either `remote` or `local`. +- The `none` mode is invalid for mesh gateways with cluster peering connections. + +Refer to [mesh gateway modes](/consul/docs/connect/gateways/mesh-gateway#modes) for more information. + +### Mesh gateway configuration for Kubernetes + +Mesh gateways are required for cluster peering connections. Complete the following steps to add mesh gateways to your deployment so that you can establish cluster peering connections: + +1. In `cluster-01` create the `Mesh` custom resource with `peeringThroughMeshGateways` set to `true`. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: Mesh + metadata: + name: mesh + spec: + peering: + peerThroughMeshGateways: true + ``` + + + +1. Apply the mesh gateway to `cluster-01`. Replace `CLUSTER1_CONTEXT` to target the first Consul cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply -f mesh.yaml + ``` + +1. Repeat the process to create and apply a mesh gateway with cluster peering enabled to `cluster-02`. Replace `CLUSTER2_CONTEXT` to target the second Consul cluster. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: Mesh + metadata: + name: mesh + spec: + peering: + peerThroughMeshGateways: true + ``` + + + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply -f mesh.yaml + ``` + +## Exported service requirements + +The `exported-services` CRD is required in order for services to communicate across partitions with cluster peering connections. + +Refer to the [`exported-services` configuration entry](/consul/docs/connect/config-entries/exported-services) reference for more information. + +## ACL requirements + +If ACLs are enabled, you must add tokens to grant the following permissions: + +- Grant `service:write` permissions to services that define mesh gateways in their server definition. +- Grant `service:read` permissions for all services on the partition. +- Grant `mesh:write` permissions to the mesh gateways that participate in cluster peering connections. This permission allows a leaf certificate to be issued for mesh gateways to terminate TLS sessions for HTTP requests. \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx new file mode 100644 index 0000000000..71e0d46638 --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/establish-peering.mdx @@ -0,0 +1,453 @@ +--- +layout: docs +page_title: Establish Cluster Peering Connections on Kubernetes +description: >- + To establish a cluster peering connection on Kubernetes, generate a peering token to establish communication. Then export services and authorize requests with service intentions. +--- + +# Establish cluster peering connections on Kubernetes + +This page details the process for establishing a cluster peering connection between services in a Consul on Kubernetes deployment. + +The overall process for establishing a cluster peering connection consists of the following steps: + +1. Create a peering token in one cluster. +1. Use the peering token to establish peering with a second cluster. +1. Export services between clusters. +1. Create intentions to authorize services for peers. + +Cluster peering between services cannot be established until all four steps are complete. + +For general guidance for establishing cluster peering connections, refer to [Establish cluster peering connections](/consul/docs/connect/cluster-peering/usage/establish-peering). + +## Prerequisites + +You must meet the following requirements to use Consul's cluster peering features with Kubernetes: + +- Consul v1.14.1 or higher +- Consul on Kubernetes v1.0.0 or higher +- At least two Kubernetes clusters + +In Consul on Kubernetes, peers identify each other using the `metadata.name` values you establish when creating the `PeeringAcceptor` and `PeeringDialer` CRDs. For additional information about requirements for cluster peering on Kubernetes deployments, refer to [Cluster peering on Kubernetes technical specifications](/consul/docs/k8s/connect/cluster-peering/tech-specs). + +### Assign cluster IDs to environmental variables + +After you provision a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters, you can assign your clusters to environmental variables for future use. + +1. Get the context names for your Kubernetes clusters using one of these methods: + + - Run the `kubectl config current-context` command to get the context for the cluster you are currently in. + - Run the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. + +1. Use the `kubectl` command to export the Kubernetes context names and then set them to variables. For more information on how to use kubeconfig and contexts, refer to the [Kubernetes docs on configuring access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). + + ```shell-session + $ export CLUSTER1_CONTEXT= + $ export CLUSTER2_CONTEXT= + ``` + +### Update the Helm chart + +To use cluster peering with Consul on Kubernetes deployments, update the Helm chart with [the required values](/consul/docs/k8s/connect/cluster-peering/tech-specs#helm-requirements). After updating the Helm chart, you can use the `consul-k8s` CLI to apply `values.yaml` to each cluster. + +1. In `cluster-01`, run the following commands: + + ```shell-session + $ export HELM_RELEASE_NAME1=cluster-01 + ``` + + ```shell-session + $ helm install ${HELM_RELEASE_NAME1} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc1 --kube-context $CLUSTER1_CONTEXT + ``` + +1. In `cluster-02`, run the following commands: + + ```shell-session + $ export HELM_RELEASE_NAME2=cluster-02 + ``` + + ```shell-session + $ helm install ${HELM_RELEASE_NAME2} hashicorp/consul --create-namespace --namespace consul --version "1.0.1" --values values.yaml --set global.datacenter=dc2 --kube-context $CLUSTER2_CONTEXT + ``` + +### Configure the mesh gateway mode for traffic between services + +In Kubernetes deployments, you can configure mesh gateways to use `local` mode so that a service dialing a service in a remote peer dials the remote mesh gateway instead of the local mesh gateway. To configure the mesh gateway mode so that this traffic always leaves through the local mesh gateway, you can use the `ProxyDefaults` CRD. + +1. In `cluster-01` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ProxyDefaults + metadata: + name: global + spec: + meshGateway: + mode: local + ``` + + + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply -f proxy-defaults.yaml + ``` + +1. In `cluster-02` apply the following `ProxyDefaults` CRD to configure the mesh gateway mode. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ProxyDefaults + metadata: + name: global + spec: + meshGateway: + mode: local + ``` + + + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply -f proxy-defaults.yaml + ``` + +## Create a peering token + +To begin the cluster peering process, generate a peering token in one of your clusters. The other cluster uses this token to establish the peering connection. + +Every time you generate a peering token, a single-use secret for establishing the secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. + +1. In `cluster-01`, create the `PeeringAcceptor` custom resource. To ensure cluster peering connections are secure, the `metadata.name` field cannot be duplicated. Refer to the peer by a specific name. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringAcceptor + metadata: + name: cluster-02 ## The name of the peer you want to connect to + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. Apply the `PeeringAcceptor` resource to the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply --filename acceptor.yaml + ``` + +1. Save your peering token so that you can export it to the other cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT get secret peering-token --output yaml > peering-token.yaml + ``` + +## Establish a connection between clusters + +Next, use the peering token to establish a secure connection between the clusters. + +1. Apply the peering token to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename peering-token.yaml + ``` + +1. In `cluster-02`, create the `PeeringDialer` custom resource. To ensure cluster peering connections are secure, the `metadata.name` field cannot be duplicated. Refer to the peer by a specific name. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringDialer + metadata: + name: cluster-01 ## The name of the peer you want to connect to + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. Apply the `PeeringDialer` resource to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename dialer.yaml + ``` + +## Export services between clusters + +After you establish a connection between the clusters, you need to create an `exported-services` CRD that defines the services that are available to another admin partition. + +While the CRD can target admin partitions either locally or remotely, clusters peering always exports services to remote admin partitions. Refer to [exported service consumers](/consul/docs/connect/config-entries/exported-services#consumers-1) for more information. + + +1. For the service in `cluster-02` that you want to export, add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods prior to deploying. The annotation allows the workload to join the mesh. It is highlighted in the following example: + + + + ```yaml + # Service to expose backend + apiVersion: v1 + kind: Service + metadata: + name: backend + spec: + selector: + app: backend + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 9090 + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: backend + --- + # Deployment for backend + apiVersion: apps/v1 + kind: Deployment + metadata: + name: backend + labels: + app: backend + spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + serviceAccountName: backend + containers: + - name: backend + image: nicholasjackson/fake-service:v0.22.4 + ports: + - containerPort: 9090 + env: + - name: "LISTEN_ADDR" + value: "0.0.0.0:9090" + - name: "NAME" + value: "backend" + - name: "MESSAGE" + value: "Response from backend" + ``` + + + +1. Deploy the `backend` service to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename backend.yaml + ``` + +1. In `cluster-02`, create an `ExportedServices` custom resource. The name of the peer that consumes the service should be identical to the name set in the `PeeringDialer` CRD. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ExportedServices + metadata: + name: default ## The name of the partition containing the service + spec: + services: + - name: backend ## The name of the service you want to export + consumers: + - peer: cluster-01 ## The name of the peer that receives the service + ``` + + + +1. Apply the `ExportedServices` resource to the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename exported-service.yaml + ``` + +## Authorize services for peers + +Before you can call services from peered clusters, you must set service intentions that authorize those clusters to use specific services. Consul prevents services from being exported to unauthorized clusters. + +1. Create service intentions for the second cluster. The name of the peer should match the name set in the `PeeringDialer` CRD. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceIntentions + metadata: + name: backend-deny + spec: + destination: + name: backend + sources: + - name: "*" + action: deny + - name: frontend + action: allow + peer: cluster-01 ## The peer of the source service + ``` + + + +1. Apply the intentions to the second cluster. + + + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT apply --filename intention.yaml + ``` + + + +1. Add the `"consul.hashicorp.com/connect-inject": "true"` annotation to your service's pods before deploying the workload so that the services in `cluster-01` can dial `backend` in `cluster-02`. To dial the upstream service from an application, configure the application so that that requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/consul/docs/discovery/dns#service-virtual-ip-lookups). In the following example, the annotation that allows the workload to join the mesh and the configuration provided to the workload that enables the workload to dial the upstream service using the correct DNS name is highlighted. [Service Virtual IP Lookups for Consul Enterprise](/consul/docs/discovery/dns#service-virtual-ip-lookups-for-consul-enterprise) details how you would similarly format a DNS name including partitions and namespaces. + + + + ```yaml + # Service to expose frontend + apiVersion: v1 + kind: Service + metadata: + name: frontend + spec: + selector: + app: frontend + ports: + - name: http + protocol: TCP + port: 9090 + targetPort: 9090 + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: frontend + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: frontend + labels: + app: frontend + spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + "consul.hashicorp.com/connect-inject": "true" + spec: + serviceAccountName: frontend + containers: + - name: frontend + image: nicholasjackson/fake-service:v0.22.4 + securityContext: + capabilities: + add: ["NET_ADMIN"] + ports: + - containerPort: 9090 + env: + - name: "LISTEN_ADDR" + value: "0.0.0.0:9090" + - name: "UPSTREAM_URIS" + value: "http://backend.virtual.cluster-02.consul" + - name: "NAME" + value: "frontend" + - name: "MESSAGE" + value: "Hello World" + - name: "HTTP_CLIENT_KEEP_ALIVES" + value: "false" + ``` + + + +1. Apply the service file to the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT apply --filename frontend.yaml + ``` + +1. Run the following command in `frontend` and then check the output to confirm that you peered your clusters successfully. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT exec -it $(kubectl --context $CLUSTER1_CONTEXT get pod -l app=frontend -o name) -- curl localhost:9090 + ``` + + + + ```json + { + "name": "frontend", + "uri": "/", + "type": "HTTP", + "ip_addresses": [ + "10.16.2.11" + ], + "start_time": "2022-08-26T23:40:01.167199", + "end_time": "2022-08-26T23:40:01.226951", + "duration": "59.752279ms", + "body": "Hello World", + "upstream_calls": { + "http://backend.virtual.cluster-02.consul": { + "name": "backend", + "uri": "http://backend.virtual.cluster-02.consul", + "type": "HTTP", + "ip_addresses": [ + "10.32.2.10" + ], + "start_time": "2022-08-26T23:40:01.223503", + "end_time": "2022-08-26T23:40:01.224653", + "duration": "1.149666ms", + "headers": { + "Content-Length": "266", + "Content-Type": "text/plain; charset=utf-8", + "Date": "Fri, 26 Aug 2022 23:40:01 GMT" + }, + "body": "Response from backend", + "code": 200 + } + }, + "code": 200 + } + ``` + + + +### Authorize service reads with ACLs + +If ACLs are enabled on a Consul cluster, sidecar proxies that access exported services as an upstream must have an ACL token that grants read access. + +Read access to all imported services is granted using either of the following rules associated with an ACL token: + +- `service:write` permissions for any service in the sidecar's partition. +- `service:read` and `node:read` for all services and nodes, respectively, in sidecar's namespace and partition. + +For Consul Enterprise, the permissions apply to all imported services in the service's partition. These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). + +Refer to [Reading servers](/consul/docs/connect/config-entries/exported-services#reading-services) in the `exported-services` configuration entry documentation for example rules. + +For additional information about how to configure and use ACLs, refer to [ACLs system overview](/consul/docs/security/acl). \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx new file mode 100644 index 0000000000..956298fe3c --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/l7-traffic.mdx @@ -0,0 +1,75 @@ +--- +layout: docs +page_title: Manage L7 Traffic With Cluster Peering on Kubernetes +description: >- + Combine service resolver configurations with splitter and router configurations to manage L7 traffic in Consul on Kubernetes deployments with cluster peering connections. Learn how to define dynamic traffic rules to target peers for redirects in k8s. +--- + +# Manage L7 traffic with cluster peering on Kubernetes + +This usage topic describes how to configure the `service-resolver` custom resource definition (CRD) to set up and manage L7 traffic between services that have an existing cluster peering connection in Consul on Kubernetes deployments. + +For general guidance for managing L7 traffic with cluster peering, refer to [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management). + +## Service resolvers for redirects and failover + +When you use cluster peering to connect datacenters through their admin partitions, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically forward traffic to services hosted on peer clusters. + +However, the `service-splitter` and `service-router` CRDs do not natively support directly targeting a service instance hosted on a peer. Before you can split or route traffic to a service on a peer, you must define the service hosted on the peer as an upstream service by configuring a failover in a `service-resolver` CRD. Then, you can set up a redirect in a second service resolver to interact with the peer service by name. + +For more information about formatting, updating, and managing configuration entries in Consul, refer to [How to use configuration entries](/consul/docs/agent/config-entries). + +## Configure dynamic traffic between peers + +To configure L7 traffic management behavior in deployments with cluster peering connections, complete the following steps in order: + +1. Define the peer cluster as a failover target in the service resolver configuration. + + The following example updates the [`service-resolver` CRD](/consul/docs/connect/config-entries/) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend + spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'frontend' + namespace: 'default' + ``` + +1. Define the desired behavior in `service-splitter` or `service-router` CRD. + + The following example splits traffic evenly between `frontend` and `frontend-peer`: + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceSplitter + metadata: + name: frontend + spec: + splits: + - weight: 50 + ## defaults to service with same name as configuration entry ("frontend") + - weight: 50 + service: frontend-peer + ``` + +1. Create a second `service-resolver` configuration entry on the local cluster that resolves the name of the peer service you used when splitting or routing the traffic. + + The following example uses the name `frontend-peer` to define a redirect targeting the `frontend` service on the peer `cluster-02`: + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: ServiceResolver + metadata: + name: frontend-peer + spec: + redirect: + peer: 'cluster-02' + service: 'frontend' + ``` \ No newline at end of file diff --git a/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx b/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx new file mode 100644 index 0000000000..bc622fe15d --- /dev/null +++ b/website/content/docs/k8s/connect/cluster-peering/usage/manage-peering.mdx @@ -0,0 +1,121 @@ +--- +layout: docs +page_title: Manage Cluster Peering Connections on Kubernetes +description: >- + Learn how to list, read, and delete cluster peering connections using Consul on Kubernetes. You can also reset cluster peering connections on k8s deployments. +--- + +# Manage cluster peering connections on Kubernetes + +This usage topic describes how to manage cluster peering connections on Kubernetes deployments. + +After you establish a cluster peering connection, you can get a list of all active peering connections, read a specific peering connection's information, and delete peering connections. + +For general guidance for managing cluster peering connections, refer to [Manage L7 traffic with cluster peering](/consul/docs/connect/cluster-peering/usage/peering-traffic-management). + +## Reset a peering connection + +To reset the cluster peering connection, you need to generate a new peering token from the cluster where you created the `PeeringAcceptor` CRD. The only way to create or set a new peering token is to manually adjust the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token causes the previous token to expire. + +1. In the `PeeringAcceptor` CRD, add the annotation `consul.hashicorp.com/peering-version`. If the annotation already exists, update its value to a higher version. + + + + ```yaml + apiVersion: consul.hashicorp.com/v1alpha1 + kind: PeeringAcceptor + metadata: + name: cluster-02 + annotations: + consul.hashicorp.com/peering-version: "1" ## The peering version you want to set, must be in quotes + spec: + peer: + secret: + name: "peering-token" + key: "data" + backend: "kubernetes" + ``` + + + +1. After updating `PeeringAcceptor`, repeat all of the steps to [establish a new peering connection](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). + +## List all peering connections + +In Consul on Kubernetes deployments, you can list all active peering connections in a cluster using the Consul CLI. + +1. If necessary, [configure your CLI to interact with the Consul cluster](/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy#configure-your-cli-to-interact-with-consul-cluster). + +1. Run the [`consul peering list` CLI command](/consul/commands/peering/list). + + ```shell-session + $ consul peering list + Name State Imported Svcs Exported Svcs Meta + cluster-02 ACTIVE 0 2 env=production + cluster-03 PENDING 0 0 + ``` + +## Read a peering connection + +In Consul on Kubernetes deployments, you can get information about individual peering connections between clusters using the Consul CLI. + +1. If necessary, [configure your CLI to interact with the Consul cluster](/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy#configure-your-cli-to-interact-with-consul-cluster). + +1. Run the [`consul peering read` CLI command](/consul/commands/peering/read). + + ```shell-session + $ consul peering read -name cluster-02 + Name: cluster-02 + ID: 3b001063-8079-b1a6-764c-738af5a39a97 + State: ACTIVE + Meta: + env=production + + Peer ID: e83a315c-027e-bcb1-7c0c-a46650904a05 + Peer Server Name: server.dc1.consul + Peer CA Pems: 0 + Peer Server Addresses: + 10.0.0.1:8300 + + Imported Services: 0 + Exported Services: 2 + + Create Index: 89 + Modify Index: 89 + ``` + +## Delete peering connections + +To end a peering connection in Kubernetes deployments, delete both the `PeeringAcceptor` and `PeeringDialer` resources. + +1. Delete the `PeeringDialer` resource from the second cluster. + + ```shell-session + $ kubectl --context $CLUSTER2_CONTEXT delete --filename dialer.yaml + ``` + +1. Delete the `PeeringAcceptor` resource from the first cluster. + + ```shell-session + $ kubectl --context $CLUSTER1_CONTEXT delete --filename acceptor.yaml + ```` + +To confirm that you deleted your peering connection in `cluster-01`, query the the `/health` HTTP endpoint: + +1. Exec into the server pod for the first cluster. + + ```shell-session + $ kubectl exec -it consul-server-0 --context $CLUSTER1_CONTEXT -- /bin/sh + ``` + +1. If you've enabled ACLs, export an ACL token to access the `/health` HTP endpoint for services. The bootstrap token may be used if an ACL token is not already provisioned. + + ```shell-session + $ export CONSUL_HTTP_TOKEN= + ``` + +1. Query the the `/health` HTTP endpoint. Peered services with deleted connections should no longe appear. + + ```shell-session + $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" + ``` \ No newline at end of file diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 7828cfa7b8..e39e520641 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -497,10 +497,6 @@ { "title": "Enabling Peering Control Plane Traffic", "path": "connect/gateways/mesh-gateway/peering-via-mesh-gateways" - }, - { - "title": "Enabling Service-to-service Traffic Across Peered Clusters", - "path": "connect/gateways/mesh-gateway/service-to-service-traffic-peers" } ] }, @@ -518,16 +514,29 @@ "title": "Cluster Peering", "routes": [ { - "title": "What is Cluster Peering?", + "title": "Overview", "path": "connect/cluster-peering" }, { - "title": "Create and Manage Peering Connections", - "path": "connect/cluster-peering/create-manage-peering" + "title": "Technical Specifications", + "path": "connect/cluster-peering/tech-specs" }, { - "title": "Cluster Peering on Kubernetes", - "path": "connect/cluster-peering/k8s" + "title": "Usage", + "routes": [ + { + "title": "Establish Cluster Peering Connections", + "path": "connect/cluster-peering/usage/establish-cluster-peering" + }, + { + "title": "Manage Cluster Peering Connections", + "path": "connect/cluster-peering/usage/manage-connections" + }, + { + "title": "Manage L7 Traffic With Cluster Peering", + "path": "connect/cluster-peering/usage/peering-traffic-management" + } + ] } ] }, @@ -963,6 +972,36 @@ "title": "Admin Partitions", "href": "/docs/enterprise/admin-partitions" }, + { + "title": "Cluster Peering", + "routes": [ + { + "title": "Overview", + "href": "/docs/connect/cluster-peering" + }, + { + "title": "Technical Specifications", + "path": "k8s/connect/cluster-peering/tech-specs" + }, + { + "title": "Usage", + "routes": [ + { + "title": "Establish Cluster Peering Connections", + "path": "k8s/connect/cluster-peering/usage/establish-peering" + }, + { + "title": "Manage Cluster Peering Connections", + "path": "k8s/connect/cluster-peering/usage/manage-peering" + }, + { + "title": "Manage L7 Traffic With Cluster Peering", + "path": "k8s/connect/cluster-peering/usage/l7-traffic" + } + ] + } + ] + }, { "title": "Transparent Proxy", "href": "/docs/connect/transparent-proxy" @@ -1251,7 +1290,7 @@ }, { "title": "Network Segments", - "routes":[ + "routes": [ { "title": "Network Segments Overview", "path": "enterprise/network-segments/network-segments-overview" @@ -1319,8 +1358,8 @@ "path": "api-gateway/usage/usage" }, { - "title": "Reroute HTTP Requests", - "path": "api-gateway/usage/reroute-http-requests" + "title": "Reroute HTTP Requests", + "path": "api-gateway/usage/reroute-http-requests" }, { "title": "Route Traffic to Peered Services", @@ -1329,8 +1368,7 @@ { "title": "Error Messages", "path": "api-gateway/usage/errors" - } - + } ] }, { diff --git a/website/public/img/cluster-peering-diagram.png b/website/public/img/cluster-peering-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d00aa3eb0f3bbad88b0e13d6d905fbc346f2cab7 GIT binary patch literal 129219 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%0Am)ijXV#D)>5x zggp5Tn`n;w+WglW5N1PEp>DZXbG`~EtH6__8B2Rs`@HHH0lcBBk*g*@w9(RY+kRLm zAIBr6FgKt`cFpN@p8OcC#fEt^H~fVIy3`aN#B)UEX5+h61Kl_$__Br4(Pg|*9V{8Y zJl*S@Wb}QmeK!tNU*qq>^Vx0SO##iGcKmun$HAj7{5P_~Jl3SBTb)hu#OUuN+>^EV zYx5mjeCRouZ)uX`gfm;nmow)93qj+^9f_jfEi$bZ8w&+rhywaI21$T|vQC zBrZXe;@@M(ssjr_qet8_`&^h7SpaP1)@O$9Khv(v0Z3}iV1ELH7*p}J9ss}w2NzfR zEZP(TsZvOlN4n1oMD%3NB-ZPz8BR!z+drL}OaH{C)`Du>s8HmMZ3tNZ{?WuX=Ai_Rok4q&}1rK{&o)v z>HdK87Mk>gRdxB+cNkLu!%dYd(u^$4AYWDp9|s>xo&^&`dJt0y=f_s!L#_>$lIoZG z+k>7?gh`0#9#NSld`+Fp9v~}dcpR0lpH{e#HmAuXk@fGp4 z;z_T1qVna^RpTgRk@joL5Tei2J<%UB#VZf>46mr3z-Sv{Ku|It^k}7^ZK1*{La{(I z@ijf@8tO?%$d9@7E23Ey`3>N!!8t8H_@(wN2(#@UF>arbuTo}hfA{vZiap7#(s&j` zh0Na>CFe5DDMOMUS@EdJW?$JAf%HY3)RQ+8*3YnoTW&64Bm&1vO(aGuLqZ5OCV*fl z_Nu}(F;nm4HOjMdo^#(jI;>>@GxDb!B3SzlwONjONnO95G*EZ@8y>c9CyKJhZzz(7 zEToHV-I^1gR|EimhQkxiKc!Ce#oi_GbOi_xOdVp@_CH&10ct_0}u`$`XGaqYyHV+UiXO#B5k@?ye?tmkkIWn@@3^S zz^^gJC)Wdt8uCZi-Swq0vYoHZ9%YBPMe4u;*Cj_?kbqj?0yAS3KrVT16i4{Y#S$oy z%cf@SB_vQf%0&c%Ki`WA5NNbkfD+D&z=Ge^k_WYqt2iVHc6;KA1B9*wpe)v&%k-C2lDOxx zc9!B$dZ~0OdEw&VhXYKrBanlNh#+8cu2Q7*6%i~%Rn2>k;S*~jb@D^hcmV%ePJ#2$!9yIhyG|AB>Xh=U!>kDI&kKGim4`h;C#ekFj@DYkw zO?;1JW$YYvDwa^_Zf#0aDCg|`k$K@lS1|ZkUAFr&M=VC{@?owa-c;I|s z+rb(rCV-vr;8;bVv_qVa#_gs?}UQIfFIk4RKJjLi#`I|J@)%Whb{U>&mNGxCvk2Ogq8 zg5crE_Dutht@KJ$AXmNn*&j8I`lJ>lcy+W<|4o}xL8VS9?2=PGr|^A+8jbnC`0H@E zP76R^Cv7;~7uw5IpDjrdT>UL@FjV!(A;+Qx9b_8_r@JGp*8^!epdhpQPro6f=#YR%qu^W;w|f!>>GB(#4-HBH$-FO(zjb6^HNf6pa;ryjCe- zH=AFWX%;n;$n!_lQRRk-2VibxVcQjTm=jh&KcYngOFR_3=*2IkQEFJfSO=v{kdG{1 zJ^k|8!pNTEH|p>Unx0)-=mO2a}@UM>PNGJw_gEMw5 z5Xm16+}kr?$L? z1a~EqzKY~rl(qDVzdm-msA~{*I9pslLUmJj@8zwNwD-6UFyr%651F;j`)Kp*vv!J^ z4iMvPn6+qAKD!h+r&vm4=cn#5o3{jS?=epu{74>zBDUoSE6$gimoVkZF)jk+l=7LH zB9iqq2XZ4#WU_k}DL9#M{-Y0AKSxnaCV#GmBiIzp%v(Pysqk8IV zu2;x}4MjoK%bX~v4|$KoUTI**ZahNn2W^VUj$Z_@ROQQ9Phx6u9r4?=F z8i{_DH_l4O1SM8|*gnTNITPF&Q|TzH5h_=} zbzpiu2F-CG>8WIzf?&n~S<&<~{=X5_pvFB1_%U*KmP2elo zIAihoR9UZegWhh|M$v3}Pb#$2(2QHOrcz{*<-FB5vxpv-HS}#x!95G;__gVa#XUXK ztPwpu^95_nRkCPfSKzD|?tsW@s(v`dD0pb(wgPl{@|A|$TIxvbfAg*ftabheT^%Lh zeE9j~_nX4M_In*b6yz2MnvWb;8Shf1{Tn*-H^<~J)&WfvD2%Mr8U2le@NaJcEa5;> zsIk%)jeRBm?Hj5ekihWJEL&du??juhP{1lV?Dz!;JpA{P`>Om;WBvzz(qE(X|7K)z zg&Fhi77I5B(fJuc+9C^K`|u%ETNWUW@PeSAAT0@a@fjR9^lL^jGm1RilP%mPBMfi; zPqBz$=0^bWp(ulqMh}RX!Mjl+_cNP+TtG4RKS1`N*O)+fJbpM<_TEeBl5`FR?aQ+4 z|9t0X&r<{+9#rk==}B2r-7>gF!Rg8#?)tllK%9q@2I!+s=2-^Gy}pchep@e{-(60T z*f*O%UznHBl>&%+Ha0dBv!dA?ARi{A!M}R(!G+N&BePI^<9>VWpMQQD6=Ik!Fc;jf zdIUt;a?3pC64&agJVuXaq{I*>@u)aFzLsDeSJ3bL}Y1z*k{ zNE!UDKEH2ThrK>5?L4IQ%}4Iro?5xt?YhR^2zROBH?`xx2o9e`bMsYNW$0KwW`_bq65b;5*oUq3VG_fZAZ{0(}CoS`kJ znM>Sh?7>lz@asAeLq}6u{?&{R0*swHm$@lg$ZNwcYm<(n;o*#uOdue=qVBfBj|WdL z&1ruH?W;YBn^8ferh=u~rsJSlopajI+}_)$2!Sa|^bdGmro$brV;4Ms*&r8mKCnuJ zmsvJlEBg+xot%L7RL?roc!A*R!WoUfyZ6^Z0oq_OuYY{@c<754FS`FceZ?+TtiG6f zhdLRuOW;rD{l%91dYZBT3%RKox;XsiR_rXeHA)|Hq>THM$9_wDk|iuFi&wJf)8IXj z&-K$UIm@~QfAvGv?tm2~5D*caV)uDgwN38rHYq}wF@j1AIA@vnZPZ#Zsx46BO{R0r zVph#V{$3L2*ah$X({Ag-i&{YD9?s}q!^#KB10fdv_JDsn?e%%s-*cPc=S5Y;0IT0n zN=mjby!GKnMza>EtJwV+<6rdv(8r>==h^}?eRXy9i3f47aQ((M>PF{$WL(N`akOD$ zhZnQ@V$X=BR99C&xzP;PDm5;5PQU2;51WZ{UlmQeTGTek=)BT##lSk_^bLm3FDCcL zqn=eEZ{G}TC^EMxf2^IIQ z*aO$Wo#SZ#T=(bQ_V~lJ!7aPYysn4)n5}TB+6g+$%1Y`c zj?&xR4Z3JMz49ykW~DK-{iuPtxpNS{`{A+RSA;;vA3xU$exdPNCj_wUc{YBD-GIZc)5U8hWK#WTd(B6@51CH}uJ@jKqkz^L{!^z^fN(YJjie_iEg0g5GHc_6R6n*P5u<|g3b zRV(x9iQ8B5*P{zm{-4JDZ#UsTBlDk;**5|w{_`~L8;^fht^c1RGr6~<^q4K8vU-96 P_|d+lceCQg!|?wC_y&s& literal 0 HcmV?d00001 diff --git a/website/redirects.js b/website/redirects.js index 8e31b56f34..74caeb5e38 100644 --- a/website/redirects.js +++ b/website/redirects.js @@ -4,4 +4,16 @@ // modify or delete existing redirects without first verifying internally. // Next.js redirect documentation: https://nextjs.org/docs/api-reference/next.config.js/redirects -module.exports = [] +module.exports = [ + { + source: '/docs/connect/cluster-peering/create-manage-peering', + destination: + '/docs/connect/cluster-peering/usage/establish-cluster-peering', + permanent: true, + }, + { + source: '/docs/connect/cluster-peering/k8s', + destination: '/docs/k8s/connect/cluster-peering/k8s-tech-specs', + permanent: true, + }, +]