Featured image of post Spring Cloud微服务网关Zuul灰度发布入门实战

Spring Cloud微服务网关Zuul灰度发布入门实战

一、灰度发布

灰度发布是指在系统迭代的时候一种平滑过度上线发布方式。灰度发布是在原有的系统的基础上面,额外增加一个新版本,这个新版本包含新上线的需要验证的功能,通过负载均衡引入部分流量到新版本的应用上,如果在这个过程中没有出现问题,便可以平滑地把线上的应用一步步替换成新的版本,这样就完成了一次灰度发布。通过灰度发布的方式可以在用户无感的情况下完成系统发版升级。

二、基于Eureka的metadata实现灰度发布

这里使用一个开源的实现:ribbon-discovery-filter-spring-cloud-starter

在Eureka中有两种metadata:

  • 标准元数据:主要是服务的各种信息。如服务IP、端口、服务健康状态、续约信息等。
  • 自定义的元数据:往Eureka注册的服务可以通过eureka.instance.metadata-map.<key>=<value>来配置,内部就是一个map来保存的。可以配置在远程的服务,也可以随着服务的注册保存到Eureka注册表中。

基于Eureka的元数据实现完成灰度发布的原理是:通过获取Eureka的元数据信息,根据元数据信息的识别,最后在路由规则进行负载均衡。

2.1 新建cloud-service-usercenter

依赖:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- springboot web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--不用Tomcat,使用undertow -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
        <dependency>
            <groupId>io.undertow</groupId>
            <artifactId>undertow-servlet</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

配置文件:这里通过配置文件配置三份配置,-1随机端口,node1和node2是v1版本,node3是v2版本。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
server:
  port: 8877
spring:
  profiles: node1
  application:
    name: usercenter
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
  instance:
    prefer-ip-address: true
    metadata-map:
      version: release

---

server:
  port: 8899
spring:
  profiles: node2
  application:
    name: usercenter
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
  instance:
    prefer-ip-address: true
    metadata-map:
      version: release

---

server:
  port: 8866
spring:
  profiles: node3
  application:
    name: usercenter
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
  instance:
    prefer-ip-address: true
    metadata-map:
      version: gray

Controller:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@RestController
@RequestMapping("user")
public class UsercenterController {

    @Value("${spring.profiles}")
    private String profile;
    @Value("${server.port}")
    private Integer port;

    @GetMapping("list")
    public String list() {
        return "active " + profile + " port " + port;
    }
}

启动类:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class UsercenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(UsercenterApplication.class, args);
    }
}

2.2 Zuul网关添加路由和依赖

配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
server:
  port: 88
spring:
  application:
    name: zuul-server
eureka:
  client:
    serviceUrl:
      defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
  instance:
    prefer-ip-address: true
logging:
  level:
    org.springframework.cloud.netflix: debug
zuul:
  routes:
    service-a:
      path: /usercenter/**
      serviceId: usercenter

依赖:

1
2
3
4
5
<dependency>
  <groupId>io.jmnarloch</groupId>
  <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
  <version>2.1.0</version>
</dependency>

编写灰度发布过滤器:当携带请求gray_switch并且值为open时就会去请求灰度版本的应用。否则就是请求release版本的应用。

现实中我们可以通过使用Nginx,然后将部分流量添加请求头,这样就可以切一部分的流量去做灰度了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Component
public class GrayFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        return !ctx.containsKey(FORWARD_TO_KEY) && !ctx.containsKey(SERVICE_ID_KEY);
    }

    @Override
    public Object run() throws ZuulException {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String mark = request.getHeader("gray_switch");
        if (StringUtils.isNotBlank(mark) && "open".equals(mark)) {
            RibbonFilterContextHolder.getCurrentContext().add("version", "gray");
        } else {
            RibbonFilterContextHolder.getCurrentContext().add("version", "release");
        }
        return null;
    }
}

2.3 启动测试

分别启动Eureka、Zuul和UsercenterApplication。

在启动UsercenterApplication的使用指定active profile:

  • 例如通过IDEA启动时修改配置Active profiles启动给三个分别设置 node1、node2、node3。

  • 在UsercenterApplication所在的pomx文件下,mvn运行

    mvn spring-boot:run -Dspring-boot.run.profiles=node1

    mvn spring-boot:run -Dspring-boot.run.profiles=node2

    mvn spring-boot:run -Dspring-boot.run.profiles=node3

访问接口时

http://localhost:88/usercenter/user/list