Backend Development 14 min read

Implement Full‑Link Gray Release with Spring Cloud Gateway, Ribbon, and OpenFeign

This article explains how to achieve full‑link gray (canary) release in a microservice architecture by adding a gray tag in a Spring Cloud Gateway global filter, propagating it through request headers, customizing Ribbon load‑balancing rules to select gray instances from Nacos, and using an OpenFeign interceptor to transmit the tag downstream, ensuring seamless A/B testing and stable deployments.

macrozheng
macrozheng
macrozheng
Implement Full‑Link Gray Release with Spring Cloud Gateway, Ribbon, and OpenFeign

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 a portion of users to the new feature while the rest continue using the old one.

Why Full‑Link Gray Release?

Previous articles only covered gateway‑level gray routing, which cannot propagate the gray tag to downstream services. Full‑link gray release ensures the gray tag is transmitted through the entire call chain.

Gateway Gray Routing

Use Ribbon + Spring Cloud Gateway to modify the load‑balancing strategy.

In a global filter, add a gray tag to the request based on business rules.

Put the gray tag into the request header for downstream services.

Customize Ribbon’s load‑balancing rule to select gray services from Nacos.

Forward the request.

<code>public class GlobalGrayFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // ① parse request header, set gray tag 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
        ServerHttpRequest tokenRequest = exchange.getRequest().mutate()
                .header(GrayConstant.GRAY_HEADER, GrayRequestContextHolder.getGrayTag().toString())
                .build();
        ServerWebExchange build = exchange.mutate().request(tokenRequest).build();
        return chain.filter(build);
    }
}
</code>

The filter must run before the OAuth2.0 authentication filter.

Custom Ribbon Rule (GrayRule)

GrayRule reads the gray tag from ThreadLocal, separates gray and normal instances from Nacos metadata, and selects the appropriate instance.

<code>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);
    }
}
</code>

Configuration class to expose the rule (must not be scanned into the Spring container unless you want it global).

<code>public class GrayRuleConfig {
    @Bean
    public GrayRule grayRule() {
        return new GrayRule();
    }
}
</code>

OpenFeign Gray Tag Propagation

Implement a RequestInterceptor to copy the grayTag header from the incoming request to the Feign request and store it in GrayRequestContextHolder.

<code>@Component
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()) {
            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);
                if (StrUtil.equals(GrayConstant.GRAY_HEADER, key) && Boolean.TRUE.toString().equals(value)) {
                    GrayRequestContextHolder.setGrayTag(true);
                    map.put(key, value);
                }
            }
        }
        return map;
    }
}
</code>

Use @RibbonClients to specify which services (e.g., article‑server, comments) should use the gray rule.

<code>@RibbonClients(value = {
    @RibbonClient(value = "comments", configuration = GrayRuleConfig.class)
})
public class ArticleApplication {}
</code>

Gray Tag in Nacos

Two ways to mark a service as gray:

Define metadata in the service’s configuration file:

<code>spring:
  cloud:
    nacos:
      discovery:
        metadata:
          grayTag: true
</code>

Set the metadata dynamically in the Nacos console.

Clients only need to send the header

grayTag=true

to invoke the gray instance.

Summary

Gateway global filter adds the gray tag to the request header.

Custom Ribbon rule selects gray or normal instances from Nacos based on the tag.

Feign interceptor copies the gray tag to downstream calls and stores it in ThreadLocal.

Downstream services use the same GrayRule to route to gray instances.

Microservicesgray releaseNacosOpenFeignSpring Cloud GatewayRibbon
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

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.