Terraform设计和运维经验

总结

由于使用了Terraform已经过去了大约一年,因此我打算开始公开设计和运维方面的经验与技巧。
基本上,由于大多数情况下我们使用AWS,所以这些内容会偏向AWS方面的话题。

设计编码

查找可用的提供商。

Terraform以其能将云服务如AWS和GCP进行代码化而闻名,甚至仅官方版就支持多个供应商。除AWS外,我还将GitHub和Datadog进行了代码化运维。

网络设计

在建立服务时,除了正式环境之外,还需要创建开发环境和测试环境。通常情况下,环境的划分主要依据以下三种方式之一。

    1. 将所有环境放入一个VPC中。

 

    1. 按照每个环境分隔VPC。

 

    按照每个环境分隔AWS账户。

如果使用2个模式进行设计,使用Terraform构建配置时,需要将VPC分为开发、暂存和生产,并创建用于部署在每个VPC中的共享服务的管理模块。管理模块可以放置一个跳转机或管理CI/CD的实例。

在网络上,可以通过VPC对接从管理模块连接到每个环境,但是环境之间的连接(例如从开发到暂存,从暂存到生产)会在路由表上进行限制。

開発基盤の概要-Page-3 (2).png

目录设计

如果要使用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选项。

Screen_Shot_2019-07-15_at_23_21_12.png

整理格式

使用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向け。入力補完やコード検証

bannerAds