虛擬專用雲端(Virtual Private Cloud ,VPC)在雲端運算技術中扮演了重要角色。
VPC能讓兩個雲端實例之間進行私有通信,將網路流量與其他網路使用者隔離開,從而提高安全性。
換句話說,作為一種虛擬網路環境,VPC能在公有雲基礎架構中創建一個隔離且私密的網路空間,為使用者提供一個安全、靈活、可自訂的網路環境,幫助他們建置和管理各種類型的應用和服務。
也就是說,就像是「套娃」一樣,我們可以藉助這種技術,在公共網路的基礎上建構一個規模更小,更私密的專用「Internet」來。想想是不是還蠻激動?
下文我們將透過一個儀表板為例進行介紹。
為此我們將部署兩個應用程序,每個應用程式由一個應用伺服器和一個資料庫伺服器組成(總共四個伺服器)。
第一個應用程式和對應的資料庫伺服器採用正常方式來部署,第二組則配置為在VPC中運作。
每個應用程式的前端都使用Qwik構建,並使用Tailwind進行樣式設計。 伺服器端由Qwik City(Qwik的官方元框架)提供支持,並在Node.js上運行,託管在共享的Linode VPS上。
這些應用程式也使用PM2進行進程管理,並使用Caddy作為反向代理和SSL供應商。
資料儲存在PostgreSQL資料庫中,該資料庫也在共享的Linode VPS上運作。
應用程式使用Drizzle與資料庫進行交互,Drizzle是JavaScript的物件關聯映射器(ORM)。
兩個應用程式的整個基礎架構都由Terraform管理,具體來說,使用了Terraform的Linode提供者進行管理。
初次接觸Linode的同學對此可能感覺有些陌生,但只要學會了操作方法,就能快速輕鬆地進行基礎架構的配置和銷毀。
如上文所述,本例將部署兩個相同的應用程式。
這方面並沒有特別需要注意的,對應的內容如下圖。
整個技術棧沒有什麼特別之處。
之所以選擇這些工具也沒什麼特別的原因,大家完全可以結合自己的需求和習慣來選擇自己覺得最適合的工具。
真正有趣的部分在於基礎架構。
首先看看一號應用程序,它基本上由兩個託管在Akamai雲端中的伺服器組成,一個用於應用程序,另一個用於資料庫。
當使用者載入應用程式時,應用程式伺服器將從資料庫中提取數據,建立HTML,並將結果傳回給使用者。
這裡的問題在於資料庫連線的配置。
在某些情況下,我們可能會部署一個資料庫伺服器,但並不能事先知道需要允許哪些IP位址的設備(如應用程式伺服器)存取。
此時許多人慣用的做法是簡單粗暴地直接允許任何具備正確憑證的電腦連線。
但這會帶來安全隱患,因為攻擊者有可能藉此連接到資料庫並竊取敏感資料。
雖然攻擊者仍然需要知道資料庫主機、連接埠、使用者名稱和密碼才能取得存取權限,因此看起來這個隱患其實也不是很大。
畢竟剛才也說了,這種做法在業界其實很常見。 但其實還有更好的做法!
如果已經知道每台需要存取的電腦的IP位址,那麼比較好的解決方案就是設定防火牆或VLAN。
但如果基礎架構更加動態,伺服器會頻繁啟動和關閉,IP位址清單的維護工作可能會顯得非常麻煩。
這就到了VPC的用武之地。
我們可以將伺服器設定到VPC中,並允只允許位於同一個VPC網路中的電腦相互自由通訊。
這就是二號應用程式的設定方式。
使用者可以連接到應用程式伺服器,該伺服器位於VPC中,但允許處理來自公共互聯網的流量。
此應用程式伺服器可以連接到資料庫,資料庫也位於同一個VPC中,並且只允許來自同一網路的存取。
然後,應用程式伺服器就可以照常從資料庫取得數據,建立HTML,並將頁面傳回給使用者。
對於普通用戶,兩個應用程式的存取體驗沒有任何差異。
瀏覽器可以很好地載入任何一個應用程式的介面。
對一般使用者來說,VPC使用與否都不會對自己產生一絲一毫的影響。
然而對於攻擊者來說,體驗就截然不同了。
即便攻擊者設法取得了資料庫存取憑證,他們也無法連接,因為VPC的網路是被隔離的。
此時,VPC充當了虛擬防火牆的角色,確保只有位於同一個網路中的裝置才能存取資料庫。
(這個概念有時也被稱為「分割[segmentation]」)
透過加入emoji圖示的示意圖來展示技術概念,這固然是一種可愛的做法,但為了更有說服力,最好還是能給出實證。
因此我們將嘗試使用資料庫客戶端DBeaver直接連接兩個資料庫,然後看看會發生什麼。
對於一號資料庫,我們設定了一個Postgres連線:使用從Akamai儀表板取得的主機IP位址以及在Terraform腳本中設定的連接埠、使用者名稱和密碼。發現連接可以按預期工作。
對於二號資料庫,我們只需要更改IP位址,因為所有資料庫配置都是由相同Terraform腳本處理的,唯一的區別是:二號資料庫伺服器被放在與二號應用伺服器相同的VPC中,並且配置為
只允許來自同一網路內的電腦存取。
毫不意外,當我們嘗試連接時,儘管提供了所有正確信息,但依然收到了一個錯誤:
錯誤訊息中完全沒有提到任何關於VPC的內容。
它只是說我們的IP位址不在設定檔的允許存取清單中。 這是合理的。
如果需要,我們可以明確地新增自己的IP位址並獲得對資料庫的存取權限,但這不是重點。
這裡的重點是:我們並沒有明確地將任何IP位址加入Postgres的允許清單中。
然而應用伺服器能夠正常連接,但其他所有人都被VPC阻止了。
為了方便大家自行實驗和練習,我們提供了部署上述應用程式的Terraform程式碼。
相關文件請造訪:github.com/AustinGil/linode-vpc-demo/blob/main/terraform/terraform.tf
需要注意的是,我們已經盡力讓這個Terraform檔案對其他人可重複使用。
這需要更多的變數和基於tfvars檔案的配置設定:github.com/AustinGil/linode-vpc-demo/blob/main/terraform/terraform.tfvars.example。
接下来一起看看其中的关键部分。
首先,由於使用了Linode Terraform提供程序,因此有必要了解這是如何設定的:
terraform {
required_providers {
linode = {
source = "linode/linode"
version = "2.13.0"
}
}
}
variable "LINODE_TOKEN" {}
provider "linode" {
token = var.LINODE_TOKEN
}
這部分設定了提供者以及Terraform要求我們提供的變量,或者大家也可以使用tfvars檔案提供。
接下來需要設定實際的VPC資源以及子網路資源。
resource "linode_vpc" "vpc" {
label = "\${local.app_name}-vpc"
region = var.REGION
}
resource "linode_vpc_subnet" "vpc_subnet" {
vpc_id = linode_vpc.vpc.id
label = "\${local.app_name}-vpc-subnet"
ipv4 = "\${var.VPC_SUBNET_IP}"
}
伺服器只能加入到同一地區的VPC。
在撰寫本文時,Akamai雲端平台支援VPC的地區有13個。 最新資訊請參閱Akamai官網。
有三個IPv4位址範圍是專門保留可用於私有網路(如VPC)的:
10.0.0.0 – 10.255.255.255
172.16.0.0 – 172.31.255.255
192.168.0.0 – 192.168.255.255
因此在配置子網時,我們必須從這三個選項中選擇一個,並且必須使用CIDR格式來指定想使用的IP範圍。這裡我們使用了10.0.0.0/24。
私有網路中的每台伺服器都將擁有該範圍內的一個IPv4位址。
為了讓Terraform部署我們的應用伺服器,我們使用了linode_instance資源,此外還使用了stackscript資源來創建一個可重複使用的部署腳本,用於安裝和配置軟體。這就像一個位於Akamai雲儀錶板中的Bash腳本,我們可以直接將其用於新的伺服器。
相關代碼就不放在這裡了,但需要通過NVM安裝Node.js 20,安裝PM2,克隆專案代碼庫,運行應用,並設置Caddy。StackScript的內容請自行參閱原始程式碼,下文想簡單說說Terraform的部分。
resource "linode_instance" "application1" {
depends_on = [
linode_instance.database1
]
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "\${local.app_name}-application1"
group = "\${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_app_server.id
stackscript_data = {
"GIT_REPO" = var.GIT_REPO,
"START_COMMAND" = var.START_COMMAND,
"DOMAIN" = var.DOMAIN1,
"NODE_PORT" = var.NODE_PORT,
"DB_HOST" = linode_instance.database1.ip_address,
"DB_PORT" = var.DB_PORT,
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
}
}
resource "linode_instance" "application2" {
depends_on = [
linode_instance.database2
]
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "\${local.app_name}-application2"
group = "\${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_app_server.id
stackscript_data = {
"GIT_REPO" = var.GIT_REPO,
"START_COMMAND" = var.START_COMMAND,
"DOMAIN" = var.DOMAIN2,
"NODE_PORT" = var.NODE_PORT,
"DB_HOST" = var.DB_PRIVATE_IP,
"DB_PORT" = var.DB_PORT,
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
}
interface {
purpose = "public"
}
interface {
purpose = "vpc"
subnet_id = linode_vpc_subnet.vpc_subnet.id
}
}
這兩個資源的配置過程幾乎相同,只有一些重要的事情需要注意:
二號應用套裝程式含了將應用添加到VPC的相關配置。
StackScript
需要資料庫的IP位址。一號應用程式使用一號資料庫的公共IP位址(linode_instance.database1.ip_address),二號應用程式則使用了一個變數(var.DB_PRIVATE_IP)。這個變數稍後會用到,它是分配給VPC中運行的二號資料庫的私有IP位址。該位址也可以手動分配,所以我們將其設置為10.0.0.3。
另外還要注意,應用伺服器要部署到與VPC相同的地區,原因如上文所述。
資料庫也使用linode_instance和linode_stackscript資源進行設置。同樣,StackScript的內容就直接略過了,感興趣的同學請直接參閱代碼。接下來我們需要安裝Postgres,設置資料庫和憑據,並提供一些配置選項。
resource "linode_instance" "database1" {
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "\${local.app_name}-db1"
group = "\${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_db_server.id
stackscript_data = {
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
"PG_HBA_ENTRY" = "host all all all md5"
}
}
resource "linode_instance" "database2" {
image = "linode/ubuntu20.04"
type = "g6-nanode-1"
label = "\${local.app_name}-db2"
group = "\${local.app_name}-group"
region = var.REGION
authorized_keys = [ linode_sshkey.ssh_key.ssh_key ]
stackscript_id = linode_stackscript.configure_db_server.id
stackscript_data = {
"DB_NAME" = var.DB_NAME,
"DB_USER" = var.DB_USER,
"DB_PASS" = var.DB_PASS,
"PG_HBA_ENTRY" = "host all all samenet md5"
}
interface {
purpose = "public"
}
interface {
purpose = "vpc"
subnet_id = linode_vpc_subnet.vpc_subnet.id
ipv4 {
vpc = var.DB_PRIVATE_IP
}
}
}
與應用程式伺服器一樣,這兩個資料庫伺服器大致相同,但有一些關鍵區別:
第二個資料庫包含將自己添加到VPC的配置。
用戶端身份驗證文件(pg_hba.conf)中會被寫入不同的設置。一號資料庫允許所有的互聯網連接("host
all all all md5"),而二號資料庫只允許來自相同網路的訪問("host all all
samenet md5")。
另外需要注意,在配置VPC設置時,我們明確分配了伺服器的私有IP地址(var.DB_PRIVATE_IP)。這與應用程式伺服器所獲得的靜態值相同,因此可以從VPC內部連接到資料庫。
希望本文能讓大家瞭解到VPC是什麼,有什麼用,以及何時應該考慮使用。這就像擁有了自己的小型私有互聯網。嚴格意義上來說,VPC並不是為了替代VLAN或防火牆,但它是現有安全實踐很好的補充。