如何使用CloudFormation 構建VPC(第一部分)

摘要

本文描述如何使用AWS CloudFormation 創建和管理虛擬私有云(VPC),包括子網、NATting 等。這是關於在構建和管理雲資源時將基礎設施視爲代碼的經驗。

本文要點

  • AWS CloudFormation 允許我們在AWS環境中實現“基礎設施即代碼”。

  • 藉助CloudFormation 很容易就可以構建出複雜的虛擬私有云(VPC),並可以自動升級。

  • 我們可以重用CloudFormation 模板構建用於各種用途的各種資源堆棧。

  • CloudFormation 提供了僞參數和內部函數。

-我們可以在用完之後把堆棧刪除。

本文描述如何使用AWS CloudFormation 創建和管理虛擬私有云(VPC),包括子網、NATting 等。本文的重點是使用CloudFormation 和基礎設施即代碼構建和管理AWS資源,有關VPC 設計的問題介紹得相對較少。

網絡專家:由於本文的重點是CloudFormation,所以你可能對CIDR塊、路由表等有不同的看法。沒問題,你可以根據需要修改這個模板。

轉入正題:本文介紹的CloudFormation模板源代碼可以在GitHub上找到。你可以隨意下載、修改和使用此模板(不過我不會爲誤用承擔責任)。

爲什麼使用CloudFormation?

你可能想知道,當我們可以通過管理控制檯中的VPC嚮導創建VPC時,爲什麼要使用CloudFormation來構建VPC,原因如下。

基礎設施即代碼:CloudFormation使我們只用一個步驟就可以創建一個“資源堆棧”。資源是我們創建的東西(EC2實例、VPC、子網等等),一組這樣的資源稱爲堆棧。我們可以編寫一個模板,使用它可以很容易地按照我們的意願通過一個步驟創建一個網絡堆棧。這比通過管理控制檯或CLI手動創建網絡更快,而且可重複,一致性更好。我們可以將模板簽入源代碼控制,並在任何時候根據需要把它用於任何目的。

可升級:我們可以通過修改CloudFormation 模板來修改網絡堆棧,然後根據修改後的模板修改堆棧。CloudFormation足夠智能,可以通過修改堆棧來匹配模板。

可重用:我們可以重用這個模板,在不同時期、不同區域創建多個不用用途的網絡。

漂移檢測:CloudFormation有一個新特性(截止到2018年11月),可以讓我們知道資源是否已經“漂移”出了最初的配置。這可能發生在管理員手動更改資源時,這通常不是成熟的組織所鼓勵的做法。

用完即棄:我們很容易在用完之後把堆棧刪除。

你需要做什麼準備?

學習本教程之前,你需要做好以下準備。

一個AWS賬戶。作爲IAM用戶,你可能可以繼續本教程,但你必須擁有創建VPC、子網、路由表、EC2實例等的權限。通常,我會建議你創建一個你可以完全控制的私人AWS賬戶。在這個帳戶中,創建一個具有全部權限的IAM用戶,用於日常工作。與本教程相關的成本非常低,特別是當你在用完後把堆棧刪除的話。

一個文本編輯器。幾乎任何編輯器都可以:Sublime、Atom、nano,或者Eclipse、IntelliJ、Visual Studio等成熟的IDE。你只要確保不使用文字處理器——它會嵌入一些會導致語法錯誤的特殊字符。我將使用Visual Studio Code,這是我最喜歡的編輯器(本週)。

開始

創建一個空的YAML文件:首先創建一個空文件。保存時將其命名爲“MyNetwork.YML”。你可以使用任何自己喜歡的名稱,但稍後我會用到這個文件名。一定要使用YML擴展名。CloudFormation支持JSON或YAML,我們將使用後者。主要原因是:1)句法不那麼講究,2)能夠在工作中添加註釋,沒有註釋我就記不起一週前在做什麼了。

添加樣板內容:創建好空文件之後,複製並粘貼下面這個結構,這是任何CloudFormation模板都需要的樣板內容:

AWSTemplateFormatVersion: 2010-09-09

# 這個CloudFormation模板會部署一個基本的VPC/網絡

