「DevOps系列」 Terraform 實戰(上)——如何管理Terraform狀態

「DevOps系列」 Terraform 實戰(上)——如何管理Terraform狀態

這是Terraform系列的第3部分。 ,我們解釋了為什麼我們選擇Terraform作為我們選擇的IAC工具而不是Chef,Puppet,Ansible,SaltStack或CloudFormation。 ,我們介紹了Terraform的基本語法和功能,並使用它們在AWS上部署Web服務器集群。在這篇文章中,我們將討論Terraform如何管理狀態以及對Terraform項目中文件佈局,隔離和鎖定的影響。

以下是我們將要討論的主題:

  1. 什麼是Terraform 狀態?
  2. 狀態文件的共享存儲
  3. 隔離狀態文件
  4. terraform_remote_state數據源

什麼是Terraform狀態

如果您完成了本系列第2部分中的教程,您可能已經注意到,當您運行terraform plan或terraform apply命令時,Terraform能夠找到它之前創建的資源並相應地更新它們。 但Terraform是如何知道它應該管理哪些資源的呢? 您可以通過各種機制部署AWS賬戶中的各種基礎架構(一些是手動的,一些是通過Terraform,一些是通過CLI),那麼Terraform如何知道它負責哪個基礎架構?

答案是Terraform記錄有關它在Terraform狀態文件中創建的基礎結構的信息。 默認情況下,當您在文件夾/foo/bar中運行Terraform時,Terraform會創建文件/foo/bar/terraform.tfstate。 此文件包含自定義JSON格式,該格式記錄模板中Terraform資源到現實世界中這些資源的表示的映射。 例如,假設您的Terraform模板包含以下內容:

resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}

運行terraform apply後,terraform.tfstate文件將如下所示:

{
"version": 4,
"terraform_version": "0.12.0",
"serial": 1,
"lineage": "1f2087f9-4b3c-1b66-65db-8b78faafc6fb",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "example",
"provider": "provider.aws",
"instances": [
{
"schema_version": 1,
"attributes": {
"ami": "ami-0c55b159cbfafe1f0",
"availability_zone": "us-east-2c",
"id": "i-00d689a0acc43af0f",
"instance_state": "running",
"instance_type": "t2.micro",
"(...)": "(truncated)"
}
}
]
}
]
}

使用這種簡單的JSON格式,Terraform知道類型為aws_instance和 命名為example的資源對應於ID為i-00d689a0acc43af0f的AWS賬戶中的EC2實例。 每次運行Terraform時,它都可以從AWS獲取此EC2實例的最新狀態,並將其與Terraform配置中的內容進行比較,以確定需要應用哪些更改。 換句話說,plan命令的輸出是計算機上的代碼與現實世界中部署的基礎結構之間的差異,通過狀態文件中的ID發現。

如果您將Terraform用於個人項目,則將狀態存儲在本地terraform.tfstate文件中可以正常工作。 但是如果你想在實際產品上使用Terraform作為一個團隊,你會遇到幾個問題:

  1. 狀態文件的共享存儲:為了能夠使用Terraform更新您的基礎架構,您的每個團隊成員都需要訪問相同的Terraform狀態文件。 這意味著您需要將這些文件存儲在共享位置。
  2. 鎖定狀態文件:一旦共享數據,就會遇到一個新問題:鎖定。 如果沒有鎖定,如果兩個團隊成員同時運行Terraform,您可能會遇到競爭條件,因為多個Terraform進程會對狀態文件進行併發更新,從而導致衝突,數據丟失和狀態文件損壞。
  3. 隔離狀態文件:在對基礎架構進行更改時,隔離不同環境是最佳做法。 例如,在staging環境中進行更改時,您需要確保不會意外中斷生產環境。 但是,如果所有基礎架構都在同一個Terraform狀態文件中定義,那麼如何隔離更改?

在以下部分中,我們將深入研究這些問題,並向您展示如何解決這些問題。

狀態文件的共享存儲

