操作系统部署方案

操作系统部署方案
[TOC]
🔄 VMware 用户视角对比
概念映射
| VMware | KVM | 原理 |
|---|---|---|
| 完整克隆 (Full Clone) | ⚠️ virt-clone / 完整拷贝 | 完全独立,占用完整空间 |
| 链接克隆 (Linked Clone) | qemu-img create -b 增量盘 | COW,共享 base,只存差异 |
| 快照 (Snapshot) | virsh snapshot | 保存状态,可回滚 |
| 模板 (Template) | base 镜像 / virt-sysprep | 不可变基础镜像 |
VMware 链接克隆 = KVM COW 增量盘
🧱 存储目录规划
在 Virt02 中我们聊过 COW(Copy-On-Write)的概念——多台 VM 共享同一个后端盘(base image),每台只存自己的差异数据

✅ 三层目录设计
| 目录 | 用途 | 特点 |
|---|---|---|
base/ | 存放官方原始镜像 | 只读,不直接运行 VM 作为所有 VM 的后端盘 |
images/ | VM 运行时使用的磁盘 | 差异盘,通过 qemu-img create -b 链接到 base每台 VM 一个,只存差异数据 |
seed/ | cloud-init 配置光盘 | 每个 VM 一个 iso,挂载后首次启动自动配置 设置主机名、IP、SSH 公钥等 |
mkdir -p /home/jiuzhao/storage/{base,images,seed}/home/jiuzhao/storage/├── base/│ ├── rocky10.qcow2 # 官方下载的原始镜像(只读)│ └── ubuntu2404.qcow2│ └── Rocky.img # 传统ISO镜像制作的后端盘├── images/│ ├── web01.qcow2 # 差异盘,仅存差异数据│ ├── web02.qcow2└── seed/ ├── web01-seed.iso # cloud-init 配置光盘 └── web02-seed.iso📌 关键原则:base 不动、images 是差异层、seed 配置每台 VM 的独有属性 不管创建 10 台还是 100 台 VM,base 只需一份,镜像管理清晰
和默认路径的关系
| 路径 | 说明 |
|---|---|
/var/lib/libvirt/images/ | libvirt default 存储池路径 |
/home/jiuzhao/storage/ | 我们自己的镜像仓库,统一管理 base/images/seed |
💡 用 virsh pool-define-as 注册为 libvirt 的存储池(详见 Virt03 存储池章节)
路线 A:传统 ISO 安装
流程:创建空白后端盘 → virt-install 装系统 (ISO/FTP) → 系统内优化配置 → virt-sysprep 清理镜像 → 导出XML模板并清理 → 创建前端盘 → 批量部署
这条路线在 Virt02 模版机制作 中有完整记录 包括 base_config.sh 优化脚本、qemu-guest-agent、cloud-init、console 串口、开机自动扩容、virt-sysprep 清理、template.xml 制作与批量创建
✨操作命令速查
1)创建空白后端盘qemu-img create -f qcow2 /home/jiuzhao/storage/base/Rocky.img 15Gvirsh pool-refresh base# 刷新base存储池
2)virt-install 安装系统virt-install --name Rocky --memory 2048 --vcpus 2 \ --osinfo rocky9 \ --disk path=/home/jiuzhao/storage/base/Rocky.img \ --cdrom /home/jiuzhao/img/Rocky-10.2-x86_64-minimal.iso \ --network network=default --graphics vnc,listen=0.0.0.0 --wait -1
3)进入系统 → 优化配置# 换 yum/dnf 源、装工具包、时间同步、关 SELinux/Firewalld、改 PS1# dnf install -y qemu-guest-agent cloud-init cloud-utils-growpart# grubby 配置 console 串口(详见 Virt02)# 编写 /etc/rc.d/rc.local 开机自动扩容# growpart → pvresize → lvextend → xfs_growfs⚠️ 注意核对实际 LV 名,通过 lsblk 查看poweroff
4)virt-sysprep 清理模板sudo virt-sysprep -d Rocky⚠️ Ubuntu 上 virt-sysprep 需要 sudo
5)导出并精简 template.xmlvirsh dumpxml Rocky > ./storage/Rocky-template.xml✅️ 精简原则:删 UUID、MAC、PCIe root port、audio/watchdog/tablet📌 只保留核心 7 件:磁盘 + 网络 + console + Guest Agent + VNC + memballoon + rng# 名称<name>Rocky-template</name># 前端盘<source file="/home/jiuzhao/storage/images/Rocky-template.qcow2"/>
6)创建前端盘virsh vol-create-as \ --pool "数据盘" \ --name node1.qcow2 \ --capacity 30G \ --format qcow2 \ --backing-vol /home/jiuzhao/storage/base/Rocky.img \ --backing-vol-format qcow2# node1.qcow2 卷会被自动放到该存储池(数据盘)对应的目录下# 后端盘(指定路径)jiuzhao@Ubuntu ~$ virsh vol-list "数据盘" Name Path--------------------------------------------------------- node1.qcow2 /home/jiuzhao/storage/images/node1.qcow2
7)从模板生成 XML 并定义 VMsudo bash -c "sed 's@Rocky-template@node1@' /home/jiuzhao/storage/Rocky-template.xml > /etc/libvirt/qemu/node1.xml"# libvirt 自动生成 UUID 和 MAC,不会冲突jiuzhao@Ubuntu images$ cd /etc/libvirt/qemu/jiuzhao@Ubuntu qemu$ lsnetworks node1.xml# 这里我取消定义了模板机的xml文件,只保留了后端磁盘virsh define /etc/libvirt/qemu/node1.xml
8)启动验证virsh start --console node1# 验证磁盘扩容 `lsblk`# 分区撑满指定大小,LV 和文件系统已扩容🌐 常用云镜像下载
Ubuntu 官方为云环境提供预配置的 cloud 镜像,基于 qcow2 格式,即下即用
官方下载:https://cloud-images.ubuntu.com/releases/
Ubuntu 每个版本除了版本号,还有一个动物代号(codenames):
| 版本 | 代号 | 类型 |
|---|---|---|
| 24.04 LTS | Noble Numbat | 长期支持 ✅️ |
| 22.04 LTS | Jammy Jellyfish | 长期支持 ✅️ |
| 20.04 LTS | Focal Fossa | 长期支持 |
当前我们使用的是 Ubuntu 24.04 LTS (Noble Numbat)
https://cloud-images.ubuntu.com/releases/noble/release/
# 下载 Ubuntu 24.04 云镜像wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img \ -O /home/jiuzhao/ubuntu2404.qcow2# -O 重命名为易识别的文件名Ubuntu 的 .img 和 .qcow2 是同一格式,直接下载后重命名即可
qemu-img info 查看会显示 file format: qcow2
Rocky Linux 官方也提供 GenericCloud 镜像:
官方下载:https://download.rockylinux.org/pub/rocky/10.2/images/x86_64/
# 下载 Rocky Linux 10 云镜像wget https://download.rockylinux.org/pub/rocky/10.2/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2 \ -O /home/jiuzhao/storage/base/rocky10.qcow2其他常用 Linux 云镜像
| 发行版 | 下载地址 |
|---|---|
| Debian | https://cloud.debian.org/images/cloud/ |
| CentOS Stream | https://cloud.centos.org/centos/ |
| Fedora | https://dl.fedoraproject.org/pub/fedora/linux/releases/ |
路线 B:☁️ 云镜像
官方预装好的 Cloud Image,cloud-init 自动入职——不进系统、不跑脚本、不 virt-sysprep
和路线 A 的本质区别
| 路线 A(传统 ISO) | 路线 B(云镜像) | |
|---|---|---|
| 获取系统 | qemu-img create + virt-install 手动装 | wget 一行下载 |
| growpart / cloud-init / qemu-ga / console | 全部手动安装 | 出厂自带,一个都不需要装 |
| 优化配置 | 进系统敲命令 / 注入 bash 脚本 / virt-sysprep 清洗 | 全写在 seed.iso 的 user-data 里,首次启动自动执行 |
| 模板 | 手动做镜像 → virt-sysprep → 导出 XML | base 镜像永不变,改配置 = 改 user-data |
| 加新 VM | 创建增量盘 + XML + sed | 一模一样,但 cloud-init 帮你把脏活全干了 |
cloud-init 就是每台新虚拟机的”自动入职流程”——首次启动时读取 seed.iso,执行 user-data 中的所有配置,下次启动不再重复
流程总览
下载官方 qcow2 → base/ │ ├─ virt-customize 最小预设(仅设 root 密码,1 行) │ ├─ 编写 seed.iso(user-data + meta-data) │ ├─ 换阿里云源 / DNS 223.5.5.5 │ ├─ 装包 / 禁用 IPv6 / 关 SELinux / 时钟同步 / PS1 │ ├─ SSH 公钥 / 时区 / 主机名 │ └─ 一切定制化写在这里,不进系统 │ ├─ genisoimage 生成 seed.iso ├─ virsh vol-create-as 创建 COW 增量盘 └─ 模板 XML → sed 替换 → define → start → 启动即用① 下载云镜像
# Rocky Linux 10wget https://download.rockylinux.org/pub/rocky/10.2/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2 \ -O /home/jiuzhao/storage/base/rocky10.qcow2
# Ubuntu 24.04wget https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img \ -O /home/jiuzhao/storage/base/ubuntu2404.qcow2② virt-customize 最小预设
sudo virt-customize -a /home/jiuzhao/storage/base/rocky10.qcow2 \ --root-password password:1# 云镜像没有预设密码,用一行命令设好# 其他所有配置(换源、装包、DNS)都交给 cloud-init,不要在这里做为什么只设密码,不注入脚本? 路线 A 需要 virt-customize 注入 bash 脚本,是因为传统 ISO 装机后没有自动化机制。 云镜像出厂自带 cloud-init——把脚本逻辑全部迁移到 user-data,享受声明式配置的幂等性和可重复性。
③ 编写 seed.iso 配置
新建两个文件:/home/jiuzhao/storage/seed/user-data 和 meta-data
⚠️ 文件名必须是
user-data和meta-data,cloud-init 源码写死的
meta-data:
# 实例唯一标识符instance-id: cloud-test# 主机名(优先级更高)local-hostname: cloud-testuser-data:
#cloud-config
timezone: Asia/Shanghai
# root 密码 + SSH 公钥ssh_pwauth: truechpasswd: list: | root:1 expire: false
users: - name: root ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHce3fbO8q7VgnnzqgNzBoJrrP/nTl69ACyH5GTe/7b3 jiuzhao@Ubuntu
# 装包 — 全部走 runcmd(等换源后再装,避免默认源超时)# 写入配置文件write_files: # DNS 直通(NetworkManager 全局 DNS,重启不丢) - path: /etc/NetworkManager/conf.d/99-dns.conf permissions: '0644' content: | [global-dns-domain-*] servers=223.6.6.6,114.114.114.114
# /etc/resolv.conf 兜底(cloud-init 阶段用) - path: /etc/resolv.conf permissions: '0644' content: | nameserver 223.6.6.6 nameserver 114.114.114.114
# VM 内 TCP 缓冲区 + 禁用 IPv6 - path: /etc/sysctl.d/99-fast-network.conf permissions: '0644' content: | net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216
# chrony 阿里云时钟同步 - path: /etc/chrony.conf permissions: '0644' content: | server ntp.aliyun.com iburst sourcedir /run/chrony-dhcp driftfile /var/lib/chrony/drift makestep 1.0 3 rtcsync ntsdumpdir /var/lib/chrony logdir /var/log/chrony
# PS1 配色 - path: /etc/profile.d/ps1.sh permissions: '0644' content: | PS1="[\[\e[34;1m\]\u@\[\e[0m\]\[\e[32;1m\]\H\[\e[0m\]\[\e[31;1m\] \W\[\e[0m\]]\\$ "
# ASCII 动态欢迎(登录时 figlet 显示主机名) - path: /etc/profile.d/color-welcome.sh permissions: '0755' content: | #!/bin/bash hostname | figlet -f big | lolcat
# 首次启动按顺序执行runcmd: # 换阿里云 dnf 源 - sed -e 's|^mirrorlist=|#mirrorlist=|g' -e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' -i.bak /etc/yum.repos.d/[Rr]ocky*.repo - dnf clean all - dnf makecache # 系统更新 + 基础包(baseos 自带) - dnf update -y - dnf install -y vim-enhanced net-tools bash-completion tree wget lrzsz tar unzip sysstat bc telnet # 装 EPEL + EPEL 包(htop / figlet 在 EPEL) - dnf install -y epel-release - dnf install -y htop figlet sl cowsay lolcat # 关闭 SELinux - sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config # SSH 优化 - sed -i '/^#UseDNS/c\UseDNS no' /etc/ssh/sshd_config - sed -i '/^#GSSAPIAuthentication/c\GSSAPIAuthentication no' /etc/ssh/sshd_config # 文件描述符 - echo "* - nofile 65535" >> /etc/security/limits.conf # 应用内核参数 + 重启 chronyd - sysctl -p /etc/sysctl.d/99-fast-network.conf - systemctl restart chronyd④ 生成 seed.iso
cd /home/jiuzhao/storage/seed/
genisoimage -input-charset utf-8 -output cloud-test-seed.iso \ -volid cidata -joliet -rock user-data meta-data
# -volid cidata:卷标必须叫 cidata,cloud-init 靠这个识别# 验证内容isoinfo -R -f -i cloud-test-seed.iso⑤ 创建 COW 增量盘
virsh vol-create-as \ --pool images \ --name cloud-test.qcow2 \ --capacity 30G \ --format qcow2 \ --backing-vol /home/jiuzhao/storage/base/rocky10.qcow2 \ --backing-vol-format qcow2⑥ XML 模板
模板已放在 ~/storage/Cloud-template.xml,包含 seed cdrom:
<domain type='kvm'> <name>Cloud-template</name> <memory unit='KiB'>2097152</memory> <currentMemory unit='KiB'>2097152</currentMemory> <vcpu placement='static'>2</vcpu> <os> <type arch='x86_64' machine='pc-q35-noble'>hvm</type> <boot dev='hd'/> </os> <features> <acpi/> <apic/> </features> <cpu mode='host-passthrough' check='none' migratable='on'/> <clock offset='utc'> <timer name='rtc' tickpolicy='catchup'/> <timer name='pit' tickpolicy='delay'/> <timer name='hpet' present='no'/> </clock> <on_poweroff>destroy</on_poweroff> <on_reboot>restart</on_reboot> <on_crash>destroy</on_crash> <devices> <emulator>/usr/bin/qemu-system-x86_64</emulator>
<!-- 磁盘:占位符,sed 替换 --> <disk type='file' device='disk'> <driver name='qemu' type='qcow2'/> <source file='/home/jiuzhao/storage/images/Cloud-template.qcow2'/> <target dev='vda' bus='virtio'/> </disk>
<!-- cloud-init seed --> <disk type="file" device="cdrom"> <driver name="qemu" type="raw"/> <source file="/home/jiuzhao/storage/seed/Cloud-template-seed.iso"/> <target dev="sda" bus="sata"/> <readonly/> </disk>
<!-- 网络 --> <interface type='network'> <source network='default'/> <model type='virtio'/> </interface>
<!-- console 串口 --> <serial type='pty'> <target type='isa-serial' port='0'> <model name='isa-serial'/> </target> </serial> <console type='pty'> <target type='serial' port='0'/> </console>
<!-- Guest Agent --> <channel type='unix'> <target type='virtio' name='org.qemu.guest_agent.0'/> </channel>
<!-- VNC --> <graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'> <listen type='address' address='0.0.0.0'/> </graphics> <video> <model type='virtio' heads='1' primary='yes'/> </video>
<memballoon model='virtio'/> <rng model='virtio'> <backend model='random'>/dev/urandom</backend> </rng> </devices></domain>⑦ sed 替换 + 启动
sudo bash -c "sed 's#Cloud-template#cloud-test#g' /home/jiuzhao/storage/Cloud-template.xml > /etc/libvirt/qemu/cloud-test.xml"sudo virsh define /etc/libvirt/qemu/cloud-test.xml
# console 观察 cloud-init 输出virsh start --console cloud-test⑧ 验证
[root@cloud-test ~]# hostnamecloud-test
[root@cloud-test ~]# cat /etc/resolv.confnameserver 223.5.5.5nameserver 223.6.6.6
[root@cloud-test ~]# lsblkNAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTSsr0 11:0 1 366K 0 rom # ← seed.iso 光盘vda 252:0 0 30G 0 disk├─vda1 252:1 0 2M 0 part├─vda2 252:2 0 200M 0 part /boot/efi├─vda3 252:3 0 1000M 0 part /boot└─vda4 252:4 0 28.8G 0 part / # ← 分区自动撑满 30G
[root@cloud-test ~]# cat ~/.ssh/authorized_keysssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMAt2bYeCYLeKJ7m3IJfKSQkUHeql9HUPQKUIYjV4MHo jiuzhao@Ubuntu批量创建新 VM
新加一台 VM:复制一份 seed 配置(改 hostname)→ 增量盘 → sed → done
cd /home/jiuzhao/storage/seed/
# 1. 改主机名sed -i 's/cloud-test/web01/' meta-datagenisoimage -input-charset utf-8 -output web01-seed.iso -volid cidata -joliet -rock user-data meta-data
# 2. 增量盘virsh vol-create-as --pool "数据盘" --name web01.qcow2 --capacity 30G \ --format qcow2 --backing-vol /home/jiuzhao/storage/base/rocky10.qcow2 --backing-vol-format qcow2
# 3. XML + definesudo bash -c "sed 's#Cloud-template#web01#g' /home/jiuzhao/storage/Cloud-template.xml > /etc/libvirt/qemu/web01.xml"sudo virsh define /etc/libvirt/qemu/web01.xmlvirsh start web01base 镜像永远不动。 想加软件?改 user-data 的 packages 列表。想换源?改 runcmd 里的 sed 命令。
10 台 VM 全装 nginx?一行 yaml 搞定,重新生成 seed.iso 即可
和路线 A 的最终对比
| 操作 | 路线 A(传统 ISO) | 路线 B(云镜像) |
|---|---|---|
| 获取基础系统 | qemu-img create + virt-install 装半小时 | wget 一分钟 |
| 系统优化 | 进系统敲命令 / virt-customize 注入脚本 | user-data 声明式,首次启动自动跑 |
| 模板制作 | virt-sysprep 清洗 ±15 分钟 | base 镜像不动,干净永久 |
| 新增 VM | 增量盘 + XML + sed | 同样操作,但 cloud-init 接管所有初始化 |
| 修改全局配置 | 重做模板(virt-sysprep 再来一遍) | 改 user-data,重建 seed.iso |
ASCII艺术字符
sudo yum -y install figlet lolcat
2)编写动态脚本vim /etc/profile.d/color-welcome.sh#!/bin/bash
# 核心命令:figlet 生成大字符 + lolcat 上彩虹色hostname | figlet -f big | lolcat -fs# Ubuntu系统:hostname | figlet -f big | /usr/games/lolcat -sf
# 附加信息(可省略)# echo -e "\033[1;33m登录时间: $(date '+%Y-%m-%d %H:%M:%S')\033[0m"# echo -e "\033[1;32m当前在线用户: $(who | wc -l)\033[0m"chmod +x /etc/profile.d/color-welcome.sh☁️ 从ISO传统镜像制作Ubuntu云镜像
1)制作后端盘virsh vol-create-as \ --pool base \ --name Ubuntu.img\ --capacity 10GB \ --format qcow2# 由 libvirtd (root) 代理创建
2)安装镜像virt-install --name Ubuntu --memory 2048 --vcpus 2 \ --osinfo ubuntu24.04 \ --disk path=/home/jiuzhao/storage/base/Ubuntu.img \ --cdrom /home/jiuzhao/img/ubuntu-24.04.4-live-server-amd64.iso \ --network network=default --graphics vnc,listen=0.0.0.0 --wait -1
3)系统优化# 执行之前的脚本即可 cloud-init ✅ 已装,Ubuntu Server 默认带 growpart ✅ 已装,来自 cloud-guest-utils(cloud-init 的自动依赖) # 不需要单独装 cloud-utils-growpart cloud-init (Ubuntu Server 自带) └── Depends: cloud-guest-utils ← 自动装上 └── Depends: cloud-utils⚠️ 手动装:apt install -y qemu-guest-agent
4)console 串口# 编辑 GRUB 加入 console 参数sed -i 's/^GRUB_CMDLINE_LINUX=""/GRUB_CMDLINE_LINUX="console=ttyS0,115200n8"/' /etc/default/grubupdate-grub# 启用串口登录systemctl enable --now serial-getty@ttyS0.service# 重启后验证rebootvirsh console Ubuntu # 应该看到登录提示符Ubuntu 自动扩容
Rocky 和 Ubuntu 传统 ISO 都走 LVM,扩容链完全一样——4 步,只差最后一步的文件系统命令:
| 发行版 | 扩容链 | 差异 |
|---|---|---|
| Rocky | growpart → pvresize → lvextend → xfs_growfs | XFS,xfs_growfs |
| Ubuntu | growpart → pvresize → lvextend → resize2fs | ext4,resize2fs |
Rocky 用
rc.local,Ubuntu 用 systemd oneshot——因为/etc/rc.local在 Ubuntu 24.04 默认不存在且需要额外chmod +x,systemd 更干净
#!/bin/bashset -e
[ -f /var/lib/growpart-done ] && exit 0# 判断标记文件是否存在(由services服务成功运行后创建)# 一旦执行过,这个文件被创建出来,脚本直接成功退出(exit 0)
DISK=/dev/vdaPARTNUM=3PART=${DISK}${PARTNUM}VG=ubuntu-vgLV=ubuntu-lv
growpart $DISK $PARTNUM || truepartprobe $DISK# 强制内核重读分区表pvresize $PARTlvextend -l +100%FREE /dev/$VG/$LV || trueresize2fs /dev/$VG/$LV || true⚠️ 注意核对 VG/LV 名:vgs / lvs 查看当前值。Ubuntu 24.04 默认 VG=ubuntu-vg,LV=ubuntu-lv
[Unit]Description=Auto-expand LVM root partition on first bootAfter=local-fs.target# 确保本地文件系统已经挂载好(分区能正常读写)
[Service]Type=oneshotExecStart=/usr/local/sbin/growpart-lvm.shExecStartPost=/usr/bin/touch /var/lib/growpart-done# 主进程启动成功后,才会创建这个空文件,作为“已完成”的标记,之后开机跳过# 脚本执行失败,是不会创建这个文件的# 标记文件绑脚本成功,能知道脚本到底跑通没RemainAfterExit=yes# 即使服务进程退出了,systemd 仍把它视为“已激活”状态# 方便依赖它的后续服务查询状态
[Install]WantedBy=multi-user.target| 参数 | 触发时机 | 执行条件 | 失败影响 | 典型用途 |
|---|---|---|---|---|
ExecStartPost | 主进程启动成功后 | 主进程退出码为 0 | 影响服务状态,失败则服务标记为 failed | 成功后必须完成的收尾(如写标记文件) |
ExecStartPost=- | 主进程启动成功后 | 主进程退出码为 0 | 不影响服务状态,失败只记日志 | 成功后非关键的辅助操作(如发通知) |
ExecStopPost | 主进程停止后(含成功/失败/被kill) | 无条件执行 | 不影响服务状态,失败只记日志 | 无论成败都必须做的清理(如解锁、删临时文件) |
# 1. 写入脚本并赋权vim /usr/local/sbin/growpart-lvm.shchmod +x /usr/local/sbin/growpart-lvm.sh
# 2. 写入服务vim /etc/systemd/system/growpart-lvm.service
# 3. 启用systemctl daemon-reloadsystemctl enable growpart-lvm.service其他优化
① 禁用 IPv6 + 扩充 TCP buffercat > /etc/sysctl.d/99-network.conf << 'EOF'# 禁用 IPv6net.ipv6.conf.all.disable_ipv6 = 1net.ipv6.conf.default.disable_ipv6 = 1# TCP 缓冲区net.core.rmem_max = 16777216net.core.wmem_max = 16777216net.core.rmem_default = 262144net.core.wmem_default = 262144EOF
sysctl -p /etc/sysctl.d/99-network.conf
② DNS 直通配 systemd-resolved:cat > /etc/systemd/resolved.conf << 'EOF'[Resolve]DNS=223.5.5.5 223.6.6.6FallbackDNS=114.114.114.114DNSOverTLS=noEOF
systemctl restart systemd-resolved# 只改 /etc/systemd/resolved.conf,不要动 /etc/resolv.conf# 原因很简单——Ubuntu 的 DNS 架构不是直写文件,而是代理模式:/etc/resolv.conf → symlink → /run/systemd/resolve/stub-resolv.conf ↑ nameserver 127.0.0.53 │ systemd-resolved 本地缓存代理 │ /etc/systemd/resolved.conf ← 这里决定上游 DNS# 直接改 /etc/resolv.conf 是没用的——它是 link,systemd-resolved 下次启动就会覆盖- Ubuntu 安装器(subiquity)的默认行为
- 装完后写了一个 /etc/cloud/cloud-init.disabled,并且把 datasource 锁成 None
- cloud-init 状态显示 status: disabled
- 后果:就算你挂 seed.iso,cloud-init 也不会读——它在启动时就跳过了
'修复计划(virt-sysprep 之前)'1)删除 disabled 标记——恢复 cloud-init 运行rm -f /etc/cloud/cloud-init.disabled
2)修复 datasource——允许 NoCloud(读取 seed.iso)sed -i '/datasource_list:/d' /etc/cloud/cloud.cfg.d/*.cfgsed -i '/^datasource:/,/^[a-z]/d' /etc/cloud/cloud.cfg.d/99-installer.cfg
3)重置 cloud-init 状态(清除 instance-id,下次启动视为新机器)cloud-init clean --machine-id# ⚠️ 注:cloud-init clean 后会删除 SSH host keys,virt-sysprep 也会清 ⭕ 日志里看到的 growpart: 和 resize_rootfs: false 保持不变 ⭕ 因为扩容我们走 systemd 服务,不与 cloud-init 的 growpart 模块冲突# 改完就能进 virt-syspreppoweroff# 关机
4)virt-sysprep 清理模板sudo virt-sysprep -d Ubuntu✨注意事项
# 后面如果要修改模板机时,一些收尾工作💼
1)收尾(顺序不能错)rm -f /var/lib/growpart-done # ①删标记cloud-init clean --machine-id # ②重置 cloud-init> /root/.bash_history # ③清命令历史poweroff # ④关机
2)virt-sysprep(清理 machine-id、SSH host keys、日志等)sudo virt-sysprep -d Ubuntu# 这是 libguestfs 的老问题——virt-sysprep 无法正确挂载 LVM 根文件系统# 在 Ubuntu LVM 安装上必炸 --> 替代方案:手动清洗(在 VM 里直接做)
# 删除标记 & 重置 cloud-initrm -f /var/lib/growpart-donecloud-init clean --machine-id
# 清 machine-id(下次启动 cloud-init 当作新机器)rm -f /etc/machine-idecho "" > /etc/machine-id 2>/dev/null || true
# 清日志> /var/log/lastlog> /var/log/wtmprm -rf /tmp/*rm -rf /var/tmp/*rm -f /var/lib/systemd/random-seed
# 清 SSH host keys(首次启动自动重新生成)rm -f /etc/ssh/ssh_host_*_key* && ssh-keygen -A
# 清 cloud-init 缓存rm -rf /var/lib/cloud/instances/*
# 清 shell 历史> /root/.bash_history
# 关机poweroff批量创建
1)创建 COW 增量盘virsh vol-create-as --pool images --name Ceph01.qcow2 \ --capacity 30G --format qcow2 \ --backing-vol /home/jiuzhao/storage/base/Ubuntu.img \ --backing-vol-format qcow2
2)准备 seed.iso(最小化)cd /home/jiuzhao/storage/seed/# 只写 hostname + SSH,其他全在 base(基础盘里)
3)种子文件cat > user-data <<'EOF'#cloud-configtimezone: Asia/Shanghai
ssh_pwauth: truechpasswd: list: | root:1 expire: false
write_files: - path: /root/.ssh/authorized_keys permissions: '0600' owner: 'root:root' content: | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHce3fbO8q7VgnnzqgNzBoJrrP/nTl69ACyH5GTe/7b3 jiuzhao@UbuntuEOF
cat > meta-data <<'EOF'instance-id: Ceph01local-hostname: Ceph01EOF
genisoimage -input-charset utf-8 -output Ceph01-seed.iso \ -volid cidata -joliet -rock user-data meta-data
4)XML配置清单——Cloud-template.xml 可以直接用sudo bash -c "sed 's#Cloud-template#Ceph01#g' /home/jiuzhao/storage/Cloud-template.xml > /etc/libvirt/qemu/Ceph01.xml"sudo virsh define /etc/libvirt/qemu/Ceph01.xml
5)启动验证# console 观察 cloud-init 输出virsh start --console Ceph01| 检查项 | 结果 |
|---|---|
| 主机名 | Ceph01 ✅ |
| 扩容 | vda 30G → LV 28.2G ✅ |
| growpart 服务 | active (exited) SUCCESS ✅ |
| cloud-init | done ✅ |
| SSH 公钥 | 注入成功 + 免密登录验证通过 ✅ |
| DNS | Global 223.5.5.5 ✅ |
| IPv6 禁用 | 1 ✅ |
| TCP buffer | 16MB ✅ |
| 串口 / qemu-ga | active / active ✅ |
| SSH host keys | 3 个 ✅ |
# 固定IP# vim /etc/netplan/50-cloud-init.yamlnetwork: version: 2 ethernets: enp1s0: match: macaddress: "52:54:00:c6:b9:fa" dhcp4: false dhcp6: false set-name: "enp1s0" addresses: - 10.0.0.3/24 # IP地址/子网掩码位数 routes: - to: default via: 10.0.0.1 # 网关,virbr0 的 IP nameservers: addresses: - 223.6.6.6 - 114.114.114.114
netplan apply
# 清屏sudo apt install kitty-terminfo# 下载 Kitty 的 terminfo 文件并安装wget https://raw.githubusercontent.com/kovidgoyal/kitty/master/terminfo/kitty.terminfotic -x kitty.terminfo
# 验证echo $TERM # 应该显示 xterm-kittyclear # 应该正常工作文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!















