Cloud Native 13 min read

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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Full-Link Gray Release Implementation with Spring Cloud Alibaba, Ribbon, and OpenFeign

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: true

When 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Cloud NativeMicroservicesgray releaseNacosSpring CloudOpenFeignRibbon
Code Ape Tech Column
Written by

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

0 followers
Reader feedback

How this landed with the community

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.