# operator-numaadj
**Repository Path**: yukaii/operator-numaadj
## Basic Information
- **Project Name**: operator-numaadj
- **Description**: 基于Pod亲和性,通过NRI插件和Operator用于调整Pod资源配置
- **Primary Language**: Go
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2024-10-31
- **Last Updated**: 2024-10-31
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 🎡 介绍
本产品是一款基于Pod亲缘关系来动态调整节点资源的NRI插件。由于多处理器的服务器使用Numa架构,不同的Numa节点资源分配对Pod的性能是一个重要的影响因素。实验表明,Pod之间存在很多的网络互相行为,可以将他们视作一组具有亲缘关系的Pod。如果将这一组具有亲缘关系的Pod分配在统一Numa节点上,可以有效的降低他们的跨Numa访问次数,从而提升系统的吞吐率。
## ⛄️ 能力介绍
识别Pod之间的亲缘关系,动态调整Pod的资源分配,使节点的资源分配更加合理,从而提高系统吞吐率。
## ⚙️ 基本概念
+ **Pod**: kubernets中可以创建和管理的最小单元,也是NRI插件重新分配资源的目标。
+ **container**:容器,镜像运行的实例,Pod中可以包含多个容器,一个容器对应一个进程。容器是用来感知Pod之间亲缘关系的重要参数来源。
+ **亲缘关系**:指容器或Pod之间在一定程度上具有网络互访或内存互访的行为。
+ **net_rship**: 一个二进制程序,采用eBPF技术用来获取网络亲缘关系。
+ **crictl**:是kubernetes中的一个容器接口工具,可以用于分析容器对应的进程Id,在NRI插件中需要借助该命令行工具来解析出容器对应的进程Id。
## ✏️ 实现原理

