stock-machine-addition.md 8.0 KB

库存管理新增机器实现说明

本文总结当前仓库中“库存管理 -> 新增机器”的实现方式。这里的“新增机器”不是只往数据库插一条记录,而是“后端落库存 + 调用 orchestrator 下发纳管 + 后端定时回写库存状态”的完整流程。

总体链路

  1. 前端在库存管理页点击“新增机器”。
  2. 用户填写 IP、SSH 账号密码、CPU/内存/SSD 等信息,必要时先点击“自动获取以下配置”。
  3. 前端调用后端 POST /api/resource/stocks
  4. 后端完成权限校验、字段校验、SSH 密码加密、IP 去重后,先写入 stock_info,并将状态置为 JOINING
  5. 后端再调用 k8s orchestrator 的 addNode 接口,但不等待纳管完成,接口立即返回。
  6. orchestrator 创建 ClusterOperation,随后创建 NodeInventory
  7. NodeInventory controller 通过 SSH 登录目标机器,安装 kubeadm 依赖并执行 kubeadm join
  8. 后端定时任务 StockJoiningStatusReconciler 轮询 orchestrator 的节点列表,发现对应 NodeInventory 进入 IDLE 后,把库存状态回写为 IDLE

前端实现

入口页面是 StockPage.vue

1. 新增入口

页面右上角提供“+ 新增机器”按钮,点击后打开创建弹窗。创建弹窗和编辑弹窗共用同一套表单结构。

2. 手动填写与自动探测

新增弹窗要求填写以下信息:

  • ipAddress
  • sshUsername
  • sshPassword
  • cpuModel
  • cpuCores
  • ramGb
  • ssdGb
  • description

新增时还提供一个“自动获取以下配置”按钮,会调用 frontend/src/api/stock.js 里的 probeStock(),对应后端 POST /api/resource/stocks/probe。后端会通过 SSH 在目标机器上执行探测脚本,从机器自身读取 CPU、内存和磁盘信息,再回填到表单中。

3. 表单提交

提交时前端调用 createStock(payload),即:

  • POST /api/resource/stocks

前端做了基本校验:

  • IP 格式校验
  • SSH 用户名必填
  • 新增时 SSH 密码必填
  • CPU / 内存 / SSD 必填
  • 备注长度限制

后端实现

新增机器的入口在 StockController.java

  • POST /api/resource/stocks -> stockService.addMachine(...)

真正的业务逻辑在 StockService.java

1. 权限控制

addMachine() 会先调用 requireOpsRole(),因此只有以下角色可以新增机器:

  • ADMIN
  • DEVELOPER
  • OPS

2. 创建前校验

后端会执行以下处理:

  • 规范化输入字段,去掉前后空格。
  • 新机器状态固定初始化为 JOINING,对应前端展示的“加入中”。
  • 校验 IP 地址合法性,支持 IPv4 和 IPv6。
  • 校验 CPU、内存、SSD、CPU 型号、SSH 用户名、SSH 密码等字段长度和取值范围。
  • 校验 IP 唯一性,避免重复纳管同一 IP。
  • SSH 密码只在创建时接收明文,随后会被加密后存库。

对应的请求 DTO 是 StockMachineCreateReq.java,库存实体是 StockInfoDO.java

3. 先落库,再异步纳管

addMachine() 的核心顺序是:

  1. 将 SSH 密码加密,写入 sshPasswordCipher
  2. 清空明文密码字段。
  3. 将机器状态初始化为 JOINING
  4. stockDAO.save(normalizedMachine) 插入 stock_info
  5. 通过 OrchestratorIdMapper.toNodeId(machineId) 生成固定节点名,格式为 node-{machineId}
  6. 调用 k8sOrchestratorClient.addNode(...) 发起纳管。
  7. 由定时 reconciler 调用 orchestrator 的 GET /v1/nodes,按 node-{machineId} 找到对应 NodeInventory
  8. 看到该 NodeInventorystatus.phase 变为 IDLE 后,再把库存状态改回 IDLE

这里有一个重要设计点:库存记录先落库,纳管结果后回写。如果 orchestrator 短期内还没完成,库存列表会持续显示“加入中”,直到后台 reconciler 看到节点真正就绪。

4. 响应数据

创建成功后,后端会返回新增后的机器信息,前端把它插入表格首行,并显示“纳管请求已提交”提示。此时状态一般是“加入中”,不会立即显示为 IDLE

数据库落点

库存数据表是 backend/src/main/resources/db/schema.sql 里的 stock_info

关键字段:

  • machine_id
  • ip_address
  • cpu_cores
  • cpu_model
  • ram_gb
  • ssd_gb
  • ssh_username
  • ssh_password_cipher
  • status
  • description

对应 MyBatis SQL 在 StockDAO.xml

  • save 负责插入机器记录
  • findByIpAddress 用于 IP 去重
  • updateStatusByIdAndStatus 用于状态切换
  • deleteByIdAndStatus 用于删除前的状态约束

Orchestrator 侧实现

后端通过 HttpK8sOrchestratorClient.java 调用编排器。

addNode() 会向 orchestrator 的 POST /v1/nodes 发送:

  • nodeId
  • cpu
  • ram
  • ssd
  • ipAddress
  • sshUser
  • sshPassword
  • sshPort

1. 创建操作

orchestrator 侧 OrchestratorService 会把这次请求包装成一个 ClusterOperation,操作类型是 AddNode

2. 创建 NodeInventory

ClusterOperationReconciler 在处理 AddNode 时,会创建一个 NodeInventory 资源,关键字段来自请求:

  • spec.address -> 机器 IP
  • spec.capacity -> CPU / RAM / SSD
  • spec.sshSecretRef -> SSH Secret
  • spec.bootstrap.joinCommandOverride -> join 命令覆盖参数

相关代码在 clusteroperation_controller.gonodeinventory_types.go

3. NodeInventory 控制器的状态机

NodeInventoryReconciler 的状态流转在 nodeinventory_controller.go

  • NEW -> 初始化状态
  • PROVISIONING -> 开始 SSH 纳管
  • IDLE -> 节点已成功加入,可分配
  • FAILED -> 纳管失败,控制器会重试

ensureNodeJoined() 会先尝试通过固定节点名或 IP 找到已存在的节点;如果不存在,再读取 SSH Secret,使用 onboarder 执行远程安装和 kubeadm join

4. onboarder 做了什么

SSHOnboarderonboarder.go 里完成三步:

  1. 在目标机器上安装 kubeadm、kubelet、containerd 等依赖。
  2. 清理旧的 kubelet/kubeadm 残留状态。
  3. 执行 join 命令,并附加 --node-name,保证 Kubernetes 节点名和 NodeInventory 名称一致。

新增机器时的关键约束

  • 机器初始状态固定为 JOINING,不会直接写成 WORKING
  • JOININGWORKING 都属于受管生命周期态,库存新增接口不会允许手工写入。
  • JOININGWORKING 都属于受管生命周期态,编辑时只允许修改备注。
  • stock_info.ssh_password_cipher 只保存加密后的密码,不保存明文。
  • ip_address 必须唯一。
  • 如果纳管还没完成,库存记录会保留在 JOINING 状态,后台 StockJoiningStatusReconciler 会持续轮询直到它变成 IDLE

一句话总结

当前的“新增机器”本质上是一个两阶段流程:先把机器作为库存资产写入后端数据库,再交给 k8s orchestrator 通过 NodeInventory 完成真实纳管和 Kubernetes 入网。