Vinc3nt's Life

使用 Vagrant 在本地模擬 AWS VPC 架構:NAT Gateway 與 EC2 實例配置指南

2025-01-22
develop
vagrant
aws
net-gateway
aws-ec2
aws-nat-gateway
virtualbox
最後更新:2025-01-26
16分鐘
3027字

最近在撰寫「從 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 的版本:

Terminal window
1
vagrant version
2
# output
3
Installed Version: 2.4.3
4
Latest Version: 2.4.3
5
6
You're running an up-to-date version of Vagrant!

這樣就確認完成安裝了。

準備專案環境

Vagrant 的專案管理方式與 Git 類似,都需要一個專案資料夾作為工作目錄,並進行初始化設定。這種方式讓我們能夠輕鬆管理不同的虛擬環境配置。

首先,建立專案資料夾:

Terminal window
1
mkdir D:/workspace/vagrant-space

接下來,將 powershell 當前目錄移動到該路徑,使用以下指令進行初始化:

Terminal window
1
vagrant init bento/ubuntu-22.04

此時 Vagrant 在 workspace 中會初始化了一個 Vagrantfile,不過我們暫時先不用管它。

接著,啟動我們的第一台虛擬機:

Terminal window
1
vagrant up

第一次啟動,Vagrant 需要去下載 ubuntu-22.04 的 box 檔案,可能需要等幾分鐘。

完成啟動後,可以使用以下指令登入機器:

Terminal window
1
vagrant 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 資料夾:

Terminal window
1
tree -aL 2
2
# output
3
.
4
├── .vagrant
5
│   ├── machines
6
│   └── rgloader
7
└── Vagrantfile

Vagrantfile 是 Vagrant 的核心配置文件,它定義了虛擬機的配置和行為,包括所使用的基礎鏡像(box)、網絡設置、資源分配、自動化腳本等。

.vagrant 儲存整個專案的管理信息,供 Vagrant 使用。是 Vagrant 追蹤虛擬機狀態的核心目錄。

  • .vagrant/machines 會按虛擬機名稱劃分目錄,記錄每台虛擬機的配置與狀態。包含虛擬化提供者相關的細節(如 VirtualBox 的 UUID)。
  • .vagrant/rgloader 處理 Vagrant 與 Ruby 的插件或腳本兼容性。

建議不要去變動 .vagrant 底下的內容,隨意更改可能導致 Vagrant 無法正確管理虛擬機。

設置 Vagrantfile:模擬 AWS VPC 架構

在進入實作之前,我們先來了解一下要模擬的 AWS VPC 架構。這個架構是雲端環境中常見的網路設計,主要用於提供安全且彈性的網路環境。

default

標準的 AWS VPC 架構,包含:

  • NAT Gateway (nat_gateway):用於處理私有子網的對外網路流量
  • 公有子網的 EC2 虛擬機 (ec2_public):可直接與外部網路通訊
  • 私有子網的 EC2 虛擬機 (ec2_private):透過 NAT Gateway 存取外部網路

至於 VPC 層的 ROUTER 和 IGW,可以透過宿主機來實現。

我們期望的架構大致如下:

default

有了明確的目標,以此撰寫配置檔案:

