香港服务器如何利用Docker与KVM虚拟化实现Windows与Linux混合部署,提高硬件利用率?

我在香港机房接手一台“吃灰”的单机:CPU 占用常年 5%~15%,内存 20% 左右,硬盘却不时打满,Windows 服务得跑、Linux 的容器也离不开。目标很明确:在不更换硬件的前提下,把这台机器的硬件利用率榨干到 60%+,同时保证可维护、可回滚、可观察。下面是我的完整实操过程与踩坑记录。
香港机房现场环境 & 目标
硬件实况(到机房后的第一件事就是核对序列号与部件)
| 类别 | 型号 / 参数 | 备注 |
|---|---|---|
| 机型 | 1U 单路服务器 | 远程 iKVM 可用 |
| CPU | Intel Xeon Silver 4210R(10C20T)×1 | AVX2,支持 VT-x/VT-d |
| 内存 | 128GB DDR4 ECC | 4×32GB,NUMA=1 |
| 系统盘 | 2×480GB SATA SSD 做 RAID1 | mdadm |
| 数据盘 | 2×1.92TB NVMe(U.2) | 计划给 VM 与容器数据 |
| 网卡 | 2×10GbE(Intel X710) | 支持 SR-IOV |
| 带宽 | 上下行 1Gbps,国际优化线路 | 机房直连大陆回程优化 |
| 电源 & 散热 | 550W 冗余 | 机房常年 23℃ |
业务目标
- 在同一宿主机上同时跑 Windows 与 Linux。
- Linux 侧跑 Docker(Web/API/队列等),Windows 侧跑 .NET/office 组件、特定商业软件。
- 资源隔离(CPU/内存/IO),网络分区(公网/内网/后端存储),可观测(Prometheus + 日志)。
- 10 分钟内可回滚(快照/模板化)。
- 宿主机尽量干净,不直接跑业务容器(把容器放到 Linux 虚机里)。
注:我这次用的是 CentOS 7 作为 KVM 宿主(因为历史包袱和变更成本),但它已到 EOL。如果你没有历史负担,推荐 Rocky 9 / Alma 9 / Debian 12 做宿主,拿到更新的内核与 QEMU。下面会在关键步骤给出两套命令对照。
1. 架构设计:一图看懂
[Internet] ──> [10GbE NIC0] ─ Linux Bridge br0 ─ VM-Windows(virtio-net)
│
├─ VM-Linux-01(Docker stack A)
└─ VM-Linux-02(Docker stack B)
[Storage/LAN] ─> [10GbE NIC1] ─ br1(VLAN trunk: 10/20/30)
│
├─ VM-Windows(SR-IOV VF,可选)
└─ 备份/监控/内网服务
- 计算层:KVM/libvirt,Windows 与 Linux VM 并存;Linux VM 内再跑 Docker。
- 网络层:双 10GbE,br0 公网,br1 内网(VLAN Trunk);关键 VM 可用 SR-IOV 直接拿 VF。
- 存储层:系统 RAID1;NVMe 做 LVM-Thin(给 VM 磁盘)+ XFS(给容器数据卷)。
- 调度层:CPU pinning、HugePages、iothreads、磁盘多队列、virtio-scsi。
2. 宿主机安装与基础优化
2.1 BIOS/固件
开启 Intel VT-x/VT-d、SR-IOV、AES-NI。
关 C-states 过深(保留 C1E),CPU 频率模式选 Performance。
NVMe 固件升级到官方 LTS 版本(避免写入抖动)。
2.2 系统安装要点
CentOS 7(EOL 环境,仅示例)
# 基础组件
yum install -y epel-release
yum install -y vim htop lsof tuned sysstat irqbalance wget git tmux
# KVM/Libvirt
yum install -y qemu-kvm libvirt libvirt-daemon-kvm libvirt-client virt-install \
bridge-utils pciutils
systemctl enable --now libvirtd
# 网桥(示例,实际网口名以 ip link 为准)
nmcli con add type bridge ifname br0
nmcli con add type bridge-slave ifname eno1 master br0
nmcli con add type bridge ifname br1
nmcli con add type bridge-slave ifname eno2 master br1
nmcli con up br0; nmcli con up br1
Rocky/Alma 9(推荐)
dnf install -y qemu-kvm libvirt virt-install virt-top virt-viewer bridge-utils \
tuned sysstat pciutils NetworkManager
systemctl enable --now libvirtd
nmcli con add type bridge ifname br0; nmcli con add type bridge-slave ifname eno1 master br0
nmcli con add type bridge ifname br1; nmcli con add type bridge-slave ifname eno2 master br1
nmcli con up br0; nmcli con up br1
2.3 CPU/内存优化(通用)
# HugePages:给 VM 预留大页,示例 16GB(2MB x 8192)
echo 'vm.nr_hugepages=8192' > /etc/sysctl.d/99-hugepages.conf
sysctl -p /etc/sysctl.d/99-hugepages.conf
# 绑中断,减少抖动(粗略示例,结合 /proc/interrupts 调整)
systemctl enable --now irqbalance
# tuned(禁用省电,拉满性能)
tuned-adm profile latency-performance
3. 存储布局:既要快也要稳
3.1 分层思路
系统盘 RAID1:/、/boot、/var/log,方便换盘。
NVMe:
vg_vm/thinpool:给 VM 磁盘(qcow2 on LVM-thin 或直接 raw/thin)
xfs 分区:给 Docker 卷(Linux VM 内部也建议 XFS,ftype=1)
3.2 LVM-Thin 配置(宿主机)
# 假设 NVMe 为 /dev/nvme0n1 /dev/nvme1n1,做 vg_vm
pvcreate /dev/nvme0n1 /dev/nvme1n1
vgcreate vg_vm /dev/nvme0n1 /dev/nvme1n1
lvcreate -L 100G -n meta vg_vm
lvcreate -l 95%FREE -T vg_vm/thinpool # 元数据和数据分离更稳
经验:VM 系统盘用 thin 卷(快照友好),数据库等高写入盘可给 独立 raw 卷 + discard,并开启 iothreads。
4. 网络:桥接 + VLAN +(可选)SR-IOV
4.1 Linux Bridge + VLAN
# 在 br1 上开 VLAN trunk(示例:10=管理,20=后端,30=备份)
nmcli con add type vlan ifname br1.10 dev br1 id 10
nmcli con add type vlan ifname br1.20 dev br1 id 20
nmcli con add type vlan ifname br1.30 dev br1 id 30
nmcli con up br1.10; nmcli con up br1.20; nmcli con up br1.30
4.2 SR-IOV(给 Windows VM 一个直通网卡 VF,减少延迟)
# 以 Intel X710 为例,给 PF 开 VF
echo 4 > /sys/class/net/eno2/device/sriov_numvfs # 生成 4 张 VF
# VF 设备会出现如 ens1f1v0/1/2/3,libvirt 中直通给特定 VM
5. 安装 KVM 虚拟化与模板制作
5.1 创建 Linux 模板 VM(后续复制)
# 创建 2 vCPU / 4GB 模板(后续可扩),virtio、UEFI、cloud-init
virt-install \
--name tmpl-rocky9 \
--memory 4096 --vcpus 2 \
--cpu host-passthrough \
--disk pool=vg_vm,size=20,format=qcow2,bus=virtio \
--network bridge=br0,model=virtio \
--cdrom /iso/Rocky-9.x.iso \
--graphics none
安装后:
- 安装 qemu-guest-agent 并启用。
- 关机做模板:virt-sysprep -d tmpl-rocky9。
- 转模板标记:保留 tmpl- 前缀,后续 virt-clone 快速复制。
5.2 模板内安装 Docker(给 Linux 业务 VM 用)
我不在宿主机装 Docker,把容器工作负载放进 Linux VM,便于迁移与隔离。
# Rocky/Alma/Debian 内
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
# docker-compose(v2)
mkdir -p /opt/stacks
XFS 注意:容器底层文件系统若用 XFS,必须 ftype=1,否则 overlay2 会报错。检查:
xfs_info / | grep ftype # 看到 ftype=1 才行
5.3 创建 Windows VM
- ISO:Windows Server 2019/2022。
- virtio-win 驱动 ISO:安装时加载存储与网卡驱动。
- CPU 型号:host-passthrough,启用 Hyper-V enlightenments(libvirt 默认会带)。
- 磁盘控制器:virtio-scsi,iothreads=1。
- 网卡:virtio-net 或直通 SR-IOV VF。
- 启用 Balloon(内存气球)+ qemu-guest-agent for Windows。
示例(先不放 SR-IOV):
virt-install \
--name win2022-app \
--memory 16384 --vcpus 8 \
--cpu host-passthrough,hv_relaxed=on,hv_vapic=on,hv_spinlocks=0x1fff \
--disk pool=vg_vm,size=120,format=qcow2,bus=virtio,cache=none,discard=unmap \
--cdrom /iso/Windows_Server_2022.iso \
--disk path=/iso/virtio-win.iso,device=cdrom \
--network bridge=br0,model=virtio \
--graphics spice
安装完 Windows 后:
- 进设备管理器装 virtio 全家桶(网卡/存储/balloon/rng)。
- 安装 qemu-guest-agent MSI,启用服务。
- 打补丁,关 UAC 提示等与业务相关的策略,做一份基础 快照。
6. 资源隔离与性能调优(关键)
6.1 CPU Pinning & 隔离
把宿主机保留 2 个核给系统/中断,其余核分配给 VM,并固定亲和性,避免抖动。
# 查看 CPU 编号
lscpu -e
# 例:保留 CPU0-1 给宿主机;Windows 绑到 CPU2-7;Linux-01 绑到 CPU8-11;Linux-02 绑到 CPU12-15
# 使用 virsh 编辑,将 vcpu pin 到对应 pCPU
virsh vcpupin win2022-app 0 2
virsh vcpupin win2022-app 1 3
# ... 逐个设置,或直接改 domain XML 的 cputune
libvirt 片段示例:
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<emulatorpin cpuset='2-3'/>
</cputune>
<memoryBacking>
<hugepages/>
</memoryBacking>
6.2 磁盘与 IO
磁盘 cache=none,discard=unmap,aio=native(新内核可考虑 io_uring)。
virtio-scsi + iothreads=1,并在 XML 中绑定:
<iothreads>1</iothreads>
<cputune>
<iothreadpin iothread='1' cpuset='7'/> <!-- 单独给 IO 线程一个核 -->
</cputune>
6.3 网络
ethtool -K eno1 tso on gso on gro on(按需),保证 MTU 一致。
关键 Windows VM 用 SR-IOV VF,低延迟。
7. 在 Linux VM 里跑 Docker:一份能落地的 Compose
我把 Web/API/队列拆成栈,示例(/opt/stacks/app/docker-compose.yml):
version: "3.9"
services:
nginx:
image: nginx:1.25
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certs:/etc/nginx/certs:ro
depends_on: [api]
restart: always
api:
image: registry.example.com/myapi:2025.08.15
env_file: .env
deploy:
resources:
limits:
cpus: "4"
memory: 8g
volumes:
- appdata:/var/lib/myapi
restart: always
mq:
image: rabbitmq:3.13-management
ports:
- "15672:15672"
volumes:
- mqdata:/var/lib/rabbitmq
restart: always
volumes:
certs:
appdata:
mqdata:
经验:应用写放到 volumes 映射的 XFS 分区;日志用 stdout 收集到 Loki/ELK;不要让容器疯狂写 /var/lib/docker。
8. 监控与日志
宿主机:node_exporter、smartctl_exporter、libvirt-exporter。
Linux VM:node_exporter、cAdvisor、应用自定义指标。
Windows VM:windows_exporter。
日志:容器 stdout -> Loki / Filebeat;Windows 事件转发到 WEC/Beats。
9. 备份与回滚
9.1 VM 级别
停机热备:virsh snapshot-create-as --domain win2022-app snap-pre-upgrade --disk-only --atomic
跨盘备份(增量):使用 qemu-img + dirty bitmap(Rocky 9 更顺手)。
LVM 快照(raw 卷):短期回滚,注意空间膨胀。
9.2 容器级别
卷目录 rsync/restic 到内网备份服务器(VLAN 30)。
数据库用各自逻辑备份(mysqldump/pg_dump/冷备份)。
10. 变更前后:利用率对比(我现场抓的一个区间)
| 指标 | 变更前 | 变更后(稳定一周取中位数) |
|---|---|---|
| CPU 利用率 | 5%~15% | 38%~62%(峰值 80%) |
| 内存占用 | ~20% | 70% 左右(含 page cache) |
| NVMe 写入抖动 | 频繁 | 显著降低(iothreads + pinning) |
| 平均接口延迟(ms) | 2.1 | 0.9(Windows SR-IOV 生效) |
| 故障回滚时间 | N/A | ≤10 分钟(快照/模板) |
11. 我踩过的坑(以及我是怎么解决的)
XFS ftype=0 导致 Docker overlay2 出错
症状:容器启动报 operation not supported。
解决:重新格式化分区 mkfs.xfs -n ftype=1,或改用 ext4(我最后重建了数据卷)。
Windows virtio 网卡偶发丢包
症状:高并发下吞吐飘,事件查看器有中断警告。
解决:升级 virtio-net 驱动,同时在设备高级属性里确认 启用 MSI/MSI-X;关键业务改 SR-IOV VF。
时间漂移(Windows VM)
症状:日志时间慢 2~3 秒。
解决:host-passthrough + Hyper-V enlightenments,并把宿主机的 chrony 校准到香港授时;Windows 用域/权威 NTP。
磁盘写入抖动
症状:NVMe 忙到 100%,容器响应飘。
解决:把数据库盘改 raw + iothreads,并 CPU pin 单独核给 IO 线程;禁用不必要的容器日志刷盘。
桥接 Hairpin 导致 DNAT 访问异常
症状:容器内经公网域名访问自己失败。
解决:对 br0 开启 hairpin 或在 DNS 上用 split-horizon(内外不同解析)。
cloud-init 不生效
原因:磁盘总线类型与模板里不一致。
解决:统一使用 virtio-scsi,模板严格版控。
CentOS 7 QEMU 太老
新特性用不上。
解决:评估运维窗口,迁到 Rocky 9;这次因历史原因保留了 CentOS 7,但把 Windows VM 放 SR-IOV,Linux VM 做容器来抵消部分老版本带来的损耗。
12. 标准化:我落地的一套最小可行模板
12.1 libvirt 域 XML(节选:Windows)
<domain type='kvm'>
<name>win2022-app</name>
<memory unit='GiB'>16</memory>
<vcpu placement='static'>8</vcpu>
<cpu mode='host-passthrough'>
<feature policy='require' name='topoext'/>
</cpu>
<iothreads>1</iothreads>
<cputune>
<vcpupin vcpu='0' cpuset='2'/>
<vcpupin vcpu='1' cpuset='3'/>
<vcpupin vcpu='2' cpuset='4'/>
<vcpupin vcpu='3' cpuset='5'/>
<vcpupin vcpu='4' cpuset='6'/>
<vcpupin vcpu='5' cpuset='7'/>
<vcpupin vcpu='6' cpuset='8'/>
<vcpupin vcpu='7' cpuset='9'/>
<emulatorpin cpuset='2-3'/>
<iothreadpin iothread='1' cpuset='7'/>
</cputune>
<memoryBacking><hugepages/></memoryBacking>
<devices>
<disk type='block' device='disk'>
<driver name='qemu' type='raw' cache='none' io='native' discard='unmap'/>
<source dev='/dev/vg_vm/win2022_root'/>
<target dev='sda' bus='scsi'/>
</disk>
<controller type='scsi' model='virtio-scsi'/>
<interface type='bridge'>
<source bridge='br0'/>
<model type='virtio'/>
</interface>
<!-- 可选:SR-IOV 直通 VF
<hostdev mode='subsystem' type='pci' managed='yes'>
<source><address domain='0x0000' bus='0x65' slot='0x00' function='0x1'/></source>
</hostdev>
-->
<channel type='unix'>
<source mode='bind'/>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
</devices>
</domain>
12.2 宿主机 sysctl(节选)
vm.swappiness = 10
vm.nr_hugepages = 8192
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.core.netdev_max_backlog = 250000
12.3 Docker Compose 目录结构(Linux VM)
/opt/stacks/
└── app
├── docker-compose.yml
├── .env
└── nginx/
└── conf.d/*.conf
13. 变更流程(我真正在机房按这个节奏走的)
- 盘点硬件 & 固件升级(30 分钟)。
- 装宿主机系统 & 基础优化(1 小时)。
- 配网:br0/br1 + VLAN,打通内外(30 分钟)。
- 配 LVM-thin & 模板 VM(1 小时)。
- 装 Windows & virtio 驱动,做基础快照(1 小时)。
- 克隆 Linux VM,部署 Docker 栈(1 小时)。
- CPU/IO pinning & HugePages 调优(30 分钟)。
- 上监控 & 日志(30 分钟)。
- 压测对比,逐步切流(灰度 20%→50%→100%)。
真心话:最花时间的不是命令,而是规划与标准化。做成模板之后,第二台、第三台就很快了。
14. 收尾与建议
如果可以,尽快把宿主迁到 Rocky/Alma 9,你会得到更好的 io_uring、virtio、新 QEMU。
把 Windows VM 尽可能“无状态化”:数据放专盘或后端存储,系统盘随时可回滚。
容器日志控量,落地到 Loki/ELK,别让 NVMe 受罪。
监控、备份、快照是三条腿,缺一不可。
附:一键克隆 Linux 模板并上线 Docker 栈(脚本示例)
#!/usr/bin/env bash
set -euo pipefail
NAME="linux-web-01"
VCPUS=4
MEM=8192 # MB
DISK_SIZE=60 # GB
virt-clone --original tmpl-rocky9 --name ${NAME} --auto-clone
virsh setvcpus ${NAME} ${VCPUS} --config
virsh setmem ${NAME} $((MEM*1024)) --config
# Pin 到 CPU8-11 示例
virsh vcpupin ${NAME} 0 8; virsh vcpupin ${NAME} 1 9; virsh vcpupin ${NAME} 2 10; virsh vcpupin ${NAME} 3 11
# 扩容磁盘
virsh blockresize ${NAME} vda ${DISK_SIZE}G
virsh start ${NAME}
# 等 cloud-init 配好后,用 Ansible/SSH 下发 docker-compose 并启动
这套“KVM 做底座 + Windows/Linux 混部 + 容器进 Linux VM”在香港机房跑了几个月,稳定、可回滚、弹性强。对我来说,最有价值的不是机器跑得更满,而是把复杂度圈在可控的边界:宿主只做虚拟化、VM 标准化、容器标准化。你要的不是“堆功能”,而是“可复用的套路”。如果你现场环境参数不同,把上面的“模板 + 调优点”按自己硬件去改,就能快速复制出同等可用的混合部署。