Backend Development 20 min read

Implementing Gray (Canary) Release in Spring Cloud with Nacos and Custom Load Balancer

This article explains how to implement gray (canary) release in a Spring Cloud project using Nacos for service discovery, custom request headers, ThreadLocal gray flags, Spring Cloud Gateway filters, custom Ribbon load balancer rules, and provides full code snippets, configuration steps, and deployment instructions.

Top Architect
Top Architect
Top Architect
Implementing Gray (Canary) Release in Spring Cloud with Nacos and Custom Load Balancer

Concept

Gray release, also known as canary deployment, allows a smooth transition between versions by routing a subset of traffic to a new version while keeping the rest on the stable version.

Component Versions

The demo uses Spring Boot 2.3.12.RELEASE, Spring Cloud Hoxton.SR12, and Spring Cloud Alibaba 2.2.9.RELEASE.

Core Components

Registry: Nacos

Gateway: Spring Cloud Gateway

Load Balancer: Ribbon (or Spring Cloud LoadBalancer)

RPC: OpenFeign

Gray Release Implementation

When a request reaches the gateway, a pre‑filter checks whether gray deployment is enabled and whether the request matches any gray criteria (custom header, IP, city, user ID). The result is stored in a ThreadLocal holder ( GrayFlagRequestHolder ) and the gray flag is added to the HTTP header gray . Downstream services read this header via a Spring MVC interceptor or a Feign request interceptor, ensuring the flag propagates through the call chain. Custom Ribbon load‑balancer rules then select service instances whose Nacos metadata version matches the desired version (PROD or GRAY).

Key Code Snippets

public class GrayFlagRequestHolder { private static final ThreadLocal<GrayStatusEnum> grayFlag = new ThreadLocal<>(); public static void setGrayTag(GrayStatusEnum tag){ grayFlag.set(tag);} public static GrayStatusEnum getGrayTag(){ return grayFlag.get(); } public static void remove(){ grayFlag.remove(); } }
public class GrayGatewayBeginFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){ GrayStatusEnum grayStatus = GrayStatusEnum.ALL; if (grayGatewayProperties.getEnabled()){ grayStatus = GrayStatusEnum.PROD; if (checkGray(exchange.getRequest())){ grayStatus = GrayStatusEnum.GRAY; } } GrayFlagRequestHolder.setGrayTag(grayStatus); ServerHttpRequest newRequest = exchange.getRequest().mutate().header(GrayConstant.GRAY_HEADER, grayStatus.getVal()).build(); ServerWebExchange newExchange = exchange.mutate().request(newRequest).build(); return chain.filter(newExchange); } }

Running the Demo

Deploy five services (gateway, user‑app V1 & V2, order‑app V1 & V2) with Nacos metadata version set to V1 or V2, configure gray settings in Nacos (enable flag, gray header key/value, IP and city lists), and test the behavior by toggling the gray switch or sending the custom gray header to observe traffic routing to the appropriate version.

Microservicesgray releaseNacosgatewaySpring CloudCanary Deploymentribbon
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.