2016年5月1日 星期日

我的免費有線分享器: qemu/kvm 虛擬區網

qemu/kvm 虛擬區網 貴哥小時候沒修過網路的課, 後來玩 linux 勉強學會一點點 網路基本指令 (<==以下假設讀者可以手動設定有線網路並且玩過 qemu/kvm 虛擬機)。 這幾天爬了好多文終於學會用 bridge 跟 tap 手工建立虛擬區網。 我們的目標是要在你的電腦 (Host) 上面用 qemu/kvm 開兩部虛擬機 GuestA 跟 GuestB, 把它們放在同一個虛擬區域網路裡面, 讓 GuestA、 GuestB、 Host 可以直接看到彼此的 IP。 當然, GuestA 跟 GuestB 要能夠上網。

只想用虛擬機做事的讀者不必讀本文, 因為只要改採用更高階的 libvirt, 這些 「接水管」 的問題就不必自己操心了。 想當水管工的, 或是 [沒有足夠的設備也硬是要] 開設網路實習課程的老師才請進。

一、 行前準備

先談一下很簡單、 根本不需要讀本文的狀況。

如果只是要讓虛擬機能上網 => 什麼都不必設定。 這等同於在 kvm 命令列上使用 -net nic -net user 選項, 建立一個 SLIRP 類型的簡單網路

如果 虛擬機要提供服務給 Host (例如 ssh 或 http 等等) => 在 kvm 命令列上使用 -redir tcp:13022::22 就夠了, 也不需要做複雜的設定。 然後就可以從 Host 這樣連線: ssh -p 13022 localhost

[2017/5/14] 如果虛擬機要有自己的私有 IP, 那就開始有點挑戰了。 不需要學細節, 只想快解決問題的讀者, 可以直接看 精簡版手工 qemu-kvm 虛擬區域網路。 以下開始很囉嗦的解釋。

更挑戰的是, 這幾年當中, linux 的網路指令/系統服務/kvm指令選項都有一些變動。 有些文章採用舊指令; 有些文章採用新指令。 特別是 qemu/kvm, 爬文幾乎都採舊指令; 但 qemu networking kvm networking 兩份最重要的官網文件採用的卻是新指令, 卻又很不完整。 我先用舊指令實驗成功, 最後再從猜測及錯誤訊息搜尋當中試出新的指令。 實驗環境是 lubuntu 15.04。 虛擬機跑的是簡單又強大的 finnix 開機光碟。 我在 .bashrc 裡面有這樣兩句設定:

alias mykvm='kvm -monitor stdio -m 1024 -vga std -cpu host'
alias myfkvm='mykvm -cdrom /x/cdrom/finnix-ckhung16c.iso -boot order=dc'

所以原本我打 myfkvm 就可以直接開一部 finnix 虛擬機。 (如果滑鼠不能動, 請加上 -usbdevice mouse 重試一次看看)

你可以把 tun/tap 想成是虛擬網路線。 在 Host 那頭, 用 ip tuntap add name 虹吸管 mode tap 建立一條名為 「虹吸管」 的虛擬網路線的一頭; 用 ip link show 可以看見虹吸管。 (好啦, 我寫得好累, 請准我亂取名字舒緩一下心情; 請你要用英文名字) 在 GuestX 那頭, 啟動 kvm 時要用相同的名字, 這樣就在 Host 跟 GuestX 之間建立了一條虛擬網路線。 tun 是 IP 層的通道; tap 是 ethernet 層的通道。 這篇 tun/tap 簡介 很白話易懂; 這篇 第一節也很值得讀。

二、 實作

本文只用 tap, 而且在 Host 這邊, 不採用指令而是寫在設定檔裡。 請在 Host 的 /etc/network/interfaces 裡面加上這幾句:

