在Kubernetes上使用Istio进行微服务流量管理

Microservices Traffic Management Using Istio on Kubernetes

原文作者:Piotr Mińkowski

原文地址:https://dzone.com/articles/microservices-traffic-management-using-istio-on-ku

译者微博:@从流域到海域

译者博客:blog.csdn.net/solo95

在Kubernetes上使用Istio进行微服务流量管理

我已经在之前的一篇文章(5步在Kubernetes上搭建使用Istio的Service Mesh)中介绍了在Kubernetes上部署的两个微服务之间的路由配置的简单示例。如果您对Istio的基本信息以及通过Minikube在Kubernetes上进行的部署感兴趣,可以参考本文。今天,我们将基于上一篇关于Istio的文章中使用的相同示例应用程序,创建一些更高级的流量管理规则。

示例应用程序的源代码可在GitHub的sample-istio-serviceshttps://github.com/piomin/sample-istio-services.git中找到。有两个示例应用程序callme-servicecaller-service,并部署在两个不同的版本1.02.01.0版本在分支v1(https://github.com/piomin/sample-istio-services/tree/v1)中可以找到,而2.0版本在分支v2中(https://github.com/piomin/sample-istio-services/tree/v2)中可以找到。在不同版本中使用这些示例应用程序时,我将根据传入的请求中设置的HTTP标头向您展示不同的流量管理策略。

我们可能会强制callme-service将所有请求路由到特定版本的callme-service,方法是将标头x-version设置为v1v2。我们也可以不在请求中设置此标头,这会导致所有现有版本的服务之间的流量分割。如果请求到达caller-servicev1版本,那么流量在callme-service的两个实例之间按50-50进行分割(即五五开)。如果请求被caller-serviceV2版本的实例接收,那么75%的流量被转发到callme-servicev2版本,而只有25%到达v1版本。上面描述的情形已在下图中进行了说明。

在我们开始这个例子之前,我应该说一些关于Istio流量管理的话。如果你已经阅读过我之前关于Istio的文章,那么你可能会知道每个规则都被分配到了一个目的地。这些规则控制着服务网格内请求的路由过程。关于它们有一个非常重要的事情,特别是对于上图中所示的例子而言,可以将多个规则应用于相同的目的地。每条规则的优先级取决于规则的precedence域。有一个与该字段的值相关的原则:该整数字段的值越高,则对应规则的优先级越高。正如您可以猜到的那样,如果有多个具有相同优先级值的规则,则规则评估的顺序是未定义的。除了目的地之外,我们还可以定义请求的来源,以便仅将规则限制到特定的调用者。如果一个正在调用的服务有多个部署,我们甚至可以通过设置其来源的标签字段将其过滤掉(一部分)。当然,我们也可以指定HTTP请求的属性,例如URl,scheme或用于匹配请求和定义规则的header(即报文头,下面不再注释)。

现在我们来看看具有最高优先级的规则。它的名字是callme-service-v1(1)。它适用于callme-service(2),并且与其他规则(3)相比具有最高的优先级。它仅适用于由caller-service(4)发送的请求,这种请求包含带有v1(5)值的 HTTP header x-version。这条路线的规则只适用于callme-service(6)v1版本 。

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v1 # (1)
spec:
  destination:
    name: callme-service # (2)
  precedence: 4 # (3)
  match:
    source:
      name: caller-service # (4)
    request:
      headers:
        x-version:
          exact: "v1" # (5)
  route:
  - labels:
      version: v1 # (6)

这是第一个图的片段,按此路线规则处理。

下一个规则callme-service-v2(1)的优先级较低(2)。但是,它与第一条规则不冲突,因为它仅适用于包含值为v2(3)的x-version header的请求。它把所有请求转发到的callme-service(4)v2版本。

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v2 # (1)
spec:
  destination:
    name: callme-service
  precedence: 3 # (2)
  match:
    source:
      name: caller-service
    request:
      headers:
        x-version:
          exact: "v2" # (3)
  route:
  - labels:
      version: v2 # (4)

和上面一样,这里是第一个图的片段,它按这条路线规则处理。

下面代码片段中可见规则callme-service-v1-default(1)的优先级(2)低于前面描述的两个规则。在实践中,这意味着只有在前两条规则中定义的条件未得到满足时才能够执行。如果您没有在HTTP请求中传递headerx-version,或者它的值不同于v1v2,则会出现这种情况。下面可见的规则仅适用于标有v1version(3)的服务实例。最后,到callme-service的流量在两种版本的服务(4)之间以50比50的比例进行负载均衡。

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v1-default # (1)
spec:
  destination:
    name: callme-service
  precedence: 2 # (2)
  match:
    source:
      name: caller-service
      labels:
        version: v1 # (3)
  route: # (4)
  - labels:
      version: v1
    weight: 50
  - labels:
      version: v2
    weight: 50

