一、一张红色告警背后的问题

每次集群一告警,最头疼的就是:打开vCenter,看到一堆虚拟机的CPU曲线像心电图一样乱跳。看似谁也说不清原因,其实很简单:CPU不够分,大家开始抢了。尤其在一个大集群里,几十台虚拟机放在同一个默认资源池下面,资源一紧张,那些喜欢“吃”CPU的虚拟机(比如批处理任务、编译环境)就会把别人该用的计算时间抢走。DRS本意是帮大家自动搬家、平衡负载,可如果连“家”都没分好,再怎么搬家也只是把表面的数字抹平,根本问题还在。

举个生活里的例子:一栋大房子里住着好几个人,共用一个厨房。平时人少,谁也不误谁。可到了饭点,有人一下子占住灶台炖汤,另外的人就只能干等着。DRS就好比一个“协调员”,看到有人等太久,会劝炖汤的人去楼下厨房做饭。可楼下厨房也就那么一个灶台,炖汤的人过去了,楼下的人又得等。转来转去,问题没根治。原因就是没有把“谁在哪个厨房做饭”这件事从一开始定清楚。

二、CPU抢占和不平衡是怎么来的?

先说两个常被忽略的事实:第一,虚拟机的CPU资源是共享的,特别是超分配环境下,物理核的数量往往小于所有虚拟机核数的总和。第二,不同的业务对CPU的需求曲线不一样,有的平时闲着,一到晚上跑报表就争分夺秒。DRS会根据集群整体负载,把虚拟机从满载的主机迁移到空闲主机上,这是它的核心机制。但DRS做决策时,看的是“每个虚拟机”,而不是“每个业务组”。当很多虚拟机混在同一层资源池里,它们的份额没有区别,也没有任何资源边界。某个虚拟机一旦出现CPU突发,ESXi按照默认份额把CPU时间分配给所有虚拟机,但突发虚拟机可能消耗掉大量空余CPU,甚至连其他虚拟机的基础配额也被它“借走”。DRS的迁移需要满足条件,不是一秒钟就完成,而且频繁迁移又会引起计算开销。这一来二去,CPU抢占就发生了。

还有一个根源:很多集群里的资源池根本没有做“自定义”。默认只有一个根资源池,所有的虚拟机都在里面,就像所有人都在同一个池子里游泳。你说想限制某些人别游那么快?对不起,没有规则。只有把资源池拆开,给每个池设定明确的水位线和出口宽度,才能避免“一个人抽走整池水”的局面。

三、自定义资源池为什么能管住?

资源池的本质,是一组CPU和内存的“调度容器”。你可以为不同的业务创建不同的池,然后给每个池设置三个关键参数:预留、份额、限制。预留是多少?相当于提前划出一块专用CPU时间,这个池在任何时候都至少能拿到这些资源,别人抢不走。份额是什么?是当物理CPU不够用的时候,所有池按什么样的比例去分配剩余资源,份额越高的池,在排队时越靠前。限制是什么?是设置一个天花板,这个池最多只能占用这么多资源,就算下面虚拟机联合起来也难以突破。

把关键业务放进一个预留高、份额高的池,再把非关键业务放进一个限制低、份额低的池,两个池之间就有了明确的“防火墙”。DRS在计算负载时,会先把一个池看成一个整体,再做内部调度。这样即使某个虚拟机疯狂抢CPU,它的影响也只能控制在它所在的那个池内,其他池里的虚拟机不会跟着遭殃。这就是“强制隔离”的核心思路。

四、动手:用PowerCLI把隔离做起来

下面开始干活。文章里的所有操作都基于VMware PowerCLI,一种用PowerShell管理vSphere的命令行工具。建议你先在测试环境跑通,再上生产。

4.1 准备工作

首先,安装并连接PowerCLI,确保你有vCenter管理员权限。

# 安装PowerCLI模块(如果还没装)
Install-Module -Name VMware.PowerCLI -Scope CurrentUser -Force