NRI(Node Resource Interface),是节点资源接口,支持将特定逻辑插入兼容OCI的运行时,从而在容器生命周期时间点执行OCI规定范围之外的操作。NRI插件有两种运行方式,一种是NRI插件作为一个独立的进程,通过NRI Socket与CRI运行时通信;第二种是把NRI插件部署成为一个Daemonset,更方便在kubernetes上管理。因此,这里的主要原理是通过分析Pod之间的亲缘关系,然后通过NRI插件调整节点资源,将具有亲缘关系的Pod调整到合适的Numa节点上。
## 🌲 环境准备
#### 环境要求
+ 安装好kubernetes
+ containerd version 大于 1.7
+ 操作系统:openeuler 22.03-SP4,内核版本:kernel-5.10.0-230.0.0.129.oe2203sp4.aarch64
+ 安装好crictl工具
#### 环境搭建
操作系统:启动参数增加:kpti=off men_sampling_on numa_icon=enable
## 🧾 任务场景
#### 任务场景
工作节点上至少需要两个Pod,如果这两个Pod具有网络亲缘关系,NRI插件将他们调整到一个numa节点上。测试用例提供了2个Pod,分别为wrk客户端和Nginx服务器,客户端给服务器发送网络请求,并输出压测结果。可以在wrk的deployment文件中传入wrk压测参数,并通过查看Pod日志来查看吞吐率。
#### 开发流程
1. 在openeluer 22.03-SP4上安装Go和C的开发环境。
2. 定义CRD,用于描述节点资源和分配。
3. 编写NRI插件和Operator用于调整Pod的节点资源。
4. 编写grpc服务器,用于感知Pod之间的网络亲缘关系,并和NRI插件中的grpc客户端交互,发送亲缘关系拓扑结构。
## 🚀 调测验证
##### 启用NRI插件之前
0. 克隆代码仓到本地
1. 部署测试用例:
kubectl apply -f nginx-deployment.yaml
kubectl apply -f wrk-deployment.yaml
kubectl apply -f nginx-svc.yaml
2. 使用kubectl 命令查看wrk对应Pod的日志,日志的输出为压测的值
##### 启用NRI插件之后
1. 编译构建grpc服务器:make build_grpc_server (可选,代码仓已提供编译后的二进制文件)
2. 启动ebpf程序 ./net_rship -l -I 6000 -i 3000
3. 安装NRI插件:make plugin_install
4. 再次使用kubectl log命令查看wrk对应Pod日志,查看输出压测的值
### NUMA亲和NRI插件代码详细设计
#### 1、资源调整模块
利用NRI接口,对Pod资源(cpuset、memset)进行调整,该模块提供资源调整接口,供其他模块调用,只负责资源调整,不涉及策略
使用NRI更新容器接口
```go
nri.stub.UpdateContainers
```
#### 2、资源调整策略模块
资源调整策略
- 负载均衡调整
当使能k8s的绑核功能时,k8s默认使用装箱策略进行Pod部署,即依次从NUMA-0、NUMA-1...分配Pod的资源进行绑核,对于鲲鹏众核场景,若节点资源分配非100%,此时往往会出现靠后的NUMA节点资源空闲较多,比如920芯片,NUMA-0/1/2资源占用100%而NUMA-3未跑满,出现业务性能不平衡,抖动较大等问题。该策略也可用户POC跑分场景,提升同一类型的Pod业务性能
所以该插件支持负载均衡策略,启动该策略时默认将各个Pod平均部署在各个NUMA中
节点负载情况获取,读取节点上的/var/lib/kubelet/cpu_manager_state文件
```json
{"policyName":"static","defaultCpuSet":"0,23-31,34-127","entries":{"52e08695-1b3d-43c4-bc3d-8020a27a876f":{"specjbb-container2":"1-20"},"88ee6d1e-f436-45ca-a8ae-3db9d2309358":{"specjb-container":"32-33"},"b6d7cfab-8f71-4e52-ace3-6c62b4f3cbef":{"specjbb-container":"21-22"}},"checksum":736622555}
```
以此文件为例,表示节点上有三个绑核的Pod,其中specjbb-container的cpu绑核到了32-33号u上
当Pod未声明资源时,只为了测试场景,可以将各个Pod直接平均分配到各个NUMA中。例子:有4个Pod,16核4个NUMA,则Pod分别绑核cpuset:0-3 cpuset:4-7 cpuset:8-11 cpuset:12-15
- 资源亲和性调整
对于在Pod间有大流量传输类或频繁交互的业务,如果将Pod的资源迁移到一个NUMA内或NUMA DISTANCE更近的NUMA节点上,可以大大提升业务吞吐量,摸底测试nginx wrk压测,客户端与服务端在同一个NUMA上时有50%以上的单位时间内处理请求量提升
统计时需要考虑到Pod内所有的进程,获取容器内所有进程方法:因为Pod内所有进程都的父进程都是Pause容器进程,所有根据启动进程获取其父亲进程pid-x,pid-x的子进程列表就是POD内所有的进程
通过autonuma实时获取线程间的网络亲和性,亲和性转换:线程间->进程间->Pod
进而将有亲和性的Pod的资源通过模块1调整到一个NUMA-NODE上
autonuma接口
```c
/dev/relationship_ctrl
```
线程到Pod的转化算法
```go
func calculateGid(podinfos *[]PodInfo) {
for idx := 0; idx < len(*podinfos); idx++ {
podinfo := &(*podinfos)[idx]
podinfo.gids = make([]int, 0)
for _, tid := range podinfo.tids {
gid := C.get_gid(C.int(tid))
podinfo.gids = append(podinfo.gids, int(gid))
}
}
}
```
在调整Pod的资源时,整体的原则是对当前业务的影响尽可能少,按此原则分为以下几种实际场景实现
申请资源:Pod-a 8u Pod-b 8u
假设当前Pod的部署情况:
Pod-a、Pod-b有亲和关系时,此时Pod-a部署在NUMA-0 cpuset(0-7), Pod-b部署在NUMA-3 cpuset(0-7),NUMA-distance-0-1=16,NUMA-distance-0-3=32
| 场景 | 实施策略 |
| ----------- | ----------- |
| NUMA-0空闲CPU 16u | 将Pod-b迁移到NUMA-0 cpuset(8-15) |
| NUMA-3空闲CPU 16u | 将Pod-a迁移到NUMA-3 cpuset(8-15) |
| NUMA-1空闲CPU 7u、NUMA-2空闲 16u | 将Pod-a 迁移到NUMA-2 cpuset(0-7) cpuset(8-15) |
| NUMA-1空闲CPU 7u、NUMA-2空闲 10u | 将Pod-b迁移到NUMA-2 cpuset(0-7) |
- *Todo 自动绑核*
在某些客户的与AMD等友商对比测试场景,要求开箱性能,以默认方式进行部署测试,不绑核
为了提升在该场景下的开箱性能,本插件实现了自动绑核的能力,直接将POD的资源分配在单个NUMA中,避免跨NUMA性能损耗,实现效果类似于负载均衡
注册NRI插件create container接口,在创建前进行绑核
- *Todo 自定义调整策略*
用户自定义调整Pod资源的分布,通过配置文档,规定某些Pod的资源在NUMA节点上的分布方式,用于POC测试场景
#### 3、哪些Pod需要纳管
管理Pod的命名空间与该operator一致
在命名空间中的Pod只要满足以下条件中的一个条件,就纳管该Pod
- a. 配置了白名单的Pod,在配置文件中配置Pod的名称,显式表明需要管理的Pod;
- b. 按CRD的声明格式实现CR,在CR中关联的Pod需要纳管
CRD格式
```yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (devel)
name: oenumas.resource.sop.huawei.com
spec:
group: resource.sop.huawei.com
names:
kind: Oenuma
listKind: OenumaList
plural: oenumas
singular: oenuma
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: Oenuma is the Schema for the oenumas API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: OenumaSpec defines the desired state of Oenuma
properties:
name:
description: INSERT ADDITIONAL SPEC FIELDS -- desired state of cluster
type: string
node:
items:
properties:
name:
type: string
numa:
items:
properties:
cpuset:
type: string
memset:
type: string
numaNum:
format: int32
type: integer
type: object
type: array
podAffinity:
items:
properties:
namespace:
type: string
numaNum:
format: int32
type: integer
podName:
type: string
type: object
type: array
type: object
type: array
replicas:
format: int32
type: integer
type: object
status:
description: |-
OenumaStatus defines the observed state of Oenuma.
It should always be reconstructable from the state of the cluster and/or outside world.
type: object
type: object
served: true
storage: true
```