这是第一个图的片段,按此路线规则处理。

最后一条规则与前面描述的callme-service-v1-default非常相似。它的名字是callme-service-v2-default(1) ,它仅适用于caller-servicev2版本(3) 。它具有最低的优先级(2),并且按75-25的比例在callme-service两个版本之间按版本v2的偏好(4)进行分割。

apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: callme-service-v2-default # (1)
spec:
  destination:
    name: callme-service
  precedence: 1 # (2)
  match:
    source:
      name: caller-service
      labels:
        version: v2 # (3)
  route: # (4)
  - labels:
      version: v1
    weight: 25
  - labels:
      version: v2
    weight: 75

与上面一样,我也提供了说明此规则行为的图表。

所有规则都可以放在一个文件中。在这种情况下,它们应该使用---行分隔开。该文件在callme-service模块的代码仓库中作为multi-rule.yaml文件可以找到。要在Kubernetes上部署所有定义的规则,只需执行以下命令。

$ kubectl apply -f multi-rule.yaml

部署成功后,您可以通过运行该命令来查看可用规则的列表istioctl get routerule

在我们开始任何测试之前,我们显然需要在Kubernetes上部署示例应用程序。这些应用程序非常简单,非常类似于我之前关于Istio的文章中用于测试的应用程序。以下可见的控制器实现了GET /callme/ping方法,该方法打印出从pom.xml中获取的应用程序的版本以及请求中接收到的x-versionHTTP标头的值。

@RestController
@RequestMapping("/callme")
public class CallmeController {
 private static final Logger LOGGER = LoggerFactory.getLogger(CallmeController.class);
 @Autowired
 BuildProperties buildProperties;
 @GetMapping("/ping")
 public String ping(@RequestHeader(name = "x-version", required = false) String version) {
  LOGGER.info("Ping: name={}, version={}, header={}", buildProperties.getName(), buildProperties.getVersion(), version);
  return buildProperties.getName() + ":" + buildProperties.getVersion() + " with version " + version;
 }
}

下面是实现GET /caller/ping方法的控制器类。它打印出从pom.xml获取的caller-service版本并调用由callme-service暴露出的GET callme/ping方法。发送到下游服务时,它需要在请求中包含x-version header。

@RestController
@RequestMapping("/caller")
public class CallerController {
 private static final Logger LOGGER = LoggerFactory.getLogger(CallerController.class);
 @Autowired
 BuildProperties buildProperties;
 @Autowired
 RestTemplate restTemplate;
 @GetMapping("/ping")
 public String ping(@RequestHeader(name = "x-version", required = false) String version) {
  LOGGER.info("Ping: name={}, version={}, header={}", buildProperties.getName(), buildProperties.getVersion(), version);
  HttpHeaders headers = new HttpHeaders();
  if (version != null)
   headers.set("x-version", version); < span id = "mce_SELREST_start"
  style = "overflow:hidden;line-height:0;" > < /span>
  HttpEntity entity = new HttpEntity(headers);
  ResponseEntity response = restTemplate.exchange("http://callme-service:8091/callme/ping", HttpMethod.GET, entity, String.class);
  return buildProperties.getName() + ":" + buildProperties.getVersion() + ". Calling... " + response.getBody() + " with header " + version;
 }
}

现在,我们可以继续在Kubernetes上构建和部署应用程序。以下是后面的步骤。

1.构建应用程序

首先,通过执行命令mvn clean install切换到分支v1并构建整个sample-istio-services项目。

2.构建Docker镜像

Dockerfiles放置在每个应用程序的根目录中。通过执行以下命令来构建Docker镜像。

$ docker build -t piomin/callme-service:1.0 .
$ docker build -t piomin/caller-service:1.0 .

或者,你可以省略这个步骤,因为图像piomin/callme-servicepiomin/caller-service可以在Docker Hub帐户中找到。

3.将Istio组件注入到Kubernetes部署文件中

Kubernetes YAML部署文件在每个应用程序的根目录中都作为deployment.yaml文件可用。以下命令的结果应保存为单独的文件,例如deployment-with-istio.yaml:。

$ istioctl kube-inject -f deployment.yaml

4.在Kubernetes上部署

最后,您可以执行追梦的kubectl命令,以便使用我们的示例应用程序部署Docker容器。

$ kubectl apply -f deployment-with-istio.yaml

然后切换到分支v2,并针对示例应用程序的2.0版本重复上述步骤。最终的部署结果在下图中可以看到。