允許多個團隊成員訪問一組公共文件的最常用技術是將它們置於版本控制(例如Git)中。 對於Terraform狀態,由於以下原因,這並不是一個好辦法:

  1. 手動錯誤:在運行Terraform之前忘記從版本控制中刪除最新更改或在運行Terraform後將最新更改推送到版本控制是很容易的。 您團隊中的某個人運行Terraform時會出現過時的狀態文件,因此會意外地回滾或複製以前的部署。
  2. 鎖定:大多數版本控制系統不提供任何形式的鎖,以防止兩個團隊成員同時運行terraform應用於同一狀態文件。
  3. 秘密:Terraform狀態文件中的所有數據都以純文本格式存儲。 這是一個問題,因為某些Terraform資源需要存儲敏感數據。 例如,如果使用aws_db_instance資源創建數據庫,Terraform將以純文本形式將數據庫的用戶名和密碼存儲在狀態文件中。 在任何地方存儲純文本秘密都是一個糟糕得事情,包括版本控制。

管理狀態文件的共享存儲的最佳方法是使用Terraform對遠程後端的內置支持,而不是使用版本控制。 Terraform後端確定Terraform如何加載和存儲狀態。 您一直使用的默認後端是本地後端,它將狀態文件存儲在本地磁盤上。 遠程後端允許您將狀態文件存儲在遠程共享存儲中。 支持許多遠程後端,包括Amazon S3,Azure存儲,Google雲端存儲以及HashiCorp的Terraform Pro和Terraform Enterprise。

遠程後端解決了上面列出的所有三個問題:

  1. 手動錯誤:一旦配置了遠程後端,Terraform將在每次運行plan或apply時自動從該後端加載狀態文件,並且每次應用後它將自動將狀態文件存儲在該後端,因此不存在手動錯誤的可能性。
  2. 鎖定:大多數遠程後端本身都支持鎖。 要運行terraform apply,Terraform將自動獲得鎖; 如果其他人已經在運行申請,他們將已經擁有鎖,你將不得不等待。 您可以使用-lock-timeout = <time>參數運行apply,以告知Terraform等待一段時間以釋放鎖(例如,-lock-timeout = 10m將等待10分鐘)。/<time>
  3. 秘密:大多數遠程後端本身支持傳輸中的加密和狀態文件磁盤上的加密。 此外,這些後端通常會提供配置訪問權限的方法(例如,使用帶有S3存儲桶的IAM策略),因此您可以控制誰有權訪問您的狀態文件以及可能包含的機密。 如果Terraform本身支持加密狀態文件中的秘密仍然會更好,但是這些遠程後端減少了大多數安全問題,因為至少狀態文件沒有以明文形式存儲在磁盤上的任何地方。

如果您在AWS上使用Terraform,通常Amazon S3(簡單存儲服務)(亞馬遜的託管文件存儲)是您作為遠程服務器的最佳選擇後端。

要使用S3啟用遠程狀態存儲,第一步是創建S3存儲桶。 在新文件夾中創建一個main.tf文件,並在文件頂部指定AWS作為服務商:

provider "aws" {
region = "us-east-2"
}

接下來,使用aws_s3_bucket資源創建一個S3 bucket:

resource "aws_s3_bucket" "terraform_state" {
bucket = "terraform-up-and-running-state"
# Enable versioning so we can see the full revision history of our
# state files
versioning {
enabled = true
}
# Enable server-side encryption by default
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}

此代碼設置三個參數:

  1. bucket:這是S3 bucket的名稱。 請注意,S3 bucket名稱在所有AWS客戶中必須是全局唯一的。 因此,您必須將bucket參數從terraform-up-running-state(我已經創建)更改為您自己的名稱。 請務必記住此名稱,並記下您正在使用的AWS區域,因為稍後您將再次需要這兩個信息。
  2. 版本控制:此塊允許在S3 bucket上進行版本控制,因此對bucket中文件的每次更新實際上都會創建該文件的新版本。 這允許您查看文件的舊版本,並隨時恢復到那些舊版本。
  3. server_side_encryption_configuration:對於寫入此S3 bucket的所有數據,此塊默認打開服務器端加密。 這可確保您的狀態文件及其可能包含的任何機密在存儲在S3中時始終在磁盤上加密。

接下來,您需要創建一個用於鎖的DynamoDB表。 DynamoDB是亞馬遜的分佈式鍵值存儲。 它支持強一致性讀取和條件寫入,這是分佈式鎖定系統所需的所有組件。

要使用DynamoDB與Terraform進行鎖定,您必須創建一個DynamoDB表,其中包含一個名為LockID的主鍵。 您可以使用aws_dynamodb_table資源創建這樣的表:

resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-up-and-running-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}

