最近在尝试对 WSUS 服务器进行性能深度优化。就如笔者在上一篇公众号文章中所述,唯高质才能有真正的高效。
IT环境优化是每一个系统管理员都应该去做的事情,而且要深入地充分压榨软硬件的性能,这才是高质运维达致的高效运维。
笔者:国际认证信息系统审计师、软考系统分析师、软件工程硕士
深度优化是个反复测试评估的过程,在有结果之前,必须要先把一些最基础的 WSUS 服务器优化措施进行复盘总结,更新操作规程,同时在优化的测试和部署过程中做好过程日志。
严谨认真而不是片面追求短平快,是达致高质量的前提。
WSUS,也就是 Windows Server Update Service,它的作用相信不需要笔者详细介绍。
其实本来笔者是想吐槽 WSUS 在设计和功能上的各种不便,所以才有了上面的题头图。不过,吐槽无意义,动手改变才是实事求是。
但凡内网部署 Windows 系统有点规模,系统管理员都知道设置一台 WSUS 服务器的必要性。
不然每个月 Microsoft 推送打补丁那几天,互联网出口再高的带宽都有可能卡成狗。
WSUS 服务器的性能优化从来都是关键话题,任何一个操作过 WSUS 提供的服务器清理向导功能(就是下面图1这玩意)的系统管理员都会对该过程的缓慢程度留下深刻印象。
图1 WSUS 服务器清理向导操作建议
WSUS 是由多层次的软件组件构成,所以性能优化,需要对构成 WSUS 服务器的这些组件逐层分别进行。
而随着 PowerShell 已经非常成熟,过往改注册表、改组策略的手工操作调整方法大部分都可以改用 PowerShell 脚本实现,且更安全、直观和可追溯。
注:全文基于 Windows Server 2022 实施。
支撑 WSUS 的软件组件可以在其安装过程的依赖安装信息中获悉,主要包括了:IIS、WID和BITS,具体如图2。
图2 WSUS 安装期间依赖信息
IIS 方面的优化主要在于 WsusPool 这个 WEB 应用程序池的运行上,包括内存占用限制、进程运行时长限制、队列限制等。
因为 WSUS 服务器的默认配置面向的是只有少量终端的小规模局域网环境,对于大规模环境,不优化就频繁出错崩溃,根本不能正常运行。
具体的优化内容可以通过以下两张图进行说明:
图3 WsusPool 应用程序池运行配置优化
图4 WsusPool 应用程序池访问配置优化
手工操作可以按图3、图4进行。如果使用 PowerShell,则有如下脚本:
Import-Module WebAdministration
# 站点名称,如果是英文版操作系统要相应地修改
$siteName = "WSUS 管理"
# 设置站点配置所在路径
$webConfigPath = "IIS:\Sites\$siteName"
# 获取当前配置
$config = Get-WebConfiguration "/system.web/httpRuntime" -PSPath $webConfigPath
# 如果配置不存在,则创建
if (-not $config) {
Add-WebConfiguration "/system.web" -PSPath $webConfigPath
Add-WebConfigurationProperty -pspath $webConfigPath -filter "system.web" -name "httpRuntime" -value @{maxRequestLength="204800"}
Add-WebConfigurationProperty -pspath $webConfigPath -filter "system.web" -name "httpRuntime" -value @{executionTimeout="7200"}
} else {
# 更新现有配置
# 扩充客户端请求内容的长度限制(数值为KB),这对于安装了大量微软软件要检查补丁更新的客户端有效。
Set-WebConfigurationProperty -filter "system.web/httpRuntime" -PSPath $webConfigPath -name "maxRequestLength" -value "204800"
# 扩充客户端请求内容的执行超时(秒),这对于客户端缺了很多更新需要全部逐一枚举的情况有效。
Set-WebConfigurationProperty -filter "system.web/httpRuntime" -PSPath $webConfigPath -name "executionTimeout" -value "7200"
}
# 应用到 WSUS 应用程序池
$appPoolName = "WsusPool"
# 允许使用无限制的专用内存
Set-ItemProperty IIS:\AppPools\$appPoolName -Name recycling.periodicRestart.privateMemory -Value 0
# 允许使用无限制的虚拟内存
Set-ItemProperty IIS:\AppPools\$appPoolName -Name recycling.periodicRestart.memory -Value 0
# 扩充队列长度避免太快出现 503 "Service Unavailable" 错误
Set-ItemProperty IIS:\AppPools\$appPoolName -Name queueLength -Value 25000
# 避免当 IIS Worker 崩溃时导致出现 "Error: Unexpected Error - Reset Server Node" 错误
Set-ItemProperty IIS:\AppPools\$appPoolName -Name failure.loadBalancerCapabilities -Value "TcpLevel"
# 延长 CPU 监视重置的周期时间为15分钟,提高 CPU 资源占用的容忍度。
Set-ItemProperty IIS:\AppPools\$appPoolName -Name cpu.resetInterval -Value "00:15:00"
# 关闭进程模型的 PING 功能,避免长时间运行不响应导致进程被终止。
Set-ItemProperty IIS:\AppPools\$appPoolName -Name processModel.pingingEnabled -Value "False"
注意脚本要以 ASCII 编码保存。如果是 UTF-8 则中文会出错。
在安装 WSUS 时可选使用 Windows Server 自带的内部数据库 WID 或者独立的 MS SQL Server 。
对于 Windows Server 2022,其自带的 WID 实质是 SQL Server 2014 RTM/SP2 的嵌入式版本(版本号 12.0.2000.8/12.0.5214.6),所以对 SQL Server 有效的优化措施都可以施加在 WID 之上。
对 WID 的操作也是使用 Microsoft SQL Server Management Studio (SSMS),但 WID 只允许本机连接,不能通过网络连接。
在服务器上安装 SSMS 后,使用如下标识并取消加密选项(如图5)便可连接到 WID:
np:\\.\pipe\MICROSOFT##WID\tsql\query
图5 SSMS 连接 WID 设置
其后就是对 WID 进行 CPU 和内存资源的配置。系统管理员应该按照 WSUS 数据库也就是 SUSDB 的数据量规模,参考 MS SQL Server 的优化方法,为 WID 恰当配置 CPU 和内存资源。
由于 WID 并不是完整的 SQL Server 安装,因此试图通过修改服务器属性的方式调整服务器运行参数只会获得如下图5的出错提示:
图6 SSMS 访问 WID 属性出错
正确的方法是通过存储过程 sp_configure 进行配置。
比如要限制 WID 能使用的最大内存,可以先使用如下的查询命令获取:
SELECT [name], [value], [value_in_use]
FROM sys.configurations
WHERE [name] = 'max server memory (MB)' OR [name] = 'min server memory (MB)';
默认情况下,最小内存配置是16MB,最大内存配置是2048TB,和 SQL Server 一样的。
由于 SQL Server 的设计特性是占用所有内存,而 WSUS 服务器要保留一部分空闲内存作为文件系统的缓冲,所以必须限制 WID 的最大内存。
具体限制比例读者可以自行估算,一般地,允许 WID 使用的最大内存能把整个 SUSDB 装到内存里是最优解。
但如果 SUSDB 特别庞大,又或者服务器内存本来就不多,比如笔者的测试环境设置了 8GB 内存,那么参考微软的 WSUS Best Pratice [1],为了把内存尽量留给 WSUS 的 IIS 应用程序,只保留 1GB 内存给 WID:
sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'max server memory', 1024;
GO
RECONFIGURE;
GO
一般来说给 WSUS 用的服务器都不会特别高配置,所以对于内存确实不太够的情况,还可以启用锁定内存页(Lock Pages In Memory,LPIM)功能,把 WID 使用的内存锁定在物理内存中。
具体手工操作,在本地组策略-计算机配置-WINDOWS 设置-安全设置-本地策略-用户权限分配,双击策略:锁定内存页,然后在对话框点击添加用户或组,添加 SQL Server Service 的运行账户,如图7:
图7 组策略设置锁定内存页
更详细的操作说明可以参考微软的文档:
Enable the Lock pages in memory option (Windows)
https://learn.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows
笔者编写了如下的 PowerShell 脚本可以替代手工执行过程,但读者如果要应用于生产环境,应先行测试和进行必要的修改,比如增加更多的容错机制:
# 1. 使用 Get-WmiObject 来获取 SQL Server 服务的运行账户名称,然后转换为SID。
# 2. 修改本地安全策略,将获取的服务运行账户名称添加到“锁定内存页”策略中。
# 3. 确保更改的本地安全策略生效。
# 获取 SQL Server 服务的服务名称和运行账户名称
$sqlServiceName = 'MSSQL$MICROSOFT##WID'
try {
$serviceAccount = (Get-WmiObject -Query "SELECT StartName FROM Win32_Service WHERE Name='$sqlServiceName'").StartName
# $sqlService = Get-WmiObject -Class Win32_Service -Filter "Name='$sqlServiceName'"
# $serviceAccount = $sqlService.StartName
if (-not $serviceAccount) {
raise
}
Write-Output "SQL Server Service Account: $serviceAccount"
# 把账号转换为SID
$ntAccount = New-Object System.Security.Principal.NTAccount($serviceAccount)
$sid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value
Write-Output "SQL Server Service Account SID: $sid"
} catch {
Write-Error "Failed to get service account name. Now quit."
exit
}
# 导出当前本地安全政策
$secpolPath = "C:\Windows\Temp\secpol.cfg"
secedit /export /cfg $secpolPath | Out-Null
# 读取策略文件并添加服务账户
$content = Get-Content -Path $secpolPath
# 找到 [Privilege Rights] 部分的起始位置
$lpimLine = "SeLockMemoryPrivilege"
$lpimSectionIndex = $content.IndexOf("[Privilege Rights]")
if ($lpimSectionIndex -eq -1) {
Write-Error "[Privilege Rights] not found."
exit
}
# 在 [Privilege Rights] 部分之前和之后分开
$preContent = $content[0..$lpimSectionIndex]
$postContent = $content[($lpimSectionIndex+1)..($content.Length-1)]
# 查找 lpimLine 是否已存在并进行相应处理
$lpimLineFound = $false
$postContent = $postContent | ForEach-Object {
if ($_ -like "$lpimLine *") {
$lpimLineFound = $true
if ($_ -notmatch "\b$($sid)\b") {
$_ +=",*$sid"
}
}
$_
}
if (-not $lpimLineFound) {
$postContent = ,("$lpimLine = *$sid") + $postContent
}
# 合并新内容并写回文件
$newContent = $preContent + $postContent
# 将修改后的策略写回文件
Set-Content -Path $secpolPath -Value $newContent -Force
# 导入修改后的本地安全策略
# 需要时,请自行添加生成回滚模版的处置
# secedit /generaterollback
# 需要时,增加校验配置文件的处置
# secedit /validate /cfg $secpolPath
# 按需要自行调整导入操作的参数,比如限定仅修改特定安全区域 /areas USER_RIGHTS,以及取消 /overwrite 参数
secedit /configure /db secedit.sdb /cfg $secpolPath /overwrite /quiet
# 更新应用组策略
gpupdate /force
# 删除临时文件
Remove-Item -Path $secpolPath
Write-Output "LPIM policy is updated. Restart service to take effect."
BITS 方面的优化在于带宽利用和前后台下载模式的选择。一般建议是初始配置 WSUS 服务器时切换为前台下载,从而尽快完成初始配置。随后再切换回去后台下载。
另一种需要保持 BITS 一直使用前台下载模式的原因是,如果网络出口部署的防火墙或者代理服务器不支持 HTTP 1.1 的 Range Request 功能,则会导致 BITS 在使用后台下载模式(主动分段下载)时下载失败。
笔者建议是如果服务器专用于 WSUS 服务,始终保持前台下载。
通过 PowerShell 切换到前台下载的脚本:
$conf=(Get-WSUSServer).GetConfiguration()
$conf.BitsDownloadPriorityForeground=$True
$conf.save()
如果要切换回去后台下载,把脚本中的 $True 更改为 $False 后再运行一次即可。
WSUS 本身的优化在于两方面,一是数据库 SUSDB 的优化,二是操作界面的优化。
最简单直接的优化是通过 SSMS 的报告功能,从“性能仪表板”报告中获取由 WID 带有的 SQL Server 优化器所识别的潜在缺少索引的情况报告,然后按给出的建议补充添加索引。
报告界面不能直接复制其内容,先导出之后才能复制。
对于笔者的测试环境,简单运行操作后,SSMS 报告建议创建如下的索引:
CREATE INDEX missing_index_7 ON [SUSDB].[dbo].[tbRevision] ([State]) INCLUDE ([LocalUpdateID])
CREATE INDEX missing_index_9 ON [SUSDB].[dbo].[tbDeployment] ([DeploymentStatus], [TargetGroupTypeID], [LastChangeNumber], [UpdateType]) INCLUDE ([GoLiveTime], [RevisionID], [TargetGroupID])
CREATE INDEX missing_index_16 ON [SUSDB].[dbo].[tbDeployment] ([TargetGroupTypeID]) INCLUDE ([ActionID], [RevisionID])
CREATE INDEX missing_index_21 ON [SUSDB].[dbo].[tbProperty] ([CreationDate], [ReceivedFromCreatorService]) INCLUDE ([RevisionID], [PublicationState])
CREATE INDEX missing_index_23 ON [SUSDB].[dbo].[tbDeadDeployment] ([TargetGroupTypeID], [LastChangeNumber], [UpdateType]) INCLUDE ([TargetGroupID], [UpdateID], [RevisionNumber])
CREATE INDEX missing_index_24 ON [SUSDB].[dbo].[tbRevision] ([State]) INCLUDE ([RevisionID], [LocalUpdateID], [RowID])
随着 WSUS 的持续运行,WID 的 SQL Server 优化器会持续给出补充索引的建议,所以在 WSUS 完成部署投入运行后,系统管理员应定期关注是否需要继续补充索引。
至于补充的索引具体是否有效果,需要在有足够数据量的环境下进行比对测试。读者有兴趣可以自行尝试。
如果需要更深入地优化索引,还可以启动数据库引擎优化顾问程序,对 SUSDB 进行“计划缓存”类别的优化分析,如图8、图9:
图8 数据库引擎优化顾问操作设置
图9 数据库引擎优化顾问优化建议
SUSDB 包含了名为 spDeleteUpdate 这个用于删除更新记录的存储过程。由于该存储过程在执行时动态创建的临时表没有创建主键,导致在大量删除时执行非常缓慢,时间甚至会以天计算。
微软给出了解决办法即重建该存储过程。因为代码篇幅较长,本文就不转载了,读者可以通过以下 URL 跳转到微软网站页面查阅。
The spDeleteUpdate stored procedure runs slowly
https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/update-management/spdeleteupdate-slow-performance
WSUS 服务器经过一段时间的使用后,SUSDB 里面各数据表的索引会变得庞大且碎片化,整体效率下降。
微软给出了对 SUSDB 数据库进行优化维护的 T-SQL 脚本,同样因为篇幅原因,笔者不直接转载,读者可以通过以下 URL 跳转到微软网站页面查阅。
Reindex the WSUS database
https://learn.microsoft.com/en-us/troubleshoot/mem/configmgr/update-management/reindex-the-wsus-database
WSUS 的控制台从 Windows Server 2008 开始十几年不变,一个凑合性质的操作界面,完全谈不上好用。
记得第一次看到简陋的 WSUS 控制台界面时,笔者甚至产生了怀疑,WSUS 是否只是一个凑合性质的功能实现,目的其实是为了减轻微软自己的补丁下载压力。
它在响应速度上的最大问题在于,当系统管理员选中具有非常大篇幅内容说明的更新项目后,界面下方的内容说明窗口内容刷新速度太慢,眼睁睁看着窗口滚动条变长再缩短,没个5秒钟以上刷新不完。
典型如就是 Edge 浏览器或 Windows 的月度更新项目,一点中就可以拿杯子喝水了。
从表征上观察,原因可能是两点因素的交织:1、构建更新详情内容报告时频繁刷新窗口内容;2、更新详情内容包含有大量链接(取代过往更新)时。
这个问题微软一直忽视,笔者也未找到优化办法。如果哪位读者解决了,不妨留言。
充分压榨软硬件性能,花最少的钱实现最大的效果,还提升了运维人员的技能水平和理解深度,才是高质运维。
参考引用:
[1] Windows Server Update Services best practices
[2] SQL Server memory configuration options
本站微信订阅号:
本页网址二维码: