概述
在这篇文章中,我们将向您展示如何使用Linkerd实现的Service Mesh来处理Kubernetes上的入口流量,在Service Mesh中的每个实例上分发流量。我们还将通过一个完整的例子来介绍Linkerd的高级路由功能:如何将特定的请求路由到较新版本的应用实例上,比如用于内部测试的、预发布的应用版本。
这篇文章是关于使用Linkerd作为到Kubernetes网络流量的入口点。 从0.9.1开始,Linkerd直接支持Kubernetes的Ingress资源,这对于本文中的一些用例来说是一个可替代的,也是更简单的起点。
注意: 这是关于Linkerd、Kubernetes和service mesh的系列文章其中一篇,其余部分包括:
- Kubernetes的service mesh – 第一部分:Service的重要指标
- Kubernetes的service mesh – 第二部分:以DaemonSet方式运行linkerd
- Kubernetes的service mesh – 第三部分:将一切加密
- Kubernetes的service mesh – 第四部分:通过流量切换持续部署
- Kubernetes的service mesh – 第五部分:DogFood环境,Ingress和Edge路由(本文)
- Staging microservices without the tears
- Distributed tracing made easy
- Linkerd as an ingress controller
- gRPC for fun and profit
- The Service Mesh API
- Egress
- Retry budgets, deadline propagation, and failing gracefully
- 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: httplabel: ingressdtab: |/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: defaulttransformers:- kind: io.l5d.k8s.daemonsetnamespace: defaultport: incomingservice: l5dservers:- port: 4142ip: 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: l5dtype: LoadBalancerports:- name: ingressport: 80targetPort: 4142- name: incomingport: 4141- name: adminport: 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环境而不需要单独的集群或部署时间复杂性。