Kubernetes的service mesh – 第五部分:DogFood环境,Ingress和Edge路由

概述

在这篇文章中,我们将向您展示如何使用Linkerd实现的Service Mesh来处理Kubernetes上的入口流量,在Service Mesh中的每个实例上分发流量。我们还将通过一个完整的例子来介绍Linkerd的高级路由功能:如何将特定的请求路由到较新版本的应用实例上,比如用于内部测试的、预发布的应用版本。

这篇文章是关于使用Linkerd作为到Kubernetes网络流量的入口点。 从0.9.1开始,Linkerd直接支持Kubernetes的Ingress资源,这对于本文中的一些用例来说是一个可替代的,也是更简单的起点。

注意: 这是关于Linkerd、Kubernetes和service mesh的系列文章其中一篇,其余部分包括:

  1. Kubernetes的service mesh – 第一部分:Service的重要指标
  2. Kubernetes的service mesh – 第二部分:以DaemonSet方式运行linkerd
  3. Kubernetes的service mesh – 第三部分:将一切加密
  4. Kubernetes的service mesh – 第四部分:通过流量切换持续部署
  5. Kubernetes的service mesh – 第五部分:DogFood环境,Ingress和Edge路由(本文)
  6. Staging microservices without the tears
  7. Distributed tracing made easy
  8. Linkerd as an ingress controller
  9. gRPC for fun and profit
  10. The Service Mesh API
  11. Egress
  12. Retry budgets, deadline propagation, and failing gracefully
  13. Autoscaling by top-line metrics

在本系列的前几个部分,我们向您展示了如何使用Linkerd来捕获top-line的服务指标,在服务中透明地添加TLS以及执行蓝绿发布。这些文章展示了如Kubernetes这样的环境中如何使用Linkerd作为Service Mesh的组件,为内部服务到服务调用中添加了一层弹性和性能的保障,在本篇文章中,我们将这个模型扩展到入口路由。

虽然这篇文章以Kubernetes为例,但我们不会使用Kubernetes内置的Ingress资源对象。虽然Ingress提供了一种便捷的基于宿主机和基本路径的路由方法,但在本文撰写时,Kubernetes Ingress的功能是相当有限的。在下面的例子中,我们将远远超出Ingress提供的范围。

一、发布Linkerd Service Mesh

从以前文章中基本的Linkerd Service Mesh配置开始,我们进行两个更改来支持Ingress:我们将修改Linkerd的配置用以添加一个额外的逻辑路由器,我们在Kubernetes Service资源对象中调整VIP在Linkerd中的范围。

在Linkerd的实例提供了一个新的逻辑路由器,用于处理入口流量并将其路由到相应的服务:

yaml
routers:
- protocol: http
  label: ingress
  dtab: |
    /srv                    => /#/io.l5d.k8s/default/http ;
    /domain/world/hello/www => /srv/hello ;
    /domain/world/hello/api => /srv/api ;
    /host                  => /$/io.buoyant.http.domainToPathPfx/domain ;
    /svc                    => /host ;
  interpreter:
    kind: default
    transformers:
    - kind: io.l5d.k8s.daemonset
      namespace: default
      port: incoming
      service: l5d
  servers:
  - port: 4142
    ip: 0.0.0.0

在这个配置中,我们使用Linkerd的路由语法,dtabs将请求从域名传递到服务——在这种情况下从api.hello.world传递到api服务,从www.hello.world到hello服务。为了简单起见,我们已经为每个域添加了一个规则,但是对于更复杂的设置,也可以轻松地生成映射规则。

我们已经将这个入口路由器添加到每个Linkerd实例中 – 以真正Service Mesh的方式,我们将在这些实例中完全分配入口流量,使得应用不存在单点故障。

我们还需要修改Kubernetes Service对象,以在端口80上用非入口VIP替换出口的VIP——这将允许我们直接将出口流量发送到Linkerd的Service Mesh中,主要是用于调试的目的,因为这个流量在到达Linkerd之前不会被审查(在下一步,我们将解决这个问题)。

对Kubernetes Service修改如下:

apiVersion: v1
kind: Service
metadata:
  name: l5d
spec:
  selector:
    app: l5d
  type: LoadBalancer
  ports:
  - name: ingress
    port: 80
    targetPort: 4142
  - name: incoming
    port: 4141
  - name: admin
    port: 9990

以上所有的修改都可以通过简单运行一个命令就能生效,细节请查看:

$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/linkerd-ingress.yml

二、部署服务

对于此例中的Service,我们将使用早先发布的博客使用到的例子hello and world configs,并且我们会添加两个新的service:一个api service,其用来调用hello和world,还有world service的新版本:world-v2,其将会返回一个“earth”而非“world”——我们扩大黑客团队已经向我们保证,他们的A/B测试显示这一变化将使参与度提高10倍。

下面的命令将会把hello world services部署在default命名空间下。这些应用依赖于Kubernetes downward API提供的节点名来发现Linkerd。为了检查你的集群是否支持节点名,你可以运行该测试任务:

kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/node-name-test.yml

然后看它的日志信息:

kubectl logs node-name-test

如果你看到了IP,那就非常棒,接着使用如下命令部署hello world应用:

$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/hello-world.yml
$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/api.yml
$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/world-v2.yml

如果你看到的是“server can’t find…”这样的报错信息,请部署hello-world遗留版本,该版本依赖于hostIP而不是节点名:

$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/hello-world-legacy.yml
$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/api-legacy.yml
$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/world-v2.yml

这时我们应该能够通过入口Kubernetes VIP发送流量来测试设备,在没有使用DNS的情况下,我们将在请求中手动设置一个Host Header:

