Implementing Full‑Chain Gray Release with Spring Cloud Alibaba, Ribbon, and OpenFeign
This article explains how to achieve 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, when a requirement changes, the common practice is to release a new version to a small portion of traffic for testing before rolling it out to all users. This approach, known as full‑link gray release (or canary release), ensures system stability by detecting and fixing issues early.
What Is Gray Release?
Gray release allows a smooth transition between two versions of a feature (A and B). A subset of users receives version B while the rest continue with version A; if no objections arise, the rollout is expanded until all users are on version B.
Why Full‑Link Gray Release?
Previous articles covered gateway‑level gray release, which only routes traffic at the gateway based on a gray tag. However, downstream services called via OpenFeign do not automatically receive this tag, causing calls to the non‑gray version of the service. Full‑link gray release solves this by propagating the gray tag through the entire call chain.
Gateway Gray Routing
The gateway uses Ribbon + Spring Cloud Gateway to implement a custom load‑balancing strategy that selects gray services from Nacos based on the gray tag.
Implementation steps:
In a global filter, parse the request header for the gray tag and store it in a ThreadLocal .
Insert the gray tag into the outgoing request header.
Customize Ribbon's load‑balancing rule to fetch gray services from the service registry.
Forward the request.
Example of the global filter (pseudo‑code):
public class GlobalGrayFilter implements GlobalFilter {
@Override
public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//① Parse request header for gray tag and store in ThreadLocal
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
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);
}
}Note: This filter must be placed before the OAuth2.0 authentication filter with a higher priority.
Custom Ribbon Rule (GrayRule)
The GrayRule class extends ZoneAvoidanceRule and selects gray or normal instances based on the gray tag stored in GrayRequestContextHolder .
public class GrayRule extends ZoneAvoidanceRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {}
@Override
public Server choose(Object key) {
try {
boolean grayTag = GrayRequestContextHolder.getGrayTag().get();
List
serverList = this.getLoadBalancer().getReachableServers();
List
grayServerList = new ArrayList<>();
List
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 {
// Clear gray tag after selection
GrayRequestContextHolder.remove();
}
}
private Server originChoose(List
serverList, Object key) {
Optional
server = getPredicate().chooseRoundRobinAfterFiltering(serverList, key);
return server.orElse(null);
}
}The rule is registered via a configuration class:
public class GrayRuleConfig {
@Bean
public GrayRule grayRule() {
return new GrayRule();
}
}Only services specified in @RibbonClients will use this rule, preventing it from affecting all services.
OpenFeign Gray Tag Propagation
Because OpenFeign creates a new request without copying original headers, a RequestInterceptor is needed to forward the gray tag.
@Component
@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
Map
headers = getHeaders(httpServletRequest);
for (Map.Entry
entry : headers.entrySet()) {
//② Set header into new request
template.header(entry.getKey(), entry.getValue());
}
}
private Map
getHeaders(HttpServletRequest request) {
Map
map = new LinkedHashMap<>();
Enumeration
enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
// Save 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, e.g.:
@RibbonClients(value = {
@RibbonClient(value = "comments", configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}Marking Services in Nacos
Gray services can be identified by adding metadata in the service configuration:
spring:
cloud:
nacos:
discovery:
metadata:
grayTag: trueAlternatively, the gray tag can be set dynamically via the Nacos console.
Summary
The gateway adds a gray tag to the request header via a global filter.
A custom Ribbon rule selects gray instances from Nacos based on the tag.
OpenFeign interceptors propagate the gray tag to downstream services.
Downstream services use the same Ribbon rule to call the appropriate gray or normal instances.
By following these steps, a full‑chain gray release can be achieved in a Spring Cloud microservice ecosystem.
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.