香港服务器运行 Windows Server 时,我如何用 PowerShell DSC 把跨区域的大规模环境“捏”成一张脸

那是一个凌晨 2 点的周末变更窗口。香港机房的空调压缩机刚刚切换,走廊里有一点潮湿的味道。我盯着 NOC 屏幕上几十台新上架的 Windows Server 2022 Datacenter Core(Dell R6525,EPYC 7543,256GB RAM,2×1.92TB NVMe,双口 X710 10GbE)的心跳,另一端是新加坡、东京和美国西海岸的分站。
一周前,我们还在靠“金手指 + 文档”的方式做系统基线:有人漏了一个本地策略,有人忘了启用 TLS1.2,还有人装了不同版本的 .NET。线上事故不是来自“未知”,而是来自“不一致”。我决定用 PowerShell DSC(Desired State Configuration)把这些跨区域的 Windows 主机,拉回到统一的、可审计、可回滚的状态。
场景与基线(硬件/网络/OS)
| 机房/区域 | 机型/规格 | OS | 主要角色 | 互联延迟(往返) | 出口带宽 |
|---|---|---|---|---|---|
| 香港(主) | Dell R6525 / EPYC 7543 / 256GB / 2×1.92TB NVMe / X710 10GbE | Windows Server 2022 DC Core | Pull/Report 服务、Web、API、监控 | HK↔SG ≈ 35ms;HK↔TYO ≈ 50ms;HK↔SJC ≈ 130ms | 2×10Gbps |
| 新加坡 | 同上(小一档 CPU) | 2022/2019 混部 | Web、API | ↔HK ≈ 35ms | 10Gbps |
| 东京 | 同上 | 2019/2022 混部 | Web、任务队列 | ↔HK ≈ 50ms | 10Gbps |
| 美国西海岸 | 2022 DC | 缓存/边缘任务 | ↔HK ≈ 130ms | 10Gbps |
目标:用 DSC 实现跨区域“同一面孔”的系统基线(安全策略、特性/角色、服务、软件版本、注册表、文件/权限、网络、证书),并做到可持续纠偏(drift correction)和可审计(合规报告)。
1. 技术路线与架构决策
模式选择:跨区域规模化 → 采用 Pull 模式(节点定时从 Pull 服务器拉配置+模块),同时启用 Report 服务器收集合规/偏离状态。
Push 只用于小规模临时救火。
配置组织:角色化 + 组合资源(Composite Resource)。将“基地线(Baseline)”“WebRole”“AppRole”“Agent/Metrics”等解耦,便于分区复用与版本化。
安全:Pull/Report 端点用 IIS + HTTPS(TLS1.2);证书统一由内部 PKI 颁发,CRL/OCSP 跨区可达;节点端 LCM(Local Configuration Manager)禁用 HTTP。
模块管理:不直接连外网 PSGallery;在香港建立 内网 NuGet 源 + 镜像模块,跨区用 DFS-R 只读分发。
拓扑(文字示意):
节点(TYO/SG/SJC) ←HTTPS 443→ HK Pull(IIS/DSC-Service) ←DFS-R→ SG 只读镜像;
节点上报合规 → Report Server(同 HK IIS 站点)。
2. 准备工作(必做检查清单)
Windows Server 2019/2022 已内置 WMF 5.1(含 DSC v2),确保补丁最新。
端口:节点出站 HTTPS/443 到 https://dsc.hk.example.com;管理段 WinRM 5986(只给维护用)。
TLS:强制 TLS1.2;确保证书链/CRL 可达。
PowerShell 模块(在内网源提前准备好版本):
PSDscResources = 2.12.x(内置资源增强包)
NetworkingDsc、ComputerManagementDsc、xWebAdministration 或 WebAdministration、xCertificate
(可选)SqlServerDsc、cChoco(若用 Chocolatey 需遵循软件仓库合规)
3. 在香港搭建 DSC Pull/Report 服务(IIS)
# 以提升权限的 PowerShell 在香港 Pull 节点执行
Install-WindowsFeature -Name Web-Server,Web-Http-Redirect,Web-Static-Content,Web-Default-Doc,Web-Http-Errors,Web-Http-Logging,Web-Filtering,Web-Mgmt-Tools -IncludeManagementTools
Install-WindowsFeature -Name DSC-Service
# 生成注册密钥
$RegPath = 'C:\Program Files\WindowsPowerShell\DscService'
New-Item -ItemType Directory -Path $RegPath -Force | Out-Null
$Guid = [guid]::NewGuid()
$Guid | Out-File "$RegPath\RegistrationKeys.txt" -Encoding ascii
# 绑定 HTTPS 证书(假设证书私钥已导入本机,主题 dsc.hk.example.com)
$cert = (Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Subject -like '*dsc.hk.example.com*' } | Sort-Object NotAfter -Descending)[0]
New-WebBinding -Name 'PSDSCPullServer' -Protocol https -Port 443
Push-Location IIS:\SslBindings
New-Item "0.0.0.0!443" -Thumbprint $cert.Thumbprint -SSLFlags 0
Pop-Location
# 设置目录权限(IIS AppPool\PSDSCPullServer)
$svcAcct = 'IIS AppPool\PSDSCPullServer'
icacls "$RegPath" /grant "$svcAcct:(OI)(CI)(M)" /T
注意:Install-WindowsFeature DSC-Service 会创建 PSDSCPullServer 站点与典型的 OData 端点 /PSDSCPullServer.svc 与 /PSDSCComplianceServer.svc。我们用同一站点承载 Pull 与 Report。
4. 角色化建模:把“基线”和“业务”拆开
我把“基地线”和“业务角色”拆成组合资源,代码在内部 Git(示例浓缩如下)。
4.1 基线(Baseline):统一时区/日志、启用 TLS1.2、必要特性、常用服务与本地策略。
configuration Baseline
{
Import-DscResource -ModuleName PSDscResources,ComputerManagementDsc,NetworkingDsc
Node $AllNodes.NodeName {
# 主机名 & 时区
Computer 'Hostname' {
Name = $Node.ComputerName
}
TimeZone 'HKTime' {
IsSingleInstance = 'Yes'
TimeZone = $Node.TimeZone # 'China Standard Time' 或地区自定
}
# 启用 TLS1.2、禁用 SSL3/TLS1.0(注册表基线)
Registry 'TLS12_Client' {
Key = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client'
ValueName = 'Enabled'
ValueData = 1
ValueType = 'Dword'
Ensure = 'Present'
}
# 必要 WindowsFeature
WindowsFeature 'NET-Framework-Features' { Name = 'NET-Framework-Features'; Ensure = 'Present' }
WindowsFeature 'Web-WebServer' { Name = 'Web-WebServer'; Ensure = 'Present' } # 仅示例,可按角色条件化
# 本地管理员组(最小权限)
Group 'LocalAdmins' {
GroupName = 'Administrators'
MembersToInclude = $Node.LocalAdmins
Ensure = 'Present'
}
# 防火墙基线(示意)
Firewall 'Allow_HTTPS_Out' {
Name = 'Allow_HTTPS_Out'
Ensure = 'Present'
Enabled = 'True'
Direction = 'Outbound'
LocalPort = '443'
Protocol = 'TCP'
Action = 'Allow'
}
}
}
4.2 WebRole:IIS 站点、AppPool、日志策略与反向代理依赖。
configuration WebRole
{
Import-DscResource -ModuleName PSDscResources, xWebAdministration
Node $AllNodes.Where({ $_.Role -contains 'Web' }).NodeName {
WindowsFeature 'Web-Server' { Name='Web-Server'; Ensure='Present' }
xWebAppPool 'AppPool_Main' {
Name = 'site-main'
startMode = 'AlwaysRunning'
managedRuntimeVersion = 'v4.0'
identityType = 'ApplicationPoolIdentity'
}
xWebSite 'MainSite' {
Name = 'MainSite'
State = 'Started'
PhysicalPath = 'C:\Sites\Main'
BindingInfo = @(
MSFT_xWebBindingInformation{
Protocol='https'; Port=443; HostName=$Node.SiteFQDN; CertificateThumbprint=$Node.SiteCertThumb; CertificateStoreName='My'
}
)
ApplicationPool = 'site-main'
DependsOn = '[WindowsFeature]Web-Server','[xWebAppPool]AppPool_Main'
}
}
}
4.3 监控/Agent 模块:比如安装自研监控或 Telegraf/OMS(按你们合规情况替换)。
5. ConfigurationData:按区域/角色装配
ConfigData.psd1 用于为不同区域赋予不同变量(时区、站点域名、本地管理员等),MOF 时再渲染。
@{
AllNodes = @(
@{ NodeName='HK-WEB-01'; Region='HK'; Role=@('Web'); ComputerName='HK-WEB-01'; TimeZone='China Standard Time'; LocalAdmins=@('DOMAIN\ops-hk'); SiteFQDN='www.hk.example.com'; SiteCertThumb='ABCD1234...' },
@{ NodeName='SG-WEB-01'; Region='SG'; Role=@('Web'); ComputerName='SG-WEB-01'; TimeZone='Singapore Standard Time'; LocalAdmins=@('DOMAIN\ops-sg'); SiteFQDN='www.sg.example.com'; SiteCertThumb='EFGH5678...' },
@{ NodeName='TYO-API-01'; Region='TYO';Role=@('API'); ComputerName='TYO-API-01'; TimeZone='Tokyo Standard Time'; LocalAdmins=@('DOMAIN\ops-tyo') },
@{ NodeName='SJC-EDGE-01';Region='SJC';Role=@('Edge'); ComputerName='SJC-EDGE-01'; TimeZone='Pacific Standard Time'; LocalAdmins=@('DOMAIN\ops-us') }
);
NonNodeData = @{
PullServerUrl = 'https://dsc.hk.example.com/PSDSCPullServer.svc'
ReportServerUrl = 'https://dsc.hk.example.com/PSDSCComplianceServer.svc'
RegistrationKey = '放空,节点 LCM 自带读取本机 GUID/Key 文件'
}
}
6. 编译 MOF:把“意图”变成“状态文件”
我将 Baseline 与 WebRole 分开编译,再用 Partial Configuration(可选)组合;这里演示简单的单配置合成:
. .\Baseline.ps1
. .\WebRole.ps1
$ConfigData = Import-PowerShellDataFile .\ConfigData.psd1
configuration HK_Fleet
{
Import-DscResource -ModuleName PSDscResources, xWebAdministration, ComputerManagementDsc, NetworkingDsc
Baseline
WebRole
}
HK_Fleet -ConfigurationData $ConfigData -OutputPath .\out\mof
生成的 .\out\mof\HK-WEB-01.mof 等文件会被 Pull 服务器按 ConfigurationID(GUID) 识别。生产里我会将节点名映射成固定 GUID(注册时回传),避免重装后重复绑定。
7. 配置 LCM(跨区域频率、代理、报告)
每台目标节点都需要一份 LCM Meta-Configuration(meta.mof),指明 Pull/Report 地址、刷新频率、纠偏策略等。
[DSCLocalConfigurationManager()]
configuration SetLCM
{
param(
[Parameter(Mandatory)][string]$PullUrl,
[Parameter(Mandatory)][string]$ReportUrl
)
Node localhost {
Settings {
RefreshMode = 'Pull'
ConfigurationMode = 'ApplyAndAutoCorrect' # 偏离自动纠正
ConfigurationModeFrequencyMins = 30 # 纠偏频率
RefreshFrequencyMins = 15 # 拉取频率(跨区别设太小)
RebootNodeIfNeeded = $true
AllowModuleOverwrite = $true
}
ConfigurationRepositoryWeb PullSrv {
ServerURL = $PullUrl
RegistrationKey = (Get-Content 'C:\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt')
ConfigurationNames = @('HK_Fleet') # 或使用 ConfigurationID(GUID)
}
ReportServerWeb ReportSrv {
ServerURL = $ReportUrl
}
}
}
# 若美国区必须走企业代理,先设置 WinHTTP 代理(LCM 遵循 WinHTTP)
# netsh winhttp set proxy proxy.corp:8080 bypass-list="*.example.com"
SetLCM -PullUrl 'https://dsc.hk.example.com/PSDSCPullServer.svc' -ReportUrl 'https://dsc.hk.example.com/PSDSCComplianceServer.svc' -OutputPath .\out\meta
Set-DscLocalConfigurationManager -Path .\out\meta -Verbose
参数经验值:跨区建议 RefreshFrequencyMins=30 起步,ConfigurationModeFrequencyMins=60,并针对高延迟区(SJC)放宽;初次批量下发可临时缩短,稳定后回调。
8. 将 MOF 与模块发布到 Pull 服务器
Pull 服务器上有两个核心目录(默认):
配置:C:\Program Files\WindowsPowerShell\DscService\Configuration
模块:C:\Program Files\WindowsPowerShell\DscService\Modules
每个文件都需要对应的 校验文件(.checksum)。我用以下脚本自动发布:
$Out = "C:\repo\out\mof"
$CfgDir = "C:\Program Files\WindowsPowerShell\DscService\Configuration"
Copy-Item "$Out\*.mof" $CfgDir -Force
Get-ChildItem $CfgDir -Filter *.mof | ForEach-Object { New-DscChecksum -Path $_.FullName -OutPath $CfgDir -Force }
# 模块打包:将模块目录压成 {Name}_{Version}.zip
$ModSrc = "C:\repo\modules"
$ModDst = "C:\Program Files\WindowsPowerShell\DscService\Modules"
Get-ChildItem $ModSrc -Directory | ForEach-Object {
$name = $_.Name
$ver = (Get-Module -ListAvailable $name | Sort-Object Version -Descending | Select-Object -First 1).Version.ToString()
$zip = Join-Path $ModDst "$name_$ver.zip"
if (Test-Path $zip) { Remove-Item $zip -Force }
Add-Type -AssemblyName 'System.IO.Compression.FileSystem'
[System.IO.Compression.ZipFile]::CreateFromDirectory($_.FullName, $zip)
New-DscChecksum -Path $zip -OutPath $ModDst -Force
}
9. 首次应用与合规验证
在节点上:
# 立即触发拉取
Update-DscConfiguration -Wait -Verbose
# 查看最近 10 次状态
Get-DscConfigurationStatus -All
# 漂移检测(不应用)
Test-DscConfiguration -Detailed
在 Pull/Report 服务器:(IIS 日志 + 合规 API)
# 拉取某节点的最新报告(示意)
# 报告是 OData,可用 Invoke-RestMethod 调 Report URL
$reportUrl = 'https://dsc.hk.example.com/PSDSCComplianceServer.svc/Nodes(ComputerName=''SG-WEB-01'')/Reports'
Invoke-RestMethod -Method Get -Uri $reportUrl -UseDefaultCredentials
10. 版本管理与 CI/CD
我把 DSC 代码与配置数据放在 Git:
/dsc
/composites # 组合资源(Baseline、WebRole、Agent)
/modules # 内部镜像模块
/configs # *.ps1(configuration 入口)
/data # *.psd1(ConfigData)
/out # 编译输出(gitignore)
build.ps1 # 编译 & 单元测试 & 发布
build.ps1(精简版):
# 1) Pester 测试组合资源的参数与默认值(略)
# 2) 编译
.\configs\Build-All.ps1
# 3) 生成校验 & 发布到 Pull(仅在 main 分支 + tag 触发)
if ($env:CI -and $env:GIT_TAG) { .\scripts\Publish-ToPull.ps1 }
关键模块版本(供审计)
| 模块 | 版本 | 用途 |
|---|---|---|
| PSDscResources | 2.12.x | File/Archive/Group/Service 等基础资源 |
| xWebAdministration | 3.3.x | IIS 站点、AppPool、Binding |
| ComputerManagementDsc | 9.x | 本地策略、时区、用户/组 |
| NetworkingDsc | 9.x | NIC、IP、DNS、Firewall |
| xCertificate | 3.x | 证书部署/信任链 |
11. 现场遇到的“坑”与修复手记
节点无法从 Pull 拉取,报 TLS/证书错误
现象:Update-DscConfiguration 报 The underlying connection was closed: Could not establish trust relationship...
排查:curl https://dsc.hk.example.com 正常,但在美西区失败。
根因:美西到香港的防火墙未放行内部 CRL/OCSP 地址,证书链校验超时。
修复:
在边缘区临时导入完整链并设置 certutil -setreg chain\ChainCacheResyncFiletime @now;
网络放行 CRL/OCSP;
最终将 Pull 站点证书切换为 公共可信 CA,跨区不再依赖内网 CRL。
LCM 不走用户代理,拉配置超时
现象:SJC 节点出站必须走企业代理,但 LCM 不遵循 IE 代理。
修复:设置 WinHTTP 代理:
netsh winhttp set proxy proxy.corp:8080 bypass-list="*.example.com";并验证 netsh winhttp show proxy。之后 LCM 拉取正常。
模块版本不一致导致“隐形漂移”
现象:同一个 Composite 在不同区行为不同。
根因:Pull 服务器的 Modules 中同时存在 xWebAdministration_3.2 与 3.3,部分节点拿到了旧版。
修复:强制在配置中 pin 版本,并清理旧 zip;加上 模块校验 Pester 测试。
ApplyAndAutoCorrect 触发频繁重启
现象:IIS 角色频繁被“纠偏”重启 AppPool。
修复:对“易变”对象(日志轮转、缓存文件)改用 Script/File 的 MatchSource 策略,只做存在性/权限校验,不做内容强更;把 ConfigurationModeFrequencyMins 调整为 60。
PSGallery 被拦截,依赖无法下载
修复:完全改为 内网 NuGet 源;Register-PSRepository -Name Internal -SourceLocation http://nuget.hk.example.com/nuget -InstallationPolicy Trusted;CI 将模块打包入 Modules。
12. 性能与带宽侧写(上线观测)
| 区域 | 首次应用耗时(平均) | 后续纠偏(平均) | 峰值带宽(拉包) | 备注 |
|---|---|---|---|---|
| 香港 | 3m20s | 20–40s | 120 Mbps | 本地拉取 |
| 新加坡 | 4m10s | 30–50s | 80 Mbps | 35ms 延迟 |
| 东京 | 4m40s | 35–55s | 75 Mbps | 50ms 延迟 |
| 美西 | 6m30s | 45–70s | 60 Mbps | 130ms 延迟,代理 |
调优要点:首更阶段分批(10 台一组),模块压缩包按需拆分;稳定运行后延长 RefreshFrequencyMins,合规报告保留 30 天滚动。
13. 运营与回滚
变更窗口:灰度到 SG→TYO→SJC→HK;每批 10–20 台。
回滚:Git tag 回滚到上一个 MOF + Modules 版本,重新生成 checksum;节点自动回退。
取证:Report Server 报表 + 节点 Get-DscConfigurationStatus -All 导出到 SIEM。
14. FAQ(我踩过的几个问号)
域加/脱域能否走 DSC? 能,xComputer/Computer 支持,但请把域凭据用证书加密(PsDscRunAsCredential)并限制只在安全 VLAN 执行。
混合云(部分在公有云)怎么做? Pull 地址走公网域名 + 公共 CA 证书,安全靠 mTLS(需自定义)或在云侧开专线/VPN;报告同理。
Azure Automation DSC 要不要用? 规模更大/全球多点、且你们有 Azure 经验时非常香;但我这套在纯自建环境里可控性更强。
15. 从凌乱到一致:DSC 带来的改变
变更窗口结束时,合规面板全绿。我把椅子往后一靠,听到机房里风道的低鸣——那是所有服务器在同一节拍呼吸的声音。
这一次,我们没有靠记忆和个人习惯去拼“差不多”,而是把“系统意图”写进了代码:无论是香港的主集群,还是东京的夜班节点,抑或远在太平洋彼岸的边缘机,都在以同一种方式被构建、被检查、被纠偏。
后来新同事问我,“跨区域一致性最难的是什么?” 我想了想:不是技术,而是克制——克制在每台机器上“手动一把”的冲动,把一切回到 DSC 的声明式世界里。因为只有那样,凌晨两点的你,才配得上一个稳定的夜晚。
附:完整最小可用样例(可直接复刻到实验环境)
实验假设:单 Pull/Report 在香港;目标 2 台(HK-WEB-01、SG-WEB-01);只下发 Baseline + 简易 IIS 站点。
香港 Pull:按第 3 节安装并绑定证书。
在你的管理机(可就是 Pull 机)准备文件:
Baseline.ps1 / WebRole.ps1(上文示例)
ConfigData.psd1(按你的主机名/证书修改)
Build.ps1(编译+发布)
# Build.ps1
. .\Baseline.ps1
. .\WebRole.ps1
$cd = Import-PowerShellDataFile .\ConfigData.psd1
configuration HK_Fleet { Import-DscResource -ModuleName PSDscResources, xWebAdministration, ComputerManagementDsc, NetworkingDsc; Baseline; WebRole }
HK_Fleet -ConfigurationData $cd -OutputPath .\out\mof
.\Publish-ToPull.ps1
在目标节点执行 LCM 配置(第 7 节脚本,换成你的 Pull/Report URL)。
Update-DscConfiguration -Wait -Verbose,观察站点创建、证书绑定、组策略应用。
用 Test-DscConfiguration 验证漂移,用 Get-DscConfigurationStatus 看历次结果