Kubernetes的service mesh – 第二部分:以DaemonSet方式运行linkerd

导言

在我们发表的上一篇关于linkerd的文章中提到过,linkerd是使用DaemonSet而非sidecar来安装的。在本文中,我们将解释我们为什么(怎么样)这么做。
注意:这是关于Linkerd、Kubernetes和service mesh的系列文章其中一篇,其余部分包括:

  1. Top-line service metrics
  2. Pods are great, until they’re not(本文)
  3. Encrypting all the things
  4. Continuous deployment via traffic shifting
  5. Dogfood environments, ingress, and edge routing
  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以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。

本文译者:平凡,欢迎关注微信公众号