一千萬個為什麽

搜索

Terraform:如何支持發布代碼的新版本,與上一個穩定版本並行運行?



我有一個生產服務(在AWS上),它遵循不可變的服務器模式。其部署如下所示:

  1. Create a new AMI with Packer
  2. Create a new CloudFormation stack, starting with an auto-scaling group of size 1.

當我看到新版本很好時,我可以增加實例的數量並最終關閉舊實例,最後從以前的版本中刪除CloudFormation堆棧。

在我的部署初始版本中,我只使用了一個堆棧並對其進行了更新。對於正常版本,這意味著自動擴展組由CloudFormation修改為指向新的AMI。然後,我不得不殺死一個現有的實例,或者增加自動擴展組來獲得運行該版本的實例。

對於每個版本使用新的堆棧使得該過程對我來說更簡單,因為回滾更容易,並且可以更輕松地向部分用戶推出版本。類似於不可變的服務器模式,我避免了原地更新,而只是創建新資源(在這種情況下為堆棧)。

在我工作的公司中,現在更常用的是使用 Terraform 而不是CloudFormation。我想知道是否有可能熟練使用Terraform繪制的部署。我不介意使用其他工具,我的主要觀點是我想保留這些基本概念:

  • 允許部署新版本而不觸及穩定版本
  • 不是(就地)更新設置,而是創建新資源並終止舊資源。

所以,迄今為止,我只與Terraform一起工作過,並且只用它來管理我們基礎設施的一小部分。按照建議,我將狀態保存在S3存儲桶中,例如:

# (from main.tf)
terraform {
  required_version = ">= 0.9.4"
  backend "s3" {
    bucket = "example-company-terraform-state"
    key    = "/foo-service/terraform.tfstate"
    region = "eu-central-1"
  }
}

在這裏,鑰匙總是固定的。所以,Terraform將會更新所有的東西。我認為你可以使用新版本的新密鑰來創建新的CloudFormation堆棧。

我沒有弄清楚的是如何將流量轉移到新的實例。所有實例都應位於負載平衡器(ELB)之後。我認為,您可以為ELB使用單獨的設置,這會將流量分配給舊版本和新版本的實例。

所以,如果新版本推出,將會有三種不同的Terraform狀態:

  1. ELB的Terraform設置
  2. 舊版本的Terraform設置(更具體地說,它的自動縮放組)
  3. 新版本的Terraform設置

的問題:</強>

  • 使用Terraform的不同S3鍵的方法與使用單獨的CloudFormation堆棧有相同的效果嗎? (我在尋找的是一種讓多個Terraform設置互不幹擾的方法)。
  • 您是否看到解決共享ELB背後的所有實例(來自舊版本和新版本)的問題?
  • 我讀過你可以在Terraform中導出資源。您是否認為我可以為ELB創建Terraform設置,將ELB作為資源導出,並將其用於Terraform設置中的實例(以及自動縮放組)以使它們連接到共享的ELB?
  • >

(註意:對於我的其他服務,在我使用CloudFormation的地方,我們不使用ELB,因此嚴格來說,這是一個不同的問題,我只是提到它來解釋我對部署有用的內容,以及為什麽我會考慮應用一些其他服務的想法。)

轉載註明原文: Terraform:如何支持發布代碼的新版本,與上一個穩定版本並行運行?

一共有 1 個回答:

實現這種目標有幾種不同的方式,每種方法都有一些不同的折衷。我將描述下面最常見的一些。


最簡單的方法是使用 Terraform的 create_before_destroy 機制與自動縮放組合。這種模式的示例包含在 aws_launch_configuration 文檔

在這種情況下,更改AMI id會導致啟動配置被重新創建。由於 create_before_destroy ,首先創建新配置,然後創建一個新的自動縮放組,將新實例添加到所連接的ELB。 aws_autoscaling_groupmin_elb_capacity 參數可用於確保在考慮創建自動縮放組之前,在連接的ELB中存在給定數量的實例並保持健康狀態,從而延遲銷毀舊的自動縮放組並啟動配置,直到新服務請求發出。

這種方法的缺點是缺乏對它的控制。由於Terraform將整套更改視為單次運行,因此在創建新實例後不可能暫停,以便在破壞舊實例之前執行其他檢查。因此,ELB健康檢查是決定新版本是否“良好”的唯一輸入,並且一旦舊資源被破壞就不可能回滾。


第二種常見方法是采用一種“藍/綠部署”模式,並對兩個集群進行明確更改。這是通過將所有每個版本的資源放在一個子模塊中,並使用不同的參數實例化該模塊兩次來完成的。在頂層模塊中,這看起來如下所示:

resource "aws_elb" "example" {
  instances = "${concat(module.blue.ec2_instance_ids, module.green.ec2_instance_ids)}"

  # ...
}

module "blue" {
  source = "./app"

