Full-Link Gray Release Implementation with Spring Cloud Alibaba, Ribbon, and OpenFeign
This article explains how to implement a full-link gray (canary) release in a Spring Cloud Alibaba microservice architecture by marking traffic at the gateway, propagating the gray tag through request headers, customizing Ribbon load‑balancing, and configuring Nacos metadata for selective service routing.
In production, changes are often rolled out gradually by routing a small portion of traffic for testing before full deployment, which helps keep most clients stable if bugs appear.
What is Gray Release?
Gray release (also called canary release) is a deployment method that allows a smooth transition between old and new versions, enabling A/B testing by directing part of users to version B and expanding if no issues are found.
Why Full-Link Gray Release?
Previous articles covered gateway‑level gray release, which only routes traffic at the gateway but does not propagate the gray tag to downstream services, causing calls to the original service instead of the gray version. Full-link gray release requires both gateway routing and downstream services to transmit the gray tag.
Gateway Layer Gray Routing
The gateway uses Ribbon+Spring Cloud Gateway to add a global filter that reads a grayTag request header, stores it in a ThreadLocal, and forwards the header to downstream services.
public class GlobalGrayFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//① Parse request header for gray tag
HttpHeaders headers = exchange.getRequest().getHeaders();
if (headers.containsKey(GrayConstant.GRAY_HEADER)) {
String gray = headers.getFirst(GrayConstant.GRAY_HEADER);
if (StrUtil.equals(gray, GrayConstant.GRAY_VALUE)) {
//② Set gray tag in ThreadLocal
GrayRequestContextHolder.setGrayTag(true);
}
}
//③ Put gray tag into request header for downstream services
ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
.header(GrayConstant.GRAY_HEADER, GrayRequestContextHolder.getGrayTag().toString())
.build();
ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
return chain.filter(build);
}
}The filter must be placed before the OAuth2.0 authentication filter to ensure the tag is transmitted early.
Custom Ribbon Load‑Balancing Rule
A custom GrayRule extends ZoneAvoidanceRule and selects gray or normal instances based on the gray tag stored in GrayRequestContextHolder. It reads metadata from Nacos to distinguish gray services.
public class GrayRule extends ZoneAvoidanceRule {
@Override
public Server choose(Object key) {
try {
boolean grayTag = GrayRequestContextHolder.getGrayTag().get();
List<Server> serverList = this.getLoadBalancer().getReachableServers();
List<Server> grayServerList = new ArrayList<>();
List<Server> normalServerList = new ArrayList<>();
for (Server server : serverList) {
NacosServer nacosServer = (NacosServer) server;
if (nacosServer.getMetadata().containsKey(GrayConstant.GRAY_HEADER)
&& nacosServer.getMetadata().get(GrayConstant.GRAY_HEADER).equals(GrayConstant.GRAY_VALUE)) {
grayServerList.add(server);
} else {
normalServerList.add(server);
}
}
if (grayTag) {
return originChoose(grayServerList, key);
} else {
return originChoose(normalServerList, key);
}
} finally {
GrayRequestContextHolder.remove();
}
}
private Server originChoose(List<Server> list, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(list, key);
return server.orElse(null);
}
}The rule is exposed as a bean in a configuration class that must not be scanned into the Spring IoC container globally.
public class GrayRuleConfig {
@Bean
public GrayRule grayRule() {
return new GrayRule();
}
}Propagating the Gray Tag with OpenFeign
Because OpenFeign creates a new request without copying original headers, a RequestInterceptor copies all incoming headers, especially the grayTag, into the new request and stores the tag in GrayRequestContextHolder for downstream services.
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
Map<String, String> headers = getHeaders(httpServletRequest);
for (Map.Entry<String, String> entry : headers.entrySet()) {
//② Set header into new request
template.header(entry.getKey(), entry.getValue());
}
}
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
// Store gray tag
if (StrUtil.equals(GrayConstant.GRAY_HEADER, key) && Boolean.TRUE.toString().equals(value)) {
GrayRequestContextHolder.setGrayTag(true);
map.put(key, value);
}
}
}
return map;
}
}Services that need gray release are annotated with @RibbonClients specifying the target service and the custom rule.
@RibbonClients(value = {
@RibbonClient(value = "comments", configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}Marking Services in Nacos
Gray services can be identified either by adding grayTag: true in the service’s application.yml metadata or by setting the metadata dynamically in the Nacos console.
spring:
cloud:
nacos:
discovery:
metadata:
grayTag: trueWhen a client sends the request header grayTag=true, the gateway and downstream services route the call to the gray instances.
Summary
The full‑link gray release solution for microservices consists of four steps:
Gateway adds a global filter to set the gray tag and forward it via request headers.
Gateway uses a custom Ribbon load‑balancing rule to select gray instances from Nacos.
OpenFeign interceptors extract the gray tag from incoming headers and store it in a ThreadLocal context.
OpenFeign also uses the same custom Ribbon rule to call gray instances downstream.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
