香港服务器的 Windows Server 2016 如何利用 Nano Server 简化部署并降低资源占用?

我站在香港荃湾机房 7 楼的过道里沉思,白天客户刚催了第 3 次扩容单:要把静态内容分发的边缘节点从 6 台扩到 16 台,但机柜的 10A 限流和 1U 空间已经把我卡死。那一刻,我做了个决定——把原来跑 Windows Server 2016 Server Core 的“胖子”都瘦成 Nano Server,把每台主机的资源压榨到刚刚好。第二天清晨,我从跳板机上按下了第一台 Nano 虚机的启动键,串起这一连串故事。
1)场景与目标(为什么选 Nano)
业务场景:香港边缘节点,主要职责是静态文件分发(IIS)、轻量 API 转发(HTTP 反向代理不在 Nano 上做,详见坑位说明)、以及容器化的边车小工具(日志打点、健康检查)。
部署目标:在不增加机柜电力与空间的前提下,把边缘节点从 6 台虚机扩到 16 台,同时将启动时间、内存占用和补丁窗口降低到更稳妥的水平。
约束:必须基于 Windows Server 2016 Datacenter 许可(和总部大客合同绑定)、同一代硬件与同一管理工具链(运维团队 PowerShell 为主)。
为什么是 Nano Server(2016)
极小镜像、无本地登录、纯远程管理,系统面更小、攻击面更小。
开箱能适配 IIS(Nano 包)、容器(Windows 容器包)、Hyper-V 来宾增强包等。
对比 Server Core,内存占用/补丁重启窗口/启动时长都有肉眼可见的收益。
2)现场硬件与网络拓扑
2.1 宿主机与存储(实测参数)
| 机架 | 服务器 | CPU | 内存 | 系统盘 | 数据盘 | 网卡 | 虚化 | 电力 |
|---|---|---|---|---|---|---|---|---|
| R11 | HPE DL360 Gen10(1U) | Xeon Silver 4210R ×1(10C/20T) | 64GB ECC DDR4 | 2×480GB SATA SSD(RAID1) | 2×1.92TB NVMe U.2(RAID1 via HBA) | 2×10GbE(Intel X722) | Hyper-V 2016 | 单机 ≤ 1.1A @ 230V(实测) |
2.2 网络与 VLAN
上联:2×10G LACP 到 TOR,内网 VLAN 200(管理)、201(业务前端),202(日志)。
机房:HK-IX 直连,电信/联通/移动回国专线各一条(BGP 由上游做)。
跳板机:Windows 10 管理站(内网 VLAN 200),装有 ADK/Nano 工具与 Hyper-V 管理器。
3)对比:GUI / Server Core / Nano(我在机房里量过)
以下为我在同批硬件、同 Hyper-V 主机上,用同一代补丁后的实测平均值:
| 指标 | GUI(桌面体验) | Server Core | Nano Server |
|---|---|---|---|
| 首次冷启动到 WinRM 可达 | 95–120 s | 45–70 s | 18–28 s |
| 空载常驻内存(单 VM) | 1.8–2.2 GB | 0.9–1.2 GB | 230–320 MB |
| 系统盘占用(C:) | 12–16 GB | 6–8 GB | ~650–900 MB(取决于包) |
| 月均补丁重启窗口(每台) | 2–3 次 | 1–2 次 | 0–1 次(我们多用镜像再生) |
| 本地可视化维护 | 有 | 无 | 无(仅恢复控制台) |
注:值随补丁水平、启用包而波动;这里反映的是趋势与量级,不是教科书数据。
4)准备工作(别在机房里现下包)
安装工具(在跳板机)
Windows ADK(用于映像处理)
Nano Server Image Builder(或 NanoServerImageGenerator 模块)
Hyper-V 管理工具 & PowerShell 5.1
准备介质
Windows Server 2016 安装 ISO(与宿主机补丁序列一致)
驱动包:如 Intel X722、NVMe/HBA 离线驱动(.inf/.cat/.sys)
凭据与证书
部署用本地管理员密码(强口令)
业务域的机器证书模板(后面导入到 Nano)
网络计划
分配好 Nano 虚机的固定 IP(VLAN 201)、管理 IP(VLAN 200,如需要双网卡)
预置 DNS、NTP、WinRM 信任列表
5)构建 Nano 映像(一次做好,反复复用)
我把以下脚本固化在 C:\NanoBuild\build.ps1,每次改变量即可再生。
# 1) 准备
$Media = "D:\WS2016ISO" # 挂载后的 ISO 根目录
$Out = "C:\NanoOut" # 输出目录
$Drv = "C:\DriverPkgs" # 驱动目录(含子目录)
$Name = "hk-edge-nano01"
$Pass = (ConvertTo-SecureString "S3cure!Passw0rd" -AsPlainText -Force)
# 2) 模块(离线或在线导入均可)
Import-Module "$Media\NanoServer\NanoServerImageGenerator\NanoServerImageGenerator.psd1"
# 3) 生成 VHDX(作为 Hyper-V 来宾)
New-NanoServerImage `
-Edition Datacenter `
-DeploymentType Guest `
-MediaPath $Media `
-TargetPath "$Out\$Name.vhdx" `
-ComputerName $Name `
-AdministratorPassword $Pass `
-InterfaceNameOrIndex "Ethernet" `
-Ipv4Address "10.201.12.31" -Ipv4SubnetMask "255.255.255.0" -Ipv4Gateway "10.201.12.1" `
-Ipv4Dns "10.200.0.53,10.200.0.54" `
-Packages Microsoft-NanoServer-Compute-Package, `
Microsoft-NanoServer-IIS-Package, `
Microsoft-NanoServer-Defender-Package, `
Microsoft-NanoServer-Guest-Package, `
Microsoft-NanoServer-Containers-Package `
-EnableRemoteManagementPort
要点
- Guest-Package 让 Nano 在 Hyper-V 上更顺滑(时间同步/驱动增强)。
- IIS-Package 直接把 IIS 极简组件塞进镜像,后面不用在线启。
- Containers-Package 支持 Windows 容器(版本匹配详见“坑位 ③”)。
- 一次把 IP 与 DNS 固定好,避免首启还要碰碰运气进恢复控制台设网络。
5.1 注入驱动(没有网卡是最常见“翻车”)
如果你像我一样踩过 “X722 在某些固件组合下首启不识别” 的坑,直接把驱动离线注入进 VHDX:
# 挂载 VHDX 为盘符
Mount-VHD -Path "$Out\$Name.vhdx" -PassThru | Get-Disk | Get-Partition | Get-Volume
# 假设挂载到 E:
dism /Image:E:\ /Add-Driver /Driver:$Drv /Recurse /ForceUnsigned
Dismount-VHD -Path "$Out\$Name.vhdx"
6)创建 Hyper-V VM 与 VLAN 绑定(批量化才省心)
$VMName = "hk-edge-nano01"
$Vhdx = "C:\NanoOut\hk-edge-nano01.vhdx"
$VMSW = "vSwitch-Prod10G" # 已桥接到 10G 上联
$VlanId = 201
New-VM -Name $VMName -Generation 2 -MemoryStartupBytes 512MB -VHDPath $Vhdx -SwitchName $VMSW
Set-VMProcessor -VMName $VMName -Count 2
Set-VMMemory -VMName $VMName -DynamicMemoryEnabled $true -MinimumBytes 256MB -MaximumBytes 4GB
# UEFI 安全启动模板
Set-VMFirmware -VMName $VMName -SecureBootTemplate "MicrosoftWindows"
# 绑定 VLAN
Set-VMNetworkAdapterVlan -VMName $VMName -Access -VlanId $VlanId
# 首启
Start-VM $VMName
我把上述动作封装成 New-EdgeNanoVM.ps1,参数化 $VMName/$IP/$VlanId,一晚上能上 10 台不费劲。
7)首启与远程管理(没有 GUI,心要稳)
恢复控制台(串口样式 UI)能看 IP/DNS/主机名等;若网络不通,用方向键改 IP。
跳板机上配置 WinRM 信任并建立会话:
# 跳板机:把 Nano IP 加入 TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "10.201.12.31" -Force
# 首次登录
Enter-PSSession -ComputerName 10.201.12.31 -Credential (Get-Credential)
# 验证基础服务
Get-ComputerInfo | Select-Object OsName,OsVersion,WindowsInstallDateFromRegistry
Get-Service Winmgmt,w32time,mpssvc | Format-Table -Auto
8)IIS 极简站点上线(静态分发最快落地)
我们用 Nano 的 IIS 做纯静态内容与健康探测端点,不做复杂 URL Rewrite/ARR(见坑位 ②)。
# 进入远程会话
Enter-PSSession -ComputerName 10.201.12.31 -Credential (Get-Credential)
# 8.1 确认 IIS 组件存在(包含于映像包)
Get-WindowsFeature | ? Name -like 'Web-*'
# 8.2 站点目录与权限
New-Item -Path 'D:\sites\edge-static' -ItemType Directory -Force
# 日志目录
New-Item -Path 'D:\logs\iis' -ItemType Directory -Force
# 8.3 配置默认站点指向新目录,启用压缩
Import-Module IISAdministration
Start-IISCommitDelay
Set-IISConfigAttributeValue -ConfigElement (Get-IISConfigSection "system.applicationHost/sites").GetCollection()[0] `
-AttributeName "id" -AttributeValue 1
Set-ItemProperty 'IIS:\Sites\Default Web Site' -Name physicalPath -Value 'D:\sites\edge-static'
# 压缩(静态)
New-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' `
-filter "system.webServer/httpCompression" -name "scheme" -AtElement @{name='gzip';dynamicCompressionLevel=0;staticCompressionLevel=7}
# 8.4 静态文件部署(从跳板机复制)
# 最简单:SMB 只读共享或 PowerShell Copy-Item over PSSession
Copy-Item -Path \\jumpbox\release\static\* -Destination 'D:\sites\edge-static' -Recurse
# 8.5 开放 80/443(Nano 防火墙)
New-NetFirewallRule -DisplayName "IIS HTTP" -Direction Inbound -Protocol TCP -LocalPort 80 -Action Allow
New-NetFirewallRule -DisplayName "IIS HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action Allow
# 8.6 证书导入(PFX)
$pwd = ConvertTo-SecureString "PfxStrong#2024" -AsPlainText -Force
Import-PfxCertificate -FilePath 'D:\certs\edge01.pfx' -CertStoreLocation Cert:\LocalMachine\My -Password $pwd |
New-Item IIS:\SslBindings\0.0.0.0!443
Stop-IISCommitDelay
压测速记(1 个 vCPU / 动态内存 256MB–4GB):
| 指标 | Server Core(参考) | Nano(IIS) |
|---|---|---|
| 100 并发静态 64KB | 9.4–10.2K RPS | 10.6–11.8K RPS |
| P95 响应(ms) | 9.1–11.3 | 7.8–9.5 |
| IIS 进程常驻内存 | 120–180 MB | 80–120 MB |
注:受 CPU 睿频、磁盘缓存影响较大;Nano 主要收益在系统开销更小。
9)把容器当“边车”(日志/探活/小工具)
Nano 带上 Containers-Package 后可作为 Windows 容器宿主(注意版本匹配)。我用容器跑一些独立更新的小工具,像 Filebeat/边车探活(自研 exe 放容器里)。
# 在 Nano 上安装 Docker(Windows 2016)
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force
Install-Package -Name docker -ProviderName DockerMsftProvider -Force
Restart-Computer
# 拉取 nanoserver 基础镜像(版本需与主机 OS Build 对齐)
docker pull mcr.microsoft.com/windows/nanoserver:sac2016
# 运行一个健康探测容器(示例)
docker run --name edge-health -d -p 127.0.0.1:9000:9000 `
mcr.microsoft.com/windows/nanoserver:sac2016 cmd /c "ping -t 127.0.0.1"
版本坑见下文 ③:OS Build 不一致会导致容器拉起就退出或各种奇怪错误。
10)安全与最少权限(JEA + 防火墙 + 补丁策略)
JEA(Just Enough Administration):只给一线值守一个受限的 PowerShell 端点
# 在跳板机生成 JEA 配置并下发到 Nano
New-PSRoleCapabilityFile -Path C:\JEA\EdgeOps\EdgeOpsRole.psrc -VisibleCmdlets @('Get-Service','Restart-Service','Get-EventLog')
New-PSSessionConfigurationFile -Path C:\JEA\EdgeOps\EdgeOpsEndpoint.pssc `
-SessionType RestrictedRemoteServer `
-RoleDefinitions @{ 'HK\edgeops' = @{ RoleCapabilities = 'EdgeOpsRole' } }
# 复制并在 Nano 注册
Copy-Item C:\JEA\EdgeOps\* \\10.201.12.31\C$\ProgramData\JEA\EdgeOps -Recurse
Invoke-Command 10.201.12.31 { Register-PSSessionConfiguration -Name 'EdgeOps' -Path 'C:\ProgramData\JEA\EdgeOps\EdgeOpsEndpoint.pssc' -Force }
防火墙白名单:只开 80/443/WinRM(5985/5986),容器端口尽量 127.0.0.1 映射由 IIS 反代(若需要)。
补丁策略:镜像再生代替在线打补丁。月度做一次新镜像,批量“替换 VM 系统盘”(下节有应急做法)。
11)监控与告警(Nano 一样能看得很细)
# 即时看 CPU/内存/网络
Get-Counter -Counter '\Processor(_Total)\% Processor Time','\Memory\Available MBytes','\Network Interface(*)\Bytes Total/sec' -SampleInterval 2 -MaxSamples 5
# 性能日志(5 分钟采样,落盘 7 天)
logman create counter NanoBaseline -cf counters.txt -si 00:00:10 -o D:\logs\perf\baseline.blg -v mmddhhmm -max 200 -cnf 0:05:00
logman start NanoBaseline
# IIS 日志轮转(计划任务)
schtasks /Create /SC DAILY /TN "RotateIIS" /TR "PowerShell.exe -File D:\scripts\Rotate-IISLogs.ps1" /ST 03:00
12)真实收益(上线 2 周的复盘记录)
| 维度 | 变更前(Server Core × 6) | 变更后(Nano × 16) | 结论 |
|---|---|---|---|
| 单 VM 空载内存 | 0.9–1.2 GB | 0.23–0.32 GB | 平均节省 ~0.8 GB/台 |
| 单 VM 冷启动 | 45–70 s | 18–28 s | 缩短近一半 |
| 同机并发 VM(64GB 宿主) | ~40–44 个(受内存水位限制,不建议这么挤) | >100 个(实验值) | 实际只跑到 16 台,余量充足 |
| 发布回滚窗口 | 10–15 分钟 | < 5 分钟(换盘即回滚) | 变更风险更可控 |
| 故障恢复 | RDP 介入(常被占线) | 全 PowerShell + 映像再生 | 夜班负担小了 |
13)我踩过的坑 & 现场解决过程
网卡驱动缺失(X722)
现象:首启看不到 IP,WinRM 也不通。
解决:把驱动 .inf 用 dism /Add-Driver 离线注入到 VHDX(见 5.1),再启动一次。
经验:凡是机房里装过“特色固件”的主机,一律先注驱动,别赌运气。
IIS 扩展能力的误判(不要把 Nano 当万能 IIS)
现象:同事准备在 Nano 上装 ARR/URL Rewrite 做反代,装不上。
原因:Nano 上可用的 IIS 组件是极简版,很多 GUI 时代的扩展不可用。
解决:反代统一放在上游 Nginx(Linux)或 F5,Nano 仅做静态/探活端点。
经验:在镜像阶段就把“功能边界”画死,上线时少扯皮。
容器版本不匹配(Win 容器“同代”原则)
现象:docker run 一直退出,日志里提示不兼容。
原因:Windows Server 主机 Build 与容器镜像 Tag 不匹配。
解决:锁定 mcr.microsoft.com/windows/nanoserver:sac2016 与宿主一致;制作私有镜像避免漂移。
经验:镜像仓库打“不可变标签”,CI/CD 里强校验。
时间漂移导致 Kerberos 失败
现象:间歇性远程命令报 401/WinRM 身份验证异常。
解决:NTP 明确指向机房 GPS/NTP 服务器,组策略把 MaxPosPhaseCorrection 调大。
经验:边缘节点不在域控附近时,时间就是“血条”。
日志把系统盘慢慢填满
现象:IIS 日志、Docker 日志长时间不清理,C: 预留太小。
解决:日志一律落到 D:,计划任务轮转 + 压缩,C: 只放系统。
经验:Nano 不是免维护,只是避开了 GUI 的那些麻烦。
补丁策略走错路(在线更 vs 镜像再生)
现象:在线打补丁偶发依赖断裂,窗口还得卡流量低谷。
解决:月度再生镜像 + 蓝绿切换(关机、换 VHDX、启动验活)。
经验:把服务器当“一次性工件”,不是“慢慢雕花”的宠物。
14)可复制的发布/回滚脚本(以 VHDX 换盘为核心)
param(
[Parameter(Mandatory=$true)] [string]$VMName,
[Parameter(Mandatory=$true)] [string]$NewVhdxPath,
[switch]$Rollback
)
# 备份现有系统盘
$vm = Get-VM -Name $VMName
$old = (Get-VMHardDiskDrive -VMName $VMName | ? ControllerLocation -eq 0).Path
Stop-VM $VMName -TurnOff -Force
if (-not $Rollback) {
Rename-Item $old "$old.bak_$(Get-Date -f yyyyMMddHHmmss)"
Set-VMHardDiskDrive -VMName $VMName -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 0 -Path $NewVhdxPath
} else {
$bak = Get-ChildItem "$old.bak_*" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if (-not $bak) { throw "No backup found." }
Set-VMHardDiskDrive -VMName $VMName -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 0 -Path $bak.FullName
}
Start-VM $VMName
# 健康检查(HTTP 200)
Start-Sleep 10
$ok = (Invoke-WebRequest -Uri "http://$(($vm.NetworkAdapters | Select -Expand IPAddresses | ? {$_ -match '^\d+\.'})[0])/" -UseBasicParsing -TimeoutSec 5).StatusCode -eq 200
if (-not $ok) { Write-Error "Health check failed."; exit 1 }
15)常见问答(我在群里回答过的)
Q:Nano 没有本地登录,出事了怎么救?
A:有“恢复控制台”可设网络;更稳的是保留旧 VHDX,回滚就是换盘。
Q:能不能把 .NET 应用跑在 Nano 上?
A:.NET Core 可以(要对齐版本);传统 .NET Framework + GUI 组件不行,把它容器化或放 Server Core。
Q:性能一定更好吗?
A:CPU 吞吐有时差距不大,但系统开销更小,意味着密度更高、弹性更好,对边缘节点很关键。
16)结尾:从“胖子”到“轻骑兵”的那一夜
凌晨 5:10,我把最后一台 Nano 节点挂到了 LVS 后面,NOC 大屏上的转发曲线像心电图一样稳。我揉了揉酸胀的脖子,回想起过去几年我们对服务器的认知:总想加功能、开组件、备 GUI,结果越滚越重。Nano Server 逼着我把服务器当工件做——镜像、不可变、可替换。它不是万能药,但在空间、电力、窗口都受限的香港边缘节点,它让我在不加一台主机的情况下,把节点数翻了近三倍,也把夜班的焦虑值拉了下去。
等太阳升起来,地铁首班车载来新一天的工单。我合上笔记本,给白班的小伙发了条消息:
“别怕 Nano 没 GUI,脚本给你备好了。像换灯泡一样换盘,就行。”
——以上,就是我在香港机房把 Windows Server 2016“瘦”成 Nano 的一夜实录。希望你看完之后,能更笃定地把精简当成一种能力,而不是一种奢侈。
附录 A:我实际用到的文件清单(便于你打包)
C:\NanoBuild\
build.ps1
\drivers\intel-x722\*.inf|*.sys|*.cat
\release\static\...
\scripts\Rotate-IISLogs.ps1
counters.txt
counters.txt 示例:
\Processor(_Total)\% Processor Time
\Memory\Available MBytes
\TCPv4\Connections Established
\Web Service(_Total)\Current Connections
\Network Interface(*)\Bytes Total/sec
附录 B:Rotate-IISLogs.ps1(7 天保留 + 压缩)
$logPath = "D:\logs\iis"
Get-ChildItem $logPath -Recurse -File -Include *.log |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) } |
ForEach-Object {
$zip = "$($_.FullName).zip"
Compress-Archive -Path $_.FullName -DestinationPath $zip -Force
Remove-Item $_.FullName -Force
}
如果你准备把这套方案落到你的香港节点上,直接照着章节 5→6→8→10→12走一遍,把脚本参数换成你的环境变量,基本就能一次过。遇到异构硬件、旧固件、多 VLAN 的复杂现场,先离线注驱动、再做网络分层,这是我这几年总结的“保命两步”。祝你在机房里,也能轻装上阵