香港服务器KVM与Docker混合部署,网络和存储隔离如何落地?
香港服务器上落地 KVM 与 Docker 混合部署,建议采用“KVM 宿主机承载虚拟机,Docker 只运行在专用业务虚拟机内”的分层方案。宿主机负责虚拟化、管理网络和虚拟磁盘;业务虚拟机负责 Docker Engine、容器网络和应用数据。这样可以避免 Docker 修改宿主机转发规则,也避免业务容器直接接触宿主机文件系统。
以下方案以一台只有一个公网 IPv4 地址的香港服务器为例:宿主机使用公网地址,虚拟机接入 libvirt 私有 NAT 网络,宿主机通过 Nginx 反向代理将 80/443 请求转发到虚拟机。系统选用 Ubuntu Server 22.04 LTS,宿主机和业务虚拟机均采用同一大版本,Docker 使用 Compose 插件管理应用。示例地址均为文档保留地址或内网地址,实际部署时必须替换为服务商分配的参数。
A5数据提供香港物理服务器租用,覆盖入门建站、Xeon Gold与AMD EPYC等配置,并配备SSD或NVMe存储及CN2、国际带宽等线路方案,可为KVM宿主机、业务虚拟机和Docker应用提供独立的计算、网络与存储资源基础。针对数据库、接口服务、多容器并行运行等场景,也可结合不同内存、磁盘和带宽配置承载相应业务。
一、部署目标和隔离架构
1. 目标环境
本方案的上线目标如下:
- 宿主机安装 KVM、QEMU 和 libvirt。
- 创建一台专用业务虚拟机
app-vm。 - Docker Engine 和 Compose 只安装在
app-vm内。 - 宿主机公网网卡不直接桥接给 Docker。
- 虚拟机使用独立的私有网络地址
10.10.20.10。 - Docker 使用独立的应用网络和数据库网络。
- Docker 数据目录挂载到虚拟机独立数据盘
/srv/docker-data。 - 宿主机只通过 Nginx 向虚拟机转发业务流量,不共享宿主机应用目录。
这种结构的边界如下:

