Kubernetes 部署 SpringCloud 网关 Zuul 1.x + Eureka 动态路由

Kubernetes 部署 SpringCloud 网关 Zuul 1.x + Eureka 动态路由

文章目录

  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。


系统环境

  • Redis 版本:5.0.5
  • Kubernetes 版本:1.5.3
  • SpringBoot 版本:2.1.8.RELEASE
  • SpringCloud 版本:Greenwich.SR2
  • SpringCloud Kubernetes 版本:1.0.2.RELEASE
  • spring-cloud-zuul-ratelimit 版本:2.2.5.RELEASE

参考地址

  • 示例 Github 地址:https://github.com/my-dlq/blog-example/tree/master/springcloud/springcloud-zuul-demo

一、Zuul 概念简介

1、简单介绍下 Zuul

Zuul 是 Netflix 开源的一个网关服务, 本质上是一个 Web Servlet 应用。主要是作用在服务最外层充当网关角色,还包括下面功能:

  • 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
  • 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
  • 安全验证: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
  • 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
  • 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。

2、为什么选择 Zuul 1.x 版本

现在很多 SpringCloud 组件都在往 Kubernetes 中迁移,SpringBoot Admin 作为我们微服务监控经常使用的组件,也要将它迁移到 Kubernetes 环境下。虽然现在 Netflix 的组件以及不再维护,但是它的很多组件大多数都是经历过生产环境下验证过得,尤其是网关 Zuul。

现在很多公司部署网关还是依旧优先选择使用 Zuul 1.x 版本部署。Zuul 2.x 版本和现在 SpringCloud 新推出的 Gateway 使用 Netty 作为底层,提供异步接口,不过这也间接增加了代码复杂性和缺少生产环境验证,所以现在生产环境还是推荐使用 Zuul 1.x 版本,这里记录在 Kubernetes 下部署 Zuul 1.x 的构建及部署过程。

二、Zuul 配置简介

1、Zuul 的路由配置

(1)、自动路由配置