Resources:

註釋:在“#”字符之後的所有內容都是註釋。我鼓勵你們在自己有新發現時寫下自己的註釋。我的建議是:僅把註釋用於描述任何你必須做的不尋常的事情上,尤其是那些你花了一段時間才發現的事情。想象一下,你的同事正在閱讀你的模板,他的問題無法通過已發佈的文檔得到解答,那就是你的註釋。

添加一個VPC

首先要添加的資源是VPC本身。複製這些行,資源段將變成下面這樣:

Resources:
  # 首先,一個VPC:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value:  !Join ['', [!Ref "AWS::StackName", "-VPC" ]]

縮進:第一件事,把間距調整好。“Resources:”前面不應該有空格。“VPC:”應該縮進兩個空格,“Type”和“Properties”應該再縮進兩個空格,等等。不要使用製表符(Tab),除非編輯器會將製表符轉換爲兩個空格。只要知道你正在創建的文件的類型,上面列出的大多數編輯器都會這樣做。縮進在YAML中非常重要,它是我們爲避免JSON中的括號和逗號而付出的代價。

VPC:該資源指示CloudFormation創建一個VPC資源,以及一些基本屬性和名稱。第一行只有“VPC”——這是我們爲了在堆棧中標識這個資源而指定的任意一個名稱。令人困惑的是,許多資源類型都具有各自的“名稱”屬性,那是不同的東西。前者僅用於引用堆棧中的資源,後者是生成的資源的公共名稱。

Type: AWS::EC2::VPC:如果你使用谷歌進行搜索,就會看到一個鏈接,該鏈接將直接把你帶到有關VPC資源的CloudFormation文檔。在這裏,你會看到一個與你在這裏看到的非常相似的示例。我個人認爲,CloudFormation參考頁面是不可或缺的。我承認,複製和粘貼示例代碼片段是我自己創建資源的一個起點。

雙引號:你可以看到,官方文檔在不必要的地方使用了雙引號。值AWS::EC2::VPC不需要用雙引號,下面的“true”值也不需要。我的推測是,AWS文檔團隊最初從JSON中複製了YAML示例,後者幾乎需要把所有內容都放在引號中。

Properties:每個資源都有屬性。文檔會告訴你哪些是必需的,哪些不是。文檔沒有告訴你可選設置的默認值,也沒有解釋大多數設置的含義和允許值。一般來說,你需要完全理解所有可能值的含義、語法和所有可能值的結果,但沒有人能做到這一點。大概90%的開發時間都花在了這種輔助研究上。

CidrBlock:我給我的VPC指定的CIDR是10.1.0.0/16。如果你需要有關這個值的含義的詳細說明,請參閱此描述。但簡單地說,我將爲VPC提供超過65000個可能的私有IP地址,所有這些都將以“10.1”開頭。這對於大多數用例來說已經足夠了。

DNS支持/主機名:這些設置只是爲了可以把DNS主機名自動分配給在VPC中創建的EC2實例。關於DNS的詳細信息這裏就不做過多介紹了,這些設置將提供一個名稱,通過它我們可以到達EC2實例,而不僅僅是一個IP地址。不是必需的,但通常有用。

Tags / Key / Value:我們的VPC需要一個名稱。奇怪的是,這裏沒有“name”屬性要設置。因此,我們將使用一個標籤,其“Key”爲“Name”,“Value”爲我們想要使用的任意VPC名。該值將在許多(但不是所有)情況下用作顯示名稱。

這個值很有意思。我們可以簡單地硬編碼一個名稱,但是如果我們在相同的區域使用這個模板創建兩個堆棧,我們就會擁有兩個同名的VPC(這實際上是可以的,可以試一下!)這可能會造成不必要的混亂,因此,我們將採取額外的步驟來動態分配名稱。

!Join:”!Join”是CloudFormation中稱爲內部函數(intrinsic function)的一種東西。CloudFormation大約有15個這樣的函數,我們將在本文中用到幾個。簡單地說,!Join用於將文本字符串連接在一起。第一個中括號中的’'標識是要放在連接值之間的字符。我們不想要,所以就放了一個空字符串。

**!:**在閱讀文檔時,請注意,函數有一個長格式(fn::Join)和一個可在YAML中作爲替代使用的短格式( !Join)。你會看到,我用的就是這個。但是要注意,在某些情況下,短格式不可用,通常是在將一個函數嵌套到另一個函數中的情況下。

**!Ref:**下一個函數是引用函數。它引用在其他地方定義的東西,通常是在模板中。你將會看到,這個函數用的很多,因爲資源之間經常相互引用。

AWS::StackName:這就是CloudFormation中所謂的僞參數。當我們運行模板時,它將解析爲堆棧的名稱。大約有7個僞參數可用來動態確定當前區域、當前用戶等。使用這些僞參數可以極大地提高模板的靈活性。在這種情況下,VPC的名稱將與它所屬的CloudFormation堆棧相呼應。當你擁有由許多不同堆棧創建的大量資源時,這非常有用——請相信我。

-VPC:這只是VPC名稱的後綴。因此,如果我們通過CloudFormation以堆棧名“MyNetwork”運行這個模板,我們的VPC將被標記爲“MyNetwork-VPC”。這個名稱將在許多(但不是全部)列出或顯示VPC的地方使用。

運行模板

把你所做的工作保存,這還遠未完成,但是,你可以使用CloudFormation運行這個模板來檢查你的工作。

從AWS管理控制檯

從瀏覽器打開AWS管理控制檯。登錄,選擇任何區域。在菜單中找到CloudFormation,如果需要的話,使用搜索功能。登入後,點擊“創建堆棧”。選擇“上傳你自己的模板”選項,然後單擊“下一步”。

出現什麼錯誤了嗎?如果是這樣,就是CloudFormation遇到了語法問題。在繼續之前修復這些問題。通常,錯誤是由於使用製表符代替空格、不正常地使用空格、=代替:或使用的編輯器嵌入了特殊字符造成的。

進入下一頁後,給堆棧起一個名稱。在本文的剩餘部分,我將使用“MyNetwork”,但是你可以使用任何你喜歡的名字。繼續嚮導,不輸入其他任何內容,創建堆棧。

在堆棧運行時,查看“事件(Events)”和“資源(Resources)”選項卡。這些事件將顯示你的VPC是在什麼時候創建的,以及它是在什麼時候成功創建的。當堆棧中的最後一個資源被創建時,堆棧的狀態將更改爲已成功創建。

出現什麼錯誤了嗎?堆棧將顯示創建失敗的狀態。如果這裏遇到的問題不是語法上的,那麼通常就與你試圖創建的內容的邏輯有關。使用事件選項卡查找最早出現的錯誤。我發現,大多數情況下,信息都足夠清晰,可以引導我解決問題,並明確標識出出問題的資源。

有一個重要的概念需要注意,如果在創建堆棧時遇到任何錯誤,整個堆棧(所有資源)將回滾。這種行爲可以被重寫,但通常這樣就行!通常情況下,從頭開始執行每一步要容易得多。

如果堆棧失敗,它仍然會顯示在堆棧列表中,即使堆棧中沒有資源。這樣做是爲了給你時間來調查錯誤。確定問題後,使用“刪除堆棧”操作。

從命令行界面

如果你安裝了AWS CLI,並且已經配置了訪問密鑰和祕密密鑰(任何區域),而且命令提示符與你的YML文件位於同一個目錄,那麼你就可以運行以下命令:

aws cloudformation create-stack --stack-name MyNetwork --template-body file://MyNetwork.yml

–stack-name: 可以是你喜歡的任何名稱,但是我在本文中將始終使用“MyNetwork”。
–template-body:你一直在編輯的文件。

如果有任何語法錯誤,你立馬就可以收到反饋。在繼續之前修復這些問題。通常,錯誤是由於使用製表符代替空格、不正常地使用空格、=代替:或使用的編輯器嵌入了特殊字符造成的。

然而,並不是所有的錯誤都會被CloudFormation立即發現。在創建堆棧幾分鐘後,你可能會遇到非語法錯誤(例如權限)。發生這種情況時,你就需要通過定期地檢查狀態來檢測問題。另一個方便的選擇是等待堆棧完成,或者錯誤輸出,藉助下面這個函數:

aws cloudformation wait stack-create-complete --stack-name MyNetwork

當堆棧完成後,可以使用下面的命令進行檢查:

aws cloudformation describe-stacks

檢查VPC:從管理控制檯轉到VPC部分。你可以在列表中找到你的VPC,以及你提供的名稱。當然,這個VPC中沒有任何內容,但我們將在下一篇文章中討論它。如果你感興趣,這個VPC或CloudFormation的使用都是不收費的,不過下面會有所更改(請參閱NAT部分)。

更新堆棧

在繼續我們的模板之前,讓我們先嚐試一下更新堆棧。CloudFormation其中一個奇妙的特性是能夠基於對模板的更改修改堆棧。要演示這一點,請返回到模板並進行以下一項或多項更改:

  • 將VPC“enableDns*”設置改爲false;

  • 將VPC的tag/value改爲 !Join [’’, [!Ref “AWS::StackName”, “-VPC2” ]]。
    保存修改。

從AWS管理控制檯

從列表中選擇現有堆棧並選擇“更新堆棧”操作。選擇“上傳你自己的模板”選項,然後單擊“下一步”。繼續點擊“下一步”,一直到要求你創建更改集的界面。更改集實際上就是CloudFormation打算應用於你的資源的更改。CloudFormation可以輕鬆地進行一些更改,但有些需要刪除和重新創建現有資源。選擇最後一個選項來執行更改集,並查看正在進行的更改。

從命令行界面

從命令行界面更新堆棧請使用以下命令:

aws cloudformation update-stack --stack-name MyNetwork --template-body file://MyNetwork.yml

和之前一樣,你可以使用wait 命令來監控這個過程:

aws cloudformation wait stack-update-complete --stack-name MyNetwork

我們的堆棧很小,所以更新應該只需要幾分鐘。你可能會遇到錯誤,如果出現這種情況,結果會更令人困惑,因爲CloudFormation會將你的堆棧還原到它之前的形式。由於各種原因,可能會發生錯誤,例如刪除/重新創建資源會影響堆棧外的另一個資源。

刪除堆棧

關於原生雲,我們需要熟悉的一個概念是,資源用完即棄。現在我們有了模板,可以隨時創建和刪除堆棧。

從控制檯選擇堆棧並執行“刪除堆棧”操作,或者從命令行界面運行以下命令:

aws cloudformation delete-stack -stack-name MyNetwork

Internet網關和附件

回到我們的模板:大多數VPC需要連接到互聯網。我們將通過爲InternetGatway和GatewayAttachment添加資源來實現。在VPC資源下複製以下這些行:

  # 我的VPC需要訪問互聯網: 
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DependsOn: VPC
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    # 注意,除非兩個都已經創建,否則你無法將IGW附加到VPC:
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

AWS::EC2::InternetGateway: 沒有它,我們的VPC將無法與公網交互。值得注意的是,你不必將VPC連接到互聯網,許多組織構建的VPC完全脫離公共世界,使用VPN連接或AWS Direct Connect專門連接到現有的本地網絡。

VPCGatewayAttachment: AttachGateway資源更有趣。這是VPC和InternetGateway之間真正的鉤子。注意屬性:VpcId引用了上面定義的VPC資源,而InternetGatewayId引用我們的InternetGateway。當CloudFormation運行一個堆棧時,它將嘗試同時創建所有資源。然而!Ref函數意味着一個順序,CloudFormation將同時創建VPC和InternetGateway,但是在使用它們創建“附件(attachment)”之前,它們都必須完成。

!Ref: 如上所述,這是CloudFormation內置的“引用”函數。這是資源相互引用的主要方式。

稍後,在運行此模板時,你會發現,創建前三個資源(尤其是附件)需要幾秒鐘。稍後,這個延遲會成爲問題,在附件完成之前我們無法與公網進行任何交互。稍後請注意使用“DependsOn”來解決這個問題。

某個子網

現在,使用下面的代碼創建一個子網:

PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.10.0/24
      AvailabilityZone: !Select [ 0, !GetAZs ]    # 獲取列表中的第一個AZ  
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-Public-A

PublicSubnetA: 同樣,任意資源名稱,僅適用於堆棧內。你喜歡怎麼叫就怎麼叫。名稱中的“A”是爲了表明將這個子網關聯到可用區域“A”(如下)。

**!Ref VPC:**子網必須存在於VPC中,所以這就是我們將它們關聯起來的方式。VPC會首先創建,然後把這個子網附加到這個VPC。

CidrBlock:這個CIDR是VPC(10.1.0 /16)的子區間。本質上,它是指這個子網將包含所有以10.1.10.*開始的地址。這也意味着子網只有256個可用地址(實際上是251個,因爲AWS保留5個地址自己用,參見說明)。這有點小,而且我正在造成相當數量的浪費,所以在現實場景中,你可能希望調整這個值。想要深入瞭解CIDR,請參見VPC部分提供的鏈接。

AvailabilityZone:每個子網的作用域是特定的可用區域。我們可以簡單地硬編碼一個值,如“us-east-1a”,但這將我們的模板限制在了維吉尼亞州。最佳實踐是構建儘可能與區域無關的模板,因此我們動態地確定AZ。

**!GetAZs:**另一個內置的CloudFormation函數。它返回當前區域中所有可用區域的列表(即不管我們在何處運行堆棧)。例如,如果我們在俄勒岡州運行,返回的列表將是{us-west-2a, us-west-2b, us-west-2c}。關鍵的是,無論何時調用,它都將以相同的順序顯示這些AZ。

**!Select:**這個內置函數從指定的列表中選擇指定的索引。0是列表中的第一項,1是第二項,等等。所以這個表達式表示“給我列表中的第一個AZ”。如果是在俄勒岡州運行,就會得到“us-west-2a”,如果是在維吉尼亞州運行,就會得到“us-east-1a”等等。總之,我們的子網會自動把自己與它在其中運行的區域中的第一個AZ關聯起來。

Tags:和前面一樣,我們想給子網一個簡單的名稱,但是沒有name屬性可用。使用“Name”標記提供一個在許多(但不是全部)需要列出或顯示子網的地方使用的名稱。

**!Sub:**這是替換函數。它獲取後面的字符串,並動態替換在${}標記中找到的任何值。在我們的示例中,由於AWS::StackName會被翻譯成“MyNetwork”,所以得到的子網名將是“MyNetwork-Public-A”。

你可能已經注意到,這實現了與前面介紹的!Join函數相同的結果。我這樣做是爲了向你展示兩種可以互相替代的技術。哪個更好?那得由你來決定。如果需要在連接的項之間插入分隔符字符,!Join通常會更好。
更多子網
讓我們遵循上面的模式創建更多的子網:

  PublicSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.20.0/24
      AvailabilityZone: !Select [ 1, !GetAZs ]    # 獲取列表中的第二個AZ
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-Public-B
  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.50.0/24
      AvailabilityZone: !Select [ 0, !GetAZs ]    # 獲取列表中的第一個AZ
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-Private-A
  PrivateSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.60.0/24
      AvailabilityZone: !Select [ 1, !GetAZs ]    # 獲取列表中的第二個AZ
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-Private-B

這些子網之間的區別包括:1)邏輯名稱、2)物理名稱(通過標記值)、3)CIDR範圍(沒有重疊)和4)指定的可用區域。結果是四個子網,兩個是“公共”的,兩個是“私有”的,兩個在可用區域“A”,兩個在可用區域“B”。好了。

如果你願意,現在可以保存並運行此模板。我建議像這樣通過迭代開發來檢查錯誤。

添加路由表

不管名稱,子網僅根據其所關聯的路由表的定義確定是“公共”或“私有”。路由表定義了可以在子網中將流量路由到哪裏。這個主題很複雜,我不會在這裏詳細介紹,有關子網路由的詳細信息,請參閱此信息。

用於子網的部分路由表:

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: Public
  PublicRoute1:   # 直接路由到IGW的公共路由表:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway  