Vagrantfile
1
# -*- mode: ruby -*-
2
# vi: set ft=ruby :
3
4
Vagrant.configure("2") do |config|
5
# NAT Gateway
6
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 = 1024
14
vb.cpus = 1
15
end
78 collapsed lines
16
node.vm.provision "shell", run: "always", inline: <<-SHELL
17
echo "等待網絡初始化..."
18
sleep 10
19
# 啟用 IP 轉發
20
sudo sysctl -w net.ipv4.ip_forward=1
21
# 移除預設路由網卡 eth0
22
sudo ip route del default dev eth0
23
24
# 動態檢測外網網卡並檢查 NAT 規則
25
OUT_IF=$(ip route | grep default | grep -v '10.0.2.' | awk '{print $5}')
26
if [ -n "$OUT_IF" ]; then
27
# 確保 NAT 規則未重複添加
28
if ! sudo iptables -t nat -C POSTROUTING -o $OUT_IF -j MASQUERADE 2>/dev/null; then
29
sudo iptables -t nat -A POSTROUTING -o $OUT_IF -j MASQUERADE
30
echo "NAT 規則已應用到網卡: $OUT_IF"
31
else
32
echo "NAT 規則已存在,跳過設置。"
33
fi
34
else
35
echo "未能檢測到外網網卡!"
36
exit 1
37
fi
38
SHELL
39
node.trigger.after :up do |trigger|
40
trigger.info = "延遲執行 NAT Gateway 配置,等待網卡初始化完成..."
41
trigger.run_remote = {
42
inline: <<-SHELL
43
# 檢測預設路由網卡
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, src
51
}')
52
echo "網卡名稱: $OUT_IF"
53
echo "IP 地址: $SRC_IP"
54
SHELL
55
}
56
end
57
end
58
59
# EC2 Public
60
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 = 2048
68
vb.cpus = 2
69
end
70
node.vm.provision "shell", run: "always", inline: <<-SHELL
71
# 修改默認路由,使外網流量走 Public Network
72
sudo ip route del default
73
sudo ip route add default via 192.168.31.1 dev eth2
74
SHELL
75
end
76
77
# EC2 Private
78
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 = 2048
85
vb.cpus = 2
86
end
87
node.vm.provision "shell", run: "always", inline: <<-SHELL
88
# 修改默認路由,使外網流量通過 NAT Gateway
89
sudo ip route del default
90
sudo ip route add default via 172.16.0.1 dev eth1
91
SHELL
92
end
93
end

為了確保虛擬機乾淨部署,我們先移移除之前專案所有機器,再重新啟動

Terminal window
1
# 移除機器
2
vagrant destroy
3
# 啟動機器
4
vagrant up

完成後,使用以下指令連線到虛擬機內:

Terminal window
1
# vagrant ssh [HOSTNAME]
2
vagrant ssh ec2_private
3
# output
4
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-116-generic x86_64)
5
...

驗證

default

從上圖可以得知,我們架構中可以直接對外連線的兩種類型機器,皆有 Public Network Interface,並且掛上了 IP。

我們來測試以下內容:

ec2 之間可以透過內網互相連線

我們可以預期,ec2 之間的連線,會透過 eth1 的網卡進行傳輸。

default

結果是正確的。

ec2 private 要發起對外連線,會透過 nat gateway

default

ec2 public 為發起對外連線,會透過自己的 public network interface,外網也可以透過這個 interface 的 Ip 與 Ec2 public 建立連線

我的宿主機內網 IP 為 192.168.31.5,我們來驗證看看:

default

Vagrantfile 常用語法介紹

想要快速的入門 vargrant,就需要了解 vagrant 的區塊配置。 讓我們直接拿上面的配置檔案講解。

基本結構

1
Vagrant.configure("2") do |config|
2
# ...
3
end
  • Vagrant.configure("2"):指定使用 Vagrant 的第 2 版配置格式。
  • config:存放虛擬機的所有設置。
  • 每台虛擬機用 config.vm.define 定義。
  • 如果只有一台,也可以直接使用 config.vm.box 直接開始配置虛擬機

節點配置

1
config.vm.define "nat_gateway" do |node|
2
node.vm.box = "bento/ubuntu-22.04" # 使用 Ubuntu 22.04 作為基礎鏡像
3
node.vm.hostname = "nat-gateway" # 設置主機名稱
  • 網絡配置

    1
    node.vm.network "private_network", ip: "172.16.0.1", virtualbox__intnet: "vpc_network"
    2
    node.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 或相同名稱,則機器會配置為相同的內部網路。
    • public_network
      • 橋接宿主機的網絡,模擬外網接入。
      • 如果宿主機有兩個以上的網路介面,就需要選擇要橋接到哪個網卡,bridge 選項事先指定要用哪張網網卡,也可以不填,在機器啟動時再做選擇
  • 資源配置

    1
    node.vm.provider "virtualbox" do |vb|
    2
    vb.name = "nat_gateway"
    3
    vb.memory = 1024
    4
    vb.cpus = 1
    5
    end
    • 分配 1 GB 內存和 1 核 CPU。
    • 不同的 Provider 可能會有不同的用法
  • provision

    1
    node.vm.provision "shell", run: "always", inline: <<-SHELL
    2
    ...
    3
    SHELL
    • 這個區塊可以在機器上自動安裝軟體、更改配置等,作為 vagrant 上線過程的一部分。
    • 只有在第一次 vagrant up,還有使用 --provision flag 才會執行它。
  • trigger:

    1
    node.trigger.after :up do |trigger|
    2
    trigger.info = "延遲執行 NAT Gateway 配置,等待網卡初始化完成..."
    3
    trigger.run_remote = {
    4
    inline: <<-SHELL
    5
    ...
    6
    SHELL
    7
    }
    8
    end
    • 在指定事件、命令的生命週期前、後,執行腳本任務或顯示訊息

