rancher 基于隧道的集群注册与集群管理机制

前言

上一篇比较侧重源码,讲了 rancher 中的 remotedialer 模块是如何实现一个双工隧道的。这里再简单的总结一下。安全性催生了隧道,双工隧道可采用四层连接实现,但处理四层连接的数据收发相对处理七层麻烦一些;而七层协议中 websocket 支持双工,故将七层协议作为一个四层实现,即可实现这条双工隧道。

这一篇,我们将分析一下基于这条隧道,rancher 是怎么做集群注册和集群管理的。

集群注册

rancher server: 集群创建

假设我们不对接 IaaS,使用 import 已有集群的方式,那么在集群注册之前,还需要在 server 上进行一次集群导入,或者更准确的说:集群创建。

在导入集群之后,实际上只是在 rancher 的管理面,创建一个 cluster 实例。该实例没有真正的 cluster 信息,只有一些 meta 信息,比如集群的 id,集群的 display name 等等。集群的实际信息,比如 cluster endpoint 等等,需要后续由 agent 在集群注册阶段填充。

当然,rancher 会做一些额外的事情,比如说

  • 有 service account 被对应创建
  • 有 cluster registration token 被相应创建,用作 agent 的认证用

我们现在可以暂时忽略这些部分,从 agent 发起的集群注册流程开始分析。

rancher agent: 集群注册

在集群创建完毕之后,用户可以生成一份部署 agent 的 yaml 文件,在目标 k8s 集群上部署 agent 之后,agent 将发起集群注册。

agent 的逻辑入口在 cmd/agent/main.go 中。忽略一些无用的、辅助性的代码,我们可以直接看 run() 这个函数。在阅读整个 agent 逻辑的过程中,我们需要知晓如下的背景知识:

  1. agent 分为 cluster agent 和 node agent,在 rancher 的架构说明中,rancher 会优先尝试连接 cluster agent,在 cluster agent 不可用时,将尝试访问 node agent。而 node agent 和 cluster agent 本质上并没有太大差别,我们只看 cluster agent 即可。
  2. 一些环境变量,以便于我们判断代码逻辑的分支。CLUSTER_CLEANUP=false, CATTLE_WRITE_CERT_ONLY=false, CATTLE_TOKEN 是 rancher 签发的一个 token,CATTLE_SERVER 是 rancher 的服务端地址。

整个流程分为下列步骤:

  • 获取 k8s 的 root CA cert、service account token、APIServer endpoint。这些信息将被序列化,封装在 agent 发起的请求的 header 中,其 key 是 X-API-Tunnel-Params
  • 获取 cluster registration token 和 rancher server。token 会被封装为 X-API-Tunnel-Token 中。
  • 持续的向 server 发起 websocket 连接,连接上之后,调用特定的回调函数 onConnect。要注意的是这条 websocket 连接被封装在 remotedialer 的 client 中,一方面 websocket 建立连接之后,调用回调函数;另一方面连接之后,client 侧就可以接收 server 端转发来的请求,转发到后端的 k8s 集群。

回调函数中启动了一些 k8s operator,其代码比较 trivial,这里不做展开。

通过上面的叙述,我们看到 cluster agent 通过携带的 token 完成服务端的隧道认证;并在集群注册中,上报了 k8s 集群的信息。

集群管理

集群管理大致包含了如下职能:

  1. 管理集群的元数据,管理元数据与集群资源的映射管理
  2. 同步集群的状态,包括健康状态、认证信息等
  3. 管理与各个集群建立的隧道
  4. 管理路由转发规则

rancher server 通过 multi-cluster-manager 组件实现集群管理与路由功能。

multi-cluster-manager

