微服务中的服务网关至少有以下作用:

  • 转发(服务路由与负载均衡)
  • 鉴权(身份认证与权限校验)
  • 限流(流量控制与熔断)

它们在SpringCloud和K8S中有着不同的实现方式:

SpringCloudK8S
服务网关SpringCloudGateway、ZuulIngress

服务路由是网关的核心功能,下面将基于SpringCloudGateway与Ingress分别介绍SpinrgCloud与K8S中服务路由的使用方法。

一、SpringCloud中的服务网关

(一)启动网关服务

spring-cloud-starter-gateway是实现SpringCloudGateway的核心依赖,同时,也可引入nacos-discoveryloadbalancer依赖以支持注册中心与负载均衡。如下所示:

<dependencies>
    <!-- 网关依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- 注册中心依赖 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- 负载均衡(不加此依赖,路由地址uri使用lb协议可能会503报错) -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

随后,在application.yml文件中配置路由转发规则:

server:
  port: 8888 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: nacos-provider # 路由id,全局唯一
          uri: lb://nacos-provider # 路由目的地
          predicates: # 路由断言,符合断言条件才会跳转到路由目的地
            - Path=/echo/** # 请求路径断言
          filters: # 路由过滤器,对请求和响应做处理
            - AddResponseHeader=X-Response-Special, Special # 当前路由专用过滤器
      default-filters:
        - AddResponseHeader=X-Response-Default, Default # 默认过滤器

这里的uri使用的是lb协议,其会默认从注册中心找到对应的服务,如果使用k8s的负载均衡或不使用负载均衡,uri可以使用http协议,如:http://localhost:8081。

这里假定本地已经启动了nacos注册中心服务和nacos-provider服务,nacos-provider的代码详见github。有关predicatesfilters,详见后文

随后,定义启动类:

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

上述代码详见github

启动并访问:

curl "http://localhost:8888/echo?name=zhangsan"

可以看到正常打印响应结果:

echo:zhangsan

至此,该网关已经实现了对nacos-provider服务的路由转发,用户无需知道(也不应该知道)nacos-provider的访问地址,直接可以通关网关进行访问。

(二)路由断言与过滤

上述配置文件中,Path请求路径断言由PathRoutePredicateFactory处理,AddResponseHeaderAddResponseHeaderGatewayFilterFactory处理,断言工厂和路由过滤器工厂的详细信息可参见官网Route Predicate FactoriesGatewayFilter Factories

除了上述配置文件中定义的路由专用过滤器(routes.filters)和默认过滤器(default-filters)外,SpringCloudGateway还支持一种可定制性更强的全局过滤器(GlobalFilter)。一个示例如下:

@Component
@Order(-1)
public class AddResponseHeaderGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("X-Response-Global", "Global");
        return chain.filter(exchange);
    }
}

该过滤器与上述配置文件中的AddResponseHeader过滤器实现一样的功能:增强响应头。

再次重启应用,并发送请求:

curl -i "http://localhost:8888/echo?name=zhangsan" # curl -i参数打印响应头

响应结果如下:

HTTP/1.1 200 OK
X-Response-Default: Default
X-Response-Special: Special
X-Response-Global: Global
Content-Type: text/plain;charset=UTF-8
Content-Length: 13
Date: Sat, 10 Jun 2023 06:37:58 GMT

echo:zhangsan

可以看到,三个过滤器都生效了。上述过滤器存在以下执行顺序:

  • 以Order值排序。全局过滤器的Order由用户指定(使用@Order注解或实现Ordered接口)。默认过滤器和路由专用过滤器的Order则从1开始计数,按照定义的顺序累加,同时,默认过滤器与专用过滤器的Order值独立累加。
  • 若Order值一致,则以默认过滤器 > 路由专用过滤器 > 全局过滤器排序。

二、K8S中的服务网关

(一)启动一个应用

  1. 启动一个deployment
kubectl create deployment web --image=nginx
  1. 暴露一个service
kubectl expose deployment web --type=NodePort --port=8080 --target-port=80
kubectl get service web
  1. 通过minikube调用服务
minikube service web --url # 输出类似于 http://192.168.49.2:31959
curl http://192.168.49.2:31959

192.168.49.2是minikube的集群ip,使用ifconfig命令查看网络适配器情况,可以看到一个minikube的专用IP网段192.168.49.1。

正常将会显示nginx的首页html文件内容。这是通过minikube自带的service指令实现访问minikube内部service的方法,后续将会基于Ingress提供更通用的外部访问服务的方法。

(二)启用Ingress控制器

在使用Ingress之前,需要先启用Ingress控制器。Minikube支持在本地环境快速启用Ingress控制器,下面将以Minikube为例介绍Ingress的配置过程。

生产环境Ingress控制器可以参考Ingress-Nginx Controller Installation Guide

  1. 启动minikube
minikube start --image-mirror-country='cn'
  1. 启用Ingress控制器
minikube addons enable ingress

国内环境可能拉取不了镜像,可以参考附录minikube在国内环境启用addons方法

  1. 查看Ingress控制器状态
kubectl get pods -n ingress-nginx

结果如下:

NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-x98cn        0/1     Completed   0          90m
ingress-nginx-admission-patch-mscpc         0/1     Completed   0          90m
ingress-nginx-controller-7b7b49467c-4kt7m   1/1     Running     0          90m

(三)创建Ingress

  1. 定义Ingress配置文件

example-ingress.yaml:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 8080

该配置文件定义了hello-world.info域名到web这个service的转发规则。

  1. 启动Ingress
kubectl create -f example-ingress.yaml
  1. 查看Ingress状态
kubectl get ingress example-ingress

结果如下:

NAME              CLASS   HOSTS              ADDRESS        PORTS   AGE
example-ingress   nginx   hello-world.info   192.168.49.2   80      44s
  1. 通过Ingress访问服务
curl --resolve "hello-world.info:80:$( minikube ip )" -i http://hello-world.info

正常将会显示nginx的首页html文件内容。其中minikube ip是minikube的集群IP(192.168.49.2),curl --resolve定义了域名解析(也可在本机host中配置)。

至此,该Ingress实现了以hello-world.info域名为入口,到pod内部的流量转发。

后续可按需基于Ingress规则配置更多的服务路由转发策略。

三、总结

SpringCloud通过SpringCloudGateway等组件实现网关。SpringCloudGateway是用Java开发,可定制性更强。其包括:服务路由、断言、过滤、断路器支持、服务发现支持、限流等功能。

K8S通过Ingress实现网关,除了前面描述的服务路由功能外,Ingress也支持其它网关功能(如:鉴权、限流等等)。Ingress服务需要依赖于特定的Ingress控制器,比如NGINX Ingress Controller。NGINX Ingress网关的功能更多地依赖于NGINX本身的特性,有关NGINX Ingress的更多特性,也可参见其官网

与SpringCloud与K8S中的服务配置中类似:SpringCloud偏向于开发,而K8S则更偏向于运维。有关技术选型,需视情况而定。

附录

minikube在国内环境启用addons方法

  1. 在本地拉取docker镜像
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.7.0
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794

版本号可按需修改。

  1. 将本地镜像加载到minikube集群中
minikube image load registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.7.0
minikube image load registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794

上一步的docker pull不是必须的。如果上一步执行了docker pull,这里的load命令就会从本地Docker镜像中拷贝,反之则从远程仓库拉取。

  1. 启用addons,并修改镜像地址
minikube addons enable ingress --images="KubeWebhookCertgenCreate=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794,KubeWebhookCertgenPatch=registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230312-helm-chart-4.5.2-28-g66a760794,IngressController=google_containers/nginx-ingress-controller:v1.7.0" --registries="IngressController=registry.cn-hangzhou.aliyuncs.com"

参考文档

  1. 《深入理解SpringCloud与实战 方剑 著》
  2. SpringCloud官方网站
  3. 黑马程序员SpringCloud微服务技术栈教程
  4. Kubernetes Ingress 介绍
  5. 在 Minikube 环境中使用 NGINX Ingress 控制器配置 Ingress
  6. Ingress-Nginx Controller Installation Guide
  7. NGINX Ingress Controller
  8. Minikube安装ingress插件的有效方法