从零构建异构高性能计算集群:Kubernetes与Ceph实战指南 1. 项目概述从“winner1300”看高性能计算集群的平民化实践最近在折腾一个老项目翻出来一堆退役的服务器硬件型号杂七杂八性能也参差不齐。看着这些“电子垃圾”我就在想能不能用它们搭一个能真正干点“重活”的计算集群不是为了跑分好看而是能稳定处理一些并行计算任务比如视频转码、数据清洗或者小规模的机器学习模型训练。这个想法催生了我称之为“winner1300”的项目。这个名字听起来有点中二其实是我给这个自建集群起的代号“1300”源于最初拼凑起来的核心线程数总和。这不仅仅是一次硬件堆砌更是一次关于如何用最低的成本、最普通的硬件构建一个稳定、可用且易于管理的高性能计算环境的深度探索。你可能觉得搞集群那是大公司、科研机构的事动辄成千上万的节点用的是专有网络和存储。但我想分享的是随着开源软件生态的成熟和硬件价格的走低构建一个服务于中小团队甚至个人开发者的“微型高性能计算集群”已经变得非常可行。winner1300项目的核心目标就是验证并实践一套从硬件选型、系统部署、资源调度到任务分发的完整方案让计算力变得触手可及。无论你是一个想学习分布式计算的学生一个需要处理海量数据的小团队还是一个热衷于硬件的极客这个项目都能给你提供一条清晰的路径。接下来我会详细拆解从零搭建到实际应用的全过程分享其中踩过的坑和总结出的宝贵经验。2. 整体架构设计与核心思路拆解搭建一个计算集群远不是把几台机器用网线连起来那么简单。它涉及到计算、存储、网络和管理四个层面的深度融合。在规划winner1300时我首先明确了几个核心原则成本可控、易于维护、高可用性、弹性扩展。基于这些原则我设计了一套分层架构。2.1 硬件层异构硬件的统一管理我的硬件来源很杂有淘汰的企业级双路服务器也有几台高性能的游戏台式机甚至还有两台树莓派4B。它们的CPU架构x86_64和ARM、内存大小、磁盘类型都不同。这种异构性是业余项目的常态但也带来了挑战。我的策略是抽象化硬件差异。通过统一的Linux操作系统我选择了Ubuntu Server LTS版本和容器化技术将应用与底层硬件解耦。对于x86和ARM的差异我们在软件编译和容器镜像选择时需要注意但通过Docker的多架构镜像或统一使用兼容性好的基础镜像如alpine可以很大程度上规避问题。关键在于我们的集群管理软件如Kubernetes或更轻量的Docker Swarm需要能够识别并调度任务到合适的节点上。例如我将树莓派节点标记为“arm”节点只调度那些已经编译好ARM版本或兼容ARM架构的轻量级任务如数据采集、消息转发。注意异构集群中性能最差的节点往往会成为整个系统的短板尤其是在需要跨节点同步数据的场景下。因此合理的节点角色划分至关重要。我将性能最强的双路服务器作为“计算节点”和“存储节点”的核心而性能较弱的机器和树莓派则作为“边缘节点”或“专用任务节点”例如专门运行日志收集服务。2.2 网络层低延迟与高带宽的平衡集群内部网络通信的效率和稳定性直接决定了并行计算的性能。家用千兆交换机是起点但远远不够。我采用了双网卡绑定Bonding和专用存储网络的策略。管理/业务网络使用普通的千兆交换机所有节点通过一个网卡连接用于SSH管理、服务发现、API调用等常规流量。我将两个千兆网卡绑定为“mode4”802.3ad动态链路聚合需要交换机支持LACP。这提供了负载均衡和故障转移有效提升了带宽和可靠性。存储网络为了满足计算节点高速访问共享存储的需求我额外为几台核心服务器配备了万兆光纤网卡并通过一台二手万兆交换机直连组建了一个独立的存储网络。在这个网络上运行NFS或Ceph这样的分布式存储服务可以避免存储IO成为性能瓶颈。网络规划表网络名称用途典型带宽技术选型关键考虑管理网络SSH, 服务发现集群管理1Gbps (Bonded)千兆交换机 Bonding稳定性、IP地址规划存储网络分布式存储数据同步、虚拟机/容器磁盘IO10Gbps万兆光纤交换机低延迟、高带宽、隔离性业务网络对外服务访问可选1Gbps千兆交换机/VLAN安全隔离、负载均衡2.3 软件栈轻量、易用与强大的组合软件选型上我遵循“不重复造轮子”和“社区活跃”的原则。核心软件栈如下操作系统Ubuntu Server 22.04 LTS。选择LTS版本是为了获得长期稳定的更新和支持兼容性好文档丰富。容器引擎Docker。它是现代应用打包和运行的事实标准提供了良好的隔离性和可移植性。集群编排Kubernetes (K8s)。虽然学习曲线陡峭但它是容器编排的王者提供了无与伦比的自动化部署、扩展和管理能力。对于小规模集群使用kubeadm工具可以相对轻松地完成部署。存储方案Rook Ceph。这是一个在K8s内部运行Ceph存储集群的Operator。它让我能用声明式的方式管理一个高可用、可扩展的分布式存储系统完美契合K8s的哲学。数据持久化问题迎刃而解。监控告警Prometheus Grafana。Prometheus负责采集集群和应用的各项指标CPU、内存、磁盘、网络、服务健康状态Grafana则用于可视化展示。再搭配Alertmanager可以实现灵活的告警规则。日志收集EFK Stack (Elasticsearch, Fluentd, Kibana)。Fluentd作为日志收集代理部署在每个节点将容器和系统日志统一发送到Elasticsearch进行索引和存储最后通过Kibana进行查询和展示。这套组合拳下来winner1300就从一个硬件集合变成了一个具备自我修复、弹性伸缩、服务发现、配置管理和可视化管理能力的现代化计算平台。3. 核心组件部署与关键配置详解有了清晰的架构设计下一步就是动手实施。这里我重点分享Kubernetes集群和Ceph存储的部署过程中那些容易踩坑的关键点。3.1 Kubernetes集群的“非标准”初始化在异构硬件和混合网络环境下用kubeadm初始化集群需要一些额外的配置。首先在所有节点上安装Docker和Kubeadm工具包是标准操作这里不再赘述。关键步骤在于初始化主控制平面节点。因为我有多个网络接口必须明确指定API Server的监听地址。# 在主节点上执行假设管理网络IP是192.168.1.100 sudo kubeadm init --apiserver-advertise-address192.168.1.100 --pod-network-cidr10.244.0.0/16--apiserver-advertise-address参数至关重要它告诉其他节点通过哪个IP来访问API Server。务必设置为节点间能互通的那个IP通常是管理网络IP。初始化成功后按照提示配置kubectl并安装Pod网络插件。我选择了Flannel因为它简单可靠对新手友好。但Flannel默认的VXLAN后端在跨节点通信时会有一定的性能开销。在我的万兆存储网络上我尝试了Flannel的host-gw模式它要求所有节点在同一个二层网络性能几乎无损。# 下载Flannel的k8s配置文件并修改backend type wget https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # 编辑kube-flannel.yml在net-conf.json部分将Backend的Type从vxlan改为host-gw kubectl apply -f kube-flannel.yml将工作节点加入集群后一个基础的K8s集群就搭建完成了。但此时集群还不能很好地识别我们的异构硬件。3.2 节点标签与污点管理精细化调度为了将任务调度到合适的节点我们需要给节点打上标签Labels。例如给ARM架构的树莓派打标kubectl label node raspberry-pi-1 node-typearm kubectl label node raspberry-pi-1 disk-typeslow # 如果它的磁盘是SD卡然后在部署应用的YAML文件中可以使用nodeSelector来指定调度到带有node-typex86的节点上。对于像存储节点这样需要特殊资源或不希望随意调度普通Pod的节点可以设置污点Taint只有容忍Toleration该污点的Pod才能被调度上去。这在部署Ceph这样的存储服务时是标准做法。3.3 使用Rook部署Ceph存储集群在K8s上管理存储Rook是目前最优雅的方案之一。部署过程是声明式的但前期准备和参数调优决定成败。第一步准备工作确保计划作为存储节点的机器有额外的裸磁盘未分区、未挂载。在我的环境中我为三台核心服务器各添加了一块1TB的SSD。通过lsblk命令确认磁盘路径例如/dev/sdb。第二步部署Rook Operator这很简单直接从GitHub拉取部署清单即可。git clone --single-branch --branch v1.10.0 https://github.com/rook/rook.git cd rook/deploy/examples kubectl create -f crds.yaml -f common.yaml -f operator.yaml第三步配置Ceph集群这是核心步骤。需要编辑cluster.yaml文件。关键配置项包括storage.nodes: 指定哪些节点参与存储以及使用这些节点上的哪些磁盘。必须正确填写节点的名称kubectl get nodes查看和磁盘路径。storage.useAllNodes和useAllDevices: 通常设为false以进行精确控制。network: 强烈建议为Ceph集群数据同步配置独立的存储网络。这里可以指定集群网络Cluster Network和公共网络Public Network的CIDR。例如我的存储网络是10.10.0.0/24。一个简化的节点配置示例storage: nodes: - name: node1 devices: - name: sdb - name: node2 devices: - name: sdb第四步创建存储类StorageClassCeph集群运行起来后需要通过StorageClass来提供动态卷供应。Rook提供了示例文件csi/rbd/storageclass.yaml。创建后应用就可以通过PVCPersistentVolumeClaim来申请持久化存储了。实操心得Ceph对时钟同步要求极高所有节点的时间偏差必须非常小建议在毫秒级。务必确保集群内运行了可靠的NTP服务如chrony。我曾因为时间不同步导致Ceph MON监控器无法达成共识集群状态一直异常排查了很久。4. 实战应用构建分布式视频转码流水线集群搭建好了存储也就绪了是时候让它真正“干活”了。我选择“分布式视频转码”作为第一个实战应用因为它计算密集、易于并行化且能直观体现集群的价值。4.1 应用架构设计视频转码流水线可以抽象为三个阶段任务分发接收用户上传的视频文件将其拆分成若干片段如按5分钟一段并将每个片段作为一个转码任务放入任务队列。并行转码多个工作Pod从队列中领取任务独立进行转码计算。结果合并所有片段转码完成后将音频视频流合并成一个完整的文件。在K8s上我们可以这样实现任务队列使用Redis部署为一个StatefulSet确保队列服务本身的高可用。任务分发器一个自定义的控制器可以用Pythonkubernetes客户端库编写监听文件上传事件例如通过MinIO对象存储负责拆分任务并推入Redis队列。它本身也部署为一个Deployment。转码工作器核心计算单元。我们创建一个包含FFmpeg工具的Docker镜像。工作器Pod从Redis队列中POP任务执行FFmpeg命令进行转码完成后将输出文件写入共享存储Ceph RBD卷并通知任务分发器。合并器另一个独立的Pod监听所有片段完成的事件然后调用FFmpeg进行合并。4.2 工作器Pod的详细配置工作器的K8s部署文件Deployment需要仔细配置资源请求和限制并利用好我们之前设置的节点标签。apiVersion: apps/v1 kind: Deployment metadata: name: video-transcoder-worker spec: replicas: 5 # 根据集群资源决定启动多少个工作副本 selector: matchLabels: app: transcoder-worker template: metadata: labels: app: transcoder-worker spec: nodeSelector: node-type: x86 # 只调度到x86高性能节点 containers: - name: worker image: my-registry/transcoder-ffmpeg:latest resources: requests: memory: 2Gi cpu: 1000m limits: memory: 4Gi cpu: 2000m volumeMounts: - name: transcode-storage mountPath: /workspace env: - name: REDIS_HOST value: redis-service - name: REDIS_QUEUE value: video_tasks volumes: - name: transcode-storage persistentVolumeClaim: claimName: transcode-pvc # 提前创建好的PVC指向Ceph存储关键点解析nodeSelector: 确保Pod只运行在标记为node-typex86的节点上避免调度到ARM节点。resources.requests/limits: 这是K8s进行资源调度和管理的依据。requests是调度时保证的最小资源limits是运行时的硬性上限。为CPU和内存设置合理的值可以防止单个Pod耗尽节点资源也便于K8s在节点间均衡负载。FFmpeg转码是CPU密集型任务我给了它1个核心1000m的请求和2个核心的限制。persistentVolumeClaim: 所有工作器挂载同一个共享存储这样它们都能读取到输入的片段并写入转码后的输出片段。4.3 任务队列与工作流的协调任务分发器将一个大视频文件拆分成N个任务每个任务包含片段起止时间、输入文件路径、输出格式参数等。它将任务序列化为JSON字符串推入Redis的List中。工作器Pod内运行一个简单的Python脚本循环执行以下逻辑while True: task_json redis_client.rpop(REDIS_QUEUE) if task_json: task json.loads(task_json) # 1. 从共享存储读取输入片段 # 2. 组装FFmpeg命令并执行 # 3. 将输出写入共享存储 # 4. 向另一个Redis Key (e.g., completed_tasks) 发送完成信号 else: time.sleep(5) # 队列为空休眠等待合并器则监听completed_tasks的数量当数量等于总片段数N时触发合并操作。整个流程通过Redis这个简单的中间件实现了松耦合的协同。5. 监控、运维与问题排查实录一个集群能否稳定运行三分靠部署七分靠运维。完善的监控和清晰的排查思路是运维的生命线。5.1 构建全方位的监控仪表板使用Prometheus Operator可以非常方便地在K8s中部署监控栈。它自动为集群内的各种资源节点、Pod、Service等配置抓取目标。核心监控指标集群健康度Node的CPU/内存使用率、磁盘IOPS和容量、网络带宽。通过node_exporter采集。Pod/应用状态每个Pod的CPU/内存使用量、重启次数、就绪状态。通过K8s内置的cAdvisor和kube-state-metrics采集。业务指标对于我们自建的转码服务需要暴露自定义指标。例如使用Prometheus的Python客户端库在任务分发器和工作器中暴露tasks_in_queue、tasks_completed_total、transcode_duration_seconds等指标。Ceph存储状态Rook已经集成了Ceph的Prometheus监控我们可以直接获取到POOL的使用率、OSD状态、IOPS、延迟等关键数据。在Grafana中我将这些指标组织成几个核心仪表板集群概览一眼看清所有节点的资源水位和Pod运行状态。存储集群详情聚焦Ceph各个POOL的使用量、OSD的UP/IN状态、读写延迟。视频转码业务看板显示任务队列长度、各工作器活跃任务数、平均转码时长、成功率等。这能直接反映业务流水线的健康度。5.2 典型问题排查与解决记录在winner1300的运行过程中我遇到了形形色色的问题以下是几个典型案例问题一Pod一直处于Pending状态。现象kubectl get pods显示某个Pod卡在Pending。排查kubectl describe pod pod-name查看事件。最常见的原因是资源不足Insufficient cpu/memory或没有节点满足节点选择器/污点要求。检查节点资源kubectl describe node node-name看Allocatable资源是否充足。检查节点标签和污点kubectl describe node node-name查看Labels和Taints部分。解决如果是资源不足考虑增加节点、减少Pod副本数或优化Pod的资源请求。如果是节点选择问题修正Pod的nodeSelector或为节点添加正确的标签。问题二Pod运行后频繁重启CrashLoopBackOff。现象Pod状态在Running和Error/CrashLoopBackOff之间循环。排查kubectl logs pod-name --previous查看上一次崩溃的日志。这通常能直接定位到应用代码错误、配置错误或依赖缺失。如果日志没有明显错误检查Pod的资源限制limits。可能是内存不足OOMKilled可以通过kubectl describe pod看到退出码是137。检查存储挂载是否成功比如PVC是否处于Pending或Bound状态挂载路径是否存在权限问题。解决根据日志修正应用错误适当调高内存限制检查PVC配置和存储后端Ceph的健康状态。问题三Ceph集群健康状态为HEALTH_WARN或HEALTH_ERR。现象kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph status显示非HEALTH_OK状态。常见原因及解决OSD down某个存储守护进程挂了。检查对应节点和磁盘状态。可能是磁盘故障、节点重启或网络分区。尝试重启OSD Podkubectl -n rook-ceph delete pod osd-pod-nameRook会自动重建。PG状态异常Placement GroupPG是Ceph数据分布的单位。出现inactive,stale,degraded等状态通常与OSD down或网络问题有关。先解决OSD问题PG状态通常会自行恢复。可以使用ceph pg repair pg_id尝试修复但需谨慎。空间接近满监控仪表板会提前告警。需要及时添加新的OSD或扩容现有OSD磁盘也可以删除不必要的数据。问题四跨节点Pod网络不通。现象Pod A在Node1无法ping通Pod B在Node2的ClusterIP或Pod IP。排查检查Flannel或其他CNI插件的Pod是否在所有节点正常运行kubectl get pods -n kube-system -l appflannel。检查节点路由表在Node1上ip route show看是否有到其他节点Pod网段如10.244.0.0/16的路由下一跳是否正确指向了目标Node2的IP。检查防火墙确保所有节点之间在相关网段如管理网的192.168.1.0/24和Pod网的10.244.0.0/16的流量没有被防火墙如ufw或firewalld阻止。这是最常见的原因解决确保CNI插件Pod健康如果使用host-gw模式确认节点间二层互通最关键的在集群所有节点上放行CNI所需的端口和网段。对于Flannel通常需要放行UDP 8285和8472端口以及VXLAN或主机网关的流量。避坑技巧养成“从底向上”的排查习惯。网络不通先查物理链路和交换机再查主机防火墙和路由最后查K8s网络插件。Pod起不来先看节点资源再看调度约束最后看容器日志。建立清晰的排查路径图能极大提升效率。6. 性能调优与成本控制实践集群跑起来只是第一步如何让它跑得更快、更省才是体现功力的地方。在winner1300项目上我做了以下几方面的调优。6.1 计算密集型任务调优对于视频转码这类CPU密集型负载CPU绑核通过设置Pod的spec.containers[].resources.limits.cpu为整数如2并添加注解spec.template.spec.containers[].resources.requests.cpu等于limits可以暗示K8s进行“静态”CPU管理减少上下文切换在某些场景下能提升性能。但要注意这会降低调度灵活性。使用本地临时存储转码过程中会产生大量的临时文件。如果每次都读写网络存储CephIO延迟会成为瓶颈。我为工作器Pod挂载了emptyDir卷并设置medium: Memory将临时文件写入内存盘tmpfs速度极快。只需确保最终输出结果写入持久化存储即可。FFmpeg参数优化这是应用层最大的优化点。例如使用更高效的编码器如libx265vslibx264调整线程数-threads参数与Pod分配的CPU核心数匹配利用硬件加速如果显卡支持等。需要根据源视频格式和目标质量进行反复测试。6.2 存储性能优化Ceph的默认配置偏向于通用和稳定针对特定负载可以调整。CRUSH Map调优确保数据副本均匀分布在不同的主机和机架故障域上。在我的小规模集群中我设置了host级别的故障域保证同一个PG的两个副本不会落在同一台物理主机上。Pool配置为视频转码业务创建独立的存储池Pool。可以针对性地设置size: 副本数我设置为2在容量和可靠性间平衡。pg_num: Placement Group数量。这是一个非常关键的参数设置过小会导致数据分布不均和OSD负载不均衡设置过大会增加管理开销。可以使用Ceph官方提供的PG数量计算器来估算。对于我的1TB左右池我设置了128个PG。使用SSD作为DB/WAL设备如果OSD使用的是HDD可以添加一块小容量SSD作为“日志设备”将写操作的Journal放在SSD上能显著提升随机写性能。我的环境全部是SSD所以无需此步骤。6.3 成本与能效控制7x24小时运行一个集群电费不容忽视。节点自动启停对于非核心的边缘节点如树莓派或者在工作负载低谷期可以通过脚本结合K8s的cordon/drain命令安全地排空节点并关机。在需要时再远程唤醒WoL并恢复调度。这需要主板和网卡支持网络唤醒功能。Pod水平自动伸缩HPA为转码工作器的Deployment配置HPA基于CPU利用率或自定义指标如队列长度自动调整Pod副本数。当没有任务时副本数可以缩容到0节省资源。apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: transcoder-worker-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: video-transcoder-worker minReplicas: 0 # 允许缩容到0 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # CPU平均使用率超过70%则扩容资源超售与服务质量QoS合理设置Pod的requests和limits。对于非关键任务可以设置较低的requests允许一定的超售提高集群资源利用率。K8s会根据requests来划分Pod的QoS等级Guaranteed, Burstable, BestEffort在节点资源紧张时BestEffort的Pod会最先被终止。构建和维护winner1300这样的项目是一个持续学习和优化的过程。它让我深刻体会到将分散的计算资源整合成一个有机整体所带来的力量。从最初的硬件拼凑到如今能稳定处理实际工作负载每一步都充满了挑战和收获。对于想要入门分布式系统和云原生技术的朋友我强烈建议从这样一个具体的、有明确产出目标的小项目开始。你会遇到真实的问题并迫使自己去理解网络、存储、调度、应用编排等每一个环节这种经验远比单纯阅读文档要深刻得多。最后一个小建议做好文档记录无论是集群的配置清单、部署步骤还是遇到的问题和解决方案积累下来的知识库是你未来运维和扩展最宝贵的财富。