[英]Initial setup of terraform backend using terraform
我剛剛開始使用 terraform,我希望能夠使用 AWS S3 作為我的后端來存儲我的項目的 state。
terraform {
backend "s3" {
bucket = "tfstate"
key = "app-state"
region = "us-east-1"
}
}
我覺得使用 terraform 為后端存儲基礎設施設置我的 S3 存儲桶、IAM 組和策略也是明智的。
如果我在應用初始 terraform 基礎設施之前設置后端 state,它會合理地抱怨后端存儲桶尚未創建。 所以,我的問題是,如何使用 terraform 設置我的 terraform 后端,同時讓 terraform 跟蹤我的后端 state。看起來像是嵌套娃娃問題。
我對如何編寫腳本有一些想法,例如,檢查存儲桶是否存在或是否已設置某些 state,然后引導 terraform,最后在第一次運行后將 terraform tfstate 從本地文件系統復制到 s3。 但在走上這條艱辛的道路之前,我想我會確保我沒有遺漏一些明顯的東西。
要使用 terraform 遠程狀態進行設置,我通常在我的 dev 和 prod terraform 文件夾中有一個名為remote-state
的單獨文件夾。
以下main.tf
文件將為您發布的內容設置遠程狀態:
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "tfstate"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "app-state"
read_capacity = 1
write_capacity = 1
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
然后使用cd remote-state
進入該文件夾,並運行terraform init && terraform apply
- 這應該只需要運行一次。 您可能會在存儲桶和 dynamodb 表名中添加一些內容來分隔不同的環境。
基於 Austin Davis 的巨大貢獻,這是我使用的一個變體,其中包括對數據加密的要求:
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "tfstate"
versioning {
enabled = true
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "app-state"
read_capacity = 1
write_capacity = 1
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
resource "aws_s3_bucket_policy" "terraform_state" {
bucket = "${aws_s3_bucket.terraform_state.id}"
policy =<<EOF
{
"Version": "2012-10-17",
"Id": "RequireEncryption",
"Statement": [
{
"Sid": "RequireEncryptedTransport",
"Effect": "Deny",
"Action": ["s3:*"],
"Resource": ["arn:aws:s3:::${aws_s3_bucket.terraform_state.bucket}/*"],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
},
"Principal": "*"
},
{
"Sid": "RequireEncryptedStorage",
"Effect": "Deny",
"Action": ["s3:PutObject"],
"Resource": ["arn:aws:s3:::${aws_s3_bucket.terraform_state.bucket}/*"],
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
},
"Principal": "*"
}
]
}
EOF
}
正如您所發現的,您首先不能使用 terraform 來構建 terraform 需要的組件。
雖然我理解讓 terraform “跟蹤一切”的傾向,但這非常困難,而且比它的價值更令人頭疼。
我通常通過創建一個簡單的引導 shell 腳本來處理這種情況。 它會創建如下內容:
雖然您應該只需要運行一次(技術上),但我發現當我開發一個新系統時,我會反復啟動和拆卸。 因此,在一個腳本中包含這些步驟會使這變得簡單得多。
我通常將腳本構建為冪等的。 這樣,您可以多次運行它,而不必擔心創建重復的存儲桶、用戶等
我創建了一個帶有一些引導命令/指令的 terraform 模塊來解決這個問題:
https://github.com/samstav/terraform-aws-backend
自述文件中有詳細說明,但要點是:
# conf.tf
module "backend" {
source = "github.com/samstav/terraform-aws-backend"
backend_bucket = "terraform-state-bucket"
}
然后,在您的 shell 中(確保您尚未編寫terraform {}
塊):
terraform get -update
terraform init -backend=false
terraform plan -out=backend.plan -target=module.backend
terraform apply backend.plan
現在編寫您的terraform {}
塊:
# conf.tf
terraform {
backend "s3" {
bucket = "terraform-state-bucket"
key = "states/terraform.tfstate"
dynamodb_table = "terraform-lock"
}
}
然后你可以重新初始化:
terraform init -reconfigure
利用 AWS s3 存儲桶設置 Terraform 后端相對容易。
首先,在您選擇的區域(例如 eu-west-1)中創建一個存儲桶,命名為terraform-backend-store (請記住選擇一個唯一的名稱。)
為此,請打開您的終端並運行以下命令,假設您已正確設置 AWS CLI(否則,請按照官方文檔中的說明進行操作):
aws s3api create-bucket --bucket terraform-backend-store \
--region eu-west-1 \
--create-bucket-configuration \
LocationConstraint=eu-west-1
# Output:
{
"Location": "http://terraform-backend-store.s3.amazonaws.com/"
}
命令應該是不言自明的; 要了解更多信息,請查看此處的文檔。
一旦存儲桶就位,就需要進行適當的配置以確保安全性和可靠性。 對於持有 Terraform 狀態的存儲桶,啟用服務器端加密是常識。 保持簡單,首先嘗試AES256方法(盡管我建議使用KMS並實施適當的密鑰輪換):
aws s3api put-bucket-encryption \
--bucket terraform-backend-store \
--server-side-encryption-configuration={\"Rules\":[{\"ApplyServerSideEncryptionByDefault\":{\"SSEAlgorithm\":\"AES256\"}}]}
# Output: expect none when the command is executed successfully
接下來,限制對存儲桶的訪問至關重要; 創建一個非特權 IAM 用戶,如下所示:
aws iam create-user --user-name terraform-deployer
# Output:
{
"User": {
"UserName": "terraform-deployer",
"Path": "/",
"CreateDate": "2019-01-27T03:20:41.270Z",
"UserId": "AIDAIOSFODNN7EXAMPLE",
"Arn": "arn:aws:iam::123456789012:user/terraform-deployer"
}
}
記下命令輸出中的 Arn(它看起來像:“Arn”:“arn:aws:iam::123456789012:user/terraform-deployer”)。
為了在后期與 s3 服務和 DynamoDB 正確交互以實施鎖定,我們的 IAM 用戶必須擁有足夠的權限集。 建議對生產環境設置嚴格的限制,但為了簡單起見,開始分配AmazonS3FullAccess和AmazonDynamoDBFullAccess :
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess --user-name terraform-deployer
# Output: expect none when the command execution is successful
aws iam attach-user-policy --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess --user-name terraform-deployer
# Output: expect none when the command execution is successful
必須啟用新創建的 IAM 用戶才能對您的 s3 存儲桶執行所需的操作。 您可以通過創建和應用正確的策略來做到這一點,如下所示:
cat <<-EOF >> policy.json
{
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/terraform-deployer"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::terraform-remote-store"
}
]
}
EOF
此基本策略文件授予具有 arn “arn:aws:iam::123456789012:user/terraform-deployer” 的委托人,以使用 arn “arn”對存儲桶執行所有可用操作(“Action”:“s3:*”) :aws:s3:::terraform-remote-store”。同樣,在生產中需要強制執行更嚴格的策略。作為參考,請查看AWS Policy Generator 。
返回終端並運行如下所示的命令,以在您的存儲桶中實施策略:
aws s3api put-bucket-policy --bucket terraform-remote-store --policy file://policy.json
# Output: none
最后一步,啟用存儲桶的版本控制:
aws s3api put-bucket-versioning --bucket terraform-remote-store --versioning-configuration Status=Enabled
它允許保存不同版本的基礎設施狀態並輕松回滾到前一階段,而無需費力。
AWS s3 存儲桶已准備就緒,是時候將其與 Terraform 集成了。 下面列出的是設置此遠程后端所需的最低配置:
# terraform.tf
provider "aws" {
region = "${var.aws_region}"
shared_credentials_file = "~/.aws/credentials"
profile = "default"
}
terraform {
backend "s3" {
bucket = "terraform-remote-store"
encrypt = true
key = "terraform.tfstate"
region = "eu-west-1"
}
}
# the rest of your configuration and resources to deploy
一旦到位,必須(再次)初始化 terraform。 terraform init
遠程后端准備就緒,測試一下。
鎖了怎么辦? 遠程存儲狀態會帶來一個陷阱,尤其是在多個任務、工作和團隊成員可以訪問它的情況下工作時。 在這些情況下,多個並發嘗試更改狀態的風險很高。 這里來幫助鎖定,該功能可防止在已使用時打開狀態文件。
您可以實施創建AWS DynamoDB Table的鎖,terraform 使用它來設置和取消設置鎖。 使用 terraform 本身提供資源:
# create-dynamodb-lock-table.tf
resource "aws_dynamodb_table" "dynamodb-terraform-state-lock" {
name = "terraform-state-lock-dynamo"
hash_key = "LockID"
read_capacity = 20
write_capacity = 20
attribute {
name = "LockID"
type = "S"
}
tags {
Name = "DynamoDB Terraform State Lock Table"
}
}
並如圖所示部署它: terraform plan -out "planfile" && terraform apply -input=false -auto-approve "planfile"
命令執行完成后,必須將鎖定機制添加到您的后端配置中,如下所示:
# terraform.tf
provider "aws" {
region = "${var.aws_region}"
shared_credentials_file = "~/.aws/credentials"
profile = "default"
}
terraform {
backend "s3" {
bucket = "terraform-remote-store"
encrypt = true
key = "terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-state-lock-dynamo"
}
}
# the rest of your configuration and resources to deploy
全部完成。 請記住再次運行terraform init
並享受您的遠程后端。
正如你所說,我通常做的是在沒有遠程后端的情況下開始創建初始基礎設施、S3、IAM 角色和其他重要的東西。 一旦我有了它,我只需添加后端配置並運行 terraform init 即可遷移到 S3。
這不是最好的情況,但在大多數情況下,我不會每天都重建我的整個環境,所以這種半自動化的方法已經足夠好了。 我還將基礎設施的下一個“層”(VPC、子網、IGW、NAT 等)分為不同的狀態。
我一直在做的解決這個問題是,您可以注釋掉初始運行的“后端”塊,並僅在狀態存儲桶和任何相關資源(如存儲桶策略)上應用選定的 terraform。
# backend "s3" {
# bucket = "foo-bar-state-bucket"
# key = "core-terraform.tfstate"
# region = "eu-west-1"
# }
#}
provider "aws" {
region = "eu-west-1"
profile = "terraform-iam-user"
shared_credentials_file = "~/.aws/credentials"
}
terraform apply --target aws_s3_bucket.foobar-terraform --target aws_s3_bucket_policy.foobar-terraform
這將配置您的 s3 狀態存儲桶,並將 .tfstate 文件本地存儲在您的工作目錄中。
稍后,取消注釋“后端”塊並重新配置后端terraform init --reconfigure
,這將提示您將本地存在的 .tfstate 文件(跟蹤您的后端 s3 存儲桶的狀態)復制到遠程后端,現在可以使用terraform 用於任何后續運行。
如果您計划僅使用存儲桶來存儲 TF 狀態,那么這里有一個強調存儲桶訪問安全性的解決方案。
使用以下代碼在單獨的文件夾中創建一個main.tf
文件並運行terraform apply
。
provider "aws" {
region = "my-region"
...
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-bucket"
acl = "private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state_access" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
ignore_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
resource "aws_dynamodb_table" "terraform_state_lock" {
name = "my-table"
read_capacity = 1
write_capacity = 1
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
然后在您的主 Terraform 文件夾中,添加后端並運行terraform init
。
backend "s3" {
bucket = "my-bucket"
key = "terraform.tfstate"
region = "my-region"
dynamodb_table = "my-table"
encrypt = true
}
這里有一些很好的答案,我想提供一個替代管理后端狀態的方法;
一旦您的 terraform 雲帳戶設置並連接到您存儲 terraform 計划和模塊的 VCS 存儲庫...通過單擊注冊表選項卡將您的 terraform 模塊存儲庫添加到 terraform 雲中。 您需要確保對 terraform 模塊進行版本控制/標記並遵循正確的命名約定。 如果您有一個在 AWS 中創建負載均衡器的 terraform 模塊,您將命名 terraform 模塊存儲庫(例如在 github 中),如下所示:terraform-aws-loadbalancer。 只要它以 terraform-aws- 開頭,就可以了。 然后向其添加版本標簽,例如 1.0.0
因此,假設您創建了一個指向該負載均衡器模塊的 terraform 計划,這就是您將后端配置指向 terraform cloud 和負載均衡器模塊的方式:
后端狀態.tf 內容:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "YOUR-TERRAFORM-CLOUD-ORG"
workspaces {
# name = "" ## For single workspace jobs
# prefix = "" ## for multiple workspaces
# you can use name instead of prefix
prefix = "terraform-plan-name-"
}
}
}
terraform plan main.tf 內容;
module "aws_alb" {
source = "app.terraform.io/YOUR-TERRAFORM-CLOUD-ORG/loadbalancer/aws"
version = "1.0.0"
name = "load-balancer-test"
security_groups = [module.aws_sg.id]
load_balancer_type = "application"
internal = false
subnets = [data.aws_subnet.public.id]
idle_timeout = 1200
# access_logs_enabled = true
# access_logs_s3bucket = "BUCKET-NAME"
tags = local.tags
}
從您的終端本地(以 Mac OSX 為例);
terraform init
# if you're using name instead of prefix in your backend set
# up, no need to run terraform workspace cmd
terraform workspace new test
terraform plan
terraform apply
您將在 terraform cloud 中使用此名稱在您的工作區下看到應用: terraform-plan-name-test “test” 附加到您的工作區前綴名稱,該名稱在上面的 backend-state.tf 中定義。 您最終會在您的工作區中獲得一個包含您的 terraform 計划的 GUI / 控制台,就像您可以在 AWS 中查看您的 Cloudformation 堆棧一樣。 我發現用於 Cloudformation 並過渡到 Terraform 的 devops,就像這個設置一樣。
一個優點是,在 Terraform Cloud 中,您可以輕松地對其進行設置,以便通過 git 提交或合並到主分支來觸發計划(堆棧構建)。
1 參考: https ://www.terraform.io/docs/language/settings/backends/remote.html#basic-configuration
我克服這個問題的方法是在第一個初始化計划應用周期中創建項目遠程狀態,並在第二個初始化計划應用周期中初始化遠程狀態。
# first init plan apply cycle
# Configure the AWS Provider
# https://www.terraform.io/docs/providers/aws/index.html
provider "aws" {
version = "~> 2.0"
region = "us-east-1"
}
resource "aws_s3_bucket" "terraform_remote_state" {
bucket = "terraform-remote-state"
acl = "private"
tags = {
Name = "terraform-remote-state"
Environment = "Dev"
}
}
# add this sniped and execute the
# the second init plan apply cycle
# https://www.terraform.io/docs/backends/types/s3.html
terraform {
backend "s3" {
bucket = "terraform-remote-state"
key = "path/to/my/key"
region = "us-east-1"
}
}
我強烈建議使用Terragrunt來保持 Terraform 代碼的可管理性和DRY (不要重復自己的原則)。
Terragrunt 具有許多功能 - 對於您的特定情況,我建議您遵循保持遠程狀態配置 DRY部分。
我將在下面添加一個簡短的摘要。
假設您有以下 Terraform 基礎設施:
├── backend-app
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
├── frontend-app
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
├── mysql
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
└── mongo
├── main.tf
└── other_resources.tf
└── variables.tf
每個應用程序都是一個 terraform 模塊,您需要將其 Terraform 狀態存儲在遠程后端。
如果沒有 Terragrunt,您將不得不為每個應用程序編寫backend
配置塊,以便將當前狀態保存在遠程狀態存儲中:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "frontend-app/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "my-lock-table"
}
}
像上面的例子一樣管理幾個模塊,為每個模塊添加這個文件並不是一個負擔——但它不會持續到現實世界的場景中。
如果我們可以做某種繼承(比如面向對象編程)不是更好嗎?
使用 Terragrunt 可以輕松做到這一點。
回到模塊結構。
使用 Terragrunt,我們只需要添加一個包含所有配置的根terragrunt.hcl
,並為每個模塊添加一個僅包含 on 語句的子 terragrunt.hcl:
├── terragrunt.hcl #<---- Root
├── backend-app
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
│ └── terragrunt.hcl #<---- Child
├── frontend-app
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
│ └── terragrunt.hcl #<---- Child
├── mysql
│ ├── main.tf
│ └── other_resources.tf
│ └── variables.tf
│ └── terragrunt.hcl #<---- Child
└── mongo
├── main.tf
└── other_resources.tf
└── variables.tf
└── terragrunt.hcl. #<---- Child
根terragrunt.hcl
將保留您的遠程狀態配置,並且子項將只有以下語句:
include {
path = find_in_parent_folders()
}
此include
塊告訴 Terragrunt 使用與通過路徑參數指定的根terragrunt.hcl
文件完全相同的 Terragrunt 配置。
下次您運行 terragrunt 時,它會通過調用terraform init
自動配置remote_state.config
塊中的所有設置(如果尚未配置)。
backend.tf
文件將自動為您創建。
您可以擁有數百個具有嵌套層次結構的模塊(例如划分為區域、租戶、應用程序等),並且仍然只能維護遠程狀態的一種配置。
terraform 中存在版本問題,對我來說,它適用於上述版本。 另外,最好在桶上有 terraform 狀態。
terraform {
required_version = "~> 0.12.12"
backend "gcs" {
bucket = "bbucket-name"
prefix = "terraform/state"
}
}
提醒一下,我不會使用 terraform 創建 terraform statefile,以防有人無意中刪除它。 因此,請使用不維護狀態的 aws-cli 或 boto3 之類的腳本,並將這些腳本限制為 s3 存儲桶名稱的變量。 從長遠來看,您不會真正更改 terraform 狀態存儲桶的腳本,除非在存儲桶內創建其他文件夾,這可以在資源級別的 terraform 之外完成。
提供的所有答案都非常好。 我只想強調“關鍵”屬性。 當您進入 Terraform 的高級應用程序時,您最終需要引用這些 S3 密鑰,以便將遠程狀態拉入當前項目,或利用“terraform move”。
當你計划你的“terraform”節來定義你的后端時,使用智能鍵名真的很有幫助。
我推薦以下作為基本鍵名:account_name/{development:production}/region/module_name/terraform.tfstate
修改以滿足您的需求,但是當我在許多帳戶和地區擴展我對 Terraform 的使用時,返回並修復我所有的關鍵名稱一點也不好玩。
您可以簡單地使用 terraform cloud 並按如下方式配置您的后端:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "your-tf-organization-name"
workspaces {
name = "your-workspace-name"
}
}
}
用 terraform 管理 terraform state 桶是一種先有雞還是先有蛋的問題。 我們可以解決的方法之一是:
使用帶有本地后端的 terraform 創建 terraform state 存儲桶,然后將 state 遷移到新創建的 state 存儲桶。
如果您試圖通過 CI/CD 管道實現這一點並試圖使工作本質上是冪等的,這可能會有點棘手。
在單獨的文件中模塊化后端配置。
terraform.tf
terraform {
required_version = "~> 1.3.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.48.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
主程序
module "remote_state" {
# you can write your own module or use any community module which
# creates a S3 bucket and dynamoDB table (ideally with replication and versioning)
source = "../modules/module-for-s3-bucket-and-ddtable"
bucket_name = "terraform-state-bucket-name"
dynamodb_table_name = "terraform-state-lock"
}
后端.tf
terraform {
backend "s3" {
bucket = "terraform-state-bucket-name"
key = "state.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-lock"
}
}
通過以下步驟,我們可以在同一個 state 中管理和創建 state S3 存儲桶。
function configure_state() {
# Disable S3 bucket backend
mv backend.tf backend.tf.backup
# Since S3 config is not present terraform local state will be initialized
# Or copied from s3 bucket if it already existed
terraform init -migrate-state -auto-approve
# Terraform apply will create the S3 bucket backend and save the state in local state
terraform apply -target module.remote_state
# It will re-enable S3 backend configuration for storing state
mv backend.tf.backup backend.tf
#It will migrate the state from local to S3 bucket
terraform init -migrate-state -auto-approve
}
假設您在本地而不是在某個虛擬服務器上運行 terraform,並且您希望將 terraform 狀態存儲在不存在的 S3 存儲桶中。 這就是我的處理方式,
創建 terraform 腳本,提供 S3 存儲桶
創建用於配置您的基礎設施的 terraform 腳本
在您的 terraform 腳本的末尾提供存儲桶以供第二個 terraform 腳本用於存儲狀態文件,包括用於提供空資源的代碼。
在空資源的代碼塊中,使用 local-exec provisioner run 命令進入第二個 terraform 腳本所在的目錄,然后使用通常的 terraform init 來初始化后端,然后是 terraform 計划,然后是 terraform apply
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.