Skip to main content

Install Cisco Catalyst 8000V in KVM Environment

·10 mins
8000v
Table of Contents

测试了下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



Comments