2. 实验环境搭建:虚拟机与容器化环境(Docker/KVM)的配置与快照管理

做故障注入实验,环境搭建是第一步,也是最容易踩坑的一步。我个人习惯把实验环境分成两类:虚拟机(KVM)和容器(Docker)。两者各有千秋,咱们得根据实验场景来选。

2.1 为什么需要虚拟化?

说白了,故障注入实验本质上就是「搞破坏」。你想想看,直接在物理机上搞内存位翻转、磁盘 I/O 错误、网络丢包——万一恢复不了,那可就真「炸」了。虚拟化技术给了我们一个安全沙箱:

  • 隔离性:实验环境与宿主机完全隔离,搞坏了也不影响生产
  • 快照回滚:这是故障注入实验的「后悔药」,一键回到干净状态
  • 资源管控:精确控制 CPU、内存、磁盘,模拟资源受限场景
  • 网络拓扑:轻松搭建多节点集群,模拟分布式系统故障

核心原则:故障注入实验的环境,必须做到「可重复、可回滚、可观测」。做不到这三点,实验数据就是废纸。

2.2 KVM 虚拟机环境配置

KVM 是 Linux 内核原生的虚拟化方案,性能损耗小,适合做系统级的故障注入。我在项目中遇到过好几次,用 VirtualBox 做内核调试时各种兼容性问题,换成 KVM 就稳了。

2.2.1 安装与基础配置

# 检查 CPU 是否支持虚拟化
grep -E '(vmx|svm)' /proc/cpuinfo

# 安装 KVM 及相关工具
sudo apt-get install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager

# 启动 libvirt 服务
sudo systemctl enable --now libvirtd

# 将当前用户加入 libvirt 组
sudo usermod -aG libvirt $USER

小技巧:安装完成后,记得用 virsh list --all 验证服务是否正常。我曾经因为忘记重启终端,导致新用户组没生效,折腾了半小时。

2.2.2 创建实验虚拟机

我建议用 virt-install 命令行创建,比图形化界面更可控,也方便写脚本批量部署。

# 创建 Ubuntu 22.04 实验虚拟机
virt-install \
  --name fault-injection-lab \
  --ram 4096 \
  --vcpus 2 \
  --disk path=/var/lib/libvirt/images/fault-lab.qcow2,size=40 \
  --os-variant ubuntu22.04 \
  --network bridge=virbr0 \
  --graphics vnc,listen=0.0.0.0 \
  --cdrom /path/to/ubuntu-22.04-server.iso
参数 说明 我的建议
--ram 内存大小(MB) 故障注入实验建议 4GB 起步
--vcpus 虚拟 CPU 核心数 2 核足够,太多反而掩盖资源竞争问题
--disk 磁盘路径与大小 qcow2 格式支持快照,必须用这个
--network 网络模式 默认 NAT,需要集群实验时改用 bridge

2.2.3 快照管理——实验的「后悔药」

快照是故障注入实验的灵魂。没有快照,你每次实验都得重装系统,那效率太低了。KVM 的快照分两种:

  • 磁盘快照:只保存磁盘状态,速度快,占用空间小
  • 系统快照:保存整个虚拟机状态(内存+磁盘),恢复时完全回到那一刻
# 创建磁盘快照(实验前必须做)
virsh snapshot-create-as fault-injection-lab \
  --name "clean-state" \
  --description "干净的 Ubuntu 22.04 初始状态"

# 查看快照列表
virsh snapshot-list fault-injection-lab

# 恢复快照(实验搞坏了?一键恢复)
virsh snapshot-revert fault-injection-lab --snapshotname "clean-state"

# 删除快照
virsh snapshot-delete fault-injection-lab --snapshotname "old-snapshot"

⚠️ 重要提醒:快照不是备份!快照文件与原始磁盘文件是依赖关系,删了原始文件,快照就废了。我曾经吃过这个亏,实验数据全丢了。建议定期导出完整镜像。

2.3 Docker 容器环境配置

Docker 适合做应用层的故障注入。比如模拟微服务之间的网络延迟、CPU 资源争抢、磁盘 I/O 限速。相比 KVM,Docker 启动快、资源占用小,但隔离性弱一些。

2.3.1 安装 Docker

# 安装 Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 将当前用户加入 docker 组
sudo usermod -aG docker $USER

# 验证安装
docker --version
docker run hello-world

2.3.2 构建故障注入实验镜像

我习惯把实验工具直接打包进镜像,省得每次启动容器都要安装。

