我刚刚开始使用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之后第一次运行。但在走下这条艰难的道路之前,我想我确定自己并没有错过任何明显的东西。
答案 0 :(得分:34)
要使用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表名来分隔您的不同环境。
答案 1 :(得分:9)
正如您所发现的那样,您无法首先使用terraform来构建组件所需的组件。
虽然我理解倾向于使用terraform"跟踪所有内容"但是它非常困难,而且比它的价值更令人头痛。
我通常通过创建一个简单的bootstrap shell脚本来处理这种情况。它创造了类似的东西:
虽然你应该只需要运行一次(从技术上讲),但我发现当我开发一个新系统时,我会反复旋转并撕下来。因此,在一个脚本中使用这些步骤可以使这更简单。
我通常将脚本构建为幂等的。这样,您可以多次运行它,而无需担心您正在创建重复的存储桶,用户等
答案 2 :(得分:6)
基于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
}
答案 3 :(得分:3)
利用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”,以针对存储区执行所有可用操作(“ Action”:“ s3:*”) 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
并享受您的远程后端。
答案 4 :(得分:1)
我通常要做的是从不使用远程后端开始创建您所说的初始基础结构,S3,IAM角色和其他重要内容。了解这些信息后,我只需添加后端配置并运行terraform init即可迁移到S3。
这不是最好的情况,但是在大多数情况下,我不会每天重建整个环境,因此这种半自动化的方法就足够了。 我还将基础架构的下一个“层”(VPC,子网,IGW,NAT等)划分为不同的状态。
答案 5 :(得分:1)
我用一些引导程序命令/指令创建了一个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
答案 6 :(得分:1)
我为解决此问题所做的事情是,您可以注释掉初始运行的“后端”块,并仅在状态存储桶和任何相关资源(例如存储桶策略)上应用选定的地形。 / p>
# 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用于后续运行。>
答案 7 :(得分:0)
我解决此问题的方法是在第一个init计划应用周期中创建项目远程状态,并在第二个init计划应用周期中初始化远程状态。
[int('{0:g}'.format(float(i))) if float(i)%1 == 0. else float(i) for i in l]
# # [67.5, 70, 72.5, 75, 77.5, 80, 82.5]
答案 8 :(得分:0)
terraform中存在一个版本问题,对我来说,它适用于所提到的版本。另外,最好在铲斗上保留地形状态。
terraform {
required_version = "~> 0.12.12"
backend "gcs" {
bucket = "bbucket-name"
prefix = "terraform/state"
}
}
答案 9 :(得分:0)
请注意,如果有人不小心删除了terraform,我不会创建带有terraform的terraform状态文件。因此,请使用诸如aws-cli或boto3之类的脚本,它们不会保持状态,并将这些脚本限制为s3存储桶名称的变量。从长远来看,您不会真正更改terraform状态存储区的脚本,除了可以在存储区内部创建其他文件夹(可以在资源级别的terraform之外完成)之外。
答案 10 :(得分:0)
提供的所有答案都非常好。我只想强调“键”属性。当您进入Terraform的高级应用程序时,最终将需要引用这些S3键,以便将远程状态拉入当前项目或利用“ terraform move”。
在计划“ terraform”节定义后端时,使用智能键名确实有帮助。
我建议以下内容作为基本键名称: account_name / {development:production} /region/module_name/terraform.tfstate
进行修改以适应您的需求,但是当我在许多帐户和地区中扩展使用Terraform时,回头再修正所有的键名根本没意思。
答案 11 :(得分:0)
如果您打算仅使用存储桶来存储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
}
答案 12 :(得分:0)
这里有一些很好的答案,我想提供一种管理后端状态的替代方法;
一旦您的 terraform 云帐户设置并连接到您存储 terraform 计划和模块的 VCS 存储库... 通过单击模块选项卡,将您的 terraform 模块存储库添加到 terraform 云中。您需要确保您的 terraform 模块被版本化/标记并遵循正确的命名约定。如果您有一个在 AWS 中创建负载均衡器的 terraform 模块,您可以命名 terraform 模块存储库(例如在 github 中),如下所示:terraform-aws-loadbalancer。只要它以 terraform-aws- 开头,你就很好。然后你给它添加一个版本标签,比如 1.0.0
假设您创建了一个指向该负载均衡器模块的 terraform 计划,这就是您将后端配置指向 terraform 云和负载均衡器模块的方式:
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
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
terraform workspace new test
terraform plan
terraform apply
您将看到应用在您的工作区下的 terraform cloud 中使用以下名称进行:terraform-plan-name-test “test”附加到您的工作区前缀名称,该名称在上面的 backend-state.tf 中定义。您最终会在您的工作区中获得一个包含您的 terraform 计划的 GUI/控制台,就像您可以在 AWS 中看到您的 Cloudformation Stacks 一样。我发现 DevOps 习惯于 Cloudformation 并过渡到 Terraform,就像这样设置。
一个优势是,在 Terraform Cloud 中,您可以轻松设置它,以便通过 git 提交或合并到主分支来触发计划(堆栈构建)。
1 参考: https://www.terraform.io/docs/language/settings/backends/remote.html#basic-configuration
答案 13 :(得分:0)
我强烈建议使用 Terragrunt 来保持 Terraform 代码的可管理性和 DRY(不要重复自己 原则)。
Terragrunt 具有许多功能 - 对于您的具体情况,我建议您遵循 Keep your remote state configuration 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
配置块,以便在 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.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
文件。
您可以拥有数百个具有嵌套层次结构的模块(例如划分为区域、租户、应用程序等),并且仍然只能维护远程状态的一种配置。
答案 14 :(得分:-1)
假设您在本地运行terraform而不是在某个虚拟服务器上,并且您希望在不存在的S3存储桶中存储terraform状态。这就是我接近它的方式,
创建terraform脚本,提供S3存储桶
创建用于配置基础架构的terraform脚本
在您的terraform脚本结束时,要配置第二个terraform脚本用于存储状态文件的存储桶,请包含用于配置空资源的代码。
在null资源的代码块中使用local-exec provisioner run命令进入第二个terraform脚本所在的目录,然后是通常的terraform init来初始化后端然后是terraform plan,然后是terraform apply