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的差异显示能够一目了然地显示变更前后的差异,因此可以轻松修复。
terraform的侧面修正如下:
resource "google_cloudfunctions_function" "hoge" {
...略...
+ labels = {
+ deployment-tool = "cli-gcloud"
+ }
}
顺便提一下,如果想要将导入的资源放置在管理外面的话
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
}