最近在撰寫「從 Linux 基礎實現 Docker Bridge 網路」系列文時,我使用 AWS EC2 作為實驗環境。然而,不僅要承擔隨時間增加的費用壓力,還經常遇到 SSH 連線不穩定的問題,這樣的配置似乎有點大材小用了。
回想起之前在學習 Kubernetes 時,曾使用 Vagrant 輕鬆搭建多節點叢集環境。因此,我決定重新拾起這個工具,並且在此與大家分享使用心得。
Vagrant 是什麼?
Vagrant 是一個開源的虛擬化環境管理工具,它提供了簡潔的命令列介面和可移植的配置檔案 (Vagrantfile)。透過 Vagrant,開發者可以快速啟動一致的虛擬機環境 (如 VirtualBox、VMware) 或容器 (如 Docker),有效解決跨環境配置不一致的問題。順帶一提, Vagrant 和 Terraform 都是 HashiCorp 公司的產品,我們可以將 Vagrant 簡單理解為「本地端的 Terraform」。
接下來,讓我們看看如何在 Windows 11 上使用這個強大的工具。
安裝 Vagrant 和 VirtualBox
在開始使用 Vagrant 之前,我們需要先安裝兩個主要的軟體:虛擬機環境和 Vagrant 本身。
這裡我們選擇 VirtualBox 作為虛擬機環境,因為它不僅是 Vagrant 的預設 Provider,也是目前最受歡迎的虛擬化平台之一。它穩定、免費,而且跨平台支援度高。
VirtualBox
去 VirtualBox 官網的下載頁,下載 Windows 版的安裝檔,依照指示完成安裝。
Vagrant
去 Vagrant 官網的安裝引導頁,下載 Windows 版的安裝檔,依照指示完成安裝。
安裝完成後,到 PowerShell 查詢 Vagrant 的版本:
1vagrant version2# output3Installed Version: 2.4.34Latest Version: 2.4.35
6You're running an up-to-date version of Vagrant!
這樣就確認完成安裝了。
準備專案環境
Vagrant 的專案管理方式與 Git 類似,都需要一個專案資料夾作為工作目錄,並進行初始化設定。這種方式讓我們能夠輕鬆管理不同的虛擬環境配置。
首先,建立專案資料夾:
1mkdir D:/workspace/vagrant-space
接下來,將 powershell 當前目錄移動到該路徑,使用以下指令進行初始化:
1vagrant init bento/ubuntu-22.04
此時 Vagrant 在 workspace 中會初始化了一個 Vagrantfile,不過我們暫時先不用管它。
接著,啟動我們的第一台虛擬機:
1vagrant up
第一次啟動,Vagrant 需要去下載 ubuntu-22.04 的 box 檔案,可能需要等幾分鐘。
完成啟動後,可以使用以下指令登入機器:
1vagrant ssh
Vagrant Box 是什麼?
Vagrant Box 可以視為虛擬機的基礎映像檔(Base Image)。每個 Box 都包含了預先配置好的作業系統環境(例如 Ubuntu、CentOS、Windows 等),讓開發者能夠快速建立和管理虛擬機。這就像 Docker Image 一樣,Vagrant 透過 Box 為開發者提供了一個統一的起點,大幅簡化了多個虛擬機環境的部署流程。
只要輸入 Box 名稱(例如 bento/ubuntu-22.04
),Vagrant 就能找到對應的 Box。我們可以在 Vagrant Cloud 上找到官方發布或社群分享的 Box,同時也能分享自己製作的 Box。值得一提的是,Vagrant Cloud 上的 Box 通常都經過優化,啟動速度比一般虛擬機映像檔更快。
Vagrant 專案架構
我們到 D:/workspace/vagrant-space
目錄可以看到,存在 Vagrantfile
檔案以及 .vagrant
資料夾:
1tree -aL 22# output3.4├── .vagrant5│ ├── machines6│ └── rgloader7└── Vagrantfile
Vagrantfile
是 Vagrant 的核心配置文件,它定義了虛擬機的配置和行為,包括所使用的基礎鏡像(box)、網絡設置、資源分配、自動化腳本等。
.vagrant
儲存整個專案的管理信息,供 Vagrant 使用。是 Vagrant 追蹤虛擬機狀態的核心目錄。
.vagrant/machines
會按虛擬機名稱劃分目錄,記錄每台虛擬機的配置與狀態。包含虛擬化提供者相關的細節(如 VirtualBox 的 UUID)。.vagrant/rgloader
處理 Vagrant 與 Ruby 的插件或腳本兼容性。
建議不要去變動 .vagrant
底下的內容,隨意更改可能導致 Vagrant 無法正確管理虛擬機。
設置 Vagrantfile
:模擬 AWS VPC 架構
在進入實作之前,我們先來了解一下要模擬的 AWS VPC 架構。這個架構是雲端環境中常見的網路設計,主要用於提供安全且彈性的網路環境。
標準的 AWS VPC 架構,包含:
- NAT Gateway (
nat_gateway
):用於處理私有子網的對外網路流量 - 公有子網的 EC2 虛擬機 (
ec2_public
):可直接與外部網路通訊 - 私有子網的 EC2 虛擬機 (
ec2_private
):透過 NAT Gateway 存取外部網路
至於 VPC 層的 ROUTER 和 IGW,可以透過宿主機來實現。
我們期望的架構大致如下:
有了明確的目標,以此撰寫配置檔案:
1# -*- mode: ruby -*-2# vi: set ft=ruby :3
4Vagrant.configure("2") do |config|5 # NAT Gateway6 config.vm.define "nat_gateway" do |node|7 node.vm.box = "bento/ubuntu-22.04"8 node.vm.hostname = "nat-gateway"9 node.vm.network "private_network", ip: "172.16.0.1", virtualbox__intnet: "vpc_network"10 node.vm.network "public_network", bridge: "Intel(R) I211 Gigabit Network Connection"11 node.vm.provider "virtualbox" do |vb|12 vb.name = "nat_gateway"13 vb.memory = 102414 vb.cpus = 115 end78 collapsed lines
16 node.vm.provision "shell", run: "always", inline: <<-SHELL17 echo "等待網絡初始化..."18 sleep 1019 # 啟用 IP 轉發20 sudo sysctl -w net.ipv4.ip_forward=121 # 移除預設路由網卡 eth022 sudo ip route del default dev eth023
24 # 動態檢測外網網卡並檢查 NAT 規則25 OUT_IF=$(ip route | grep default | grep -v '10.0.2.' | awk '{print $5}')26 if [ -n "$OUT_IF" ]; then27 # 確保 NAT 規則未重複添加28 if ! sudo iptables -t nat -C POSTROUTING -o $OUT_IF -j MASQUERADE 2>/dev/null; then29 sudo iptables -t nat -A POSTROUTING -o $OUT_IF -j MASQUERADE30 echo "NAT 規則已應用到網卡: $OUT_IF"31 else32 echo "NAT 規則已存在,跳過設置。"33 fi34 else35 echo "未能檢測到外網網卡!"36 exit 137 fi38 SHELL39 node.trigger.after :up do |trigger|40 trigger.info = "延遲執行 NAT Gateway 配置,等待網卡初始化完成..."41 trigger.run_remote = {42 inline: <<-SHELL43 # 檢測預設路由網卡44 read OUT_IF SRC_IP <<< $(ip route | grep default | awk '45 /default/ {46 for (i=1; i<=NF; i++) {47 if ($i == "dev") dev=$(i+1)48 if ($i == "src") src=$(i+1)49 }50 print dev, src51 }')52 echo "網卡名稱: $OUT_IF"53 echo "IP 地址: $SRC_IP"54 SHELL55 }56 end57 end58
59 # EC2 Public60 config.vm.define "ec2_public" do |node|61 node.vm.box = "bento/ubuntu-22.04"62 node.vm.hostname = "ec2-public"63 node.vm.network "private_network", ip: "172.16.0.100", virtualbox__intnet: "vpc_network"64 node.vm.network "public_network", ip: "192.168.31.100", bridge: "Intel(R) I211 Gigabit Network Connection"65 node.vm.provider "virtualbox" do |vb|66 vb.name = "ec2_public"67 vb.memory = 204868 vb.cpus = 269 end70 node.vm.provision "shell", run: "always", inline: <<-SHELL71 # 修改默認路由,使外網流量走 Public Network72 sudo ip route del default73 sudo ip route add default via 192.168.31.1 dev eth274 SHELL75 end76
77 # EC2 Private78 config.vm.define "ec2_private" do |node|79 node.vm.box = "bento/ubuntu-22.04"80 node.vm.hostname = "ec2-private"81 node.vm.network "private_network", ip: "172.16.0.101", virtualbox__intnet: "vpc_network"82 node.vm.provider "virtualbox" do |vb|83 vb.name = "ec2_private"84 vb.memory = 204885 vb.cpus = 286 end87 node.vm.provision "shell", run: "always", inline: <<-SHELL88 # 修改默認路由,使外網流量通過 NAT Gateway89 sudo ip route del default90 sudo ip route add default via 172.16.0.1 dev eth191 SHELL92 end93end
為了確保虛擬機乾淨部署,我們先移移除之前專案所有機器,再重新啟動
1# 移除機器2vagrant destroy3# 啟動機器4vagrant up
完成後,使用以下指令連線到虛擬機內:
1# vagrant ssh [HOSTNAME]2vagrant ssh ec2_private3# output4Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-116-generic x86_64)5...
驗證
從上圖可以得知,我們架構中可以直接對外連線的兩種類型機器,皆有 Public Network Interface,並且掛上了 IP。
我們來測試以下內容:
ec2 之間可以透過內網互相連線
我們可以預期,ec2 之間的連線,會透過 eth1
的網卡進行傳輸。
結果是正確的。
ec2 private 要發起對外連線,會透過 nat gateway
ec2 public 為發起對外連線,會透過自己的 public network interface,外網也可以透過這個 interface 的 Ip 與 Ec2 public 建立連線
我的宿主機內網 IP 為 192.168.31.5
,我們來驗證看看:
Vagrantfile 常用語法介紹
想要快速的入門 vargrant,就需要了解 vagrant 的區塊配置。 讓我們直接拿上面的配置檔案講解。
基本結構
1Vagrant.configure("2") do |config|2 # ...3end
Vagrant.configure("2")
:指定使用 Vagrant 的第 2 版配置格式。config
:存放虛擬機的所有設置。- 每台虛擬機用
config.vm.define
定義。 - 如果只有一台,也可以直接使用
config.vm.box
直接開始配置虛擬機
節點配置
1config.vm.define "nat_gateway" do |node|2 node.vm.box = "bento/ubuntu-22.04" # 使用 Ubuntu 22.04 作為基礎鏡像3 node.vm.hostname = "nat-gateway" # 設置主機名稱
-
網絡配置:
1node.vm.network "private_network", ip: "172.16.0.1", virtualbox__intnet: "vpc_network"2node.vm.network "public_network", bridge: "Intel(R) I211 Gigabit Network Connection"private_network
:- 配置私有子網,IP 為
172.16.0.1
。 - (virtualbox only) 如果沒有配置
virtualbox__intnet
,會使用 virtualbox 的 host_only 模式;設置為true
或相同名稱,則機器會配置為相同的內部網路。
- 配置私有子網,IP 為
public_network
:- 橋接宿主機的網絡,模擬外網接入。
- 如果宿主機有兩個以上的網路介面,就需要選擇要橋接到哪個網卡,
bridge
選項事先指定要用哪張網網卡,也可以不填,在機器啟動時再做選擇
-
資源配置:
1node.vm.provider "virtualbox" do |vb|2vb.name = "nat_gateway"3vb.memory = 10244vb.cpus = 15end- 分配 1 GB 內存和 1 核 CPU。
- 不同的 Provider 可能會有不同的用法
-
provision:
1node.vm.provision "shell", run: "always", inline: <<-SHELL2...3SHELL- 這個區塊可以在機器上自動安裝軟體、更改配置等,作為 vagrant 上線過程的一部分。
- 只有在第一次
vagrant up
,還有使用--provision
flag 才會執行它。
-
trigger:
1node.trigger.after :up do |trigger|2trigger.info = "延遲執行 NAT Gateway 配置,等待網卡初始化完成..."3trigger.run_remote = {4inline: <<-SHELL5...6SHELL7}8end- 在指定事件、命令的生命週期前、後,執行腳本任務或顯示訊息
想要更深入的了解 Vagrantfile 語法,請參考官方文件
補充說明
使用一般的 ssh 指令登入 vagrant 建立的機器
vagrant 所建立的機器,會在 host 與虛擬機之間,做 ssh Protocol 的 Port forwarding:
vagrant ssh
登入指令的原理,就是一般 ssh 指令的再包裝。
我們可以使用以下指令,取得所有或特定 Host 的 ssh config:
1# vagrant ssh-config [HOSTNAME]2vagrant ssh-config nat_gateway3# output4Host nat_gateway5 HostName 127.0.0.16 User vagrant7 Port 22228 UserKnownHostsFile /dev/null9 StrictHostKeyChecking no10 PasswordAuthentication no11 IdentityFile D:/workspace/vagrant-space/.vagrant/machines/nat_gateway/virtualbox/private_key12 IdentitiesOnly yes13 LogLevel FATAL14 PubkeyAcceptedKeyTypes +ssh-rsa15 HostKeyAlgorithms +ssh-rsa
接著就可以使用上面的資訊來 ssh 登入:
1ssh -i D:/workspace/vagrant-space/.vagrant/machines/nat_gateway/virtualbox/private_key -p 2222 vagrant@127.0.0.1
故障排除
在使用 Vagrant 的過程中,最常遇到的問題之一就是資源鎖定衝突。這通常發生在以下情況:
- 先前的操作未正常結束
- 有其他 Vagrant 程式正在執行
- 系統重啟後,遺留的鎖定狀態未正確清除
以我自己為例,在實作本篇的架構時,就發生好幾次,執行 vagrant destroy
移除虛擬機時,遇到這樣的錯誤訊息:
1vagrant destroy2# output3An action 'destroy' was attempted on the machine 'ec2_private',4but another process is already executing an action on the machine.5Vagrant locks each machine for access by only one process at a time.6Please wait until the other Vagrant process finishes modifying this7machine, then try again.8
9If you believe this message is in error, please check the process10listing for any "ruby" or "vagrant" processes and kill them. Then11try again.
這個錯誤表示 Vagrant 在嘗試刪除 ec2_private
虛擬機時,發現該機器已被其他程式鎖定。這種情況通常發生在先前的操作未正常結束,或是有其他 Vagrant 程式正在使用該虛擬機。
這時,只要找到佔用的應用或程式,強制關閉即可:
Linux
1ps aux | grep vagrant2ps aux | grep ruby3# kill4kill -9 <PROCESS ID>
Windows
1Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" }2# 顯示更多資訊3Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" } | Format-Table -Property Id, Name, CPU, Path4# kill5Stop-Process -Id <進程ID> -Force6# 使用 Pipe 將指令組合7Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" } | Stop-Process -Force
最後再清理環境:
1vagrant destroy2vagrant up
結語
透過 Vagrant,我們不僅可以在本地環境中模擬複雜的雲端架構,還能大幅降低開發和測試的成本。這個工具特別適合以下場景:
- 開發環境的標準化
- 測試環境的快速部署
- 團隊協作時的環境一致性保證
- 學習和實驗各種網路架構
不過 Vagrant 沒有原生版本控制,擴展性與雲端整合性也沒有 Terraform 強。如果要使用在生產環境,還是建議使用 Terraform。
建議將 Vagrant 主要用於本地開發和學習環境,搭配版本控制系統(如 Git)來管理 Vagrantfile,這樣可以更好地發揮它的優勢。