Kubernetes / Linux Note / 运维笔记

Kubernetes (六) SpringCloud Zuul Eureka 动态路由

Einic Yeo · 10月5日 · 2019年 · · · · · ·

系统环境

  • 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://mirrors.infvie.org/blog-example/springcloud/springcloud-zuul-demo

一、Zuul 概念简介

1、简单介绍下 Zuul

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

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

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

现在很多 SpringCloud 组件都在往 Kubernetes 中迁移,版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!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: 服务的监听地址,可以通过访问此地址访问到对应的服版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!务信息。
  • zuul.routes.<routeName>.serviceId: 注册中心中服务实例名称。

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

zuul:
  routes:
    demo:
      path: /api/**
      serviceId: demo

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

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

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

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

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

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

#忽略全部服务自动路由配置
zuul:
  ignored-services: "*"

2、Cookie 与敏感 Headers 信息

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

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

zuul:
  sensitiveHeaders: Cookie,Set-Cookie,Authorization

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

#全局配置
zuul:
  sensitiveHeaders: Cookie

#针对某条路由配置
zuul:
  routes:
    myServiceId:
      path: /api/**
      serviceId: myService
      sensitiveHeaders: Cookie,Authorization

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

#全局配置
zuul:
  sensitiveHeaders: 
  
#针对某条路由配置
zuul:
  routes:
    myServiceId:
      path: /api/**
      serviceId: myService
      sensitiveHeaders: 

3、忽略 Headers 配置

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

zuul:
  ignoredHeaders: customHeader1,customHeader2

这里的 igno版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!redHeaders 参数和上面的 sensitiveHeaders 参数类似,都是不向下游传递某些 header 信息,不过理念不同:

  • zuul.sensitiveHeaders: 主要思想是防止数据泄漏。例如 Zuul 连接到外部下游应用程序时,不应将敏感标头(如授权令牌)发送到外部服务。
  • zuul.ignoredHeaders: 主要思想是删除特定的标头。Zuul可以充当两方之间的匿名者,并且完全忽略可能泄露有关系统数据的标头。
版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!

4、Zuul 常用配置参数

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

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

5、Hystrix 和 Ribbon 支持

设置 Ribbon 超时时间

ribbon:
  ReadTimeout: 15000
  ConnectTimeout: 5000
  SocketTimeout: 15000

设置 Hystrix 断路器超时时间

hystrix:
  command:
    myusers-service:
      execution:
        isolation:
          thread:
            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(断路器),当连接服务超时时候会进行服务降级,执行降级操作。这个服务降级可以指定只针对某些服务或者全部服务。

指定某服务降级设置:

class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        // 可以单独设置为 ServiceID 来指定只生效某个服务
        return "customers";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return status;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return status.value();
            }

            @Override
            public String getStatusText() throws IOException {
                return status.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

全部服务降级设置:

class MyFallbackProvider implements FallbackProvider {
    @Override
    public String getRoute() {
        // 设置为 “*” 即指定为全部服务
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

三、Zuul 对服务限制流量

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

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

1、Maven 引入其依赖

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

<!--ratelimit-->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>LATEST</version>
</dependency>
<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

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

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

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

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

四、Kubernetes 部署 Zuul 需要的环境

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

  • 注册中心: Eureka
  • 配置中心: SpringCloud Kubernetes Config
  • Redis: R版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!dis 缓存

1、注册中心

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

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

  • http://eureka-0.eureka.infvieqcloud:8080/eureka/
  • http://eureka-1.eureka.infviecloud:8080/eureka/
  • http://eureka-2.eureka.infviecloud:8080/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 引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
    </parent>

    <groupId>club.infvie</groupId>
    <artifactId>springcloud-zuul-demo</artifactId>
    <version>0.0.1</version>
    <name>springcloud-zuul-demo</name>
    <description>springboot zuul 1.x demo project</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--zuul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!--rate limit-->
        <dependency>
            <groupId>com.marcosbarbero.cloud</groupId>
            <artifactId>spring-cloud-zuul-ratelimit</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--springcloud kubernetes config-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-kubernetes-config</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <!--SpringCloud 版本管理-->
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

</project>

2、服务降级

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

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.hystrix.exception.HystrixTimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class ServiceFallbackProvider implements FallbackProvider {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String getRoute() {
        // 可以单独指定 ServiceID,也可以设置“*”全部服务
        return "springboot-helloworld";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return response(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private ClientHttpResponse response(final HttpStatus status) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() {
                return status;
            }

            @Override
            public int getRawStatusCode() {
                return status.value();
            }

            @Override
            public String getStatusText() {
                return status.getReasonPhrase();
            }

            @Override
            public void close() {
            }

            @Override
            public InputStream getBody() throws JsonProcessingException {
                Map<String,String> map = new HashMap<>();
                map.put("code", "501");
                map.put("message", "后台服务异常");
                String json = objectMapper.writeValueAsString(map);
                return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

3、配置文件

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

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

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

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

application.yml

#Redis
spring:
  redis:
    #host: 192.168.2.11
    #port: 30379
    host: redis-master.infviecloud
    port: 6379
    password: 123456

#Log Config
logging:
  path: /opt/logs/

#Eureka Config
eureka:
  client:
    service-url:
      #defaultZone: http://192.168.2.11:31011/eureka/
      defaultZone: http://eureka-0.eureka.infviecloud:8080/eureka/,http://eureka-1.eureka.infviecloud:8080/eureka/,http://eureka-2.eureka.infviecloud:8080/eureka/

#Ribbon Timeout Config
ribbon:
  ReadTimeout: 2000
  ConnectTimeout: 5000
  SocketTimeout: 2000

#Hystrix Config
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000

#Zuul Config
zuul:
  retryable: false
  add-host-header: false
  prefix: /v1
  routes:
    helloworld:
      path: /helloworld/**
      serviceId: springboot-helloworld
  ratelimit:
    enabled: true
    key-prefix: retelimit
    repository: redis
    behind-proxy: true
    add-response-headers: true
    # globle retelimit config
    default-policy-list:
      - limit: 10
        quota: 100
        refresh-interval: 10
    # single service config
    policy-list:
      helloworld:
        - limit: 5
          quota: 100
          refresh-interval: 10

bootstrap.yml

#Base Config
server:
  port: 8080
spring:
  application:
    name: springcloud-zuul-demo
  cloud:
    kubernetes:
      reload:
        enabled: true
        mode: polling
        period: 5000
        strategy: refresh
        monitoring-secrets: true
      config:
        enabled: true
        enableApi: true
        sources:
          - namespace: infviecloud
            name: springcloud-zuul-config
#Actuator Config
management:
  server:
    port: 8080
  endpoint:
    restart:
      enabled: true
    health:
      show-details: always
  endpoints:
    web:
      exposure:
        include: "*"

4、启动类

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

  • @EnableZuulProxy: 启用 Zuul。
  • @EnableDiscoveryClient: 启用 SpringCloud 服务发现,这里会开启 Eureka。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class Application {

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

}

六、构建 Docker 镜像

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

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

$ mvn clean install

2、准备 Dockerfile

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

Dockerfile:

FROM openjdk:8u222-jre-slim
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JVM_OPTS="-Xss256k -XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai -Djava.security.egd=file:/dev/./urandom"
ENV JAVA_OPTS=""
ENV APP_OPTS=""
ENTRYPOINT [ "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 命令,将镜像推送到镜像仓库。

# 构建镜像
$ docker build -t infvieclub/springcloud-zuul:0.0.1 .

# 推送镜像
$ docker push infvieclub/springcloud-zuul:0.0.1

七、Kubernetes 部署 Zuul

1、创建应用权限 RBAC

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

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

zuul-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: springcloud-zuul
  namespace: infviecloud
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: springcloud-zuul
subjects:
  - kind: ServiceAccount
    name: springcloud-zuul
    namespace: infviecloud
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

在 Kubernetes 中部创建 RBAC 对象

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

2、ConfigMap 应用配置文件

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

application-configmap.yaml

kind: ConfigMap
apiVersion: v1
metadata:
  name: springcloud-zuul-config
data:
  application.yaml: |-
    #Redis
    spring:
      redis:
        #host: 192.168.2.11
        #port: 30379
        host: redis-master.infviecloud
        port: 6379
        password: 123456
    
    #Log Config
    logging:
      path: /opt/logs/
    
    #Eureka Config
    eureka:
      client:
        service-url:
          #defaultZone: http://192.168.2.11:31011/eureka/
          defaultZone: http://eureka-0.eureka.infviecloud:8080/eureka/,http://eureka-1.eureka.infviecloud:8080/eureka/,http://eureka-2.eureka.infviecloud:8080/eureka/
    
    #Ribbon Timeout Config
    ribbon:
      ReadTimeout: 2000
      ConnectTimeout: 5000
      SocketTimeout: 2000
    
    #Hystrix Config
    hystrix:
      command:
        default:
          execution:
            isolation:
              thread:
                timeoutInMilliseconds: 10000
    
    #Zuul Config
    zuul:
      retryable: false
      add-host-header: false
      prefix: /v1
      routes:
        helloworld:
          path: /helloworld/**
          serviceId: springboot-helloworld
      ratelimit:
        enabled: true
        key-prefix: retelimit
        repository: redis
        behind-proxy: true
        add-response-headers: true
        # globle retelimit config
        default-policy-list:
          - limit: 10
            quota: 100
            refresh-interval: 10
        # single service config
        policy-list:
          helloworld:
            - limit: 5
              quota: 100
              refresh-interval: 10    

在 Kubernetes 中部署应 ConfigMap 对象

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

3、Kubernetes 应用部署文件

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

springcloud-zuul.yaml

apiVersion: v1
kind: Service
metadata:
  name: springcloud-zuul
spec:
  type: NodePort
  ports:
    - name: server
      nodePort: 31085
      port: 8080
      targetPort: 8080
    - name: management
      nodePort: 31086
      port: 8081
      targetPort: 8081
  selector:
    app: springcloud-zuul
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: springcloud-zuul
  labels:
    app: springcloud-zuul
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springcloud-zuul
  template:
    metadata:
      name: springcloud-zuul
      labels:
        app: springcloud-zuul
    spec:
      serviceAccountName: springcloud-zuul
      containers:
        - name: springcloud-zuul
          image: infvieclub/springcloud-zuul:0.0.1
          #imagePullPolicy: Always
          ports:
            - name: server
              containerPort: 8080
            - name: management
              containerPort: 8081
          resources:
            limits:
              memory: 2048Mi
              cpu: 2000m
            requests:
              memory: 2048Mi
              cpu: 2000m
          readinessProbe:
            initialDelaySeconds: 20
            periodSeconds: 5
            timeoutSeconds: 10
            failureThreshold: 5
            httpGet:
              path: /actuator/health
              port: 8081
          livenessProbe:
            initialDelaySeconds: 60
            periodSeconds: 5
            timeoutSeconds: 5
            failureThreshold: 3
            httpGet:
              path: /actuator/health
              port: 8081
          volumeMounts:
            - name: log
              mountPath: /opt/logs
      volumes:
        - name: log
          hostPath:
            type: DirectoryOrCreate
            path: /data/apps/logs

在 Kubernetes 中部署 Zuul 应用

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

八、测试部署的应用接口

将 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 服务,返回结果如下:

HelloWorld!

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

2、测试限流

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

HelloWorld!

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

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Oct 05 20:39:11 CST 2019
There was an unexpected error (type=Too Many Requests, status=429).
429 TOO_MANY_REQUESTS

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

3、测试动态路由

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

zuul:
  prefix: /v1
  routes:
    helloworld:
      path: /test/**
      serviceId: springboot-helloworld

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

HelloWorld!

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


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

0 条回应