運行terraform init以下載服務商代碼,然後運行terraform apply 來部署。 部署完所有內容後,您將擁有一個S3 bucket和DynamoDB表,但您的Terraform狀態仍將存儲在本地。 要配置Terraform以將狀態存儲在S3 bucket中(使用加密和鎖定),您需要在Terraform代碼中添加後端配置。 這是Terraform本身的配置,因此它位於terraform塊中,並具有以下語法:

terraform {
backend "<backend>" {

[CONFIG...]
}
}
/<backend>

其中BACKEND_NAME是您要使用的後端的名稱(例如,“s3”),CONFIG由一個或多個特定於該後端的參數組成(例如,要使用的S3存儲桶的名稱)。 以下是S3後端的後端配置:

terraform {
backend "s3" {
# Replace this with your bucket name!
bucket = "terraform-up-and-running-state"
key = "global/s3/terraform.tfstate"
region = "us-east-2"
# Replace this with your DynamoDB table name!
dynamodb_table = "terraform-up-and-running-locks"
encrypt = true
}
}

讓我們一個個講解下這些設置:

  1. bucket:要使用的S3 bucket的名稱。 確保將其替換為您之前創建的S3存儲桶的名稱。
  2. key:應該寫入Terraform狀態文件的S3 bucket中的文件路徑。 稍後您將看到為什麼上面的示例代碼將此設置為global/s3/terraform.tfstate。
  3. region:S3 bucekt所在的AWS區域。 確保將其替換為您之前創建的S3存儲桶的區域。
  4. dynamodb_table:用於鎖的DynamoDB表。 確保將其替換為您之前創建的DynamoDB表的名稱。
  5. encrypt:將此設置為true可確保在S3中存儲時,Terraform狀態將在磁盤上加密。 我們已經在S3存儲桶本身中啟用了默認加密,因此這是第二層,以確保數據始終是加密的。

要告訴Terraform將狀態文件存儲在此S3存儲桶中,您將再次使用terraform init命令。 這個小命令不僅可以下載服務商代碼,還可以配置Terraform後端(稍後您將看到另一個用途!)。 而且,init命令是冪等的,所以一遍又一遍地運行它是安全的:

$ terraform init
Initializing the backend...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local"
backend to the newly configured "s3" backend. No existing state
was found in the newly configured "s3" backend. Do you want to
copy this state to the new "s3" backend? Enter "yes" to copy and
"no" to start with an empty state.
Enter a value:

Terraform將自動檢測到您已在本地擁有狀態文件,並提示您將其複製到新的S3後端。 如果輸入“是”,您應該看到:

Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes.

運行此命令後,Terraform狀態將存儲在S3存儲桶中。 您可以通過在瀏覽器中轉到S3控制檯並單擊您的存儲桶來檢查:

「DevOps系列」 Terraform 實戰(上)——如何管理Terraform狀態

啟用此後端後,Terraform將在運行命令之前自動從此S3存儲桶中提取最新狀態,並在運行命令後自動將最新狀態推送到S3存儲桶。 要查看此操作,請添加以下輸出變量:

output "s3_bucket_arn" {
value = aws_s3_bucket.terraform_state.arn
description = "The ARN of the S3 bucket"
}
output "dynamodb_table_name" {
value = aws_dynamodb_table.terraform_locks.name
description = "The name of the DynamoDB table"
}

這些變量將打印出S3存儲桶的Amazon資源名稱(ARN)和DynamoDB表的名稱。 運行terraform apply:

$ terraform apply
Acquiring state lock. This may take a few moments...
aws_dynamodb_table.terraform_locks: Refreshing state...
aws_s3_bucket.terraform_state: Refreshing state...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Releasing state lock. This may take a few moments...
Outputs:
dynamodb_table_name = terraform-up-and-running-locks
s3_bucket_arn = arn:aws:s3:::terraform-up-and-running-state

(注意Terraform現在如何在運行apply之前獲取鎖定並在之後釋放鎖定!)

現在,再次轉到S3控制檯,刷新頁面,然後單擊“Versions”旁邊的灰色“顯示”按鈕。現在,您應該在S3存儲桶中看到terraform.tfstate文件的多個版本:

「DevOps系列」 Terraform 實戰(上)——如何管理Terraform狀態

這意味著Terraform會自動向S3和S3推送和拉取狀態數據,S3正在存儲狀態文件的每個版本,如果出現問題,這對於調試和回滾到舊版本非常有用。


分享到:


相關文章: