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/tutorials/consul/get-started-service-networking).
|
||
|
||
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-session
|
||
$ 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-session
|
||
$ 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.
|
||
|
||
```shell-session
|
||
$ 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-session
|
||
$ consul config write l7_config/api_service_defaults.json
|
||
```
|
||
|
||
Find more information on `service-defaults` configuration entries in the
|
||
[documentation](/docs/agent/config-entries/service-defaults).
|
||
|
||
-> **Automation Tip:** To automate interactions with configuration entries, use
|
||
the HTTP API endpoint [`http://localhost:8500/v1/config`](/api/config).
|
||
|
||
### 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-session
|
||
$ consul config write l7_config/api_service_resolver.json
|
||
```
|
||
|
||
Find more information about service resolvers in the
|
||
[documentation](/docs/agent/config-entries/service-resolver).
|
||
|
||
### 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](/docs/connect/l7-traffic-management#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-session
|
||
$ 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-session
|
||
$ 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-session
|
||
$ 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.
|
||
|
||
```shell-session
|
||
$ 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-session
|
||
$ 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.
|
||
|
||
```shell-session
|
||
$ 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-session
|
||
$ 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-session
|
||
$ 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](/docs/connect/l7-traffic-management).
|