| 层级 | 示例组件 | 主要职责 | 隔离方式 |
|---|---|---|---|
| 物理服务器 | 香港机房服务器 | 提供 CPU、内存、磁盘和公网带宽 | 服务商硬件边界 |
| KVM 宿主机 | Ubuntu Server 22.04 | 管理虚拟机、虚拟网络和虚拟磁盘 | 宿主机权限与 libvirt |
| 业务虚拟机 | app-vm | 运行 Docker 和业务服务 | KVM 虚拟机边界 |
| Docker 前端网络 | app_frontend | 暴露需要接收请求的容器 | Docker bridge 网络 |
| Docker 后端网络 | app_backend | 连接应用和数据库 | internal 内部网络 |
| 虚拟机数据盘 | /srv/docker-data | 保存镜像层、容器卷和业务数据 | 独立虚拟磁盘和文件系统 |
KVM 提供的是虚拟机级别的内核隔离,Docker 容器仍然共享业务虚拟机内核。因此,不建议把来源不明、需要强安全边界的容器直接混放在同一台业务虚拟机内。不同租户或不同信任级别的应用,应拆分到不同 KVM 虚拟机。
2. 网络地址规划
示例网络规划如下:
| 用途 | 地址或接口 | 说明 |
|---|---|---|
| 宿主机公网接口 | eno1 | 物理网卡,不直接承载容器网络 |
| 宿主机公网桥 | br0 | 宿主机公网地址绑定在此处 |
| libvirt 私有网络 | virbr10 | 宿主机与虚拟机之间的私有网段 |
| 私有网关 | 10.10.20.1 | 宿主机在私有网络中的地址 |
| 业务虚拟机 | 10.10.20.10 | 由 DHCP 保留地址分配 |
| Docker 前端网络 | 172.30.10.0/24 | 业务入口容器使用 |
| Docker 后端网络 | 172.30.20.0/24 | 应用和数据库内部通信 |
如果服务商额外提供多个公网 IP,并明确允许虚拟机使用多个 MAC 地址,也可以把虚拟机接入 br0。但这需要服务商侧放行二层地址或完成额外配置,不能仅在服务器上添加一个 bridge 就认为公网已经可用。
3. 存储隔离原则
存储隔离不等于简单地把所有目录放到一个大分区中。建议至少区分以下对象:
- 宿主机系统盘:保存宿主机系统和管理工具。
- 虚拟机系统盘:保存业务虚拟机操作系统。
- 虚拟机数据盘:保存 Docker 数据和应用持久化数据。
- 数据库目录:使用独立目录,避免和临时日志混放。
- 宿主机镜像目录:每台虚拟机使用独立镜像文件,不让 Docker 直接绑定宿主机目录。
不要将宿主机的 /var/lib/docker、应用代码目录或数据库目录直接挂载到虚拟机中。此类共享会降低权限边界,也会让宿主机目录结构和虚拟机内部服务产生耦合。
二、准备条件
1. 硬件和服务商条件
建议准备以下资源:
- 支持硬件虚拟化的物理服务器或裸金属实例。
- 至少 4 个逻辑 CPU。
- 至少 8 GB 内存;同时运行数据库和多个容器时建议从 16 GB 起步。
- 系统盘之外,预留一块可分配给业务虚拟机的数据盘空间。
- 服务商允许安装自定义操作系统或提供控制台重装能力。
- 可以通过带外控制台、VNC 或串口控制台处理网络配置错误。
- 记录公网 IPv4、子网掩码、默认网关、DNS、SSH 管理来源地址。
- 确认服务商是否限制额外 MAC 地址、广播、ARP 或桥接流量。
香港服务器的网络交付方式并不完全一致。有的实例使用静态公网地址,有的使用 DHCP,有的要求网关采用特殊掩码。没有确认服务商参数前,不要直接套用下面的公网地址配置。
2. 安装系统和检查基础状态
宿主机使用 Ubuntu Server 22.04 LTS。完成系统安装并通过控制台确认 SSH 可用后,先检查硬件和磁盘:
sudo -i
cat /etc/os-release
uname -r
lscpu | egrep 'Architecture|CPU\(s\)|Virtualization'
free -h
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
ip -br addr
ip route
如果 lscpu 中没有显示 VT-x 或 AMD-V 等虚拟化能力,继续安装 KVM 可能会失败。使用 libvirt 自带检查工具确认:
apt update
apt install -y cpu-checker
kvm-ok
预期结果类似:
INFO: /dev/kvm exists
KVM acceleration can be used
如果显示 /dev/kvm does not exist,先检查服务商是否提供裸金属环境,以及 BIOS 或实例规格是否开启虚拟化。云服务器的嵌套虚拟化未开启时,不能通过软件命令绕过。
3. 建立配置备份目录
修改网络、libvirt 或 Docker 配置前,先建立备份目录:
install -d -m 0700 /root/deploy-backup
cp -a /etc/netplan /root/deploy-backup/netplan-$(date +%F-%H%M%S)
如果服务器已有业务,不要直接覆盖原有防火墙、网络和容器配置。先记录现状:
systemctl --type=service --state=running
ss -lntup
ufw status verbose 2>/dev/null || true
nft list ruleset > /root/deploy-backup/nft-before.txt 2>/dev/null || true
后续涉及 netplan apply、ufw enable、mkfs、删除容器或重启 Docker 的操作,都应保留当前 SSH 会话,并准备使用服务商控制台回退。
三、安装 KVM、QEMU 和 libvirt
1. 安装宿主机依赖
在宿主机执行:
apt update
apt install -y \
qemu-kvm \
qemu-utils \
libvirt-daemon-system \
libvirt-clients \
virtinst \
bridge-utils \
ovmf \
cloud-image-utils
启动并设置 libvirt 开机启动:
systemctl enable --now libvirtd
systemctl status libvirtd --no-pager
确认 KVM 设备和 libvirt 服务:
ls -l /dev/kvm
virsh uri
virsh version
virt-host-validate qemu
virt-host-validate 出现 PASS 表示检查项通过。部分环境可能在 IOMMU、Secure Boot 或权限项显示 WARN,需要结合实际用途判断;如果 QEMU: Checking for hardware virtualization 直接失败,则先不要创建业务虚拟机。
2. 验证宿主机资源
创建虚拟机前,确认资源余量:
free -h
df -hT
df -ih
virsh list --all
示例分配可以是:
- 宿主机保留 2 个逻辑 CPU和 2 GB 内存。
app-vm分配 4 个 vCPU、8 GB 内存。- 虚拟机系统盘 60 GB。
- 虚拟机 Docker 数据盘 100 GB。
不要把宿主机全部内存分配给虚拟机。宿主机还需要承担 QEMU、libvirt、Nginx、日志和文件缓存开销。
四、配置宿主机公网桥
1. 选择网卡名称
网卡名称不一定是 eno1,先确认实际名称:
ip -br link
下面假设物理网卡为 eno1,宿主机公网地址使用文档示例:
- 地址:
192.0.2.10/24 - 网关:
192.0.2.1 - DNS:
1.1.1.1、8.8.8.8
这些地址不能直接用于生产环境,必须替换为服务商提供的实际参数。
2. 编写 Netplan 配置
先查看现有文件:
ls -l /etc/netplan/
sed -n '1,200p' /etc/netplan/*.yaml
确认没有需要保留的现有配置后,创建桥接配置:
cat >/etc/netplan/01-br0.yaml <<'EOF'
network:
version: 2
renderer: networkd
ethernets:
eno1:
dhcp4: false
dhcp6: false
bridges:
br0:
interfaces:
- eno1
addresses:
- 192.0.2.10/24
routes:
- to: default
via: 192.0.2.1
nameservers:
addresses:
- 1.1.1.1
- 8.8.8.8
parameters:
stp: false
forward-delay: 0
EOF
如果服务商要求 DHCP,公网部分应改为:
bridges:
br0:
interfaces:
- eno1
dhcp4: true
dhcp6: false
parameters:
stp: false
forward-delay: 0
不要同时在 eno1 和 br0 上配置公网地址。公网地址应绑定到 br0,物理网卡只作为桥成员。
3. 应用并验证网络
远程操作优先使用 netplan try,不要直接使用 netplan apply:
netplan generate
netplan try
netplan try 会在确认超时后自动恢复原配置。确认 SSH、路由和 DNS 均正常后,再执行:
netplan apply
ip -br addr show br0
ip route
ping -c 3 192.0.2.1
getent hosts archive.ubuntu.com
预期结果中,公网地址应出现在 br0,而不是 eno1。如果 SSH 中断,立即通过控制台登录并恢复备份:
rm -f /etc/netplan/01-br0.yaml
cp -a /root/deploy-backup/netplan-备份时间/* /etc/netplan/
netplan generate
netplan apply
备份目录名称需要替换为实际目录。公网桥配置错误是最容易造成远程失联的步骤,不能在没有控制台的情况下盲目操作。
五、创建 KVM 私有网络
1. 编写 libvirt 网络定义
创建只用于宿主机和业务虚拟机通信的私有 NAT 网络:
cat >/root/vm-private.xml <<'EOF'
vm-private
EOF
定义并启动网络:
virsh net-define /root/vm-private.xml
virsh net-autostart vm-private
virsh net-start vm-private
验证网络状态:
virsh net-list --all
virsh net-info vm-private
ip -br addr show virbr10
预期状态:
Name State Autostart Persistent
vm-private active yes yes
如果提示网络名称已存在,不要直接删除。先查看原配置:
virsh net-dumpxml vm-private
确认原网络没有承载其他虚拟机后,再决定使用 virsh net-edit vm-private 调整配置。删除或重新定义已有网络可能导致其他虚拟机断网。
2. 验证 NAT 出口
私有网络启动后,宿主机应生成 virbr10。libvirt 默认会配置 NAT 规则,使虚拟机可以访问软件仓库,但外部网络不能主动访问虚拟机。
此时可以检查:
virsh net-dhcp-leases vm-private
ip route
如果还没有 DHCP 租约,属于正常情况,因为虚拟机尚未启动。
六、创建业务虚拟机并分离数据盘
1. 创建虚拟机
将 Ubuntu Server ISO 放到宿主机,例如:
/var/lib/libvirt/iso/ubuntu-22.04-live-server-amd64.iso
确认文件存在:
ls -lh /var/lib/libvirt/iso/
创建 60 GB 系统盘和业务虚拟机:
mkdir -p /var/lib/libvirt/images
virt-install \
--name app-vm \
--memory 8192 \
--vcpus 4 \
--cpu host-passthrough \
--disk path=/var/lib/libvirt/images/app-vm.qcow2,size=60,format=qcow2,bus=virtio \
--cdrom /var/lib/libvirt/iso/ubuntu-22.04-live-server-amd64.iso \
--network network=vm-private,model=virtio,mac=52:54:00:10:20:10 \
--os-variant ubuntu22.04 \
--graphics none \
--console pty,target_type=serial
通过控制台完成 Ubuntu 安装。安装时建议:
- 创建普通管理用户。
- 开启 OpenSSH Server。
- 不在业务虚拟机中安装桌面环境。
- 系统盘只用于操作系统和软件包。
- 使用虚拟机内的 DHCP,不要手工配置与其他网段冲突的静态地址。
安装完成后退出安装器并重启:
virsh console app-vm
控制台中使用 Ctrl 加 ] 退出。确认虚拟机状态:
virsh list --all
virsh autostart app-vm
2. 安装虚拟机增强组件
进入 app-vm 后执行:
sudo apt update
sudo apt install -y qemu-guest-agent
sudo systemctl enable --now qemu-guest-agent
在宿主机检查虚拟机地址:
virsh domifaddr app-vm --source lease
virsh net-dhcp-leases vm-private
预期可以看到:
10.10.20.10
如果看不到地址,依次检查虚拟机网卡、DHCP 服务和网卡状态:
virsh domiflist app-vm
virsh net-dhcp-leases vm-private
在虚拟机内执行:
ip -br addr
ip route
ping -c 3 10.10.20.1
ping -c 3 1.1.1.1
虚拟机能够访问 10.10.20.1 但不能访问公网时,优先检查 libvirt NAT、宿主机出口和 DNS,不要先修改 Docker 配置。
3. 创建独立数据盘
停止虚拟机后创建新的数据盘。下面命令只适用于新建的空白镜像,qemu-img create 不会删除已有文件,但后续 mkfs 会清空目标设备:
virsh shutdown app-vm
virsh domstate app-vm
确认状态为 shut off 后创建数据盘:
qemu-img create -f qcow2 /var/lib/libvirt/images/app-vm-data.qcow2 100G
挂载为虚拟机的 vdb:
virsh attach-disk app-vm \
/var/lib/libvirt/images/app-vm-data.qcow2 \
vdb \
--driver qemu \
--subdriver qcow2 \
--persistent
启动虚拟机并检查设备:
virsh start app-vm
virsh domblklist app-vm
进入虚拟机后确认新磁盘:
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS
如果确认 /dev/vdb 是刚创建的空白数据盘,再格式化:

sudo mkfs.ext4 -L docker-data /dev/vdb
sudo mkdir -p /srv/docker-data
sudo mount /dev/vdb /srv/docker-data
mkfs.ext4 会清除 /dev/vdb 上的原有数据。如果设备名称不是 /dev/vdb,必须以 lsblk 的实际结果为准,不能照抄。
写入 UUID 挂载配置:
UUID=$(blkid -s UUID -o value /dev/vdb)
echo "UUID=${UUID} /srv/docker-data ext4 defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo mount -a
findmnt /srv/docker-data
df -hT /srv/docker-data
预期结果中,/srv/docker-data 的文件系统类型为 ext4,设备来源为数据盘 UUID。
七、在业务虚拟机中安装 Docker
1. 安装 Docker Engine
以下命令在 app-vm 内执行:
sudo apt update
sudo apt install -y ca-certificates curl gnupg openssl
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
sudo systemctl enable --now docker
验证版本和服务:
sudo docker version
sudo docker compose version
sudo systemctl is-active docker
如果软件仓库无法访问,先检查虚拟机的默认路由和 DNS:
ip route
resolvectl status
curl -I https://download.docker.com
不要在 Docker 尚未安装完成时手工创建大量网络规则。网络出口问题应先在虚拟机层解决。
2. 将 Docker 数据根目录迁移到独立数据盘
如果这是新建虚拟机,Docker 尚未运行过,可以直接使用以下配置:
sudo mkdir -p /srv/docker-data/docker
sudo chmod 0711 /srv/docker-data
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"data-root": "/srv/docker-data/docker",
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}
EOF
sudo systemctl restart docker
验证 Docker 数据根目录:
sudo docker info --format '{{.DockerRootDir}}'
df -hT /srv/docker-data
预期结果:
/srv/docker-data/docker
如果 Docker 已经运行并且 /var/lib/docker 中存在镜像、容器或卷,不能直接修改 data-root 后重启。应先停止 Docker,再迁移数据:
sudo systemctl stop docker
sudo rsync -aHAX --numeric-ids /var/lib/docker/ /srv/docker-data/docker/
sudo systemctl start docker
sudo docker info --format '{{.DockerRootDir}}'
确认容器和镜像可用后,才考虑清理旧目录。清理前必须完成备份和回滚验证,不要直接执行 rm -rf /var/lib/docker。
八、落地 Docker 网络隔离
1. 创建应用目录和权限
在虚拟机内创建业务目录:
sudo mkdir -p \
/srv/docker-data/apps/site/html \
/srv/docker-data/apps/postgres
sudo chown -R root:root /srv/docker-data/apps
sudo chmod 0755 /srv/docker-data/apps/site
sudo chmod 0755 /srv/docker-data/apps/site/html
写入测试页面:
echo 'kvm-docker-isolation-ok' | sudo tee /srv/docker-data/apps/site/html/index.html
数据库目录的最终属主应以镜像内数据库进程 UID 为准。以 PostgreSQL 常见镜像为例,可先使用容器启动后的实际 UID 调整,不要仅凭宿主机用户名判断。
2. 编写 Compose 配置
创建项目目录:
sudo mkdir -p /srv/docker-data/apps/stack
sudo chown "$USER":"$USER" /srv/docker-data/apps/stack
cd /srv/docker-data/apps/stack
生成随机数据库密码并限制配置文件权限:
DB_PASSWORD=$(openssl rand -hex 24)
printf 'POSTGRES_PASSWORD=%s\n' "$DB_PASSWORD" > .env
chmod 600 .env
创建 compose.yaml:

services:
web:
image: nginx:1.27-alpine
container_name: app-web
restart: unless-stopped
ports:
- "10.10.20.10:8080:80"
volumes:
- /srv/docker-data/apps/site/html:/usr/share/nginx/html:ro
networks:
- frontend
- backend
depends_on:
- db
db:
image: postgres:16-alpine
container_name: app-db
restart: unless-stopped
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- /srv/docker-data/apps/postgres:/var/lib/postgresql/data
networks:
- backend
networks:
frontend:
name: app_frontend
driver: bridge
ipam:
config:
- subnet: 172.30.10.0/24
backend:
name: app_backend
driver: bridge
internal: true
ipam:
config:
- subnet: 172.30.20.0/24
这里有三层限制:
web只绑定到虚拟机私有地址的8080端口,没有绑定虚拟机公网地址。db没有ports配置,不会在虚拟机上发布数据库端口。backend设置为internal: true,数据库只通过 Docker 内部网络提供服务。
生产环境应将镜像版本固定到经过验证的版本或摘要,并把数据库密码放入受控的密钥管理系统。示例密码仅用于部署演示。
3. 验证 Compose 配置并启动
先检查配置语法:
docker compose config --quiet
无输出且返回码为 0,表示 Compose 配置可以解析。然后启动:
docker compose up -d
docker compose ps
检查网络和端口:
docker network inspect app_frontend
docker network inspect app_backend
ss -lntp | grep 8080
预期监听地址应类似:
10.10.20.10:8080
如果看到 0.0.0.0:8080,说明 Compose 文件没有按预期绑定到私有地址,需要检查缩进和端口配置。
从业务虚拟机内测试:
curl -i http://10.10.20.10:8080
预期响应正文包含:
kvm-docker-isolation-ok
检查容器挂载来源:
docker inspect app-web --format '{{json .Mounts}}' | jq
docker inspect app-db --format '{{json .Mounts}}' | jq
挂载源应分别位于:
/srv/docker-data/apps/site/html
/srv/docker-data/apps/postgres
如果数据库容器反复重启,先查看日志:
docker compose logs --tail=100 db
常见原因包括数据目录中已有不完整初始化文件、目录权限不正确或数据盘未挂载。不要在未备份数据库数据的情况下执行 docker compose down -v,该命令可能删除 Compose 管理的卷。
九、配置宿主机反向代理和防火墙
1. 安装宿主机 Nginx
在 KVM 宿主机执行:
apt update
apt install -y nginx
systemctl enable --now nginx
创建反向代理配置:
cat >/etc/nginx/sites-available/app-vm.conf <<'EOF'
server {
listen 80;
listen [::]:80;
server_name app.example.com;
location / {
proxy_pass http://10.10.20.10:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
}
EOF
ln -s /etc/nginx/sites-available/app-vm.conf \
/etc/nginx/sites-enabled/app-vm.conf
nginx -t
systemctl reload nginx
server_name 改为实际域名。生产环境应在宿主机 Nginx 处完成 HTTPS 配置,虚拟机内部只接收来自 10.10.20.1 的反向代理流量。
从宿主机测试到虚拟机的连通性:
curl -i http://10.10.20.10:8080
curl -i -H 'Host: app.example.com' http://127.0.0.1
如果第一条成功、第二条出现 502 Bad Gateway,说明虚拟机服务可用,但 Nginx 访问路径、配置或宿主机防火墙需要检查。
2. 配置宿主机入口防火墙
如果宿主机没有现有防火墙策略,可以使用 UFW。执行前必须确认管理来源地址,并保留服务商控制台:
apt install -y ufw
ufw status verbose
将 198.51.100.25 替换为实际办公出口或管理地址:
ufw allow from 198.51.100.25 to any port 22 proto tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw default deny incoming
ufw default allow outgoing
ufw --force enable
验证规则:
ufw status numbered
ss -lntp
这组规则的效果是:
- 公网只允许 SSH 管理地址访问 22 端口。
- 公网允许访问宿主机的 80/443。
- 虚拟机不直接暴露公网地址。
- 宿主机可以访问私有网络中的
10.10.20.10:8080。
如果 SSH 当前来源没有被允许,启用 UFW 后会立即失去远程管理能力。已经存在 nftables、云平台安全组或服务商防火墙时,不要重复套用默认拒绝规则,应先合并现有策略。
3. 检查 Docker 端口暴露边界
在业务虚拟机中执行:
ss -lntp
docker ps --format 'table {{.Names}}\t{{.Ports}}'
正确的结果应类似:
10.10.20.10:8080->80/tcp
数据库容器不应出现 0.0.0.0:5432->5432/tcp 或类似公网发布结果。如果数据库需要被其他服务访问,应让其他服务加入 app_backend 网络,而不是发布数据库端口。
十、逐层验证和常见异常处理
1. KVM 检查失败
检查命令:
virt-host-validate qemu
ls -l /dev/kvm
systemctl status libvirtd --no-pager
判断方式:
- 没有
/dev/kvm:检查硬件虚拟化或服务商是否允许嵌套虚拟化。 - libvirt 未运行:启动
libvirtd,再检查日志。 - 虚拟机只能软件模拟:确认是否必须使用 KVM 加速;生产业务不建议忽略性能警告。
- 虚拟机启动后立即退出:查看
journalctl -u libvirtd和虚拟机 XML,重点检查磁盘路径、权限和 CPU 模式。
2. 虚拟机没有地址
在宿主机执行:
virsh domiflist app-vm
virsh net-info vm-private
virsh net-dhcp-leases vm-private
在虚拟机内执行:
ip -br link
ip -br addr
sudo systemctl status systemd-networkd --no-pager
如果 virsh domiflist 没有 vm-private,说明虚拟机网卡接错网络。可以关机后编辑虚拟机配置,不要在生产运行中随意删除网卡:
virsh edit app-vm
确认网卡的 MAC 地址与 DHCP 保留配置中的 52:54:00:10:20:10 一致。
3. Nginx 返回 502
按照由外到内的顺序检查:

curl -i http://10.10.20.10:8080
docker compose ps
docker compose logs --tail=100 web
systemctl status nginx --no-pager
tail -n 100 /var/log/nginx/error.log
不同结果的含义:
- 虚拟机内访问
10.10.20.10:8080失败:先修复 Docker 服务。 - 虚拟机内成功、宿主机访问失败:检查宿主机到
virbr10的路由和防火墙。 - 宿主机访问成功、域名访问失败:检查 DNS、宿主机 80/443 防火墙和服务商入口策略。
- Nginx 错误日志显示连接拒绝:检查 Docker 发布地址、容器监听端口和虚拟机防火墙。
- Nginx 错误日志显示超时:检查应用响应时间、容器资源和虚拟机负载。
4. 数据盘没有挂载
检查:
findmnt /srv/docker-data
lsblk -f
cat /etc/fstab
df -hT
df -ih
如果虚拟机重启后 Docker 使用了 /var/lib/docker,通常说明 /srv/docker-data 没有在 Docker 启动前挂载。应先停止 Docker,修复 /etc/fstab,再确认挂载成功后启动 Docker:
sudo systemctl stop docker
sudo mount -a
findmnt /srv/docker-data
sudo systemctl start docker
docker info --format '{{.DockerRootDir}}'
磁盘空间和 inode 都要检查。磁盘还有容量但 inode 用尽时,容器同样会创建文件失败。
5. 容器网络网段冲突
如果香港服务器所在内网、libvirt 网段或应用内部网段已经使用 172.30.10.0/24,Docker 可能出现路由冲突。检查:
ip route
docker network ls
docker network inspect app_frontend app_backend
确认网段未与宿主机、虚拟机和业务网络重叠后再启动应用。修改 Compose 网络网段会导致容器网络重建,生产操作前应记录容器配置并安排维护窗口。
十一、上线前结果检查
完成部署后,按以下顺序验收:
宿主机检查
virsh list --all
virsh net-list --all
virsh net-dhcp-leases vm-private
virsh domblklist app-vm
systemctl is-active libvirtd nginx
ip -br addr
ss -lntp
应满足:
app-vm为running。vm-private为active且设置了自启动。- 虚拟机获得
10.10.20.10。 - 虚拟机系统盘和数据盘均存在。
- 公网监听只出现在宿主机 Nginx。
- 宿主机没有意外监听 Docker 管理端口。
虚拟机检查
findmnt /srv/docker-data
df -hT
docker info --format '{{.DockerRootDir}}'
docker compose -f /srv/docker-data/apps/stack/compose.yaml ps
ss -lntp
应满足:
/srv/docker-data来源为独立虚拟数据盘。- Docker Root Dir 位于
/srv/docker-data/docker。 - Web 容器正常运行。
- 数据库容器没有发布到公网。
- Web 仅绑定
10.10.20.10:8080。
网络隔离检查
curl -i http://10.10.20.10:8080
docker network inspect app_frontend
docker network inspect app_backend
重点确认:
- 宿主机可以访问虚拟机业务端口。
- 外部只能访问宿主机 Nginx 的 80/443。
- 数据库没有宿主机端口映射。
- 后端网络使用
internal: true。 - 虚拟机没有直接接入公网桥,除非已经确认服务商允许该模式。
存储隔离检查
docker inspect app-web --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
docker inspect app-db --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
virsh domblklist app-vm
应用数据源路径应位于虚拟机的 /srv/docker-data 下,宿主机只看到虚拟磁盘文件。需要注意,这种方式主要是权限和目录边界隔离,并不等于加密。如果数据盘需要防止宿主机管理员直接读取,应进一步采用虚拟机内加密磁盘,并单独设计密钥托管和启动流程。
十二、回滚方案
1. 回滚 Docker 应用
只停止应用而保留数据:
cd /srv/docker-data/apps/stack
docker compose down
不要带 -v 参数,否则可能删除 Compose 管理的卷。恢复配置后重新启动:
docker compose up -d
如果只是回滚某个镜像版本,先修改 compose.yaml 并保存当前文件:
cp -a compose.yaml compose.yaml.before-rollback
docker compose pull
docker compose up -d
2. 回滚 Docker 数据根目录
如果迁移到独立数据盘后出现异常:
sudo systemctl stop docker
sudo cp -a /etc/docker/daemon.json /root/deploy-backup/daemon.json.after-move
将 data-root 改回原路径,或恢复原配置:
sudo rm -f /etc/docker/daemon.json
sudo systemctl start docker
docker info --format '{{.DockerRootDir}}'
原 /var/lib/docker 未确认可用前,不要删除 /srv/docker-data/docker。如果数据目录中已经产生新数据,回滚前需要先做数据库备份和应用文件备份。
3. 回滚虚拟机数据盘
需要拆除数据盘时,先停止 Docker 和虚拟机:
sudo systemctl stop docker
virsh shutdown app-vm
确认虚拟机已关机后再分离磁盘:
virsh detach-disk app-vm vdb --persistent
这只会从虚拟机配置中移除磁盘,不会自动删除宿主机上的 app-vm-data.qcow2。确认数据已经备份后,再决定是否保留或清理镜像文件。
4. 回滚私有网络
如果私有网络配置错误,先确认没有其他虚拟机使用:
virsh net-list
virsh net-dhcp-leases vm-private
停止业务虚拟机后,可以停止网络:
virsh net-destroy vm-private
net-destroy 会立即影响连接到该网络的虚拟机。若只是暂时停用,保留网络定义;不要在没有备份 XML 的情况下执行删除操作。
5. 回滚宿主机公网桥
如果 br0 配置导致公网失联,应通过服务商控制台操作:
cp -a /root/deploy-backup/netplan-备份时间/* /etc/netplan/
netplan generate
netplan apply
恢复后确认:
ip -br addr
ip route
systemctl status systemd-networkd --no-pager
公网桥和防火墙回滚必须优先于应用回滚。宿主机无法管理时,虚拟机和 Docker 内的任何操作都无法解决外部访问问题。
十三、最终验收与回滚检查项
上线前应逐项确认:
- [ ] 已确认服务商允许安装 KVM,且
/dev/kvm可用。 - [ ] 已记录真实公网 IP、网关、DNS 和管理来源地址。
- [ ] 宿主机公网地址绑定在
br0,没有重复绑定到物理网卡。 - [ ]
vm-private网络已设置自启动。 - [ ]
app-vm使用私有网络,没有未经确认的公网桥接。 - [ ] 虚拟机系统盘和数据盘分离。
- [ ]
/srv/docker-data能在重启后自动挂载。 - [ ] Docker Root Dir 位于独立数据盘。
- [ ] Web 容器仅发布到
10.10.20.10:8080。 - [ ] 数据库容器没有发布宿主机端口。
- [ ] Docker 前端网络与后端网络用途分开。
- [ ] 宿主机 Nginx 可以访问虚拟机业务端口。
- [ ] 宿主机防火墙只放行必要的 SSH、HTTP 和 HTTPS。
- [ ] 已备份 Netplan、libvirt XML、Compose 文件和应用数据。
- [ ] 已确认
docker compose down不会误删生产数据。 - [ ] 已通过控制台验证网络和防火墙失误后的恢复路径。
- [ ] 已记录虚拟机磁盘、Docker 数据目录和数据库备份的位置。
这套结构的核心不是把 KVM 和 Docker 安装在同一台机器上,而是明确两者的边界:KVM 隔离虚拟机、虚拟网络和虚拟磁盘,Docker 只在业务虚拟机内部管理应用,宿主机通过受控入口转发流量。对于单公网 IP 的香港服务器,这种私有 NAT 加宿主机反向代理的方式更容易控制暴露面;当业务需要多个公网地址、独立租户或更强安全边界时,应继续拆分虚拟机,而不是只增加 Docker 容器数量。



