terraform的实际使用方法在开发过程中

我认为Terraform有很多特点,所以在熟悉之前很难使用。
最近感觉已经相对熟悉了,所以我个人使用Terraform的具体例子介绍如下。

只是我个人认为是好的方法,并不是最佳实践或最优解。
由于我主要使用AWS,所以例子大多基于AWS,但是在GCP上基本上也应该能用类似的方式实现。

写后端

推荐将.tfstate文件放在后端而不是本地。原因是可以防止同时编辑引起的冲突,并且可以将写入状态的敏感信息集中在一处,这是其中的一个优点。

如果要将后端设置为S3,则在开始使用 Terraform 之前,必须提前创建好对应的存储桶。手动创建这个存储桶将导致原本完备的基础设施即代码失去完整性,所以最好尽量避免这种情况。

为了完成terraform部署

resource "aws_s3_bucket" "hogehoge_backend" {
  bucket = "hogehoge-backend"
}

terraform {
  required_version = "= 0.12.20"
}

保持使用本地后端运行 terraform apply,

resource "aws_s3_bucket" "hogehoge_backend" {
  bucket = "hogehoge-backend"
}

terraform {
  required_version = "= 0.12.20"
+  backend "s3" {
+    bucket = "hogehoge-backend"
+    key    = "hogehoge/terraform"
+    region = "ap-northeast-1"
+  }
}

只需一种选项,原文是日语”通过添加追加和执行terraform init,可以更改后端。虽然本地.tfstate文件可能仍然存在,但可安全删除。”

通过添加追加和执行terraform init,您可以更改后端。尽管本地的.tfstate文件可能仍然存在,但是可以放心地删除。

如果使用s3作为后端,建议启用版本控制等功能。
上面的示例是最简配置,实际使用时请确保正确设置存储桶。
此外,将存储桶设置模块化可以方便重复利用,也有现成的解决方案可供使用。

将模块分开放在GitHub仓库中。

我认为在后端方面也提到了,主动进行模块化是一个好的选择。
可以使用现有的模块,但是将经常使用的配置整理到自己的代码库中可以更方便地使用。

module "backend" {
  source = "git::ssh://git@github.com/hoge/my-terraform-s3-backend.git"
}

# リポジトリのサブディレクトリも指定できる
# ひとつのリポジトリに複数モジュールをまとめても良さそう
module "backend" {
  source = "git::ssh://git@github.com/hoge/my-terraform-modules.git//s3-backend"
}

# ブランチやタグも指定できる
module "backend" {
  source = "git::ssh://git@github.com/hoge/my-terraform-modules.git//s3-backend?ref=master"
}

如果不需要建立存储库,可以使用本地模块。

没有必要将仅在当前项目中使用的模块单独放在另一个存储库中。例如,如果要定义一个名为hoge的应用程序资源,

.
|-- backend.tf 
|-- common.tf // 共通リソース(s3/iamなど)
|-- hoge.dev.tf // dev環境
|-- hoge.prod.tf // prod環境
|-- modules
|   |-- hoge
|   |   |-- cat.tf
|   |   |-- dog.tf
|   |   |-- cow.tf
|   |   `-- variables.tf
|   `-- fuga
|       |-- fuga.tf
|       `-- variables.tf
`-- terraform.tf

我們將將目錄結構設置成這樣的方式。

module "hoge_prod" {
  source             = "./modules/hoge"
  env                = "prod"
  common_resource_id = common_resource.common.id # common.tfで定義したリソース
}

如果这样做的话会感觉不错。

您也可以从hoge模块中引用fuga模块。

设立别名

因为terraform plan太长了,所以…


alias tp='terraform plan'
alias ta='terraform apply'

每次稍微更改都会及时进行计划。
如果计划需要花费时间的话,

tp --target aws_s3_bucket.hogehoge // 特定のリソースだけplan
tp --target module.hoge // hogeモジュールだけplan

很方便。

管理認証信息

在AWS的情况下,我认为将$AWS_ACCESS_KEY_ID和$AWS_SECRET_ACCESS_KEY环境变量导出到.env等文件中是一个不错的选择。虽然也可以将这些信息写入.tfvars文件中,但是将认证信息写入环境变量更加方便。

这样做可以兼顾aws-cli的配置。

由于GCP默认会自动读取认证信息,所以大多数情况下不需要担心任何问题。这真是太感谢了。

