Terraform设计和运维经验
总结
由于使用了Terraform已经过去了大约一年,因此我打算开始公开设计和运维方面的经验与技巧。
基本上,由于大多数情况下我们使用AWS,所以这些内容会偏向AWS方面的话题。
设计编码
查找可用的提供商。
Terraform以其能将云服务如AWS和GCP进行代码化而闻名,甚至仅官方版就支持多个供应商。除AWS外,我还将GitHub和Datadog进行了代码化运维。
网络设计
在建立服务时,除了正式环境之外,还需要创建开发环境和测试环境。通常情况下,环境的划分主要依据以下三种方式之一。
-
- 将所有环境放入一个VPC中。
-
- 按照每个环境分隔VPC。
- 按照每个环境分隔AWS账户。
如果使用2个模式进行设计,使用Terraform构建配置时,需要将VPC分为开发、暂存和生产,并创建用于部署在每个VPC中的共享服务的管理模块。管理模块可以放置一个跳转机或管理CI/CD的实例。
在网络上,可以通过VPC对接从管理模块连接到每个环境,但是环境之间的连接(例如从开发到暂存,从暂存到生产)会在路由表上进行限制。

目录设计
如果要使用Terraform管理资源,我认为你可能会为目录结构而犯愁。根据 Code structure 的介绍,我将采用以下这种结构。
# 構築する対象サービス。EC2、Security Groupといった単位
{service}
# resourceやmodule
main.tf
# main.tfやbackend.tfで参照する変数のリスト
variables.tf
# 出力パラメータのリスト
outputs.tf
# プロバイダ情報
providers.tf
# backendやdataのリスト
backend.tf
基础设施由多个服务组成,因此实际上会具有以下目录结构。
providers
aws
# AWS運用上必須となる基盤サービス
base
# リージョンに依存しないサービス
global
iam
route53
...
# 特定のリージョンを構築するサービス
ap-northeast-1
cognito
cloudtrail
...
# アプリケーションを構成する上で必要となるサービス
{APP NAME}
# ワークスペースに依存しないサービス
base
route53
...
# ワークスペースで構成されるサービス
workspace
ap-northeast-1
global
vpc
ecs
...
阅读《Terraform 最佳实践》。
让我们阅读《Terraform最佳实践》。其中,命名约定是非常有用的。下面是一些我关注的要点的总结。
-
- Resource and data source arguments
リソース名や変数、出力名の区切り文字には-の代わりに_を使う
リソース名にはリソースタイプを含めない (例えば、aws_route_tableのリソース名にroute_tableなどのタイプを含めない)
単一リソースや妥当な名前がない場合、リソース名はthisとする
tags、depends_on、lifecycleはリソースの最後に記述する
Variables
default = []がある場合、type = “list”は省略する
default = {}がある場合、type = “map”は省略する
listやmapの変数名は複数形を使用する
Outputs
名前は{name}_{type}_{attribute}形式が望ましい
更改资源名称
使用terraform state mv命令。
# リソース名をfooからbarに変更
$ terraform state mv aws_kms_key.foo aws_kms_key.bar
如果您正在使用Workspace,则需要按Workspace运行。
将资源模块化
基本上與更改資源名稱一致。只需要在前面加上module.{MODULE_NAME}作為前綴。
# リソースfooをbarモジュール内のaws_kms_key.bazに変更
$ terraform state mv aws_kms_key.foo module.bar.aws_kms_key.baz
实施部分
使用Docker版本的Terraform。
使用Docker版本的Terraform。
请指定版本
通过明确使用的版本,可以避免不必要的麻烦。
改土造山
terraform {
required_version = "= 0.12.4"
}
如果使用Docker版Terraform,建议您不要使用latest版本,而是应该通过数字指定版本,例如0.12.4。
供应商版本
provider "aws" {
version = "= 2.19.0"
}
模块版本
module "foo" {
source = "terraform-aws-modules/security-group/aws"
version = "3.0.1"
}
如果使用的source是GitHub的话,就不能使用version,而是需要使用ref来指定版本。
module "foo" {
source = "github.com/azavea/terraform-aws-acm-certificate?ref=1.1.0"
}
tfstate文件的管理方法
管理资源状态的tfstate文件不应该在本地或Git上进行管理。可以通过使用Terraform的后端功能,在DynamoDB或S3中管理tfstate。如果使用AWS,那么S3可能更方便。
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
若要使用S3,请强烈建议启用目标存储桶的版本控制。即使意外删除了tfstate文件,或是通过Terraform升级导致文件结构改变,您仍可以轻松地恢复状态文件。
使用 Terraform 模块注册表
在Terraform模块注册表中,有一些通用的模块可以使用。就像在AWS中使用EC2或Security Group一样,只需传递一些变量就可以创建资源。
注册表主要分为由Terraform社区创建的模块和经过HashiCorp验证的Verified模块。
如果要在生产环境中使用,请建议在搜索时选择Verified选项。

