terraform-必知必会
本文记录一些关于terraform的理解,只记录我认为有必要记录的东西,以及一些我自身的理解,
经验
部分来自我自身面试经历,本文绝大部分来自教程: https://lonegunmanb.github.io/introduction-terraform/
概述
HashiCorp这家公司,极其重视”基础设施”建设,他们思考terraform的配置文件应该使用json或yaml时,都不满意,于是设计了
HCL
(HashiCorp Configuration Language).他们又花时间开发了 go-plugin 这个项目,把插件编译成一个独立进程,与主进程通过 rpc 进行互操作。
Terraform是一种基础设施即代码(infrastructure as code)
的实现.允许开发人员使用声明式语言
定义和管理基础设施资源.
原理
- 声明式语言: Terraform使用声明式语言语言来
定义/描述
你想要的额基础设施的最终状态,而不需要关心怎么达到该状态. - 资源提供者(provider): Terraform通过provider与各种基础设施平台(AWS, GCP, K8S)交互,不同的provider负责不同平台的API通信已经资源的管理.provider也就是terraform的插件,与terraform主进程通过rpc交互.
- 状态管理: Terraform使用状态文件
statefile
来记录当前基础设施的状态.这个文件记录所有已创建的资源及其配置信息,以便在后续的执行中进行比较和更新. - 执行计划:
terraform plan
它会分析当前状态文件和代码,对比并生成一个执行计划,计划显示将要创建/更新/删除的资源以及顺序. - 应用计划:
terraform apply
应用上面的计划,实际改变平台资源. - 可重现性: 无论何时运行terraform,它都会尽力使基础设施达到你所定义的状态,如果已经符合,则不会做出任何改变.
Provider
即terraform的go-plugin插件,不同平台对应不同的provider,terraform负责分析代码然后通过provider调用平台的api或者SDK实现资源的管理.
1 |
|
provider的配置是全局的,只能配在根模块
下.
terraform init
重要一步就是下载provider到本地
状态文件
terrafrom的状态文件主要包含三个状态信息:
- 资源当前状态
- 配置中定义的期望状态
- 上面二者之间的映射
状态锁
State Lock
是一种机制,用于在并行执行Terraform命令时出现资源抢占的问题.状态锁确保同一时间只有一个terraform可以修改状态文件.
当运行一个terraform命令时, 它会生成一个锁文件并获取它,知道执行完成才会释放该文件.执行过程中其他terraform命令因为获取不到这个锁文件所以无法执行.
Backend
Backend用于存储和管理状态文件
,包括读写锁定等操作.它可以是本地文件系统,也可以远程存储(GCS, S3等).
同时terraform支持http形式
的backend,意味着我们可以简单写个http服务在实现backend,然后terraform就可以通过http请求管理.
另外terraform也支持主流 db来存储状态文件,意味着PostgreSQL db/MariaDB/SQL server 等数据库也能作为一个backend.
下面用python写个httpserver来实现简单的backend
1 |
|
1 |
|
上面只是实现了GET/POST,也就是获取和更新状态文件的功能,实际生产中还要实现文件的持久化、并发访问控制、状态锁定等功能.
terraform常见文件
- main.tf: 类似函数中的
main()
- ouput.tf: 定义terraform的输出信息,在terraform执行完成后输出
- data.tf: 定义数据源,用于查询平台上资源的信息.
- variables.tf: 定义terraform的变量
- terraform.tfvars: 定义terraform的变量默认值,它可以包含变量的默认值或敏感信息
- provider.tf: 定义具体的provider,以及它的版本.
- terraform.tfstate: 状态文件,一般存在远端backend
- versions.tf: 定义terraform的版本要求
代码编写
terrafrom使用的HCL声明式语言,以块的形式组织代码,通过大括号来划分块,常用的块有:
- local{}: 定义本地变量
- resource{}: 定义资源
- data{}: 用于数据查询或计算
- module{}: 定义/引用模块
- source定义模块位置
- ouput{}: 输出数据
环境变量
TF_LOG: 定义terraform内部日志输出级别
1
export TF_LOG=[TRACE|DEBUG|INFO|WARN|ERROR]
TF_LOG_PATH: 顾名思义,定义日志输出文件
1
export TF_LOG_PATH=./terraform.log
TF_INPUT: 定义是否交互式执行terraform命令, 0或者false代表不交互
1
export TG_INPUT=0
TF_VAR_name: 此变量用于给
name
变量赋值1
2
3
4export TF_VAR_region=us-west-1
export TF_VAR_ami=ami-049d8641
export TF_VAR_alist='[1,2,3]'
export TF_VAR_amap='{ foo = "bar", baz = "qux" }'TF_CLI_ARGS: 定义使用命令行时,默认附加给CLI的参数,比如
TF_CLI_ARGS="-input=false" terraform apply -force
相当于执行terraform apply -input=false -force
,用于自动化CI环境下定制terraform的默认参数.TF_CLI_ARGS_name: 则可以指定特定的子命令下定制默认参数.比如
TF_CLI_ARGS_plan="-refresh=false"
就只针对plan命令起作用.TF_DATE_DIR: 定义工作目录下terraform数据的保存位置,默认是
.terraform
.一般情况下不应该改动它TF_IN_AUTOMATION: 如果该值定义为
非空值
,terraform会认为自己运行在一个自动化环境下,从而减少一些非必要的输出信息,比如给出该执行什么子命令的建议.TF_REGISTRY_DISCOVERY_RETRY和TF_REGISTRY_DISCOVERY_TIMEOUT: 定义连接registry下载provider或plugin的重试次数和超时时间(默认10s)
TF_CLI_CONFIG_FILE: 定义terraform命令行配置文件的位置
1
export TF_CLI_CONFIG_FILE="$HOME/.terraformrc-custom"
常用命令
init: 初始化terraform工作目录,包括下载provider,反复执行是安全的
plan: 创建变更计划,
-out
将输出保存在一个文件中apply: 执行计划
destroy: 销毁资源
import: 将资源导入到状态文件,一般用于将非terraform管理的资源纳入terraform管理中,也可以将资源的状态文件导出到本地
validate: 检查目录下的terraform代码语法,只是检查语法不会访问远端资源
state: 对状态文件进行管理操作,一般情况下不建议手动更改state文件,此时可以使用state命令来对资源进行细粒度处理, 它有很多子命令, 例如:
terraform state list
: 列出当前配置中所有资源及其状态, 它会显示当前状态文件中存在的所有资源的地址.terraform state show
: 显示指定资源的详细信息, 包括其属性和当前状态.terraform state rm
: 从状态文件中删除指定资源,但不删除实际资源只是剔除出terraform的管理terraform state pull
: 从远端backend拉取最新的状态文件到本地terraform state push
: 将本地状态文件推到远端backendterraform state mv
: 代码移动或重命名后资源文件中也要做对应的操作terraform state refresh
:刷新状态文件以获取最新的资源状态。它用于将当前的资源状态与实际的基础设施进行同步
terraform state lock/unlock
: 手动锁定/解锁状态文件
output: 用来提取输出
fmt: 格式化terraform代码文件的格式和规范
graph: 用来生成代码描述的技术设施或执行计划的可视化视图,输出
DOT
格式,可以通过GraphViz转换成图形文件terraform graph | dot -Tsvg > graph.svg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 经验
## 状态文件迁移
假设在本地init并apply,会在本地产生statefile,此时添加新的backend配置再次init.
此时terraform会检测到backend改变了,它会先检查新backend有没有状态文件,没有的话就会直接迁移,需要用户手动确认.
如果新backend已经有状态文件了,terraform会把`新旧`状态文件下载到本地的一个临时目录,然后要求用户人工核对后决定是否覆盖既有的statefile.
## backend配置错误
第一次init时会报错连不上backend,修改好后再次init还是会报错.
这是因为terraform在第一次init连不上backend时会在本地写statefile,第二次连接发现backend配置变了,它会尝试从原来的backend读取statefile,并且尝试将其迁移到新的backend,但是因为原来的backend本身就是错的,所以它还是会报错.
`解决办法`是要把本地的statefile删掉或者移走.
> 上面两个问题的原理其实都是一样的,那就是terraform在使用新backend的时候,都会做一个动作: `先检查旧的再判断新的`
## 代码结构改变
当terraform配置的路径和结构与statefile不一致时,会丢失资源的跟踪.比如你把module.a移动到了module.b,apply时terraform会更根据现有的statefile去寻找资源,这些资源仍然关联着旧的路径(module.a),此时terraform会认为无法找到module.b这个资源,而尝试重新创建它. 因此路径变更如果未及时反映到状态文件中,会导致terraform状态与实际云环境的状态不一致,plan和apply可能会提出错误的改动计划,可能会导致资源无意中的破坏或重复创建.另外状态文件不更新也会导致依赖关系混论.
```shell
# 更新状态文件的方法主要靠terraform state子命令
# address: module.<模块名>.<资源类型>.<资源名称>
# module路径改动
terraform state mv module.old_module.resource_name module.new_module.resource_name
# 在terraform管理中移除资源但不实际销毁它
terraform state rm <address>
# 列出当前terraform状态文件中所有资源
terraform state list
terraform state show <address>