在本系列文章的上一篇中,细心的读者注意到,linkerd是使用DaemonSet而不是作为挎斗(SideCar)进程安装的(关于SideCar的概念及翻译引用自Azure技术社区的文档)。在这篇文章中,我们将解释为什么这样做以及怎样做。
作为服务网格,linkerd被设计为与应用程序代码一起运行,管理和监视服务间通信(具体包括执行服务发现,重试,负载均衡和协议升级)。
乍一看,Kubernetes的挎斗方式部署非常适合。毕竟,Kubernetes的一个标志性特点就是就是它的pod模型。作为挎斗部署概念简单且失败语义明确,并且我们之前已经花费了大量时间来优化linkerd在这种场景下的性能。
然而,挎斗模型也有一个缺点:每个Pod的部署意味增加部署一个Pod对应的资源成本的开销。如果你的服务是轻量级的,并且需要运行许多实例,比如 Monzo(它基于linkerd和Kubernetes建立了一个完整的数字移动银行),这种场景下使用sidecars的成本就会相当高。
我们可以通过为每个主机而不是每个pod部署linker来降低资源成本。这样可以使资源消耗按主机为单位进行扩展,资源的开销一般要比pod慢得多。而且幸运的是Kubernetes专门为此提供了 DaemonSet 。
但是麻烦的是,对于linkerd,以主机为单元的部署要比使用DaemonSet要复杂一些。请阅读我们如何解决Kubernetes中按主机为单元部署的服务网格问题。
衡量服务网格的一个特征是其将应用通信与传输通信拆分的能力。例如,如果服务A和B使用HTTP,则服务网格可以在不影响应用的情况下将其转换为HTTPS。服务网格也可以实现连接池,准入控制或其他传输层功能,对应用同样是透明的。
为了完全做到这一点,linkerd必须在每个请求的发送端和接收端代理本地实例。例如,要实现HTTP到HTTPS的升级,linkerd必须能够启动和终止TLS。在DaemonSet模式下,通过linkerd的请求路径如下图所示:
正如你所看到的,从主机1上的Pod A到主机2上的Pod J的请求必须通过Pod A的 host-local linkerd实例,然后到达Host 2的linkerd实例,最后到Pod J。要完成这条路径,linkerd必须解决三个问题:
以下是我们解决这三个问题的技术细节。如果您只需要实现让 linkerd 与 Kubernetes DaemonSets搭配使用而不想了解细节,请参阅上一篇文章。
由于DaemonSets使用了Kubernetes的 hostPort
,我们知道linkerd是在主机IP的固定端口上运行的。为了将请求发送到运行在同一台机器上的 linkerd 进程,我们需要确定其主机的IP地址。
在Kubernetes 1.4及更高版本中,这些信息可以通过Downward API直接获得。除了hello-world.yml之外 ,这里有一个更简明的例子来展示如何将节点名称传递给应用程序:
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
环境变量来让所有的HTTP调用通过 host-local linkerd 实例, 虽然这种方法适用于大多数HTTP应用程序,但是非HTTP应用程序需要做一些不同的事情)。
在1.4及以前的Kubernetes版本中,这些信息仍然可用,但不能直接获取。我们提供了一个 简单的脚本 ,它通过调用Kubernetes API来获取主机IP; 这个脚本的输出可以被应用程序使用,或者用来构建如上例所示的 http_proxy
环境变量。
以下是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的name和namespace与为pod中的环境变量相一致。
在我们的服务网格部署中,传出请求不应直接发送到目标应用程序,而应该发送到在该应用程序的主机上运行的linkerd。为此,我们可以利用linkerd 0.8.0引入的一个强大的新特性——transformers,它可以对链接到路由的目标地址的请求进行任意的后处理。在这种情况下,我们可以使用DaemonSet transformer自动将目标地址替换为目标主机上运行的DaemonSet pod的地址。例如,下面的输出路由linkerd 配置会将所有请求发送到与目标应用程序位于同一主机上的linkerd的传入端口:
routers:
- protocol: http
label: outgoing
interpreter:
kind: default
transformers:
- kind: io.l5d.k8s.daemonset
namespace: default
port: incoming
service: l5d
...
当一个请求最终到达目标pod的linkerd实例时,它必须被正确地路由到pod本身。为此,我们使用 localnode
转换器(transformer)将路由锁定为当前主机上运行的pod。linkerd 配置示例:
routers:
- protocol: http
label: incoming
interpreter:
kind: default
transformers:
- kind: io.l5d.k8s.localnode
...
由上可知,将 linkerd 部署为Kubernetes DaemonSet是一件一举两得的好事 - 它既不影响我们使用服务网格要达到的所有目标(如透明TLS,协议升级,支持延迟感知的负载均衡等),又将让linkerd以主机为单元而不是以pod为单元部署 。
如果需要完整的示例,请参阅 上一篇博文,或下载 示例。对于有对这个配置或其他于linkerd相关的问题,欢迎到我们非常活跃的Slack提问, 或者在linkerd discourse下发表话题讨论 。