Raft 的成员变更与 Etcd 实现
本文配合 Etcd v3.4 的实现来分析 Raft 协议中有关成员变更的内容。
集群的成员变化即是集群配置的变化。Raft 允许在一个集群不重启的前提下,自动化地对一个集群的配置进行变更。
单成员变更
安全性
对一个集群配置的变更而言,首先要考虑的就是安全性,即不破坏集群的大多数(majorities)。若在集群上每次只增加或删除一个 server,无论原始集群的个数是奇数还是偶数,一个旧集群的大多数和一个新集群的大多数必然会产生一个重叠,如下图所示。这个重叠就避免了一个集群被分离为两个大多数集群,因为它同时拥有向两端大多数的投票权,若新配置在集群中没有被复制到大多数,它的一票还是会决定集群继续使用旧配置;若新配置在集群中被复制到了大多数,它的一票就会将集群的配置切换为新配置。这种切换可以是直接切换,因为是安全的。
MetalLB 工作原理解析
本文代码基于 MetalLB v0.13.9 展开。
MetalLB 是一个基于标准路由协议的,用于裸机(bare-metal)k8s 集群的负载均衡器。这里裸机是指,直接部署的 k8s 集群并不能使用 LoadBalancer 类型的 Service,因为它没有提供一种负载均衡器的实现,只有在一些云服务 IaaS 平台(例如 AWS、GCP 等)上才能使用。
MetalLB 从两个方面实现了这么一个负载均衡器:地址分配(Address Allocation)和外部广播(External Announcement)。
地址分配
类似于各种云厂商的实现,对每个向负载均衡器的请求分配 IP 地址。MetalLB 则负责在裸机集群中分配 IP 地址,这个 IP 地址是从预先配置的地址池(AddressPool)中获取的;同样当 Service 被删除后,MetalLB 也负责回收该地址。
核心方法
reconcileService
此方法是 service-controller 的调协方法,位于 MetalLB 的 controller 组件中,负责监听所有类型的 Service,然后对它们的 IP 地址进行管理(分配或回收)。
// internal/k8s/controllers/service_controller.go
func (r *ServiceReconciler) reconcileService(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// ...
var service *v1.Service
// 根据 Endpoint 提供的 NamespacedName 对象寻找对应的 Service 对象
service, err := r.serviceFor(ctx, req.NamespacedName)
if err != nil { \
return ctrl.Result{}, err \
} -->-- r.Get(ctx, name, &res)
// 若 MetalLB 的配置文件中指定了 LoadBalancerClass,则比对它和 Service 的是否一致
// 只有一致或无指定配置时才可通过,默认情况下,配置文件不指定该字段
if filterByLoadBalancerClass(service, r.LoadBalancerClass) {
return ctrl.Result{}, nil
}
// 根据 Service 获取其所代理的 Endpoints 或 EndpointSlice
epSlices, err := epsOrSlicesForServices(ctx, r, req.NamespacedName, r.Endpoints)
if err != nil {
return ctrl.Result{}, err
}
// 此时根据 Service 是否为空,可以判断出此次调谐是对 Service 的删除还是更新
// 对 Service 进行处理,包括 IP 地址的分配和回收
res := r.Handler(r.Logger, req.NamespacedName.String(), service, epSlices)
switch res {
case SyncStateError:
return ctrl.Result{}, retryError
case SyncStateReprocessAll:
// 重新进行全量的调谐
r.forceReload()
return ctrl.Result{}, nil
case SyncStateErrorNoRetry:
return ctrl.Result{}, nil
}
return ctrl.Result{}, nil
}
Envoy 中的 Internal Listener
Envoy 支持用户态的 socket,而且在 Enovy 中,用于接受用户态连接的 listener 被称为 internal listener。internal listener 一般用于接受来自 Envoy 内部的连接,例如从 upstream cluster 接受连接请求并建立 TCP 流。使用 internal listener 时,必须将它的 name 作为一个 upstream cluster 的 endpoint 地址。
The nftables in Linux Kernel
本文代码基于 Linux Kernel v4.10 展开。
nftables(Netfilter Tables,下文简称 nft)是 linux 内核于 v3.13 引入的,意在取代传统的 xtables 工具(比如 iptables、arptables、ebtables 和 ipset 等)。nft 与它们相比,在便捷性、功能和性能上有着巨大的提升。
数据结构
开始分析 nft 之前,先自顶向下熟悉一下 nft 涉及的几种基本数据结构。
nft_table
nft 中规则集 ruleset 表示所有规则的集合,table 作为 ruleset 的顶层容器,能存储 chains、sets、stateful objects 等对象,其结构如下:
// include/net/netfilter/nf_tables.h
struct nft_table {
struct list_head list; // 内部遍历使用的参数,下同
struct list_head chains; // chains in table
struct list_head sets; // sets in table
struct list_head objects; // objects in table
u64 hgenerator; //
u32 use; // 计数,有多少 chain 引用了该 table
u16 flags:14, // nft_table_flags 的二进制位掩码
\
\
--->--- enum nft_table_flags {
NFT_TABLE_F_DORMANT = 0x1, // table 不可用
};
genmask:2; //
char name[NFT_TABLE_MAXNAMELEN]; // table name
};
kube-proxy 实现原理与源码解析
本文代码基于 Kubernetes v1.26 展开。
kube-proxy,以下简称 kp,是负责实现 Service VIP 机制(ExternalName
类型除外)的组件。
代理模式
kp 的代理模式可由配置文件来指定:kp 的配置通过 ConfigMap 实现,ConfigMap 的配置参数合法性并不会被 kp 全部验证,比如宿主机是否禁止使用了 iptables 命令。
kubectl describe -n kube-system configmaps kube-proxy
iptables
该模式下,kp 监听 k8s 控制面增加和移除 Service、EndpointSlice 对象的事件。
- kp 只能看到通过 readiness 探针测试的后端 pod,从而避免将流量发送到失败的 pod 上
对于一个大型集群来说,kp 对于 iptables 规则的更新成为了 Service 性能的瓶颈,对此 kp 存在两个性能优化参数:
iptables:
minSyncPeriod: 1s # kp 与内核同步 iptables 的时机,默认在 svc 等资源更新后 1s 再同步
syncPeriod: 30s # kp 与内核同步 iptables 的周期,默认 30s 同步一次,无论是否有资源更新
Multus CNI 工作原理解析
本文代码基于 Multus CNI v3.7 展开。
Multus CNI 专门负责为 Pod 增加新的网络接口,以接入不同类型的网络,比如 macvlan 等等。而且它的定位是个 Meta CNI,即可代理调用其他 CNI 集群网络插件,因此可以与 calico、flannel 等 CNI 共存。
13 post articles, 2 pages.