# 导入模块
Import-Module VMware.PowerCLI

# 连接vCenter,把下面的地址和账号换成你真实的
Connect-VIServer -Server vc.lab.local -User 'administrator@vsphere.local' -Password 'P@ssw0rd'

4.2 查看集群当前的DRS状态

先看看DRS是否开启,自动化级别是什么。这一步能帮你确认后续改动不会和集群默认行为冲突。

# 获取名为ProdCluster的集群
$cluster = Get-Cluster -Name "ProdCluster"

# 查看DRS相关设置
$cluster | Select-Object Name, DrsEnabled, DrsAutomationLevel, DrsMigrationThreshold

如果输出的DrsEnabled是True,说明DRS已经在工作。DrsAutomationLevel如果是FullyAutomated,那就是完全自动,可以自己迁移虚拟机。这里我们不用改,保持原样就行。

4.3 创建两个资源池

场景是这样的:ProdCluster里既有生产业务,也有开发测试业务。咱们把它们分成两个池,Production池给生产用,DevTest池给测试用。生产池要求“饿不着、冲不高”,开发测试池要求“饿不死、别捣乱”。

# 创建生产资源池
$prodPoolParams = @{
    Name                      = "Production"       # 池名称,方便管理
    Location                  = $cluster           # 放在ProdCluster这个集群下
    CpuReservationMhz         = 20000              # 至少保留20GHz给这个池
    CpuExpandableReservation  = $false             # 关闭可扩展预留,防止借用其他池的预留
    CpuLimitMhz               = 40000              # 最多使用40GHz,防止它挤爆整个集群
    CpuSharesLevel            = "High"             # 高份额,CPU竞争时优先保证
    MemReservationMB          = 32768              # 至少保证32GB内存
    MemExpandableReservation  = $false             # 内存也关闭可扩展预留
    MemLimitMB                = 65536              # 内存上限64GB
    MemSharesLevel            = "High"             # 内存份额也设高
}
New-ResourcePool @prodPoolParams

# 创建设计来限制开发测试池的资源
$devPoolParams = @{
    Name                      = "DevTest"
    Location                  = $cluster
    CpuReservationMhz         = 0                  # 不预留CPU,没压力时不用白不用
    CpuExpandableReservation  = $false
    CpuLimitMhz               = 10000              # 最多只能占10GHz,不能影响生产
    CpuSharesLevel            = "Low"              # 低份额,竞争时往后站
    MemReservationMB          = 0
    MemExpandableReservation  = $false
    MemLimitMB                = 16384              # 内存最多16GB
    MemSharesLevel            = "Low"
}
New-ResourcePool @devPoolParams

注意看,两个池都没有设置可扩展预留。为什么要关掉?因为可扩展预留允许当前池在资源不够时,把父池(也就是集群根池)的预留拿过来用。这样一来,所谓的“隔离”就会被悄悄破坏。所以想让隔离生效,记得保持$false

4.4 把虚拟机移入相应的池

池建好了,下面就是搬家。用虚拟机名字匹配来移动,不会影响运行。

# 把名字以App-Web开头的虚拟机移到Production池
Get-VM -Name "App-Web-*" | Move-VM -Destination (Get-ResourcePool -Name "Production")

# 把名字以Dev-开头的虚拟机移到DevTest池
Get-VM -Name "Dev-*" | Move-VM -Destination (Get-ResourcePool -Name "DevTest")

# 移动后检查一下每个虚拟机所在的池
Get-VM | Select-Object Name, @{N='ResourcePool';E={$_.ResourcePool.Name}}

如果移动过程没报错,说明虚拟机已经在新的资源池里了。用最后一条命令能清楚地看到哪些虚拟机在哪个池里。

4.5 验证隔离效果

你可以通过PowerCLI查看资源池的实际配置,确认参数都生效了。

# 查看Production池的配置
Get-ResourcePool -Name "Production" | Format-List Name, CpuReservationMhz, CpuSharesLevel, CpuLimitMhz, MemReservationMB, MemSharesLevel, MemLimitMB

