1. Introduction

1.1 Problems solved

An isolated environment is an abstract concept;

Main problems solved: During the development process, there are often multiple tasks in parallel, and a project may be maintained by several people;

If you deploy to QA at the same time, you will not only have code conflicts, but you will also have business dependencies that will not match the desired results.

Note: the core problem to solve is this, to make a larger, can be a gray environment for gray testing

1.2 Implementation of the core logic

Core implementation idea: to create a project code branch based on requirements, create an independent requirements testing environment, that is, the project involved in this requirement change is deployed in one environment, the other unchanged projects use common QA;

Implementation premise:

1) During service deployment, the corresponding environment identification is carried;

2) For front-end services, there should be corresponding API gateway services;

Implementation logic:

1) Carry the environment number of the current test from the client (add the corresponding environment variable in the header);

2) Gateway (nginx) passthrough;

3) THE API gateway selects the API service of the corresponding environment according to the environment variables (not following the default load balancing policy);

4) API service carries environmental variables in middleware such as request microservice and MQ;

The overall process is as follows:

2. Core code interpretation

So before we start reading, what are some things to think about?

1) Grayscale range of services, which services are required and which are not required?

2) Whether the service itself should be marked with gray scale?

3) How does the service itself not carry the mark, then where is the gray mark of the service stored?

4) Are the load balancing components of back-end architecture middleware consistent, and can a common set be extracted?

2.0 Eureka service registration grayscale environment adaption

As a registry, Eureka itself stores service meta-information, so it is a perfect location for storing service identifiers.

😄 transformation is done;

Eureka.instance. instance-id: 👌🏻;

The effect of service registration with Eureka after processing is as follows:

2.1 API gateway grayscale environment adaptation

Let’s take spring-cloud-gateway as an example.

For those not familiar with spring-cloud-gateway, see this: Gateway learning

Before the transformation, first think about which step we need to transform, 😄, of course, is to forward the request, select the service;

Here is the Gateway request link diagram:

Which entry point did you choose? LoadBlancedClientFilter 😄

We customize LoadBlancedClientFilter to store grayscale identifier into threadLocal;

Because the underlying selection service is Ribbon, we can customize the ruler to complete the redefinition of service selection rules.

Custom LoadBlancedClientFilter:

@Component
@ConditionalOnProperty(name = "enable_gray_env")
public class GrayEnvLoadBalancerClientFilter extends LoadBalancerClientFilter {
    public GrayEnvLoadBalancerClientFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
    }


    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {

        // Get the entry environment and store it in threadLocal
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String podEnv = headers.getFirst(Constants.POD_ENV);
        if(StringUtils.isNotEmpty(podEnv)){
            EnvHolder.setEnv(Constants.POD_ENV,podEnv);
        }

        return super.choose(exchange); }}Copy the code

Custom ruler

public class GrayEnvRuler extends ZoneAvoidanceRule {

    private Logger logger = LoggerFactory.getLogger(GrayEnvRuler.class);

    /** * Grayscale environment switch */
    @Value("${enable_gray_env:false}")
    private Boolean enableGrayEnv;

    /** * Current environment */
    @Value("${env}")
    private String env;

    /** * allows grayscale environments */
    @Value("${gray_envs}")
    private String grayEnvs;

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {}@Override
    public Server choose(Object key) {
        if(! enableGrayEnv){return super.choose(key);
        }

        // Go to default polling if the environment is not allowed
        List<String> enableGrayEnvList = Arrays.asList(grayEnvs.split(","));
        if(! enableGrayEnvList.contains(env)) {return super.choose(key);
        }

        ILoadBalancer lb = getLoadBalancer();
        if (Objects.isNull(lb)) {
            return null;
        }

        // Get the active service
        List<Server> serverList = lb.getReachableServers();
        // Get the entry environment
        String podEnv = EnvHolder.getEnv(Constants.POD_ENV);

        if(StringUtils.isNotEmpty(podEnv)){
            EnvHolder.clear();
        }

        for (Server server : serverList) {
            String instanceId = server.getMetaInfo().getInstanceId();
            List<String> instanceIdList = Arrays.asList(instanceId.split(":"));
            if(instanceIdList.size() ! =3) {
                continue;
            }
            / / id format: eureka. The instance. The instance id = ${spring. Cloud. Client. Ipaddress} : ${server. The port} : ${pod_env}
            String instancePodEnv = instanceIdList.get(2);

            if (StringUtils.equals(instancePodEnv, podEnv)) {
                // Match to the corresponding business environment service
                returnserver; }}return super.choose(key); }}Copy the code

2.2 Grayscale environment adaptation for FEIGN Service invocation

Feign-core is generally used for the call before spring-Cloud service. Ribbon is also used for load balancing, that is, ruler defined above can be used for switching.

Again, where does the environment identity in the request go? Because there is a user request, directly find the interceptor OK;

Implement RequestInterceptor;

Here is the implementation:

@Configuration
public class FeignHeaderIntercept implements RequestInterceptor {

    private Logger logger = LoggerFactory.getLogger(FeignHeaderIntercept.class);

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        if(Objects.isNull(requestAttributes)){
            return;
        }

        HttpServletRequest request = requestAttributes.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();

        if(Objects.isNull(headerNames)){
            return ;
        }

        boolean xForwardIpExist = false;
        while (headerNames.hasMoreElements()){
            String name = headerNames.nextElement();
            String values = request.getHeader(name);

            // Obtain the real IP address of the user
            if (name.equalsIgnoreCase(Constants.X_FORWARDED_FOR)) {
                if(IpUtils.isLocalAddress(values)){
                    values = IpUtils.getIpAddr(request);
                }
                template.header(Constants.X_FORWARDED_FOR,values);
                xForwardIpExist = true;
            }

            // Customize HTTP header retrieval
            if(name.startsWith(Constants.POD_ENV)){
                values = request.getHeader(name);
                logger.info("HTTP interception header: name = {},values = {}",name,values); template.header(name,values); EnvHolder.setEnv(Constants.POD_ENV,values); }}if(! xForwardIpExist){ String remoteAddr = IpUtils.getIpAddr(request); logger.info(Httpx-forwarded-for Adds remoteAddr header: {},remoteAddr); template.header(Constants.X_FORWARDED_FOR,remoteAddr); }}}Copy the code

2.3 Rocket-MQ grayscale environment adaptation

Don’t worry about this, first wear the whole, after running through, the other is to add bricks and tiles;

3. Grayscale cases

According to the second step, we do a simple give a simple version of the architecture diagram;

Eureka effect after the Demo service is deployed.

What? Where’s the demo? How to play, all TM saw this, from a start bai, 😄!!