使用terraform初始设置terraform后端

时间:2017-12-20 19:23:09

标签: amazon-s3 terraform

我刚刚开始使用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之后第一次运行。但在走下这条艰难的道路之前,我想我确定自己并没有错过任何明显的东西。

15 个答案:

答案 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脚本来处理这种情况。它创造了类似的东西:

  1. 用于状态存储的s3存储桶
  2. 向所述存储桶添加版本控制
  3. 一个terraform IAM用户和组,其中包含我需要用于terraform构建的某些策略
  4. 虽然你应该只需要运行一次(从技术上讲),但我发现当我开发一个新系统时,我会反复旋转并撕下来。因此,在一个脚本中使用这些步骤可以使这更简单。

    我通常将脚本构建为幂等的。这样,您可以多次运行它,而无需担心您正在创建重复的存储桶,用户等

答案 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-b​​ackend-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用于后续运行。

Prompt for copying exisitng state to remote backend

答案 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)

这里有一些很好的答案,我想提供一种管理后端状态的替代方法;

  1. 设置 Terraform Cloud 帐户(最多 5 个用户免费)。
  2. 为您的组织创建一个工作区(版本控制工作流是典型的)
  3. 选择您的 VCS,例如 github 或 bitbucket(用于存储 terraform 计划和模块的位置)
  4. Terraform Cloud 将为您提供新 OAuth 连接所需的说明
  5. 设置完成后,您可以选择设置通常不需要的 SSH 密钥对,您可以单击“跳过并完成”按钮

一旦您的 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 管理远程状态的问题

假设您拥有以下 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 文件。


总结

您可以拥有数百个具有嵌套层次结构的模块(例如划分为区域、租户、应用程序等),并且仍然只能维护远程状态的一种配置。

答案 14 :(得分:-1)

假设您在本地运行terraform而不是在某个虚拟服务器上,并且您希望在不存在的S3存储桶中存储terraform状态。这就是我接近它的方式,

  

创建terraform脚本,提供S3存储桶

     

创建用于配置基础架构的terraform脚本

     

在您的terraform脚本结束时,要配置第二个terraform脚本用于存储状态文件的存储桶,请包含用于配置空资源的代码。

     

在null资源的代码块中使用local-exec provisioner run命令进入第二个terraform脚本所在的目录,然后是通常的terraform init来初始化后端然后是terraform plan,然后是terraform apply