想要更深入的了解 Vagrantfile 語法,請參考官方文件

補充說明

使用一般的 ssh 指令登入 vagrant 建立的機器

vagrant 所建立的機器,會在 host 與虛擬機之間,做 ssh Protocol 的 Port forwarding:

default

vagrant ssh 登入指令的原理,就是一般 ssh 指令的再包裝。

我們可以使用以下指令,取得所有或特定 Host 的 ssh config:

Terminal window
1
# vagrant ssh-config [HOSTNAME]
2
vagrant ssh-config nat_gateway
3
# output
4
Host nat_gateway
5
HostName 127.0.0.1
6
User vagrant
7
Port 2222
8
UserKnownHostsFile /dev/null
9
StrictHostKeyChecking no
10
PasswordAuthentication no
11
IdentityFile D:/workspace/vagrant-space/.vagrant/machines/nat_gateway/virtualbox/private_key
12
IdentitiesOnly yes
13
LogLevel FATAL
14
PubkeyAcceptedKeyTypes +ssh-rsa
15
HostKeyAlgorithms +ssh-rsa

接著就可以使用上面的資訊來 ssh 登入:

Terminal window
1
ssh -i D:/workspace/vagrant-space/.vagrant/machines/nat_gateway/virtualbox/private_key -p 2222 vagrant@127.0.0.1

default

故障排除

在使用 Vagrant 的過程中,最常遇到的問題之一就是資源鎖定衝突。這通常發生在以下情況:

  • 先前的操作未正常結束
  • 有其他 Vagrant 程式正在執行
  • 系統重啟後,遺留的鎖定狀態未正確清除

以我自己為例,在實作本篇的架構時,就發生好幾次,執行 vagrant destroy 移除虛擬機時,遇到這樣的錯誤訊息:

Terminal window
1
vagrant destroy
2
# output
3
An action 'destroy' was attempted on the machine 'ec2_private',
4
but another process is already executing an action on the machine.
5
Vagrant locks each machine for access by only one process at a time.
6
Please wait until the other Vagrant process finishes modifying this
7
machine, then try again.
8
9
If you believe this message is in error, please check the process
10
listing for any "ruby" or "vagrant" processes and kill them. Then
11
try again.

這個錯誤表示 Vagrant 在嘗試刪除 ec2_private 虛擬機時,發現該機器已被其他程式鎖定。這種情況通常發生在先前的操作未正常結束,或是有其他 Vagrant 程式正在使用該虛擬機。

這時,只要找到佔用的應用或程式,強制關閉即可:

Linux

Terminal window
1
ps aux | grep vagrant
2
ps aux | grep ruby
3
# kill
4
kill -9 <PROCESS ID>

Windows

Terminal window
1
Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" }
2
# 顯示更多資訊
3
Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" } | Format-Table -Property Id, Name, CPU, Path
4
# kill
5
Stop-Process -Id <進程ID> -Force
6
# 使用 Pipe 將指令組合
7
Get-Process | Where-Object { $_.Name -like "*vagrant*" -or $_.Name -like "*ruby*" } | Stop-Process -Force

最後再清理環境:

Terminal window
1
vagrant destroy
2
vagrant up

結語

透過 Vagrant,我們不僅可以在本地環境中模擬複雜的雲端架構,還能大幅降低開發和測試的成本。這個工具特別適合以下場景:

  • 開發環境的標準化
  • 測試環境的快速部署
  • 團隊協作時的環境一致性保證
  • 學習和實驗各種網路架構

不過 Vagrant 沒有原生版本控制,擴展性與雲端整合性也沒有 Terraform 強。如果要使用在生產環境,還是建議使用 Terraform。

建議將 Vagrant 主要用於本地開發和學習環境,搭配版本控制系統(如 Git)來管理 Vagrantfile,這樣可以更好地發揮它的優勢。

參考

本文標題:使用 Vagrant 在本地模擬 AWS VPC 架構:NAT Gateway 與 EC2 實例配置指南
文章作者:Vincent Lin
發布時間:2025-01-22