开发环境

有一个很不错的插件叫做VScode,可以帮助我们进行代码格式化、自动补全和语法高亮等操作。我们来使用一下吧。

使用tfenv

这个工具可以在使用过程中切换多个Terraform版本,这是一种常见的功能在各种语言中都会遇到。
由于Terraform存在版本兼容性的问题,我认为使用这个工具可以让人们更加快乐。

如果创建了一个名为.terraform-version的文件,它将自动帮你切换版本。

顺便提一下,列举可安装的版本。

tfenv list-remote

感觉它有点不同于其他同类工具。

手动制作后再导入

有些人说:“手动操作比使用Terraform更快,所以我选择手动操作。”我认为这个说法确实是真的。

建议采用aws-cli、gcloud命令或Web控制台手动创建资源,然后再使用Terraform导入,因为一开始使用Terraform定义并应用资源效率较低。

terraform import是一個命令,它將terraform外部管理的資源與尚未應用的terraform內部資源定義相關聯,並將其導入。
我覺得這個方法對於RDS和ECS等方面可能更有效速。

例如,对于ECS任务定义,以下是一些步骤。

首先,在网页控制台上创建一个任务定义。

我会随意地编写资源定义


# ひとまず最小構成でOK
resource "aws_ecs_task_definition" "service" {
  family                = "service"
  container_definitions = jsonencode([{
  }])
}

有3个导入操作。
由于导入资源的方式因资源而异,因此请参阅文档。

terraform import aws_ecs_task_definition.service arn:aws:ecs:ap-northeast-1:123456789:task-definition/service:1

# providerを指定してる場合は明示する必要がある。
terraform import --provider aws.us-east-1 aws_ecs_task_definition.service arn:aws:ecs:ap-northeast-1:123456789:task-definition/service:1

执行4个Terraform计划以检查差异,并对定义进行修正,直到差异消失。
Terraform的差异显示能够一目了然地显示变更前后的差异,因此可以轻松修复。

image.png

terraform的侧面修正如下:

resource "google_cloudfunctions_function" "hoge" {
   ...略...
+  labels = {
+    deployment-tool = "cli-gcloud"
+  }
}
image.png

顺便提一下,如果想要将导入的资源放置在管理外面的话

terraform state rm google_cloudfunctions_function.hoge

当有需要更换资源时,可以先执行一次 state rm 再进行 import,这种组合技术是有效的。

将文件按功能单元进行分割

我觉得在Terraform中常常会根据服务的单位来分割文件。

.
|-- s3.tf
|-- iam.tf
|-- vpc.tf
|-- rds.tf
|-- cloud_watch.tf
`-- ecs.tf

然而,按照这种划分方式,当更改特定功能的构成时,需要跨越多个文件进行修改,而且无法一眼看出哪些资源在哪里被使用。

从个人角度来看,我建议根据功能将文件分开。

.
|-- common.tf             // vpc,iam,s3など
|-- webserver.tf          // ecs,ecr,security groupなど
|-- error_notification.tf // cloud watch, lambdaなど
`-- database.tf           // rds,security groupなど

我认为这样做会使阅读变得相当容易。(注:功能的划分方式只是一个例子)

如果一个文件不能容纳下来,将其作为模块分割到”./modules”中。
这样,各个功能的合并部分将变得明确,易于保持低耦合性。

有些人认为,服务的定义分散并且难以查看。但是在terraform中,资源名称使用的是由提供商规定的固定字符串(例如aws_s3_bucket),因此可以很容易地通过字符串搜索来查看列表。

在使用变量和对象时,无法设置默认值。

在设置变量时可以设定默认值,但是如果对于对象类型的变量进行指定的话,(可以设定默认值)

variable "ports" {
  type = object({
    webserver = number
    db        = number
  })
  default = {
      webserver = 8080
      db        = 3306
  }
}

尽管我希望仅指定bd的值

ports = {
    db = 3000
}

需要”webserver”属性。产生了错误。
因为对象类型的默认值只能以整个对象的值来指定。(希望能够实现这个功能…)

如果预计只需覆盖部分默认值,应该使用单独的变量而不是object类型。

variable "port_webserver" {
  type    = number
  default = 8080
}

variable "port_db" {
  type    = number
  default = 3306
}
广告
将在 10 秒后关闭
bannerAds