测试了下Ubuntu宿主机上使用KVM启动Cisco 8000v+ Linux PC, 记录下。
实验拓扑和地址规划 #
C8000v添加两个接口并连接两台Linux PC,两台PC分别属于不同的网段。
创建两个隔离的二层网络:
net-a:给 pc1 使用
- pc1:192.168.10.10/24
- 网关:192.168.10.1(C8000V 的 net-a 接口)
net-b:给 pc2 使用
- pc2:192.168.20.10/24
- 网关:192.168.20.1(C8000V 的 net-b 接口)
准备工作 #
准备8000v和Linux的qcow2镜像文件, 8000v选择最新的版本c8000v-universalk9_16G_serial.17.15.04c.qcow2。 串口(serial), 16G的磁盘可以拥有更大的bootflash的空间。Linux 选择ubuntu的Cloud image Ubuntu 24.04 LTSt, ubuntu-24.04-server-cloudimg-amd64.img 下载后修改名字为qcow2即可。
创建目录分别放置下载的镜像:
sudo mkdir -p /var/lib/libvirt/images/lab
sudo mkdir -p /var/lib/libvirt/images/c8000v
sudo chown -R libvirt-qemu:kvm /var/lib/libvirt/images
sudo chmod -R 755 /var/lib/libvirt/images
Ubuntu 宿主机安装虚拟化组件:
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients virtinst libguestfs-tools openssl
检查命令:
sudo virsh list --all
sudo virsh net-list --all
KVM启动过程 #
创建两个 isolated 网络(net-a / net-b) #
用 forward mode=‘none’ 创建纯隔离二层网络(不做 NAT 出网),只用于实验互联。
net-a(virbr10)
cat > /tmp/net-a.xml <<'EOF'
<network>
<name>net-a</name>
<bridge name='virbr10' stp='on' delay='0'/>
<forward mode='none'/>
</network>
EOF
sudo virsh net-define /tmp/net-a.xml
sudo virsh net-start net-a
sudo virsh net-autostart net-a
net-b(virbr20)
cat > /tmp/net-b.xml <<'EOF'
<network>
<name>net-b</name>
<bridge name='virbr20' stp='on' delay='0'/>
<forward mode='none'/>
</network>
EOF
sudo virsh net-define /tmp/net-b.xml
sudo virsh net-start net-b
sudo virsh net-autostart net-b
验证:
sudo virsh net-list --all
离线注入PC认证信息和静态IP #
准备PC1/PC2的qcow2 镜像, 从上传的base镜像进行复制:
sudo cp -f /var/lib/libvirt/images/lab/ubuntu-24.04-server-cloudimg-amd64.qcow2 /var/lib/libvirt/images/lab/pc1.qcow2
sudo cp -f /var/lib/libvirt/images/lab/ubuntu-24.04-server-cloudimg-amd64.qcow2 /var/lib/libvirt/images/lab/pc2.qcow2
sudo chown libvirt-qemu:kvm /var/lib/libvirt/images/lab/pc1.qcow2 /var/lib/libvirt/images/lab/pc2.qcow2
sudo chmod 640 /var/lib/libvirt/images/lab/pc1.qcow2 /var/lib/libvirt/images/lab/pc2.qcow2
设置PC的认证信息, 这里使用的认证信息为 rory/Rxlabs!13579
PASS_HASH="$(openssl passwd -6 'Rxlabs!13579')"
echo "$PASS_HASH"
对PC1 注入认证和地址信息 192.168.10.10/24 GW 192.168.10.1
sudo virt-customize -a /var/lib/libvirt/images/lab/pc1.qcow2 \
--run-command "id -u rory >/dev/null 2>&1 || useradd -m -s /bin/bash rory" \
--run-command "usermod -aG sudo rory" \
--run-command "mkdir -p /etc/sudoers.d && echo 'rory ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-rory && chmod 440 /etc/sudoers.d/90-rory" \
--run-command "echo 'rory:${PASS_HASH}' | chpasswd -e" \
--run-command "passwd -u rory || true" \
--write "/etc/netplan/99-static.yaml:network:
version: 2
ethernets:
enp1s0:
dhcp4: false
optional: true
addresses: [192.168.10.10/24]
routes:
- to: 0.0.0.0/0
via: 192.168.10.1
" \
--write "/etc/systemd/system/linkup-enp1s0.service:[Unit]
Description=Bring up enp1s0
After=network-pre.target
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip link set enp1s0 up
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
" \
--run-command "systemctl enable linkup-enp1s0.service || true" \
--run-command "netplan generate || true" \
--run-command "touch /etc/cloud/cloud-init.disabled || true"
对PC2 注入认证和地址信息 192.168.20.10/24 GW 192.168.20.1
sudo virt-customize -a /var/lib/libvirt/images/lab/pc2.qcow2 \
--run-command "id -u rory >/dev/null 2>&1 || useradd -m -s /bin/bash rory" \
--run-command "usermod -aG sudo rory" \
--run-command "mkdir -p /etc/sudoers.d && echo 'rory ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-rory && chmod 440 /etc/sudoers.d/90-rory" \
--run-command "echo 'rory:${PASS_HASH}' | chpasswd -e" \
--run-command "passwd -u rory || true" \
--write "/etc/netplan/99-static.yaml:network:
version: 2
ethernets:
enp1s0:
dhcp4: false
optional: true
addresses: [192.168.20.10/24]
routes:
- to: 0.0.0.0/0
via: 192.168.20.1
" \
--write "/etc/systemd/system/linkup-enp1s0.service:[Unit]
Description=Bring up enp1s0
After=network-pre.target
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip link set enp1s0 up
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
" \
--run-command "systemctl enable linkup-enp1s0.service || true" \
--run-command "netplan generate || true" \
--run-command "touch /etc/cloud/cloud-init.disabled || true"
启动 Linux PC #
sudo virt-install \
--name pc1 \
--memory 2048 --vcpus 1 \
--cpu host-passthrough \
--os-variant ubuntu24.04 \
--import \
--disk path=/var/lib/libvirt/images/lab/pc1.qcow2,format=qcow2,bus=virtio \
--network network=net-a,model=virtio \
--graphics none \
--console pty,target_type=serial \
--boot hd,useserial=on \
--noautoconsole
sudo virt-install \
--name pc2 \
--memory 2048 --vcpus 1 \
--cpu host-passthrough \
--os-variant ubuntu24.04 \
--import \
--disk path=/var/lib/libvirt/images/lab/pc2.qcow2,format=qcow2,bus=virtio \
--network network=net-b,model=virtio \
--graphics none \
--console pty,target_type=serial \
--boot hd,useserial=on \
--noautoconsole
访问PC的console 进行地址检查:
sudo virsh console pc1
ip a
ip route
启动C8000v #
添加双网卡,分别连接net-a 和 net-b
sudo virt-install \
--name c8000v \
--memory 8192 --vcpus 4 \
--cpu host-passthrough \
--machine q35 \
--os-variant generic \
--import \
--disk path=/var/lib/libvirt/images/c8000v/c8000v-universalk9_16G_serial.17.15.04c.qcow2,format=qcow2,bus=virtio \
--network network=net-a,model=virtio \
--network network=net-b,model=virtio \
--graphics none \
--console pty,target_type=serial \
--boot hd,useserial=on \
--noautoconsole
sudo virsh console c8000v
配置PC的GW:
conf t
interface GigabitEthernet1
ip address 192.168.10.1 255.255.255.0
no shutdown
!
interface GigabitEthernet2
ip address 192.168.20.1 255.255.255.0
no shutdown
end
write
常用 KVM/libvirt命令 #
sudo virsh list --all
sudo virsh net-list --all
sudo virsh domiflist c8000v
sudo virsh domblklist pc1 --details
sudo virsh domblklist c8000v --details
sudo virsh destroy pc1 && sudo virsh undefine pc1
一键脚本 #
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# Lab script (Plan A): libvirt/KVM + virt-customize (NO cloud-init seed)
#
# net-a: pc1=192.168.10.10/24 gw=192.168.10.1 (C8000V)
# net-b: pc2=192.168.20.10/24 gw=192.168.20.1 (C8000V)
#
# PC login: rory / Rxlabs!13579
#
# Important: NIC name in this image is enp1s0 (NOT ens3).
###############################################################################
# ====== EDIT THESE TWO PATHS ======
UBUNTU_BASE_IMG="/var/lib/libvirt/images/lab/ubuntu-24.04-server-cloudimg-amd64.qcow2"
C8000V_QCOW2="/var/lib/libvirt/images/c8000v/c8000v-universalk9_16G_serial.17.15.04c.qcow2"
# ====== PC credentials ======
PC_USER="rory"
PC_PASS="Rxlabs!13579"
# ====== NIC name inside Ubuntu (confirmed) ======
PC_NIC="enp1s0"
# ====== Networks & IPs ======
NET_A_NAME="net-a"
NET_B_NAME="net-b"
NET_A_BR="virbr10"
NET_B_BR="virbr20"
PC1_IP="192.168.10.10/24"
PC1_GW="192.168.10.1"
PC2_IP="192.168.20.10/24"
PC2_GW="192.168.20.1"
# ====== VM names ======
VM_C8K="c8000v"
VM_PC1="pc1"
VM_PC2="pc2"
# ====== Storage ======
IMG_DIR="/var/lib/libvirt/images/lab"
PC1_DISK="${IMG_DIR}/pc1.qcow2"
PC2_DISK="${IMG_DIR}/pc2.qcow2"
OS_VARIANT="ubuntu24.04" # metadata only
need_root() { [[ "${EUID}" -eq 0 ]] || { echo "ERROR: run with sudo."; exit 1; }; }
require_cmd(){ command -v "$1" >/dev/null 2>&1 || { echo "ERROR: missing command: $1"; exit 1; }; }
vm_exists(){ virsh dominfo "$1" >/dev/null 2>&1; }
net_exists(){ virsh net-info "$1" >/dev/null 2>&1; }
cleanup_vm() {
local name="$1"
if vm_exists "$name"; then
echo "[*] Removing VM: ${name} (state: $(virsh domstate "$name" 2>/dev/null || true))"
virsh destroy "$name" >/dev/null 2>&1 || true
virsh undefine "$name" >/dev/null 2>&1 || true
fi
}
cleanup_net() {
local name="$1"
if net_exists "$name"; then
echo "[*] Removing network: ${name}"
virsh net-destroy "$name" >/dev/null 2>&1 || true
virsh net-undefine "$name" >/dev/null 2>&1 || true
fi
}
write_net_xml() {
local name="$1" br="$2" xml="$3"
cat > "$xml" <<EOF
<network>
<name>${name}</name>
<bridge name='${br}' stp='on' delay='0'/>
<forward mode='none'/>
</network>
EOF
}
check_prereqs() {
require_cmd virsh
require_cmd virt-install
require_cmd virt-customize
require_cmd openssl
[[ -f "$UBUNTU_BASE_IMG" ]] || { echo "ERROR: UBUNTU_BASE_IMG not found: $UBUNTU_BASE_IMG"; exit 1; }
[[ -f "$C8000V_QCOW2" ]] || { echo "ERROR: C8000V_QCOW2 not found: $C8000V_QCOW2"; exit 1; }
mkdir -p "$IMG_DIR"
chown -R libvirt-qemu:kvm "$IMG_DIR"
}
copy_disk_from_base() {
local out="$1"
cp -f "$UBUNTU_BASE_IMG" "$out"
chown libvirt-qemu:kvm "$out"
chmod 640 "$out"
}
inject_user_and_netplan() {
local disk="$1" ipcidr="$2" gw="$3"
local pass_hash
pass_hash="$(openssl passwd -6 "${PC_PASS}")"
local netplan
netplan="network:
version: 2
ethernets:
${PC_NIC}:
dhcp4: false
optional: true
addresses: [${ipcidr}]
routes:
- to: 0.0.0.0/0
via: ${gw}
"
# Unit: bring NIC up at boot (some minimal images leave it DOWN until configured)
local unit
unit="[Unit]
Description=Bring up ${PC_NIC}
After=network-pre.target
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip link set ${PC_NIC} up
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
"
echo "[*] Injecting user/password + netplan + link-up unit into ${disk} ..."
sudo virt-customize -a "${disk}" \
--run-command "id -u ${PC_USER} >/dev/null 2>&1 || useradd -m -s /bin/bash ${PC_USER}" \
--run-command "usermod -aG sudo ${PC_USER}" \
--run-command "mkdir -p /etc/sudoers.d && echo '${PC_USER} ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-${PC_USER} && chmod 440 /etc/sudoers.d/90-${PC_USER}" \
--run-command "echo '${PC_USER}:${pass_hash}' | chpasswd -e" \
--run-command "passwd -u ${PC_USER} || true" \
--write "/etc/netplan/99-static.yaml:${netplan}" \
--write "/etc/systemd/system/linkup-${PC_NIC}.service:${unit}" \
--run-command "systemctl enable linkup-${PC_NIC}.service || true" \
--run-command "netplan generate || true" \
--run-command "touch /etc/cloud/cloud-init.disabled || true"
}
cleanup_lab() {
echo "[*] Cleaning lab (VMs + networks + pc disks)..."
cleanup_vm "$VM_PC1"
cleanup_vm "$VM_PC2"
cleanup_vm "$VM_C8K"
cleanup_net "$NET_A_NAME"
cleanup_net "$NET_B_NAME"
rm -f "$PC1_DISK" "$PC2_DISK" || true
echo "[OK] Cleanup done."
}
create_networks() {
echo "[*] Creating networks..."
write_net_xml "$NET_A_NAME" "$NET_A_BR" /tmp/net-a.xml
write_net_xml "$NET_B_NAME" "$NET_B_BR" /tmp/net-b.xml
virsh net-define /tmp/net-a.xml
virsh net-define /tmp/net-b.xml
virsh net-start "$NET_A_NAME"
virsh net-start "$NET_B_NAME"
virsh net-autostart "$NET_A_NAME"
virsh net-autostart "$NET_B_NAME"
}
create_pcs() {
echo "[*] Creating PC disks from base image..."
copy_disk_from_base "$PC1_DISK"
copy_disk_from_base "$PC2_DISK"
inject_user_and_netplan "$PC1_DISK" "$PC1_IP" "$PC1_GW"
inject_user_and_netplan "$PC2_DISK" "$PC2_IP" "$PC2_GW"
}
create_vms() {
echo "[*] Creating VMs..."
virt-install \
--name "$VM_PC1" \
--memory 2048 --vcpus 1 --cpu host-passthrough \
--os-variant "$OS_VARIANT" \
--import \
--disk path="$PC1_DISK",format=qcow2,bus=virtio \
--network network="$NET_A_NAME",model=virtio \
--graphics none --console pty,target_type=serial \
--boot hd,useserial=on --noautoconsole
virt-install \
--name "$VM_PC2" \
--memory 2048 --vcpus 1 --cpu host-passthrough \
--os-variant "$OS_VARIANT" \
--import \
--disk path="$PC2_DISK",format=qcow2,bus=virtio \
--network network="$NET_B_NAME",model=virtio \
--graphics none --console pty,target_type=serial \
--boot hd,useserial=on --noautoconsole
virt-install \
--name "$VM_C8K" \
--memory 8192 --vcpus 4 --cpu host-passthrough \
--machine q35 --os-variant generic \
--import \
--disk path="$C8000V_QCOW2",format=qcow2,bus=virtio \
--network network="$NET_A_NAME",model=virtio \
--network network="$NET_B_NAME",model=virtio \
--graphics none --console pty,target_type=serial \
--boot hd,useserial=on --noautoconsole
}
create_lab() {
check_prereqs
cleanup_lab
create_networks
create_pcs
create_vms
echo
echo "============================================================"
echo "[OK] Lab created."
echo "PC console login: ${PC_USER} / ${PC_PASS}"
echo
echo "Console connect:"
echo " sudo virsh console ${VM_PC1}"
echo " sudo virsh console ${VM_PC2}"
echo " sudo virsh console ${VM_C8K}"
echo "Exit console: Ctrl+]"
echo
echo "Next: configure C8000V L3 interfaces (adjust Gi numbers):"
echo " conf t"
echo " int GiX ; ip address 192.168.10.1 255.255.255.0 ; no shut"
echo " int GiY ; ip address 192.168.20.1 255.255.255.0 ; no shut"
echo " end"
echo
echo "Ping test:"
echo " pc1 -> pc2: ping -c 3 192.168.20.10"
echo " pc2 -> pc1: ping -c 3 192.168.10.10"
echo "============================================================"
}
show_status() {
echo "=== Networks ==="
virsh net-list --all || true
echo "=== VMs ==="
virsh list --all || true
echo
for vm in "$VM_PC1" "$VM_PC2" "$VM_C8K"; do
if vm_exists "$vm"; then
echo "--- $vm ---"
echo "State: $(virsh domstate "$vm" 2>/dev/null || true)"
virsh domiflist "$vm" 2>/dev/null || true
virsh domblklist "$vm" --details 2>/dev/null || true
echo
fi
done
}
print_c8000v_config_template() {
cat <<'EOF'
=== C8000V minimal L3 config template (adjust interface names!) ===
sudo virsh console c8000v
show ip interface brief
conf t
interface GigabitEthernet2
description TO-net-a
ip address 192.168.10.1 255.255.255.0
no shutdown
!
interface GigabitEthernet3
description TO-net-b
ip address 192.168.20.1 255.255.255.0
no shutdown
end
write memory
EOF
}
menu() {
while true; do
echo
echo "================== C8000V Lab Menu =================="
echo "1) Create/Recreate lab (auto-clean old lab)"
echo "2) Cleanup old lab only (delete VMs/networks/pc disks)"
echo "3) Show lab status"
echo "4) Print C8000V config template"
echo "5) Exit"
echo "====================================================="
read -r -p "Select [1-5]: " choice
case "$choice" in
1) need_root; create_lab ;;
2) need_root; check_prereqs; cleanup_lab ;;
3) need_root; show_status ;;
4) print_c8000v_config_template ;;
5) exit 0 ;;
*) echo "Invalid option." ;;
esac
done
}
menu
脚本运行结果:
rory@rory-VMware-Virtual-Platform:~$ sudo ./lab_up.sh
================== C8000V Lab Menu ==================
1) Create/Recreate lab (auto-clean old lab)
2) Cleanup old lab only (delete VMs/networks/pc disks)
3) Show lab status
4) Print C8000V config template
5) Exit
=====================================================
Select [1-5]: 1
[*] Cleaning lab (VMs + networks + pc disks)...
[OK] Cleanup done.
[*] Creating networks...
Network net-a defined from /tmp/net-a.xml
Network net-b defined from /tmp/net-b.xml
Network net-a started
Network net-b started
Network net-a marked as autostarted
Network net-b marked as autostarted
[*] Creating PC disks from base image...
[*] Injecting user/password + netplan + link-up unit into /var/lib/libvirt/images/lab/pc1.qcow2 ...
[ 0.0] Examining the guest ...
[ 14.7] Setting a random seed
virt-customize: warning: random seed could not be set for this type of
guest
[ 14.8] Setting the machine ID in /etc/machine-id
[ 14.8] Running: id -u rory >/dev/null 2>&1 || useradd -m -s /bin/bash rory
[ 15.0] Running: usermod -aG sudo rory
[ 15.0] Running: mkdir -p /etc/sudoers.d && echo 'rory ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-rory && chmod 440 /etc/sudoers.d/90-rory
[ 15.1] Running: echo 'rory:$6$FMk4mI7E690LNn06$4l/79GcwUbvZPqp3mKxOwKBXJs.Gsgx1uYSInumoTvVOKVRmCtk3WB8u.VS7C2oyrxzCBqoLfi.GKqdst4WuB1' | chpasswd -e
[ 15.1] Running: passwd -u rory || true
[ 15.2] Writing: /etc/netplan/99-static.yaml
[ 15.2] Writing: /etc/systemd/system/linkup-enp1s0.service
[ 15.2] Running: systemctl enable linkup-enp1s0.service || true
[ 15.3] Running: netplan generate || true
[ 15.8] Running: touch /etc/cloud/cloud-init.disabled || true
[ 15.8] SELinux relabelling
[ 16.0] Finishing off
[*] Injecting user/password + netplan + link-up unit into /var/lib/libvirt/images/lab/pc2.qcow2 ...
[ 0.0] Examining the guest ...
[ 14.6] Setting a random seed
virt-customize: warning: random seed could not be set for this type of
guest
[ 14.6] Setting the machine ID in /etc/machine-id
[ 14.6] Running: id -u rory >/dev/null 2>&1 || useradd -m -s /bin/bash rory
[ 14.9] Running: usermod -aG sudo rory
[ 15.0] Running: mkdir -p /etc/sudoers.d && echo 'rory ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/90-rory && chmod 440 /etc/sudoers.d/90-rory
[ 15.0] Running: echo 'rory:$6$yNgusV2erQPQYmXS$Dps9iQ0pZsWd/Md.fD14UB3CEIOiepYZw/ByhxrR6T0gidIthu0yXW8ikR0ig3BKGkI84C/4aJXFrRS9ER03Q/' | chpasswd -e
[ 15.1] Running: passwd -u rory || true
[ 15.1] Writing: /etc/netplan/99-static.yaml
[ 15.1] Writing: /etc/systemd/system/linkup-enp1s0.service
[ 15.1] Running: systemctl enable linkup-enp1s0.service || true
[ 15.2] Running: netplan generate || true
[ 15.7] Running: touch /etc/cloud/cloud-init.disabled || true
[ 15.7] SELinux relabelling
[ 15.8] Finishing off
[*] Creating VMs...
WARNING Requested memory 2048 MiB is less than the recommended 3072 MiB for OS ubuntu24.04
Starting install...
Creating domain... | 0 B 00:00:00
Domain creation completed.
WARNING Requested memory 2048 MiB is less than the recommended 3072 MiB for OS ubuntu24.04
Starting install...
Creating domain... | 0 B 00:00:00
Domain creation completed.
WARNING Using --osinfo generic, VM performance may suffer. Specify an accurate OS for optimal results.
Starting install...
Creating domain... | 0 B 00:00:00
Domain creation completed.
============================================================
[OK] Lab created.
PC console login: rory / Rxlabs!13579
Console connect:
sudo virsh console pc1
sudo virsh console pc2
sudo virsh console c8000v
Exit console: Ctrl+]
Next: configure C8000V L3 interfaces (adjust Gi numbers):
conf t
int GiX ; ip address 192.168.10.1 255.255.255.0 ; no shut
int GiY ; ip address 192.168.20.1 255.255.255.0 ; no shut
end
Ping test:
pc1 -> pc2: ping -c 3 192.168.20.10
pc2 -> pc1: ping -c 3 192.168.10.10
============================================================
================== C8000V Lab Menu ==================
1) Create/Recreate lab (auto-clean old lab)
2) Cleanup old lab only (delete VMs/networks/pc disks)
3) Show lab status
4) Print C8000V config template
5) Exit
=====================================================
Select [1-5]: 5