K8S如何基于Istio重新实现微服务

K8S如何基于Istio重新实现微服务

  • 认识 Istio
    • 前言
    • Istio 的理念
    • Istio 的架构
      • 数据平面
      • 控制平面
      • 服务与 Istio 的关系
  • Istio 实践
    • 环境准备
    • 安装 Helm
    • 安装Istio
  • 使用 Istio 代理运行应用
    • 情感分析应用的架构
    • 使用 Istio 代理运行应用
      • Sidecar 注入
      • Ingress 网关
      • 网关资源
      • VirtualService 资源
  • 跨服务跟踪和流量管理
    • 内置的特性
      • Kiali——可观察性
      • Grafana——指标可视化
      • Jaeger——跟踪
    • 流量管理
      • A/B 测试——Destination Rule 实践
      • DestinationRules
      • Shadowing———Virtual Services 服务实践
        • 通过 Destination Rule 声明子集
        • 金丝雀部署
        • 超时和重试
  • 认证和授权
    • 断路器和舱壁模式
    • Istio 中的认证和授权
      • 使用 Auth0 进行认证
      • 使用 Auth0 认证请求
        • 更新前端
      • 使用 Auth0 进行授权
        • 安装和配置 Auth0 授权
          • 1. 创建Group
          • 2. 添加 Group Claim 到 Access Token
          • 3. 在 Istio 中配置授权
          • 4. 配置常规的用户访问
          • 5. 配置 Moderator 用户访问
  • 结论
  • 参考链接

认识 Istio

前言

从单体架构迁移到基于微服务的应用时,会带来很多的挑战:

流量管理(Traffic management):超时、重试、负载均衡;

安全性(Security):终端用户的认证和授权;

可观察性(Observability):跟踪、监控和日志。

微服务单项目示例.png

这些问题都可以在应用层解决,但不符合微服务的初衷,针对每个服务,所有的过程和努力必须都要再来一遍,每实现这些功能都会对公司的资源带来一定的压力。而且对我们来说,添加一个服务是一项巨大的任务。

Istio 的理念

Istio 是一个开源项目,由来自 Google、IBM 和 Lyft 团队协作开发,它为一组特定的问题提供了解决方案。

在没有 Istio 的时候,某个服务会直接发起对另一个服务的访问,如果出现失败,这个服务需要作出的处理包括重试、超时以及熔断等等。

Kubernetes 中的网络流量

Istio 提供了一个非常巧妙的方案,它能够从服务中完全分离出来,通过只拦截所有的网络通信来发挥作用。能够实现如下功能:

容错:当请求失败和重试的时候,使用它能够理解的响应状态码;

金丝雀发布:只将特定百分比的请求转发到服务的新版本上;

监控和指标:统计服务响应所耗费的时间;

跟踪和可观察性:它在每个请求上添加了一个特殊的头部信息,并在集群中跟踪它们;

安全性:抽取 JWT Token 并对用户进行认证和授权。

Istio 的架构

Istio 会拦截所有的网络流量,并且会在每个 pod 上以 sidecar 的形式注入一个智能代理,从而能够应用一组特定的规则。让所有特性生效的代理是由**数据平面(Data Plane)组成的,而数据平面是由控制平面(Control Plane)**动态配置的。

数据平面

注入的代理能够让 Istio 很容易地满足我们的需求

Envoy 是如何实现重试和断路器的

简单总结一下:

Envoy(数据平面) 发送请求给服务 B 的第一个实例,不过调用失败了;

Envoy Sidecar 会进行重试;(1)

失败的请求返回发起调用的代理;

开启断路器并在随后的请求中调用下一个服务。(2)

这意味着我们不需要使用额外的重试库,也不需要使用各种各样的语言实现断路器和服务发现功能。这些特性都是由 Istio 所提供的,我们不需要任何的代码修改。

它是一个适用于所有场景的解决方案吗?通常来讲,适用所有场景意味着难以非常好地适用任何场景

控制平面

控制平面由三个组件组成:Pilot、Mixer 和 Citadel,它们组合配置 Envoy 以便于路由流量、执行策略和收集遥测数据。

控制平面与数据平面的关系

Envoy(也就是数据平面)会由 Istio 规定的Kubernetes自定义资源定义(Kubernetes Custom Resource Definition)来进行配置,这些定义是专门为了实现该目的的。这就是说,对你而言,它只是具有熟悉语法的 Kubernetes 资源而已。在创建之后,控制平面会获取到它,并将其用到 Envoy 上。

服务与 Istio 的关系

服务(Service)是 Kubernetes 中基础的网络抽象,而 Istio 作为服务网格,增强了服务的流量管理、安全、监控等能力,使得微服务架构更加可靠和可管理。

我们可以选取一个可运行的集群,在安装 Istio 组件之后,集群中的服务依然能够继续运行,我们可以按照相同的方式将组件移除掉,所有的事情都能正常运行,只不多我们失去了 Istio 所提供的功能而已。

Istio 实践

环境准备

我们需要搭建一个至少 4 vCPU 和 8 GB RAM 的集群。本文已经在如下的 Kubernetes 实现中测试通过:

Google Container Engine (GKE)

Azure Kubernetes Service (AKS)

Digital Ocean (20天免费的Kubernetes链接)

创建完集群并使用 Kubernetes 命令行配置完访问权限后,我们就可以使用 Helm 包管理器安装 Istio 了。

安装 Helm

按照官方文档的描述在你的机器上安装 Helm 客户端。在下一节中,我们将会使用它来生成 Istio 安装模板。

安装Istio

从最新的发布版本下载 Istio 的资源,将其抽取到一个目录中,我们会将其称为[istio-resources]。

为了更容易地识别 Istio 资源,我们在 Kubernetes 集群中创建命名空间istio-system:


$ kubectl create namespace istio-system

切换至[istio-resources]并执行如下命令完成安装:

$ helm template install/kubernetes/helm/istio \ --set global.mtls.enabled=false \--set tracing.enabled=true \--set kiali.enabled=true \--set grafana.enabled=true \ --namespace istio-system > istio.yaml