Zuul 中引入 Eureka 依赖时并开启服务发现后,Zuul 会自动将注册中心中服务列表中的服务生成路由规则。例如,注册中心存在 service-A,service-B 两个服务实例,Zuul 会自动为其配置路由规则:

  • 访问地址 /service-A/** 来访问 service-A 服务。
  • 访问地址 /service-B/** 来访问 service-B 服务。

(2)、自定义路由配置

当然,我们也可以手动设置服务的路由规则,手动配置一般是在配置文件中设置服务路由规则,规则如下:

  • zuul.routes.<routeName>.path: 服务的监听地址,可以通过访问此地址访问到对应的服务信息。
  • zuul.routes.<routeName>.serviceId: 注册中心中服务实例名称。

例如,我们有一个 demo 的服务,该服务注册到注册中心实例名称也同样为 demo,那么可以按下配置:

1zuul:
2  routes:
3    demo:
4      path: /api/**
5      serviceId: demo

路由配置中的 path 就是用来匹配请求 url 中的路径,可以通过访问该地址从而访问到服务。path通常需要使用通配符,如下:

通配符 说明 举例
? 匹配单个字符 /api/?
* 匹配任意数量字符,但不支持多级目录 /api/*
** 匹配任意数量字符,支持多级目录 /api/**

如果有一个可以同时满足多个 path 的匹配的情况,此时匹配结果取决于路由规则的定义顺序。

(3)、忽略默认自动路由配置

Zuul 中引入 Eureka 依赖时并开启服务发现后,Zuul 会自动将注册中心中服务列表中的服务生成路由规则,默认的 path 为注册中心中服务名称,如果想忽略该服务,可以配置 zuul.ignored-services 参数。

  • 如果参数为服务名称,则 Zuul 不自动配置该服务路由。
  • 如果参数为 “*” ,则 Zuul 不自动配置全部服务。
1#忽略某个服务路由配置
2zuul:
3  ignored-services: myService
4
5#忽略全部服务自动路由配置
6zuul:
7  ignored-services: "*"

2、Cookie 与敏感 Headers 信息

默认情况下,Zuul 在请求路由时会过滤掉 HTTP 请求头信息中的一些敏感信息,防止这些敏感的头信息传递到下游外部服务器。但是如果我们使用安全框架,如 Spring Security、Apache Shiro 等,需要使用 Cookie 做登录和鉴权,这时可以配置 zuul.sensitiveHeaders 参数,该参数相当于配置一个黑名单,配置的值都是属于程序的敏感 header 信息,不允许传递到外部服务器。

sensitiveHeaders 设置的敏感头的默认值:

1zuul:
2  sensitiveHeaders: Cookie,Set-Cookie,Authorization

如果想自定义这些敏感头信息,可以设置如下:

 1#全局配置
 2zuul:
 3  sensitiveHeaders: Cookie
 4
 5#针对某条路由配置
 6zuul:
 7  routes:
 8    myServiceId:
 9      path: /api/**
10      serviceId: myService
11      sensitiveHeaders: Cookie,Authorization

如果认为 header 信息都不敏感,希望下游服务器能够获取全部 header 信息,则可以设置 zuul.sensitiveHeaders 参数为空:

 1#全局配置
 2zuul:
 3  sensitiveHeaders: 
 4  
 5#针对某条路由配置
 6zuul:
 7  routes:
 8    myServiceId:
 9      path: /api/**
10      serviceId: myService
11      sensitiveHeaders: 

3、忽略 Headers 配置

除了能设置敏感 header 过滤外,还可以设置 zuul.ignoredHeaders 参数来忽略某些 header 信息。

1zuul:
2  ignoredHeaders: customHeader1,customHeader2

这里的 ignoredHeaders 参数和上面的 sensitiveHeaders 参数类似,都是不向下游传递某些 header 信息,不过理念不同:

  • zuul.sensitiveHeaders: 主要思想是防止数据泄漏。例如 Zuul 连接到外部下游应用程序时,不应将敏感标头(如授权令牌)发送到外部服务。
  • zuul.ignoredHeaders: 主要思想是删除特定的标头。Zuul可以充当两方之间的匿名者,并且完全忽略可能泄露有关系统数据的标头。

4、Zuul 常用配置参数

zuul 有一些常用参数需要配置,例如路由配置等,常用参数如下:

 1zuul:                                       #---zuul配置---
 2  prefix: /v1                               #为路由添加统一前缀
 3  retryable: false                          #是否开启重试机制
 4  add-host-header: true                     #在请求路由转发前为请求设置Host头信息
 5  ignored-services: a-service,b-service     #忽略的服务
 6  ignored-patterns: /**/api/**              #通配符表达式方式来忽略多个请求路径
 7  sensitiveHeaders:                         #全局配置是为空来覆盖默认敏感头信息,默认情况下zuul不会将Cookie,Set-Cookie,Authorization这三个头
 8                                            #传递到下游服务,如果需要传递,就需要忽略这些敏感头
 9  routes:                                   #---路由策略配置---
10    myServiceId:                            #自定义zuul路由服务ID
11      path: /api/**                         #自定义服务监听地址
12      serviceId: myService                  #注册中心中的服务名称
13      stripPrefix: true                     #是否移除前缀,例如设置为ture时,访问/api/aa那么将代理到服务的/aa接口 
14      customSensitiveHeaders:               #是否对指定路由开启自定义敏感头

5、Hystrix 和 Ribbon 支持

设置 Ribbon 超时时间

1ribbon:
2  ReadTimeout: 15000
3  ConnectTimeout: 5000
4  SocketTimeout: 15000

设置 Hystrix 断路器超时时间

1hystrix:
2  command:
3    myusers-service:
4      execution:
5        isolation:
6          thread:
7            timeoutInMilliseconds: 15000

使用 ribbon.ConnectTimeout 参数创建请求连接的超时时间,当 ribbon.ConnectTimeout 的配置值小于 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 的配置值时,若出现请求超时的时候,会自动进行重试路由请求,如果依然失败,Zuul 会返回如下 JSON 信息给外部调用方。

如果 ribbon.ConnectTimeout 的配置值大于 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 的配置值时,当出现请求超时的时候不会进行重试,直接超时处理返回 timeout 的错误信息。

6、Zuul 中的 Hystrix 服务降级

Zuul 中默认引入了 Hystrix(断路器),当连接服务超时时候会进行服务降级,执行降级操作。这个服务降级可以指定只针对某些服务或者全部服务。

指定某服务降级设置:

 1class MyFallbackProvider implements FallbackProvider {
 2
 3    @Override
 4    public String getRoute() {
 5        // 可以单独设置为 ServiceID 来指定只生效某个服务
 6        return "customers";
 7    }
 8
 9    @Override
10    public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
11        if (cause instanceof HystrixTimeoutException) {
12            return response(HttpStatus.GATEWAY_TIMEOUT);
13        } else {
14            return response(HttpStatus.INTERNAL_SERVER_ERROR);
15        }
16    }
17
18    private ClientHttpResponse response(final HttpStatus status) {
19        return new ClientHttpResponse() {
20            @Override
21            public HttpStatus getStatusCode() throws IOException {
22                return status;
23            }
24
25            @Override
26            public int getRawStatusCode() throws IOException {
27                return status.value();
28            }
29
30            @Override
31            public String getStatusText() throws IOException {
32                return status.getReasonPhrase();
33            }
34
35            @Override
36            public void close() {
37            }
38
39            @Override
40            public InputStream getBody() throws IOException {
41                return new ByteArrayInputStream("fallback".getBytes());
42            }
43
44            @Override
45            public HttpHeaders getHeaders() {
46                HttpHeaders headers = new HttpHeaders();
47                headers.setContentType(MediaType.APPLICATION_JSON);
48                return headers;
49            }
50        };
51    }
52}

全部服务降级设置:

 1class MyFallbackProvider implements FallbackProvider {
 2    @Override
 3    public String getRoute() {
 4        // 设置为 “*” 即指定为全部服务
 5        return "*";
 6    }
 7
 8    @Override
 9    public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
10        return new ClientHttpResponse() {
11            @Override
12            public HttpStatus getStatusCode() throws IOException {
13                return HttpStatus.OK;
14            }
15
16            @Override
17            public int getRawStatusCode() throws IOException {
18                return 200;
19            }
20
21            @Override
22            public String getStatusText() throws IOException {
23                return "OK";
24            }
25
26            @Override
27            public void close() {
28
29            }
30
31            @Override
32            public InputStream getBody() throws IOException {
33                return new ByteArrayInputStream("fallback".getBytes());
34            }
35
36            @Override
37            public HttpHeaders getHeaders() {
38                HttpHeaders headers = new HttpHeaders();
39                headers.setContentType(MediaType.APPLICATION_JSON);
40                return headers;
41            }
42        };
43    }
44}

三、Zuul 对服务限制流量

Zuul 作为网关,经常要做一些限流策略来防止被恶意攻击。由于 Zuul 自身并没有提供限流功能,我们可以依赖第三方组件帮我们完成限流工作。

Zuul 限流组件有很多,比较常用是 spring-cloud-zuul-ratelimit 组件,由于 Zuul 是分布式的,所以多个实例间共享限流数据,ratelimit 该组件提供了多种存储访问数据量的方法,如将访问次数和量存入 Redis、Consul 或者数据库以及 Bucket4j JCache 中,最常用的就是存入 Redis 中,所以这里以使用 Redis 作为介绍。

1、Maven 引入其依赖

一般情况下,我们不光会使用其 redis 作为值存储,所以共需要引入两个依赖:

 1<!--ratelimit-->
 2<dependency>
 3    <groupId>com.marcosbarbero.cloud</groupId>
 4    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
 5    <version>LATEST</version>
 6</dependency>
 7<!--redis-->
 8<dependency>
 9    <groupId>org.springframework.boot</groupId>
10    <artifactId>spring-boot-starter-data-redis</artifactId>
11</dependency>

2、配置文件中设置限流配置

限流需要配置一些全局和针对某个服务的限流策略,更具体可以访问 spring-cloud-zuul-ratelimit 的 github 地址 了解更多信息,下面部分是常用参数:

 1zuul:
 2  ratelimit:                    #---zuul 限流配置---
 3    enabled: true               #是否启用限流
 4    key-prefix: your-prefix     #限流数据 key 前缀
 5    repository: redis           #限流数据存储方式
 6    behind-proxy: true          #
 7    add-response-headers: true  #
 8    default-policy-list:        #---全局默认限流策略---
 9      - limit: 10               #单位时间内允许访问的次数
10        quota: 100              #单位时间内全部访问时间总和的限制(全部访问时间不能超过该时间)单位(s)
11        refresh-interval: 10    #单位时间设置,超过该时间后刷新限制,默认60s,单位(s)
12    policy-list:                #---针对某个服务单独设置限流策略---
13      myService:              #zuul路由策略中配置的路由ID,不是 ServiceId
14        - limit: 20             #该服务的单位时间内允许访问的次数
15          quota: 100            #该服务单位时间内全部访问时间总和的限制
16          refresh-interval: 30  #单位时间设置

上面就是 Zuul-Ratelimit 中对服务限速的设置,设置了全局限速设置为 10s 内每个服务只能访问 10 次。另外还单独对 myService 服务限制 30s 内只能访问 20 次。

四、Kubernetes 部署 Zuul 需要的环境

Kubernetes 下部署 Zuul 网关,需要在该环境下提前部署注册中心、配置中心、Redis 等组件:

  • 注册中心: Eureka
  • 配置中心: SpringCloud Kubernetes Config
  • Redis: Rdis 缓存

1、注册中心

在 SpringCloud 微服务框架中,注册中心是一个必不可少的组件,全部微服务会注册到其中。Eureka 是我们常用的注册中心,Zuul 会从注册中心中拉取一份服务列表,并且动态配置路由规则。所以我们需要在 Kubernetes 环境下,提前布置好注册中心。如何在 Kubernetes 部署 Eureka 注册中心 在之前博博客中已经写过,这里不过多描述。

本人在示例的 Kuberenetes 内部已经部署的 Eureka 的地址环境如下:

2、配置中心

在 SpringCloud 微服务框架中,我们需要一个配置中心来管理整个微服务的配置,并且实现动态刷新配置。我们常用的做法是部署 SpringCloud Config 组件,但是由于 SpringCloud Config 需要将配置存放于 Git 或者 SVN 中,这两种存储仓库都会有不可读取或者读取超时问题,动态通知可能还需要部署一个消息队列动来充当消息总线,还要保证队列的稳定性从而不影响全部服务的可用性,故而比较麻烦和需要保证可用性,这里不太推荐。

由于 SpringCloud 各个组件和服务都是部署在 Kubernetes 环境下,而 Kubernetes 自己也有个配置管理的 ConfigMap。考虑到这点 SpringCloud 也有 SpringCloud Kubernetes Config 项目来完成从 ConfigMap 动态读取配置信息,只要 Kubernetes 整个集群不宕机,那么配置就可以得到保障,这是目前比较推荐的组件。

关于 如何使用 SpringCloud Config 完成动态配置 之前文章中也有写,需要了解的可以查看下。当然,如果你使用的是 Apollo 配置中心也是可以。

3、Redis 缓存

由于分布式环境,限速需要将访问数据共享给各个客户端,将这些数据存入 Redis 中,所以需要在提前部署 Redis 在 Kubernetes 集群内或者集群外都可。

五、Zuul 示例项目

1、Maven 引入相关依赖

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 4    <modelVersion>4.0.0</modelVersion>
 5
 6    <parent>
 7        <groupId>org.springframework.boot</groupId>
 8        <artifactId>spring-boot-starter-parent</artifactId>
 9        <version>2.1.8.RELEASE</version>
10    </parent>
11
12    <groupId>club.mydlq</groupId>
13    <artifactId>springcloud-zuul-demo</artifactId>
14    <version>0.0.1</version>
15    <name>springcloud-zuul-demo</name>
16    <description>springboot zuul 1.x demo project</description>
17
18    <properties>
19        <java.version>1.8</java.version>
20    </properties>
21
22    <dependencies>
23        <!--web-->
24        <dependency>
25            <groupId>org.springframework.boot</groupId>
26            <artifactId>spring-boot-starter-web</artifactId>
27        </dependency>
28        <!--actuator-->
29        <dependency>
30            <groupId>org.springframework.boot</groupId>
31            <artifactId>spring-boot-starter-actuator</artifactId>
32        </dependency>
33        <!--eureka-->
34        <dependency>
35            <groupId>org.springframework.cloud</groupId>
36            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
37        </dependency>
38        <!--zuul-->
39        <dependency>
40            <groupId>org.springframework.cloud</groupId>
41            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
42        </dependency>
43        <!--rate limit-->
44        <dependency>
45            <groupId>com.marcosbarbero.cloud</groupId>
46            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
47            <version>2.2.5.RELEASE</version>
48        </dependency>
49        <!--redis-->
50        <dependency>
51            <groupId>org.springframework.boot</groupId>
52            <artifactId>spring-boot-starter-data-redis</artifactId>
53        </dependency>
54        <!--lombok-->
55        <dependency>
56            <groupId>org.projectlombok</groupId>
57            <artifactId>lombok</artifactId>
58            <scope>provided</scope>
59        </dependency>
60        <!--springcloud kubernetes config-->
61        <dependency>
62            <groupId>org.springframework.cloud</groupId>
63            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
64        </dependency>
65    </dependencies>
66
67    <dependencyManagement>
68        <!--SpringCloud 版本管理-->
69        <dependencies>
70            <dependency>
71                <groupId>org.springframework.cloud</groupId>
72                <artifactId>spring-cloud-dependencies</artifactId>
73                <version>Greenwich.SR2</version>
74                <type>pom</type>
75                <scope>import</scope>
76            </dependency>
77        </dependencies>
78    </dependencyManagement>
79
80    <build>
81        <plugins>
82            <plugin>
83                <groupId>org.springframework.boot</groupId>
84                <artifactId>spring-boot-maven-plugin</artifactId>
85            </plugin>
86        </plugins>
87    </build>
88
89</project>

2、服务降级

Zuul 中默认引入了 Hystrix 断路器,当服务熔断时会进行服务降级,这里可以配置服务降级类。

 1import com.fasterxml.jackson.core.JsonProcessingException;
 2import com.fasterxml.jackson.databind.ObjectMapper;
 3import com.netflix.hystrix.exception.HystrixTimeoutException;
 4import lombok.extern.slf4j.Slf4j;
 5import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
 6import org.springframework.http.HttpHeaders;
 7import org.springframework.http.HttpStatus;
 8import org.springframework.http.MediaType;
 9import org.springframework.http.client.ClientHttpResponse;
10import org.springframework.stereotype.Component;
11import java.io.ByteArrayInputStream;
12import java.io.InputStream;
13import java.nio.charset.StandardCharsets;
14import java.util.HashMap;
15import java.util.Map;
16
17@Slf4j
18@Component
19public class ServiceFallbackProvider implements FallbackProvider {
20
21    private static final ObjectMapper objectMapper = new ObjectMapper();
22
23    @Override
24    public String getRoute() {
25        // 可以单独指定 ServiceID,也可以设置“*”全部服务
26        return "springboot-helloworld";
27    }
28
29    @Override
30    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
31        if (cause instanceof HystrixTimeoutException) {
32            return response(HttpStatus.GATEWAY_TIMEOUT);
33        } else {
34            return response(HttpStatus.INTERNAL_SERVER_ERROR);
35        }
36    }
37
38    private ClientHttpResponse response(final HttpStatus status) {
39        return new ClientHttpResponse() {
40            @Override
41            public HttpStatus getStatusCode() {
42                return status;
43            }
44
45            @Override
46            public int getRawStatusCode() {
47                return status.value();
48            }
49
50            @Override
51            public String getStatusText() {
52                return status.getReasonPhrase();
53            }
54
55            @Override
56            public void close() {
57            }
58
59            @Override
60            public InputStream getBody() throws JsonProcessingException {
61                Map<String,String> map = new HashMap<>();
62                map.put("code", "501");
63                map.put("message", "后台服务异常");
64                String json = objectMapper.writeValueAsString(map);
65                return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
66            }
67
68            @Override
69            public HttpHeaders getHeaders() {
70                HttpHeaders headers = new HttpHeaders();
71                headers.setContentType(MediaType.APPLICATION_JSON);
72                return headers;
73            }
74        };
75    }
76}

3、配置文件

这里需要配置 application.yml 和 bootstrap.yml 两个文件,SpirngBoot 在加载时候会先加载 bootstrap.yml 内容然后再加载 application.yml。

在这里 application.yml 和 bootstrap.yml 两个文件分别有不同的作用:

  • application: 这里一般用于 Redis、Log、Eureka、Hystrix、ribbon、ZuulZuul-Ratelimit 等的配置,在将 Zuul 部署到 Kubernetes 前,会将此配置文件提前存入 Kubernetes 的 ConfigMap 中。
  • bootstrap: 设置 SpringCloud Kubernetes Config 配置参数,动态读取上面配置的 Kubernetes 下的 ConfigMap 的参数值。

在 applicaion.yml 中还存在路由规则的配置,不过一般情况下路由规则配置会存入 Kubernetes 下的 ConfigMap 中,我们可以配合 SpringCloud Kubernetes Config 组件对其进行动态配置,使路由规则快速生效,这里为了演示方便,下面将配置一条路由规则,由于在本人 Kubernetes 集群中已经存在的一个供测试的 HelloWorld 服务,这里便设置路由规则来代理该服务

application.yml

 1#Redis
 2spring:
 3  redis:
 4    #host: 192.168.2.11
 5    #port: 30379
 6    host: redis-master.mydlqcloud
 7    port: 6379
 8    password: 123456
 9
10#Log Config
11logging:
12  path: /opt/logs/
13
14#Eureka Config
15eureka:
16  client:
17    service-url:
18      #defaultZone: http://192.168.2.11:31011/eureka/
19      defaultZone: http://eureka-0.eureka.mydlqcloud:8080/eureka/,http://eureka-1.eureka.mydlqcloud:8080/eureka/,http://eureka-2.eureka.mydlqcloud:8080/eureka/
20
21#Ribbon Timeout Config
22ribbon:
23  ReadTimeout: 2000
24  ConnectTimeout: 5000
25  SocketTimeout: 2000
26
27#Hystrix Config
28hystrix:
29  command:
30    default:
31      execution:
32        isolation:
33          thread:
34            timeoutInMilliseconds: 10000
35
36#Zuul Config
37zuul:
38  retryable: false
39  add-host-header: false
40  prefix: /v1
41  routes:
42    helloworld:
43      path: /helloworld/**
44      serviceId: springboot-helloworld
45  ratelimit:
46    enabled: true
47    key-prefix: retelimit
48    repository: redis
49    behind-proxy: true
50    add-response-headers: true
51    # globle retelimit config
52    default-policy-list:
53      - limit: 10
54        quota: 100
55        refresh-interval: 10
56    # single service config
57    policy-list:
58      helloworld:
59        - limit: 5
60          quota: 100
61          refresh-interval: 10

bootstrap.yml

 1#Base Config
 2server:
 3  port: 8080
 4spring:
 5  application:
 6    name: springcloud-zuul-demo
 7  cloud:
 8    kubernetes:
 9      reload:
10        enabled: true
11        mode: polling
12        period: 5000
13        strategy: refresh
14        monitoring-secrets: true
15      config:
16        enabled: true
17        enableApi: true
18        sources:
19          - namespace: mydlqcloud
20            name: springcloud-zuul-config
21#Actuator Config
22management:
23  server:
24    port: 8080
25  endpoint:
26    restart:
27      enabled: true
28    health:
29      show-details: always
30  endpoints:
31    web:
32      exposure:
33        include: "*"

4、启动类

启动类上需要加上俩个注解来启用 Zuul 和服务发现:

  • @EnableZuulProxy: 启用 Zuul。
  • @EnableDiscoveryClient: 启用 SpringCloud 服务发现,这里会开启 Eureka。
 1import org.springframework.boot.SpringApplication;
 2import org.springframework.boot.autoconfigure.SpringBootApplication;
 3import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 4import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
 5
 6@EnableZuulProxy
 7@EnableDiscoveryClient
 8@SpringBootApplication
 9public class Application {
10
11    public static void main(String[] args) {
12        SpringApplication.run(Application.class, args);
13    }
14
15}

六、构建 Docker 镜像

1、执行 Maven 命令将 Zuul 打包成 Jar

首先执行 Maven 命令,将项目编译成一个可执行 JAR。

1$ mvn clean install

2、准备 Dockerfile

创建构建 Docker 镜像需要的 Dockerfile 文件,放置到项目 根目录 中。脚本会在执行构建镜像时,将 Maven 编译的 JAR 复制到镜像内部。

Dockerfile:

1FROM openjdk:8u222-jre-slim
2VOLUME /tmp
3ADD target/*.jar app.jar
4RUN sh -c 'touch /app.jar'
5ENV JVM_OPTS="-Xss256k -XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai -Djava.security.egd=file:/dev/./urandom"
6ENV JAVA_OPTS=""
7ENV APP_OPTS=""
8ENTRYPOINT [ "sh", "-c", "java $JVM_OPTS $JAVA_OPTS -jar /app.jar $APP_OPTS" ]

上面设置了三个变量,分别是:

  • JVM_OPTS 设置一些必要的 JVM 启动参数。
  • JAVA_OPTS: Java JVM 启动参数变量,这里需要在这里加一个时区参数。
  • APP_OPTS: Spring 容器启动参数变量,方便后续操作时能通过此变量配置 Spring 参数。

3、构建与推送镜像

执行 Docker Build 命令构建 Docker 镜像,构建完成后在执行 Docker push 命令,将镜像推送到镜像仓库。

1# 构建镜像
2$ docker build -t mydlqclub/springcloud-zuul:0.0.1 .
3
4# 推送镜像
5$ docker push mydlqclub/springcloud-zuul:0.0.1

七、Kubernetes 部署 Zuul

1、创建应用权限 RBAC

由于程序需要读取 ConfigMap 中的配置,需要一定的权限,这里提前创建一个 RBAC 对象来供程序绑定以获取读取的权限,下面是 RBAC 对象的部署文件。

  • 注意:需要修改下面的全部 Namespace 参数,修改为你自己 Kubernetes 集群的 Namespace 名称。

zuul-rbac.yaml

 1apiVersion: v1
 2kind: ServiceAccount
 3metadata:
 4  name: springcloud-zuul
 5  namespace: mydlqcloud
 6---
 7kind: ClusterRoleBinding
 8apiVersion: rbac.authorization.k8s.io/v1
 9metadata:
10  name: springcloud-zuul
11subjects:
12  - kind: ServiceAccount
13    name: springcloud-zuul
14    namespace: mydlqcloud
15roleRef:
16  kind: ClusterRole
17  name: cluster-admin
18  apiGroup: rbac.authorization.k8s.io

在 Kubernetes 中部创建 RBAC 对象

  • -n:创建应用到指定的 Namespace 中。
1$ kubectl apply -f zuul-rbac.yaml -n mydlqcloud

2、ConfigMap 应用配置文件

创建 application-configmap.yaml 配置文件,将示例项目中 application.yml 配置文件中的配置项复制到 ConfigMap 中,应用中已经在 bootstrap.yml 文件中配置了 SpringCloud Kubernetes Config 组件的配置项,用于读取 ConfigMap 中的配置。

application-configmap.yaml

 1kind: ConfigMap
 2apiVersion: v1
 3metadata:
 4  name: springcloud-zuul-config
 5data:
 6  application.yaml: |-
 7    #Redis
 8    spring:
 9      redis:
10        #host: 192.168.2.11
11        #port: 30379
12        host: redis-master.mydlqcloud
13        port: 6379
14        password: 123456
15    
16    #Log Config
17    logging:
18      path: /opt/logs/
19    
20    #Eureka Config
21    eureka:
22      client:
23        service-url:
24          #defaultZone: http://192.168.2.11:31011/eureka/
25          defaultZone: http://eureka-0.eureka.mydlqcloud:8080/eureka/,http://eureka-1.eureka.mydlqcloud:8080/eureka/,http://eureka-2.eureka.mydlqcloud:8080/eureka/
26    
27    #Ribbon Timeout Config
28    ribbon:
29      ReadTimeout: 2000
30      ConnectTimeout: 5000
31      SocketTimeout: 2000
32    
33    #Hystrix Config
34    hystrix:
35      command:
36        default:
37          execution:
38            isolation:
39              thread:
40                timeoutInMilliseconds: 10000
41    
42    #Zuul Config
43    zuul:
44      retryable: false
45      add-host-header: false
46      prefix: /v1
47      routes:
48        helloworld:
49          path: /helloworld/**
50          serviceId: springboot-helloworld
51      ratelimit:
52        enabled: true
53        key-prefix: retelimit
54        repository: redis
55        behind-proxy: true
56        add-response-headers: true
57        # globle retelimit config
58        default-policy-list:
59          - limit: 10
60            quota: 100
61            refresh-interval: 10
62        # single service config
63        policy-list:
64          helloworld:
65            - limit: 5
66              quota: 100
67              refresh-interval: 10    

在 Kubernetes 中部署应 ConfigMap 对象

  • -n:创建应用到指定的 Namespace 中。
1$ kubectl apply -f application-configmap.yaml -n mydlqcloud

3、Kubernetes 应用部署文件

Zuul 要部署在 Kubernetes 环境下,需要部署为无状态应用,即需要将 Zuul 创建一个 Deployment 对象,下面是 Zuul 的部署文件。

springcloud-zuul.yaml

 1apiVersion: v1
 2kind: Service
 3metadata:
 4  name: springcloud-zuul
 5spec:
 6  type: NodePort
 7  ports:
 8    - name: server
 9      nodePort: 31085
10      port: 8080
11      targetPort: 8080
12    - name: management
13      nodePort: 31086
14      port: 8081
15      targetPort: 8081
16  selector:
17    app: springcloud-zuul
18---
19apiVersion: apps/v1
20kind: Deployment
21metadata:
22  name: springcloud-zuul
23  labels:
24    app: springcloud-zuul
25spec:
26  replicas: 1
27  selector:
28    matchLabels:
29      app: springcloud-zuul
30  template:
31    metadata:
32      name: springcloud-zuul
33      labels:
34        app: springcloud-zuul
35    spec:
36      serviceAccountName: springcloud-zuul
37      containers:
38        - name: springcloud-zuul
39          image: mydlqclub/springcloud-zuul:0.0.1
40          #imagePullPolicy: Always
41          ports:
42            - name: server
43              containerPort: 8080
44            - name: management
45              containerPort: 8081
46          resources:
47            limits:
48              memory: 2048Mi
49              cpu: 2000m
50            requests:
51              memory: 2048Mi
52              cpu: 2000m
53          readinessProbe:
54            initialDelaySeconds: 20
55            periodSeconds: 5
56            timeoutSeconds: 10
57            failureThreshold: 5
58            httpGet:
59              path: /actuator/health
60              port: 8081
61          livenessProbe:
62            initialDelaySeconds: 60
63            periodSeconds: 5
64            timeoutSeconds: 5
65            failureThreshold: 3
66            httpGet:
67              path: /actuator/health
68              port: 8081
69          volumeMounts:
70            - name: log
71              mountPath: /opt/logs
72      volumes:
73        - name: log
74          hostPath:
75            type: DirectoryOrCreate
76            path: /data/apps/logs

在 Kubernetes 中部署 Zuul 应用

  • -n:创建应用到指定的 Namespace 中。
1$ kubectl apply -f springcloud-zuul.yaml -n mydlqcloud

八、测试部署的应用接口

将 Zuul 部署到 Kubernetes 后,我们可以通过 Kubernetes 集群地址 192.168.2.11 加上配置的 Service 的 NodePort 端口 31085 访问 Zuul 中 helloworld 服务,所以这里输入地址 http://192.168.2.11:31085 加上设置的前缀 /v1 与测试服务 helloworld 的服务名 helloworld 进行测试。

1、测试网关路由

输入地址 http://192.168.2.11:31085/v1/helloworld 访问 springboot-helloworld 服务,返回结果如下:

1HelloWorld!

可以看到 Zuul 网关生效,能成功代理 springboot-helloworld 服务接口。

2、测试限流

Zuul 中引用了 zuul-ratelimit 组件用于限速,上面配置 helloworld 服务,在 10s 内限制访问 5 次,连续访问 5 次地址 http://192.168.2.11:31085/v1/helloworld 访问 springboot-helloworld 服务,返回结果如下:

1HelloWorld!

然后输入第 6 次时,返回信息如下:

1Whitelabel Error Page
2This application has no explicit mapping for /error, so you are seeing this as a fallback.
3
4Sat Oct 05 20:39:11 CST 2019
5There was an unexpected error (type=Too Many Requests, status=429).
6429 TOO_MANY_REQUESTS

可以看到抛出限速异常以及状态码 426,说明限速已经生效。

3、测试动态路由

改变之前存入 ConfigMap 中的路由配置:

1zuul:
2  prefix: /v1
3  routes:
4    helloworld:
5      path: /test/**
6      serviceId: springboot-helloworld

等待几秒钟后,再次输入地址 http://192.168.2.11:31085/v1/test 访问 springboot-helloworld 服务,查看改变后是否能生效,返回结果如下:

1HelloWorld!

可以看到,可以成功的访问到 springboot-helloworld 服务,动态路由配置能及时生效。


好了,到此 Kubernetes 部署 Zuul 结束,在 Kubernetes 中集群外提供服务一般我们会部署 Ingress Controller 来代理 Kubernetes 内部服务。有了 Ingress Controller 就可以喷子 Zuul 的 Ingress 路由规则,让外部流量流入 Zuul 服务。由于不同的 Ingress Controller 有不同的 Ingress 路由配置,这里就不过多描述。

---END---


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。