「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正在存储状态文件的每个版本,如果出现问题,这对于调试和回滚到旧版本非常有用。


分享到:


相關文章: