mirror of https://github.com/hashicorp/consul
461 lines
16 KiB
Plaintext
461 lines
16 KiB
Plaintext
|
---
|
|||
|
name: Traffic Splitting for Service Deployments
|
|||
|
content_length: 15
|
|||
|
id: connect-splitting
|
|||
|
products_used:
|
|||
|
- Consul
|
|||
|
description: >-
|
|||
|
In this guide you will split layer-7 traffic, using Envoy proxies configured
|
|||
|
by
|
|||
|
|
|||
|
Consul, to roll out a new version of a service. You can use this method for
|
|||
|
|
|||
|
zero-downtime, blue-green, and canary deployments.
|
|||
|
level: Implementation
|
|||
|
---
|
|||
|
|
|||
|
-> **Note:** This guide requires Consul 1.6.0 or newer.
|
|||
|
|
|||
|
When you deploy a new version of a service, you need a way to start using the
|
|||
|
new version without causing downtime for your end users. You can't just take the
|
|||
|
old version down and deploy the new one, because for a brief period you would
|
|||
|
cause downtime. This method runs the additional risk of being hard to roll back
|
|||
|
if there are unexpected problems with the new version of the service.
|
|||
|
|
|||
|
You can solve this problem by deploying the new service, making sure it works in
|
|||
|
your production environment with a small amount of traffic at first, then slowly
|
|||
|
shifting traffic over as you gain confidence (from monitoring) that it is
|
|||
|
performing as expected. Depending on the rate at which you shift the traffic and
|
|||
|
the level of monitoring you have in place, a deployment like this might be
|
|||
|
called a zero-downtime, blue-green, canary deployment, or something else.
|
|||
|
|
|||
|
In this guide you will deploy a new version of a service and shift HTTP
|
|||
|
traffic slowly to the new version.
|
|||
|
|
|||
|
## Prerequisites
|
|||
|
|
|||
|
The steps in this guide use Consul’s service mesh feature, Consul Connect. If
|
|||
|
you aren’t already familiar with Connect you can learn more by following [this
|
|||
|
guide](https://learn.hashicorp.com/consul/getting-started/connect).
|
|||
|
|
|||
|
We created a demo environment for the steps we describe here. The environment
|
|||
|
relies on Docker and Docker Compose. If you do not already have Docker and
|
|||
|
Docker Compose, you can install them from [Docker’s install
|
|||
|
page](https://docs.docker.com/install/).
|
|||
|
|
|||
|
## Environment
|
|||
|
|
|||
|
This guide uses a two-tiered application made of of three services: a
|
|||
|
public web service, two versions of the API service, and Consul. The Web service
|
|||
|
accepts incoming traffic and makes an upstream call to the API service. At the
|
|||
|
start of this scenario version 1 of the API service is already running in
|
|||
|
production and handling traffic. Version 2 contains some changes you will ship
|
|||
|
in a canary deployment.
|
|||
|
|
|||
|
![Architecture diagram of the splitting demo. A web service directly connects to two different versions of the API service through proxies. Consul configures those proxies.](/static/img/consul-splitting-architecture.png)
|
|||
|
|
|||
|
## Start the Environment
|
|||
|
|
|||
|
First clone the repo containing the source and examples for this guide.
|
|||
|
|
|||
|
```shell
|
|||
|
$ git clone git@github.com:hashicorp/consul-demo-traffic-splitting.git
|
|||
|
```
|
|||
|
|
|||
|
Change directories into the cloned folder, and start the demo environment with
|
|||
|
`docker-compose up`. This command will run in the foreground, so you’ll need to
|
|||
|
open a new terminal window after you run it.
|
|||
|
|
|||
|
```shell
|
|||
|
$ docker-compose up
|
|||
|
|
|||
|
Creating consul-demo-traffic-splitting_api_v1_1 ... done
|
|||
|
Creating consul-demo-traffic-splitting_consul_1 ... done
|
|||
|
Creating consul-demo-traffic-splitting_web_1 ... done
|
|||
|
Creating consul-demo-traffic-splitting_web_envoy_1 ... done
|
|||
|
Creating consul-demo-traffic-splitting_api_proxy_v1_1 ... done
|
|||
|
Attaching to consul-demo-traffic-splitting_consul_1, consul-demo-traffic-splitting_web_1, consul-demo-traffic-splitting_api_v1_1, consul-demo-traffic-splitting_web_envoy_1, consul-demo-traffic-splitting_api_proxy_v1_1
|
|||
|
```
|
|||
|
|
|||
|
Consul is preconfigured to run as a single server, with all the
|
|||
|
configurations for splitting enabled.
|
|||
|
|
|||
|
- Connect is enabled - Traffic shaping requires that you use Consul Connect.
|
|||
|
|
|||
|
- gRPC is enabled - splitting also requires the you use Envoy as a sidecar
|
|||
|
proxy, and Envoy gets its configuration from Consul via gRPC.
|
|||
|
|
|||
|
- Central service configuration is enabled - you will use configuration entries
|
|||
|
to specify the API service protocol, and define your splitting ratios.
|
|||
|
|
|||
|
These settings are defined in the Consul configuration file at
|
|||
|
`consul_config/consul.hcl`, which contains the follwoing.
|
|||
|
|
|||
|
```hcl
|
|||
|
data_dir = "/tmp/"
|
|||
|
log_level = "DEBUG"
|
|||
|
server = true
|
|||
|
|
|||
|
bootstrap_expect = 1
|
|||
|
ui = true
|
|||
|
|
|||
|
bind_addr = "0.0.0.0"
|
|||
|
client_addr = "0.0.0.0"
|
|||
|
|
|||
|
connect {
|
|||
|
enabled = true
|
|||
|
}
|
|||
|
|
|||
|
ports {
|
|||
|
grpc = 8502
|
|||
|
}
|
|||
|
|
|||
|
enable_central_service_config = true
|
|||
|
```
|
|||
|
|
|||
|
You can find the service definitions for this demo in the `service_config`
|
|||
|
folder. Note the metadata stanzas in the registrations for versions 1 and 2 of
|
|||
|
the API service. Consul will use the metadata you define here to split traffic
|
|||
|
between the two services. The metadata stanza contains the following.
|
|||
|
|
|||
|
```json
|
|||
|
"meta": {
|
|||
|
"version": "1"
|
|||
|
},
|
|||
|
```
|
|||
|
|
|||
|
Once everything is up and running, you can view the health of the registered
|
|||
|
services by checking the Consul UI at
|
|||
|
[http://localhost:8500](http://localhost:8500). The docker compose file has
|
|||
|
started and registered Consul, the web service, a sidecar for the web service,
|
|||
|
version 1 of the API service, and a sidecar for the API service.
|
|||
|
|
|||
|
![List of services in the Consul UI including Consul, and the web and API services with their proxies](/static/img/consul-splitting-services.png)
|
|||
|
|
|||
|
Curl the Web endpoint to make sure that the whole application is running. The
|
|||
|
Web service will get a response from version 1 of the API service.
|
|||
|
|
|||
|
```hcl
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V1
|
|||
|
```
|
|||
|
|
|||
|
Initially, you will want to deploy version 2 of the API service to production
|
|||
|
without sending any traffic to it, to make sure that it performs well in a new
|
|||
|
environment. Prevent traffic from flowing to version 2 when you register it, you
|
|||
|
will preemptively set up a traffic split to send 100% of your traffic to
|
|||
|
version 1 of the API service, and 0% to the not-yet-deployed version 2.
|
|||
|
|
|||
|
## Configure Traffic Splitting
|
|||
|
|
|||
|
Traffic splitting makes use of configuration entries to centrally configure
|
|||
|
services and Envoy proxies. There are three configuration entries you need to
|
|||
|
create to enable traffic splitting:
|
|||
|
|
|||
|
- Service defaults for the API service to set the protocol to HTTP.
|
|||
|
- Service splitter which defines the traffic split between the service subsets.
|
|||
|
- Service resolver which defines which service instances are version 1 and 2.
|
|||
|
|
|||
|
### Configuring Service Defaults
|
|||
|
|
|||
|
Traffic splitting requires that the upstream application uses HTTP, because
|
|||
|
splitting happens on layer 7 (on a request-by-request basis). You will tell
|
|||
|
Consul that your upstream service uses HTTP by setting the protocol in a
|
|||
|
service-defaults configuration entry for the API service. This configuration
|
|||
|
is already in your demo environment at `l7_config/api_service_defaults.json`. It
|
|||
|
contains the following.
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"kind": "service-defaults",
|
|||
|
"name": "api",
|
|||
|
"protocol": "http"
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
To apply the configuration, you can either use the Consul CLI or the API. In
|
|||
|
this example we’ll use the CLI to write the configuration, providing the file location.
|
|||
|
|
|||
|
```shell
|
|||
|
$ consul config write l7_config/api_service_defaults.json
|
|||
|
```
|
|||
|
|
|||
|
Find more information on `service-defaults` configuration entries in the
|
|||
|
[documentation](https://www.consul.io/docs/agent/config-entries/service-defaults.html).
|
|||
|
|
|||
|
-> **Automation Tip:** To automate interactions with configuration entries, use
|
|||
|
the HTTP API endpoint [`http://localhost:8500/v1/config`](https://www.consul.io/api/config.html).
|
|||
|
|
|||
|
### Configuring the Service Resolver
|
|||
|
|
|||
|
The next configuration entry you need to add is the service resolver, which
|
|||
|
allows you to define how Consul’s service discovery selects service instances
|
|||
|
for a given service name.
|
|||
|
|
|||
|
Service resolvers allow you to filter for subsets of services based on
|
|||
|
information in the service registration. In this example, we are going to define
|
|||
|
the subsets “v1” and “v2” for the API service, based on their registered
|
|||
|
metadata. API service version 1 in the demo is already registered with the
|
|||
|
service metadata `version:1`, and an optional tag, `v1`, to make the version
|
|||
|
number appear in the UI. When you register version 2 you will give it the
|
|||
|
metadata `version:2`, which Consul will use to find the right service, and
|
|||
|
optional tag `v2`. The `name` field is set to the name of the service in the
|
|||
|
Consul service catalog.
|
|||
|
|
|||
|
The service resolver is already in your demo environment at
|
|||
|
`l7_config/api_service_resolver.json` and it contains the following
|
|||
|
configuration.
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"kind": "service-resolver",
|
|||
|
"name": "api",
|
|||
|
|
|||
|
"subsets": {
|
|||
|
"v1": {
|
|||
|
"filter": "Service.Meta.version == 1"
|
|||
|
},
|
|||
|
"v2": {
|
|||
|
"filter": "Service.Meta.version == 2"
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Write the service resolver configuration entry using the CLI and providing the
|
|||
|
location, just like in the previous example.
|
|||
|
|
|||
|
```shell
|
|||
|
$ consul config write l7_config/api_service_resolver.json
|
|||
|
```
|
|||
|
|
|||
|
Find more information about service resolvers in the
|
|||
|
[documentation](https://www.consul.io/docs/agent/config-entries/service-resolver.html).
|
|||
|
|
|||
|
### Configure Service Splitting - 100% of traffic to Version 1
|
|||
|
|
|||
|
Next, you’ll create a configuration entry that will split percentages of traffic
|
|||
|
to the subsets of your upstream service that you just defined. Initially, you
|
|||
|
want the splitter to send all traffic to v1 of your upstream service, which
|
|||
|
prevents any traffic from being sent to v2 when you register it. In a production
|
|||
|
scenario, this would give you time to make sure that v2 of your service is up
|
|||
|
and running as expected before sending it any real traffic.
|
|||
|
|
|||
|
The configuration entry for service splitting has the `kind` of
|
|||
|
`service-splitter`. Its `name` specifies which service that the splitter will
|
|||
|
act on. The `splits` field takes an array which defines the different splits; in
|
|||
|
this example, there are only two splits; however, it is [possible to configure
|
|||
|
multiple sequential
|
|||
|
splits](https://www.consul.io/docs/connect/l7-traffic-management.html#splitting).
|
|||
|
|
|||
|
Each split has a `weight` which defines the percentage of traffic to distribute
|
|||
|
to each service subset. The total weights for all splits must equal 100. For
|
|||
|
your initial split, configure all traffic to be directed to the service subset
|
|||
|
v1.
|
|||
|
|
|||
|
The service splitter already exists in your demo environment at
|
|||
|
`l7_config/api_service_splitter_100_0.json` and contains the following
|
|||
|
configuration.
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"kind": "service-splitter",
|
|||
|
"name": "api",
|
|||
|
"splits": [
|
|||
|
{
|
|||
|
"weight": 100,
|
|||
|
"service_subset": "v1"
|
|||
|
},
|
|||
|
{
|
|||
|
"weight": 0,
|
|||
|
"service_subset": "v2"
|
|||
|
}
|
|||
|
]
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Write this configuration entry using the CLI as well.
|
|||
|
|
|||
|
```shell
|
|||
|
$ consul config write l7_config/api_service_splitter_100_0.json
|
|||
|
```
|
|||
|
|
|||
|
This concludes the set up of the first stage in your deployment; you can now
|
|||
|
launch the new version of the API service without it immediately being used.
|
|||
|
|
|||
|
### Start and Register API Service Version 2
|
|||
|
|
|||
|
Next you’ll start version 2 of the API service, and register it with the
|
|||
|
settings that you used in the configuration entries for resolution and
|
|||
|
splitting. Start the service, register it, and start its connect sidecar with
|
|||
|
the following command. This command will run in the foreground, so you’ll need
|
|||
|
to open a new terminal window after you run it.
|
|||
|
|
|||
|
```shell
|
|||
|
$ docker-compose -f docker-compose-v2.yml up
|
|||
|
```
|
|||
|
|
|||
|
Check that the service and its proxy have registered by checking for new `v2`
|
|||
|
tags next to the API service and API sidecar proxies in the Consul UI.
|
|||
|
|
|||
|
### Configure Service Splitting - 50% Version 1, 50% Version 2
|
|||
|
|
|||
|
Now that version 2 is running and registered, the next step is to gradually
|
|||
|
increase traffic to it by changing the weight of the v2 service subset in the
|
|||
|
service splitter configuration. In this example you will increase the percent of
|
|||
|
traffic destined for the the v2 service to 50%. In a production roll out you
|
|||
|
would typically set the initial percent to be much lower. You can specify
|
|||
|
percentages as low as 0.01%.
|
|||
|
|
|||
|
Remember; total service percent must equal 100, so in this example you will
|
|||
|
reduce the percent of the v1 subset to 50. The configuration file is already in
|
|||
|
your demo environment at `l7_config/api_service_splitter_50_50.json` and it
|
|||
|
contains the following.
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"kind": "service-splitter",
|
|||
|
"name": "api",
|
|||
|
"splits": [
|
|||
|
{
|
|||
|
"weight": 50,
|
|||
|
"service_subset": "v1"
|
|||
|
},
|
|||
|
{
|
|||
|
"weight": 50,
|
|||
|
"service_subset": "v2"
|
|||
|
}
|
|||
|
]
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Write the new configuration using the CLI.
|
|||
|
|
|||
|
```shell
|
|||
|
$ consul config write l7_config/api_service_splitter_50_50.json
|
|||
|
```
|
|||
|
|
|||
|
Now that you’ve increased the percentage of traffic to v2, curl the web service
|
|||
|
again. Consul will equally distribute traffic across both of the service
|
|||
|
subsets.
|
|||
|
|
|||
|
```hcl
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V1
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V2
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V1
|
|||
|
```
|
|||
|
|
|||
|
### Configure Service Splitting - 100% Version 2
|
|||
|
|
|||
|
Once you are confident that the new version of the service is operating
|
|||
|
correctly, you can send 100% of traffic to the version 2 subset. The
|
|||
|
configuration for a 100% split to version 2 contains the following.
|
|||
|
|
|||
|
```json
|
|||
|
{
|
|||
|
"kind": "service-splitter",
|
|||
|
"name": "api",
|
|||
|
"splits": [
|
|||
|
{
|
|||
|
"weight": 0,
|
|||
|
"service_subset": "v1"
|
|||
|
},
|
|||
|
{
|
|||
|
"weight": 100,
|
|||
|
"service_subset": "v2"
|
|||
|
}
|
|||
|
]
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Apply it with the CLI, providing the path to the configuration entry.
|
|||
|
|
|||
|
```shell
|
|||
|
$ consul config write l7_config/api_service_splitter_0_100.json
|
|||
|
```
|
|||
|
|
|||
|
Now when you curl the web service again. 100% of traffic goes to the version
|
|||
|
2 subset.
|
|||
|
|
|||
|
```hcl
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V2
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V2
|
|||
|
$ curl localhost:9090
|
|||
|
Hello World
|
|||
|
###Upstream Data: localhost:9091###
|
|||
|
Service V2
|
|||
|
```
|
|||
|
|
|||
|
Typically in a production environment, you would now remove the version 1
|
|||
|
service to release capacity in your cluster. Once you remove version 1's
|
|||
|
registration from Consul you can either remove the splitter and resolver
|
|||
|
entirely, or leave them in place, removing the stanza that sends traffic to
|
|||
|
version 1, so that you can eventually deploy version 3 without it receiving any
|
|||
|
initial traffic.
|
|||
|
|
|||
|
Congratulations, you’ve now completed the deployment of version 2 of your
|
|||
|
service.
|
|||
|
|
|||
|
## Demo Cleanup
|
|||
|
|
|||
|
To stop and remove the containers and networks that you created you will run
|
|||
|
`docker-compose down` twice: once for each of the docker compose commands you
|
|||
|
ran. Because containers you created in the second compose command are running on
|
|||
|
the network you created in the first command, you will need to bring down the
|
|||
|
environments in the opposite order that you created them in.
|
|||
|
|
|||
|
First you’ll stop and remove the containers created for v2 of the API service.
|
|||
|
|
|||
|
```shell
|
|||
|
$ docker-compose -f docker-compose-v2.yml down
|
|||
|
Stopping consul-demo-traffic-splitting_api_proxy_v2_1 ... done
|
|||
|
Stopping consul-demo-traffic-splitting_api_v2_1 ... done
|
|||
|
WARNING: Found orphan containers (consul-demo-traffic-splitting_api_proxy_v1_1, consul-demo-traffic-splitting_web_envoy_1, consul-demo-traffic-splitting_consul_1, consul-demo-traffic-splitting_web_1, consul-demo-traffic-splitting_api_v1_1) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
|
|||
|
Removing consul-demo-traffic-splitting_api_proxy_v2_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_api_v2_1 ... done
|
|||
|
Network consul-demo-traffic-splitting_vpcbr is external, skipping
|
|||
|
```
|
|||
|
|
|||
|
Then, you’ll stop and remove the containers and the network that you created in
|
|||
|
the first docker compose command.
|
|||
|
|
|||
|
```shell
|
|||
|
$ docker-compose down
|
|||
|
Stopping consul-demo-traffic-splitting_api_proxy_v1_1 ... done
|
|||
|
Stopping consul-demo-traffic-splitting_web_envoy_1 ... done
|
|||
|
Stopping consul-demo-traffic-splitting_consul_1 ... done
|
|||
|
Stopping consul-demo-traffic-splitting_web_1 ... done
|
|||
|
Stopping consul-demo-traffic-splitting_api_v1_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_api_proxy_v1_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_web_envoy_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_consul_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_web_1 ... done
|
|||
|
Removing consul-demo-traffic-splitting_api_v1_1 ... done
|
|||
|
Removing network consul-demo-traffic-splitting_vpcbr
|
|||
|
```
|
|||
|
|
|||
|
## Summary
|
|||
|
|
|||
|
In this guide, we walked you through the steps required to perform Canary
|
|||
|
deployments using traffic splitting and resolution.
|
|||
|
|
|||
|
Find out more about L7 traffic management settings in the
|
|||
|
[documentation](https://www.consul.io/docs/connect/l7-traffic-management.html).
|