$ INGRESS_LB=$(kubectl get svc l5d -o jsonpath="{.status.loadBalancer.ingress[0].*}")
$ curl -s -H "Host: www.hello.world" $INGRESS_LB
Hello (10.0.5.7) world (10.0.4.7)!!
$ curl -s -H "Host: api.hello.world" $INGRESS_LB
{"api_result":"api (10.0.3.6) Hello (10.0.5.4) world (10.0.1.5)!!"}

或者如果集群不支持外部负载均衡器,请使用hostIP:

$ INGRESS_LB=$(kubectl get po -l app=l5d -o jsonpath="{.items[0].status.hostIP}"):$(kubectl get svc l5d -o 'jsonpath={.spec.ports[0].nodePort}')

成功了!我们已经将Linkerd设置为入口控制器,并且我们已经使用它将不同域中收到的请求路由到不同的服务。正如您所见,生产流量正冲击world-v1服务——我们还没准备要将world-v2推出。

三、Nginx层

至此ingress已经可以使用了。但还不能用于生产环境中。因为我们的ingress路由器不能将header从请求中提取出来。这就意味着我们不能接收包含header的外部请求。例如,Linkerd允许设置15d-dtab头部以按请求应用路由规则,这对于新服务的临时分级是一个有用的功能,但这可能不适合于来自外部的请求。

例如,我们可以使用15d-dtab请求头覆盖路由逻辑来使用world-v2而不是world-v1来服务外部请求:

$ curl -H "Host: www.hello.world" -H "l5d-dtab: /host/world => /srv/world-v2;" $INGRESS_LB
Hello (10.100.4.3) earth (10.100.5.5)!!

当earth作为响应,意味着这是world-v2服务的结果。

我们通过添加nginx来解决这个问题(或者其他问题如服务静态文件)。如果我们在代理请求到linkerd ingress路由之前配置nginx来提取输入的header,我们将会两全其美:有能力安全处理外部流量的ingress层并且Linkerd做动态的、基于服务的路由。

让我们添加nginx到集群中。使用this nginx conf来配置它。我们将在www.hello.world和api.hello.world虚拟服务器下使用proxy_pass命令来发送请求给Linkerd实例,并且我们将使用Headers More模块提供的more_clear_input_headers命令来提取linkerd’s context headers。

对于linkerd 0.9.0,我们可以通过在ingress路由器服务器上设置clearContext:true来清除输入的15d-*请求头。然而,nginx有许多我们可以使用到的特性,所以使用nginx连接Linkerd很有价值。

我们已经发布了一个安装了Headers More模块的Docker镜像:buoyantio/nginx:1.11.5。我们用该配置部署这个镜像:

$ kubectl apply -f https://raw.githubusercontent.com/linkerd/linkerd-examples/master/k8s-daemonset/k8s/nginx.yml

在等待外部IP出现后,我们可以通过点击nginx.conf中的简单测试端点来测试nginx是否启动:

$ INGRESS_LB=$(kubectl get svc nginx -o jsonpath="{.status.loadBalancer.ingress[0].*}")
$ curl $INGRESS_LB
200 OK

或者如果集群的外部负载均衡器不可用,使用hostIP:

$ INGRESS_LB=$(kubectl get po -l app=nginx -o jsonpath="{.items[0].status.hostIP}"):$(kubectl get svc nginx -o 'jsonpath={.spec.ports[0].nodePort}')

我们现在应用已经可以通过nginx发送流量给我们的服务了:

$ curl -s -H "Host: www.hello.world" $INGRESS_LB
Hello (10.0.5.7) world (10.0.4.7)!!
$ curl -s -H "Host: api.hello.world" $INGRESS_LB
{"api_result":"api (10.0.3.6) Hello (10.0.5.4) world (10.0.1.5)!!"}

最后,让我们尝试直接与world-v2服务通信:

$ curl -H "Host: www.hello.world" -H "l5d-dtab: /host/world => /srv/world-v2;" $INGRESS_LB
Hello (10.196.1.8) world (10.196.2.13)!!

没有earth,Nginx正在净化外部流量。

四、简述Dogfood

好了,到了重点部分:让我们使用world-v1服务来配置dogfood的环境,不过这仅针对一些流量。

为简化问题,我们只关注设置了特殊cookiespecial_employee_cookie的流量。在实践中,你可能想要比这更复杂的情况如验证它,要求它来自公司网络IP范围等。

用nginx和Linkerd安装,完成这个相当简单。我们将会使用nginx来检查该cookie是否存在,为linkerd设置一个dtab覆盖头来调整其路由。有关的nginx配置如下:

if ($cookie_special_employee_cookie ~* "dogfood") {
  set $xheader "/host/world => /srv/world-v2;";
}

proxy_set_header 'l5d-dtab' $xheader;

如果你已经按以上步骤做了,被部署的nginx已经包含了这个配置。我们可以用如下来进行测试:

$ curl -H "Host: www.hello.world" --cookie "special_employee_cookie=dogfood" $INGRESS_LB
Hello (10.196.1.8) earth (10.196.2.13)!!

系统已经工作了。当这个cookie被设置了,你将置于dogfood模式下。如果没有它,你将置于常规生产流量模式。最重要的是,dogfood模式可以包含在service stack到处出现的service的新版本,甚至很多层——只要服务代码转发Linkerd的上下文头,Linkerd服务网格将负责其余部分。

结束语

在本文中,我们看到了如何使用Linkerd给Kubernetes集群提供有力而又灵活的入口。我们已经演示了如何部署名义上的生产就绪设置,使用Linkerd进行服务路由。我们已经演示了如何使用Linkerd的一些高级路由功能来将流量服务拓扑从部署拓扑结构中分离出来,从而允许创建dogfood环境而不需要单独的集群或部署时间复杂性。

译者:容器时代 大伟

欢迎关注微信公众号