  ami_id = "ami-1234"
  count  = 10
}

module "green" {
  source = "./app"

  ami_id = "ami-5678"
  count  = 0
}

這裏的操作原理是,在“穩定狀態”(不進行部署)中,這些模塊中只有一個具有非零計數,另一個具有零。在部署期間,它們都設置為相同的非零計數,但具有不同的 ami_id 值。每個部署都會交換哪個模塊是“活動”模塊,並且這兩個模塊在部署過程中均處於活動狀態。

使用這種方法時,每一步都是一個獨特的Terraform操作:

  1. 將 模塊的計數更改為非零並設置其AMI ID
  2. 在Terraform中應用更改,從而激活新模塊
  3. 確認新版本不錯
  4. 將舊模塊的計數更改為零
  5. 在Terraform中應用更改,從而停用舊模塊

雖然這有更多的步驟,但它允許在步驟3期間通過任意驗證和任意數量的時間。它還允許通過將先前不活動的群集計數重置為零來“回滾”。

由於舊集群和新集群都以相同配置存在,因此存在錯誤地使用此模式並過早破壞活動集群的風險。這可以通過仔細閱讀Terraform的計劃來減輕,以確保它保持原有群集不變,但Terraform本身無法保證這一點。

另外,由於兩個集群都使用相同的子模塊配置,因此在保留藍色/綠色分隔的同時更新該配置可能會非常棘手。如果進行更改需要Terraform替換正在運行的實例,則需要臨時在磁盤上擁有兩個模塊代碼副本,使 source 參數指向單獨的副本,並僅將更改設置為非活動模塊使用的副本。


我將介紹的最後一種方法是最極端的和手動的,但它能夠滿足您的要求並保持控制。實際上,這是對當前CloudFormation工作流程的最直接解釋,並且是您在問題中討論的方法的更具體的版本。

在這種方法中,有兩種完全獨立的Terraform配置,我將其稱為“版本不可知”(必須在版本之間存在的東西,比如ELB)和“版本特定的”(重新創建的資源每個新版本)。

版本不可知的配置將包含ELB,並且如您所懷疑的那樣將按照特定於版本的配置導出其ID以用於消費。

terraform {
  required_version = ">= 0.9.4"
  backend "s3" {
    bucket = "example-company-terraform-state"
    key    = "exampleapp/version-agnostic"
    region = "eu-central-1"
  }
}

resource "aws_elb" "example" {
  # ...
}

output "elb_id" {
  value = "${aws_elb.example.id}"
}

這種配置可以像平常一樣初始化,計劃和應用,創建一個ELB而不需要附加實例來啟動。

特定於版本的配置與以前方法中的“app”子模塊相似,但是這次是作為頂級模塊。該模塊的後端配置將省略S3密鑰,因為這將隨每個新版本的變化而變化:

terraform {
  required_version = ">= 0.9.4"
  backend "s3" {
    bucket = "example-company-terraform-state"
    region = "eu-central-1"
  }
}

運行 terraform init 時,可以設置(或重新設置)特定的鍵:

$ terraform init -reconfigure -backend-config="key=exampleapp/20170808-1"

在這裏,我選擇使用“當前日期,發布索引”元組作為發布的標識符。通過為該參數運行帶有新值的 terraform init ,將創建​​一個完全獨立的狀態,與上一個狀態無關。使用 -reconfigure 告訴Terraform您不希望將舊狀態遷移到新的狀態,而是直接切換到新的狀態路徑,可能會在流程中創建一個新狀態。

然後,您可以運行 terraform show 來確認狀態是否為空(因此操作不會影響現有資源),然後像平常一樣運行計劃/應用周期。

一旦您對新版本感到滿意,您可以切換回以前的版本並銷毀它。

為了填充 aws_autoscaling_groupload_balancers 屬性,特定於版本的配置將需要版本無關配置中的ELB的ID。為了訪問它,我們可以使用 terraform_remote_state 數據源從S3中的狀態讀取值:

data "terraform_remote_state" "version_agnostic" {
  backend = "s3"
  config {
    bucket = "example-company-terraform-state"
    key    = "exampleapp/version-agnostic"
    region = "eu-central-1"
  }
}

resource "aws_autoscaling_group" "example" {
  # ...

  load_balancers = ["${data.terraform_remote_state.version_agnostic.elb_id}"]
}

在這種復雜性的系統中,最好通過某種包裝腳本或編排來運行Terraform,從而使發布過程更輕松。例如,這樣的腳本可能會自動生成新的版本號,以避免操作人員誤認日期或意外與現有版本沖突的風險。在指南 運行中,通過腳本運行Terraform有一些建議和註意事項自動化中的Terraform


雖然這裏的第三個選項是CloudFormation方法的最直接映射,但第二個選項更常用,因為它在控制和工作流程開銷之間實現了合理的折衷。