PublicRouteTable:又一個僅在堆棧中使用的任意名稱,但這裏我們儘量使這個名稱具有描述性。路由表必須與VPC關聯。使用堆棧前綴名進行標記,以便以後更容易識別。

PublicRoute1:這描述了路由表中的一個條目。該條目與公共路由表(!Ref PublicRouteTable)關聯,並將任何互聯網流量(DestinationCidrBlock: 0.0.0.0/0)路由到互聯網網關(!Ref InternetGateway)。每個路由表的第一個條目都是隱含的,我們看不到,這個條目稱爲“local”路由,所有10.1.0.0/16的流量都會留在VPC中。關於本地路由的詳細信息,請點擊這裏

**DependsOn: AttachGateway:**這是一個關鍵點。如果我們試圖構建一個指向未附加網關的路由表條目,就會發生錯誤。爲了消除這個錯誤,我們讓CloudFormation知道這個依賴關係。在堆棧創建期間,它就不會嘗試構建此路由表條目,直到創建了AttachGateway資源之後。它還將等待VPC和互聯網網關,不過,這些會提前完成。

你可能還是不知道,爲什麼CloudFormation不能自己弄清楚這種依賴關係——這是一個很好的問題。CloudFormation肯定知道你通過!Ref或!GetAtt(稍後將介紹)所做的任何顯式引用,但是,它確實無法弄清楚這些資源在你引用它們時實際上是否能夠工作。CloudFormation只是代替你進行API調用。如果使用CLI創建引用未附加網關的路由表條目,會遇到同樣的錯誤。這種情況也出現在其他一兩個地方,比如當EC2實例需要連接到未完成的RDS實例。

你可以保存你的工作並運行該模板,它應該可以成功。你會注意到,大部分創建時間都花在了AttachGateway資源上。

此時,你的公共子網是可用的。如果你在其中一個子網中使用公共IP地址啓動EC2實例,就可以從公網訪問它。現在,私有子網…
私有路由表
私有路由表在大多數方面都是相似的,除了不引用InternetGateway:

  # 這是一個私有路由表:
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: Private
  PrivateRoute1:            # 私有路由表可以通過NAT訪問Web(下面創建)
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      # Route traffic through the NAT Gateway:
      NatGatewayId: ???

NatGatewayId:如果你猜到“???”是一個無效的值,那麼你猜對了。要解釋這一點,我們首先需要岔開下話題,快速地解釋下NAT的概念(你可以根據需要選擇跳過)。

NAT可以做什麼?

NAT代表網絡地址轉換。要獲得完整的解釋,請參閱關於NAT的背景信息。但簡單來說:我們不希望私有子網中的實例可以從公網訪問。但是,我們確實希望這些實例能夠發起出站連接,例如下載。另外,我們希望他們能夠在沒有公共IP地址的情況下做到這一點。

NAT爲此提供了便利。它將擁有一個公共IP地址,並與一個公共子網相關聯。私有子網中的私有實例將能夠使用它發起出站連接。但是,NAT不允許相反的情況發生,位於公網上的一方不能使用NAT連接到我們的私有實例。

在AWS中有兩種基本的NATting(這是一個詞嗎?),一種是配置爲NAT的EC2實例,另一種是相對較新的AWS特性,稱爲NAT網關。我們將使用後者。

創建一個NAT網關

添加以下幾行創建一個NAT網關:

 # NAT網關:
 NATGateway:
   Type: AWS::EC2::NatGateway
   Properties:
     AllocationId: !GetAtt ElasticIPAddress.AllocationId
     SubnetId: !Ref PublicSubnetA
     Tags:
     - Key: Name
       Value: !Sub NAT-${AWS::StackName}
 ElasticIPAddress:
   Type: AWS::EC2::EIP
   Properties:
     Domain: VPC

NAT網關與其中一個公共子網相關聯。我們可以(可能也應該)爲每個公共子網創建一個NAT網關,但現在,單個網關使事情變得簡單。名稱像以前一樣是動態設置的。

AllocationId:NAT需要一個固定的公共IP地址。這是由一個彈性IP地址提供的,下面會說明。