上述的命令会将 Istio 的核心组件打印到istio.yaml文件中。我们使用如下的参数对模板进行自定义:

global.mtls.enabled:将这个值设置为 false,保证我们这篇介绍文章能够只关注重点;

tracing.enabled:启用 jaeger 的请求跟踪功能;

kiali.enabled:在我们的集群中安装 Kiali,实现服务和流量的可视化;

grafana.enabled:安装 Grafana,实现收集指标的可视化。

注意:从 Istio 1.5 版本开始,global.mtls.enabled 被弃用,Istio 引入了更为灵活和细粒度的 mTLS 控制机制,使用 PeerAuthentication 和 DestinationRule 来配置不同命名空间或服务的 mTLS 策略。通过这些新机制,用户可以更好地控制某些服务或命名空间的加密策略,而不是全局统一开启或关闭。

创建istio资源

kubectl apply -f istio.yaml

执行如下的命令,然后等待istio- system命名空间中的所有 pod 均处于 Running 或 Completed 状态:


kubectl get pods -n istio-system

使用 Istio 代理运行应用

情感分析应用的架构

继续使用Kubernetes入门文章中的样例,它较为复杂,足以通过实践展示 Istio 的特性

这个应用由四个微服务组成:

SA-Frontend 服务:前端的 Reactjs 应用;

SA-WebApp 服务:处理情感分析的请求;

SA-Logic 服务:执行情感分析;

SA-Feedback 服务:接收用户关于分析精确性的反馈。

情感分析的微服务

除了服务之外,我们还看到了 Ingress Controller,在 Kubernetes 中,它会将传入的请求路由至对应的服务,Istio 采用了类似的概念,名为 Ingress Gateway,在本文后续的内容中,我们将会对其进行介绍。

使用 Istio 代理运行应用

如果要要跟着本文一起练习的话,读者可以 clone 该 GitHub 仓库 istio-mastery,其中包含了适用于 Kubernetes 和 Istio 的应用程序与 manifest。

Sidecar 注入

注入可以自动或手动完成。如果要启用自动化的 Sidecar 注入,我们需要使用istio-injection=enabled来标记命名空间,这可以通过执行如下的命令来实现:

$ kubectl label namespace default istio-injection=enablednamespace/default labeled

现在,默认命名空间中部署的所有 pod 都将会被注入 sidecar。切换至[istio-mastery]仓库的根目录,并执行如下的命令:


$ kubectl apply -f resource-manifests/kube
persistentvolumeclaim/sqlite-pvc created 
deployment.extensions/sa-feedback created 
service/sa-feedback created 
deployment.extensions/sa-frontend created
service/sa-frontend created 
deployment.extensions/sa-logic created 
service/sa-logic created 
deployment.extensions/sa-web-app created 
service/sa-web-app created

执行如下的命令并检查 Ready 列,我们会看到“2/2”,这表明第二个容器已经注入进来了。


$ kubectl get pods
NAME                         READY STATUS   RESTARTS   AGE 
sa-feedback-55f5dc4d9c-c9wfv 2/2    Running  0         12m 
sa-frontend-558f8986-hhkj9   2/2    Running  0         12m
sa-logic-568498cb4d-s9ngt   2/2     Running  0         12m 
sa-web-app-599cf47c7c-s7cvd 2/2     Running  0         12m

服务已经启动并运行了,每个容器都包含了 sidecar 代理。

某个 Pod 中的 Envoy 代理

但是,要访问服务,我们需要允许传入的流量进入集群,也就是所谓的 Ingress 流量

Ingress 网关

允许流量进入集群的一个最佳实践就是使用 Istio 的 Ingress 网关,它处于集群的边缘并且靠近传入的流量,它能够实现 Istio 的多项特性,比如路由、安全和监控。

在 Istio 安装的时候,Ingress 网关组件以及将其暴露给外部的服务已经安装到了集群中,可以通过如下的命令获取它的外部 IP:


$ kubectl get svc -n istio-system -l istio=ingressgateway
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)
istio-ingressgateway   LoadBalancer   10.0.132.127   13.93.30.120   80:31380/TCP,443[...]

在本文后续的内容中,我们将会通过该 IP(将其称为EXTERNAL-IP)访问应用程序,为了便利起见,我们通过下面的命令将其保存到变量中:

$ EXTERNAL_IP=$(kubectl get svc -n istio-system \-l app=istio-ingressgateway \-o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')

如果你在浏览器中访问该 IP 的话,将会看到服务不可用的错误,默认情况下,在我们定义网关之前,Istio 会阻止所有传入的流量

网关资源

网关是一种 Kubernetes 自定义资源定义(Kubernetes Custom Resource Definition),它是我们在集群中安装 Istio 时所定义的,借助它,我们能够指定允许传入流量的端口、协议和主机

在我们的场景中,我们想要为所有主机开放 80 端口。我们可以通过如下的定义来进行配置:


apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:name: http-gateway
spec:selector:istio: ingressgatewayservers:- port:number: 80name: httpprotocol: HTTPhosts: - "*"

除了istio: ingressgateway选择器之外,所有配置项的含义均不言自明。通过使用这个选择器,我们可以指定哪个 Ingress 网关使用该配置,在我们的场景中,也就是在 Istio 安装时的默认 Ingress 网关控制器。

通过执行如下的命令,应用该配置:


$ kubectl apply -f resource-manifests/istio/http-gateway.yaml
gateway.networking.istio.io "http-gateway" created

网关允许我们访问 80 端口,但是它还不知道要将请求路由至何处,而这一功能是通过 Virtual Service 来实现的

VirtualService 资源

VirtualService 能够指导 Ingress 网关如何路由允许进入集群的请求

对于我们的应用来说,通过 http-gateway 的请求必须要路由至** sa-frontend、sa-web-app 和 sa-feedback 服务**。

通过 VirtualService 配置路由

现在,我们拆分一下应该路由至 SA-Frontend 的请求:

  1. 精确的路径“/”应该路由至 SA-Frontend 以便于获取 Index.html;

  2. 带有“/static/*”前缀的路径应该路由至 SA-Frontend,以便于获取前端所需的静态文件,比如级联样式表和 JavaScript 文件;

  3. 匹配正则表达式“^.*.(ico|png|jpg)$”的路径应该路由至 SA-Frontend,因为它代表的是页面展现所需的图片。

这样,我们就会得到如下的配置:

kind: VirtualService 
metadata:name: sa-external-services 
spec:hosts:- "*"gateways:- http-gateway      # 1http:- match:- uri:exact: /- uri:exact: /callback- uri:prefix: /static- uri:regex: '^.*\.(ico|png|jpg)$' route:- destination:host: sa-frontend    # 2port:number: 80

这里的重点在于:

VirtualService 将会应用于通过 http-gateway 的请求;

destination 定义了请求要路由至哪个服务。

注意:上面的配置在sa-virtualservice-external.yaml文件中,它还包含了路由至 SA-WebApp 和 SA-Feedback 的配置,但是简洁期间,我们将其省略了。

通过执行如下的命令,应用 VirtualService:


$ kubectl apply -f resource-manifests/istio/sa-virtualservice-external.yaml
virtualservice.networking.istio.io "sa-external-services" created

注意:当我们应用该资源时(其实所有的 Istio 资源均如此),Kubernetes API Server 会创建一个新的事件,该事件会被 Istio 的控制平面接收到,然后会将新的配置应用到每个 pod 的 envoy 代理上。Ingress Gateway 控制器是 Control Plane 配置的另外一个 Envoy,如图所示。

配置 Istio-IngressGateway 来路由请求

现在,我们可以通过http://{{EXTERNAL-IP}}/访问情感分析应用了。如果你遇到 Not Found 状态的话,请不要担心,有时候配置生效并更新 envoy 缓存会耗费一点时间。

跨服务跟踪和流量管理

内置的特性

通过拦截所有的网络通信,Istio 能够得到一些指标和数据,这些指标和数据能够用来实现整个应用的可观察性。Kiali是一个开源的项目,它能够利用这些数据回答这样的问题:微服务是如何成为 Istio 服务网格的一部分的,它们是如何连接在一起的?

Kiali——可观察性

在安装 Istio 到我们的集群中之前,我们创建了一个用于 kiali 的 secret(还有另外一个用于 grafana),其中,我们将 user 和 password 都设置为 admin。要访问 Kiali 的 Admin UI,需要执行如下的命令:


# 端口转发:在本地通过指定的端口访问运行在集群内的 Pod 上的服务 kiali
$ kubectl port-forward \$(kubectl get pod -n istio-system -l app=kiali -o jsonpath='{.items[0].metadata.name}') \-n istio-system 20001

打开http://localhost:20001/并使用“admin”(不含引号)作为 user 和 password 进行登录。这里很有多有用的特性,比如检查 Istio 组件的配置、拦截网络请求和响应所收集的信息构建的可视化服务,比如“谁调用了谁?”,“哪个版本的服务出现了故障?”等等。在进入下一步章节学习使用 Grafana 可视化指标之前,请花一些时间检验 Kiali 的功能。

Kiali——服务可观察性

Grafana——指标可视化

Istio 收集到的指标被放到了 Prometheus 中,并使用 Grafana 进行可视化。要访问 Grafana 的 Admin UI,请执行如下的命令并打开http://localhost:3000。


$ kubectl -n istio-system port-forward \$(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000

在左上角点击 Home 菜单,并选择 Istio Service Dashboard,然后选择 sa-web-app 打头的服务,这样我们就会看到收集到的指标,如下图所示:

Grafana 指标的可视化

通过如下的命令,我们可以造一些负载出来:


$ while true; do \curl -i http://$EXTERNAL_IP/sentiment -H "Content-type: application/json" \ -d '{"sentence": "I love yogobella"}' \sleep .8; done

Prometheus 能够用来监控,Grafana 能用来对指标进行可视化,有了它们之后,我们就能知道随着时间的推移服务的性能、健康状况以及是否有升级和降级。

Jaeger——跟踪

我们需要跟踪系统的原因在于服务越多就越难以定位失败的原因。以下面图中这个简单的场景为例:

一个常见的请求失败

请求调用进来了,但是出现了失败,失败的原因是什么呢?是第一个服务失败还是第二个服务失败?两者都发生了异常,我们需要探查它们的日志。

这是微服务中一个非常普遍的问题,借助分布式的跟踪系统可以解决该问题,这种跟踪系统会在服务间传递一个唯一的 header 信息,然后这个信息会发送至分布式跟踪系统中,这样请求的跟踪信息就能汇总在一起了。

用于识别请求 span 的 TraceId

Istio 使用了 Jaeger Tracer,后者实现了 OpenTracing API,这是一个独立于供应商的框架。要访问 Jaegers UI,请执行如下的命令:


$ kubectl port-forward -n istio-system \$(kubectl get pod -n istio-system -l app=jaeger -o jsonpath='{.items[0].metadata.name}') 16686

然后通过http://localhost:16686打开 UI,选择 sa-web-app 服务,如果服务没有出现在下拉列表中的话,那么在页面进行一些活动,并点击刷新。随后点击 Find Traces 按钮,它只会展现最近的 trace,选择任意一条记录,所有的跟踪信息将会分别展示,如图所示:

Jaeger-一个请求的跟踪信息

trace 展示了如下所述的信息:

  1. 传入 istio-ingressgateway 的请求(这是第一次与某个服务产生关联,所以会生成 Trace ID),随后网关将请求转发至 sa-web-app 服务;

  2. 在 sa-web-app 服务中,请求会被 Envoy 容器捕获并生成一个 span child(这就是我们能够在 trace 中看到它的原因),然后会被转发至 sa-web-app 应用。

  3. 在这里,sentimentAnalysis 方法会处理请求。这些 trace 是由应用生成的,这意味着需要对代码进行一些修改;

  4. 在此之后,会发起针对 sa-logic 的 POST 请求。sa-web-app 需要将 Trace ID 传播下去;

……

注意:在第 4 步,我们的应用需要获取 Istio 生成的 header 信息,并将其向下传递给下一个请求,如下面的图片所示。

请求传播全路径

(A)Istio 传播 header 信息;
(B)服务传播 header 信息

Istio 承担了主要的工作,它会为传入的请求生成 header 信息、为每个 sidecar 创建新的 span 并传递 span,但是,如果我们的服务不同时传播这些 header 信息的话,那么我们就会丢失请求的完整跟踪信息。

要传播的 header 信息如下所示:

x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context

尽管这是一个很简单的任务,但依然有很多的库在简化这个过程,例如,在 sa-web-app 服务中,RestTemplate 客户端就进行了 instrument 操作,这样的话,我们只需要在依赖中添加 Jaeger 和 OpenTracing 就能传播 header 信息了。

流量管理

借助 Envoy,Istio 能够提供为集群带来很多新功能:

动态请求路由:金丝雀发布、A/B 测试
负载均衡:简单一致的 Hash 均衡
故障恢复:超时、重试、断路器
故障注入:延时、请求中断等。

在本文后面的内容中,我们将会在自己的应用中展示这些功能并在这个过程中介绍一些新的概念。我们要介绍的第一个概念就是 DestinationRules,并使用它们来实现 A/B 测试。

A/B 测试——Destination Rule 实践

A/B 测试适用于应用有两个版本的场景中(通常这些版本在视觉上有所差异),我们无法 100%确定哪个版本能够增加用户的交互,所以我们同时尝试这两个版本并收集指标。

$ kubectl apply -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions/sa-frontend-green created

绿色版本的部署 manifest 有两点差异:

  1. 镜像基于不同的标签:istio-green;

  2. pod 添加了version: green标记。

因为两个 deployment 都有app: sa-frontend标记,所以 virtual service sa-external-services在将请求路由至sa-frontend服务时,会转发到这两者上面,并且会使用 round robin 算法进行负载均衡,这样会导致下图所示的问题。

请求的文件未找到

这里有文件没有找到的错误,这是因为在应用的不同版本中它们的名称不相同。我们验证一下:

$ curl --silent http://$EXTERNAL_IP/ | tr '"' '\n' | grep main
/static/css/main.c7071b22.css 
/static/js/main.059f8e9c.js
$ curl --silent http://$EXTERNAL_IP/ | tr '"' '\n' | grep main
/static/css/main.f87cd8c9.css
/static/js/main.f7659dbb.js

也就是说,index.html在请求某个版本的静态文件时,被负载均衡到了另外一个版本的 pod 上了,这样的话,就会理解为其他文件不存在。这意味着,对我们的应用来说,如果想要正常运行的话,就要引入一种限制“为 index.html 提供服务的应用版本,必须也要为后续的请求提供服务”。

我们会使用一致性 Hash 负载均衡(Consistent Hash Loadbalancing)来达成这种效果,这个过程会将同一个客户端的请求转发至相同的后端实例中,在实现时,会使用一个预先定义的属性,如 HTTP header 信息。使用 DestionatioRules 就能够让这一切变成现实。

DestinationRules

在 VirtualService 将请求路由至正确的服务后,借助 DestinationRules 我们能够为面向该服务实例的流量指定策略,如图所示。

借助 Istio 资源进行流量管理

注意: 上图用非常易于理解的方式可视化地展现了 Istio 资源是如何影响网络流量的。但是,精确决定请求要发送至哪个实例是 Ingress Gateway 的 Envoy 完成的,它需要使用 CRD 来进行配置。

借助 Destination Rules 我们可以配置负载均衡使用一致哈希算法,从而确保相同的用户会由同一个服务实例提供响应。我们可以通过如下的配置实现这一点:


apiVersion: networking.istio.io/v1alpha3 
kind: DestinationRule
metadata:name: sa-frontend
spec:host: sa-frontendtrafficPolicy:loadBalancer:consistentHash:httpHeaderName: version   # 1

1.根据“version”头信息的内容生成 consistentHash

执行如下的命令应用该配置:

$ kubectl apply -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yamldestinationrule.networking.istio.io/sa-frontend created

执行如下命令并校验在指定 version header 信息时会得到相同的文件:


$ curl --silent -H "version: yogo" http://$EXTERNAL_IP/ | tr '"' '\n' | grep main

注意:在浏览器中,你可以使用chrome扩展为 version 设置不同的值。

DestinationRules 有很多其他负载均衡的功能,关于其细节,请参考官方文档。

在更详细地探索 VirtualService 之前,通过如下的命令移除应用的 green 版本和 DestinationRules:


$ kubectl delete -f resource-manifests/kube/ab-testing/sa-frontend-green-deployment.yaml
deployment.extensions "sa-frontend-green" deleted$ kubectl delete -f resource-manifests/istio/ab-testing/destinationrule-sa-frontend.yaml
destinationrule.networking.istio.io "sa-frontend" deleted

Shadowing———Virtual Services 服务实践

当我们想要在生产环境测试某项变更,但是不想影响终端用户的时候,可以使用影子(Shadowing)或镜像(Mirroring)技术,这样的话,我们能够将请求镜像至具有变更的第二个实例并对其进行评估。或者说更简单的场景,你的同事解决了一个最重要的缺陷,并提交了包含大量内容的 Pull Request,没人能够对其进行真正的审查。

为了测试这项特性,我们通过如下的命令创建 SA-Logic 的第二个实例(它是有缺陷的):


$ kubectl apply -f resource-manifests/kube/shadowing/sa-logic-service.buggy.yaml

执行下面的命令校验所有的版本除了app=sa-logic之外都带有对应版本的标记:


$ kubectl get pods -l app=sa-logic --show-labels
NAME                              READY   LABELS
sa-logic-568498cb4d-2sjwj         2/2    app=sa-logic,version=v1
sa-logic-568498cb4d-p4f8c         2/2    app=sa-logic,version=v1
sa-logic-buggy-76dff55847-2fl66   2/2    app=sa-logic,version=v2
sa-logic-buggy-76dff55847-kx8zz   2/2    app=sa-logic,version=v2

因为sa-logic服务的目标是带有app=sa-logic标记的 pod,所以传入的请求会在所有的实例间进行负载均衡,如图所示:

Round Robin 负载均衡

但是,我们想要将请求路由至 version v1 的实例并镜像至 version v2,如图所示:

路由至 v1 并镜像至 v2

这可以通过组合使用 VirtualService 和 DestinationRule 来实现,Destination Rule 声明子集,而 VirtualService 路由至特定的子集。

通过 Destination Rule 声明子集

通过如下的配置定义子集:

apiVersion: networking.istio.io/v1alpha3 
kind: DestinationRule
metadata:name: sa-logic
spec:host: sa-logic   # 1subsets:- name: v1   # 2labels:version: v1  # 3- name: v2labels:version: v2
  1. host 定义这个规则只适用于路由至 sa-logic 服务的请求;

  2. 当路由至子集实例时所使用子集名称;

  3. 以键值对形式定义的标记,将实例定义为子集的一部分。

通过执行如下的命令应用该配置:


$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-destinationrule.yaml
destinationrule.networking.istio.io/sa-logic created

在子集定义完之后,我们可以继续配置 VirtualService,让针对 sa-logic 的请求遵循如下的规则:

  1. 路由至名为 v1 的子集;

  2. 镜像至名为 v2 的子集。

可以通过如下的 VirtualService 实现:


apiVersion: networking.istio.io/v1alpha3 
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1mirror:host: sa-logicsubset: v2

所有的配置都很具有表述性,我们不再赘述,接下来看一下它的实际效果:


$ kubectl apply -f resource-manifests/istio/shadowing/sa-logic-subsets-shadowing-vs.yaml
virtualservice.networking.istio.io/sa-logic created

通过执行如下命令生成一些负载:

$ while true; do curl -v http://$EXTERNAL_IP/sentiment \ -H "Content-type: application/json" \-d '{"sentence": "I love yogobella"}'; \sleep .8; done

在 Grafana 检查结果,你会发现有缺陷的版本会出现 60%的请求失败,但是这些失败都不会影响终端用户,因为他们是由当前活跃的服务来提供响应的。

sa-logic 服务的成功率

在本节中,我们第一次看到如何将 VirtualService 用到服务的 envoy 上,当 sa-web-app 发起对 sa-logic 的请求时,会经过 sidecar envoy,借助我们配置的 VirtualService,将会路由至 sa-logic 的 v1 子集并镜像至 v2 子集。

金丝雀部署

金丝雀(Canary)部署指的是让少量用户使用应用新版本的一个过程,借助这一过程能够验证新版本是否存在问题,然后能够确保以更高的质量发布给更多的受众。

继续使用sa-logic带有缺陷的子集来阐述金丝雀发布。

首先,我们大胆地将 20%的用户发送至带有缺陷的版本(这就是金丝雀发布),并将 80%的用户发送至正常的服务,这可以通过如下的 VirtualService 来实现:

apiVersion: networking.istio.io/v1alpha3 
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1weight: 80         # 1- destination:host: sa-logicsubset: v2weight: 20         # 1
  1. 权重声明了请求要转发到目的地或目的地子集的百分比。

通过下面的命令,更新上述的sa-logic virtual service:

$ kubectl apply -f resource-manifests/istio/canary/sa-logic-subsets-canary-vs.yaml
virtualservice.networking.istio.io/sa-logic configured

我们马上可以看到有些请求失败了:


$ while true; do \curl -i http://$EXTERNAL_IP/sentiment -H "Content-type: application/json" \-d '{"sentence": "I love yogobella"}' \--silent -w "Time: %{time_total}s \t Status: %{http_code}\n" -o /dev/null; \ sleep .1; done
Time: 0.153075s      Status: 200
Time: 0.137581s      Status: 200
Time: 0.139345s      Status: 200
Time: 30.291806s    Status: 500

VirtualServices 实现了金丝雀发布,借助这种方法,我们将潜在的损失降低到了用户群的 20%。非常好!现在,当我们对代码没有确切把握的时候,就可以使用 Shadowing 技术和金丝雀发布

超时和重试

代码不仅仅会有缺陷,在“分布式计算的8个谬误”中,排名第一的就是“网络是可靠的”。网络实际上是不可靠的,这也是我们需要超时和重试的原因。

为了便于阐述,我们将会继续使用有缺陷版本的sa-logic,其中随机的失败模拟了网络的不可靠性。

带有缺陷的服务版本会有三分之一的概率在生成响应时耗费过长的时间,三分之一的概率遇到服务器内部错误,其余的请求均能正常完成。

为了降低这些缺陷的影响并给用户带来更好的用户体验,我们会采取如下的措施:

  1. 如果服务耗时超过了 8 秒钟,将会超时;

  2. 对于失败的请求进行重试。

可以通过如下的资源定义来实现:

apiVersion: networking.istio.io/v1alpha3 
kind: VirtualService
metadata:name: sa-logic
spec:hosts:- sa-logichttp:- route:- destination:host: sa-logicsubset: v1weight: 50- destination:host: sa-logicsubset: v2weight: 50timeout: 8s  # 请求有 8 秒钟的超时;retries:attempts: 3  # 它会尝试 3 次;perTryTimeout: 3s  # 如果耗时超过 3 秒钟就将本次尝试标记为失败。

这是一种优化:用户的等待不会超过 8 秒钟,如果失败的话,我们将会重试三次,这样的话增加了获取成功响应的概率。

通过如下命令应用更新后的配置:


$ kubectl apply -f resource-manifests/istio/retries/sa-logic-retries-timeouts-vs.yaml
virtualservice.networking.istio.io/sa-logic configured

查阅 Grafana 中的图表,看成功率是否有所提升

在使用超时和重试功能之后的成功率

在进入下一节之前,通过如下命令移除掉sa-logic-buggy和 VirtualService:

$ kubectl delete deployment sa-logic-buggy
deployment.extensions "sa-logic-buggy" deleted
$ kubectl delete virtualservice sa-logic
virtualservice.networking.istio.io "sa-logic" deleted

认证和授权

断路器和舱壁模式

在微服务架构中,有两个重要的模式,它们能够让服务实现自愈的效果。

  • 断路器模式(Circuit Breake)

能够阻止请求发送到不健康的服务实例上,这样的话,服务能够进行恢复,同时,客户端的请求将会转发到服务的健康实例上(增加了成功率)。

  • 舱壁模式(Bulkhead Pattern)

会隔离失败,避免整个系统宕机,举例来说,服务 B 处于有问题的状态,另外一个服务(服务 B 的客户端)往服务 B 发送请求将会导致客户端耗尽线程池,从而不能为其他请求提供服务(即便其他的请求与服务 B 毫无关系也是如此)。

这里我不再展示这些模式的实现了,因为我迫不及待想要分享认证和授权的功能,这些模式的实现你可以参考官方文档。

Istio 中的认证和授权

Istio 将认证和授权从我们的服务中剥离了出去,并将其委托给了 Envoy 代理,这意味着当请求抵达我们的服务时,它们已经经过了认证和授权,我们只需要编写提供业务价值的代码就可以了。

使用 Auth0 进行认证

我们会使用 Auth0 作为身份识别和访问管理的服务器,它有一个试用方案,对我来说非常便利,我非常喜欢它!这也就是说,相同的理念可以用于任意的OpenID Connect实现,比如 KeyCloak、IdentityServer 等等。

首先,使用预设账号导航至Auth0 Portal,在 Applications > Default App 下创建一个租户,并记住 Domain,如下所示:

Auth0 管理门户中的 Default App

更新resource-manifests/istio/security/auth-policy.yaml文件,以便于使用该 Domain:


apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:name: auth-policy
spec:targets:- name: sa-web-app- name: sa-feedbackorigins:- jwt:issuer: "https://{YOUR_DOMAIN}/"jwksUri: "https://{YOUR_DOMAIN}/.well-known/jwks.json"principalBinding: USE_ORIGIN

借助该资源,Pilot 将会配置 envoy 在将请求转发至 sa-web-app 和 sa-feedback 服务之前进行认证。同时,它不会将该规则应用于服务sa-frontend的 envoy,这样的话,前端能够不用进行认证。为了应用该策略,我们需要执行如下命令:


$ kubectl apply -f resource-manifests/istio/security/auth-policy.yaml
policy.authentication.istio.io "auth-policy" created

回到页面并发送一个请求,我们将会看到 401 Unauthorized,现在,我们让来自前端的用户使用 Auth0 进行认证。

使用 Auth0 认证请求

要认证终端用户的请求,我们需要在 Auth0 中创建一个 API,它代表了经过认证的服务,比如评论、详情和评级。为了创建这样的 API,我们需要导航至 Auth0 Portal > APIs > Create API,如下图所示:

创建 API

这里重要的信息是 Identifier,在随后的脚本中,我们会以如下的形式进行使用:

  • Audience: {YOUR_AUDIENCE}

其余所需的细节在 Auth0 Portal 的 Applications 下进行配置,选择自动创建的 Test Application,其名称与 API 相同。

这里需要注意:

Domain: {YOUR_DOMAIN}

Client Id: {YOUR_CLIENT_ID}

在 Test Application 中向下滚动至 Allowed Callback URLs 文本域,在这里我们可以指定在认证完成后要转发到哪个 URL,在我们的场景中,应该是:


http://{EXTERNAL_IP}/callback

在 Allowed Logout URLs 中添加如下的 URL:


http://{EXTERNAL_IP}/logout

接下来,我们更新前端。

更新前端

切换至仓库的 auth0 分支。在这个分支中,前端包含了将用户转发至 Auth0 进行认证的代码变更,并在发送至其他服务的请求中包含了 JWT Token,如下所示:


analyzeSentence() {fetch('/sentiment', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${auth.getAccessToken()}` // Access Token },body: JSON.stringify({ sentence: this.textField.getValue() }) }).then(response => response.json()) .then(data => this.setState(data));
}

为了更新前端以便于使用租户的详细信息,我们导航至sa-frontend/src/services/Auth.js文件并替换如下的值,也就是在前文中我提醒注意的地方:


const Config = {clientID: '{YOUR_CLIENT_ID}',domain:'{YOUR_DOMAIN}',audience: '{YOUR_AUDIENCE}',ingressIP: '{EXTERNAL_IP}' // Used to redirect after authentication
}

应用已经就绪了,在如下的命令指定 docker user id,然后构建和部署变更:


$ docker build -f sa-frontend/Dockerfile \-t {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0 sa-frontend$ docker push {DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0
$ kubectl set image deployment/sa-frontend \ sa-frontend={DOCKER_USER_ID}/sentiment-analysis-frontend:istio-auth0

我们可以尝试运行一下应用。访问的时候,你会被导航至 Auth0,在这里必要要登录(或注册),再次之后会重新转发会页面,这时候就可以发送认证后的请求了。如果你使用之前的 curl 命令的话,将会遇到 401 状态码,表明请求没有经过授权。

接下来,我们更进一步,对请求进行授权。

使用 Auth0 进行授权

认证能够让我们知道用户是谁,但是我们需要授权信息才能得知他们能够访问哪些内容。Istio 也提供了这样的工具。

作为样例,我们会创建两组用户(如图所示):

Users:只能访问 SA-WebApp 和 SA-Frontend 服务;

Moderators:能够访问所有的三个服务。

授权的概念

为了创建用户组,我们需要使用 Auth0 Authorization 扩展,随后借助 Istio,我们为它们提供不同级别的访问权限。

安装和配置 Auth0 授权

在 Auth0 Portal 中,切换至 Extensions,并安装“Auth0 Authorization”扩展。安装之后,切换至 Authorization Extension 并点击右上角你的租户并选择“Configuration”选项进行配置。启用组并点击“Publish rule”按钮。

在 Token Contents 中激活组

1. 创建Group

在 Authorization Extension 中,导航至 Groups 并创建 Moderators 组。我们会将所有已认证过的用户视为普通的 User,这样的话,就没有必要额外再建立一个组了。

选择 Moderators 组并点击 Add Members,然后添加你的主账号。请将一些用户不放到任何的组中,以便于检验禁止他们访问(你可以通过 Auth0 Portal > Users > Create User 手动注册新用户)。

2. 添加 Group Claim 到 Access Token

用户现在已经添加到组中了,但是这些信息还没有反映到 Access Token 中。为了确保 OpenID Connect 的一致性,同时又能返回组,我们需要在 token 中添加自定义的命名空间声明。这可以通过 Auth0 规则来实现。

要在 Auth0 Portal 中创建规则,我们需要导航至 Rules,点击“Create Rule”,并从模板中选择一个 empty rule。

Rules 截屏

粘贴如下的代码并将规则命名为“Add Group Claim”。


function (user, context, callback) { context.accessToken['https://sa.io/group'] = user.groups[0]; return callback(null, user, context);
}

注意: 这个规则会获取 Authorization Extension 中定义的第一个用户组,并将其添加到 access token 中,作为自定义命名空间的声明。

回到 Rules 页面,确保我们按照如下顺序有了两个角色:

  1. auth0-authorization-extension

  2. Add Group Claim

这里的顺序是非常重要的,因为组的字段会由auth0-authorization-extension异步获取,然后由第二个规则将其添加为命名空间声明,这将会形成如下的 access token:

{
"https://sa.io/group": "Moderators",
"iss": "https://bookinfo.eu.auth0.com/", 
"sub": "google-oauth2|196405271625531691872"
// [shortened for brevity]
}

现在,我们必须配置 Envoy 代理,从而能够在返回的 access token 中抽取https://sa.io/group声明的组。这是下一部分的主题,我们马上来看一下。

3. 在 Istio 中配置授权

为了实现授权,我们为 Istio 启用 RBAC。为了实现这一点,要将如下的配置用到 Mesh 中:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: RbacConfig
metadata:name: default
spec:mode: 'ON_WITH_INCLUSION'  # 仅为“Inclusion”字段中的服务和命名空间启用 RBAC;inclusion:services:               # 包含如下服务。- "sa-frontend.default.svc.cluster.local"- "sa-web-app.default.svc.cluster.local"- "sa-feedback.default.svc.cluster.local"

执行如下命令,应用该配置:


$ kubectl apply -f resource-manifests\istio\security\enable-rbac.yaml
rbacconfig.rbac.istio.io "default" created

现在,所有的服务都需要基于角色的访问控制(Role-Based Access Control),换句话说,访问所有的服务都会遭到拒绝并且响应中会包含“RBAC: access denied”。我们下一节的主题就是启用对授权用户的访问。

4. 配置常规的用户访问

所有的用户应该都能访问 SA-Frontend 和 SA-WebApp 服务,这是通过如下的 Istio 资源实现的:

ServiceRole:指定用户具有的权限;

ServiceRoleBinding:指定 ServiceRole 应用到哪些用户上。

对于常规用户,我们允许访问指定的服务:


apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole
metadata:name: regular-user namespace: default
spec: rules:- services:- "sa-frontend.default.svc.cluster.local"- "sa-web-app.default.svc.cluster.local" paths: ["*"]methods: ["*"]

然后,借助 regular-user-binding,我们将该 ServiceRole 应用到页面的所有访问者上:

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding
metadata:name: regular-user-bindingnamespace: default 
spec:subjects:- user: "*"roleRef:kind: ServiceRolename: "regular-user"

这里所说的所有用户,是不是意味着未认证的用户也能访问 SA WebApp 呢?并非如此,该策略依然会检查 JWT token 的合法性。

通过如下命令,应用该配置:

$ kubectl apply -f resource-manifests/istio/security/user-role.yaml
servicerole.rbac.istio.io/regular-user created
servicerolebinding.rbac.istio.io/regular-user-binding created
5. 配置 Moderator 用户访问

对于 Moderator,我们想要启用对所有服务的访问:


apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRole
metadata:name: mod-usernamespace: default 
spec:rules:- services: ["*"]paths: ["*"]methods: ["*"]

但是,我们只想要绑定 Access Token 中https://sa.io/group声明的值为 Moderators 的用户。

apiVersion: "rbac.istio.io/v1alpha1" 
kind: ServiceRoleBinding
metadata:name: mod-user-bindingnamespace: default 
spec:subjects:- properties:request.auth.claims[https://sa.io/group]: "Moderators"roleRef:kind: ServiceRolename: "mod-user"

要应用该配置,需要执行下述命令:


$ kubectl apply -f resource-manifests/istio/security/mod-role.yaml
servicerole.rbac.istio.io/mod-user unchanged servicerolebinding.rbac.istio.io/mod-user-binding unchanged

Envoy 中会有缓存,所以授权规则可能需要几分钟之后才能生效,在此之后,我们就可以校验 Users 和 Moderators 会有不同级别的访问权限。

坦白讲,你见过像这样毫不费力就能构建可扩展的认证和授权吗,还有比这更简单的过程吗?反正我是没有见过!接下来,我们总结一下得到的结论。

结论

想象不到吧?我们将其称为怪兽(Beast-io),因为它确实太强大了。借助它,我们能够:

  1. 实现服务的可观察性,借助 Kiali,我们能够回答哪个服务在运行、它们的表现如何以及它们是如何关联在一起的;

  2. 借助 Prometheus 和 Grafana,能够实现指标收集和可视化,完全是开箱即用的;

  3. 使用 Jaeger(德语中猎人的意思)实现请求跟踪;

  4. 对网络流量的完整且细粒度控制,能够实现金丝雀部署、A/B 测试和影子镜像;

  5. 很容易实现重试、超时和断路器;

  6. 极大地减少了为集群引入新服务的开销;

  7. 为各种语言编写的微服务添加了认证和授权功能,服务端代码无需任何修改。

有个这个 Beastio,我们就能让团队真正地提供业务价值,并将资源聚焦在领域问题上,不用再担心服务的开销,让服务真正做到“微型化”。

参考链接

  • istio

  • 服务网格和Istio

  • Kubernetes 中快速搭建 istio

  • 通过 Istio 重新实现微服务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/58162.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动态规划 —— 路径问题-不同路径

1. 不同路径 题目链接: 62. 不同路径 - 力扣(LeetCode)https://leetcode.cn/problems/unique-paths/description/ 2. 算法原理 1. 状态表示:以莫一个位置为结尾 dp[i]表示:以[i,j]位置为结尾时&#xff0…

本地Docker部署开源WAF雷池并实现异地远程登录管理界面

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

[四轴飞行器] 航模常见术语

航模常见术语 1.X模式和模式 从结构形式上四轴飞行器可分为十字模式和X模式。十字模式如下图左所示,X模式如下图右所示。对于姿态测量和控制来说,两种结构差别不大。如果考虑安装航拍摄 像机,为了视线不被挡住,通常采用X模式。 …

mysql原理、部署mysql主从+读写分离、监控mysql主从脚本

mysql:工作原理 从库生成两个线程,一个I/O线程,一个SQL线程; i/o线程去请求主库 的binlog,并将得到的binlog日志写到relay log(中继日志) 文件中; 主库会生成一个 log dump 线程&…

2024最新保姆级Python下载安装教程

今天给大家带来Python下载安装教程! 👉大礼包🎁:python安装包/pycharm教程免费分享(安全链接,放心点击)👈 一、下载装 Python 1、进入Python官网首页,下载最新的Pytho…

outlook创建新账户时报错2603、2604的解决办法

全新的戴尔笔记本电脑,自带的Win11家庭版,安装ms office 2021也顺利完成。 但是奇怪的是,只有其中一台笔记本电脑,OUTLOOK无法添加新账户。 但是这个账号在WEB端登录正常,由于是新入职的员工,根据以往经验&…

transformer的新手疑问

Transformer模型的原理主要基于自注意力机制(Self-Attention)和编码器-解码器结构。它的设计不依赖传统的循环神经网络(RNN)或卷积神经网络(CNN),而是通过并行的方式处理序列数据,极…

雷池社区版OPEN API使用教程

OPEN API使用教程 新版本接口支持API Token鉴权 接口文档官方没有提供,有需要可以自行爬取,爬了几个,其实也很方便 使用条件 需要使用默认的 admin 用户登录才可见此功能版本需要 > 6.6.0 使用方法 1.在系统管理创建API TOKEN 2.发…

REST APIs与微服务:关键差异

在构建基于微服务的应用程序时RESYful API和微服务这两个术语经常相伴出现。然而,它们指的是截然不同的东西。 了解 RESTful API 和微服务之间差异的最简单方式是这样: 微服务:它们是构成更大规模基于微服务的应用程序的单个服务和功能&…

《金融数据安全分级指南JR/T 0197-2020》解读与想法

#1024程序员节|征文# 一、文件框架与核心思考 1、定级目标再审视 自《金融数据安全 数据安全分级指南JR/T 0197-2020》(以下简称“指南”)发布以来,金融数据安全领域已历经四年的发展与变革。该指南作为金融标准中首个以“金融数…

Docker 基础入门

Docker 基础入门 前言 在云计算和微服务架构日益盛行的今天,软件开发与部署的效率和灵活性成为了企业竞争力的关键因素之一。Docker,作为一种开源的容器化平台,凭借其轻量级、可移植性和易于管理的特性,迅速成为现代软件开发和运…

[云] 大数据分析栈(Big Data Analytics Stack)+ Apache Hadoop分布式文件系统(HDFS)+Apache Spark

任务概述 本次作业旨在帮助你理解大数据分析栈(Big Data Analytics Stack)的工作原理,并通过实际操作加深认识。你将搭建Apache Hadoop分布式文件系统(HDFS)作为底层文件系统,并将Apache Spark作为执行引擎…

Linux第二讲:Linux权限理解

Linux第二讲:Linux权限理解 1.shell命令以及运行原理2.Linux权限2.1什么是权限2.2认识人 -- 用户、普通用户、root用户,以及用户之间的切换2.3文件属性2.4文件权限知识点补充2.4.1知识点一2.4.2知识点二2.4.3知识点三2.4.4知识点四 3.角色的修改4.关于权…

Puppeteer 与浏览器版本兼容性:自动化测试的最佳实践

Puppeteer 支持的浏览器版本映射:从 v20.0.0 到 v23.6.0 自 Puppeteer v20.0.0 起,这个强大的自动化库开始支持与 Chrome 浏览器的无头模式和有头模式共享相同代码路径,为自动化测试带来了更多便利。从 v23.0.0 开始,Puppeteer 进…

可私有化部署的集装箱箱号自动识别技术,提供API 接口

启智集装箱箱号自动识别技术特点: 集装箱箱号自动识别技术为通过手机、相机等拍摄集装箱号码后进行视频处理或图像的去燥、纠偏、二值化等分析后进行字符的识别,箱号识别具有以下特点: 1)快速:自动实时识别&#xff0c…

行为设计模式 -责任链模式- JAVA

责任链设计模式 一 .简介二. 案例2.1 抽象处理者(Handler)角色2.2 具体处理者(ConcreteHandler)角色2.3 测试 三. 结论3.1 优缺点3.2 示例3.3 要点 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神…

智能优化算法-狐狸优化算法(FOX)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1.内容介绍 狐狸优化算法 (Fox Optimization Algorithm, FOX) 是一种基于群体智能的元启发式优化算法,它模拟了狐狸的捕食行为、社会互动和环境适应能力,用于解决复杂的优化问题。 FOX的工作机制主要…

MR20一体式远程IO模块:引领工业自动化的创新之选

在快速发展的工业自动化领域,高效、可靠且易于维护的IO模块成为了众多企业的首选。其中,MR20系列一体式远程IO模块凭借其卓越的性能和人性化的设计,在众多IO模块中脱颖而出,成为工业自动化领域的璀璨明星。 小巧体积,高…

【CSS3】css开篇基础(4)

1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的…

[免费]SpringBoot+Vue智慧校园(校园管理)系统[论文+源码+SQL脚本]

大家好,我是java1234_小锋老师,看到一个不错的SpringBootVue智慧校园(校园管理)系统,分享下哈。 项目视频演示 【免费】SpringBootVue智慧校园(校园管理)系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 随着信息技术的迅猛发展&#xff0c…