rancher 的 multi-cluster-manager 比较乱,许多代码分散在各个地方,可读性很差。这里简单做个总结。multi-cluster-manager 做了下面的事情:

  • 管理集群的 CA、endpoint、健康状态等信息,用来访问后端集群
  • 管理隧道状态
  • 管理集群路由,用作决定什么请求向哪儿转发
  • 管理转发单元,用作转发请求时,添加各种附加信息
  • 管理集群状态,用作判断集群的健康状态

mcm 中封装了一个 DeferredServer,对这个 server 我们不用过分关心,只要知道它里面注册了许多 API handler。具体的路由注册见 pkg/multiclustermanager/routes.go 这个文件,其中我们需要关注的是

  • connectHandler:处理集群注册的 API handler,它其实就是 remotedialer 的 server 端
  • proxy handler:用来将请求转发到后端集群的 API handler

这俩是 rancher 进行隧道管理的核心。

remotedialer server

connect handler 作为 remotedialer server,在上一篇有详细的介绍,这里就不再多说了。读者需要记得 remotedialer 中可以注册自定义的 authorizer,用作隧道认证。rancher 声明了自己的 authorizer,见 pkg/tunnelserver/tunnel.go 文件的 Authorize 函数。这里简单说一下流程:

  1. 获取 X-API-Tunnel-Token header,查看 cluster registration token 是否在数据库内。这一步还实现了通过 token 查找 cluster 的操作,agent 不需要携带 cluster id,只需要携带 token 即可。这个信息用作隧道认证用
  2. 获取 X-API-Tunnel-Params header,解析出 k8s 集群信息,及时更新到 cluster CRD 中。这些信息在转发请求时被用到

每一次 agent 重新连接,都会上报这些信息,rancher 就可以及时更新集群信息。

在 token 管理方面,rancher 没有用业界通用的 jwt,而是选择自己生成一串 token,属实没太大必要。但是这里暂且不说认证相关的内容了。

proxy service

其实就是 "github.com/rancher/rancher/pkg/k8sproxy" 就是包,感兴趣的同学可以简单阅读它的源码,经过一堆弯弯绕绕的逻辑,最后看 "github.com/rancher/rancher/pkg/clusterrouter/proxy" 这个包即可。

那 agent 上报的集群信息到底是怎么被使用的呢?这个会涉及到认证与鉴权的相关知识,这些背景知识留到认证部分再详细解释。这里只简单的介绍 proxy 实例的关键业务流程:

  1. 判断集群是否可达,不可达直接报错
  2. 获取集群的 API endpoint
  3. 获取 agent 上报的 service account token,持有该 Token 就可以访问 k8s 集群,之后添加相应的 authorization header
  4. 向后端集群转发

这样请求就通过 remotedialer 的隧道,转到了后端的 k8s 的 APIServer。

对认证有研究的同学应该能发现这里只有认证信息,没有身份信息。rancher 对身份信息的填充放在另一个组件中进行,源码可以看 pkg/auth/requests/filter.go,采用 k8s 的 impersonation 技术,这里不做展开,之后会详细介绍。

处理高可用

如果 agent 仅仅维持一个长连接,在高并发场景下,这条隧道是可能成为瓶颈的。rancher 在这个点上并没有解决方案

如果读者还记得的话,remotedialer 包是考虑到支持多副本的,server 端维护的 session 列表是一个数组。这意味着有多个 client 时,server 本可以采用一定的负载均衡算法。对应到实际场景,多个 agent 理应可以同时注册到 rancher,并由 rancher 进行负载均衡,避免这条 websocket 连接称为瓶颈。故其实要支持高可用可能也不难,在隧道做好高可用,即可完成上层无感知的高可用方案建设。

总结

基于这条隧道,rancher 完成了集群注册、隧道认证、集群信息上报,并在 server 端用 mcm 实现了集群管理:

  • 上行:上报集群信息与集群认证信息
  • 下行:转发请求

这一篇有许多技术细节没有说,大多是关于认证、鉴权的内容,下一篇我们单独将这些技术细节。

おすすめ

転載: juejin.im/post/7049941040555360263