原文作者:Piotr Mińkowski
原文地址:https://dzone.com/articles/microservices-traffic-management-using-istio-on-ku
译者微博:@从流域到海域
译者博客:blog.csdn.net/solo95
我已经在之前的一篇文章(5步在Kubernetes上搭建使用Istio的Service Mesh)中介绍了在Kubernetes上部署的两个微服务之间的路由配置的简单示例。如果您对Istio的基本信息以及通过Minikube在Kubernetes上进行的部署感兴趣,可以参考本文。今天,我们将基于上一篇关于Istio的文章中使用的相同示例应用程序,创建一些更高级的流量管理规则。
示例应用程序的源代码可在GitHub的sample-istio-services(https://github.com/piomin/sample-istio-services.git中找到。有两个示例应用程序callme-service
和caller-service
,并部署在两个不同的版本1.0
和2.0
。1.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
设置为v1
或v2
。我们也可以不在请求中设置此标头,这会导致所有现有版本的服务之间的流量分割。如果请求到达caller-service
的v1
版本,那么流量在callme-service
的两个实例之间按50-50进行分割(即五五开)。如果请求被caller-service
的V2
版本的实例接收,那么75%的流量被转发到callme-service
的v2
版本,而只有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
,或者它的值不同于v1
或v2
,则会出现这种情况。下面可见的规则仅适用于标有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-service
的v2
版本(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-version
HTTP标头的值。
@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上构建和部署应用程序。以下是后面的步骤。
首先,通过执行命令mvn clean install
切换到分支v1
并构建整个sample-istio-services
项目。
Dockerfiles放置在每个应用程序的根目录中。通过执行以下命令来构建Docker镜像。
$ docker build -t piomin/callme-service:1.0 .
$ docker build -t piomin/caller-service:1.0 .
或者,你可以省略这个步骤,因为图像piomin/callme-service
和piomin/caller-service
可以在Docker Hub帐户中找到。
Kubernetes YAML部署文件在每个应用程序的根目录中都作为deployment.yaml
文件可用。以下命令的结果应保存为单独的文件,例如deployment-with-istio.yaml
:。
$ istioctl kube-inject -f deployment.yaml
最后,您可以执行追梦的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之间的集成来监控一些指标和进行溯源。