**!GetAtt:**又一個隱函數,這裏有介紹。這引用了另一個資源的特定屬性。這裏,使用!Ref不起作用,NAT網關資源需要彈性IP地址的allocationId,而不是地址本身。值得注意的是,文檔列出了你可以從每個資源“獲取”的屬性,通常是資源屬性的子集。

ElasticIPAddress:EIP是一個公共IP地址,它的值保持不變,不管它附加到什麼。這裏有完整的說明,足以說明這對於我們的NAT是必要的。

價格:到目前爲止,堆棧中的每個資源都是免費的。NAT網關不是。它們是按照小時以及通過的流量收費,見VPC定價。EIP的不同尋常之處在於,只要使用它們,就不收取任何費用。在沒有附加到運行資源的情況下,AWS每小時只收取象徵性的費用,這可以防止客戶囤積資源。這使得這個堆棧的成本大約是每小時1或2美分,這取決於你所運行的區域。便宜,但一定要在完成後刪除堆棧,以防止成本累積。

現在,我們有了一個NAT網關,我們可以在路由表中引用它了。修改後的路由如下所示:

PrivateRoute1:            # 私有路由表可以通過NAT(下面創建)訪問Web
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      # 通過NAT網關路由流量
      NatGatewayId: !Ref NATGateway

這條路由將互聯網流量(DestinationCidrBlock: 0.0.0.0/0)發送到NAT網關(!Ref NATGateway)。反過來,由於NAT位於公共子網中,它會將其發送到互聯網網關。NAT接收到的響應流量被轉發到發出請求的實例。同樣,沒有顯示前面介紹的本地隱式路由。

將路由表附加到子網

最後,我們需要將子網關聯到它們的相關路由表。命名爲“public”的需要關聯到公共路由表,而“private”的需要關聯到私有路由表:

# 把公共子網附加到公共路由表
  # 並把私有子網附加到私有路由表: 
  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetB
      RouteTableId: !Ref PublicRouteTable
  PrivateSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTable
  PrivateSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetB
      RouteTableId: !Ref PrivateRouteTable

AWS::EC2::SubnetRouteTableAssociation:關聯資源只是將子網關聯到路由表。如果沒有這些關聯,子網將使用與VPC關聯的“main”路由表,而這沒有包含在我們的模板中。最好有明確的路由表和關聯關係。

恭喜!現在,你已經擁有了一個用於構建典型VPC的功能完整的CloudFormation模板。保存起來以供將來使用,並根據需要擴展它。使用這個模板和前面介紹的指令創建一個堆棧,根據需要更新和刪除它。

如果你願意,可以通過在VPC中運行EC2實例來測試VPC。已經把公共IP地址附加到公共子網的的實例將可以從公網訪問。私有子網中的實例將無法從公網訪問,但可以進行出站調用。關於這一點,我建議你閱讀其他文章。

記住,NAT網關每小時要花一到兩便士,所以如果你不使用它,就最好刪除它。

小結

通過CloudFormation創建、修改和刪除資源堆棧的能力是基礎設施即代碼概念的一個有效的示例。現在,我們有了一個更快、可重複、可重用的系統,我們不再需要通過界面或命令來手動設置基礎設施。

在下一篇文章中,我將向你展示如何使這個模板更加靈活,使用參數和條件創建數量不同的子網,使私有子網可選,並探討其他NAT選項。我們還將看到,如何把這個堆棧的資源所生成的輸出作爲其他堆棧的輸入供其消費。

關於作者

Ken Krueger以“通過現代技術的應用,指導組織和個人走向商業成功”作爲自己的專業使命。他有超過30年的軟件開發、項目領導、項目經理、Scrum Master和導師經驗,跨越大型機、客戶端-服務器和Web時代。他在Java、Spring、SQL、Web開發、雲和相關技術方面有豐富的經驗。行業經驗包括電信、金融、房地產、零售、發電、航運、酒店和軟件開發。他擁有南佛羅里達大學MIS學位,羅林斯學院克魯默商學院MBA學位,以及Scrum Master、PMP、AWS和Java認證。

查看英文原文:Building a VPC with CloudFormation - Part 1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章