使用 terraform 初始设置 terraform 后端
Posted
技术标签:
【中文标题】使用 terraform 初始设置 terraform 后端【英文标题】:Initial setup of terraform backend using terraform 【发布时间】:2018-06-03 10:15:17 【问题描述】:我刚刚开始使用 terraform,我希望能够使用 AWS S3 作为我的后端来存储我的项目状态。
terraform
backend "s3"
bucket = "tfstate"
key = "app-state"
region = "us-east-1"
我觉得使用 terraform 为后端存储基础设施设置我的 S3 存储桶、IAM 组和策略是明智的。
如果我在应用初始 terraform 基础架构之前设置了后端状态,它会合理地抱怨后端存储桶尚未创建。所以,我的问题变成了,如何使用 terraform 设置我的 terraform 后端,同时保持 terraform 跟踪后端的状态。似乎是嵌套娃娃问题。
我对如何编写脚本有一些想法,例如,检查存储桶是否存在或是否已设置某些状态,然后引导 terraform,最后从本地文件系统复制 terraform tfstate 到 s3第一次运行。但在走这条艰苦的道路之前,我想我要确保我没有遗漏一些明显的东西。
【问题讨论】:
这是个好问题。 FWIW 我们有一个单独的“bootstrap”TF 项目,该项目又依赖于一个超最小的手动配置存储桶。 是的,我做过类似的事情,其中引导项目复制项目的一堆帮助程序脚本和提供程序配置,如果它不存在,还创建一个版本化的 S3 存储桶和 DynamoDb 锁定表使用AWS CLI。如果我们可以在 Terraform 中做到这一点会很好,但是当我尝试时它太乱了,不值得。 Terragrunt 也可以为您解决这个问题,如果您想移动到不同的存储桶,它会非常方便。 github.com/gruntwork-io/terragrunt 2020 年有没有更好的方法来做到这一点?我已经看到使用本地状态在不同文件夹中创建 s3 的建议。我认为这不是 CD 计划的正确方法。有没有人找到更好的方法? 我使用了您建议的相同解决方案,我有一个用于管理远程状态的小项目,您可以在此处查看:github.com/tomarv2/tfremote 【参考方案1】:要使用 terraform 远程状态进行设置,我通常在我的 dev 和 prod terraform 文件夹中有一个名为 remote-state
的单独文件夹。
以下main.tf
文件将为您发布的内容设置远程状态:
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"
然后使用cd remote-state
进入此文件夹,并运行terraform init && terraform apply
- 这应该只需要运行一次。您可以在 bucket 和 dynamodb 表名中添加一些内容来分隔不同的环境。
【讨论】:
出色的答案。 FWIW 任何阅读“Terraform Up and Running”并停留在第 3 章后端(使用旧版本的 terreform)的人,这就是解决方案。 我假设您使用terraform_state_lock
表作为互斥体来写入状态,但是从中读取/写入的是什么? terraform 会在幕后做些什么吗?如果排除 aws_dynamodb_table 资源会发生什么?
啊——我想通了。这是因为内置的 s3 后端支持使用 dynamodb_table 参数通过 dynamodb 表进行锁定:terraform.io/docs/backends/types/s3.html#dynamodb_table
@drozzy - 不是作者,但最后一段中的“远程状态文件夹”表示包含此 .tf
文件的文件夹,它应该是您的存储库中的子文件夹,例如 remote-state
.所以你需要在这两个 Terraform 命令之前执行cd remote-state
。我会建议进行编辑以澄清。
我仍然无法完全理解答案.. 当我们 cd remote-state
并运行 terraform apply
- 这个基础基础设施的 terraform.state 仍然在本地保存(没有配置后端),对?这如何解决鸡/蛋问题?这个状态文件现在也属于 terraform 管理的范围吗?【参考方案2】:
在 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
【讨论】:
这个错误出现在│ Error: Value for unconfigurable attribute
看到打开票github.com/hashicorp/terraform-provider-aws/issues/23106【参考方案3】:
正如您所发现的,您不能使用 terraform 来构建 terraform 需要的组件。
虽然我理解让 terraform “跟踪所有内容”的倾向,但这非常困难,而且比它的价值更令人头疼。
我通常通过创建一个简单的引导 shell 脚本来处理这种情况。它会创建如下内容:
-
用于状态存储的 s3 桶
向所述存储桶添加版本控制
一个 terraform IAM 用户和组,具有我在 terraform 构建中需要的某些策略
虽然您应该只需要运行一次(技术上),但我发现当我开发一个新系统时,我会反复启动和拆卸。因此,在一个脚本中包含这些步骤会使这变得简单得多。
我通常将脚本构建为幂等的。这样,您可以多次运行它,而不必担心创建重复的存储桶、用户等
【讨论】:
实际上,您可以使用 Terraform 构建远程状态组件(S3 存储桶和 DynamoDB 表) - 只需使用单独的子文件夹来构建这些组件,它有自己的(本地)Terraform 状态文件。见this answer。【参考方案4】:我创建了一个带有一些引导命令/指令的 terraform 模块来解决这个问题:
https://github.com/samstav/terraform-aws-backend
README中有详细的说明,但要点是:
# 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
【讨论】:
【参考方案5】:利用 AWS s3 存储桶设置 Terraform 后端相对容易。
首先,在您选择的区域(例如 eu-west-1)中创建一个存储桶,命名为 terraform-backend-store(请记住选择一个唯一的名称。)
为此,请打开您的终端并运行以下命令,假设您已正确设置 AWS CLI(否则,请按照official documentation 处的说明进行操作):
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/"
命令应该是不言自明的;要了解更多信息,请查看文档here。
存储桶就位后,需要进行适当的配置以确保安全性和可靠性。 对于保存 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: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 表来实现锁,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
并享受您的远程后端。
【讨论】:
我在执行此过程时遇到了问题。如果我想在开发过程中经常破坏其他资源,我无法使用 terraform 创建 dynamo db。有什么建议可以解决这个问题,还是我应该只使用 CLI? @utkarsh867 只是将 S3 和 Dynamo 的设置拆分为一个独立的项目...这样,您可以在开发过程中处理您的其他资源...我个人创建了一个 Docker 映像来设置 S3 ( shell 和 terraform 本身)...【参考方案6】:我通常做的是在没有远程后端的情况下开始创建初始基础设施,如您所说,S3、IAM 角色和其他基本内容。一旦我有了它,我只需添加后端配置并运行 terraform init 以迁移到 S3。
这不是最好的情况,但在大多数情况下,我不会每天都重建我的整个环境,所以这种半自动化的方法已经足够好了。 我还将基础设施的下一个“层”(VPC、子网、IGW、NAT 等)分为不同的状态。
【讨论】:
【参考方案7】:如果您打算仅使用存储桶来存储 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
【讨论】:
【参考方案8】:我一直在做的解决这个问题是,您可以注释掉初始运行的“后端”块,并仅在状态存储桶和任何相关资源(如存储桶策略)上应用选定的 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 用于任何后续运行。
Prompt for copying exisitng state to remote backend
【讨论】:
同意,这是一种非常简单实用的初始设置方式。【参考方案9】:这里有一些很好的答案,我想提供一种替代方法来管理您的后端状态;
-
设置 Terraform 云帐户(最多可免费供 5 位用户使用)。
为您的组织创建工作区(版本控制工作流是典型的)
选择您的 VCS,例如 github 或 bitbucket(您存储 terraform 计划和模块的位置)
Terraform Cloud 将为您提供新 OAuth 连接所需的说明
设置完成后,您可以选择设置通常不需要的 SSH 密钥对,您可以单击“跳过并完成”按钮
一旦您的 terraform 云帐户设置并连接到您存储 terraform 计划和模块的 VCS 存储库... 通过单击注册表选项卡,在 terraform 云中添加您的 terraform 模块存储库。您需要确保对 terraform 模块进行版本控制/标记并遵循正确的命名约定。如果您有一个在 AWS 中创建负载均衡器的 terraform 模块,您将命名 terraform 模块存储库(例如在 github 中),如下所示:terraform-aws-loadbalancer。只要它以 terraform-aws- 开头,就可以了。然后给它添加一个版本标签,比如 1.0.0
假设您创建了一个指向该负载平衡器模块的 terraform 计划,这就是您将后端配置指向 terraform cloud 和负载平衡器模块的方式:
backend-state.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 云中使用以下名称看到应用程序: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
【讨论】:
【参考方案10】:我克服这个问题的方法是在第一个初始化计划应用周期中创建项目远程状态,并在第二个初始化计划应用周期中初始化远程状态。
# 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"
【讨论】:
【参考方案11】:我强烈建议使用 Terragrunt 来保持 Terraform 代码的可管理性和干燥(不要重复自己原则)。
Terragrunt 有很多功能 - 对于您的具体情况,我建议您关注 Keep your remote state configuration DRY 部分。
我将在下面添加一个简短的摘要。
使用 Terraform 管理远程状态的问题
假设您有以下 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
配置块,以便将当前状态保存在 remote state storage 中:
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,我们只需要添加一个包含所有配置的根 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 时,它会自动配置 remote_state.config
块中的所有设置,如果尚未配置,请调用 terraform init
。
backend.tf
文件将自动为您创建。
总结
您可以拥有数百个具有嵌套层次结构的模块(例如划分为区域、租户、应用程序等),并且仍然只能维护远程状态的一种配置。
【讨论】:
【参考方案12】:terraform 中存在版本问题,对我来说,它适用于上述版本。另外,桶上有 terraform 状态也很好。
terraform
required_version = "~> 0.12.12"
backend "gcs"
bucket = "bbucket-name"
prefix = "terraform/state"
【讨论】:
【参考方案13】:请注意,我不会使用 terraform 创建 terraform statefile,以防有人无意中删除它。因此,请使用不维护状态的 aws-cli 或 boto3 等脚本,并将这些脚本限制为 s3 存储桶名称的变量。从长远来看,您不会真正更改 terraform 状态存储桶的脚本,除非在存储桶内创建其他文件夹,这可以在资源级别的 terraform 之外完成。
【讨论】:
【参考方案14】:提供的所有答案都非常好。我只想强调“关键”属性。当您进入 Terraform 的高级应用程序时,您最终将需要引用这些 S3 密钥,以便将远程状态拉入当前项目,或利用“terraform move”。
当你计划你的“terraform”节来定义你的后端时,使用智能键名真的很有帮助。
我推荐以下作为基本键名: account_name/development:production/region/module_name/terraform.tfstate
根据您的需要进行修改,但是当我将 Terraform 的使用扩展到许多帐户和地区时,返回并修复我所有的关键名称一点也不好玩。
【讨论】:
【参考方案15】:假设您在本地而不是在某个虚拟服务器上运行 terraform,并且您希望将 terraform 状态存储在不存在的 S3 存储桶中。这就是我的处理方式,
创建 terraform 脚本,提供 S3 存储桶
创建用于配置您的基础架构的 terraform 脚本
在您的 terraform 脚本的末尾提供存储桶以供第二个 terraform 脚本用于存储状态文件,包括用于提供空资源的代码。
在 null 资源的代码块中,使用 local-exec provisioner run 命令进入您的第二个 terraform 脚本所在的目录,然后使用通常的 terraform init 来初始化后端,然后是 terraform 计划,然后是 terraform apply
【讨论】:
以上是关于使用 terraform 初始设置 terraform 后端的主要内容,如果未能解决你的问题,请参考以下文章
如何在 terraform 中运行 kubectl apply 命令
为 Terraform 服务帐户定义 ClusterRoleBinding
在同一个 TF 脚本中使用多个 Terraform 提供程序(GCP 和 Kubernetes)创建资源
如何使用 terraform 将 aws ec2 私有 ips 传递给模板文件