From 4a0fd15b6905cffc560737056e7fd488195d4532 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Tue, 15 Nov 2022 22:07:37 -0800 Subject: [PATCH 01/16] update compatibility matrix (#15389) --- website/content/docs/k8s/compatibility.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/website/content/docs/k8s/compatibility.mdx b/website/content/docs/k8s/compatibility.mdx index 7938e57427bb..84dbdd2a2d9d 100644 --- a/website/content/docs/k8s/compatibility.mdx +++ b/website/content/docs/k8s/compatibility.mdx @@ -11,13 +11,13 @@ For every release of Consul on Kubernetes, a Helm chart, `consul-k8s-control-pla ## Supported Consul versions -Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s-control-plane`, and Helm chart) with a single semantic version. When installing or upgrading to a specific versions, ensure that you are using the correct Consul version with the compatible `consul-k8s` helm chart and/or CLI. +Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s-control-plane`, and Helm chart) with a single semantic version. When installing or upgrading to a specific versions, ensure that you are using the correct Consul version with the compatible `consul-k8s` helm chart and/or CLI. | Consul Version | Compatible consul-k8s Versions | | -------------- | -------------------------------- | -| 1.13.x | 0.47.0 - latest | -| 1.12.x | 0.43.0 - latest | -| 1.11.x | 0.39.0 - 0.42.0, 0.44.0 - latest | +| 1.13.x | 0.49.x | +| 1.12.x | 0.43.0 - 0.49.x | +| 1.11.x | 0.39.0 - 0.42.0, 0.44.0 - 0.49.x | ## Supported Envoy versions @@ -33,7 +33,7 @@ Starting with Consul K8s 0.39.0 and Consul 1.11.x, Consul Kubernetes supports th ## Platform specific compatibility notes -### Red Hat OpenShift +### Red Hat OpenShift Consul Kubernetes delivered Red Hat OpenShift support starting with Consul Helm chart version 0.25.0 for Consul 1.8.4. Please note the following details regarding OpenShift support. From b0041311f79d9df7fae31e971f49172f3209071c Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Wed, 16 Nov 2022 13:38:09 -0600 Subject: [PATCH 02/16] Fix issue with formatting in upgrade notes. (#15395) --- website/content/docs/upgrading/upgrade-specific.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index cb59d74c9287..c0ab7e7ac848 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -35,8 +35,7 @@ A breaking change was made in Consul 1.14 that: Prior to Consul 1.14, it was possible to encrypt communication between Consul and Envoy over `ports.grpc` using these settings. Consul 1.14 introduces [`ports.grpc_tls`](/docs/agent/config/config-files#grpc_tls_port), a new configuration -for encrypting communication over gRPC. The existing [`ports.grpc`](/docs/agent/config/config- -files#grpc_port) configuration **no longer supports encryption**. As of version 1.14, +for encrypting communication over gRPC. The existing [`ports.grpc`](/docs/agent/config/config-files#grpc_port) configuration **no longer supports encryption**. As of version 1.14, [`ports.grpc_tls`](/docs/agent/config/config-files#grpc_tls_port) is the only port that serves encrypted gRPC traffic. The default value for the gRPC TLS port is 8503 for Consul servers. To disable the gRPC TLS port, use value -1. From 5593d5ddb52cae6111380778332fd4ce335bad20 Mon Sep 17 00:00:00 2001 From: cskh Date: Wed, 16 Nov 2022 14:40:52 -0500 Subject: [PATCH 03/16] docs: make the h1 title consistent with the page_title (#15396) --- .../gateways/mesh-gateway/wan-federation-via-mesh-gateways.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways.mdx b/website/content/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways.mdx index 1fd4b0cba257..3898e04889ee 100644 --- a/website/content/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways.mdx +++ b/website/content/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways.mdx @@ -5,7 +5,7 @@ description: >- You can use mesh gateways to simplify the networking requirements for WAN federated Consul datacenters. Mesh gateways reduce cross-datacenter connection paths, ports, and communication protocols. --- -# Mesh Gateways for WAN Federation +# Mesh Gateways for WAN Federation Control Plane Traffic -> **1.8.0+:** This feature is available in Consul versions 1.8.0 and higher From 585371082978687460ab24ff33ec1bc331d68516 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:10:29 -0600 Subject: [PATCH 04/16] Include addresses.grpc_tls in upgrade docs. (#15408) --- website/content/docs/agent/config/config-files.mdx | 1 + website/content/docs/upgrading/upgrade-specific.mdx | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/website/content/docs/agent/config/config-files.mdx b/website/content/docs/agent/config/config-files.mdx index 7ff61f39937d..2f807c3a8833 100644 --- a/website/content/docs/agent/config/config-files.mdx +++ b/website/content/docs/agent/config/config-files.mdx @@ -102,6 +102,7 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." - `http` - The HTTP API. Defaults to `client_addr` - `https` - The HTTPS API. Defaults to `client_addr` - `grpc` - The gRPC API. Defaults to `client_addr` + - `grpc_tls` - The gRPC API with TLS. Defaults to `client_addr` - `alt_domain` Equivalent to the [`-alt-domain` command-line flag](/docs/agent/config/cli-flags#_alt_domain) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index c0ab7e7ac848..afb453474cd5 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -39,7 +39,10 @@ for encrypting communication over gRPC. The existing [`ports.grpc`](/docs/agent/ [`ports.grpc_tls`](/docs/agent/config/config-files#grpc_tls_port) is the only port that serves encrypted gRPC traffic. The default value for the gRPC TLS port is 8503 for Consul servers. To disable the gRPC TLS port, use value -1. -If you already use gRPC encryption, change the existing `ports.grpc` to `ports.grpc_tls` in your configuration to ensure compatibility. +If you already use gRPC encryption, change the following fields to ensure compatibility: + ++ Change `ports.grpc` to `ports.grpc_tls`. [visit ports documentation for details](/docs/agent/config/config-files#grpc_tls_port) ++ Change `addresses.grpc` to `addresses.grpc_tls`. [visit addresses documentation for details](/docs/agent/config/config-files#grpc_tls) #### Changes to peering From 435e16ecdac569743b6c2cb06790e5a1a2fa7c14 Mon Sep 17 00:00:00 2001 From: cskh Date: Wed, 16 Nov 2022 15:27:37 -0500 Subject: [PATCH 05/16] fix: clarifying error message when acquiring a lock in remote dc (#15394) * fix: clarifying error message when acquiring a lock in remote dc * Update website/content/commands/lock.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- agent/session_endpoint.go | 4 ++++ website/content/commands/lock.mdx | 2 ++ 2 files changed, 6 insertions(+) diff --git a/agent/session_endpoint.go b/agent/session_endpoint.go index d1b6dd7cf157..8e76d577ab04 100644 --- a/agent/session_endpoint.go +++ b/agent/session_endpoint.go @@ -47,6 +47,10 @@ func (s *HTTPHandlers) SessionCreate(resp http.ResponseWriter, req *http.Request fixupEmptySessionChecks(&args.Session) + if (s.agent.config.Datacenter != args.Datacenter) && (!s.agent.config.ServerMode) { + return nil, fmt.Errorf("cross datacenter lock must be created at server agent") + } + // Create the session, get the ID var out string if err := s.agent.RPC("Session.Apply", &args, &out); err != nil { diff --git a/website/content/commands/lock.mdx b/website/content/commands/lock.mdx index 8abf4c12cdd7..fb80f6554776 100644 --- a/website/content/commands/lock.mdx +++ b/website/content/commands/lock.mdx @@ -24,6 +24,8 @@ If the lock holder count is more than one, then a semaphore is used instead. A semaphore allows more than a single holder, but this is less efficient than a simple lock. This follows the [semaphore algorithm](https://learn.hashicorp.com/consul/developer-configuration/semaphore). +To apply a lock to a remote WAN federated datacenter, run the command with the `-datacenter=` flag on a server agent. You cannot use the command with `-datacenter` on client agents because they are unavailable to the remote datacenter. + All locks using the same prefix must agree on the value of `-n`. If conflicting values of `-n` are provided, an error will be returned. From 527b58db66ca0505c6b1d36fd10e3fc6bed61456 Mon Sep 17 00:00:00 2001 From: Michael Schurter Date: Wed, 16 Nov 2022 14:45:58 -0800 Subject: [PATCH 06/16] docs: add nomad incompatibility to 1.14 docs (#15397) docs: add nomad incompatibility to 1.14 docs --- website/content/docs/upgrading/upgrade-specific.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index afb453474cd5..f49b6a4a3dc9 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -24,6 +24,11 @@ A breaking change was made in Consul 1.14 that: - [Consul Connect is enabled by default.](/docs/connect) To disable, set [`connect.enabled`](/docs/agent/config/config-files#connect_enabled) to `false`. +The changes to Consul service mesh in version 1.14 are incompatible with Nomad 1.4.2 and +earlier. If you operate Consul service mesh using Nomad 1.4.2 or earlier, do not upgrade to Consul 1.14 until +[hashicorp/nomad#15266](https://github.com/hashicorp/nomad/issues/15266) is +fixed. + #### Changes to gRPC TLS configuration **Make configuration changes** if using [`ports.grpc`](/docs/agent/config/config-files#grpc_port) in conjunction with any of the following settings that enables encryption: From 778812a457fdc6d038869b27aed0c81316f75e5c Mon Sep 17 00:00:00 2001 From: Dan Stough Date: Thu, 17 Nov 2022 11:25:32 -0500 Subject: [PATCH 07/16] docs(peering): update k8s docs for GA (#15417) * docs(peering): update k8s docs for GA * fix code formatting and typo --- website/content/api-docs/peering.mdx | 12 ++++----- .../cluster-peering/create-manage-peering.mdx | 4 +-- .../docs/connect/cluster-peering/k8s.mdx | 26 +++++++++++-------- .../config-entries/exported-services.mdx | 22 ++++++++-------- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/website/content/api-docs/peering.mdx b/website/content/api-docs/peering.mdx index d68716f82fc3..86c74e0a4042 100644 --- a/website/content/api-docs/peering.mdx +++ b/website/content/api-docs/peering.mdx @@ -34,8 +34,8 @@ The table below shows this endpoint's support for ### JSON Request Body Schema -- `PeerName` `(string: )` - The name assigned to the peer cluster. - The `PeerName` is used to reference the peer cluster in service discovery queries +- `Peer` `(string: )` - The name assigned to the peer cluster. + The `Peer` is used to reference the peer cluster in service discovery queries and configuration entries such as `service-intentions`. This field must be a valid DNS hostname label. @@ -54,7 +54,7 @@ You can specify one or more load balancers or external IPs that route external t ```json { - "PeerName": "cluster-02", + "Peer": "cluster-02", "Meta": { "env": "production" } @@ -101,8 +101,8 @@ The table below shows this endpoint's support for ### JSON Request Body Schema -- `PeerName` `(string: )` - The name assigned to the peer cluster. - The `PeerName` is used to reference the peer cluster in service discovery queries +- `Peer` `(string: )` - The name assigned to the peer cluster. + The `Peer` is used to reference the peer cluster in service discovery queries and configuration entries such as `service-intentions`. This field must be a valid DNS hostname label. @@ -121,7 +121,7 @@ The table below shows this endpoint's support for ```json { - "PeerName": "cluster-01", + "Peer": "cluster-01", "PeeringToken": "eyJDQSI6bnVsbCwiU2V...", "Meta": { "env": "production" diff --git a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx index 651826c04d2d..166770bbaf48 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -37,7 +37,7 @@ Every time you generate a peering token, a single-use establishment secret is em 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 '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token +$ 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. @@ -48,7 +48,7 @@ Create a JSON file that contains the first cluster's name and the peering token. ```json { - "PeerName": "cluster-01", + "Peer": "cluster-01", "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" } ``` diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index 1922a86406ae..88d3a4aff9c3 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -7,13 +7,18 @@ description: >- # Cluster Peering on Kubernetes -To establish a cluster peering connection on Kubernetes, you need to enable the feature in the Helm chart and create custom resource definitions (CRDs) for each side of the peering. +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). @@ -52,14 +57,13 @@ Complete the following procedure after you have provisioned a Kubernetes cluster image: "hashicorp/consul:1.14.0" peering: enabled: true + tls: + enabled: true connectInject: enabled: true dns: enabled: true enableRedirection: true - server: - exposeService: - enabled: true controller: enabled: true meshGateway: @@ -68,10 +72,6 @@ Complete the following procedure after you have provisioned a Kubernetes cluster ``` - - These Helm values configure the servers in each cluster so that they expose ports over a Kubernetes load balancer service. For additional configuration options, refer to [`server.exposeService`](/docs/k8s/helm#v-server-exposeservice). - - When generating a peering token from one of the clusters, Consul includes a load balancer address in the token so that the peering stream goes through the load balancer in front of the servers. For additional configuration options, refer to [`global.peering.tokenGeneration`](/docs/k8s/helm#v-global-peering-tokengeneration). ### Install Consul on Kubernetes @@ -94,7 +94,7 @@ Install Consul on Kubernetes by using the CLI to apply `values.yaml` to each clu ``` ```shell-session - $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.0" --values values.yaml --kube-context $CLUSTER2_CONTEXT + $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "1.0.0" --values values.yaml --set global.datacenter=dc2 --kube-context $CLUSTER2_CONTEXT ``` ## Create a peering connection for Consul on Kubernetes @@ -288,11 +288,15 @@ The examples described in this section demonstrate how to export a service named 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. + + +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. @@ -366,7 +370,7 @@ The examples described in this section demonstrate how to export a service named 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 diff --git a/website/content/docs/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 5c6dd2a09d2c..ed08cf39b4ff 100644 --- a/website/content/docs/connect/config-entries/exported-services.mdx +++ b/website/content/docs/connect/config-entries/exported-services.mdx @@ -62,7 +62,7 @@ spec: services: - name: consumers: - - peerName: + - peer: ``` ```json @@ -113,7 +113,7 @@ spec: - name: namespace: consumers: - - peerName: + - peer: ``` ```json @@ -266,10 +266,10 @@ spec: services: - name: payments consumers: - - peerName: web-shop + - peer: web-shop - name: refunds consumers: - - peerName: web-shop + - peer: web-shop ``` ```json @@ -341,11 +341,11 @@ spec: - name: payments namespace: billing consumers: - - peerName: web-shop + - peer: web-shop - name: refunds namespace: billing consumers: - - peerName: web-shop + - peer: web-shop ``` ```json @@ -494,8 +494,8 @@ spec: services: - name: * consumers: - - peerName: monitoring - - peerName: platform + - peer: monitoring + - peer: platform ``` ```json @@ -557,8 +557,8 @@ spec: - name: * namespace: * consumers: - - peerName: monitoring - - peerName: platform + - peer: monitoring + - peer: platform ``` ```json @@ -677,4 +677,4 @@ An ACL token with `service:write` permissions is required for the cluster the qu Exports are available to all services in the consumer cluster. In the previous example, any service with `write` permissions for the `frontend` partition can read exports. -For additional information, refer to [Health HTTP Endpoint](/api-docs/health). \ No newline at end of file +For additional information, refer to [Health HTTP Endpoint](/api-docs/health). From 43097a4db9c2d76b0451e44f07c7a2c38409a67f Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Thu, 17 Nov 2022 08:51:43 -0800 Subject: [PATCH 08/16] Update guidance for vault PKI CA provider (#15422) * Update guidance for vault PKI CA provider * clarify workarounds if already using vault 1.11+ * Update website/content/docs/connect/ca/vault.mdx * Update website/content/docs/k8s/connect/connect-ca-provider.mdx * Update website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx * Apply suggestions from code review Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> * add suggestion from Matt Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> --- website/content/docs/connect/ca/vault.mdx | 2 ++ website/content/docs/k8s/connect/connect-ca-provider.mdx | 3 +++ .../vault/data-integration/connect-ca.mdx | 2 ++ 3 files changed, 7 insertions(+) diff --git a/website/content/docs/connect/ca/vault.mdx b/website/content/docs/connect/ca/vault.mdx index 420045ec360f..1fe8a233628b 100644 --- a/website/content/docs/connect/ca/vault.mdx +++ b/website/content/docs/connect/ca/vault.mdx @@ -29,6 +29,8 @@ must be met: were introduced in Vault 0.10.3. Prior versions of Vault are not compatible with Connect. +~> **Note:** Do not use Vault v1.11.0+ as Consul's Connect CA provider — the intermediate CA will become unable to issue the leaf nodes required by service mesh, and by Consul client agents if using auto-encrypt or auto-config and using TLS for agent communication. If you are already using Vault 1.11+ as a Connect CA, refer to this [Knowledge Base article](https://support.hashicorp.com/hc/en-us/articles/11308460105491) for more information about the underlying cause and recommended workaround. + ## Configuration The Vault CA is enabled by setting the CA provider to `"vault"` and diff --git a/website/content/docs/k8s/connect/connect-ca-provider.mdx b/website/content/docs/k8s/connect/connect-ca-provider.mdx index daee4106280f..cee7850c50dc 100644 --- a/website/content/docs/k8s/connect/connect-ca-provider.mdx +++ b/website/content/docs/k8s/connect/connect-ca-provider.mdx @@ -23,6 +23,9 @@ To configure the Vault service mesh provider, refer to [Vault as the Service Mes ## Configuring Vault as a Connect CA (Consul K8s 0.37.0 and earlier) + +~> **Note:** Do not use Vault v1.11.0+ as Consul's Connect CA provider — the intermediate CA will become unable to issue the leaf nodes required by service mesh, and by Consul client agents if using auto-encrypt or auto-config and using TLS for agent communication. If you are already using Vault 1.11+ as a Connect CA, refer to this [Knowledge Base article](https://support.hashicorp.com/hc/en-us/articles/11308460105491) for more information about the underlying cause and recommended workaround. + The following instructions are only valid for Consul K8s CLI 0.37.0 and prior. It describes how to configure Vault as the Connect CA. You can configure other providers during initial bootstrap of the cluster by providing the appropriate [`ca_config`] and [`ca_provider`] values for your provider. -> **Auto-renewal:** If using Vault as your Connect CA, we strongly recommend Consul 1.8.5 or later, which includes support for token auto-renewal. If the Vault token is [renewable](https://www.vaultproject.io/api-docs/auth/token#renewable), then Consul automatically renews the token periodically. Otherwise, you must [manually rotate](#manually-rotating-vault-tokens) the Vault token before it expires. diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx index 188fcdef66e0..631b60b8a1e4 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx @@ -13,6 +13,8 @@ This topic describes how to configure the Consul Helm chart to use TLS certifica Consul allows using Kubernetes auth methods to configure Connect CA. This allows for automatic token rotation once the renewal is no longer possible. +~> **Note:** Do not use Vault v1.11.0+ as Consul's Connect CA provider — the intermediate CA will become unable to issue the leaf nodes required by service mesh, and by Consul client agents if using auto-encrypt or auto-config and using TLS for agent communication. If you are already using Vault 1.11+ as a Connect CA, refer to this [Knowledge Base article](https://support.hashicorp.com/hc/en-us/articles/11308460105491) for more information about the underlying cause and recommended workaround. + ## Overview To use Vault as the service mesh certificate provider on Kubernetes, you will complete a modified version of the steps outlined in the [Data Integration](/docs/k8s/deployment-configurations/vault/data-integration) section. From 2b90307f6dfb29ba3c887fd7c857bfde71c9f176 Mon Sep 17 00:00:00 2001 From: Alexander Scheel Date: Thu, 17 Nov 2022 16:29:49 -0500 Subject: [PATCH 09/16] Detect Vault 1.11+ import, update default issuer (#15253) Consul used to rely on implicit issuer selection when calling Vault endpoints to issue new CSRs. Vault 1.11+ changed that behavior, which caused Consul to check the wrong (previous) issuer when renewing its Intermediate CA. This patch allows Consul to explicitly set a default issuer when it detects that the response from Vault is 1.11+. Signed-off-by: Alexander Scheel Co-authored-by: Chris S. Kim --- .changelog/15253.txt | 3 + agent/connect/ca/provider_vault.go | 97 ++++++++++++++++++++++--- agent/connect/ca/provider_vault_test.go | 22 ++++++ 3 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 .changelog/15253.txt diff --git a/.changelog/15253.txt b/.changelog/15253.txt new file mode 100644 index 000000000000..b0063ffb4799 --- /dev/null +++ b/.changelog/15253.txt @@ -0,0 +1,3 @@ +```release-note:bug +connect: Fixed issue where using Vault 1.11+ as CA provider would eventually break Intermediate CAs [[GH-15217](https://github.com/hashicorp/consul/issues/15217)] +``` \ No newline at end of file diff --git a/agent/connect/ca/provider_vault.go b/agent/connect/ca/provider_vault.go index 03f8aaec3ca5..25eb07691815 100644 --- a/agent/connect/ca/provider_vault.go +++ b/agent/connect/ca/provider_vault.go @@ -363,7 +363,8 @@ func (v *VaultProvider) GenerateIntermediateCSR() (string, error) { "cannot generate an intermediate CSR") } - return v.generateIntermediateCSR() + csr, _, err := v.generateIntermediateCSR() + return csr, err } func (v *VaultProvider) setupIntermediatePKIPath() error { @@ -406,11 +407,13 @@ func (v *VaultProvider) setupIntermediatePKIPath() error { return err } -func (v *VaultProvider) generateIntermediateCSR() (string, error) { +// generateIntermediateCSR returns the CSR and key_id (only present in +// Vault 1.11+) or any errors encountered. +func (v *VaultProvider) generateIntermediateCSR() (string, string, error) { // Generate a new intermediate CSR for the root to sign. uid, err := connect.CompactUID() if err != nil { - return "", err + return "", "", err } data, err := v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/generate/internal", map[string]interface{}{ "common_name": connect.CACN("vault", uid, v.clusterID, v.isPrimary), @@ -419,17 +422,26 @@ func (v *VaultProvider) generateIntermediateCSR() (string, error) { "uri_sans": v.spiffeID.URI().String(), }) if err != nil { - return "", err + return "", "", err } if data == nil || data.Data["csr"] == "" { - return "", fmt.Errorf("got empty value when generating intermediate CSR") + return "", "", fmt.Errorf("got empty value when generating intermediate CSR") } csr, ok := data.Data["csr"].(string) if !ok { - return "", fmt.Errorf("csr result is not a string") + return "", "", fmt.Errorf("csr result is not a string") } - - return csr, nil + // Vault 1.11+ will return a "key_id" field which helps + // identify the correct issuer to set as default. + // https://github.com/hashicorp/vault/blob/e445c8b4f58dc20a0316a7fd1b5725b401c3b17a/builtin/logical/pki/path_intermediate.go#L154 + if rawkeyId, ok := data.Data["key_id"]; ok { + keyId, ok := rawkeyId.(string) + if !ok { + return "", "", fmt.Errorf("key_id is not a string") + } + return csr, keyId, nil + } + return csr, "", nil } // SetIntermediate writes the incoming intermediate and root certificates to the @@ -531,7 +543,7 @@ func (v *VaultProvider) getCAChain(namespace, path string) (string, error) { // necessary, then generates and signs a new CA CSR using the root PKI backend // and updates the intermediate backend to use that new certificate. func (v *VaultProvider) GenerateIntermediate() (string, error) { - csr, err := v.generateIntermediateCSR() + csr, keyId, err := v.generateIntermediateCSR() if err != nil { return "", err } @@ -551,16 +563,81 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) { } // Set the intermediate backend to use the new certificate. - _, err = v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{ + importResp, err := v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{ "certificate": intermediate.Data["certificate"], }) if err != nil { return "", err } + // Vault 1.11+ will return a non-nil response from intermediate/set-signed + if importResp != nil { + err := v.setDefaultIntermediateIssuer(importResp, keyId) + if err != nil { + return "", fmt.Errorf("failed to update default intermediate issuer: %w", err) + } + } + return v.ActiveIntermediate() } +// setDefaultIntermediateIssuer updates the default issuer for +// intermediate CA since Vault, as part of its 1.11+ support for +// multiple issuers, no longer overwrites the default issuer when +// generateIntermediateCSR (intermediate/generate/internal) is called. +// +// The response we get from calling [/intermediate/set-signed] +// should contain a "mapping" data field we can use to cross-reference +// with the keyId returned when calling [/intermediate/generate/internal]. +// +// [/intermediate/set-signed]: https://developer.hashicorp.com/vault/api-docs/secret/pki#import-ca-certificates-and-keys +// [/intermediate/generate/internal]: https://developer.hashicorp.com/vault/api-docs/secret/pki#generate-intermediate-csr +func (v *VaultProvider) setDefaultIntermediateIssuer(vaultResp *vaultapi.Secret, keyId string) error { + if vaultResp.Data["mapping"] == nil { + return fmt.Errorf("expected Vault response data to have a 'mapping' key") + } + if keyId == "" { + return fmt.Errorf("expected non-empty keyId") + } + + mapping, ok := vaultResp.Data["mapping"].(map[string]any) + if !ok { + return fmt.Errorf("unexpected type for 'mapping' value in Vault response") + } + + var intermediateId string + // The value in this KV pair is called "key" + for issuer, key := range mapping { + if key == keyId { + // Expect to find the key_id we got from Vault when we + // generated the intermediate CSR. + intermediateId = issuer + break + } + } + if intermediateId == "" { + return fmt.Errorf("could not find key_id %q in response from vault", keyId) + } + + // For Vault 1.11+ it is important to GET then POST to avoid clobbering fields + // like `default_follows_latest_issuer`. + // https://developer.hashicorp.com/vault/api-docs/secret/pki#default_follows_latest_issuer + resp, err := v.readNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"config/issuers") + if err != nil { + return fmt.Errorf("could not read from /config/issuers: %w", err) + } + issuersConf := resp.Data + // Overwrite the default issuer + issuersConf["default"] = intermediateId + + _, err = v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"config/issuers", issuersConf) + if err != nil { + return fmt.Errorf("could not write default issuer to /config/issuers: %w", err) + } + + return nil +} + // Sign calls the configured role in the intermediate PKI backend to issue // a new leaf certificate based on the provided CSR, with the issuing // intermediate CA cert attached. diff --git a/agent/connect/ca/provider_vault_test.go b/agent/connect/ca/provider_vault_test.go index 6dbf671d4cf4..797084c2e4f6 100644 --- a/agent/connect/ca/provider_vault_test.go +++ b/agent/connect/ca/provider_vault_test.go @@ -902,6 +902,28 @@ func TestVaultProvider_ReconfigureIntermediateTTL(t *testing.T) { require.Equal(t, 333*3600, mountConfig.MaxLeaseTTL) } +func TestVaultCAProvider_GenerateIntermediate(t *testing.T) { + + SkipIfVaultNotPresent(t) + + provider, _ := testVaultProviderWithConfig(t, true, nil) + + orig, err := provider.ActiveIntermediate() + require.NoError(t, err) + + // This test was created to ensure that our calls to Vault + // returns a new Intermediate certificate and further calls + // to ActiveIntermediate return the same new cert. + new, err := provider.GenerateIntermediate() + require.NoError(t, err) + + newActive, err := provider.ActiveIntermediate() + require.NoError(t, err) + + require.Equal(t, new, newActive) + require.NotEqual(t, orig, new) +} + func getIntermediateCertTTL(t *testing.T, caConf *structs.CAConfiguration) time.Duration { t.Helper() From 2fcb0438169b005dc798e91949ac16307d5e292f Mon Sep 17 00:00:00 2001 From: David Yu Date: Thu, 17 Nov 2022 13:42:06 -0800 Subject: [PATCH 10/16] docs: Consul K8s 1.0/Consul 1.14 GA Compat Matrix change (#15400) * docs: 1.0 GA Compat Matrix change --- website/content/docs/k8s/compatibility.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/content/docs/k8s/compatibility.mdx b/website/content/docs/k8s/compatibility.mdx index 84dbdd2a2d9d..84acd6771cc9 100644 --- a/website/content/docs/k8s/compatibility.mdx +++ b/website/content/docs/k8s/compatibility.mdx @@ -9,19 +9,19 @@ description: >- For every release of Consul on Kubernetes, a Helm chart, `consul-k8s-control-plane` binary and a `consul-k8s` CLI binary is built and distributed through a single version. When deploying via Helm, the recommended best path for upgrading Consul on Kubernetes, is to upgrade using the same `consul-k8s-control-plane` version as the Helm Chart, as the Helm Chart and Control Plane binary are tightly coupled. -## Supported Consul versions +## Supported Consul and Kubernetes versions Consul Kubernetes versions all of its components (`consul-k8s` CLI, `consul-k8s-control-plane`, and Helm chart) with a single semantic version. When installing or upgrading to a specific versions, ensure that you are using the correct Consul version with the compatible `consul-k8s` helm chart and/or CLI. -| Consul Version | Compatible consul-k8s Versions | -| -------------- | -------------------------------- | -| 1.13.x | 0.49.x | -| 1.12.x | 0.43.0 - 0.49.x | -| 1.11.x | 0.39.0 - 0.42.0, 0.44.0 - 0.49.x | +| Consul Version | Compatible consul-k8s Versions | Compatible Kubernetes Versions | +| -------------- | -------------------------------- | -------------------------------| +| 1.14.x | 1.0.x | 1.22.x - 1.25.x | +| 1.13.x | 0.49.x | 1.21.x - 1.24.x | +| 1.12.x | 0.43.0 - 0.49.x | 1.19.x - 1.22.x | ## Supported Envoy versions -Supported versions of Envoy for Consul versions are also found in [Envoy - Supported Versions](/docs/connect/proxies/envoy#supported-versions). The recommended best practice is to use the default version of Envoy that is provided in the Helm values.yml file, as that is the version that has been tested with the default Consul and Consul Kubernetes binaries for a given Helm chart. +Supported versions of Envoy and `consul-dataplane` (for Consul K8s 1.0 and above) for Consul versions are also found in [Envoy - Supported Versions](/docs/connect/proxies/envoy#supported-versions). Starting with `consul-k8s` 1.0, `consul-dataplane` will include a bundled version of Envoy. The recommended best practice is to use the default version of Envoy or `consul-dataplane` that is provided in the Helm `values.yaml` file, as that is the version that has been tested with the default Consul and Consul Kubernetes binaries for a given Helm chart. ## Vault as a Secrets Backend compatibility From 781f1499d7846e1dfb8dec7616e0deb67a03cec7 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Thu, 17 Nov 2022 15:56:41 -0600 Subject: [PATCH 11/16] docs: Include env vars in consul-dataplane reference (#15369) * docs: Include env vars in consul-dataplane reference * docs: Consul Dataplane bundles Envoy 1.24 * docs: Consul Dataplane is no longer beta --- .../connect/dataplane/consul-dataplane.mdx | 88 +++++++++---------- .../content/docs/connect/dataplane/index.mdx | 18 ++-- .../content/docs/connect/proxies/envoy.mdx | 4 +- 3 files changed, 52 insertions(+), 58 deletions(-) diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx index 0d46158ce550..66069f9bbb50 100644 --- a/website/content/docs/connect/dataplane/consul-dataplane.mdx +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -15,7 +15,7 @@ Usage: `consul-dataplane [options]` ### Requirements -Consul Dataplane requires servers running Consul version `v1.14-beta1+`. To find a specific version of Consul, refer to [Hashicorp's Official Release Channels](https://www.hashicorp.com/official-release-channels). +Consul Dataplane requires servers running Consul version `v1.14+`. To find a specific version of Consul, refer to [Hashicorp's Official Release Channels](https://www.hashicorp.com/official-release-channels). ### Startup @@ -44,50 +44,50 @@ The following options are required when starting `consul-dataplane` with the CLI ### Command Options -- `-addresses` - Consul server gRPC addresses. Can be a DNS name or an executable command. Refer to [go-netaddrs](https://github.com/hashicorp/go-netaddrs#summary) for details and examples. -- `-ca-certs` - The path to a file or directory containing CA certificates used to verify the server's certificate. -- `-consul-dns-bind-addr` - The address bound to the Consul DNS proxy. Default is `"127.0.0.1"`. -- `-consul-dns-bind-port` - The port that the Consul DNS proxy listens on. Default is `-1`, which disables the DNS proxy. -- `-credential-type` - The type of credentials used to authenticate with Consul servers, either `"static"` or `"login"`. -- `-envoy-admin-bind-address` - The address the Envoy admin server is available on. Default is `"127.0.0.1"`. -- `-envoy-admin-bind-port` - The port the Envoy admin server is available on. Default is `19000`. -- `-envoy-concurrency` - The number of worker threads that Envoy uses. Default is `2`. -- `-envoy-ready-bind-address` - The address Envoy's readiness probe is available on. -- `-envoy-ready-bind-port` - The port Envoy's readiness probe is available on. -- `-grpc-port` - The Consul server gRPC port to which `consul-dataplane` connects. Default is `8502`. -- `-log-json` - Enables log messages in JSON format. Default is `false`. -- `-log-level` - Log level of the messages to print. Available log levels are `"trace"`, `"debug"`, `"info"`, `"warn"`, and `"error"`. Default is `"info"`. -- `-login-auth-method` - The auth method used to log in. -- `-login-bearer-token` - The bearer token presented to the auth method. -- `-login-bearer-token-path` - The path to a file containing the bearer token presented to the auth method. -- `-login-datacenter` - The datacenter containing the auth method. -- `-login-meta` - A set of key/value pairs to attach to the ACL token. Each pair is formatted as `=`. This flag may be passed multiple times. -- `-login-namespace` - The Consul Enterprise namespace containing the auth method. -- `-login-partition` - The Consul Enterprise partition containing the auth method. -- `-proxy-service-id` - The proxy service instance's ID. -- `-server-watch-disabled` - Prevent `consul-dataplane` from consuming the server update stream. Use this flag when Consul servers are behind a load balancer. Default is `false`. -- `-service-namespace` - The Consul Enterprise namespace in which the proxy service instance is registered. -- `-service-node-id` - The ID of the Consul node to which the proxy service instance is registered. -- `-service-node-name` - The name of the Consul node to which the proxy service instance is registered. -- `-service-partition` - The Consul Enterprise partition in which the proxy service instance is registered. -- `-static-token` - The ACL token used to authenticate requests to Consul servers when `-credential-type` is set to `"static"`. -- `-telemetry-prom-ca-certs-path` - The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate. -- `-telemetry-prom-cert-file` - The path to the client certificate used to serve Prometheus metrics. -- `-telemetry-prom-key-file` - The path to the client private key used to serve Prometheus metrics. -- `-telemetry-prom-merge-port` - The local port used to serve merged Prometheus metrics. Default is `20100`. If your service instance uses the same default port, this flag must be set to a different port in order to avoid a port conflict. -- `-telemetry-prom-retention-time` - The duration for Prometheus metrics aggregation. Default is `1m0s`. Refer to [`prometheus_retention_time`](/docs/agent/config/config-files#telemetry-prometheus_retention_time) for details on setting this value. -- `-telemetry-prom-scrape-path` - The URL path where Envoy serves Prometheus metrics. Default is `"/metrics"`. -- `-telemetry-prom-service-metrics-url` - The URL where your service instance serves Prometheus metrics. If this is set, the metrics at this URL are included in Consul Dataplane's merged Prometheus metrics. -- `-telemetry-use-central-config` - Controls whether the proxy applies the central telemetry configuration. Default is `true`. -- `-tls-cert` - The path to a client certificate file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. -- `-tls-disabled` - Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production. Default is `false`. -- `-tls-insecure-skip-verify` - Do not verify the server's certificate. Useful for testing, but not recommended for production. Default is `false`. -- `-tls-key` - The path to a client private key file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. -- `-tls-server-name` - The hostname to expect in the server certificate's subject. This flag is required if `-addresses` is not a DNS name. +- `-addresses` - Consul server gRPC addresses. Can be a DNS name or an executable command. Accepted environment variable is `DP_CONSUL_ADDRESSES`. Refer to [go-netaddrs](https://github.com/hashicorp/go-netaddrs#summary) for details and examples. +- `-ca-certs` - The path to a file or directory containing CA certificates used to verify the server's certificate. Accepted environment variable is `DP_CA_CERTS`. +- `-consul-dns-bind-addr` - The address bound to the Consul DNS proxy. Default is `"127.0.0.1"`. Accepted environment variable is `DP_CONSUL_DNS_BIND_ADDR`. +- `-consul-dns-bind-port` - The port that the Consul DNS proxy listens on. Default is `-1`, which disables the DNS proxy. Accepted environment variable is `DP_CONSUL_DNS_BIND_PORT`. +- `-credential-type` - The type of credentials used to authenticate with Consul servers, either `"static"` or `"login"`. Accepted environment variable is `DP_CREDENTIAL_TYPE`. +- `-envoy-admin-bind-address` - The address the Envoy admin server is available on. Default is `"127.0.0.1"`. Accepted environment variable is `DP_ENVOY_ADMIN_BIND_ADDRESS`. +- `-envoy-admin-bind-port` - The port the Envoy admin server is available on. Default is `19000`. Accepted environment variable is `DP_ENVOY_ADMIN_BIND_PORT`. +- `-envoy-concurrency` - The number of worker threads that Envoy uses. Default is `2`. Accepted environment variable is `DP_ENVOY_CONCURRENCY`. +- `-envoy-ready-bind-address` - The address Envoy's readiness probe is available on. Accepted environment variable is `DP_ENVOY_READY_BIND_ADDRESS`. +- `-envoy-ready-bind-port` - The port Envoy's readiness probe is available on. Accepted environment variable is `DP_ENVOY_READY_BIND_PORT`. +- `-grpc-port` - The Consul server gRPC port to which `consul-dataplane` connects. Default is `8502`. Accepted environment variable is `DP_CONSUL_GRPC_PORT`. +- `-log-json` - Enables log messages in JSON format. Default is `false`. Accepted environment variable is `DP_LOG_JSON`. +- `-log-level` - Log level of the messages to print. Available log levels are `"trace"`, `"debug"`, `"info"`, `"warn"`, and `"error"`. Default is `"info"`. Accepted environment variable is `DP_LOG_LEVEL`. +- `-login-auth-method` - The auth method used to log in. Accepted environment variable is `DP_CREDENTIAL_LOGIN_AUTH_METHOD`. +- `-login-bearer-token` - The bearer token presented to the auth method. Accepted environment variable is `DP_CREDENTIAL_LOGIN_BEARER_TOKEN`. +- `-login-bearer-token-path` - The path to a file containing the bearer token presented to the auth method. Accepted environment variable is `DP_CREDENTIAL_LOGIN_BEARER_TOKEN_PATH`. +- `-login-datacenter` - The datacenter containing the auth method. Accepted environment variable is `DP_CREDENTIAL_LOGIN_DATACENTER`. +- `-login-meta` - A set of key/value pairs to attach to the ACL token. Each pair is formatted as `=`. This flag may be passed multiple times. Accepted environment variables are `DP_CREDENTIAL_LOGIN_META{1..9}`. +- `-login-namespace` - The Consul Enterprise namespace containing the auth method. Accepted environment variable is `DP_CREDENTIAL_LOGIN_NAMESPACE`. +- `-login-partition` - The Consul Enterprise partition containing the auth method. Accepted environment variable is `DP_CREDENTIAL_LOGIN_PARTITION`. +- `-proxy-service-id` - The proxy service instance's ID. Accepted environment variable is `DP_PROXY_SERVICE_ID`. +- `-proxy-service-id-path` - The path to a file containing the proxy service instance's ID. Accepted environment variable is `DP_PROXY_SERVICE_ID_PATH`. +- `-server-watch-disabled` - Prevent `consul-dataplane` from consuming the server update stream. Use this flag when Consul servers are behind a load balancer. Default is `false`. Accepted environment variable is `DP_SERVER_WATCH_DISABLED`. +- `-service-namespace` - The Consul Enterprise namespace in which the proxy service instance is registered. Accepted environment variable is `DP_SERVICE_NAMESPACE`. +- `-service-node-id` - The ID of the Consul node to which the proxy service instance is registered. Accepted environment variable is `DP_SERVICE_NODE_ID`. +- `-service-node-name` - The name of the Consul node to which the proxy service instance is registered. Accepted environment variable is `DP_SERVICE_NODE_NAME`. +- `-service-partition` - The Consul Enterprise partition in which the proxy service instance is registered. Accepted environment variable is `DP_SERVICE_PARTITION`. +- `-static-token` - The ACL token used to authenticate requests to Consul servers when `-credential-type` is set to `"static"`. Accepted environment variable is `DP_CREDENTIAL_STATIC_TOKEN`. +- `-telemetry-prom-ca-certs-path` - The path to a file or directory containing CA certificates used to verify the Prometheus server's certificate. Accepted environment variable is `DP_TELEMETRY_PROM_CA_CERTS_PATH`. +- `-telemetry-prom-cert-file` - The path to the client certificate used to serve Prometheus metrics. Accepted environment variable is `DP_TELEMETRY_PROM_CERT_FILE`. +- `-telemetry-prom-key-file` - The path to the client private key used to serve Prometheus metrics. Accepted environment variable is `DP_TELEMETRY_PROM_KEY_FILE`. +- `-telemetry-prom-merge-port` - The local port used to serve merged Prometheus metrics. Default is `20100`. If your service instance uses the same default port, this flag must be set to a different port in order to avoid a port conflict. Accepted environment variable is `DP_TELEMETRY_PROM_MERGE_PORT`. +- `-telemetry-prom-retention-time` - The duration for Prometheus metrics aggregation. Default is `1m0s`. Accepted environment variable is `DP_TELEMETRY_PROM_RETENTION_TIME`. Refer to [`prometheus_retention_time`](/docs/agent/config/config-files#telemetry-prometheus_retention_time) for details on setting this value. +- `-telemetry-prom-scrape-path` - The URL path where Envoy serves Prometheus metrics. Default is `"/metrics"`. Accepted environment variable is `DP_TELEMETRY_PROM_SCRAPE_PATH`. +- `-telemetry-prom-service-metrics-url` - The URL where your service instance serves Prometheus metrics. If this is set, the metrics at this URL are included in Consul Dataplane's merged Prometheus metrics. Accepted environment variable is `DP_TELEMETRY_PROM_SERVICE_METRICS_URL`. +- `-telemetry-use-central-config` - Controls whether the proxy applies the central telemetry configuration. Default is `true`. Accepted environment variable is `DP_TELEMETRY_USE_CENTRAL_CONFIG`. +- `-tls-cert` - The path to a client certificate file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. Accepted environment variable is `DP_TLS_CERT`. +- `-tls-disabled` - Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production. Default is `false`. Accepted environment variable is `DP_TLS_DISABLED`. +- `-tls-insecure-skip-verify` - Do not verify the server's certificate. Useful for testing, but not recommended for production. Default is `false`. `DP_TLS_INSECURE_SKIP_VERIFY`. +- `-tls-key` - The path to a client private key file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. Accepted environment variable is `DP_TLS_KEY`. +- `-tls-server-name` - The hostname to expect in the server certificate's subject. This flag is required if `-addresses` is not a DNS name. Accepted environment variable is `DP_TLS_SERVER_NAME`. - `-version` - Print the current version of `consul-dataplane`. -- `-xds-bind-addr` - The address the Envoy xDS server is available on. Default is `"127.0.0.1"`. -- `-xds-bind-port` - The port on which the Envoy xDS server is available. Default is `0`. When set - to `0`, an available port is selected at random. +- `-xds-bind-addr` - The address the Envoy xDS server is available on. Default is `"127.0.0.1"`. Accepted environment variable is `DP_XDS_BIND_ADDR`. +- `-xds-bind-port` - The port on which the Envoy xDS server is available. Default is `0`. When set to `0`, an available port is selected at random. Accepted environment variable is `DP_XDS_BIND_PORT`. ## Examples diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 58cdde728cc2..7e372b0a5275 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -7,11 +7,9 @@ description: >- # Simplified Service Mesh with Consul Dataplane -~> **Consul Dataplane is currently in beta:** Functionality associated with Consul Dataplane is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. - This topic provides an overview of Consul Dataplane, a lightweight process for managing Envoy proxies introduced in Consul v1.14.0. Consul Dataplane removes the need to run client agents on every node in a cluster for service discovery and service mesh. Instead, Consul deploys sidecar proxies that provide lower latency, support additional runtimes, and integrate with cloud infrastructure providers. -Consul Dataplane requires servers running Consul v1.14.0-beta1+ and Consul K8s v1.0.0-beta1+. +Consul Dataplane requires servers running Consul v1.14.0+ and Consul K8s v1.0.0+. ## What is Consul Dataplane? @@ -39,38 +37,36 @@ To get started with Consul Dataplane, use the following reference resources: ### Installation -To install the beta release of Consul Dataplane, set `VERSION` to `1.0.0-beta` and then follow the instructions to install a specific version of Consul [with the Helm Chart](/docs/k8s/installation/install#install-consul) or [with the Consul-k8s CLI](/docs/k8s/installation/install-cli#install-a-previous-version). +To install Consul Dataplane, set `VERSION` to `1.0.0` and then follow the instructions to install a specific version of Consul [with the Helm Chart](/docs/k8s/installation/install#install-consul) or [with the Consul-k8s CLI](/docs/k8s/installation/install-cli#install-a-previous-version). #### Helm ```shell-session -$ export VERSION=1.0.0-beta3 +$ export VERSION=1.0.0 $ helm install consul hashicorp/consul --set global.name=consul --version ${VERSION} --create-namespace --namespace consul ``` #### Consul-k8s CLI ```shell-session -$ export VERSION=1.0.0-beta3 && \ +$ export VERSION=1.0.0 && \ curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip ``` -## Beta release features +## Feature support -The beta release of Consul Dataplane supports the following features: +Consul Dataplane supports the following features: - Single and multi-cluster installations, including those with WAN federation, cluster peering, and admin partitions are supported. - Ingress, terminating, and mesh gateways are supported. - Running Consul service mesh in AWS Fargate and GKE Autopilot is supported. - xDS load balancing is supported. - Servers running in Kubernetes and servers external to Kubernetes are both supported. - -Integration with HCP Consul is being tested in an invitation-only closed beta. HCP Consul support for Dataplane will be available for all users in a future release. +- HCP Consul is supported. ### Technical Constraints Be aware of the following limitations and recommendations for Consul Dataplane: - Consul API Gateway is not currently supported. -- Transparent proxies are not supported. - Consul Dataplane is not supported on Windows. diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 1a0ffd086d32..109df9787a95 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -48,13 +48,11 @@ Envoy 1.16.x and older releases are no longer supported (see [HCSEC-2022-07](htt ### Envoy and Consul Dataplane -~> **Note:** Consul Dataplane is currently in beta. - Consul Dataplane is a feature introduced in Consul v1.14. Because each version of Consul Dataplane supports one specific version of Envoy, you must use the following versions of Consul, Consul Dataplane, and Envoy together. | Consul Version | Consul Dataplane Version | Bundled Envoy Version | | ------------------- | ------------------------ | ---------------------- | -| 1.14.x | 1.0.x | 1.23.x | +| 1.14.x | 1.0.x | 1.24.x | ## Getting Started From 26f9008808cd281129eb1b2feb367cb0f767ce20 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Thu, 17 Nov 2022 16:58:07 -0500 Subject: [PATCH 12/16] Update licensing docs to account for virtual agents. (#15398) * Update licensing docs to account for virtual agents. * Update website/content/docs/enterprise/license/overview.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/enterprise/license/overview.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/content/docs/enterprise/license/overview.mdx b/website/content/docs/enterprise/license/overview.mdx index 76070dd888b0..2528c11c652b 100644 --- a/website/content/docs/enterprise/license/overview.mdx +++ b/website/content/docs/enterprise/license/overview.mdx @@ -20,6 +20,9 @@ agent's configuration or environment. Also, prior to 1.10.0, server agents would the license between themselves. This no longer occurs and the license must be present on each server agent when it is started. +Consul Enterprise 1.14.0, when running on Kubernetes, removed client agents and replaced these with virtual agents. +Virtual agents are nodes that Consul service mesh services run on. HashiCorp uses virtual agents to determine license entitlements for customers on per-node licensing and pricing agreements. + -> Visit the [Enterprise License Tutorial](https://learn.hashicorp.com/tutorials/nomad/hashicorp-enterprise-license?utm_source=docs) for detailed steps on how to install the license key. ### Applying a License @@ -41,6 +44,8 @@ may also be licensed in the very same manner. However, to avoid the need to configure the license on many client agents and snapshot agents, those agents have the capability to retrieve the license automatically under the conditions described below. +Virtual agents do not need the license to run. + Updating the license for an agent depends on the method you used to apply the license. - **If you used the `CONSUL_LICENSE` environment variable**: After updating the environment variable, restart the affected agents. From 05f7b51c6a1abb189e3f311147d8d060508c3472 Mon Sep 17 00:00:00 2001 From: Nitya Dhanushkodi Date: Thu, 17 Nov 2022 14:26:14 -0800 Subject: [PATCH 13/16] generate helm docs (#15443) --- website/content/docs/k8s/helm.mdx | 465 ++++++++++++++++-------------- 1 file changed, 241 insertions(+), 224 deletions(-) diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 4a5206e54824..84cc28fdae29 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -28,7 +28,6 @@ Use these links to navigate to a particular top-level stanza. - [`ui`](#h-ui) - [`syncCatalog`](#h-synccatalog) - [`connectInject`](#h-connectinject) -- [`controller`](#h-controller) - [`meshGateway`](#h-meshgateway) - [`ingressGateways`](#h-ingressgateways) - [`terminatingGateways`](#h-terminatinggateways) @@ -62,28 +61,11 @@ Use these links to navigate to a particular top-level stanza. (see `-domain` (https://www.consul.io/docs/agent/config/cli-flags#_domain)) and the domain services synced from Consul into Kubernetes will have, e.g. `service-name.service.consul`. - - `peering` ((#v-global-peering)) - [Experimental] Configures the Cluster Peering feature. Requires Consul v1.13+ and Consul-K8s v0.45+. + - `peering` ((#v-global-peering)) - Configures the Cluster Peering feature. Requires Consul v1.14+ and Consul-K8s v1.0.0+. - `enabled` ((#v-global-peering-enabled)) (`boolean: false`) - If true, the Helm chart enables Cluster Peering for the cluster. This option enables peering controllers and allows use of the PeeringAcceptor and PeeringDialer CRDs for establishing service mesh peerings. - - `tokenGeneration` ((#v-global-peering-tokengeneration)) - - - `serverAddresses` ((#v-global-peering-tokengeneration-serveraddresses)) - - - `source` ((#v-global-peering-tokengeneration-serveraddresses-source)) (`string: ""`) - Source can be set to "","consul" or "static". - - "" is the default source. If servers are enabled, it will check if `server.exposeService` is enabled, and read - the addresses from that service to use as the peering token server addresses. If using admin partitions and - only Consul client agents are enabled, the addresses in `externalServers.hosts` and `externalServers.grpcPort` - will be used. - - "consul" will use the Consul advertise addresses in the peering token. - - "static" will use the addresses specified in `global.peering.tokenGeneration.serverAddresses.static`. - - - `static` ((#v-global-peering-tokengeneration-serveraddresses-static)) (`array: []`) - Static addresses must be formatted "hostname|ip:port" where the port is the Consul server(s)' grpc port. - - `adminPartitions` ((#v-global-adminpartitions)) - Enabling `adminPartitions` allows creation of Admin Partitions in Kubernetes clusters. It additionally indicates that you are running Consul Enterprise v1.11+ with a valid Consul Enterprise license. Admin partitions enables deploying services across partitions, while sharing @@ -97,27 +79,6 @@ Use these links to navigate to a particular top-level stanza. Changing the partition name would require an un-install and a re-install with the updated name. Must be "default" in the server cluster ie the Kubernetes cluster that the Consul server pods are deployed onto. - - `service` ((#v-global-adminpartitions-service)) - Partition service properties. - - - `type` ((#v-global-adminpartitions-service-type)) (`string: LoadBalancer`) - - - `nodePort` ((#v-global-adminpartitions-service-nodeport)) - Optionally set the nodePort value of the partition service if using a NodePort service. - If not set and using a NodePort service, Kubernetes will automatically assign - a port. - - - `rpc` ((#v-global-adminpartitions-service-nodeport-rpc)) (`integer: null`) - RPC node port - - - `serf` ((#v-global-adminpartitions-service-nodeport-serf)) (`integer: null`) - Serf node port - - - `https` ((#v-global-adminpartitions-service-nodeport-https)) (`integer: null`) - HTTPS node port - - - `annotations` ((#v-global-adminpartitions-service-annotations)) (`string: null`) - Annotations to apply to the partition service. - - ```yaml - annotations: | - "annotation-key": "annotation-value" - ``` - - `image` ((#v-global-image)) (`string: hashicorp/consul:`) - The name (and tag) of the Consul Docker image for clients and servers. This can be overridden per component. This should be pinned to a specific version tag, otherwise you may inadvertently upgrade your Consul version. @@ -196,16 +157,6 @@ Use these links to navigate to a particular top-level stanza. ``` and check the name of `metadata.name`. - - `consulSnapshotAgentRole` ((#v-global-secretsbackend-vault-consulsnapshotagentrole)) (`string: ""`) - The Vault role for the Consul client snapshot agent. - The role must be connected to the Consul client snapshot agent's service account. - The role must also have a policy with read capabilities for the snapshot agent config - defined by the `client.snapshotAgent.configSecret.secretName` value. - To discover the service account name of the Consul client, run - ```shell-session - $ helm template --show-only templates/client-snapshot-agent-serviceaccount.yaml --set client.snapshotAgent.enabled=true hashicorp/consul - ``` - and check the name of `metadata.name`. - - `manageSystemACLsRole` ((#v-global-secretsbackend-vault-managesystemaclsrole)) (`string: ""`) - A Vault role for the Consul `server-acl-init` job, which manages setting ACLs so that clients and components can obtain ACL tokens. The role must be connected to the `server-acl-init` job's service account. The role must also have a policy with read and write capabilities for the bootstrap, replication or partition tokens @@ -226,14 +177,14 @@ Use these links to navigate to a particular top-level stanza. ``` and check the name of `metadata.name`. - - `controllerRole` ((#v-global-secretsbackend-vault-controllerrole)) (`string: ""`) - The Vault role to read Consul controller's webhook's + - `controllerRole` ((#v-global-secretsbackend-vault-controllerrole)) (`string: ""`) - The Vault role to read Consul controller's webhook's CA and issue a certificate and private key. - A Vault policy must be created which grants issue capabilities to + A Vault policy must be created which grants issue capabilities to `global.secretsBackend.vault.controller.tlsCert.secretName`. - `connectInjectRole` ((#v-global-secretsbackend-vault-connectinjectrole)) (`string: ""`) - The Vault role to read Consul connect-injector webhook's CA and issue a certificate and private key. - A Vault policy must be created which grants issue capabilities to + A Vault policy must be created which grants issue capabilities to `global.secretsBackend.vault.connectInject.tlsCert.secretName`. - `consulCARole` ((#v-global-secretsbackend-vault-consulcarole)) (`string: ""`) - The Vault role for all Consul components to read the Consul's server's CA Certificate (unauthenticated). @@ -296,7 +247,7 @@ Use these links to navigate to a particular top-level stanza. - `controller` ((#v-global-secretsbackend-vault-controller)) - - `tlsCert` ((#v-global-secretsbackend-vault-controller-tlscert)) - Configuration to the Vault Secret that Kubernetes will use on + - `tlsCert` ((#v-global-secretsbackend-vault-controller-tlscert)) - Configuration to the Vault Secret that Kubernetes will use on Kubernetes CRD creation, deletion, and update, to get TLS certificates used issued from vault to send webhooks to the controller. @@ -312,14 +263,14 @@ Use these links to navigate to a particular top-level stanza. - `connectInject` ((#v-global-secretsbackend-vault-connectinject)) - - `caCert` ((#v-global-secretsbackend-vault-connectinject-cacert)) - Configuration to the Vault Secret that Kubernetes will use on + - `caCert` ((#v-global-secretsbackend-vault-connectinject-cacert)) - Configuration to the Vault Secret that Kubernetes will use on Kubernetes pod creation, deletion, and update, to get CA certificates used issued from vault to send webhooks to the ConnectInject. - `secretName` ((#v-global-secretsbackend-vault-connectinject-cacert-secretname)) (`string: null`) - The Vault secret path that contains the CA certificate for Connect Inject webhooks. - - `tlsCert` ((#v-global-secretsbackend-vault-connectinject-tlscert)) - Configuration to the Vault Secret that Kubernetes will use on + - `tlsCert` ((#v-global-secretsbackend-vault-connectinject-tlscert)) - Configuration to the Vault Secret that Kubernetes will use on Kubernetes pod creation, deletion, and update, to get TLS certificates used issued from vault to send webhooks to the ConnectInject. @@ -368,6 +319,7 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-global-tls-enabled)) (`boolean: false`) - If true, the Helm chart will enable TLS for Consul servers and clients and all consul-k8s-control-plane components, as well as generate certificate authority (optional) and server and client certificates. + This setting is required for [Cluster Peering](/docs/connect/cluster-peering/k8s). - `enableAutoEncrypt` ((#v-global-tls-enableautoencrypt)) (`boolean: false`) - If true, turns on the auto-encrypt feature on clients and servers. It also switches consul-k8s-control-plane components to retrieve the CA from the servers @@ -476,6 +428,20 @@ Use these links to navigate to a particular top-level stanza. - `secretKey` ((#v-global-acls-partitiontoken-secretkey)) (`string: null`) - The key within the Vault secret that holds the parition token. + - `tolerations` ((#v-global-acls-tolerations)) (`string: ""`) - tolerations configures the taints and tolerations for the server-acl-init + and server-acl-init-cleanup jobs. This should be a multi-line string matching the + Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + + - `nodeSelector` ((#v-global-acls-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + labels for the server-acl-init and server-acl-init-cleanup jobs pod assignment, formatted as a multi-line string. + + Example: + + ```yaml + nodeSelector: | + beta.kubernetes.io/arch: amd64 + ``` + - `enterpriseLicense` ((#v-global-enterpriselicense)) - This value refers to a Kubernetes or Vault secret that you have created that contains your enterprise license. It is required if you are using an enterprise binary. Defining it here applies it to your cluster once a leader @@ -516,7 +482,7 @@ Use these links to navigate to a particular top-level stanza. This address must be reachable from the Consul servers in the primary datacenter. This auth method will be used to provision ACL tokens for Consul components and is different from the one used by the Consul Service Mesh. - Please see the [Kubernetes Auth Method documentation](/docs/security/acl/auth-methods/kubernetes). + Please see the [Kubernetes Auth Method documentation](https://consul.io/docs/acl/auth-methods/kubernetes). You can retrieve this value from your `kubeconfig` by running: @@ -543,22 +509,6 @@ Use these links to navigate to a particular top-level stanza. Envoy metrics on port `20200` at the `/metrics` path and all gateway pods will have Prometheus scrape annotations. Only applicable if `global.metrics.enabled` is true. - - `consulSidecarContainer` ((#v-global-consulsidecarcontainer)) (`map`) - For connect-injected pods, the consul sidecar is responsible for metrics merging. For ingress/mesh/terminating - gateways, it additionally ensures the Consul services are always registered with their local Consul client. - - - `resources` ((#v-global-consulsidecarcontainer-resources)) (`map`) - Set default resources for consul sidecar. If null, that resource won't - be set. - These settings can be overridden on a per-pod basis via these annotations: - - - `consul.hashicorp.com/consul-sidecar-cpu-limit` - - `consul.hashicorp.com/consul-sidecar-cpu-request` - - `consul.hashicorp.com/consul-sidecar-memory-limit` - - `consul.hashicorp.com/consul-sidecar-memory-request` - - - `imageEnvoy` ((#v-global-imageenvoy)) (`string: envoyproxy/envoy-alpine:`) - The name (and tag) of the Envoy Docker image used for the - connect-injected sidecar proxies and mesh, terminating, and ingress gateways. - See https://www.consul.io/docs/connect/proxies/envoy for full compatibility matrix between Consul and Envoy. - - `imageConsulDataplane` ((#v-global-imageconsuldataplane)) (`string: hashicorp/consul-dataplane:`) - The name (and tag) of the consul-dataplane Docker image used for the connect-injected sidecar proxies and mesh, terminating, and ingress gateways. @@ -568,7 +518,7 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-global-openshift-enabled)) (`boolean: false`) - If true, the Helm chart will create necessary configuration for running its components on OpenShift. - - `consulAPITimeout` ((#v-global-consulapitimeout)) (`string: 5s`) - The time in seconds that the consul API client will wait for a response from + - `consulAPITimeout` ((#v-global-consulapitimeout)) (`string: 5s`) - The time in seconds that the consul API client will wait for a response from the API before cancelling the request. - `cloud` ((#v-global-cloud)) - Enables installing an HCP Consul self-managed cluster. @@ -577,9 +527,47 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-global-cloud-enabled)) (`boolean: false`) - If true, the Helm chart will enable the installation of an HCP Consul self-managed cluster. - - `secretName` ((#v-global-cloud-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the HCP cloud configuration. - It contains the HCP service principal client_id and client_secret as well - as the HCP resource_id. + - `resourceId` ((#v-global-cloud-resourceid)) - The name of the Kubernetes secret that holds the HCP resource id. + This is required when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-resourceid-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the resource id. + + - `secretKey` ((#v-global-cloud-resourceid-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the resource id. + + - `clientId` ((#v-global-cloud-clientid)) - The name of the Kubernetes secret that holds the HCP cloud client id. + This is required when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-clientid-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the client id. + + - `secretKey` ((#v-global-cloud-clientid-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the client id. + + - `clientSecret` ((#v-global-cloud-clientsecret)) - The name of the Kubernetes secret that holds the HCP cloud client secret. + This is required when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-clientsecret-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the client secret. + + - `secretKey` ((#v-global-cloud-clientsecret-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the client secret. + + - `apiHost` ((#v-global-cloud-apihost)) - The name of the Kubernetes secret that holds the HCP cloud client id. + This is optional when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-apihost-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the api hostname. + + - `secretKey` ((#v-global-cloud-apihost-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the api hostname. + + - `authUrl` ((#v-global-cloud-authurl)) - The name of the Kubernetes secret that holds the HCP cloud authorization url. + This is optional when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-authurl-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the authorization url. + + - `secretKey` ((#v-global-cloud-authurl-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the authorization url. + + - `scadaAddress` ((#v-global-cloud-scadaaddress)) - The name of the Kubernetes secret that holds the HCP cloud scada address. + This is optional when global.cloud.enabled is true. + + - `secretName` ((#v-global-cloud-scadaaddress-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the scada address. + + - `secretKey` ((#v-global-cloud-scadaaddress-secretkey)) (`string: null`) - The key within the Kubernetes secret that holds the scada address. ### server ((#h-server)) @@ -633,7 +621,7 @@ Use these links to navigate to a particular top-level stanza. Vault Secrets backend: If you are using Vault as a secrets backend, a Vault Policy must be created which allows `["create", "update"]` - capabilities on the PKI issuing endpoint, which is usually of the form `pki/issue/consul-server`. + capabilities on the PKI issuing endpoint, which is usually of the form `pki/issue/consul-server`. Please see the following guide for steps to generate a compatible certificate: https://learn.hashicorp.com/tutorials/consul/vault-pki-consul-secure-tls Note: when using TLS, both the `server.serverCert` and `global.tls.caCert` which points to the CA endpoint of this PKI engine @@ -669,13 +657,13 @@ Use these links to navigate to a particular top-level stanza. - `storageClass` ((#v-server-storageclass)) (`string: null`) - The StorageClass to use for the servers' StatefulSet storage. It must be able to be dynamically provisioned if you want the storage - to be automatically created. For example, to use + to be automatically created. For example, to use local(https://kubernetes.io/docs/concepts/storage/storage-classes/#local) storage classes, the PersistentVolumeClaims would need to be manually created. A `null` value will use the Kubernetes cluster's default StorageClass. If a default StorageClass does not exist, you will need to create one. - Refer to the [Read/Write Tuning](https://www.consul.io/docs/install/performance#read-write-tuning) - section of the Server Performance Requirements documentation for considerations + Refer to the [Read/Write Tuning](https://www.consul.io/docs/install/performance#read-write-tuning) + section of the Server Performance Requirements documentation for considerations around choosing a performant storage class. ~> **Note:** The [Reference Architecture](https://learn.hashicorp.com/tutorials/consul/reference-architecture#hardware-sizing-for-consul-servers) @@ -749,7 +737,7 @@ Use these links to navigate to a particular top-level stanza. --set 'server.disruptionBudget.maxUnavailable=0'` flag to the helm chart installation command because of a limitation in the Helm templating language. - - `extraConfig` ((#v-server-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/config/config-files) for Consul + - `extraConfig` ((#v-server-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul servers. This will be saved as-is into a ConfigMap that is read by the Consul server agents. This can be used to add additional configuration that isn't directly exposed by the chart. @@ -923,6 +911,39 @@ Use these links to navigate to a particular top-level stanza. feature, in case kubernetes cluster is behind egress http proxies. Additionally, it could be used to configure custom consul parameters. + - `snapshotAgent` ((#v-server-snapshotagent)) - Values for setting up and running snapshot agents + (https://consul.io/commands/snapshot/agent) + within the Consul clusters. They run as a sidecar with Consul servers. + + - `enabled` ((#v-server-snapshotagent-enabled)) (`boolean: false`) - If true, the chart will install resources necessary to run the snapshot agent. + + - `interval` ((#v-server-snapshotagent-interval)) (`string: 1h`) - Interval at which to perform snapshots. + See https://www.consul.io/commands/snapshot/agent#interval + + - `configSecret` ((#v-server-snapshotagent-configsecret)) - A Kubernetes or Vault secret that should be manually created to contain the entire + config to be used on the snapshot agent. + This is the preferred method of configuration since there are usually storage + credentials present. Please see Snapshot agent config (https://consul.io/commands/snapshot/agent#config-file-options) + for details. + + - `secretName` ((#v-server-snapshotagent-configsecret-secretname)) (`string: null`) - The name of the Kubernetes secret or Vault secret path that holds the snapshot agent config. + + - `secretKey` ((#v-server-snapshotagent-configsecret-secretkey)) (`string: null`) - The key within the Kubernetes secret or Vault secret key that holds the snapshot agent config. + + - `resources` ((#v-server-snapshotagent-resources)) (`map`) - The resource settings for snapshot agent pods. + + - `caCert` ((#v-server-snapshotagent-cacert)) (`string: null`) - Optional PEM-encoded CA certificate that will be added to the trusted system CAs. + Useful if using an S3-compatible storage exposing a self-signed certificate. + + Example: + + ```yaml + caCert: | + -----BEGIN CERTIFICATE----- + MIIC7jCCApSgAwIBAgIRAIq2zQEVexqxvtxP6J0bXAwwCgYIKoZIzj0EAwIwgbkx + ... + ``` + ### externalServers ((#h-externalservers)) - `externalServers` ((#v-externalservers)) - Configuration for Consul servers when the servers are running outside of Kubernetes. @@ -935,9 +956,10 @@ Use these links to navigate to a particular top-level stanza. - `hosts` ((#v-externalservers-hosts)) (`array: []`) - An array of external Consul server hosts that are used to make HTTPS connections from the components in this Helm chart. - Valid values include IPs, DNS names, or Cloud auto-join string. + Valid values include an IP, a DNS name, or an [exec=](https://github.com/hashicorp/go-netaddrs) string. The port must be provided separately below. - Note: `client.join` must also be set to the hosts that should be + Note: This slice can only contain a single element. + Note: If enabling clients, `client.join` must also be set to the hosts that should be used to join the cluster. In most cases, the `client.join` values should be the same, however, they may be different if you wish to use separate hosts for the HTTPS connections. @@ -968,6 +990,9 @@ Use these links to navigate to a particular top-level stanza. -o jsonpath="{.clusters[?(@.name=='')].cluster.server}" ``` + - `skipServerWatch` ((#v-externalservers-skipserverwatch)) (`boolean: false`) - If true, setting this prevents the consul-dataplane and consul-k8s components from watching the Consul servers for changes. This is + useful for situations where Consul servers are behind a load balancer. + ### client ((#h-client)) - `client` ((#v-client)) - Values that configure running a Consul client on Kubernetes nodes. @@ -1044,7 +1069,7 @@ Use these links to navigate to a particular top-level stanza. - `tlsInit` ((#v-client-containersecuritycontext-tlsinit)) (`map`) - The tls-init initContainer - - `extraConfig` ((#v-client-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/config/config-files) for Consul + - `extraConfig` ((#v-client-extraconfig)) (`string: {}`) - A raw string of extra JSON configuration (https://consul.io/docs/agent/options) for Consul clients. This will be saved as-is into a ConfigMap that is read by the Consul client agents. This can be used to add additional configuration that isn't directly exposed by the chart. @@ -1186,53 +1211,6 @@ Use these links to navigate to a particular top-level stanza. type: RollingUpdate ``` - - `snapshotAgent` ((#v-client-snapshotagent)) - Values for setting up and running snapshot agents - (https://consul.io/commands/snapshot/agent) - within the Consul clusters. They are required to be co-located with Consul clients, - so will inherit the clients' nodeSelector, tolerations and affinity. - - - `enabled` ((#v-client-snapshotagent-enabled)) (`boolean: false`) - If true, the chart will install resources necessary to run the snapshot agent. - - - `replicas` ((#v-client-snapshotagent-replicas)) (`integer: 2`) - The number of snapshot agents to run. - - - `interval` ((#v-client-snapshotagent-interval)) (`string: 1h`) - Interval at which to perform snapshots. - See https://www.consul.io/commands/snapshot/agent#interval - - - `configSecret` ((#v-client-snapshotagent-configsecret)) - A Kubernetes or Vault secret that should be manually created to contain the entire - config to be used on the snapshot agent. - This is the preferred method of configuration since there are usually storage - credentials present. Please see Snapshot agent config (https://consul.io/commands/snapshot/agent#config-file-options) - for details. - - - `secretName` ((#v-client-snapshotagent-configsecret-secretname)) (`string: null`) - The name of the Kubernetes secret or Vault secret path that holds the snapshot agent config. - - - `secretKey` ((#v-client-snapshotagent-configsecret-secretkey)) (`string: null`) - The key within the Kubernetes secret or Vault secret key that holds the snapshot agent config. - - - `serviceAccount` ((#v-client-snapshotagent-serviceaccount)) - - - `annotations` ((#v-client-snapshotagent-serviceaccount-annotations)) (`string: null`) - This value defines additional annotations for the snapshot agent service account. This should be formatted as a - multi-line string. - - ```yaml - annotations: | - "sample/annotation1": "foo" - "sample/annotation2": "bar" - ``` - - - `resources` ((#v-client-snapshotagent-resources)) (`map`) - The resource settings for snapshot agent pods. - - - `caCert` ((#v-client-snapshotagent-cacert)) (`string: null`) - Optional PEM-encoded CA certificate that will be added to the trusted system CAs. - Useful if using an S3-compatible storage exposing a self-signed certificate. - - Example: - - ```yaml - caCert: | - -----BEGIN CERTIFICATE----- - MIIC7jCCApSgAwIBAgIRAIq2zQEVexqxvtxP6J0bXAwwCgYIKoZIzj0EAwIwgbkx - ... - ``` - ### dns ((#h-dns)) - `dns` ((#v-dns)) - Configuration for DNS configuration within the Kubernetes cluster. @@ -1244,7 +1222,7 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-dns-enabled)) (`boolean: -`) - - `enableRedirection` ((#v-dns-enableredirection)) (`boolean: false`) - If true, services using Consul Connect will use Consul DNS + - `enableRedirection` ((#v-dns-enableredirection)) (`boolean: -`) - If true, services using Consul Connect will use Consul DNS for default DNS resolution. The DNS lookups fall back to the nameserver IPs listed in /etc/resolv.conf if not found in Consul. @@ -1356,15 +1334,15 @@ Use these links to navigate to a particular top-level stanza. will inherit from `global.metrics.enabled` value. - `provider` ((#v-ui-metrics-provider)) (`string: prometheus`) - Provider for metrics. See - https://www.consul.io/docs/agent/config/config-files#ui_config_metrics_provider + https://www.consul.io/docs/agent/options#ui_config_metrics_provider This value is only used if `ui.enabled` is set to true. - `baseURL` ((#v-ui-metrics-baseurl)) (`string: http://prometheus-server`) - baseURL is the URL of the prometheus server, usually the service URL. This value is only used if `ui.enabled` is set to true. - - `dashboardURLTemplates` ((#v-ui-dashboardurltemplates)) - Corresponds to https://www.consul.io/docs/agent/config/config-files#ui_config_dashboard_url_templates configuration. + - `dashboardURLTemplates` ((#v-ui-dashboardurltemplates)) - Corresponds to https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates configuration. - - `service` ((#v-ui-dashboardurltemplates-service)) (`string: ""`) - Sets https://www.consul.io/docs/agent/config/config-files#ui_config_dashboard_url_templates_service. + - `service` ((#v-ui-dashboardurltemplates-service)) (`string: ""`) - Sets https://www.consul.io/docs/agent/options#ui_config_dashboard_url_templates_service. ### syncCatalog ((#h-synccatalog)) @@ -1436,14 +1414,14 @@ Use these links to navigate to a particular top-level stanza. k8s services into. If the Consul namespace does not already exist, it will be created. This will be ignored if `mirroringK8S` is true. - - `mirroringK8S` ((#v-synccatalog-consulnamespaces-mirroringk8s)) (`boolean: false`) - If true, k8s services will be registered into a Consul namespace + - `mirroringK8S` ((#v-synccatalog-consulnamespaces-mirroringk8s)) (`boolean: true`) - If true, k8s services will be registered into a Consul namespace of the same name as their k8s namespace, optionally prefixed if `mirroringK8SPrefix` is set below. If the Consul namespace does not already exist, it will be created. Turning this on overrides the `consulDestinationNamespace` setting. `addK8SNamespaceSuffix` may no longer be needed if enabling this option. - If mirroring is enabled, avoid creating any Consul resources in the following - Kubernetes namespaces, as Consul currently reserves these namespaces for + If mirroring is enabled, avoid creating any Consul resources in the following + Kubernetes namespaces, as Consul currently reserves these namespaces for system use: "system", "universal", "operator", "root". - `mirroringK8SPrefix` ((#v-synccatalog-consulnamespaces-mirroringk8sprefix)) (`string: ""`) - If `mirroringK8S` is set to true, `mirroringK8SPrefix` allows each Consul namespace @@ -1558,7 +1536,7 @@ Use these links to navigate to a particular top-level stanza. - `enabled` ((#v-connectinject-enabled)) (`boolean: true`) - True if you want to enable connect injection. Set to "-" to inherit from global.enabled. - - `replicas` ((#v-connectinject-replicas)) (`integer: 2`) - The number of deployment replicas. + - `replicas` ((#v-connectinject-replicas)) (`integer: 1`) - The number of deployment replicas. - `image` ((#v-connectinject-image)) (`string: null`) - Image for consul-k8s-control-plane that contains the injector. @@ -1585,7 +1563,7 @@ Use these links to navigate to a particular top-level stanza. - `disruptionBudget` ((#v-connectinject-disruptionbudget)) - This configures the PodDisruptionBudget (https://kubernetes.io/docs/tasks/run-application/configure-pdb/) for the service mesh sidecar injector. - - `enabled` ((#v-connectinject-disruptionbudget-enabled)) (`boolean: true`) - This will enable/disable registering a PodDisruptionBudget for the + - `enabled` ((#v-connectinject-disruptionbudget-enabled)) (`boolean: true`) - This will enable/disable registering a PodDisruptionBudget for the service mesh sidecar injector. If this is enabled, it will only register the budget so long as the service mesh is enabled. @@ -1595,9 +1573,12 @@ Use these links to navigate to a particular top-level stanza. --set 'connectInject.disruptionBudget.maxUnavailable=0'` flag to the helm chart installation command because of a limitation in the Helm templating language. + - `minAvailable` ((#v-connectinject-disruptionbudget-minavailable)) (`integer: null`) - The minimum number of available pods. + Takes precedence over maxUnavailable if set. + - `cni` ((#v-connectinject-cni)) - Configures consul-cni plugin for Consul Service mesh services - - `enabled` ((#v-connectinject-cni-enabled)) (`boolean: false`) - If true, then all traffic redirection setup will use the consul-cni plugin. + - `enabled` ((#v-connectinject-cni-enabled)) (`boolean: false`) - If true, then all traffic redirection setup will use the consul-cni plugin. Requires connectInject.enabled to also be true. - `logLevel` ((#v-connectinject-cni-loglevel)) (`string: null`) - Log level for the installer and plugin. Overrides global.logLevel @@ -1614,7 +1595,7 @@ Use these links to navigate to a particular top-level stanza. - `multus` ((#v-connectinject-cni-multus)) (`string: false`) - If multus CNI plugin is enabled with consul-cni. When enabled, consul-cni will not be installed as a chained CNI plugin. Instead, a NetworkAttachementDefinition CustomResourceDefinition (CRD) will be created in the helm release namespace. Following multus plugin standards, an annotation is required in order for the consul-cni plugin - to be executed and for your service to be added to the Consul Service Mesh. + to be executed and for your service to be added to the Consul Service Mesh. Add the annotation `'k8s.v1.cni.cncf.io/networks': '[{ "name":"consul-cni","namespace": "consul" }]'` to your pod to use the default installed NetworkAttachementDefinition CRD. @@ -1647,6 +1628,18 @@ Use these links to navigate to a particular top-level stanza. type: RollingUpdate ``` + - `consulNode` ((#v-connectinject-consulnode)) + + - `meta` ((#v-connectinject-consulnode-meta)) (`map`) - meta specifies an arbitrary metadata key/value pair to associate with the node. + + Example: + + ```yaml + meta: + cluster: test-cluster + persistent: true + ``` + - `metrics` ((#v-connectinject-metrics)) - Configures metrics for Consul Connect services. All values are overridable via annotations on a per-pod basis. @@ -1655,18 +1648,18 @@ Use these links to navigate to a particular top-level stanza. add a listener on the Envoy sidecar to expose metrics. The exposed metrics will depend on whether metrics merging is enabled: - If metrics merging is enabled: - the Consul sidecar will run a merged metrics server + the consul-dataplane will run a merged metrics server combining Envoy sidecar and Connect service metrics, i.e. if your service exposes its own Prometheus metrics. - If metrics merging is disabled: the listener will just expose Envoy sidecar metrics. This will inherit from `global.metrics.enabled`. - - `defaultEnableMerging` ((#v-connectinject-metrics-defaultenablemerging)) (`boolean: false`) - Configures the Consul sidecar to run a merged metrics server + - `defaultEnableMerging` ((#v-connectinject-metrics-defaultenablemerging)) (`boolean: false`) - Configures the consul-dataplane to run a merged metrics server to combine and serve both Envoy and Connect service metrics. This feature is available only in Consul v1.10.0 or greater. - - `defaultMergedMetricsPort` ((#v-connectinject-metrics-defaultmergedmetricsport)) (`integer: 20100`) - Configures the port at which the Consul sidecar will listen on to return + - `defaultMergedMetricsPort` ((#v-connectinject-metrics-defaultmergedmetricsport)) (`integer: 20100`) - Configures the port at which the consul-dataplane will listen on to return combined metrics. This port only needs to be changed if it conflicts with the application's ports. @@ -1690,6 +1683,16 @@ Use these links to navigate to a particular top-level stanza. - `priorityClassName` ((#v-connectinject-priorityclassname)) (`string: ""`) - Optional priorityClassName. + - `extraLabels` ((#v-connectinject-extralabels)) (`map`) - Extra labels to attach to the connect inject pods. This should be a YAML map. + + Example: + + ```yaml + extraLabels: + labelKey: label-value + anotherLabelKey: another-label-value + ``` + - `annotations` ((#v-connectinject-annotations)) (`string: null`) - This value defines additional annotations for connect inject pods. This should be formatted as a multi-line string. @@ -1724,7 +1727,7 @@ Use these links to navigate to a particular top-level stanza. which can lead to hangs. In these environments it is recommend to use "Ignore" instead. This setting can be safely disabled by setting to "Ignore". - - `namespaceSelector` ((#v-connectinject-namespaceselector)) (`string`) - Selector for restricting the webhook to only specific namespaces. + - `namespaceSelector` ((#v-connectinject-namespaceselector)) (`string`) - Selector for restricting the webhook to only specific namespaces. Use with `connectInject.default: true` to automatically inject all pods in namespaces that match the selector. This should be set to a multiline string. See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector for more details. @@ -1776,12 +1779,12 @@ Use these links to navigate to a particular top-level stanza. k8s pods into. If the Consul namespace does not already exist, it will be created. This will be ignored if `mirroringK8S` is true. - - `mirroringK8S` ((#v-connectinject-consulnamespaces-mirroringk8s)) (`boolean: false`) - Causes k8s pods to be registered into a Consul namespace + - `mirroringK8S` ((#v-connectinject-consulnamespaces-mirroringk8s)) (`boolean: true`) - Causes k8s pods to be registered into a Consul namespace of the same name as their k8s namespace, optionally prefixed if `mirroringK8SPrefix` is set below. If the Consul namespace does not already exist, it will be created. Turning this on overrides the - `consulDestinationNamespace` setting. If mirroring is enabled, avoid creating any Consul - resources in the following Kubernetes namespaces, as Consul currently reserves these + `consulDestinationNamespace` setting. If mirroring is enabled, avoid creating any Consul + resources in the following Kubernetes namespaces, as Consul currently reserves these namespaces for system use: "system", "universal", "operator", "root". - `mirroringK8SPrefix` ((#v-connectinject-consulnamespaces-mirroringk8sprefix)) (`string: ""`) - If `mirroringK8S` is set to true, `mirroringK8SPrefix` allows each Consul namespace @@ -1868,70 +1871,16 @@ Use these links to navigate to a particular top-level stanza. - `initContainer` ((#v-connectinject-initcontainer)) (`map`) - The resource settings for the Connect injected init container. -### controller ((#h-controller)) - -- `controller` ((#v-controller)) - Controller handles config entry custom resources. - Requires consul >= 1.8.4. - ServiceIntentions require consul 1.9+. - - - `enabled` ((#v-controller-enabled)) (`boolean: true`) - Enables the controller for managing custom resources. - - - `replicas` ((#v-controller-replicas)) (`integer: 1`) - The number of deployment replicas. - - - `logLevel` ((#v-controller-loglevel)) (`string: ""`) - Log verbosity level. One of "debug", "info", "warn", or "error". - - - `serviceAccount` ((#v-controller-serviceaccount)) - - - `annotations` ((#v-controller-serviceaccount-annotations)) (`string: null`) - This value defines additional annotations for the controller service account. This should be formatted as a - multi-line string. - - ```yaml - annotations: | - "sample/annotation1": "foo" - "sample/annotation2": "bar" - ``` - - - `resources` ((#v-controller-resources)) (`map`) - The resource settings for controller pods. - - - `nodeSelector` ((#v-controller-nodeselector)) (`string: null`) - Optional YAML string to specify a nodeSelector config. - - - `tolerations` ((#v-controller-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. - - - `affinity` ((#v-controller-affinity)) (`string: null`) - Affinity Settings - This should be a multi-line string matching the affinity object - - - `priorityClassName` ((#v-controller-priorityclassname)) (`string: ""`) - Optional priorityClassName. - - - `aclToken` ((#v-controller-acltoken)) - Refers to a Kubernetes secret that you have created that contains - an ACL token for your Consul cluster which grants the controller process the correct - permissions. This is only needed if you are managing ACLs yourself (i.e. not using - `global.acls.manageSystemACLs`). - - If running Consul OSS, requires permissions: - ```hcl - operator = "write" - service_prefix "" { - policy = "write" - intentions = "write" - } - ``` - If running Consul Enterprise, talk to your account manager for assistance. - - - `secretName` ((#v-controller-acltoken-secretname)) (`string: null`) - The name of the Vault secret that holds the ACL token. - - - `secretKey` ((#v-controller-acltoken-secretkey)) (`string: null`) - The key within the Vault secret that holds the ACL token. - ### meshGateway ((#h-meshgateway)) -- `meshGateway` ((#v-meshgateway)) - Mesh Gateways enable Consul Connect to work across Consul datacenters. +- `meshGateway` ((#v-meshgateway)) - [Mesh Gateways](/docs/connect/gateways/mesh-gateway) enable Consul Connect to work across Consul datacenters. - - `enabled` ((#v-meshgateway-enabled)) (`boolean: false`) - If mesh gateways are enabled, a Deployment will be created that runs + - `enabled` ((#v-meshgateway-enabled)) (`boolean: false`) - If [mesh gateways](/docs/connect/gateways/mesh-gateway) are enabled, a Deployment will be created that runs gateways and Consul Connect will be configured to use gateways. - See https://www.consul.io/docs/connect/mesh_gateway.html - Requirements: consul 1.6.0+ if using - global.acls.manageSystemACLs. + This setting is required for [Cluster Peering](/docs/connect/cluster-peering/k8s). + Requirements: consul 1.6.0+ if using `global.acls.manageSystemACLs``. - - `replicas` ((#v-meshgateway-replicas)) (`integer: 2`) - Number of replicas for the Deployment. + - `replicas` ((#v-meshgateway-replicas)) (`integer: 1`) - Number of replicas for the Deployment. - `wanAddress` ((#v-meshgateway-wanaddress)) - What gets registered as WAN address for the gateway. @@ -2027,9 +1976,24 @@ Use these links to navigate to a particular top-level stanza. - `initServiceInitContainer` ((#v-meshgateway-initserviceinitcontainer)) (`map`) - The resource settings for the `service-init` init container. - - `affinity` ((#v-meshgateway-affinity)) (`string`) - By default, we set an anti-affinity so that two gateway pods won't be - on the same node. NOTE: Gateways require that Consul client agents are - also running on the nodes alongside each gateway pod. + - `affinity` ((#v-meshgateway-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + for mesh gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer + a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value + to the value in the example below. + + Example: + + ```yaml + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: {{ template "consul.name" . }} + release: "{{ .Release.Name }}" + component: mesh-gateway + topologyKey: kubernetes.io/hostname + ``` - `tolerations` ((#v-meshgateway-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. @@ -2085,7 +2049,7 @@ Use these links to navigate to a particular top-level stanza. include both the default annotations and any additional ones defined for a specific gateway. - - `replicas` ((#v-ingressgateways-defaults-replicas)) (`integer: 2`) - Number of replicas for each ingress gateway defined. + - `replicas` ((#v-ingressgateways-defaults-replicas)) (`integer: 1`) - Number of replicas for each ingress gateway defined. - `service` ((#v-ingressgateways-defaults-service)) - The service options configure the Service that fronts the gateway Deployment. @@ -2125,9 +2089,24 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-ingressgateways-defaults-resources)) (`map`) - Resource limits for all ingress gateway pods - - `affinity` ((#v-ingressgateways-defaults-affinity)) (`string`) - By default, we set an anti-affinity so that two of the same gateway pods - won't be on the same node. NOTE: Gateways require that Consul client agents are - also running on the nodes alongside each gateway pod. + - `affinity` ((#v-ingressgateways-defaults-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + for ingress gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer + a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value + to the value in the example below. + + Example: + + ```yaml + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: {{ template "consul.name" . }} + release: "{{ .Release.Name }}" + component: ingress-gateway + topologyKey: kubernetes.io/hostname + ``` - `tolerations` ((#v-ingressgateways-defaults-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. @@ -2178,7 +2157,7 @@ Use these links to navigate to a particular top-level stanza. `defaults`. Values defined here override the defaults except in the case of annotations where both will be applied. - - `name` ((#v-ingressgateways-gateways-name)) (`string: ingress-gateway`) + - `name` ((#v-ingressgateways-gateways-name)) (`string: ingress-gateway`) ### terminatingGateways ((#h-terminatinggateways)) @@ -2199,7 +2178,7 @@ Use these links to navigate to a particular top-level stanza. include both the default annotations and any additional ones defined for a specific gateway. - - `replicas` ((#v-terminatinggateways-defaults-replicas)) (`integer: 2`) - Number of replicas for each terminating gateway defined. + - `replicas` ((#v-terminatinggateways-defaults-replicas)) (`integer: 1`) - Number of replicas for each terminating gateway defined. - `extraVolumes` ((#v-terminatinggateways-defaults-extravolumes)) (`array`) - A list of extra volumes to mount. These will be exposed to Consul in the path `/consul/userconfig//`. @@ -2216,9 +2195,24 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-terminatinggateways-defaults-resources)) (`map`) - Resource limits for all terminating gateway pods - - `affinity` ((#v-terminatinggateways-defaults-affinity)) (`string`) - By default, we set an anti-affinity so that two of the same gateway pods - won't be on the same node. NOTE: Gateways require that Consul client agents are - also running on the nodes alongside each gateway pod. + - `affinity` ((#v-terminatinggateways-defaults-affinity)) (`string: null`) - This value defines the affinity (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity) + for terminating gateway pods. It defaults to `null` thereby allowing multiple gateway pods on each node. But if one would prefer + a mode which minimizes risk of the cluster becoming unusable if a node is lost, set this value + to the value in the example below. + + Example: + + ```yaml + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: {{ template "consul.name" . }} + release: "{{ .Release.Name }}" + component: terminating-gateway + topologyKey: kubernetes.io/hostname + ``` - `tolerations` ((#v-terminatinggateways-defaults-tolerations)) (`string: null`) - Optional YAML string to specify tolerations. @@ -2278,7 +2272,7 @@ Use these links to navigate to a particular top-level stanza. `defaults`. Values defined here override the defaults except in the case of annotations where both will be applied. - - `name` ((#v-terminatinggateways-gateways-name)) (`string: terminating-gateway`) + - `name` ((#v-terminatinggateways-gateways-name)) (`string: terminating-gateway`) ### apiGateway ((#h-apigateway)) @@ -2288,6 +2282,11 @@ Use these links to navigate to a particular top-level stanza. - `image` ((#v-apigateway-image)) (`string: null`) - Image to use for the api-gateway-controller pods and gateway instances + ~> **Note:** Using API Gateway <= 0.4 with external servers requires setting `client.enabled: true`. + + - `imageEnvoy` ((#v-apigateway-imageenvoy)) (`string: envoyproxy/envoy:`) - The name (and tag) of the Envoy Docker image used for the + apiGateway. For other Consul compoenents, imageEnvoy has been replaced with Consul Dataplane. + - `logLevel` ((#v-apigateway-loglevel)) (`string: info`) - Override global log verbosity level for api-gateway-controller pods. One of "debug", "info", "warn", or "error". - `managedGatewayClass` ((#v-apigateway-managedgatewayclass)) - Configuration settings for the optional GatewayClass installed by consul-k8s (enabled by default) @@ -2304,6 +2303,10 @@ Use these links to navigate to a particular top-level stanza. beta.kubernetes.io/arch: amd64 ``` + - `tolerations` ((#v-apigateway-managedgatewayclass-tolerations)) (`string: null`) - This value defines the tolerations that will be assigned to a gateway pod. + This should be a multi-line string matching the + Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + - `serviceType` ((#v-apigateway-managedgatewayclass-servicetype)) (`string: LoadBalancer`) - This value defines the type of service created for gateways (e.g. LoadBalancer, ClusterIP) - `useHostPorts` ((#v-apigateway-managedgatewayclass-usehostports)) (`boolean: false`) - This value toggles if the gateway ports should be mapped to host ports @@ -2315,8 +2318,9 @@ Use these links to navigate to a particular top-level stanza. Example: ```yaml - service: | - - external-dns.alpha.kubernetes.io/hostname + service: + annotations: | + - external-dns.alpha.kubernetes.io/hostname ``` - `deployment` ((#v-apigateway-managedgatewayclass-deployment)) (`map`) - This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways @@ -2366,6 +2370,9 @@ Use these links to navigate to a particular top-level stanza. beta.kubernetes.io/arch: amd64 ``` + - `tolerations` ((#v-apigateway-controller-tolerations)) (`string: null`) - This value defines the tolerations for api-gateway-controller pod, this should be a multi-line string matching the + Tolerations (https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/) array in a Pod spec. + - `service` ((#v-apigateway-controller-service)) - Configuration for the Service created for the api-gateway-controller - `annotations` ((#v-apigateway-controller-service-annotations)) (`string: null`) - Annotations to apply to the api-gateway-controller service. @@ -2388,6 +2395,16 @@ Use these links to navigate to a particular top-level stanza. This should be a multi-line string matching the Toleration array in a PodSpec. + - `nodeSelector` ((#v-webhookcertmanager-nodeselector)) (`string: null`) - This value defines `nodeSelector` (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector) + labels for the webhook-cert-manager pod assignment, formatted as a multi-line string. + + Example: + + ```yaml + nodeSelector: | + beta.kubernetes.io/arch: amd64 + ``` + ### prometheus ((#h-prometheus)) - `prometheus` ((#v-prometheus)) - Configures a demo Prometheus installation. From ad0cba9fd52cdfcdfac53eb973ae021c8465c8f0 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Thu, 17 Nov 2022 16:28:47 -0600 Subject: [PATCH 14/16] Improve language on 1.14 upgrade instructions. (#15412) --- website/content/docs/upgrading/upgrade-specific.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index f49b6a4a3dc9..ea23036642ce 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -46,8 +46,8 @@ The default value for the gRPC TLS port is 8503 for Consul servers. To disable t If you already use gRPC encryption, change the following fields to ensure compatibility: -+ Change `ports.grpc` to `ports.grpc_tls`. [visit ports documentation for details](/docs/agent/config/config-files#grpc_tls_port) -+ Change `addresses.grpc` to `addresses.grpc_tls`. [visit addresses documentation for details](/docs/agent/config/config-files#grpc_tls) ++ Change `ports.grpc` to `ports.grpc_tls`. Refer to the [`grpc_tls_port` documentation](/docs/agent/config/config-files#grpc_tls_port) for details. ++ Change `addresses.grpc` to `addresses.grpc_tls`. Refer to the [`grpc_tls` documentation](/docs/agent/config/config-files#grpc_tls) for details. #### Changes to peering From 0b711ec8a23a5c45f6ff88134335a048bf09f566 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:04:29 -0600 Subject: [PATCH 15/16] docs: Consul Dataplane updates for v.1.14.0 (#15384) * Consul Architecture update * Consul on Kubernetes architecture * Install Consul on Kubernetes with Helm updates * Vault as the Secrets Backend Data Integration * Kubernetes Service Mesh Overview * Terminating Gateways * Fully updated * Join external service to k8s * Consul on Kubernetes * Configure metrics for Consul on Kubernetes * Service Sync for Consul on Kubernetes * Custom Resource Definitions for Consul on k8s * Upgrading Consul on Kubernetes Components * Rolling Updates to TLS * Dataplanes diagram * Upgrade instructions * k8s architecture page updates * Update website/content/docs/k8s/connect/observability/metrics.mdx Co-authored-by: Riddhi Shah * Update website/content/docs/architecture/index.mdx * Update website/content/docs/k8s/connect/terminating-gateways.mdx * CRDs * updating version numbers * Updated example config * Image clean up * Apply suggestions from code review Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> * Update website/content/docs/k8s/architecture.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Riddhi Shah Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/architecture/index.mdx | 6 +- .../content/docs/connect/dataplane/index.mdx | 8 +- website/content/docs/k8s/architecture.mdx | 40 +--- website/content/docs/k8s/connect/index.mdx | 32 +-- .../k8s/connect/observability/metrics.mdx | 69 +++--- .../docs/k8s/connect/terminating-gateways.mdx | 28 +-- website/content/docs/k8s/crds/index.mdx | 93 +++----- .../clients-outside-kubernetes.mdx | 112 +++++---- .../vault/data-integration/index.mdx | 78 +++---- website/content/docs/k8s/index.mdx | 41 ++-- .../docs/k8s/installation/install-cli.mdx | 28 +-- .../content/docs/k8s/installation/install.mdx | 214 +++++++++--------- .../operations/tls-on-existing-cluster.mdx | 27 +-- website/content/docs/k8s/service-sync.mdx | 196 +++++++--------- website/content/docs/k8s/upgrade/index.mdx | 208 +++++------------ website/public/img/dataplanes-diagram.png | Bin 0 -> 171551 bytes 16 files changed, 476 insertions(+), 704 deletions(-) create mode 100644 website/public/img/dataplanes-diagram.png diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index 3a9101d275e9..d5b288e2ef01 100644 --- a/website/content/docs/architecture/index.mdx +++ b/website/content/docs/architecture/index.mdx @@ -2,7 +2,7 @@ layout: docs page_title: Consul Architecture description: >- - Consul datacenters consist of clusters of server agents (control plane) and client agents deployed alongside service instances (dataplane). Learn how these components and their different communication methods make Consul possible. + Consul datacenters consist of clusters of server agents (control plane) and client agents deployed alongside service instances (data plane). Learn how these components and their different communication methods make Consul possible. --- # Consul Architecture @@ -47,10 +47,12 @@ Consul servers establish consensus using the Raft algorithm on port `8300`. Refe ### Client agents -Consul clients report node and service health status to the Consul cluster. In a typical deployment, you must run client agents on every compute node in your datacenter. Clients use remote procedure calls (RPC) to interact with servers. By default, clients send RPC requests to the servers on port `8300`. +Consul clients report node and service health status to the Consul cluster. In a typical deployment, you must run client agents on every compute node in your datacenter. Clients use remote procedure calls (RPC) to interact with servers. By default, clients send RPC requests to the servers on port `8300`. There are no limits to the number of client agents or services you can use with Consul, but production deployments should distribute services across multiple Consul datacenters. Using a multi-datacenter deployment enhances infrastructure resilience and limits control plane issues. We recommend deploying a maximum of 5,000 client agents per datacenter. Some large organizations have deployed tens of thousands of client agents and hundreds of thousands of service instances across a multi-datacenter deployment. Refer to [Cross-datacenter requests](#cross-datacenter-requests) for additional information. +You can also run Consul with an alternate service mesh configuration that deploys Envoy proxies but not client agents. Refer to [Simplified Service Mesh with Consul Dataplanes](/docs/connect/dataplane) for more information. + ## LAN gossip pool Client and server agents participate in a LAN gossip pool so that they can distribute and perform node [health checks](/docs/discovery/checks). Agents in the pool propagate the health check information across the cluster. Agent gossip communication occurs on port `8301` using UDP. Agent gossip falls back to TCP if UDP is not available. Refer to [Gossip Protocol](/docs/architecture/gossip) for additional information. diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 7e372b0a5275..c5fc35465d1a 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -13,10 +13,12 @@ Consul Dataplane requires servers running Consul v1.14.0+ and Consul K8s v1.0.0+ ## What is Consul Dataplane? -In standard deployments, Consul uses a control plane that contains both *server agents* and *client agents*. Server agents maintain the service catalog and service mesh, including its security and consistency, while client agents manage communications between service instances, their sidecar proxies, and the servers. While this model is optimal for applications deployed on virtual machines or bare metal servers, orchestrators such as Kubernetes already include components called *kubelets* that support health checking and service location functions typically provided by the client agent. +In standard deployments, Consul uses a control plane that contains both _server agents_ and _client agents_. Server agents maintain the service catalog and service mesh, including its security and consistency, while client agents manage communications between service instances, their sidecar proxies, and the servers. While this model is optimal for applications deployed on virtual machines or bare metal servers, orchestrators such as Kubernetes already include components called _kubelets_ that support health checking and service location functions typically provided by the client agent. Consul Dataplane manages Envoy proxies and leaves responsibility for other functions to the orchestrator. As a result, it removes the need to run client agents on every pod. In addition, services no longer need to be reregistered to a local client agent after restarting a service instance, as a client agent’s lack of access to persistent data storage in Kubernetes deployments is no longer an issue. +![Diagram of Consul Dataplanes in Kubernetes deployment](/img/dataplanes-diagram.png) + ## Benefits **Fewer networking requirements**: Without client agents, Consul does not require bidirectional network connectivity across multiple protocols to enable gossip communication. Instead, it requires a single gRPC connection to the Consul servers, which significantly simplifies requirements for the operator. @@ -53,6 +55,10 @@ $ export VERSION=1.0.0 && \ curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip ``` +### Upgrading + +Before you upgrade Consul to a version that uses Consul Dataplane, you must edit your Helm chart so that client agents are removed from your deployments. Refer to [upgrading to Consul Dataplane](/docs/k8s/upgrade#upgrading-to-consul-dataplanes) for more information. + ## Feature support Consul Dataplane supports the following features: diff --git a/website/content/docs/k8s/architecture.mdx b/website/content/docs/k8s/architecture.mdx index 6c4da9b96b92..dba414b73f83 100644 --- a/website/content/docs/k8s/architecture.mdx +++ b/website/content/docs/k8s/architecture.mdx @@ -2,15 +2,17 @@ layout: docs page_title: Consul on Kubernetes Control Plane Architecture description: >- - When running on Kubernetes, Consul’s control plane architecture does not change significantly. Server agents are deployed as a StatefulSet with a persistent volume, while client agents run as a k8s DaemonSet with an exposed API port. + When running on Kubernetes, Consul’s control plane architecture does not change significantly. Server agents are deployed as a StatefulSet with a persistent volume, while client agents can run as a k8s DaemonSet with an exposed API port or be omitted with Consul Dataplanes. --- - # Architecture -This topic describes the architecture, components, and resources associated with Consul deployments to Kubernetes. Consul employs the same architectural design on Kubernetes as it does with other platforms (see [Architecture](/docs/architecture)), but Kubernetes provides additional benefits that make operating a Consul cluster easier. +This topic describes the architecture, components, and resources associated with Consul deployments to Kubernetes. Consul employs the same architectural design on Kubernetes as it does with other platforms, but Kubernetes provides additional benefits that make operating a Consul cluster easier. Refer to [Consul Architecture](/docs/architecture) for more general information on Consul's architecture. -Refer to the standard [production deployment guide](https://learn.hashicorp.com/consul/datacenter-deploy/deployment-guide) for important information, regardless of the deployment platform. +> **For more specific guidance:** +> - For guidance on datacenter design, refer to [Consul and Kubernetes Reference Architecture](/consul/tutorials/kubernetes-production/kubernetes-reference-architecture). +> - For step-by-step deployment guidance, refer to [Consul and Kubernetes Deployment Guide](/consul/tutorials/kubernetes-production/kubernetes-deployment-guide). +> - For non-Kubernetes guidance, refer to the standard [production deployment guide](/consul/tutorials/production-deploy/deployment-guide). ## Server Agents @@ -35,32 +37,12 @@ Volume Claims when a [StatefulSet is deleted](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-storage), so this must done manually when removing servers. -## Client Agents - -The client agents are run as a **DaemonSet**. This places one agent -(within its own pod) on each Kubernetes node. -The clients expose the Consul HTTP API via a static port (8500) -bound to the host port. This enables all other pods on the node to connect -to the node-local agent using the host IP that can be retrieved via the -Kubernetes downward API. See -[accessing the Consul HTTP API](/docs/k8s/installation/install#accessing-the-consul-http-api) -for an example. +## Consul Dataplane -We do not use a **NodePort** Kubernetes service because requests to node ports get randomly routed -to any pod in the service and we need to be able to route directly to the Consul -client running on our node. +By default, Consul on Kubernetes uses an alternate service mesh configuration that injects sidecars without client agents. _Consul Dataplane_ manages Envoy proxies and leaves responsibility for other functions to the orchestrator, which removes the need to run client agents on every pod. --> **Note:** There is no way to bind to a local-only -host port. Therefore, any other node can connect to the agent. This should be -considered for security. For a properly production-secured agent with TLS -and ACLs, this is safe. +![Diagram of Consul Dataplanes in Kubernetes deployment](/img/dataplanes-diagram.png) -We run Consul clients as a **DaemonSet** instead of running a client in each -application pod as a sidecar because this would turn -a pod into a "node" in Consul and also causes an explosion of resource usage -since every pod needs a Consul agent. Service registration should be handled via the -catalog syncing feature with Services rather than pods. +Refer to [Simplified Service Mesh with Consul Dataplanes](/docs/connect/dataplane/index) for more information. --> **Note:** Due to a limitation of anti-affinity rules with DaemonSets, -a client-mode agent runs alongside server-mode agents in Kubernetes. This -duplication wastes some resources, but otherwise functions perfectly fine. +Consul Dataplane is the default proxy manager in Consul on Kubernetes 1.14 and later. If you are on Consul 1.13 or older, refer to [upgrading to Consul Dataplane](/docs/k8s/upgrade#upgrading-to-consul-dataplanes) for specific upgrade instructions. \ No newline at end of file diff --git a/website/content/docs/k8s/connect/index.mdx b/website/content/docs/k8s/connect/index.mdx index 1549cdf23f89..4ba3e5272e6e 100644 --- a/website/content/docs/k8s/connect/index.mdx +++ b/website/content/docs/k8s/connect/index.mdx @@ -10,10 +10,9 @@ description: >- [Consul Service Mesh](/docs/connect) is a feature built into to Consul that enables automatic service-to-service authorization and connection encryption across your Consul services. Consul Service Mesh can be used with Kubernetes to secure pod -communication with other pods and external Kubernetes services. "Consul Connect" refers to the service mesh functionality within Consul and is used interchangeably with the name -"Consul Service Mesh." +communication with other pods and external Kubernetes services. _Consul Connect_ refers to the component in Consul that enables service mesh functionality. We sometimes use Connect to mean Consul service mesh throughout the documentation. -The Connect sidecar running Envoy can be automatically injected into pods in +Consul can automatically inject the sidecar running Envoy into pods in your cluster, making configuration for Kubernetes automatic. This functionality is provided by the [consul-k8s project](https://github.com/hashicorp/consul-k8s) and can be @@ -22,32 +21,11 @@ automatically installed and configured using the ## Usage -When the -[Connect injector is installed](/docs/k8s/connect#installation-and-configuration), -the Connect sidecar can be automatically added to all pods. This sidecar can both -accept and establish connections using Connect, enabling the pod to communicate -to clients and dependencies exclusively over authorized and encrypted -connections. +-> **Important:** As of consul-k8s `v0.26.0` and Consul Helm `v0.32.0`, a Kubernetes +service is required to run services on the Consul service mesh. --> **Note:** The examples in this section are valid and use -publicly available images. If you've installed the Connect injector, feel free -to run the examples in this section to try Connect with Kubernetes. -Please note the documentation below this section on how to properly install -and configure the Connect injector. +Installing Consul on Kubernetes with [`connect-inject` enabled](/docs/k8s/connect#installation-and-configuration) adds a sidecar to all pods. By default, it enables service mesh functionality with Consul Dataplane by injecting an Envoy proxy. You can also configure Consul to inject a client agent sidecar to connect to your service mesh. Refer to [Simplified Service Mesh with Consul Dataplane](/docs/connect/dataplane) for more information. -### Accepting Inbound Connections - -An example Deployment is shown below with Connect enabled to accept inbound -connections. Notice that the Deployment would still be fully functional without -Connect. Minimal to zero modifications are required to enable Connect in Kubernetes. -Notice also that even though we're using a Deployment here, the same configuration -would work on a Pod, a StatefulSet, or a DaemonSet. - -This Deployment specification starts a server that responds to any -HTTP request with the static text "hello world". - --> **Note:** As of consul-k8s `v0.26.0` and Consul Helm `v0.32.0`, having a Kubernetes -service is **required** to run services on the Consul Service Mesh. ```yaml apiVersion: v1 diff --git a/website/content/docs/k8s/connect/observability/metrics.mdx b/website/content/docs/k8s/connect/observability/metrics.mdx index a3b25e2223d9..8b80ed9e346a 100644 --- a/website/content/docs/k8s/connect/observability/metrics.mdx +++ b/website/content/docs/k8s/connect/observability/metrics.mdx @@ -7,20 +7,16 @@ description: >- # Configure Metrics for Consul on Kubernetes -Consul on Kubernetes integrates with Prometheus and Grafana to provide metrics for Consul Service Mesh. The metrics +Consul on Kubernetes integrates with Prometheus and Grafana to provide metrics for Consul service mesh. The metrics available are: -* Connect Service metrics -* Sidecar proxy metrics -* Consul agent metrics -* Ingress, Terminating and Mesh Gateway metrics +- Connect service metrics +- Sidecar proxy metrics +- Consul agent metrics +- Ingress, terminating, and mesh gateway metrics Specific sidecar proxy metrics can also be seen in the Consul UI Topology Visualization view. This section documents how to enable each of these. --> **Note:** Metrics will be supported in Consul-helm >= `0.31.0` and consul-k8s >= `0.25.0`. However, enabling the [metrics merging feature](#connect-service-and-sidecar-metrics-with-metrics-merging) with Helm value (`defaultEnableMerging`) or -annotation (`consul.hashicorp.com/enable-metrics-merging`) can only be used with Consul `1.10.0` and above. The -other metrics configuration can still be used before Consul `1.10.0`. - ## Connect Service and Sidecar Metrics with Metrics Merging Prometheus annotations are used to instruct Prometheus to scrape metrics from Pods. Prometheus annotations only support @@ -28,33 +24,40 @@ scraping from one endpoint on a Pod, so Consul on Kubernetes supports metrics me sidecar proxy metrics are merged into one endpoint. If there are no service metrics, it also supports just scraping the sidecar proxy metrics. + Connect service metrics can be configured with the Helm values nested under [`connectInject.metrics`](/docs/k8s/helm#v-connectinject-metrics). Metrics and metrics merging can be enabled by default for all connect-injected Pods with the following Helm values: + ```yaml connectInject: metrics: defaultEnabled: true # by default, this inherits from the value global.metrics.enabled defaultEnableMerging: true ``` + They can also be overridden on a per-Pod basis using the annotations `consul.hashicorp.com/enable-metrics` and `consul.hashicorp.com/enable-metrics-merging`. -~> In most cases, the default settings will be sufficient. If you are encountering issues with colliding ports or service +~> In most cases, the default settings are sufficient. If you encounter issues with colliding ports or service metrics not being merged, you may need to change the defaults. -The Prometheus annotations configure the endpoint to scrape the metrics from. As shown in the diagram, the annotations point to a listener on `0.0.0.0:20200` on the Envoy sidecar. This listener and the corresponding Prometheus annotations can be configured with the following Helm values (or overridden on a per-Pod basis with Consul annotations `consul.hashicorp.com/prometheus-scrape-port` and `consul.hashicorp.com/prometheus-scrape-path`): +The Prometheus annotations specify which endpoint to scrape the metrics from. The annotations point to a listener on `0.0.0.0:20200` on the Envoy sidecar. You can configure the listener and the corresponding Prometheus annotations using the following Helm values. Alternatively, you can specify the `consul.hashicorp.com/prometheus-scrape-port` and `consul.hashicorp.com/prometheus-scrape-path` Consul annotations to override them on a per-Pod basis: + ```yaml connectInject: metrics: defaultPrometheusScrapePort: 20200 defaultPrometheusScrapePath: "/metrics" ``` -Those Helm values will result in the following Prometheus annotations being automatically added to the Pod for scraping: + +The Helm values specified in the previous example result in the following Prometheus annotations being automatically added to the Pod for scraping: + ```yaml metadata: annotations: @@ -63,26 +66,24 @@ metadata: prometheus.io/port: "20200" ``` -When metrics alone are enabled, the listener in the diagram on `0.0.0.0:20200` would point directly at the sidecar -metrics endpoint, rather than the merged metrics endpoint. The Prometheus scraping annotations would stay the same. - -When metrics and metrics merging are *both* enabled, metrics are combined from the service and the sidecar proxy, and -exposed via a local server on the Consul sidecar for scraping. This endpoint is called the merged metrics endpoint and -defaults to `127.0.0.1:20100/stats/prometheus`. The listener will target the merged metrics endpoint in the above case. +When metrics and metrics merging are both enabled, metrics are combined from the service and the sidecar proxy, and +exposed through a local server on the Consul Dataplane sidecar for scraping. This endpoint is called the merged metrics endpoint and +defaults to `127.0.0.1:20100/stats/prometheus`. The listener targets the merged metrics endpoint in the above case. It can be configured with the following Helm values (or overridden on a per-Pod basis with -`consul.hashicorp.com/merged-metrics-port`): +`consul.hashicorp.com/merged-metrics-port`: + ```yaml connectInject: metrics: defaultMergedMetricsPort: 20100 ``` -The endpoint to scrape service metrics from can be configured only on a per-Pod basis via the Pod annotations `consul.hashicorp.com/service-metrics-port` and `consul.hashicorp.com/service-metrics-path`. If these are not configured, the service metrics port will default to the port used to register the service with Consul (`consul.hashicorp.com/connect-service-port`), which in turn defaults to the first port on the first container of the Pod. The service metrics path will default to `/metrics`. +The endpoint to scrape service metrics from can be configured only on a per-Pod basis with the Pod annotations `consul.hashicorp.com/service-metrics-port` and `consul.hashicorp.com/service-metrics-path`. If these are not configured, the service metrics port defaults to the port used to register the service with Consul (`consul.hashicorp.com/connect-service-port`), which in turn defaults to the first port on the first container of the Pod. The service metrics path defaults to `/metrics`. ## Consul Agent Metrics -Metrics from the Consul server and client Pods can be scraped via Prometheus by setting the field `global.metrics.enableAgentMetrics` to `true`. Additionally, one can configure the metrics retention time on the agents by configuring -the field `global.metrics.agentMetricsRetentionTime` which expects a duration and defaults to `"1m"`. This value must be greater than `"0m"` for the Consul servers and clients to emit metrics at all. As the Prometheus deployment currently does not support scraping TLS endpoints, agent metrics are currently *unsupported* when TLS is enabled. +Metrics from the Consul server Pods can be scraped with Prometheus by setting the field `global.metrics.enableAgentMetrics` to `true`. Additionally, one can configure the metrics retention time on the agents by configuring +the field `global.metrics.agentMetricsRetentionTime` which expects a duration and defaults to `"1m"`. This value must be greater than `"0m"` for the Consul servers to emit metrics at all. As the Prometheus deployment currently does not support scraping TLS endpoints, agent metrics are currently unsupported when TLS is enabled. ```yaml global: @@ -94,10 +95,10 @@ global: ## Gateway Metrics -Metrics from the Consul gateways, namely the Ingress Gateways, Terminating Gateways and the Mesh Gateways can be scraped -via Prometheus by setting the field `global.metrics.enableGatewayMetrics` to `true`. The gateways emit standard Envoy proxy -metrics. To ensure that the metrics are not exposed to the public internet (as Mesh and Ingress gateways can have public -IPs), their metrics endpoints are exposed on the Pod IP of the respective gateway instance, rather than on all +Metrics from the Consul ingress, terminating, and mesh gateways can be scraped +with Prometheus by setting the field `global.metrics.enableGatewayMetrics` to `true`. The gateways emit standard Envoy proxy +metrics. To ensure that the metrics are not exposed to the public internet, as mesh and ingress gateways can have public +IPs, their metrics endpoints are exposed on the Pod IP of the respective gateway instance, rather than on all interfaces on `0.0.0.0`. ```yaml @@ -109,16 +110,16 @@ global: ## Metrics in the UI Topology Visualization -Consul's built-in UI has a topology visualization for services part of the Consul Service Mesh. The topology visualization has the ability to fetch basic metrics from a metrics provider for each service and display those metrics as part of the [topology visualization](/docs/connect/observability/ui-visualization). +Consul's built-in UI has a topology visualization for services that are part of the Consul service mesh. The topology visualization has the ability to fetch basic metrics from a metrics provider for each service and display those metrics as part of the [topology visualization](/docs/connect/observability/ui-visualization). The diagram below illustrates how the UI displays service metrics for a sample application: ![UI Topology View](/img/ui-service-topology-view-hover.png) -The topology view is configured under `ui.metrics`. This will enable the Consul UI to query the provider specified by -`ui.metrics.provider` at the URL of the Prometheus server `ui.metrics.baseURL` to display sidecar proxy metrics for the -service. The UI will display some specific sidecar proxy Prometheus metrics when `ui.metrics.enabled` is `true` and -`ui.enabled` is true. The value of `ui.metrics.enabled` defaults to `"-"` which means it will inherit from the value of +The topology view is configured under `ui.metrics`. This configuration enables the Consul UI to query the provider specified by +`ui.metrics.provider` at the URL of the Prometheus server `ui.metrics.baseURL`, and then display sidecar proxy metrics for the +service. The UI displays some specific sidecar proxy Prometheus metrics when `ui.metrics.enabled` is `true` and +`ui.enabled` is true. The value of `ui.metrics.enabled` defaults to `"-"` which means it inherits from the value of `global.metrics.enabled.` ```yaml @@ -132,13 +133,13 @@ ui: ## Deploying Prometheus (_for demo and non-production use-cases only_) -The Helm chart contains demo manifests for deploying Prometheus. It can be installed with Helm via `prometheus.enabled`. This manifest is based on the community manifest for Prometheus. +The Helm chart contains demo manifests for deploying Prometheus. It can be installed with Helm with `prometheus.enabled`. This manifest is based on the community manifest for Prometheus. The Prometheus deployment is designed to allow quick bootstrapping for trial and demo use cases, and is not recommended for production use-cases. -Prometheus will be installed in the same namespace as Consul, and will be installed +Prometheus is be installed in the same namespace as Consul, and gets installed and uninstalled along with the Consul installation. -Grafana can optionally be utilized with Prometheus to display metrics. The installation and configuration of Grafana must be managed separately from the Consul Helm chart. The [Layer 7 Observability with Prometheus, Grafana, and Kubernetes](https://learn.hashicorp.com/tutorials/consul/kubernetes-layer7-observability?in=consul/kubernetes?in=consul/kubernetes)) tutorial provides an installation walkthrough using Helm. +Grafana can optionally be utilized with Prometheus to display metrics. The installation and configuration of Grafana must be managed separately from the Consul Helm chart. The [Layer 7 Observability with Prometheus, Grafana, and Kubernetes](/consul/tutorials/kubernetes/kubernetes-layer7-observability) tutorial provides an installation walkthrough using Helm. ```yaml prometheus: diff --git a/website/content/docs/k8s/connect/terminating-gateways.mdx b/website/content/docs/k8s/connect/terminating-gateways.mdx index b8b4e60c81df..517aa9a45429 100644 --- a/website/content/docs/k8s/connect/terminating-gateways.mdx +++ b/website/content/docs/k8s/connect/terminating-gateways.mdx @@ -9,18 +9,18 @@ description: >- Adding a terminating gateway is a multi-step process: -- Update the Helm chart with terminating gateway config options +- Update the Helm chart with terminating gateway configuration options - Deploy the Helm chart - Access the Consul agent - Register external services with Consul ## Requirements -- [Consul](https://www.consul.io/docs/install#install-consul) +- [Consul](/docs/install#install-consul) - [Consul on Kubernetes CLI](/docs/k8s/k8s-cli) - Familiarity with [Terminating Gateways](/docs/connect/gateways/terminating-gateway) -## Update the Helm chart with terminating gateway config options +## Update the Helm chart with terminating gateway configuration options Minimum required Helm options: @@ -29,10 +29,6 @@ Minimum required Helm options: ```yaml global: name: consul -connectInject: - enabled: true -controller: - enabled: true terminatingGateways: enabled: true ``` @@ -49,9 +45,10 @@ $ consul-k8s install -f values.yaml ## Accessing the Consul agent -You can access the Consul server directly from your host via `kubectl port-forward`. This is helpful for interacting with your Consul UI locally as well as for validating the connectivity of the application. +You can access the Consul server directly from your host by running `kubectl port-forward`. This is helpful for interacting with your Consul UI locally as well as for validating the connectivity of the application. + ```shell-session @@ -62,6 +59,7 @@ $ kubectl port-forward consul-server-0 8500 & $ export CONSUL_HTTP_ADDR=http://localhost:8500 ``` + If TLS is enabled use port 8501: @@ -75,6 +73,7 @@ $ export CONSUL_HTTP_ADDR=https://localhost:8501 $ export CONSUL_HTTP_SSL_VERIFY=false ``` + If ACLs are enabled also set: @@ -116,7 +115,7 @@ The following table describes traffic behaviors when using the `destination` fie | L7 | Hostname | No | Allowed | A `Host` or `:authority` header is required. | | L7 | IP | No | Allowed | There are no limitations on dialing IPs without TLS. | -You can provide a `caFile` to secure traffic between unencrypted clients that connect to external services through the terminating gateway. +You can provide a `caFile` to secure traffic that connect to external services through the terminating gateway. Refer to [Create the configuration entry for the terminating gateway](#create-the-configuration-entry-for-the-terminating-gateway) for details. -> **Note:** Regardless of the `protocol` specified in the `ServiceDefaults`, [L7 intentions](/docs/connect/config-entries/service-intentions#permissions) are not currently supported with `ServiceDefaults` destinations. @@ -149,11 +148,12 @@ $ kubectl apply --filename service-defaults.yaml All other terminating gateway operations can use the name of the `ServiceDefaults` component, in this case "example-https", as a Consul service name. + -Normally, Consul services are registered with the Consul client on the node that -they're running on. Since this is an external service, there is no Consul node -to register it onto. Instead, we will make up a node name and register the +Normally, Consul services are registered on the node that +they're running on. Since this service is an external service, there is no Consul node +to register it onto. Instead, we must make up a node name and register the service to that node. Create a sample external service and register it with Consul. @@ -194,7 +194,7 @@ $ curl --request PUT --data @external.json --insecure $CONSUL_HTTP_ADDR/v1/catal true ``` -If ACLs and TLS are enabled : +If ACLs and TLS are enabled: ```shell-session $ curl --request PUT --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" --data @external.json --insecure $CONSUL_HTTP_ADDR/v1/catalog/register @@ -275,7 +275,7 @@ spec: If TLS is enabled for external services registered through the Consul catalog and you are not using [transparent proxy `destination`](#register-an-external-service-as-a-destination), you must include the [`caFile`](/docs/connect/config-entries/terminating-gateway#cafile) parameter that points to the system trust store of the terminating gateway container. By default, the trust store is located in the `/etc/ssl/certs/ca-certificates.crt` directory. -Configure the [`caFile`](https://www.consul.io/docs/connect/config-entries/terminating-gateway#cafile) parameter in the `TerminatingGateway` config entry to point to the `/etc/ssl/cert.pem` directory if TLS is enabled and you are using one of the following components: +Configure the [`caFile`](/docs/connect/config-entries/terminating-gateway#cafile) parameter in the `TerminatingGateway` config entry to point to the `/etc/ssl/cert.pem` directory if TLS is enabled and you are using one of the following components: - Consul Helm chart 0.43 or older - An Envoy image with an alpine base image diff --git a/website/content/docs/k8s/crds/index.mdx b/website/content/docs/k8s/crds/index.mdx index 4e15ef36d1c4..7291df74d369 100644 --- a/website/content/docs/k8s/crds/index.mdx +++ b/website/content/docs/k8s/crds/index.mdx @@ -8,7 +8,7 @@ description: >- # Custom Resource Definitions (CRDs) for Consul on Kubernetes This topic describes how to manage Consul [configuration entries](/docs/agent/config-entries) -via Kubernetes Custom Resources. Configuration entries provide cluster-wide defaults for the service mesh. +with Kubernetes Custom Resources. Configuration entries provide cluster-wide defaults for the service mesh. ## Requirements @@ -50,37 +50,15 @@ Hang tight while we grab the latest from your chart repositories... Update Complete. ⎈Happy Helming!⎈ ``` -Next, you must configure consul-helm via your `values.yaml` to install the custom resource definitions -and enable the controller that acts on them: - - - -```yaml -global: - name: consul - -controller: - enabled: true - -connectInject: - enabled: true -``` - - - -Note that: - -1. `controller.enabled: true` installs the CRDs and enables the controller. -1. Configuration entries are used to configure Consul service mesh so it's also - expected that `connectInject` will be enabled. - -See [Install with Helm Chart](/docs/k8s/installation/install) for further installation +Refer to [Install with Helm Chart](/docs/k8s/installation/install) for further installation instructions. +**Note**: Configuration entries require `connectInject` to be enabled, which is a default behavior in the official Helm Chart. If you disabled this setting, you must re-enable it to use CRDs. + ## Upgrading An Existing Cluster to CRDs If you have an existing Consul cluster running on Kubernetes you may need to perform -extra steps to migrate to CRDs. See [Upgrade An Existing Cluster to CRDs](/docs/k8s/crds/upgrade-to-crds) for full instructions. +extra steps to migrate to CRDs. Refer to [Upgrade An Existing Cluster to CRDs](/docs/k8s/crds/upgrade-to-crds) for full instructions. ## Usage @@ -88,7 +66,7 @@ Once installed, you can use `kubectl` to create and manage Consul's configuratio ### Create -You can create configuration entries via `kubectl apply`. +You can create configuration entries with `kubectl apply`. ```shell-session $ cat < protocol: tcp servicedefaults.consul.hashicorp.com/foo edited @@ -171,11 +149,11 @@ Error from server (NotFound): servicedefaults.consul.hashicorp.com "foo" not fou If running `kubectl delete` hangs without exiting, there may be a dependent configuration entry registered with Consul that prevents the target configuration entry from being -deleted. For example, if you set the protocol of your service to `http` via `ServiceDefaults` and then -create a `ServiceSplitter`, you won't be able to delete the `ServiceDefaults`. +deleted. For example, if you set the protocol of your service to `http` in `ServiceDefaults` and then +create a `ServiceSplitter`, you will not be able to delete `ServiceDefaults`. This is because by deleting the `ServiceDefaults` config, you are setting the -protocol back to the default which is `tcp`. Since `ServiceSplitter` requires +protocol back to the default which is `tcp`. Because `ServiceSplitter` requires that the service has an `http` protocol, Consul will not allow the `ServiceDefaults` to be deleted since that would put Consul into a broken state. @@ -188,7 +166,7 @@ the `ServiceSplitter`. Consul Open Source (Consul OSS) ignores Kubernetes namespaces and registers all services into the same global Consul registry based on their names. For example, service `web` in Kubernetes namespace -`web-ns` and service `admin` in Kubernetes namespace `admin-ns` will be registered into +`web-ns` and service `admin` in Kubernetes namespace `admin-ns` are registered into Consul as `web` and `admin` with the Kubernetes source namespace ignored. When creating custom resources to configure these services, the namespace of the @@ -213,8 +191,7 @@ metadata: spec: ... ``` -~> **NOTE:** If two custom resources of the same kind **and** the same name are attempted to -be created in different Kubernetes namespaces, the last one created will not be synced. +~> **Note:** If you create two custom resources with identical `kind` and `name` values in different Kubernetes namespaces, the last one you create is not able to sync. #### ServiceIntentions Special Case @@ -271,25 +248,24 @@ spec: -~> **NOTE:** If two `ServiceIntentions` resources set the same `spec.destination.name`, the -last one created will not be synced. +~> **Note:** If two `ServiceIntentions` resources set the same `spec.destination.name`, the +last one created is not synced. ### Consul Enterprise Consul Enterprise supports multiple configurations for how Kubernetes namespaces are mapped to Consul namespaces. The Consul namespace that the custom resource is registered into depends on the configuration being used but in general, you should create your -custom resources in the same Kubernetes namespace as the service they're configuring and -everything will work as expected. +custom resources in the same Kubernetes namespace as the service they configure. The details on each configuration are: -1. **Mirroring** - The Kubernetes namespace will be "mirrored" into Consul, i.e. - service `web` in Kubernetes namespace `web-ns` will be registered as service `web` +1. **Mirroring** - The Kubernetes namespace is mirrored into Consul. For example, the + service `web` in Kubernetes namespace `web-ns` is registered as service `web` in the Consul namespace `web-ns`. In the same vein, a `ServiceDefaults` custom resource with - name `web` in Kubernetes namespace `web-ns` will configure that same service. + name `web` in Kubernetes namespace `web-ns` configures that same service. - This is configured via [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): + This is configured with [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): @@ -305,13 +281,12 @@ The details on each configuration are: -1. **Mirroring with prefix** - The Kubernetes namespace will be "mirrored" into Consul - with a prefix added to the Consul namespace, i.e. - if the prefix is `k8s-` then service `web` in Kubernetes namespace `web-ns` will be registered as service `web` +1. **Mirroring with prefix** - The Kubernetes namespace is mirrored into Consul + with a prefix added to the Consul namespace. For example, if the prefix is `k8s-` then service `web` in Kubernetes namespace `web-ns` will be registered as service `web` in the Consul namespace `k8s-web-ns`. In the same vein, a `ServiceDefaults` custom resource with - name `web` in Kubernetes namespace `web-ns` will configure that same service. + name `web` in Kubernetes namespace `web-ns` configures that same service. - This is configured via [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): + This is configured with [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): @@ -329,17 +304,16 @@ The details on each configuration are: 1. **Single destination namespace** - The Kubernetes namespace is ignored and all services - will be registered into the same Consul namespace, i.e. if the destination Consul - namespace is `my-ns` then service `web` in Kubernetes namespace `web-ns` will - be registered as service `web` in Consul namespace `my-ns`. + are registered into the same Consul namespace. For example, if the destination Consul + namespace is `my-ns` then service `web` in Kubernetes namespace `web-ns` is registered as service `web` in Consul namespace `my-ns`. In this configuration, the Kubernetes namespace of the custom resource is ignored. For example, a `ServiceDefaults` custom resource with the name `web` in Kubernetes - namespace `admin-ns` will configure the service with name `web` even though that + namespace `admin-ns` configures the service with name `web` even though that service is running in Kubernetes namespace `web-ns` because the `ServiceDefaults` resource ends up registered into the same Consul namespace `my-ns`. - This is configured via [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): + This is configured with [`connectInject.consulNamespaces`](/docs/k8s/helm#v-connectinject-consulnamespaces): @@ -355,13 +329,12 @@ The details on each configuration are: - ~> **NOTE:** In this configuration, if two custom resources of the same kind **and** the same name are attempted to - be created in two Kubernetes namespaces, the last one created will not be synced. + ~> **Note:** In this configuration, if two custom resources are created in two Kubernetes namespaces with identical `name` and `kind` values, the last one created is not synced. #### ServiceIntentions Special Case (Enterprise) `ServiceIntentions` are different from the other custom resources because the -name of the resource doesn't matter. For other resources, the name of the resource +name of the resource does not matter. For other resources, the name of the resource determines which service it configures. For example, this resource configures the service `web`: @@ -379,7 +352,7 @@ spec: For `ServiceIntentions`, because we need to support the ability to create -wildcard intentions (e.g. `foo => * (allow)` meaning that `foo` can talk to **any** service), +wildcard intentions (e.g. `foo => * (allow)` meaning that `foo` can talk to any service), and because `*` is not a valid Kubernetes resource name, we instead use the field `spec.destination.name` to configure the destination service for the intention: @@ -415,5 +388,5 @@ spec: In addition, we support the field `spec.destination.namespace` to configure the destination service's Consul namespace. If `spec.destination.namespace` -is empty, then the Consul namespace used will be the same as the other +is empty, then the Consul namespace used is the same as the other config entries as outlined above. diff --git a/website/content/docs/k8s/deployment-configurations/clients-outside-kubernetes.mdx b/website/content/docs/k8s/deployment-configurations/clients-outside-kubernetes.mdx index c978da82e8b1..eebd2dd81d35 100644 --- a/website/content/docs/k8s/deployment-configurations/clients-outside-kubernetes.mdx +++ b/website/content/docs/k8s/deployment-configurations/clients-outside-kubernetes.mdx @@ -1,80 +1,65 @@ --- layout: docs -page_title: Join External Clients to Consul on Kubernetes +page_title: Join External Services to Consul on Kubernetes description: >- - Client agents running on VMs can join a Consul datacenter running on Kubernetes. Configure the Kubernetes installation to accept communication from external clients. + Services running on a virtual machine (VM) can join a Consul datacenter running on Kubernetes. Learn how to configure the Kubernetes installation to accept communication from external services. --- -# Join External Clients to Consul on Kubernetes +# Join External Services to Consul on Kubernetes -Consul clients running on non-Kubernetes nodes can join a Consul cluster running within Kubernetes. - -## Networking - -Within one datacenter, Consul typically requires a fully connected -[network](/docs/architecture). This means the IPs of every client and server -agent should be routable by every other client and server agent in the -datacenter. Clients need to be able to [gossip](/docs/architecture/gossip) with -every other agent and make RPC calls to servers. Servers need to be able to -gossip with every other agent. See [Architecture](/docs/architecture) for more details. - --> **Consul Enterprise customers** may use [network -segments](/docs/enterprise/network-segments) to enable non-fully-connected -topologies. However, out-of-cluster nodes must still be able to communicate -with the server pod or host IP addresses. +Services running on non-Kubernetes nodes can join a Consul cluster running within Kubernetes. ## Auto-join + The recommended way to join a cluster running within Kubernetes is to use the ["k8s" cloud auto-join provider](/docs/install/cloud-auto-join#kubernetes-k8s). The auto-join provider dynamically discovers IP addresses to join using the Kubernetes API. It authenticates with Kubernetes using a standard -`kubeconfig` file. This works with all major hosted Kubernetes offerings +`kubeconfig` file. Auto-join works with all major hosted Kubernetes offerings as well as self-hosted installations. The token in the `kubeconfig` file needs to have permissions to list pods in the namespace where Consul servers are deployed. -The auto-join string below will join a Consul server cluster that is -started using the [official Helm chart](/docs/k8s/helm): +The auto-join string below joins a Consul server agent to a cluster using the [official Helm chart](/docs/k8s/helm): ```shell-session $ consul agent -retry-join 'provider=k8s label_selector="app=consul,component=server"' ``` + -> **Note:** This auto-join command only connects on the default gossip port -8301, whether you are joining on the pod network or via host ports. Either a -consul server or client that is already a member of the datacenter should be -listening on this port for the external client agent to be able to use +8301, whether you are joining on the pod network or via host ports. A +Consul server that is already a member of the datacenter should be +listening on this port for the external service to connect through auto-join. ### Auto-join on the Pod network -In the default Consul Helm chart installation, Consul clients and servers are -routable only via their pod IPs for server RPCs and gossip (HTTP -API calls to Consul clients can also be made through host IPs). This means any -external client agents joining the Consul cluster running on Kubernetes would -need to be able to have connectivity to those pod IPs. -In many hosted Kubernetes environments, you will need to explicitly configure +In the default Consul Helm chart installation, Consul servers are +routable through their pod IPs for server RPCs. As a result, any +external agents joining the Consul cluster running on Kubernetes +need to be able to connect to those pod IPs. + +In many hosted Kubernetes environments, you need to explicitly configure your hosting provider to ensure that pod IPs are routable from external VMs. -See [Azure AKS -CNI](https://docs.microsoft.com/en-us/azure/aks/concepts-network#azure-cni-advanced-networking), -[AWS EKS -CNI](https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html) and -[GKE VPC-native -clusters](https://cloud.google.com/kubernetes-engine/docs/concepts/alias-ips). +For more information, refer to [Azure AKS CNI](https://docs.microsoft.com/en-us/azure/aks/concepts-network#azure-cni-advanced-networking), +[AWS EKS CNI](https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html) and +[GKE VPC-native clusters](https://cloud.google.com/kubernetes-engine/docs/concepts/alias-ips). -Given you have the [official Helm chart](/docs/k8s/helm) installed with the default values, do the following to join an external client agent. +To join external agents with Consul on Kubernetes deployments installed with default values through the [official Helm chart](/docs/k8s/helm): - 1. Make sure the pod IPs of the clients and servers in Kubernetes are + 1. Make sure the pod IPs of the servers in Kubernetes are routable from the VM and that the VM can access port 8301 (for gossip) and port 8300 (for server RPC) on those pod IPs. - 1. Make sure that the client and server pods running in Kubernetes can route + 1. Make sure that the server pods running in Kubernetes can route to the VM's advertise IP on its gossip port (default 8301). - 2. Make sure you have the `kubeconfig` file for the Kubernetes cluster in `$HOME/.kube/config` on the external VM. + 1. Make sure you have the `kubeconfig` file for the Kubernetes cluster in `$HOME/.kube/config` on the external VM. + + 1. On the external VM, run: - 2. On the external VM, run: - ```bash + ```shell-session consul agent \ -advertise="$ADVERTISE_IP" \ -retry-join='provider=k8s label_selector="app=consul,component=server"' \ @@ -86,7 +71,8 @@ Given you have the [official Helm chart](/docs/k8s/helm) installed with the defa -data-dir=$DATA_DIR \ ``` - 3. Check if the join was successful by running `consul members`. Sample output: + 1. Run `consul members` to check if the join was successful. + ```shell-session / $ consul members Node Address Status Type Build Protocol DC Segment @@ -97,10 +83,10 @@ Given you have the [official Helm chart](/docs/k8s/helm) installed with the defa gke-external-agent-default-pool-32d15192-vo7k 10.138.0.42:8301 alive client 1.9.1 2 dc1 ``` -### Auto-join via host ports -If your external VMs can't connect to Kubernetes pod IPs, but they can connect -to the internal host IPs of the nodes in the Kubernetes cluster, you have the -option to expose the clients and server ports on the host IP instead. +### Auto-join through host ports + +If your external VMs cannot connect to Kubernetes pod IPs but they can connect +to the internal host IPs of the nodes in the Kubernetes cluster, you can join the two by exposing ports on the host IP instead. 1. Install the [official Helm chart](/docs/k8s/helm) with the following values: ```yaml @@ -114,19 +100,20 @@ option to expose the clients and server ports on the host IP instead. # Note that this needs to be different than 8301, to avoid conflicting with the client gossip hostPort port: 9301 ``` - This will expose the client gossip ports, the server gossip ports and the server RPC port at `hostIP:hostPort`. Note that `hostIP` is the **internal** IP of the VM that the client/server pods are deployed on. + This installation exposes the client gossip ports, the server gossip ports and the server RPC port at `hostIP:hostPort`. Note that `hostIP` is the **internal** IP of the VM that the client/server pods are deployed on. 1. Make sure the IPs of the Kubernetes nodes are routable from the VM and that the VM can access ports 8301 and 9301 (for gossip) and port 8300 (for server RPC) on those node IPs. - 1. Make sure the client and server pods running in Kubernetes can route to + 1. Make sure the server pods running in Kubernetes can route to the VM's advertise IP on its gossip port (default 8301). - 3. Make sure you have the `kubeconfig` file for the Kubernetes cluster in `$HOME/.kube/config` on the external VM. + 1. Make sure you have the `kubeconfig` file for the Kubernetes cluster in `$HOME/.kube/config` on the external VM. - 4. On the external VM, run (note the addition of `host_network=true` in the retry-join argument): - ```bash + 1. On the external VM, run: + + ```shell-session consul agent \ -advertise="$ADVERTISE_IP" \ -retry-join='provider=k8s host_network=true label_selector="app=consul,component=server"' @@ -137,7 +124,11 @@ option to expose the clients and server ports on the host IP instead. -datacenter=$DATACENTER \ -data-dir=$DATA_DIR \ ``` - 3. Check if the join was successful by running `consul members`. Sample output: + + Note the addition of `host_network=true` in the retry-join argument. + + 1. Run `consul members` to check if the join was successful. + ```shell-session / $ consul members Node Address Status Type Build Protocol DC Segment @@ -149,13 +140,12 @@ option to expose the clients and server ports on the host IP instead. ``` ## Manual join -If you are unable to use auto-join, you can also follow the instructions in -either of the auto-join sections but instead of using a `provider` key in the -`-retry-join` flag, you would need to pass the address of at least one -consul server, e.g: `-retry-join=$CONSUL_SERVER_IP:$SERVER_SERFLAN_PORT`. A -`kubeconfig` file is not required when using manual join. -However, rather than hardcoding the IP, it's recommended to set up a DNS entry -that would resolve to the consul servers' pod IPs (if using the pod network) or -host IPs that the server pods are running on (if using host ports). +If you are unable to use auto-join, try following the instructions in +either of the auto-join sections, but instead of using a `provider` key in the +`-retry-join` flag, pass the address of at least one Consul server. Example: `-retry-join=$CONSUL_SERVER_IP:$SERVER_SERFLAN_PORT`. + +A `kubeconfig` file is not required when using manual join. +Instead of hardcoding an IP address, we recommend you set up a DNS entry +that resolves to the pod IPs or host IPs that the Consul server pods are running on. \ No newline at end of file diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/index.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/index.mdx index 735b7d12fa86..3c9de15c8f8b 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/index.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/index.mdx @@ -7,13 +7,15 @@ description: >- # Vault as the Secrets Backend - Data Integration -## Overview +This topic describes how to configure Vault and Consul in order to share secrets for use within Consul. -This topic describes an overview of how to configure Vault and Consul in order to share secrets for use within Consul. +## Prerequisites + +Before you set up the data integration between Vault and Consul on Kubernetes, read and complete the steps in the [Systems Integration](/docs/k8s/deployment-configurations/vault/systems-integration) section of [Vault as a Secrets Backend](/docs/k8s/deployment-configurations/vault). -### General Integration Steps +## General integration steps -You must complete two general procedures for each secret you wish to store in Vault. +For each secret you want to store in Vault, you must complete two multi-step procedures. Complete the following steps once: 1. Store the secret in Vault. @@ -21,40 +23,18 @@ Complete the following steps once: Repeat the following steps for each datacenter in the cluster: 1. Create Vault Kubernetes auth roles that link the policy to each Consul on Kubernetes service account that requires access. - 1. Update the Consul on Kubernetes helm chart. - -## Prerequisites -Prior to setting up the data integration between Vault and Consul on Kubernetes, you will need to have read and completed the steps in the [Systems Integration](/docs/k8s/deployment-configurations/vault/systems-integration) section of [Vault as a Secrets Backend](/docs/k8s/deployment-configurations/vault). + 1. Update the Consul on Kubernetes Helm chart. -### Example - Gossip Encryption Key Integration +## Secrets-to-service account mapping -Following the general integration steps, a more detailed workflow for integration of the [Gossip encryption key](/docs/k8s/deployment-configurations/vault/data-integration/gossip) with the Vault Secrets backend would like the following: - - -Complete the following steps once: - 1. Store the secret in Vault. - - Save the gossip encryption key in Vault at the path `secret/consul/gossip`. - 1. Create a Vault policy that authorizes the desired level of access to the secret. - - Create a Vault policy that you name `gossip-policy` which allows `read` access to the path `secret/consul/gossip`. +At the most basic level, the goal of this configuration is to authorize a Consul on Kubernetes service account to access a secret in Vault. -Repeat the following steps for each datacenter in the cluster: - 1. Create Vault Kubernetes auth roles that link the policy to each Consul on Kubernetes service account that requires access. - - Both Consul servers and Consul clients need access to the gossip encryption key, so you create two Vault Kubernetes: - - A role called `consul-server` that maps the Kubernetes namespace and service account name for your consul servers to the `gossip-policy` created in [step 2](#one-time-setup-in-vault) of One time setup in Vault. - - A role called `consul-client` that maps the Kubernetes namespace and service account name for your consul clients to the `gossip-policy` created in [step 2](#one-time-setup-in-vault) of One time setup in Vault.. - 1. Update the Consul on Kubernetes helm chart. - - Configure the Vault Kubernetes auth roles created for the gossip encryption key: - - [`global.secretsBackend.vault.consulServerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulserverrole) is set to the `consul-server` Vault Kubernetes auth role created previously. - - [`global.secretsBackend.vault.consulClientRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulclientrole) is set to the `consul-client` Vault Kubernetes auth role created previously. - -## Secrets to Service Account Mapping - -At the most basic level, the goal of this configuration is to authorize a Consul on Kubernetes service account to access a secret in Vault. -Below is a mapping of Vault secrets and the Consul on Kubernetes service accounts that need to access them. -(NOTE: `Consul components` refers to all other services and jobs that are not Consul servers or clients. +The following table associates Vault secrets and the Consul on Kubernetes service accounts that require access. +(NOTE: `Consul components` refers to all other services and jobs that are not Consul servers or clients. It includes things like terminating gateways, ingress gateways, etc.) -### Primary Datacenter +### Primary datacenter + | Secret | Service Account For | Configurable Role in Consul k8s Helm | | ------ | ------------------- | ------------------------------------ | |[ACL Bootstrap token](/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token) | Consul server-acl-init job | [`global.secretsBackend.vault.manageSystemACLsRole`](/docs/k8s/helm#v-global-secretsbackend-vault-managesystemaclsrole)| @@ -67,8 +47,10 @@ It includes things like terminating gateways, ingress gateways, etc.) |[Service Mesh and Consul client TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/connect-ca) | Consul servers | [`global.secretsBackend.vault.consulServerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulserverrole)| |[Webhook TLS certificates for controller and connect inject](/docs/k8s/deployment-configurations/vault/data-integration/connect-ca) | Consul controllers
Consul connect inject | [`global.secretsBackend.vault.controllerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-controllerrole)
[`global.secretsBackend.vault.connectInjectRole`](/docs/k8s/helm#v-global-secretsbackend-vault-controllerrole)| -### Secondary Datacenters +### Secondary datacenters + The mapping for secondary data centers is similar with the following differences: + - There is no use of bootstrap token because ACLs would have been bootstrapped in the primary datacenter. - ACL Partition token is mapped to both the `server-acl-init` job and the `partition-init` job service accounts. - ACL Replication token is mapped to both the `server-acl-init` job and Consul service accounts. @@ -83,26 +65,14 @@ The mapping for secondary data centers is similar with the following differences |[Server TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/server-tls) | Consul servers
Consul clients
Consul components | [`global.secretsBackend.vault.consulServerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulserverrole)
[`global.secretsBackend.vault.consulClientRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulclientrole)
[`global.secretsBackend.vault.consulCARole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulcarole)| |[Service Mesh and Consul client TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/connect-ca) | Consul servers | [`global.secretsBackend.vault.consulServerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-consulserverrole)| |[Webhook TLS certificates for controller and connect inject](/docs/k8s/deployment-configurations/vault/data-integration/connect-ca) | Consul controllers
Consul connect inject | [`global.secretsBackend.vault.controllerRole`](/docs/k8s/helm#v-global-secretsbackend-vault-controllerrole)
[`global.secretsBackend.vault.connectInjectRole`](/docs/k8s/helm#v-global-secretsbackend-vault-controllerrole)| -### Combining policies within roles -As you can see in the table above, depending upon your needs, a Consul on Kubernetes service account could have the need to request more than one secret. In these cases, you will want to create one role for the Consul on Kubernetes service account that is mapped to multiple policies, each of which allows it access to a given secret. -For example, if your Consul on Kubernetes servers need access to [Gossip encryption key](/docs/k8s/deployment-configurations/vault/data-integration/gossip), [Consul Server TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/server-tls), and [Enterprise license](/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license), assuming you have already saved the secrets in vault, you would: -1. Create a policy for each secret. - 1. Gossip encryption key - - +### Combining policies within roles - ```HCL - path "secret/data/consul/gossip" { - capabilities = ["read"] - } - ``` +As you can see in the table above, depending upon your needs, a Consul on Kubernetes service account could have the need to request more than one secret. In these cases, you will want to create one role for the Consul on Kubernetes service account that is mapped to multiple policies, each of which allows it access to a given secret. - +For example, if your Consul on Kubernetes servers need access to [Consul Server TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/server-tls) and an [Enterprise license](/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license): - ```shell-session - $ vault policy write gossip-policy gossip-policy.hcl - ``` +1. Create a policy for each secret. 1. Consul Server TLS credentials @@ -141,12 +111,14 @@ For example, if your Consul on Kubernetes servers need access to [Gossip encrypt $ vault write auth/kubernetes/role/consul-server \ bound_service_account_names= \ bound_service_account_namespaces= \ - policies=gossip-policy,ca-policy,license-policy \ + policies=ca-policy,license-policy \ ttl=1h ``` ## Detailed data integration guides + The following secrets can be stored in Vault KV secrets engine, which is meant to handle arbitrary secrets: + - [ACL Bootstrap token](/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token) - [ACL Partition token](/docs/k8s/deployment-configurations/vault/data-integration/partition-token) - [ACL Replication token](/docs/k8s/deployment-configurations/vault/data-integration/replication-token) @@ -155,9 +127,11 @@ The following secrets can be stored in Vault KV secrets engine, which is meant t - [Snapshot Agent config](/docs/k8s/deployment-configurations/vault/data-integration/snapshot-agent-config) The following TLS certificates and keys can generated and managed by Vault the Vault PKI Engine, which is meant to handle things like certificate expiration and rotation: + - [Server TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/server-tls) - [Service Mesh and Consul client TLS credentials](/docs/k8s/deployment-configurations/vault/data-integration/connect-ca) - [Vault as the Webhook Certificate Provider for Consul Controller and Connect Inject on Kubernetes](/docs/k8s/deployment-configurations/vault/data-integration/webhook-certs) -## Secrets to Service Account Mapping +## Secrets-to-service account mapping + Read through the [detailed data integration guides](#detailed-data-integration-guides) that are pertinent to your environment. diff --git a/website/content/docs/k8s/index.mdx b/website/content/docs/k8s/index.mdx index 2c3554a044ea..b43df989e648 100644 --- a/website/content/docs/k8s/index.mdx +++ b/website/content/docs/k8s/index.mdx @@ -14,59 +14,58 @@ This section documents the official integrations between Consul and Kubernetes. ## Use Cases -**Consul Service Mesh:** +**Consul Service Mesh**: Consul can automatically inject the [Consul Service Mesh](/docs/connect) sidecar into pods so that they can accept and establish encrypted -and authorized network connections via mutual TLS. And because Consul Service Mesh -can run anywhere, pods can also communicate with external services (and -vice versa) over a fully encrypted connection. +and authorized network connections with mutual TLS. And because Consul Service Mesh +can run anywhere, pods and external services can communicate with each other over a fully encrypted connection. -**Service sync to enable Kubernetes and non-Kubernetes services to communicate:** -Consul can sync Kubernetes services with its own service registry. This allows -Kubernetes services to use native Kubernetes service discovery to discover +**Service sync to enable Kubernetes and non-Kubernetes services to communicate**: +Consul can sync Kubernetes services with its own service registry. This service sync allows +Kubernetes services to use Kubernetes' native service discovery capabilities to discover and connect to external services registered in Consul, and for external services to use Consul service discovery to discover and connect to Kubernetes services. -**And more!** Consul can run directly on Kubernetes, so in addition to the +**Additional integrations**: Consul can run directly on Kubernetes, so in addition to the native integrations provided by Consul itself, any other tool built for -Kubernetes can choose to leverage Consul. +Kubernetes can leverage Consul. ## Getting Started With Consul and Kubernetes There are several ways to try Consul with Kubernetes in different environments. -**Tutorials** +### Tutorials -- The [Getting Started with Consul Service Mesh track](https://learn.hashicorp.com/tutorials/consul/service-mesh-deploy?in=consul/gs-consul-service-mesh?utm_source=docs) +- The [Service Mesh on Kubernetes collection](/consul/tutorials/kubernetes?utm_source=docs) provides guidance for installing Consul as service mesh for Kubernetes using the Helm chart, deploying services in the service mesh, and using intentions to secure service communications. -- The [Migrate to Microservices with Consul Service Mesh on Kubernetes](https://learn.hashicorp.com/collections/consul/microservices?utm_source=docs) +- The [Migrate to Microservices with Consul Service Mesh on Kubernetes](/consul/tutorials/microservices?utm_source=docs) collection uses an example application written by a fictional company to illustrate why and how organizations can migrate from monolith to microservices using Consul service mesh on Kubernetes. The case study in this collection should provide information valuable for understanding how to develop services that leverage Consul during any stage of your microservices journey. -- The [Consul and Minikube guide](https://learn.hashicorp.com/tutorials/consul/kubernetes-minikube?utm_source=docs) is a quick step-by-step guide for deploying Consul with the official Helm chart on a local instance of Minikube. +- The [Consul and Minikube guide](/consul/tutorials/kubernetes/kubernetes-minikube?utm_source=docs) is a quick step-by-step guide for deploying Consul with the official Helm chart on a local instance of Minikube. - Review production best practices and cloud-specific configurations for deploying Consul on managed Kubernetes runtimes. - - The [Consul on Azure Kubernetes Service (AKS) tutorial](https://learn.hashicorp.com/tutorials/consul/kubernetes-aks-azure?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on AKS. The guide also allows you to practice deploying two microservices. - - The [Consul on Amazon Elastic Kubernetes Service (EKS) tutorial](https://learn.hashicorp.com/tutorials/consul/kubernetes-eks-aws?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on EKS. Additionally, it provides guidance on interacting with your datacenter with the Consul UI, CLI, and API. - - The [Consul on Google Kubernetes Engine (GKE) tutorial](https://learn.hashicorp.com/tutorials/consul/kubernetes-gke-google?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on GKE. Additionally, it provides guidance on interacting with your datacenter with the Consul UI, CLI, and API. + - The [Consul on Azure Kubernetes Service (AKS) tutorial](/consul/tutorials/kubernetes/kubernetes-aks-azure?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on AKS. The guide also allows you to practice deploying two microservices. + - The [Consul on Amazon Elastic Kubernetes Service (EKS) tutorial](/consul/tutorials/kubernetes/kubernetes-eks-aws?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on EKS. Additionally, it provides guidance on interacting with your datacenter with the Consul UI, CLI, and API. + - The [Consul on Google Kubernetes Engine (GKE) tutorial](/consul/tutorials/kubernetes/kubernetes-gke-google?utm_source=docs) is a complete step-by-step guide on how to deploy Consul on GKE. Additionally, it provides guidance on interacting with your datacenter with the Consul UI, CLI, and API. -- The [Consul and Kubernetes Reference Architecture](https://learn.hashicorp.com/tutorials/consul/kubernetes-reference-architecture?utm_source=docs) guide provides recommended practices for production. +- The [Consul and Kubernetes Reference Architecture](/consul/tutorials/kubernetes/kubernetes-reference-architecture?utm_source=docs) guide provides recommended practices for production. -- The [Consul and Kubernetes Deployment](https://learn.hashicorp.com/tutorials/consul/kubernetes-deployment-guide?utm_source=docs) tutorial covers the necessary steps to install and configure a new Consul cluster on Kubernetes in production. +- The [Consul and Kubernetes Deployment](/consul/tutorials/kubernetes/kubernetes-deployment-guide?utm_source=docs) tutorial covers the necessary steps to install and configure a new Consul cluster on Kubernetes in production. -- The [Secure Consul and Registered Services on Kubernetes](https://learn.hashicorp.com/tutorials/consul/kubernetes-secure-agents?utm_source=docs) tutorial covers +- The [Secure Consul and Registered Services on Kubernetes](consul/tutorials/kubernetes/kubernetes-secure-agents?utm_source=docs) tutorial covers the necessary steps to secure a Consul cluster running on Kubernetes in production. -- The [Layer 7 Observability with Consul Service Mesh](https://learn.hashicorp.com/tutorials/consul/kubernetes-layer7-observability?in=consul/kubernetes) tutorial covers monitoring a +- The [Layer 7 Observability with Consul Service Mesh](/consul/tutorials/kubernetes/kubernetes-layer7-observability) tutorial covers monitoring a Consul service mesh running on Kubernetes with Prometheus and Grafana. -**Documentation** +### Documentation - [Installing Consul](/docs/k8s/installation/install) covers how to install Consul using the Helm chart. - [Helm Chart Reference](/docs/k8s/helm) describes the different options for configuring the Helm chart. diff --git a/website/content/docs/k8s/installation/install-cli.mdx b/website/content/docs/k8s/installation/install-cli.mdx index f195784c0aca..e87b8106ae3f 100644 --- a/website/content/docs/k8s/installation/install-cli.mdx +++ b/website/content/docs/k8s/installation/install-cli.mdx @@ -61,7 +61,7 @@ The [Homebrew](https://brew.sh) package manager is required to complete the foll ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -90,7 +90,7 @@ The [Homebrew](https://brew.sh) package manager is required to complete the foll ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -119,7 +119,7 @@ The [Homebrew](https://brew.sh) package manager is required to complete the foll ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -137,7 +137,7 @@ Complete the following instructions to install a specific version of the CLI so 1. Download the appropriate version of Consul K8s CLI using the following `curl` command. Set the `$VERSION` environment variable to the appropriate version for your deployment. ```shell-session - $ export VERSION=0.39.0 && \ + $ export VERSION=1.0 && \ curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip ``` @@ -157,7 +157,7 @@ Complete the following instructions to install a specific version of the CLI so ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -187,7 +187,7 @@ Complete the following instructions to install a specific version of the CLI so ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -209,15 +209,15 @@ Complete the following instructions to install a specific version of the CLI so 1. Install the `consul-k8s` CLI. ```shell-session - $ export VERSION=-0.39.0 && \ + $ export VERSION=-1.0 && \ sudo yum -y install consul-k8s-${VERSION}-1 ``` -1. (Optional) Issue the `consul-k8s version` command to verify the installation. +2. (Optional) Issue the `consul-k8s version` command to verify the installation. ```shell-session $ consul-k8s version - consul-k8s 0.39.0 + consul-k8s 1.0 ``` @@ -268,15 +268,7 @@ $ consul-k8s status ==> Consul-K8s Status Summary NAME | NAMESPACE | STATUS | CHARTVERSION | APPVERSION | REVISION | LAST UPDATED ---------+-----------+----------+--------------+------------+----------+-------------------------- - consul | consul | deployed | 0.40.0 | 1.11.2 | 1 | 2022/01/31 16:58:51 PST - -==> Config: - connectInject: - enabled: true - controller: - enabled: true - global: - name: consul + consul | consul | deployed | 0.40.0 | 1.14.0 | 1 | 2022/01/31 16:58:51 PST ✓ Consul servers healthy (3/3) ✓ Consul clients healthy (3/3) diff --git a/website/content/docs/k8s/installation/install.mdx b/website/content/docs/k8s/installation/install.mdx index 54c67c05817a..064a88092cb1 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -9,28 +9,23 @@ description: >- This topic describes how to install Consul on Kubernetes using the official Consul Helm chart. For instruction on how to install Consul on Kubernetes using the Consul K8s CLI, refer to [Installing the Consul K8s CLI](/docs/k8s/installation/install-cli). - ## Introduction -We recommend using the Consul Helm chart to install Consul on Kubernetes for multi-cluster installations that involve cross-partition or cross datacenter communication. The Helm chart installs and configures all necessary components to run Consul. The configuration enables you to run a server cluster, a client cluster, or both. +We recommend using the Consul Helm chart to install Consul on Kubernetes for multi-cluster installations that involve cross-partition or cross datacenter communication. The Helm chart installs and configures all necessary components to run Consul. -Consul can run directly on Kubernetes in server or client mode so that you can leverage Consul functionality if your workloads are fully deployed to Kubernetes. For heterogeneous workloads, Consul agents can join a server running inside or outside of Kubernetes. Refer to the [architecture section](/docs/k8s/architecture) to learn more about the general architecture of Consul on Kubernetes. +Consul can run directly on Kubernetes so that you can leverage Consul functionality if your workloads are fully deployed to Kubernetes. For heterogeneous workloads, Consul agents can join a server running inside or outside of Kubernetes. Refer to the [Consul on Kubernetes architecture](/docs/k8s/architecture) to learn more about its general architecture. The Helm chart exposes several useful configurations and automatically sets up complex resources, but it does not automatically operate Consul. You must still become familiar with how to monitor, backup, and upgrade the Consul cluster. -The Helm chart has no required configuration, so it installs a Consul cluster with default configurations. We strongly recommend that you [learn about the configuration options](/docs/k8s/helm#configuration-values) prior to going to production. +The Helm chart has no required configuration, so it installs a Consul cluster with default configurations. We strongly recommend that you [learn about the configuration options](/docs/k8s/helm#configuration-values) before going to production. -> **Security warning**: By default, Helm installs Consul with security configurations disabled so that the out-of-box experience is optimized for new users. We strongly recommend using a properly-secured Kubernetes cluster or making sure that you understand and enable [Consul’s security features](/docs/security) before going into production. Some security features are not supported in the Helm chart and require additional manual configuration. -Refer to the [architecture](/docs/k8s/installation/install#architecture) section to learn more about the general architecture of Consul on Kubernetes. - -For a hands-on experience with Consul as a service mesh -for Kubernetes, follow the [Getting Started with Consul service -mesh](https://learn.hashicorp.com/tutorials/consul/service-mesh-deploy?in=consul/gs-consul-service-mesh?utm_source=docs) tutorial. +> For a hands-on experience with Consul as a service mesh for Kubernetes, follow the [Getting Started with Consul service mesh tutorial](/consul/tutorials/kubernetes-features/service-mesh-deploy?utm_source=docs). ## Requirements -- Helm version 3.2+. Visit the [Helm website](https://helm.sh/docs/intro/install/) to download the latest version. +Using the Helm Chart requires Helm version 3.2+. Visit the [Helm website](https://helm.sh/docs/intro/install/) to download the latest version. ## Install Consul @@ -46,11 +41,10 @@ mesh](https://learn.hashicorp.com/tutorials/consul/service-mesh-deploy?in=consul ```shell-session $ helm search repo hashicorp/consul NAME CHART VERSION APP VERSION DESCRIPTION - hashicorp/consul 0.39.0 1.11.1 Official HashiCorp Consul Chart + hashicorp/consul 0.45.0 1.14.0 Official HashiCorp Consul Chart ``` -1. Prior to installing via Helm, ensure that the `consul` Kubernetes namespace does not exist, as installing on a dedicated namespace - is recommended. +1. Before you install Consul on Kubernetes with Helm, ensure that the `consul` Kubernetes namespace does not exist. We recommend installing Consul on a dedicated namespace. ```shell-session $ kubectl get namespace @@ -61,46 +55,44 @@ mesh](https://learn.hashicorp.com/tutorials/consul/service-mesh-deploy?in=consul kube-system Active 18h ``` -1. Install Consul on Kubernetes using Helm. The Helm chart does everything to set up a recommended Consul-on-Kubernetes deployment. After installation, a Consul cluster will be formed, a leader will be elected, and every node will have a running Consul agent. - 1. To install the latest version of Consul on Kubernetes, issue the following command to install Consul with the default configuration using Helm. You could also install Consul on a dedicated namespace of your choosing by modifying the value of the `-n` flag for the Helm install. +1. Install Consul on Kubernetes using Helm. The Helm chart does everything to set up your deployment: after installation, agents automatically form clusters, elect leaders, and run the necessary agents. + + - Run the following command to install the latest version of Consul on Kubernetes with its default configuration. - ```shell-session - $ helm install consul hashicorp/consul --set global.name=consul --create-namespace --namespace consul - ``` + ```shell-session + $ helm install consul hashicorp/consul --set global.name=consul --create-namespace --namespace consul + ``` - 1. To install a specific version of Consul on Kubernetes, issue the following command with `--version` flag to install the specified version with the default configuration using Helm. + You can also install Consul on a dedicated namespace of your choosing by modifying the value of the `-n` flag for the Helm install. - ```shell-session - $ export VERSION=0.43.0 - $ helm install consul hashicorp/consul --set global.name=consul --version ${VERSION} --create-namespace --namespace consul - ``` + - To install a specific version of Consul on Kubernetes, issue the following command with `--version` flag: + ```shell-session + $ export VERSION=0.43.0 + $ helm install consul hashicorp/consul --set global.name=consul --version ${VERSION} --create-namespace --namespace consul + ``` ## Custom installation If you want to customize your installation, create a `values.yaml` file to override the default settings. -You can learn what settings are available by running `helm inspect values hashicorp/consul` -or by reading the [Helm Chart Reference](/docs/k8s/helm). +To learn what settings are available, run `helm inspect values hashicorp/consul` +or read the [Helm Chart Reference](/docs/k8s/helm). ### Minimal `values.yaml` for Consul service mesh -The minimal settings to enable [Consul Service Mesh]((/docs/k8s/connect)) would be captured in the following `values.yaml` config file: +The following `values.yaml` config file contains the minimum required settings to enable [Consul Service Mesh]((/docs/k8s/connect)): ```yaml global: name: consul -connectInject: - enabled: true -controller: - enabled: true ``` -Once you've created your `values.yaml` file, run `helm install` with the `--values` flag: +After you create your `values.yaml` file, run `helm install` with the `--values` flag: ```shell-session $ helm install consul hashicorp/consul --create-namespace --namespace consul --values values.yaml @@ -120,58 +112,67 @@ Because the plugin is executed by the local Kubernetes kubelet, the plugin alrea The Consul Helm Chart is responsible for installing the Consul CNI plugin. To configure the plugin to be installed, add the following configuration to your `values.yaml` file: - + + ", "]}> -```yaml -global: - name: consul -connectInject: - enabled: true - cni: + ```yaml + global: + name: consul + connectInject: enabled: true - logLevel: info - cniBinDir: "/opt/cni/bin" - cniNetDir: "/etc/cni/net.d" + cni: + enabled: true + logLevel: info + cniBinDir: "/opt/cni/bin" + cniNetDir: "/etc/cni/net.d" ``` + + + + -```yaml -global: - name: consul -connectInject: - enabled: true - cni: + ```yaml + global: + name: consul + connectInject: enabled: true - logLevel: info - cniBinDir: "/home/kubernetes/bin" - cniNetDir: "/etc/cni/net.d" + cni: + enabled: true + logLevel: info + cniBinDir: "/home/kubernetes/bin" + cniNetDir: "/etc/cni/net.d" ``` + + + + -```yaml -global: - name: consul - openshift: - enabled: true -connectInject: - enabled: true - cni: + ```yaml + global: + name: consul + openshift: + enabled: true + connectInject: enabled: true - logLevel: info - multus: true - cniBinDir: "/var/lib/cni/bin" - cniNetDir: "/etc/kubernetes/cni/net.d" + cni: + enabled: true + logLevel: info + multus: true + cniBinDir: "/var/lib/cni/bin" + cniNetDir: "/etc/kubernetes/cni/net.d" ``` + - - + The following table describes the available CNI plugin options: @@ -185,43 +186,43 @@ The following table describes the available CNI plugin options: ### Enable Consul service mesh on select namespaces -By default, Consul Service Mesh is enabled on almost all namespaces (with the exception of `kube-system` and `local-path-storage`) within a Kubernetes cluster. You can restrict this to a subset of namespaces by specifying a `namespaceSelector` that matches a label attached to each namespace denoting whether to enable Consul service mesh. In order to default to enabling service mesh on select namespaces by label, the `connectInject.default` value must be set to `true`. +By default, Consul Service Mesh is enabled on almost all namespaces within a Kubernetes cluster, with the exception of `kube-system` and `local-path-storage`. To restrict the service mesh to a subset of namespaces: + +1. specify a `namespaceSelector` that matches a label attached to each namespace where you want to deploy the service mesh. In order to default to enabling service mesh on select namespaces by label, the `connectInject.default` value must be set to `true`. -```yaml -global: - name: consul -connectInject: - enabled: true - default: true - namespaceSelector: | - matchLabels: - connect-inject : enabled -controller: - enabled: true + ```yaml + global: + name: consul + connectInject: + enabled: true + default: true + namespaceSelector: | + matchLabels: + connect-inject : enabled ``` -Label the namespace(s), where you would like to enable Consul Service Mesh. +1. Label the namespaces where you would like to enable Consul Service Mesh. -```shell-session -$ kubectl create ns foo -$ kubectl label namespace foo connect-inject=enabled -``` + ```shell-session + $ kubectl create ns foo + $ kubectl label namespace foo connect-inject=enabled + ``` -Next, run `helm install` with the `--values` flag: +1. Run `helm install` with the `--values` flag: -```shell-session -$ helm install consul hashicorp/consul --create-namespace --namespace consul --values values.yaml -NAME: consul -``` + ```shell-session + $ helm install consul hashicorp/consul --create-namespace --namespace consul --values values.yaml + NAME: consul + ``` ### Update your Consul on Kubernetes configuration -If you've already installed Consul and want to make changes, you'll need to run -`helm upgrade`. See [Upgrading](/docs/k8s/upgrade) for more details. +If you already installed Consul and want to make changes, you need to run +`helm upgrade`. Refer to [Upgrading](/docs/k8s/upgrade) for more details. ## Usage @@ -230,38 +231,38 @@ You can view the Consul UI and access the Consul HTTP API after installation. ### Viewing the Consul UI The Consul UI is enabled by default when using the Helm chart. -For security reasons, it isn't exposed via a `LoadBalancer` Service by default so you must -use `kubectl port-forward` to visit the UI. -#### TLS Disabled +For security reasons, it is not exposed through a `LoadBalancer` service by default. To visit the UI, you must +use `kubectl port-forward`. + +#### Port forward with TLS disabled -If running with TLS disabled, the Consul UI will be accessible via http on port 8500: +If running with TLS disabled, the Consul UI is accessible through http on port 8500: ```shell-session $ kubectl port-forward service/consul-server --namespace consul 8500:8500 ... ``` -Once the port is forwarded navigate to [http://localhost:8500](http://localhost:8500). +After you set up the port forward, navigate to [http://localhost:8500](http://localhost:8500). -#### TLS Enabled +#### Port forward with TLS enabled -If running with TLS enabled, the Consul UI will be accessible via https on port 8501: +If running with TLS enabled, the Consul UI is accessible through https on port 8501: ```shell-session $ kubectl port-forward service/consul-server --namespace consul 8501:8501 ... ``` -Once the port is forwarded navigate to [https://localhost:8501](https://localhost:8501). +After you set up the port forward, navigate to [https://localhost:8501](https://localhost:8501). -~> You'll need to click through an SSL warning from your browser because the +~> You need to click through an SSL warning from your browser because the Consul certificate authority is self-signed and not in the browser's trust store. #### ACLs Enabled -If ACLs are enabled, you will need to input an ACL token into the UI in order -to see all resources and make modifications. +If ACLs are enabled, you need to input an ACL token to display all resources and make modifications in the UI. To retrieve the bootstrap token that has full permissions, run: @@ -276,25 +277,18 @@ Then paste the token into the UI under the ACLs tab (without the `%`). to retrieve the bootstrap token since secondary datacenters use a separate token with less permissions. -#### Exposing the UI via a service +#### Exposing the UI through a service If you want to expose the UI via a Kubernetes Service, configure the [`ui.service` chart values](/docs/k8s/helm#v-ui-service). -This service will allow requests to the Consul servers so it should +Because this service allows requests to the Consul servers, it should not be open to the world. ### Accessing the Consul HTTP API -The Consul HTTP API should be accessed by communicating to the local agent -running on the same node. While technically any listening agent (client or -server) can respond to the HTTP API, communicating with the local agent -has important caching behavior, and allows you to use the simpler -[`/agent` endpoints for services and checks](/api-docs/agent). +While technically any listening agent can respond to the HTTP API, communicating with the local Consul node has important caching behavior and allows you to use the simpler [`/agent` endpoints for services and checks](/api-docs/agent). -For Consul installed via the Helm chart, a client agent is installed on -each Kubernetes node. This is explained in the [architecture](/docs/k8s/installation/install#client-agents) -section. To access the agent, you may use the -[downward API](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/). +To find information about a node, you can use the [downward API](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/). An example pod specification is shown below. In addition to pods, anything with a pod template can also access the downward API and can therefore also diff --git a/website/content/docs/k8s/operations/tls-on-existing-cluster.mdx b/website/content/docs/k8s/operations/tls-on-existing-cluster.mdx index ad43186c0361..96c20e27dbf8 100644 --- a/website/content/docs/k8s/operations/tls-on-existing-cluster.mdx +++ b/website/content/docs/k8s/operations/tls-on-existing-cluster.mdx @@ -15,7 +15,7 @@ However, depending on your Kubernetes use case, your upgrade procedure may be di ## Gradual TLS Rollout without Consul Connect -If you're **not using Consul Connect**, follow this process. +If you do not use a service mesh, follow this process. 1. Run a Helm upgrade with the following config: @@ -31,8 +31,7 @@ If you're **not using Consul Connect**, follow this process. updatePartition: ``` -This upgrade will trigger a rolling update of the clients, as well as any -other `consul-k8s` components, such as sync catalog or client snapshot deployments. + This upgrade trigger a rolling update of `consul-k8s` components. 1. Perform a rolling upgrade of the servers, as described in [Upgrade Consul Servers](/docs/k8s/upgrade#upgrading-consul-servers). @@ -50,9 +49,9 @@ applications to it. 1. Add a new identical node pool. -1. Cordon all nodes in the **old** pool by running `kubectl cordon` - to ensure Kubernetes doesn't schedule any new workloads on those nodes - and instead schedules onto the new nodes, which shortly will be TLS-enabled. +1. Cordon all nodes in the old pool by running `kubectl cordon`. + This command ensures Kubernetes does not schedule any new workloads on those nodes, + and instead schedules onto the new TLS-enabled nodes. 1. Create the following Helm config file for the upgrade: @@ -72,26 +71,16 @@ applications to it. ``` In this configuration, we're setting `server.updatePartition` to the number of - server replicas as described in [Upgrade Consul Servers](/docs/k8s/upgrade#upgrading-consul-servers) - and `client.updateStrategy` to `OnDelete` to manually trigger an upgrade of the clients. + server replicas as described in [Upgrade Consul Servers](/docs/k8s/upgrade#upgrading-consul-servers). -1. Run `helm upgrade` with the above config file. The upgrade will trigger an update of all - components except clients and servers, such as the Consul Connect webhook deployment - or the sync catalog deployment. Note that the sync catalog and the client - snapshot deployments will not be in the `ready` state until the clients on their - nodes are upgraded. It is OK to proceed to the next step without them being ready - because Kubernetes will keep the old deployment pod around, and so there will be no - downtime. - -1. Gradually perform an upgrade of the clients by deleting client pods on the **new** node - pool. +1. Run `helm upgrade` with the above config file. 1. At this point, all components (e.g., Consul Connect webhook and sync catalog) should be running on the new node pool. 1. Redeploy all your Connect-enabled applications. One way to trigger a redeploy is to run `kubectl drain` on the nodes in the old pool. - Now that the Connect webhook is TLS-aware, it will add TLS configuration to + Now that the Connect webhook is TLS-aware, it adds TLS configuration to the sidecar proxy. Also, Kubernetes should schedule these applications on the new node pool. 1. Perform a rolling upgrade of the servers described in diff --git a/website/content/docs/k8s/service-sync.mdx b/website/content/docs/k8s/service-sync.mdx index 577aaccff88f..f63fa88c4bba 100644 --- a/website/content/docs/k8s/service-sync.mdx +++ b/website/content/docs/k8s/service-sync.mdx @@ -23,18 +23,17 @@ For non-Kubernetes nodes, they can access services using the standard [Consul DNS](/docs/discovery/dns) or HTTP API. **Why sync Consul services to Kubernetes?** Syncing Consul services to -Kubernetes services enables non-Kubernetes services (such as external to -the cluster) to be accessed in a native Kubernetes way: using kube-dns, -environment variables, etc. This makes it very easy to automate external +Kubernetes services enables non-Kubernetes services to be accessed using kube-dns and Kubernetes-specific +environment variables. This integration makes it very easy to automate external service discovery, including hosted services like databases. -## Installation and Configuration +## Installation and configuration -~> Enabling both Service Mesh and Service Sync on the same Kubernetes services is not supported, as Service Mesh also registers Kubernetes service instances to Consul. Please ensure that Service Sync is only enabled for namespaces and services that are not injected with the Consul sidecar for Service Mesh as described in [Sync Enable/Disable](/docs/k8s/service-sync#sync-enable-disable). +~> Enabling both Service Mesh and Service Sync on the same Kubernetes services is not supported, as Service Mesh also registers Kubernetes service instances to Consul. Ensure that Service Sync is only enabled for namespaces and services that are not injected with the Consul sidecar for Service Mesh as described in [Sync Enable/Disable](/docs/k8s/service-sync#sync-enable-disable). -The service sync is done using an external long-running process in the +The service sync uses an external long-running process in the [consul-k8s project](https://github.com/hashicorp/consul-k8s). This process -can run either in or out of a Kubernetes cluster. However, running this within +can run either inside or outside of a Kubernetes cluster. However, running this process within the Kubernetes cluster is generally easier since it is automated using the [Helm chart](/docs/k8s/helm). @@ -54,8 +53,7 @@ syncCatalog: enabled: true ``` -This will enable services to sync _in both directions_. You can also choose -to only sync Kubernetes services to Consul or vice versa by disabling a direction. +This value enables service syncing in both direction. You can also disable a direction so that only Kubernetes services sync to Consul, or only Consul services sync to Kubernetes. To only enable syncing Consul services to Kubernetes use the config: @@ -75,7 +73,7 @@ syncCatalog: toK8S: false ``` -See the [Helm configuration](/docs/k8s/helm#v-synccatalog) +Refer to the [Helm configuration](/docs/k8s/helm#v-synccatalog) for more information. ### Authentication @@ -83,16 +81,15 @@ for more information. The sync process must authenticate to both Kubernetes and Consul to read and write services. -If running `consul-k8s` using the Helm chart then this authentication is handled for you. +If running `consul-k8s` using the Helm chart, then this authentication is handled for you. If running `consul-k8s` outside of Kubernetes, a valid kubeconfig file must be provided with cluster -and authentication information. The sync process will look into the default locations +and authentication information. The sync process looks into the default locations for both in-cluster and out-of-cluster authentication. If `kubectl` works, then the sync program should work. -For Consul, if ACLs are configured on the cluster, a Consul -[ACL token](https://learn.hashicorp.com/tutorials/consul/access-control-setup-production) -will need to be provided. Review the [ACL rules](/docs/security/acl/acl-rules) +If ACLs are configured on the Consul cluster, you need to provide a Consul +[ACL token](/consul/tutorials/security/access-control-setup-production). Review the [ACL rules](/docs/security/acl/acl-rules) when creating this token so that it only allows the necessary privileges. The catalog sync process accepts this token by using the [`CONSUL_HTTP_TOKEN`](/commands#consul_http_token) environment variable. This token should be set as a @@ -103,22 +100,21 @@ and referenced in the Helm chart. This sync registers Kubernetes services to the Consul catalog automatically. -This enables discovery and connection to Kubernetes services using native -Consul service discovery such as DNS or HTTP. This is particularly useful for +This sync enables discovery and connection to Kubernetes services using native +Consul service discovery protocols such as DNS or HTTP. This is particularly useful for non-Kubernetes nodes. This also causes all discoverable services to be part of a central service catalog in Consul for further syncing into alternate Kubernetes clusters or other platforms. -Each synced service will be registered onto a Consul node called `k8s-sync`. This -is not a real node because there is no Consul client registering and monitoring -the services. Instead, the catalog sync process is monitoring Kubernetes +Each synced service is registered onto a Consul node called `k8s-sync`. This node +is not a real node. Instead, the catalog sync process is monitoring Kubernetes and syncing the services to Consul. -### Kubernetes Service Types +### Kubernetes service types Not all Kubernetes services are externally accessible. The sync program by -default will only sync services of the following types or configurations. -If a service type is not listed below, then the sync program will ignore that +default only syncs services of the following types or configurations. +If a service type is not listed below, then the sync program ignores that service type. #### NodePort @@ -126,59 +122,60 @@ service type. [NodePort services](https://kubernetes.io/docs/concepts/services-networking/service/#nodeport) register a static port that every node in the K8S cluster listens on. -For NodePort services, a Consul service instance will be created for each +For NodePort services, a Consul service instance is created for each node that has the representative pod running. While Kubernetes configures a static port on all nodes in the cluster, this limits the number of service instances to be equal to the nodes running the target pods. -By default it will use the external IP of the node but this can be configured via +By default it uses the external IP of the node but this can be configured through the [`nodePortSyncType` helm option](/docs/k8s/helm#v-synccatalog-nodeportsynctype). -The service instance's port will be set to the _first_ defined node port of the service unless -set specifically via the `consul.hashicorp.com/service-port` annotation (see [Service Ports](/docs/k8s/service-sync#service-ports)). +The service instance's port is set to the first defined node port of the service unless +set specifically in the `consul.hashicorp.com/service-port` annotation. Refer to [Service Ports](/docs/k8s/service-sync#service-ports) for more information. #### LoadBalancer -For LoadBalancer services, a single service instance will be registered with +For LoadBalancer services, a single service instance is registered with the external IP of the created load balancer. Because this is already a load -balancer, only one service instance will be registered with Consul rather +balancer, only one service instance is registered with Consul rather than registering each individual pod endpoint. -The service instance's port will be set to the _first_ defined port of the -service unless set specifically via the `consul.hashicorp.com/service-port` annotation (see [Service Ports](/docs/k8s/service-sync#service-ports)). +The service instance's port is set to the first defined port of the +service unless set specifically in the `consul.hashicorp.com/service-port` annotation. Refer to [Service Ports](/docs/k8s/service-sync#service-ports) for more information. #### External IPs Any service type may specify an "[external IP](https://kubernetes.io/docs/concepts/services-networking/service/#external-ips)" configuration. The external IP must be configured by some other system, but -any service discovery will resolve to this set of IP addresses rather than a +any service discovery resolves to this set of IP addresses rather than a virtual IP. -If an external IP list is present, a service instance in Consul will be created +If an external IP list is present, a service instance in Consul is created for each external IP. It is assumed that if an external IP is present that it is routable and configured by some other system. -The service instance's port will be set to the _first_ defined port of the -service unless set specifically via the `consul.hashicorp.com/service-port` annotation (see [Service Ports](/docs/k8s/service-sync#service-ports)). +The service instance's port is set to the _first_ defined port of the +service unless set specifically with the `consul.hashicorp.com/service-port` annotation. Refer to [Service Ports](/docs/k8s/service-sync#service-ports) for more information. #### ClusterIP ClusterIP services are synced by default as of `consul-k8s` version 0.3.0. -Each pod that is an endpoint for the service will be synced as a Consul service +Each pod that is an endpoint for the service is synced as a Consul service instance with its IP set to the pod IP and its port set to the `targetPort`. -The service instance's port can be overridden via the `consul.hashicorp.com/service-port` annotation (see [Service Ports](/docs/k8s/service-sync#service-ports)). +The service instance's port can be overridden with the `consul.hashicorp.com/service-port` annotation. Refer to [Service Ports](/docs/k8s/service-sync#service-ports) for more information. -> In Kubernetes clusters where pod IPs are not accessible outside the cluster, the services registered in Consul may not be routable. To skip syncing ClusterIP services, set [`syncClusterIPServices`](/docs/k8s/helm#v-synccatalog-syncclusteripservices) to `false` in the Helm chart values file. -### Sync Enable/Disable +### Enable and disable sync -By default, all valid service types (as explained above) are synced from every Kubernetes -namespace (except for `kube-system` and `kube-public`). -If you wish to only sync specific services via annotation, set the default to `false`: +By default, all valid service types are synced from every Kubernetes +namespace except for `kube-system` and `kube-public`. + +To only sync specific services, first modify the annotation to set the default to `false`: ```yaml syncCatalog: @@ -186,7 +183,7 @@ syncCatalog: default: false ``` -And explicitly enable syncing specific services via the `consul.hashicorp.com/service-sync` annotation: +Then, explicitly enable syncing specific services with the `consul.hashicorp.com/service-sync` annotation: ```yaml kind: Service @@ -197,7 +194,7 @@ metadata: 'consul.hashicorp.com/service-sync': 'true' ``` --> **NOTE:** If the annotation is set to `false` when the default sync is `true`, the service will **not** be synced. +-> **Note:** If the annotation is set to `false` when the default sync is `true`, the service does not sync. You can allow or deny syncing from specific Kubernetes namespaces by setting the `k8sAllowNamespaces` and `k8sDenyNamespaces` keys: @@ -210,10 +207,10 @@ syncCatalog: k8sDenyNamespaces: ['kube-system', 'kube-public'] ``` -In the default configuration (shown above), services from all namespaces except for -`kube-system` and `kube-public` will be synced. +In the default configuration, services from all namespaces except for +`kube-system` and `kube-public` are synced. -If you wish to only sync from specific namespaces, you can list only those +To only sync from specific namespaces, you can list only those namespaces in the `k8sAllowNamespaces` key: ```yaml @@ -224,8 +221,7 @@ syncCatalog: k8sDenyNamespaces: [] ``` -If you wish to sync from every namespace _except_ specific namespaces, you can -use `*` in the allow list and then specify the non-syncing namespaces in the deny list: +To sync from every namespace except specific namespaces, use `*` in the allow list and then specify the non-syncing namespaces in the deny list: ```yaml syncCatalog: @@ -235,15 +231,15 @@ syncCatalog: k8sDenyNamespaces: ['no-sync-ns-1', 'no-sync-ns-2'] ``` --> **NOTE:** The deny list takes precedence over the allow list. If a namespace -is listed in both lists, it will **not** be synced. +-> **Note:** The deny list takes precedence over the allow list. If a namespace +is listed in both lists, it does not sync. -### Service Name +### Service name When a Kubernetes service is synced to Consul, the name of the service in Consul -by default will be the value of the "name" metadata on that Kubernetes service. +by default is the value of the `name` metadata on that Kubernetes service. This makes it so that service sync works with zero configuration changes. -This can be overridden using an annotation to specify the Consul service name: +This setting can be overridden using an annotation to specify the Consul service name: ```yaml kind: Service @@ -254,17 +250,15 @@ metadata: 'consul.hashicorp.com/service-name': my-consul-service ``` -**If a conflicting service name exists in Consul,** the sync program will -register additional instances to that same service. Therefore, services inside +**If a conflicting service name exists in Consul,** the sync program +registers additional instances to that same service. Therefore, services inside and outside of Kubernetes should have different names unless you want either side to potentially connect. This default behavior also enables gracefully -transitioning a service from outside of K8S to inside, and vice versa. +transitioning a service between deployments inside and outside of Kubernetes. -### Service Ports +### Service ports -When syncing the Kubernetes service to Consul, the Consul service port will be -the first defined port in the service. Additionally, all ports will be -registered in the service instance metadata with the key "port-X" where X is +When syncing the Kubernetes service to Consul, the Consul service port is the first defined port in the service. Additionally, all ports become registered in the service instance metadata with the key "port-X," where X is the name of the port and the value is the externally accessible port. The default service port can be overridden using an annotation: @@ -280,13 +274,10 @@ metadata: The annotation value may be a name of a port (recommended) or an exact port value. -### Service Tags +### Service tags -A service registered in Consul from Kubernetes will always have the tag "k8s" added -to it. Additional tags can be specified with a comma-separated annotation value -as shown below. This will also automatically include the "k8s" tag which can't -be disabled. The values should be specified comma-separated without any -additional whitespace. +A service registered in Consul from Kubernetes always has the tag "k8s" added +to it. Additional tags can be specified with a comma-separated annotation value. These custom tags automatically include the "k8s" tag, which can't be disabled. When specifying values, use commas without whitespace. ```yaml kind: Service @@ -297,16 +288,15 @@ metadata: 'consul.hashicorp.com/service-tags': 'primary,foo' ``` -### Service Meta +### Service meta -A service registered in Consul from Kubernetes will set the `external-source` key to -"kubernetes". This can be used by API consumers, the UI, CLI, etc. to filter -service instances that are set in k8s. The Consul UI (in Consul 1.2.3 and later) -will read this value to show a Kubernetes icon next to all externally +A service registered in Consul from Kubernetes sets the `external-source` key to +`kubernetes`. This can be used from the APLI, CLI and UI to filter +service instances that are set in k8s. The Consul UI displays a Kubernetes icon next to all externally registered services from Kubernetes. Additional metadata can be specified using annotations. The "KEY" below can be -set to any key. This allows setting multiple meta values: +set to any key. This allows setting multiple meta values. ```yaml kind: Service @@ -320,15 +310,13 @@ metadata: ### Consul Enterprise Namespaces Consul Enterprise supports Consul namespaces. These can be used when syncing -from Kubernetes to Consul (although not vice-versa). +from Kubernetes to Consul. However, namespaces are not supported when syncing from Consul to Kubernetes. There are three options available: 1. **Single Destination Namespace** – Sync all Kubernetes services, regardless of namespace, into the same Consul namespace. - This can be configured with: - ```yaml global: enableConsulNamespaces: true @@ -339,13 +327,11 @@ There are three options available: consulDestinationNamespace: 'my-consul-ns' ``` -1. **Mirror Namespaces** - Each Kubernetes service will be synced to a Consul namespace with the same +1. **Mirror Namespaces** - Each Kubernetes service is synced to a Consul namespace with the same name as its Kubernetes namespace. - For example, service `foo` in Kubernetes namespace `ns-1` will be synced to the Consul namespace `ns-1`. - If a mirrored namespace does not exist in Consul, it will be created. - - This can be configured with: + For example, service `foo` in Kubernetes namespace `ns-1` is synced to the Consul namespace `ns-1`. + If a mirrored namespace does not exist in Consul, it is created automatically. ```yaml global: @@ -359,13 +345,11 @@ There are three options available: addK8SNamespaceSuffix: false ``` -1. **Mirror Namespaces With Prefix** - Each Kubernetes service will be synced to a Consul namespace - with the same name as its Kubernetes namespace **with a prefix**. For example, given a prefix - `k8s-`, service `foo` in Kubernetes namespace `ns-1` will be synced to the Consul namespace +1. **Mirror Namespaces With Prefix** - Each Kubernetes service is synced to a Consul namespace + with the same name as its Kubernetes namespace with a prefix. For example, given a prefix + `k8s-`, service `foo` in Kubernetes namespace `ns-1` syncs to the Consul namespace `k8s-ns-1`. - This can be configured with: - ```yaml global: enableConsulNamespaces: true @@ -379,24 +363,20 @@ There are three options available: addK8SNamespaceSuffix: false ``` --> Note that in both mirroring examples we're setting `addK8SNamespaceSuffix: false`. If set to `true` -(the default), the Kubernetes namespace will be added as a suffix to each +-> Note that in both mirroring examples, `addK8SNamespaceSuffix` is set to `false`. If set to its default value, `true`, the Kubernetes namespace is added as a suffix to each Consul service name. For example Kubernetes service `foo` in namespace `k8s-ns` would be registered into Consul with the name `foo-k8s-ns`. This is useful when syncing from multiple Kubernetes namespaces to -a single consul namespace but is likely something you'll want turned off -when mirroring namespaces since services won't overlap with services from +a single Consul namespace. However, you may want to disable this behavior +when mirroring namespaces so that services do not overlap with services from other namespaces. ## Consul to Kubernetes This syncs Consul services into first-class Kubernetes services. -The sync service will create an [`ExternalName`](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) -`Service` for each Consul service. The "external name" will be -the Consul DNS name. +The sync creates an [`ExternalName`](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) for each Consul service. The "external name" is the Consul DNS name. -For example, given a Consul service `foo`, a Kubernetes Service will be created -as follows: +For example, given a Consul service `foo`: ```yaml apiVersion: v1 @@ -410,22 +390,22 @@ spec: ``` With Consul To Kubernetes syncing enabled, DNS requests of the form `` -will be serviced by Consul DNS. From a different Kubernetes namespace than where Consul +are serviced by Consul DNS. From a different Kubernetes namespace than where Consul is deployed, the DNS request would need to be `.`. --> **Note:** Consul to Kubernetes syncing **isn't required** if you've enabled [Consul DNS on Kubernetes](/docs/k8s/dns) -_and_ all you need to do is address services in the form `.service.consul`, i.e. you don't need Kubernetes `Service` objects created. +-> **Note:** Consul to Kubernetes syncing is not required if you enabled [Consul DNS on Kubernetes](/docs/k8s/dns). +All you need to do is address services in the form `.service.consul`, so you do not need Kubernetes `Service` objects created. ~> **Requires Consul DNS via CoreDNS in Kubernetes:** This feature requires that [Consul DNS](/docs/k8s/dns) is configured within Kubernetes. -Additionally, **[CoreDNS](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#config-coredns) -is required (instead of kube-dns)** to resolve an +Additionally, [CoreDNS](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#config-coredns) +is required instead of kube-dns to resolve an issue with resolving `externalName` services pointing to custom domains. -### Sync Enable/Disable +### Enable and disable sync All Consul services visible to the sync process based on its given ACL token -will be synced to Kubernetes. +are synced to Kubernetes. There is no way to change this behavior per service. For the opposite sync direction (Kubernetes to Consul), you can use Kubernetes annotations to disable @@ -437,24 +417,24 @@ In the future, we hope to support per-service configuration. ### Service Name When a Consul service is synced to Kubernetes, the name of the Kubernetes -service will exactly match the name of the Consul service. +service matches the name of the Consul service exactly. To change this default exact match behavior, it is possible to specify a prefix to be added to service names within Kubernetes by using the `-k8s-service-prefix` flag. This can also be specified in the Helm configuration. -**If a conflicting service is found,** the service will not be synced. This +**If a conflicting service is found**: the service is not synced. This does not match the Kubernetes to Consul behavior, but given the current -implementation we must do this because Kubernetes can't mix both CNAME and +implementation we must do this because Kubernetes cannott mix both CNAME and Endpoint-based services. -### Kubernetes Service Labels and Annotations +### Kubernetes service labels and annotations -Any Consul services synced to Kubernetes will be labeled and annotated. -An annotation `consul.hashicorp.com/synced` will be set to "true" to note +Any Consul services synced to Kubernetes are labeled and annotated. +An annotation `consul.hashicorp.com/synced` is set to `true` to note that this is a synced service from Consul. -Additionally, a label `consul=true` will be specified so that label selectors +Additionally, a label `consul=true` is specified so that label selectors can be used with `kubectl` and other tooling to easily filter all Consul-synced services. diff --git a/website/content/docs/k8s/upgrade/index.mdx b/website/content/docs/k8s/upgrade/index.mdx index 0e41d2cf7c76..13b5e2515825 100644 --- a/website/content/docs/k8s/upgrade/index.mdx +++ b/website/content/docs/k8s/upgrade/index.mdx @@ -7,40 +7,19 @@ description: >- # Upgrading Consul on Kubernetes Components -## Upgrade Types +## Upgrade types -Consul on Kubernetes will need to be upgraded/updated if you change your Helm configuration, -if a new Helm chart is released, or if you wish to upgrade your Consul version. +We recommend updating Consul on Kubernetes when: + - You change your Helm configuration + - A new Helm chart is released + - You want to upgrade your Consul version. -### Helm Configuration Changes +### Helm configuration changes -If you make a change to your Helm values file, you will need to perform a `helm upgrade` +If you make a change to your Helm values file, you need to perform a `helm upgrade` for those changes to take effect. -For example, if you've installed Consul with the following: - - - -```yaml -global: - name: consul -connectInject: - enabled: false -``` - - - -And you wish to set `connectInject.enabled` to `true`: - -```diff -global: - name: consul -connectInject: -- enabled: false -+ enabled: true -``` - -To update your deployment configuration using Helm, perform the following steps. +For example, if you installed Consul with `connectInject.enabled: false` and you want to change its value to `true`: 1. Determine your current installed chart version. @@ -58,17 +37,15 @@ To update your deployment configuration using Helm, perform the following steps. $ helm upgrade consul hashicorp/consul --namespace consul --version 0.40.0 --values /path/to/my/values.yaml ``` - **Before performing the upgrade, be sure you've read the other sections on this page, + **Before performing the upgrade, be sure you read the other sections on this page, continuing at [Determining What Will Change](#determining-what-will-change).** -~> NOTE: It's important to always set the `--version` flag, because otherwise Helm -will use the most up-to-date version in its local cache, which may result in an -unintended upgrade. +~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. -### Helm Chart Version Upgrade +### Upgrade Helm chart version -You may wish to upgrade your Helm chart version to take advantage of new features, -bugfixes, or because you want to upgrade your Consul version, and it requires a +You may wish to upgrade your Helm chart version to take advantage of new features and +bug fixes, or because you want to upgrade your Consul version and it requires a certain Helm chart version. 1. Update your local Helm repository cache: @@ -98,7 +75,8 @@ certain Helm chart version. ``` In this example, version `0.39.0` (from `consul-k8s:0.39.0`) is being used. - If you want to upgrade to the latest `0.40.0` version, use the following procedure: + +If you want to upgrade to the latest `0.40.0` version, use the following procedure: 1. Check the changelog for any breaking changes from that version and any versions in between: [CHANGELOG.md](https://github.com/hashicorp/consul-k8s/blob/main/CHANGELOG.md). @@ -111,14 +89,14 @@ certain Helm chart version. **Before performing the upgrade, be sure you've read the other sections on this page, continuing at [Determining What Will Change](#determining-what-will-change).** -### Consul Version Upgrade +### Upgrade Consul version -If a new version of Consul is released, you will need to perform a Helm upgrade -to update to the new version. +If a new version of Consul is released, you need to perform a Helm upgrade +to update to the new version. Before you upgrade to a new version: -1. Ensure you've read the [Upgrading Consul](/docs/upgrading) documentation. -1. Ensure you've read any [specific instructions](/docs/upgrading/upgrade-specific) for the version you're upgrading - to and the Consul [changelog](https://github.com/hashicorp/consul/blob/main/CHANGELOG.md) for that version. +1. Read the [Upgrading Consul](/docs/upgrading) documentation. +1. Read any [specific instructions](/docs/upgrading/upgrade-specific) for the version you want to upgrade + to, as well as the Consul [changelog](https://github.com/hashicorp/consul/blob/main/CHANGELOG.md) for that version. 1. Read our [Compatibility Matrix](/docs/k8s/compatibility) to ensure your current Helm chart version supports this Consul version. If it does not, you may need to also upgrade your Helm chart version at the same time. @@ -133,7 +111,7 @@ to update to the new version.
-1. Determine your current installed chart version. In this example, version `0.39.0` (from `consul-k8s:0.39.0`) is being used. +2. Determine the version of your exisiting Helm installation. In this example, version `0.39.0` (from `consul-k8s:0.39.0`) is being used. ```shell-session $ helm list --filter consul --namespace consul @@ -150,18 +128,16 @@ to update to the new version. **Before performing the upgrade, be sure you have read the other sections on this page, continuing at [Determining What Will Change](#determining-what-will-change).** -~> NOTE: It's important to always set the `--version` flag, because otherwise Helm -will use the most up-to-date version in its local cache, which may result in an -unintended upgrade. +~> Note: You should always set the `--version` flag when upgrading Helm. Otherwise Helm uses the most up-to-date version in its local cache, which may result in an unintended upgrade. -## Determining What Will Change +## Determine scope of changes -Before upgrading, it's important to understand what changes will be made to your -cluster. For example, you will need to take more care if your upgrade will result +Before upgrading, it is important to understand the changes that affect your +cluster. For example, you need to take more care if your upgrade results in the Consul server StatefulSet being redeployed. -There is no built-in functionality in Helm that shows what a helm upgrade will -change. There is, however, a Helm plugin [helm-diff](https://github.com/databus23/helm-diff) +There is no built-in functionality in Helm that shows what a helm upgrade +changes. There is, however, a Helm plugin [helm-diff](https://github.com/databus23/helm-diff) that can be used. 1. Install `helm-diff` with: @@ -177,80 +153,30 @@ that can be used. $ helm diff upgrade consul hashicorp/consul --namespace consul --version 0.40.0 --values /path/to/your/values.yaml ``` - This will print out the manifests that will be updated and their diffs. + This command prints out the manifests that will be updated and their diffs. -1. To see only the objects that will be updated, add `| grep "has changed"`: +1. To see only updated objects, add `| grep "has changed"`: ```shell-session $ helm diff upgrade consul hashicorp/consul --namespace consul --version 0.40.0 --values /path/to/your/values.yaml | grep "has changed" ``` -1. Take specific note if `consul-client, DaemonSet` or `consul-server, StatefulSet` are listed. - This means that your Consul client daemonset or Consul server statefulset (or both) will be redeployed. - - If either is being redeployed, we will follow the same pattern for upgrades as - on other platforms: the servers will be redeployed one-by-one, and then the - clients will be redeployed in batches. Read [Upgrading Consul](/docs/upgrading) and then continue - reading below. - - If neither the client daemonset nor the server statefulset is being redeployed, - then you can continue with the helm upgrade without any specific sequence to follow. - -## Service Mesh - -If you are using Consul's service mesh features, as opposed to the [service sync](/docs/k8s/service-sync) -functionality, you must be aware of the behavior of the service mesh during upgrades. +1. Take specific note if `consul-server, StatefulSet` is listed, as it means your Consul server statefulset will be redeployed. -Consul clients operate as a daemonset across all Kubernetes nodes. During an upgrade, -if the Consul client daemonset has changed, the client pods will need to be restarted -because their spec has changed. + If your Consul server statefulset needs to be redeployed, follow the same pattern for upgrades as + on other platforms by redploying servers one by one. Refer tp [Upgrading Consul](/docs/upgrading) for more information. -When a Consul client pod is restarted, it will deregister itself from Consul when it stops. -When the pod restarts, it will re-register itself with Consul. -Thus, during the period between the Consul client on a node stopping and restarting, -the following will occur: + If neither the server statefulset is not being redeployed, + then you can continue with the Helm upgrade without any specific sequence to follow. -1. The node will be deregistered from Consul. It will not show up in the Consul UI - nor in API requests. -1. Because the node is deregistered, all service pods that were on that node will - also be deregistered. This means they will not receive service mesh traffic - until the Consul client pod restarts. -1. Service pods on that node can continue to make requests through the service - mesh because each Envoy proxy maintains a cache of the locations of upstream - services. However, if the upstream services change IPs, Envoy will not be able - to refresh its cluster information until its local Consul client is restarted. - So services can continue to make requests without downtime for a short period - of time, however, it's important for the local Consul client to be restarted - as quickly as possible. +## Upgrade Consul servers -Once the local Consul client pod restarts, each service pod needs to be re-registered -with its local Consul client. This is done automatically by the connect inject controller. +Initiate the server upgrade: -Because service mesh pods are briefly deregistered during a Consul client restart, -it's **important that you do not restart all Consul clients at once**. Otherwise -you may experience downtime because no replicas of a specific service will be in the mesh. - -In addition, it's **important that you have multiple replicas** for each service. -If you only have one replica, then during restart of the Consul client on the -node hosting that replica, it will be briefly deregistered from the mesh. Since -it's the only replica, other services will not be able to make calls to that -service. (NOTE: This can also be avoided by stopping that replica so it is rescheduled to -a node whose Consul client has already been updated.) - -Given the above, we recommend that after Consul servers are upgraded, the Consul -client daemonset is set to use the `OnDelete` update strategy and Consul clients -are deleted one by one or in batches. See [Upgrading Consul Servers](#upgrading-consul-server) -and [Upgrading Consul Clients](#upgrading-consul-clients) for more details. - -## Upgrading Consul Servers - -To initiate the upgrade: - -1. Change the `global.image` value to the desired Consul version -1. Set the `server.updatePartition` value _equal to the number of server replicas_. - By default there are 3 servers, so you would set this value to `3` -1. Set the `updateStrategy` for clients to `OnDelete` +1. Change the `global.image` value to the desired Consul version. +1. Set the `server.updatePartition` value to the number of server replicas. By default there are 3 servers, so you would set this value to `3`. +1. Set the `updateStrategy` for clients to `OnDelete`. @@ -259,9 +185,6 @@ To initiate the upgrade: image: 'consul:123.456' server: updatePartition: 3 - client: - updateStrategy: | - type: OnDelete ``` @@ -269,13 +192,7 @@ To initiate the upgrade: The `updatePartition` value controls how many instances of the server cluster are updated. Only instances with an index _greater than_ the `updatePartition` value are updated (zero-indexed). Therefore, by setting - it equal to replicas, none should update yet. - - The `updateStrategy` controls how Kubernetes rolls out changes to the client daemonset. - By setting it to `OnDelete`, no clients will be restarted until their pods are deleted. - Without this, they would be redeployed alongside the servers because their Docker - image versions have changed. This is not desirable because we want the Consul - servers to be upgraded _before_ the clients. + it equal to replicas, updates should not occur immediately. 1. Next, perform the upgrade: @@ -283,8 +200,8 @@ To initiate the upgrade: $ helm upgrade consul hashicorp/consul --namespace consul --version --values /path/to/your/values.yaml ``` - This will not cause the servers to redeploy (although the resource will be updated). If - everything is stable, begin by decreasing the `updatePartition` value by one, + This command does not cause the servers to redeploy, although the resource is updated. If + everything is stable, decrease the `updatePartition` value by one and performing `helm upgrade` again. This will cause the first Consul server to be stopped and restarted with the new image. @@ -296,38 +213,33 @@ To initiate the upgrade: `updatePartition` is `0`. At this point, you may remove the `updatePartition` configuration. Your server upgrade is complete. -## Upgrading Consul Clients +## Upgrading to Consul Dataplane -With the servers upgraded, it is time to upgrade the clients. -If you are using Consul's service mesh features, you will want to be careful -restarting the clients as outlined in [Service Mesh](#service-mesh). +In earlier versions, Consul on Kubernetes used client agents in its deployments. As of v1.14.0, Consul uses [Consul Dataplane](/docs/connect/dataplane/) in Kubernetes deployments instead of client agents. -You can either: +If you upgrade Consul from a version that uses client agents to a version the uses dataplanes, complete the following steps to upgrade your deployment safely and without downtime. -1. Manually issue `kubectl delete pod ` for each consul daemonset pod -1. Set the updateStrategy to rolling update with a small number: +1. Before you upgrade, edit your Helm chart to enable Consul client agents by setting `client.enabled` and `client.updateStrategy`: - ```yaml + ```yaml filename="values.yaml" client: - updateStrategy: | - rollingUpdate: - maxUnavailable: 2 - type: RollingUpdate + enabled: true + updateStrategy: + type: OnDelete ``` - Then, run `helm upgrade`. This will upgrade the clients in batches, waiting - until the clients come up healthy before continuing. +1. Add `consul.hashicorp.com/consul-k8s-version: 1.0.0` to the annotations for each pod you upgrade. + +1. Follow our [recommended procedures to upgrade servers](#upgrading-consul-servers) on Kubernetes deployments to upgrade Helm values for the new version of Consul. + +1. Run `kubectl rollout restart` to restart your service mesh applications. Restarting service mesh application causes Kubernetes to re-inject them with the webhook for dataplanes. -1. Cordon and drain each node to ensure there are no connect pods active on it, and then delete the - consul client pod on that node. +1. Restart all gateways in your service mesh. --> NOTE: If you are using only the Service Sync functionality, you can perform an upgrade without -following a specific sequence since that component is more resilient to brief restarts of -Consul clients. +1. Disable client agents in your Helm chart by deleting the `client` stanza or setting `client.enabled` to `false`. -## Configuring TLS on an Existing Cluster +## Configuring TLS on an existing cluster If you already have a Consul cluster deployed on Kubernetes and would like to turn on TLS for internal Consul communication, -please see -[Configuring TLS on an Existing Cluster](/docs/k8s/operations/tls-on-existing-cluster). +refer to [Configuring TLS on an Existing Cluster](/docs/k8s/operations/tls-on-existing-cluster). diff --git a/website/public/img/dataplanes-diagram.png b/website/public/img/dataplanes-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..36db6554ffb78f43caf4a7d1de0d65839686fda1 GIT binary patch literal 171551 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�*bp6bMi20x5fEAa#l;G!MUzXoqPe#UkOap|KX%y(VIvKdJXCsN@7J zIT-Rd2si$2c`5mPVCvnQwW=c*4;%%XfK=Y+*l75Y7+IcA_L}= zlXQInC2Tet`0Z!6+)aX`dQ6^BtAF|Fd{=)S-Nm)e1>hQrZf_s3fhIFjyXL6B*?k|f zo0?p+K5zg&&J}xu!HRlh108bMN(wQuxm>xL>#$wcZbD+<_+l>bnPzu+Cl&%L{93JD z)G=(f$74byAGI!5s4@LgeA9+eF!wcJRquVLzG8ISujg5Fro0hZ2Y*h%c!|e9a;>Tv zPk?}<0gLp2pGJb#3|**;dQA$0+|)CKx!cSFv$cdE$9n^Px#@#3c0x)p zQMFs+mValJ>?%f+yqGHQ37iVp4mSZ`OVe3Q@N`Sj{{VW45{pUZLzqO4gk=N&J+%0; zS5GW+yb>cxUxGOliabJ!pS%ET|$Jx0gDF>Jr}USbCkM!)*d zhS@=;Gv0-Vp75_%f2w*bE`a3Ki~mSg91WQH4N_0?Qd$QG-3 zr8}MC%(80A&GK*hrWr6$r*7xYQ|-g7*DmkcKF-_P>MhCw2E+x#vesWIh1WVaJ7F;j zl~v0l?e%_3?z# zkYQV^b4LaG9*i~-{dZZX&pUo?q)0fd_z!Y`*1L$Wl1}fqPwqzp(8tfAG|AnEU-Yvk z_L2yfK5=**>~LNB+$4`v$0Esi?T}Dua$UUd0p9W0%lc9OHMKrp)T!&Lao3kR2Z!DMNoguJEjI?-9~nZ<;p4R z-=FANxK@6WXJpdWyU#2oq-z|-f&Vn=>$tkg8c%N0gAx=l`d@{>-{RmFml#?(R2ogw zEB^3zrYLJwqiu%WIy;ybK#dx`iNaVH2Nj(NV(Uj>QX5<4W-90|d|+2H>d2b|723$^ zdgNHPH1Z+Z8?i^EKtZ!VQDAsI@()uUL;EU)J(Dv%ptQ_cmYlktA5K3R>>GFXkWaiw zsn-f`Q6p$cC;I@4!NF%!Wx3FHHlS^bMy4aXso&=ZVTu3eC!kQ%jAeeptGFGK+S)~o zFGrDHgtP(3;hQ+PuIhlTUn|DQIyg4z9oE2z%V+F^2ua@Q>`U z%0WNDpfA9&ad0IFw*^)HxR;4V-;LWiwI1knQ2f`tV`;V!?MHZBd+r9EG%?kvKne&D zc_FUA9n@?X%>FW1S_y5FmV_7~;TQdg-0l}Kq|s;XU3AR&N_55TZB75ire97Qenr_B zV+n9L7*@CWhb-_~1@_0!M>PRA*?NS)Go{2Zu=R6y_;YgP62p(ni?EwVC6vyJCL_RV zYr|5eHPp3i%MC{VB#qnk4JF1a$X*i$Vf5$U50utn(@C+qijS2)Z{`lN!Ajs4z^73Y%BmYs5fr z#NA#yNp`?r)jI-n+CrGMu@4$Dbij}ny9w+QR}HG;n$Re9>Mv4xtLzo=((EA`mV)f< zWqsi6`dym`BOt*PzTlr=&a0B|kP$ctzpwcomI5^e6RMOP%on=eq#eL}^&<<_cZ!%A z^;PfwjW4tupg0x+xJ>rM`o1$Ox(StY&mwr)&aZr?8ii$2ej%-LxNaiu*FTEJDg#E` zouK-(D}qxc9-D1H2>=do^x5^_A^UwrGxlZc0v8xmzOj>kIpbmo(7gEVQx|`-X$S%m zvI5ZZODypVw7H!~-kHR=?!LuvMcGt@j`S;?!t0;&trquhb_(h+YQ z{fn^R#K_vEpF0!qsa>+4aoUlWej9gz0e_BHkERel){uX7Wc2z&JQE|AEVOcQ<)o=q z>`um7W0cL0`D1c}geHt`MO{CfY^?(5w&AnDfDAotDW=-e_t zpoNK3=(;iz)o#6^;ofG>hGGaF(Kri5Z&J_M{7t>IVM@*{u^LpuLu zf(|(*0r~Y}bjVr3JsV$2djyT_kx_HopD+S6$S#;)A#u1_+S3bT5rv6c9>AZ4-d+!BtJat zs>O%sNJ4IkU&EKb{scpgxOhMYmStbU;#RDhT{7VD$!%mrNO~C&vGZLu7sH~tbmWOy zrfiR&C)ooGmB;wOuMdKA4y!nB)L$=*clk!$lwc9S3zHjSY zo`vKx^(a97%M=$MalqLp^uK#wfMC9nuE)iH`;Hr;7u_)Ai?6P-n~BCwgpLopfQF8b znNwd#a_`yH!p*jXI1!kHwn^p{I3Aqy29@4obbvNuHgK6oje~PKrHTB8^|J-bA~kUb zd>FI=y%PJ)`XF)3eobb<74Vz_U^dtXD@Y!gh^ihE*~&a+8Zy9!VEzSN;xN)~razub z3=Mn`Oenr4+7+U2_oVGS1=CWws{^ps21>NZa2peyJ#{kiz=L2H;G7UbI)fPTKr3zV zCPzpv+1+4mfwCd8?qv&$&s8#{rgSlSq&*e4!Q+0=AfF%9*A=1TL^CMoWd|Mr-GiVv zy*Z>pFTvogh$?jaP5GeaQrIngW{990)E&$emJHt8b9I_e^69*+WAzlh#ZgX&9Jw?q zv@4`0qT-?7FU{okIlENbjYN}Lp*OCTQ^zOV$@2rEgo1}`GsluMYosP%DnCqQ7Lrok zG4mFgsiUjY&MW8(TJFOt-Rm2#A67Kifb*;*vMh{5XXD|sccF7M>%--^ggZuwJ8hoRd zkyU4T=Z^3jUiX@eq2FnMU>pey4SkP@?e#PNyS`m5wp9e3RDIW?hIKKuw>RcI`v|}` z3uEk2ZVz>sQC<~T^Bf9adzhnio#!UL3|xeSIU zuWctZTIzBE8gf<8+YrOj$o?iyxhu_Yen!K~eQPyFbR~jPqAVkCGT?vghkG7E1{CaM z|F*DfBzK_nG1fDRnT}ahH6$R>en#cKteDxW55HO*4e4X_QpVUv-d=#%g8;s~u5x}7 zopifwMjF%2j|d(&dNoFmWk>QseFV_MTK(^V7a-4>@H1ef(lCggz!PN=pzJx!J<%B# z2U;#v5$NAkq|zR`r_YT~Cn!4N&m#u}&j*Q8xnXm76}vNt1^hSibikl%kmz5}6TEV% zNM~^0h;kQ>lFF6J9{unkYogvEd&_-4Vb*%*(r9ZBX@Wrl%((hj zK92Jg!ZrK7prgEP_LNj>So@`iL-V?4r@Yv>=qGCMq5^NvOD*&CX%$a&$<^+1hRLcu z%qebZB|z5;pe9s>dn&;@bKo|kluFLytxv(aj*l+*8!$XGVR);erkPAGabfkE#oMRX zW6Hm@=@NeDe`Eo45cU^?;)YJ8Db~z^#_xiwXlB?%opGjJ-rpF-vGDyYGe;ljfv=>1 zkVQ^ALRT0`)2cE*IFH(kXiHehG9nOU6hTL7517ofRqSEGQQS}a3Vn27YWbN~wWj2% z{-y5;=w56`M4!kDtZRv;C;s>IgwX(#w_NzNxWiQ3jvDEl7F6#ktmQt9%%pp2B2UIL zn@C!|p7=vURyU{&K;&ueQKp`rt! z#GILce3%e`n(>KPa2}~Y)D>bVgOEBac`A8l2en3K@|2D&XBX=}Ot{a4nn z280)$9Pei^hL~SR7dy#*8r0LX1pTGz$H7p<@6gU4kx-$sn7Uh}QQE3IHy*J4ZbQ#k zTNHs00ud5nZ*t+cOy;m@d_x$HX&pXgH~LCJ!{dPV|())U~wZD&O5NN!NJ*~&1W_1E59!m8h!$QcdwA*9gr%zbo~tqJ$ z`x!}@4N3W1&suQRe~TKvoFNq)#C2z%(6!)R=&9`F3#jDYufm%@)ysNS$UFC)Lf+Yr zlR$O_d7d{Yk{>LybT3DLPTj8;8IJhDd;=f*PS)HLjnA(?R+T$!d6D7DWky!ma z!7wZ|Uf@tI!Y>^80R+rFebyc#(9Q-goYxbR@qxSf>J`1NP7(EyiIdk@z;8UKtIu!_ z9e+{z4pxk_xe31?3TKPoPD#J|1z6AaPvEd&zR{}Zy`N|Gk?i$y^n<69_yhIVDepMo zhL>4cX<1FCf96{5gZ@^_iJsM1{D@S5Bm0O{&>t9`7f7#|vL}p=zW%1R=``+Yx$0=| zAkY{ydYQC)(Qxe@K|$VUF0x`OD}f>(9kI0fcQUt-rg#`4NV$F00P3G^EZw?K31=Un z2ObA(>mg+QK)L#Te)_l^2NhOQ`i?Vd;L7 z57jj{e1sz4mvlvQ7wR@+TU8As-6m9<7|wa|mqm+s@~Lqu@Ts3pb@-lqYZHQo&Ij&g zVoh=SeL)8cd4Tg^4tjGVHo(qa^D}3g^{V_QV%54&?Yj2ho9(ePa3ch%fR$ye7mzqz zj{=+j0F|$xiOV0)oX;F&?3Q6Z&fP-5jh;y6aWc$FB{j#X^{?N}dbAa92QSjj3dXg+TW{!HTN;{s?GevuEn#4pH5E?Q2%+K+F-3^YMD;|!&W$V$yYsw#BE0KsEED1n#A!(*{ ze*U6T49J~v!{9RbSmvX<{7H&4G$Dc7I*#@3-OXWo5gCalJHJb}&gsRe9FBFR=MkEg z-c;=YRZpX7qdn=&fll&V1K1*4G>ERb`|ICSkZ&xBS3lJH36Ur~_O1gU{Qt%-Umr_X0S_ zV;I+2YZ@tRzgb~JYSF>KV%kP~u;pH!hVb8c%7ScZ>2S6{cY9*=pZC2{?>MqI|M|zw z=ZW-sKkUi3gPL|9#~>Xu6WAC%uPPWha6i`|iY~Af3n&=EWP*NP+~-fr1;_)UHCKXC zAo!Z1q5r;tDC`@{8n^^aTum)z zzAXRR%H^N&mm0uj75}uCvbD^Sj`7TRBjds3cj3nl4D}Ptp!yt@BK>AR;!?8hA))6- zzrRNUg|>J+5+9P$P@wm+TH-je)^K|`xGhPQg)OZN&Ng!S=s=!{7xow3-cr~+uwwq+ zY3KsHPc=jl9Gg#vm#G{R#VJ~G3jB8++M1-mvJ!_N>or2J32%x)Ihh`<96oS~zOR9_ z+*O`J>I>=Fmu0(1lc~F!{``1-wH@4rIL5SYo11M`rI>rQNdULXZ_~0OEltr+9MKlq zHn+L*IPmEO_}%L(B>bqRlHa;ZWE`T-B7Mt{yP)z@av!3 zDu311LXJyZ$K8`HfX!oEA7j&CiRP6mEV}GCFFO?5*RX?4IypS!qN;q-&K1>F!084h5x4X%LX^Sb0=bx*Mds zyH%vS8>AcQhW9M!^Z5O}@p=6NfnDxB_nA2}*UVfq_r)EZQ1bNgn+s{izTqFzI?6Q( zSMC7wub!BerXX`j9vWMiOwG^F(4tzbyRSlgs5%;t{EcnXsy;!?<)0gnAzZM}0*4Ri znOi$Z=*uUq4WF*2DIu>@C*FxYK0e;*5x*1lm38WbU>S7!y{utCbI_EP{T#YJW#x7t zvohsvw1C`*JQO*GgZ{puwMm1)RA`~vOn6P+SYvnBS-OSU`^j-@KQ%H;a#qG@wXo-^t_{WGOxV|n%x7b^ReoMA@YPQ& z3G(|<7rFhXa#^9;7(2-|*$Y+0X;QC!MSh^A)i^kP5Ypc9FD^top&VleEm<&@8i(!9mX&{0Onx| zIg+C_26G&!4I>KI?&mJfD$7X$PEuc1WNhCke%rK;C{D?t{KL4f4a3JLu$-lXd3Z%` zu3rvY9khDHV+IzLqf@ODe{w!Ozt4*q_mxdM_8SW_j?Ys#Px7Q9xr?sI@qyL6lem3@ z%d_D=`k1x0xP*RrIbmWpEyhRCPsnAj8N;NL>n#2?8?X}0fl{tkM^POX$GXGV5)yrK zIOB<+*P5-Xt)_WmRvHD+;|31M4;oj)AKB&$E23xGQ;ZW(!-eLD{bf?7J^=TrRU5rL zd;wwK+;YMw%P#axli0CG8}Wy2p={RJf4p|`ZWw|0n<8$Fs0-BGOWs`Nwo9|I8AP%h#5h~utuCl-ZOMsQrZe_J*8X=+wx@2C%Vy39fX zT3O3}a!cO)swk$>*K@gJK2xA);QMkSA%O|8 z2V)>CQ#-xr5dg7JGAt3I?*zR)B|lJU$Bmwr;*=jsDNIp=^^6lhi5D|{U-*k=a#}4d zddg)Y?$t71mTxgHk*2YdkFyMuCoo3bBA#=|(N9fF74si_A^Q$jDCBMe)H|flAY&Wj zF~;M`FbRU_qA?lb7@p_BvuCbyRllKR0zB%dhi0}+aktQ%wDWOFx%k7uc+jsgE zr7u?8l2zC$4AChsIjOkCc7YRY8Y?LV9Ye9%DVnb|bd0%mg+&qf(FFma=(=&LQH6bB zR;5l|{eo`M?BgKY(@TPLNh8C>*2G4SE{i-YeC$%2nnDe;XcB7X`=f4oawld2wseKoO!OMPkyBEdNej8HyY<%75Zkx4yZi9;otmUXWS&M2 z@yk6I09?>(8oq6HM^ta-hLl|VOyJ=BRh!%d!#LvYqsUNs3hWS6?6G_gQm?nj6)Jh0;DxGrcJi&e{em|kG+=dH*C3NSjm~{xhNseSokjr% z+sX6gG}_MD6n0VV*YkIT?g#aJ_W!t<&xYB2O?-5(b_AAHP=I1syP~gNyt2fQ40CJYHb?TM`cCrh zQkxXjQ4McudA+2Qx#A1uTDQaRhDF^pwu=>wNIRbWmVweYiv+nP$uBCFw2$X54r51o zuo-)87Y*aAEB0Wy4nvPV}^l zjOWps(UXov06jlrdK1F#b20GsFb+;iIUHWCe&#++dwkL5DH}6B}XPEqmjgF0dw4Ohz#`l?snD zwa;(&l|N2Tv|%2UD2qHTzihXw8KR+2`(d&sKCaS|(9Tm$VaY0M6jlB4 z#q`4Yn0VfYnC?TGCZJ-jnq_eU)R&H5jcEqa-=_mf&12pR3;pDw6dXdsYE?4tPs$t> zB~Dc?e&2+D?&3ad@h(D~L@4OTx2ZhnS+sRj7(CUA#;(KHGr~7Q(0t8u1x!p#7ws>o z-%5yYGNnkb$8R2e#8khWGCH11h-j_XVduExrX|NFcleqN4ttKVTq2Z=cPe=5HiAB% zVP(NF%uQZp&ekzM|Fg(oXK6mSeKV|H+o{-Er#V!ln{3SFwo8B_CCg=uW9bC9+52Jz z?W2ibyB!;M7Co}M?W!i(62IU& zP=Nh|1!Anx>u>=5h@e96jjIu1Gwzf006u~z#avaW-?ez{f{$x1%C;viMk*7mokc9e z2oF5<;vMaW9`7EzbIAeGS8v)q!MT>MCJ*`pUZP=Q>@ZytSIvv%T^ zE5^WJMkiGqYv~N_rH1s~Ic7YL+rm+3ItApAn^n z3ir8(&<@@Dy7o#5@;E9gU-6{1t+6oyPCPm5Z5;;DvF#?cVdFwVB;+LZnVd$ku9f#( zp6wT_m;hzdzH53eO@6HPxXyEmn`1-n)BbuW(#~LE1PKx+le_?VAktS~3E_K?$IL3LwnO4fu9f#%Ao;i@?Niic-sv6VL8GcvoFhvQ=(>T*qa49c;S+}jP^ z=2;b2UKBiZ)#TH1lyTs;L+%2qmG+R&ylA)QlunB#KbOfN&nlPCgoK~B;-Qf6@vhD2 zuY()W@8lCg$EVLj*xi+)oF4}TJxd1;o@@U+=!EfDy_In(s{YMNGjr3BYnFfe;95M_ zVFWQ;CZBTZJ_=QrJw-RmQ9GDLWzf>5J}%x((X(v!?B3|%J@JA;QBY|P@Zv}Y z*lQPWN0KpC$apJwa2@yev9h#Igmb_0s?n)a*5MC{Ti5_k``qY93m`Z*QMXQN zIUDkv(35*7Fj5hoyq9X(T$wjbQgU(l*)j}cWyAE?zA8Okb}C4BUB`mLp`cJgg<_0l zS0aANMI(Iyc1{uWim%qp0NzW8T;f(;rj+NrByGIddZ-X8ke{p0pesCjQSGJhaj#p} zGGyZ7d&haXZ_NGZ!bMk!AvtCQ>N*_iBsh2f2u{EOE}wIe(v-W^=VN022g)|K60!)u z$MDhj>C~nT$mi|u?mp>Gz6zPt6urEN)+X^ZKpX>d9+NjY;B1rAJt|Z`tXenEm1bCl=w2{U z?5Jw27-|tU7Z;7c`N05=Upa7QtUvQ+fq?h zD)_kRo;2y_w%8AG8TmmMrmad&C5jOaSq=AJN{s)1g)ebo3H-mS#cLDSb$W=Gky?XW!VI2s8BKO~iVb2LR_ z&)hSnH`b-aDWtd;{bEE%omNmGtQ8)OIjZcg#WO$=nw8ch;>hvTUe{92a3E-+CYk?O zTo4T9_B${)+xJZw@V_Y(zIW#q$**Kjik_7~ku0Hyr)G*Rb*p$qsjG?0q}jp@b4mhe zh1l|&k-;)+8K$Oe*^F7MQnsE|sn0NnF4FpG{L2RU%kjD6->?*4=c2r{krBBc7VR&T z=*TZ-?DT6ni6mG+7_Co_-8T#6*X(~ z^9SEeyJO@zM?WTxrYQ9ncE+Zi+X~H2mgoapxXJyft}4vmwc^Luy5y*`(cJA$3U)^> zpWkaA;tt>+CdXp7Aa6>QSd_zrxa0Qd5j8tK)$Lk}?ZNubqj#hGvllfz6~N+y=)clR zOYcu^!R#OX#0-?r3m*mDr*KyLi#*~7Ka|x~5`)XHR5l$gZco)mx0{0fs>uem1+^SP zAF@9?$HO`6S|jzP@k@^-B*96Tm&?y1y)1J~7J8k+E;=|t(fCuSN3AD^w%LzY;Zy_i z><~&R#uBYGRZ5mA3dTYBQdDm@iX8WrsrqJyFls4sN4Sm+!#}phbq&HjtPBI#X@ZlN zDH$B9C(xQ5hOOelvqmFP8K<1qBHqGE&_nMeuB1q!es0(DIYR`Wv6L!U`P?P4s=E{O2!JB{{L&C2zn*Fl+4eK97?o zhbRkPjGS$GkI$c03lurX%TZ7#5c3bp;ff3qd0l4g1iGDn_uN})gJ+!@cprMj_8a%* z36k(OG~cMv_sk61UksP;=1*tq6D678nuSDhK{X}Pv4fC4GH~c`B>R8Axsz);x*+$k z?@h8hmTlf@*^~+uR07>+W$5G0b*k%JU5TQn!!U2v<)x3ii-O{H_U{d$(@>;Hf9ZUx zqfq&!!8oRKU5#*i28HclLm`LYE*WtMkJL`zhj#typ_bwgB5#Cio7>@8)kVFhwx^k( zN-XYc{A*Jt(Ia4aQ^5Z`+Rw(|k?82~2*tdO`pIZhXhsNmb*)D_o!#x=Ll-D1F06qE zLQH&fEM1X=SB8;MQKl9b8WOoIf?sSQAS(%?Y!!%LIc4~_!6y%zWJB&DgQv6M%*nn7 zi4AZr#R!Q63<8&pn`o2`FX7*na=x-ZN$s1J6qw&65w>~UoT%y8tl{iwE}K0-QXgw3 zy|#2NU;4CMGBCJ4JmSLP8T>k6HGv7KgC@1Os=E4raI{H{tz&VFohr#TQdRJ$-Q z7R5QMRqn9T*3{|mriNFDO8M;0YZnh4KsJ8%ZHzY7JR_r(}Mk#sdzWeq# zVg0m~K%h9SOy?l9>@=2_q4_Seex8Z!1ga*_tKm!=^@!NT_3_>lvr!FeSUl&{gF%K6 zvIV@Raeu-)#2ad18~c*6?a>TBxQQej?@=&WZ*Ez>38>glu+w}{*Cj=vgJ*A2UYt$dp)-#O*UVZv(0TK<8!!1g;R^#M`nNaEirfV;HBWclru97Ry*~Cp^3(AGI|BFkCM*QJI=q$3>FME=&aB@j!}1;iq$h zQ}|%tE1`7CtlYp-$xj9^QeAP5c)RMJqsB+=q~~mg25tAYJa9^rBjNj2XyRf1v^^%Q zda}&Ew5Yq{A~5A(njE5jsO$eaoHd|UN@AaMzUIvQLwS4rdw4(uGze?KvrgEi1EeIt7%vjACvKfNoyy+W|JTJ;E!kPkAT&B zAns)v{9sPxU3J*@PsV7f=xvg{CDxZqdQPQUpZf&+Q9J-t_c!5dL5YsvhMH z${(f(&+T5kC%U;R%ID}mejiWieQROb0WXg##WW3;|3*+NF;!~LW=Rg;`bBtF{K&x2 zx{zm2l$c%f`{o0JwkG{k11Xv4vsbIejR_#k7pSY>?v=F(!7bo$?om(r$|ifKj@6!- z+@jL>Y{6gL-6McK?MW~vtAE_;CSi*njJ6P(4HVJ3*b5 zGUtBf;`uaoLcxIkocG&>g?($~;cP>s#o2y>@od;%Ul#Ye({{w3&vMcDrj%OC|(ts$wUt3SQ?5(R&Kiv^q;g zT7PG#|JJmp+-@>iS`&oGo66GxkeoK1+s{zDc*K;|H&tFwOaQAg2?J4f0hW+L|1V$30s1^$L2VPGg@xj7olx%paJVbjB<9DR$W^S2b`0r2L% zt#W1CbEpHu!x@$76uCs3{o}a2^*?<&l(x4o4U3l61bOrhNHMB!&R z3(6spZYPx?1_{kBWH(jfekT5+FVu})d)b!55?T{hDX%kxsq1;-D}`l~Z~-N(@i^EF z`G=hMZ`0+>h8RV zGC^*t2>&9{6!W1w3e0{_H}0>Avq-VLJRe|ft7z=<*0M!iYRyudpu!90h>bX!nfF*K z62&sbkltC6t$%Q7&Ufe2R&20~nM~T*SCa@(ZoSY7utiiefpLg#h%0AdZ`Rt20g>V+ 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 Date: Thu, 17 Nov 2022 15:12:32 -0800 Subject: [PATCH 16/16] docs: Update admin-partitions.mdx (#15428) * Update admin-partitions.mdx Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- .../docs/enterprise/admin-partitions.mdx | 64 ++++++------------- 1 file changed, 19 insertions(+), 45 deletions(-) diff --git a/website/content/docs/enterprise/admin-partitions.mdx b/website/content/docs/enterprise/admin-partitions.mdx index e07638f6223b..a60cf54c2af8 100644 --- a/website/content/docs/enterprise/admin-partitions.mdx +++ b/website/content/docs/enterprise/admin-partitions.mdx @@ -137,7 +137,7 @@ The following procedure will result in an admin partition in each Kubernetes clu Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernetes-requirements) before proceeding. -1. Verify that your VPC is configured to enable connectivity between the pods running Consul clients and servers. Refer to your virtual cloud provider's documentation for instructions on configuring network connectivity. +1. Verify that your VPC is configured to enable connectivity between the pods running workloads and Consul servers. Refer to your virtual cloud provider's documentation for instructions on configuring network connectivity. 1. Set environment variables to use with shell commands. ```shell-session @@ -154,7 +154,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet $ kubectl create secret --context ${SERVER_CONTEXT} --namespace consul generic license --from-file=key=./path/to/license.hclic ``` -1. Create the license secret in the workload client cluster. This step must be repeated for every additional workload client cluster. +1. Create the license secret in the non-default partition cluster for your workloads. This step must be repeated for every additional non-default partition cluster. ```shell-session $ kubectl create --context ${CLIENT_CONTEXT} ns consul @@ -180,7 +180,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet enableConsulNamespaces: true tls: enabled: true - image: hashicorp/consul-enterprise:1.13.2-ent + image: hashicorp/consul-enterprise:1.14.0-ent adminPartitions: enabled: true acls: @@ -188,20 +188,8 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet enterpriseLicense: secretName: license secretKey: key - server: - exposeGossipAndRPCPorts: true - connectInject: - enabled: true - consulNamespaces: - mirroringK8S: true - controller: - enabled: true meshGateway: enabled: true - replicas: 1 - dns: - enabled: true - enableRedirection: true ``` @@ -212,66 +200,66 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet 1. Install the Consul server(s) using the values file created in the previous step: ```shell-session - $ helm install ${HELM_RELEASE_SERVER} hashicorp/consul --version "0.49.0" --create-namespace --namespace consul --values server.yaml + $ helm install ${HELM_RELEASE_SERVER} hashicorp/consul --version "1.0.0" --create-namespace --namespace consul --values server.yaml ``` -1. After the server starts, get the external IP address for partition service so that it can be added to the client configuration. The IP address is used to bootstrap connectivity between servers and clients. +1. After the server starts, get the external IP address for partition service so that it can be added to the client configuration. The IP address is used to bootstrap connectivity between servers and workload pods on the non-default partition cluster. ```shell-session $ kubectl get services --selector="app=consul,component=server" --namespace consul --output jsonpath="{range .items[*]}{@.status.loadBalancer.ingress[*].ip}{end}" 34.135.103.67 ``` -1. Get the Kubernetes authentication method URL for the workload cluster: +1. Get the Kubernetes authentication method URL for the non-default partition cluster running your workloads: ```shell-session $ kubectl config view --output "jsonpath={.clusters[?(@.name=='${CLIENT_CONTEXT}')].cluster.server}" ``` - Use the IP address printed to the console to configure the `k8sAuthMethodHost` parameter in the workload configuration file for your client nodes. + Use the IP address printed to the console to configure the `k8sAuthMethodHost` parameter in the workload configuration file for your non-default partition cluster running your workloads. -1. Copy the server certificate to the workload cluster. +1. Copy the server certificate to the non-default partition cluster running your workloads. ```shell-session $ kubectl get secret ${HELM_RELEASE_SERVER}-consul-ca-cert --context ${SERVER_CONTEXT} -n consul --output yaml | kubectl apply --namespace consul --context ${CLIENT_CONTEXT} --filename - ``` -1. Copy the server key to the workload cluster. +1. Copy the server key to the non-default partition cluster running your workloads. ```shell-session $ kubectl get secret ${HELM_RELEASE_SERVER}-consul-ca-key --context ${SERVER_CONTEXT} --namespace consul --output yaml | kubectl apply --namespace consul --context ${CLIENT_CONTEXT} --filename - ``` -1. If ACLs were enabled in the server configuration values file, copy the token to the workload cluster. +1. If ACLs were enabled in the server configuration values file, copy the token to the non-default partition cluster running your workloads. ```shell-session $ kubectl get secret ${HELM_RELEASE_SERVER}-consul-partitions-acl-token --context ${SERVER_CONTEXT} --namespace consul --output yaml | kubectl apply --namespace consul --context ${CLIENT_CONTEXT} --filename - ``` -#### Install the workload client cluster +#### Install on the non-default partition clusters running workloads -1. Switch to the workload client clusters: +1. Switch to the workload non-default partition clusters running your workloads: ```shell-session $ kubectl config use-context ${CLIENT_CONTEXT} ``` -1. Create the workload configuration for client nodes in your cluster. Create a configuration for each admin partition. +1. Create a configuration for each non-default admin partition. In the following example, the external IP address and the Kubernetes authentication method IP address from the previous steps have been applied. Also, ensure a unique `global.name` value is assigned. - + ```yaml global: - name: client + name: consul enabled: false enableConsulNamespaces: true - image: hashicorp/consul-enterprise:1.13.2-ent + image: hashicorp/consul-enterprise:1.14.0-ent adminPartitions: enabled: true - name: clients + name: partition-workload tls: enabled: true caCert: @@ -293,31 +281,17 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet hosts: [34.135.103.67] # See step 4 from `Install Consul server cluster` tlsServerName: server.dc1.consul k8sAuthMethodHost: https://104.154.156.146 # See step 5 from `Install Consul server cluster` - client: - enabled: true - exposeGossipPorts: true - join: [34.135.103.67] # See step 4 from `Install Consul server cluster` - connectInject: - enabled: true - consulNamespaces: - mirroringK8S: true - controller: - enabled: true meshGateway: enabled: true - replicas: 1 - dns: - enabled: true - enableRedirection: true ``` -1. Install the workload client clusters: +1. Install the non-default partition clusters running your workloads: ```shell-session - $ helm install ${HELM_RELEASE_CLIENT} hashicorp/consul --version "0.49.0" --create-namespace --namespace consul --values client.yaml + $ helm install ${HELM_RELEASE_CLIENT} hashicorp/consul --version "1.0.0" --create-namespace --namespace consul --values client.yaml ``` ### Verifying the Deployment