Amazon-Ec2

CloudFormation 模板中的 EC2 實例交叉引用

  • July 11, 2017

我在這裡有點麻煩。我有一個特定的應用程序(一個我無法修改的應用程序),我需要使用 CloudFormation 在 3 個 AWS EC2 實例上自動部署它,並且我需要它們中的所有 3 個在它們啟動時相互了解,或者至少在任何流量之前打他們。

簡而言之,需要發生的是,隨著機器啟動,它們需要各自執行一個本地命令(一個 Windows shell 腳本),其中包括所有3 台機器的主機名或 IP。想像一下:

c:\>node startReplication.js server1.aws.com,server2.aws.com,server3.aws.com

讓這更難的是我不能使用靜態 IP 或名稱,我需要它對每個堆棧都是完全動態的。 我也不能使用任何非 AWS 原生工具,例如 Chef 或 Terraform或任何其他第 3 方,原因與此處無關,無法在此處指定。一切都必須通過原生 AWS 服務完成。

我試過做這樣的事情:

"UserData" : {
       "Fn::Base64" : {
           "Fn::Join" : [ ",", [
               { "Fn::GetAtt" : [ "server1", "PublicDnsName" ] },
               { "Fn::GetAtt" : [ "server2", "PublicDnsName" ] },
               { "Fn::GetAtt" : [ "server3", "PublicDnsName" ] } ]
       }
   }

只傳遞 dns 名稱,甚至還沒有達到弄清楚如何實際執行腳本/命令的地步——但是由於循環引用,這已經失敗了。

我的理解是,CloudFormation 將這些引用視為依賴項 - 所以要引用“server1”,我需要創建它,但如果所有 3 台機器都需要所有 3 個引用,我就碰壁了。

我對 AWS 的經驗不足(實際上,一點也不),無法為此找到替代路徑,但我有一些理論想法,您可能可以確認或提出自己的建議:

  1. 讓每台機器將自己註冊到某個外部位置 - 例如 S3 儲存桶、AWS Config、SQS 等。當 CF 完成後,執行一個 lambda,它使用該數據以某種方式在所有 3 台機器上啟動一個 shell 腳本。
  2. 當所有實例都被創建(使用等待?)並且它們的 DNS 名稱可用時,也許 CF 有一種執行 shell 腳本的方法?
  3. 開發某種代理服務以在所有機器上執行,讓一個代理服務獲取對其他兩台使用的引用UserData,然後讓它將該資訊發送給另外兩台以觸發所述腳本
  4. 也許cf-init從使用腳本啟動的節點程序執行userdata以獲取此資訊並將其傳遞給需要執行的後續腳本(不確定這是否是 cf-init 的工作方式以及是否在所有 3 台機器都分配了 dns 名稱之後發生)

我想避免過於復雜或令人費解的解決方案,因為我的知識和時間都有限(這不是當今成功的秘訣嗎?)

我希望我已經把這個問題說得夠清楚了。提前致謝!

使用 CloudFormation 堆棧名稱作為 3 個伺服器域名的子域。這樣您就可以知道 DNS 名稱是什麼,並且可以將其註入到配置文件中。

"DNSRecordInstance1": {
 "Type": "AWS::Route53::RecordSet",
 "Properties": {
   "HostedZoneName": { "Ref": "HostedZone" },
   "Name": {
     "Fn::Join": [ ".", [
         "server1",  { "Ref": "AWS::StackName" }, ".", { "Ref": "HostedZone" }
     ] ]
   },
   "Type": "A",
   "TTL": "900",
   "ResourceRecords": [
     { "Fn::GetAtt": [ "Instance1", "PrivateIp" ] }
   ]
 }
}

server1.<stack-name>.<hosted-zone>例如,這將創建一個 DNS 記錄server1.test-stack.example.com。您為所有 3 個實例執行此操作。

然後在每個實例的元數據中,您可以立即創建配置文件,因為您知道 DNS 名稱將是什麼並且不需要知道 IP 將是什麼 - DNS 會處理它。

"Instance1": {
 "Type": "AWS::EC2::Instance",
 "Properties": {
   [...]
   "UserData": {
     "Fn::Base64": { "Fn::Join": ["", [
       "<script>\n",
       "cfn-init.exe -v -s ", { "Ref" : "AWS::StackId" }, " -r Instance1", " --region ", { "Ref" : "AWS::Region" }, "\n",
       "</script>"
     ] ] }
   }
 },
 "Metadata": {
   "AWS::CloudFormation::Init": {
     "config": {
       "files": {
         "c:\\servers.conf": {
           "content": { "Fn::Join": ["", [
             "server1.", { "Ref": "AWS::StackName" }, ".", { "Ref": "HostedZone" }, "\n",
             "server2.", { "Ref": "AWS::StackName" }, ".", { "Ref": "HostedZone" }, "\n",
             "server3.", { "Ref": "AWS::StackName" }, ".", { "Ref": "HostedZone" }, "\n"
             ]]}
         }
       }
     }
 [...]

這將創建C:\servers.conf一個包含 3 個伺服器 DNS 名稱的列表。同樣,在您的每個實例上執行此操作,您就完成了:)

上面的模板片段還可以幫助您執行 Cloud Init 腳本。確保您保持實例引用正確,即cfn-init.exe -r Instance1在 Instance1 的 UserData 中。將其更新cfn-init.exe -r Instance2為 Instance2 等。後面的標籤-r必須是定義它的資源名稱。

希望有幫助!

引用自:https://serverfault.com/questions/861156