导言
在我们发表的上一篇关于linkerd的文章中提到过,linkerd是使用DaemonSet而非sidecar来安装的。在本文中,我们将解释我们为什么(怎么样)这么做。
注意:这是关于Linkerd、Kubernetes和service mesh的系列文章其中一篇,其余部分包括:
- Top-line service metrics
- Pods are great, until they’re not(本文)
- Encrypting all the things
- Continuous deployment via traffic shifting
- Dogfood environments, ingress, and edge routing
- 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以DaemonSet方式运行
作为service mesh,linkerd设计为与应用程序代码一起运行。它管理和监控service的内部通信,包括服务发现、重试、负载均衡与协议升级。
初次听闻,都会觉得这非常适合在Kubernetes中以sidecar的方式部署。毕竟,Kubernetes的定义特征之一就是它的pod模型。作为sidecar部署理论上简单,有清晰的失败语义,我们花了大量时间用于针对该用例的linkerd优化。
然而,sidecar模型有其缺陷:部署一个pod就要用掉部署一个pod的资源。如果你的service较轻量并且跑了许多的应用实例,这样使用sidecar来部署就会代价极高。
我们可以通过每一个host而非每一个pod部署一个linkerd来减少资源的开销。它对每个主机进行扩展而消耗资源,这通常是比pod数量要显著更慢的增长的指标。并且很幸运,Kubernetes为此目的提供了DaemonSets。
但美中不足的是,对于linkerd来说,按每个host部署比仅仅使用DaemonSets要复杂一些。我们如何解决service mesh这个问题?请继续阅读下文。
Kubernetes的service mesh
service mesh的定义特征之一是它将应用通信与传输通信分离开的能力。例如,如果service A与B使用HTTP协议通信,service mesh也许会通过电缆将之转换为HTTPS,而且应用程序并不知晓。service mesh也可以做连接池、权限控制,或其它的传输层特性,并且是以对应用程序透明的方式。
为了完美的做到这些,linkerd必须作为本地实例的代理处于请求的发送端与接收端。例如HTTP升级为HTTPS,linkerd一定要在传输层协议的开始与结束。在DaemonSet的世界,一个通过linkerd的请求路线看起来如下图所示:
正如你所见,一个从Host 1中Pod A发起的目标为Host 2中Pod B的请求必须通过Pod A的本地linkerd实例,然后到达Host 2的linkerd实例,最后到达Pod J。这个路径引入了linkerd必须解决的三个问题:
- 应用如何定位它的本地linkerd
- linkerd如何将传出请求路由到目标linkerd
- linkerd如何将传入请求路由到目标应用
接下来就讲述我们如何解决这三个问题的技术细节。
应用程序如何定位它的本地linkerd?
因为DaemonSet使用Kubernetes的host端口,我们就知道linkerd运行于host IP的一个固定端口。因此,为了发送一个请求给运行于同一宿主机的linkerd进程,我们需要确定这个主机的IP地址。
在Kubernetes 1.4及其以后的版本,此信息可通过底层API直接获取到。以下是来自hello-world.yaml的摘要,显示了如何将节点名称传递到应用程序中:
env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: http_proxy value: $(NODE_NAME):4140 args: - "-addr=:7777" - "-text=Hello" - "-target=world"
(请注意,此例设置http_proxy环境变量以引导通过本地linkerd实例的所有HTTP调用。然而此方法仅适用于大多数的HTTP应用,非HTTP应用需要做一些不同的工作)
Kubernetes 1.4之前的版本这些信息仍旧可用,但是方式要间接一些。
以下是hello-world-legacy.yml的一部分,显示如何将主机IP传递到应用程序中。
env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: NS valueFrom: fieldRef: fieldPath: metadata.namespace command: - "/bin/sh" - "-c" - "http_proxy=`hostIP.sh`:4140 helloworld -addr=:7777 -text=Hello -target=world"
请注意,hostIP.sh脚本需要将pod名和命名空间以环境变量的方式注入pod中。
linkerd如何将传出请求路由到目标linkerd?
在我们的service mesh部署中,传出请求不应当直接发送给目标应用,而应当发送给运行于应用程序所在主机的linkerd。为了做到这点,我们可以利用linkerd 0.8.0引入的被称为transformer的新特性,它可以对linker路由到的目标地址进行任意的后期处理。在这种情况下,我们可以使用DaemonSet transformer自动的让运行于目标主机的DaemonSet的地址取代目标地址。例如,这个linkerd传出路由配置发送所有的请求到运行于相同主机的作为目标应用的linkerd的输入端口。
routers: - protocol: http label: outgoing interpreter: kind: default transformers: - kind: io.l5d.k8s.daemonset namespace: default port: incoming service: l5d ...
linkerd如何将输入请求路由到目标应用?
当一个请求最终到达目标pod的linkerd实例,它一定得正确的路由到该pod。为此,我们使用localnode transformer将路由限制在运行于本地主机的pod。linkerd配置示例如下:
routers: - protocol: http label: incoming interpreter: kind: default transformers: - kind: io.l5d.k8s.localnode ...
结束语
将linkerd作为Kubernetes DaemonSet部署使得两全其美——它让我们完成service mesh的全部目标(如透明的TLS、协议升级、延迟感知负载均衡等等),同时按每个host扩缩linkerd实例而非每个pod。