Implementing Gray Release with Spring Cloud Gateway, Zuul and Nacos – A Complete Guide

This article explains the concept of gray (canary) release, compares implementation options such as Nginx + Lua, Netflix Zuul and Spring Cloud Gateway, and provides detailed Java code for custom routing predicates, version handling, and dynamic configuration integration with Nacos.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Implementing Gray Release with Spring Cloud Gateway, Zuul and Nacos – A Complete Guide

Gray Release

Gray release (canary deployment) ensures a smooth upgrade by gradually routing a subset of users to new service versions, allowing multiple client and server versions to coexist while maintaining compatibility.

Gray release diagram
Gray release diagram

Implementation Options

nginx + lua (openresty)

nginx lua diagram
nginx lua diagram
nginx lua flow
nginx lua flow

Netflix Zuul

Customize Ribbon's predicate to obtain the version number from request headers via TTL.

@Slf4j
public class MetadataCanaryRuleHandler extends ZoneAvoidanceRule {
    @Override
    public AbstractServerPredicate getPredicate() {
        return new AbstractServerPredicate() {
            @Override
            public boolean apply(PredicateKey predicateKey) {
                String targetVersion = RibbonVersionHolder.getContext();
                RibbonVersionHolder.clearContext();
                if (StrUtil.isBlank(targetVersion)) {
                    log.debug("客户端未配置目标版本直接路由");
                    return true;
                }
                DiscoveryEnabledServer server = (DiscoveryEnabledServer) predicateKey.getServer();
                Map<String, String> metadata = server.getInstanceInfo().getMetadata();
                if (StrUtil.isBlank(metadata.get(SecurityConstants.VERSION))) {
                    log.debug("当前微服务{} 未配置版本直接路由", server.getInstanceInfo().getAppName());
                    return true;
                }
                if (metadata.get(SecurityConstants.VERSION).equals(targetVersion)) {
                    return true;
                } else {
                    log.debug("当前微服务{} 版本为{},目标版本{} 匹配失败", server.getInstanceInfo().getAppName(), metadata.get(SecurityConstants.VERSION), targetVersion);
                    return false;
                }
            }
        };
    }
}

public class RibbonVersionHolder {
    private static final ThreadLocal<String> context = new TransmittableThreadLocal<>();
    public static String getContext() { return context.get(); }
    public static void setContext(String value) { context.set(value); }
    public static void clearContext() { context.remove(); }
}

Spring Cloud Gateway Implementation

Because Spring Cloud Gateway is built on WebFlux, traditional ThreadLocal or RequestContextHolder cannot propagate the version information. The solution overrides the choose method to pass request headers to the routing predicate.

public class LoadBalancerClientFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() { return LOAD_BALANCER_CLIENT_FILTER_ORDER; }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange);
    }
    protected ServiceInstance choose(ServerWebExchange exchange) {
        return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost());
    }
}

Custom predicate that receives the version from the request headers:

protected ServiceInstance choose(ServerWebExchange exchange) {
    HttpHeaders headers = exchange.getRequest().getHeaders();
    return loadBalancer.choose(((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost(), headers);
}

Predicate implementation that matches service metadata with the requested version:

public abstract class AbstractDiscoveryEnabledPredicate extends AbstractServerPredicate {
    @Override
    public boolean apply(@Nullable PredicateKey input) {
        return input != null && input.getServer() instanceof NacosServer &&
               apply((NacosServer) input.getServer(), (HttpHeaders) input.getLoadBalancerKey());
    }
}

public class GrayMetadataAwarePredicate extends AbstractDiscoveryEnabledPredicate {
    @Override
    protected boolean apply(NacosServer server, HttpHeaders headers) {
        PigxRibbonRuleProperties ribbonProperties = SpringContextHolder.getBean(PigxRibbonRuleProperties.class);
        if (!ribbonProperties.isGrayEnabled()) {
            log.debug("gray closed,GrayMetadataAwarePredicate return true");
            return true;
        }
        Map<String, String> metadata = server.getMetadata();
        String version = metadata.get(CommonConstants.VERSION);
        if (StrUtil.isBlank(version)) {
            log.debug("nacos server tag is blank ,GrayMetadataAwarePredicate return true");
            return true;
        }
        String target = headers.getFirst(CommonConstants.VERSION);
        if (StrUtil.isBlank(target)) {
            log.debug("request headers version is blank,GrayMetadataAwarePredicate return true");
            return true;
        }
        log.debug("请求版本:{} ,当前服务版本:{}", target, version);
        return target.equals(version);
    }
}

Integration with Nacos

Dynamic configuration from Nacos makes gray release management straightforward.

Nacos integration diagram
Nacos integration diagram

Summary

The source code is based on a personal project that uses Spring Cloud, OAuth2.0, and a Vue front‑end for a full‑stack development platform.

Feel free to discuss Spring Cloud usage via QQ: 2270033969.

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.

Microservicesgray release
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

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.