auto qibr29
iface qibr29 inet static
        address 10.0.29.254
        netmask 255.255.255.0
        bridge_ports qitap2931 qitap2932 qitap2933
        bridge_stp off
        bridge_fd 0
        post-up echo 1 > /proc/sys/net/ipv4/ip_forward
        post-up iptables -t nat -A POSTROUTING -s '10.0.29.0/24' -o ethz -j MASQUERADE

然後照第三節第一個提示啟動新的 qibr29 網路服務。 以上是從 Debian wiki 的 NAT 內網那一節 改來的。 對沒錯, 網卡的名字又是隨意取的, 不過這次是有意義的! 代表 "qemu-internel bridge 29"。 還有 bridge_ports 後面的三個裝置名稱也是任意取的, 分別會 (用一條虹吸管) 接到一部虛擬機的虛擬網卡。 注意! 其中的 "-o ethz" 請改成 Host 對外的真實網卡。 每次修改過 /etc/network/interfaces 之後, 要下 ifdown qibr29 ; ifup qibr29 或是 service ifup@qibr29 restart 才會生效。

趁虛擬機還沒打開前, 先查看一下 Host 上有哪些 (實體及虛擬) 網卡: ip link show > ~/netdev0.txt

再來啟動虛擬機:
舊語法: myfkvm -net nic,macaddr=52:54:00:12:34:1f -net tap,ifname=qitap2931,script=no,downscript=no
新語法: myfkvm -device e1000,netdev=net0,mac=52:54:00:12:34:1f -netdev tap,id=net0,ifname=qitap2931,script=no

舊語法的詳細解釋請見 suse linux 的文件。 新語法很清楚地把 GuestA 那頭關心的 (-device ...) 跟 Host 那頭關心的 (-netdev tap,...) 區分開來。

在 Host 上又執行 ip link show > ~/netdev1.txt, 然後比較前後差異: diff ~/netdev0.txt ~/netdev1.txt, 會看到多出一個 qitap2931 的裝置。

在 GuestA 上執行:

ifconfig eth0 10.0.29.31 netmask 255.255.255.0
route add default gw 10.0.29.254

然後它對外的網路就通了 -- 挑一個英文網站試試看 w3m http://www.eff.org/ 。 再來, 還可以安裝輕薄短小的網頁伺服器 nginx:

apt-get update
apt-get install nginx
w3m localhost
vim /var/www/html/index*.html # 小改一下, 再用 w3m 確認有修改到

回到 Host 上, 瀏覽器開到 http://10.0.29.31/ 成功開啟 GuestA 的網頁!

開第二部虛擬機時, 請把上面特殊顏色標記的地方都改掉: 例如 31 改成 32, 1f 改成 20。 請確認一下兩部虛擬機可以造訪彼此的網頁。 當然, 外界看不到它們 -- 就連 Host 所在的網路都看不見它們。

三、 忍不住要問細節

能動是一回事; 追問細節才能學到更多。

  1. service ifup@qibr29 restart 那個指令是怎麼回事? 現在已經沒有 "network" 這個服務了。 根據 這篇 可以用 systemctl list-units --type=service 查看有哪些服務。 其中會出現一個 ifup@qibr29 。
  2. post-up iptables ... -j MASQUERADE 那一句是從 恆逸資訊洪朝欽老師 幫我做的設定改來的, 主要就是為了把虛擬網域的封包透過真實網卡送出去。 (事實上本文整個設定檔就是從他幫我架設的 proxmox 伺服器簡化而來的 ^_^)
  3. 指定 mac address 的新舊語法不同。 而且 這段不能省略 -- 一開始我偷懶省略, 結果兩部機器各自可上網可被 Host 看見, 但看不到彼此, 因為 qemu 幫每一部虛擬機預設的 mac address 相同, 又放在同一個區網內, 所以就打架了。
  4. 如果你想在某部虛擬機裡面安裝很多張網卡呢? (反正不用錢, 哈哈) 在新語法裡面, 顯然會用到好幾對 -device-netdev。 其中每一對的 netdev=net0id=net0 要一致, 這樣 虹吸管 虛擬網路線的兩頭才可以找到彼此。 (對, net0 的名字可以隨便亂改, 不要叫虹吸管就對了。) 在舊語法裡面, 如果要創造三張網卡, 就要有六句 -net, 每句後面都要加上 ,vlan=0 之類的, 每一對的數字要相同。 詳見 這篇
  5. bridge_ports 那一句目前是寫死的。 如果開超過三部虛擬機, 後面的就沒網路可用了。 理想上, 應該要寫一些指令, 想辦法每開一部虛擬機時就自動開一個新的 tap 裝置加入 qibr29; 每關一部虛擬機時就自動把它的 tap 裝置移除。 是的, kvm 命令列上的 script=...downscript=... 正是拿來這樣用的。 你可以自己寫 shell scrip、 從這裡指向它。 不過那太麻煩了, 所以本文省略。 但是 qemu/kvm 預設會去執行某兩個 scripts, 我們搞不清楚系統在幫我們做什麼, 所以乾脆叫它不要執行預設的這兩個 scripts。 預設的 scripts 放在哪裡? 在 lubuntu 15.04 上, 用 strings /usr/bin/qemu-system-x86_64 | grep ifup 可以發現是 /etc/qemu-ifup 跟 /etc/qemu-ifdown。 在 proxmox 3.* 上, 用 strings /usr/bin/kvm | grep ifup 可以發現是 /etc/kvm/kvm-ifup 跟 /etc/kvm/kvm-ifdown。
  6. 可以進一步按照 Debian DHCP 安裝設定教學 (中文) 幫 qibr29 架設一個 dhcp 服務。 我沒仔細讀, 基本上只是關虛擬機、 安裝 isc-dhcp-server、 在 /etc/dhcp/dhcpd.conf 最下面加一段、 重新啟動 isc-dhcp-server、 重開 GuestA 跟 GuestB。 於是 GuestA 跟 GuestB 就自動取得 IP 了。
    subnet 10.0.29.0 netmask 255.255.255.0 {
        INTERFACES="qibr29";
        range 10.0.29.31 10.0.29.33;
        option routers 10.0.29.254;
    }
    

四、 融入 Host 所在的區網

那如果想要讓虛擬區網融入 Host 所在的區網呢? 觀念上, 我們只需要在 /etc/network/interfaces 裡面修改三處:

  1. 直接把 ethz 加入 qibr29 底下的埠。
  2. qibr29 自己的 IP 該如何設定? 套用原先 ethz 的設定值。
  3. 省略 post-up iptables ... -j MASQUERADE 那一句。

實作上, 還要加一句 iface ethz inet manual, 這樣才可以 避免好心的 network-manager 插手管理 ethz 結果越幫越忙。 例如我的 ethz 原先沒有出現在 /etc/network/interfaces 裡面, 而是由 network manager 在管理; 它的 IP 是從實體分享器取得的。 所以現在 /etc/network/interfaces 改成這樣:

iface ethz inet manual

auto qibr29
iface qibr29 inet dhcp
        bridge_ports ethz qitap2931 qitap2932 qitap2933
        bridge_stp off
        bridge_fd 0

那麼我的 GuestA 跟 GuestB 就會跟家裡的其他裝置 (例如我的手機) 看到彼此的 IP 了。 這也是 debian 官網解釋 bridging 的設定方式, 比第二節還簡單。 又因為實體分享器會派送 dhcp, 所以我們不必自己架 dhcp 伺服器, 虛擬機也會自動取得 IP, 超方便的啊! 不過如果你的 Host 是一部真的伺服器, 那麼最好你要在現場, 因為這種設定方式很可能一不小心網路就會壞掉。

心得: 虛擬化環境真的是學習網路設定與觀念的好環境啊!

沒有留言:

張貼留言