# Dockerfile
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    stress-ng \          # CPU/内存压力工具
    iproute2 \           # 网络故障注入
    iptables \           # 防火墙规则
    tc \                 # 流量控制
    fio \                # 磁盘 I/O 测试
    sysbench \           # 系统基准测试
    && rm -rf /var/lib/apt/lists/*

CMD ["/bin/bash"]
# 构建镜像
docker build -t fault-injection-lab:1.0 .

# 启动容器(带资源限制)
docker run -it --rm \
  --name fault-container \
  --cpus="1.5" \
  --memory="2g" \
  --memory-swap="2g" \
  --blkio-weight=500 \
  fault-injection-lab:1.0
资源限制参数 作用 故障注入场景
--cpus 限制 CPU 使用率 模拟 CPU 资源争抢
--memory 限制内存上限 触发 OOM Killer
--blkio-weight 限制磁盘 I/O 优先级 模拟磁盘性能瓶颈
--cap-add 添加系统权限 注入网络/内核故障

2.3.3 Docker 快照与镜像管理

Docker 没有 KVM 那种「一键快照」的功能,但我们可以用 docker commit 保存容器状态为镜像,效果类似。

# 在容器内做完配置后,保存为镜像
docker commit fault-container fault-injection-lab:experiment-1

# 查看镜像列表
docker images

# 从保存的镜像启动新容器
docker run -it --rm fault-injection-lab:experiment-1

# 导出镜像为 tar 文件(备份用)
docker save fault-injection-lab:experiment-1 -o experiment-1.tar

# 导入镜像
docker load -i experiment-1.tar

我的经验:Docker 的 commit 操作会丢失容器运行时的内存状态,所以它只适合「配置快照」,不适合「运行时快照」。如果你需要保存完整的运行时状态,还是得用 KVM。

2.4 KVM vs Docker:怎么选?

很多同学问我:「老师,到底用哪个?」我的回答是:看你要注入什么故障。

对比维度 KVM 虚拟机 Docker 容器
隔离级别 完全隔离(独立内核) 进程级隔离(共享内核)
启动速度 分钟级 秒级
资源占用 高(完整 OS) 低(仅进程)
快照能力 强(磁盘+内存) 弱(仅文件系统)
适用故障类型 内核故障、硬件故障模拟 应用故障、网络故障、资源争抢
网络拓扑 复杂(多网卡、桥接) 简单(Docker Network)

我的建议:做内核级别的故障注入(比如内存位翻转、系统调用劫持),用 KVM。做微服务、分布式系统的故障注入(比如网络延迟、服务熔断),用 Docker。两个都学会,才是完整的安全测试工程师。

2.5 实验环境拓扑图

下面这张图展示了我们课程中会用到的实验环境架构。KVM 负责底层系统故障注入,Docker 负责上层应用故障注入,两者通过虚拟网络连接,形成完整的故障注入实验平台。

故障注入实验环境拓扑图 故障注入实验环境拓扑图 宿主机(Ubuntu 22.04 / Linux Kernel 6.x) KVM 虚拟机 内核故障注入 内存位翻转模拟 磁盘 I/O 错误注入 快照管理(virsh) Docker 容器 网络延迟/丢包注入 CPU 资源争抢模拟 内存压力测试 镜像快照(docker commit) 虚拟网络 说明:KVM 负责底层系统故障注入,Docker 负责上层应用故障注入,两者通过虚拟网络协同工作

2.6 环境验证脚本

环境搭好了,怎么知道能不能用?我写了个简单的验证脚本,跑一遍就知道环境是否就绪。

#!/bin/bash
# 环境验证脚本

echo "=== 1. 检查 KVM ==="
virsh list --all || echo "KVM 未安装或服务未启动"

echo "=== 2. 检查 Docker ==="
docker info --format '{{.ServerVersion}}' || echo "Docker 未安装或服务未启动"

echo "=== 3. 创建测试虚拟机 ==="
virt-install --name test-vm --ram 512 --vcpus 1 \
  --disk size=5 --os-variant ubuntu22.04 \
  --graphics none --print-xml > /dev/null && \
  echo "KVM 创建虚拟机成功"

echo "=== 4. 创建测试容器 ==="
docker run --rm alpine echo "Docker 容器运行成功"

echo "=== 5. 测试快照功能 ==="
virsh snapshot-create-as test-vm --name "test-snapshot" && \
  echo "KVM 快照创建成功"
virsh snapshot-delete test-vm --snapshotname "test-snapshot"

echo "=== 环境验证完成 ==="

⚠️ 注意:验证脚本中的测试虚拟机和容器会在验证后自动清理。但如果你手动创建了实验环境,记得用 virsh undefine 和 docker rm 清理,别让测试环境占用资源。

2.7 避坑指南

最后,分享几个我踩过的坑,希望能帮你省点时间:

  • 磁盘格式:KVM 一定要用 qcow2 格式,raw 格式不支持快照。我曾经图省事用了 raw,结果做实验时没法回滚,重装了三次系统。
  • 网络模式:做分布式故障注入实验时,记得用 bridge 模式,NAT 模式会导致容器间网络隔离,没法模拟真实的微服务通信。
  • 资源预留:给虚拟机分配资源时,别把宿主机资源全占满。留 1 个 CPU 核心和 2GB 内存给宿主机,否则宿主机卡死,连恢复快照都操作不了。
  • 快照命名:养成好习惯,快照名要带时间戳和实验编号。比如 exp-20240615-memory-flip-before,不然一周后你根本分不清哪个快照是哪个。