在Kubernetes上运行Istio时,一个非常有用的功能是与Zipkin,Grafana或Prometheus等工具的即插即用集成。Istio会自动发送一些由Prometheus收集的指标,例如计算指标istio_request_count的请求的总数。这些插件的YAML部署文件在目录${ISTIO_HOME}/install/kubernetes/addons中可以找到。在使用kubectl命令安装Prometheus之前,我建议将服务类型从默认的ClusterIP通过添加line type: NodePort更改为Nodeport

apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/scrape: 'true'
  labels:
    name: prometheus
  name: prometheus
  namespace: istio-system
spec:
  type: NodePort
  selector:
    app: prometheus
  ports:
  - name: prometheus
    protocol: TCP
    port: 9090

然后我们应该运行kubectl apply -f prometheus.yamlistio-system命令以便在Kubernetes上部署Prometheus。部署在命名空间中可用。要检查服务的外部端口,请运行以下命令。对我而言,它能在http://192.168.99.100:32293/下获得。

在使用Prometheus可视化的下图中,我只过滤了发送给callme-service的请求。绿色指向由服务的v2版本接收的请求,而红色指向由服务的版本v1处理的请求。就像你可以在该图中看到的,在一开始的时候,我发送给caller-service的带有HTTP header x-version的请求设置为值v2,接着我不设置这个header的值,流量在服务的部署实例之间被分配。最后,我将它设置为v1。我定义了一个表达式rate(istio_request_count{callme-service.default.svc.cluster.local}[1m]),它返回每秒callme-service接收到的请求的速率。

测试

在向caller-service发送一些测试请求之前,我们需要在Kubernetes上获取它的地址。执行以下命令后,您会看到它在该地址下可用。http://192.168.99.100:32237/caller/ping

有四种可能的情况。在第一种情况中,当我们设置标题时,请求总是被路由到x-versionv1callme-service-v1。

如果header x-version1未包含在请求中,则流量将在callme-service-v1...

...和callme-service-v2之间分割。

最后,如果我们将header x-version设置为v2,即将请求始终路由到callme-service-v2

结论

通过使用Istio,您可以轻松地为部署在Kubernetes上的应用程序创建并应用简单并且更为先进的流量管理规则。您还可以通过Istio和Zipkin,Prometheus和Grafana之间的集成来监控一些指标和进行溯源。

本文的版权归 Steve Wang 所有,如需转载请联系作者。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大魏分享(微信公众号:david-share)

应用服务器到底是个啥?| 从开发角度看应用架构2:对Java EE应用进行打包和部署!

1322
来自专栏菩提树下的杨过

spring: 加载远程配置

通常在spring应用中,配置中的properties文件,都是打包在war包里的,部署规模较小,只有几台服务器时,这样并没有什么大问题。如果服务器多了,特别是...

2418
来自专栏Golang语言社区

用Go写的HTTP代理服务器

标题是《用Go写http代理服务器》但实际上更接近于用Go架设http代理服务器,因为代码实在太少了,就像在配置一样。 做这个http代理的起因是前段时间运维上...

3547
来自专栏Golang语言社区

golang微信支付服务端

一般来说,使用golang主要还是写服务端。所以本文主要讲golang在处理微信移动支付的服务端时的统一下单接口和支付回调接口,以及查询接口。 微信支付流程 下...

6137
来自专栏Ryan Miao

SpringCloud学习2-Springboot监控模块(actuator)

前言 学习一项新技术最大的困难是什么? 是资料。让人高兴的是找到了一本系统学习Spring Cloud的教程,《Spring Cloud微服务实战》, 接下来的...

1.3K11
来自专栏FreeBuf

打造基于Nginx的敏感信息泄露检测系统

*本文原创作者:f4ckbaidu,本文属FreeBuf原创奖励计划,未经许可禁止转载

712
来自专栏龙首琴剑庐

原 微服务Spring Cloud Eur

1284
来自专栏aoho求索

认证鉴权与API权限控制在微服务架构中的设计与实现(三)

引言: 本文系《认证鉴权与API权限控制在微服务架构中的设计与实现》系列的第三篇,本文重点讲解token以及API级别的鉴权。本文对涉及到的大部分代码进行了分析...

5184
来自专栏xingoo, 一个梦想做发明家的程序员

Head插件——学习Elasticsearch的锋刃利器!

在学习Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求,未免太过麻烦,而且也不够人性化。 此...

2196
来自专栏数据订阅

数据订阅案例

我们会通过模拟从库向主库获取对应 binlog 内容进行分析,大概架构图如下,我们会通过解析 binlog ,按照订阅通道配置的库表进行分析,所以几乎对主库没有...

1343

扫码关注云+社区