# 查看DevTest池的配置
Get-ResourcePool -Name "DevTest" | Format-List Name, CpuReservationMhz, CpuSharesLevel, CpuLimitMhz, MemReservationMB, MemSharesLevel, MemLimitMB

之后你可以在业务高峰期观察,生产池里的虚拟机CPU响应是否稳定,开发测试池里的虚拟机即使再疯跑,也只能被限制在10000MHz以下。另外也可以配合DRS规则,把同一组重要虚拟机设置为“聚集在一起”的规则,让它们尽量待在同一个主机上,减少跨主机拖拽成本。这里就不展开了,但思路是一致的。

五、这种做法适合用在哪些场景?

第一,生产环境与开发测试环境混部。很多公司为了省钱,把开发测试虚拟机和生产虚拟机塞进同一个集群。这时只要按环境分池,就能保证测试家伙把CPU吃满了,生产业务也不受影响。

第二,多租户场景。一个集群被多个业务部门共用,每个部门应该只看到自己的资源池,并且明确份额和上限。这样即使某个部门的应用出问题,也不会把整个集群拖垮。

第三,集群整体超分配比例较高的场景。当物理CPU核数不够而虚拟机堆积很多时,CPU抢占必然发生。资源池能在抢占之前就定好优先级,让更重要的业务拿到更多CPU时间。

六、技术优缺点

先说优点。用自定义资源池做隔离,不需要额外的软件,不改变虚拟机的操作系统,运行中的虚拟机也能直接被划入池内。而且规则简单清晰,可量化,可以随时通过脚本调整。它让DRS的调度更有秩序,等于给DRS戴上了一副“有色眼镜”,让它认清谁是老板、谁是打杂的。

再说缺点。资源池只能管CPU和内存这两项资源,管不了磁盘IO和网络带宽。如果你的问题出在存储或网络上,光靠资源池是不行的,还得配合存储I/O控制或网络I/O控制。而且,资源池配置需要一定的运维经验和提前规划。如果预留设得太高,比如所有池的预留加起来超过物理资源,反而会让虚拟机无法启动或者DRS无法正常迁移。另外,资源池的隔离并不是传统意义上的安全隔离,它只作用于调度层面,不能防止恶意虚拟机耗尽底层硬件资源之外的其他途径。

七、注意事项

第一,仔细设计预留、份额、限制这三个参数。每个池的预留总和不要大于集群实际可用资源,否则DRS会认为资源不足,可能拒绝放置虚拟机。第二,不要在所有池都开启“可扩展预留”。那样等于所有池都可以借用父池资源,隔离名存实亡。第三,资源池的层级不要太深,层级越多调度越复杂,建议用两层或三层结构。第四,动态调整时务必使用维护时间窗,虽然在线修改资源池配置和移动虚拟机通常不会中断业务,但需要留出足够的云主机缓存和网络带宽。第五,定期用脚本检查资源池的使用率和配置漂移,防止有人手动改了参数,导致隔离策略失效。第六,了解并尊重DRS的迁移算法,资源池只是给DRS划定了边界,不是替代DRS。如果集群里连DRS都关了,那预留和份额依然有效,但自动平衡就不工作了。

八、文章总结

说穿了,DRS集群里的CPU抢占和不平衡,不是DRS这个功能没用,而是没有给DRS一个合理的“地图”。所有虚拟机挤在一个大池子里,资源一紧张,抢占就不可避免。自定义资源池就是那张地图:用预留保证底线,用份额设定优先级,用限制封住顶部。通过PowerCLI,几分钟就能完成创建、配置、移动、验证这一整套动作。把生产、开发、测试分开,给关键业务高优先级,让后台任务尽量使用空闲资源,这个运维问题就能得到很实用的缓解。当然,隔离不是万能的,它解决的是CPU和内存的调度秩序,而存储、网络这些邻居层面的问题还要再想别的招。至少现在,当告警再次亮起时,你能有个更明确的排查方向。