整理格式
使用fmt命令可以对目录下的文件进行批量格式化整理。
# 0.11まで
$ terraform fmt
# 0.12以降
$ terraform fmt -recursive
调试
如果您想要确认变量或函数的结果,可以使用控制台命令很方便。
variable "foo" {
default = "bar"
}
# 変数の確認
$ terraform console
> var.foo
bar
# 関数の実行結果の確認
> lookup({foo="bar"}, "foo", "baz")
bar
针对每个环境读取不同值的方法。
变量名要包括工作区的名字来定义。
variable "rds_cluster" {
default = {
"default.engine_version" = "5.7.12"
"production.engine_version" = "5.7.22"
}
}
在创建资源时,通过使用lookup函数,在当前有效的工作空间为production时,则设置值为5.7.22,否则设置为5.7.11。
resource "aws_rds_cluster" "this" {
engine_version = lookup(
var.rds_cluster,
"${terraform.workspace}.engine_version",
var.rds_cluster["default.engine_version"],
)
...
使用生命周期来保护资源。
通过在资源中指定lifecycle区块,可以改变资源在创建和更新时的行为。lifecycle有几种类型可供选择,但通过指定prevent_destroy,可以在强制资源重新创建时产生错误。
lifecycle {
prevent_destroy = true
}
执行destroy的结果。
$ terraform destroy
...
Error: Instance cannot be destroyed
on main.tf line 55:
55: resource "aws_eip" "this" {
Resource aws_eip.this has lifecycle.prevent_destroy set, but the plan calls
for this resource to be destroyed. To avoid this error and continue with the
plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan
using the -target flag
如果想要强制删除资源,可以将prevent_destroy设置为false,或者直接删除整个代码块并执行apply。
※prevent_destroy仅用于在Terraform上保护删除操作,可以通过AWS控制台或CLI进行删除。对于一些服务如EC2或RDS,有删除保护选项,建议另外启用。
IAM策略的JSON可以用aws_iam_policy_document来定义。
以下资源中的IAM策略以Here文档形式编写,可读性较低。
resource "aws_iam_policy" "example" {
# ... other configuration ...
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}
POLICY
}
可以这样替换掉原来的heredoc。由于可以用HCL语言编写,更加简洁明了。
resource "aws_iam_policy" "example" {
# ... other configuration ...
policy = data.aws_iam_policy_document.example.json
}
data "aws_iam_policy_document" "example" {
version = "2012-10-17"
statement {
effect = "Allow"
actions = ["*"]
resources = ["*"
}
}
输出调试日志
可以通过环境变量TF_LOG传递日志级别来输出日志。如果想将日志输出到文件中,还可以指定TF_LOG_PATH。
- Debugging Terraform
优化执行速度
通过在Terraform命令中传递–parallelism参数,可以更改API的并行调用数量。
$ terraform plan --parallelism=20
默认值为10。根据性能进行数值调整可能会很好。另外,如果要通过环境变量进行指定,可以使用TF_CLI_ARGS_plan、TF_CLI_ARGS_apply、TF_CLI_ARGS_destroy。
扩展库
GoogleCloudPlatform/terraformer
既存のリソースからtfファイルとtfstateファイルを生成してくれる
gruntwork-io/terragrunt
Terraformのラッパー。stateファイルのロックや、モジュール管理をDRYにする仕組みを提供
hashivim/vim-terraform
Vimmer向け。保存時にフォーマット整形、コード検証
vim-terraform-completion
Vimmer向け。入力補完やコード検証