Galaxy

姚皓的技术博客-一杯咖啡,一首音乐,一台电脑,编程

0%

Terraform IaC 学习Tips

背景

在之前参与资产市场产品迭代的时候,就资产市场与研发平台的打通,研发团队就提出过使用IaC,实现“一键下行”。一键下行,指的是在下行复用组件资产的时候,将组件所依赖的资源也同时自动化开通,然后自动将组件部署在新开通的资源上。
举个例子:假设4A组件需要依赖RDS、Redis、OSS,就在后台配置的云资源环境自动申请RDS、Redis、OSS。站在研发的视角,隐藏了资源申请的复杂性,加速了复用流程,提升了研发体验。
在资产市场对外输出时,对接的云资源往往没有那么理想,必定是阿里云公共云资源。如果使用IaC,便于适配不同的云环境。

接下来即将开始的工作,可能也会涉及大量而频繁的云资源开通。最近在做Demo的时候,就深感开通和销毁云资源这个繁琐的事情有多浪费时间。
以程序员的偷懒本性,自然想到了使用IaC来提升效率。

概念和快速入门指引可以直接查阅参考资料,就不copy & paste了。只记录一些个人觉得实践时需要关注的点。
由于自家的关系,以下云资源默认为阿里公共云。

2. 工作中可能用到IaC的业务场景

IaC核心是版本控制和可重复,实现提效、降低误操作、一致性与合规安全。
适合IaC的业务场景是什么?企业上云、环境复制、环境重建、合规管控等。

3. 学习时的几个选型

3.1 Terraform vs. 阿里云ROS

对于个人而言,暂时没精力掌握两种IaC语法。考虑到后续工作会涉及跨云平台的资源编排和管理,优先选择了Terraform。

3.2 Terraform vs. Ansible

Ansible于 2015 年被Red Hat收购。从当前的厂商Redhat自己的说法,两者可以结合。

  1. 使用Terraform创建云资源:使用Terraform创建虚拟机、网络、存储等云资源,确保环境的一致性和可重复性。
  2. 使用Ansible配置软件:在创建完云资源后,使用Ansible来安装、配置和管理软件,确保每个虚拟机都具有所需的配置。
    如果需要在虚拟机之间进行复杂的配置和协调,可以优先考虑使用Ansible,因为它在主机内部执行操作,更适合执行复杂的系统管理任务。

不过我的个人未经实践的粗浅见解是,如果是有很强能力的互联网行业客户,可以考虑分别利用这两套解决方案各自的长处。但对于大量泛企业客户,同时运用两套解决方案对于运维的学习成本会更高(特别是对运维人员能力一般的企业)。
此外,在云原生时代,直接通过镜像拉起容器,在主机内部执行操作是需要尽量减少的运维行为。
以国内的运维条件,直接到主机内部执行操作肯定还是无法避免。不过如果这种是低频偶发操作,是不是靠人工+文档,比引入Ansible成本更低?如果是基于纯阿里公共云的话,还有OOS(系统运维管理)云产品可能也可以替代Ansible。

Anyhow,当前我个人测试和做demo的时候还是以纯Terraform的方案。

参考资料:

3.3 是否将Terraform用于应用部署

阿里云的官方文档有使用Terraform分别在ECS和K8S集群上部署Wordpress的实践教程。典型案例里也有使用Terraform快速拉起幻兽帕鲁服务的案例。但实际搜索应用编排,主要内容是在云效的AppStack文档中。即从阿里云的业务逻辑来看,应用编排从逻辑上更贴合DevOps。
我个人还是认同这个逻辑的。上面案例中,不管是部署Wordpress,还是幻兽帕鲁的服务,都是比较成熟的服务。而对于快速迭代的企业应用,还是适合配合DevOps来落地应用编排,而非ROS(资源编排服务)。
不过也有对Terraform深度探索使用的企业(特别是游戏行业),不仅云资源,DevOps部署应用也使用Terraform编排完成。

讲到了应用编排,顺带一提,在查资料的时候看到了华为对其资源编排的分享的材料,发现一个挺有意思的细节。
在那份材料里,华为将资源编排与应用编排分为两个不同的产品,并按照Shell脚本(命令行级别自动化)->配置管理自动化(Puppet/Chef/Ansible/Salt)->资源供给自动化(Terraform/Docker Compose)->资源编排(AWS-Cloudformation/Ali-ROS/HC-RTS)->应用编排(AWS-CFN/Pivotal-BOSH/HC-AOS)的逻辑,来描述编排服务的发展趋势来描绘云上自动化。但从这张图很容易误解资源编排和应用编排是有发展递进关系。当前,资源编排和应用编排已经都合并到华为的同一个资源编排服务(RFS)的产品里,估计也是做了云产品治理与整合。但应用编排的产品页和产品代号理论上应该随之下线,但还保留着,透露出内部组织调整的影子。

4. 几个知识点

在实践了几个Hello world级的demo后,整理了几个自己关注的知识点。

4.1 身份认证

在提到身份认证之前,记录一下自己遇到过的弱智问题。
使用Terraform在创建VPC的时候,发现可以通过页面创建上海区域可用区的VPC,但没法使用Terraform创建,会报错:”Message: code: 400, Resource you requested is not available in this region or zone. “。但在杭州区域区域,不管是页面还是Terraform都可以成功创建。提了工单,经过售后工程师提醒才发现,Terraform初始配置的时候需要配置一个环境变量:

1
export ALICLOUD_REGION="cn-hangzhou"

环境变量是前一个周五配置的,隔了一个周末继续做demo的时候,自己忘记了。。。在写HCL脚本的时候还在奇怪,怎么VPC不需要指定区域的。

这里就涉及到初始化本地Terraform的时候一个设置,就是将AK和SK设置到本机的环境变量里。
Terraform要操作云资源,操作交互自然是需要带身份认证的。
官方文档介绍了几种方案,包括通过terraform命令传参方式(-var参数)、环境变量。如果是在ECS上执行,还可以通过ECS服务角色、角色扮演、OIDC角色扮演。这3个也是生产环境到的建议方式,无需直接暴露AK/SK。

不过话又说回来,我能理解需要将AK和SK放到环境变量里,还是不太理解为什么默认要将区域也放到环境变量里,而不是也放到脚本里维护。盲猜是由于ROS的HCL脚本本身就是按区域分别维护的原因。

4.2 变量

HCL2中,变量可以不用加花括号了。即可以直接用var.region,而不需要使用"${var.region}"。(这是IntelliJ IDEA里提示后才知道的)

4.3 模块化

Terraform也支持代码复用,形式就是模块。模块可以理解为包含一个或多个资源的模板,对应Terraform代码里的一个目录。
模块使用的典型场景,包括:

  • 需要重复创建多个相同资源(可以配合count或for each语法)
  • 在不同的项目或环境,调用相同的模块。通过传入不同项目或环境的参数区分

有最佳实践推荐每个基础module尽可能只包含同一产品的相关资源。

阿里云的常用module其实可以直接从terraform官网引用,参见:Browse Modules | Terraform Registry,可以在线引用,例如:

1
2
module "vpc" {
source = "alibaba/vpc/alicloud"

用在线的还是用自己封装的,就看个人判断了。
用在线版本可能会有2个问题:

  • init的时候有可能会连不上github导致失败
  • 拉取时间变长
    阿里云的alibabacloud-automation项目里的module的代码风格也多少有些差异,猜测是没有制定统一规范,由不同云产品团队自己写的关系。
    个人觉得可以作为参考和学习资料。

4.4 Terraform后台(Backend)存储状态文件

默认情况下,Terraform 状态文件(.tfstate,即Terraform State,保存了资源配置和生成资源的元数据)会存储在本地文件系统上。然而,在团队环境中,需要更安全、持久或可共享的状态管理。
使用Git托管是不行的,会产生团队多人同时操作情况下产生的状态文件更新及时性问题。例如A刚运行完apply,本地更新了state文件,还没来得及上传git,这时候B也运行的话,就可能导致A创建的资源被销毁。
最佳实践是放到OSS Bucket远程存储,配置后台通常在 Terraform 配置文件(terraform.tf 或 backend.tf)中完成。
参考资料:

4.5 存量资源导入Terraform

除非是新创建或新迁云的企业,不然肯定会有相当数量的存量资源没有通过Terraform管理。此外还会遇到:

  • 虽然资源已经使用Terraform管理,但由于某些情况,是通过控制台对云资源做了属性变更
  • Terraform模版过于复杂而拆分

这种情况下,就需要使用Terraform的DataSource来获取资源ID,声明要导入的资源,然后通过terraform import命令导入。
如果是测试使用,也可以打上被污染的标记,让terraform销毁后重新创建。

参考资料:

按照Google的建议,要避免导入现有资源。给出的理由是:因为这样做可能会很难完全了解手动创建的资源的来源和配置。应通过 Terraform 创建新资源并删除旧资源。当然这是理想情况,Google自己也没死板地要求一定要采用。

5. 工程最佳实践

虽然在demo的时候,可以用一个main.tf搞定。但既然要系统性地学习,还是学彻底。所以也整合了Terraform工程最佳实践的资料。

5.1 目录结构

Terraform目录,可以看到一般会分根模块和子模块。
每个模块里包含:

  • CHANGELOG.md
  • README.md
  • locals.tf
  • main.tf
  • variables.tf
  • outputs.tf
    此外根模块里包含providers.tf。
    如果资源复杂,资源配置代码较长,可以按照资源类型单独使用一个独立的 .tf 文件来存放,例如,用于 ECS 实例、OSS Bucket 和数据库的配置可以分别放在 instance.tf,oss.tf 和 database.tf 中。(这种情况是不是还不如封module?)

5.2 Google方案参考

按照Google的建议,使用environments目录来方每个环境的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- SERVICE-DIRECTORY/
-- OWNERS
-- modules/
-- <service-name>/
-- main.tf
-- variables.tf
-- outputs.tf
-- provider.tf
-- README
-- ...other…
-- environments/
-- dev/
-- backend.tf
-- main.tf

-- qa/
-- backend.tf
-- main.tf

-- prod/
-- backend.tf
-- main.tf

此外,按照Google的风格,也是建议使用MonoRepo来管理所有的Terraform代码,并且由单个平台工程团队管理这个MonoRepo。
也可以将Terraform配置拆分到不同的代码库。
基础代码库:
基础代码库
特定于应用和团队的代码库:
特定于应用和团队的代码库

我将个人测试自用的项目,参考Google的MonoRepo+基础代码库+项目(服务)的目录结构方案,暂时拟定了个自用的目录结构:

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
36
37
38
-- foundations/
-- vpc/
-- examples/
-- main.tf
-- outputs.tf
-- CHANGELOG.md
-- locals.tf
-- main.tf
-- outputs.tf
-- README.md
-- variables.tf
-- ecs-instance/
-- ...other/
-- template/
-- projects/
-- project-1/
-- modules/
-- main.tf
-- outputs.tf
-- CHANGELOG.md
-- environments/
-- dev/
-- backend.tf
-- main.tf
-- sit/
-- backend.tf
-- main.tf
-- prod/
-- backend.tf
-- main.tf
-- locals.tf
-- main.tf
-- outputs.tf
-- provider.tf
-- README.md
-- variables.tf
-- ...other/
-- template/

foundations(基础设施)和projects里都包含了个template目录。新建模块和子项目的时候,可以从template里快速复制出来一份。
根据实际使用体验再调整。

5.3 代码格式化

代码格式化的规则不用记,运行terraform fmt -recursive就可以了。

5.4 gitignore文件

.gitignore文件参考:gitignore/Terraform.gitignore at main · github/gitignore
稍微修改了一下,增加了个人用的IDE的IntelliJ Idea的gitignore配置,以及感觉也需要加入的.terraform.lock.hcl文件

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
36
37
38
39
40
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc

# IDE
.idea/*

注意点:
.terraform.lock.hcl这个是依赖项锁文件,需要纳入版本控制,有助于跟踪和审核指定配置的提供商选择变化。

5.5 Google的其他最佳实践

变量相关:

  • 为变量设置type字段:避免变量类型错误
  • 如果参数包含敏感信息,在其对应的变量中将sensitive设置为true
  • 表示数值的输入、局部变量和输出(例如磁盘大小或 RAM 大小)必须使用单位命名(例如 ram_size_gb)
  • 对于具有与环境无关的值的变量(例如磁盘大小),请提供默认值
  • 对于具有特定于环境的值的变量(例如 project_id),请勿提供默认值。这样,调用模块必须提供有意义的值。
  • 对于根模块,请使用 .tfvars 变量文件提供变量。为了保持一致性,请将变量文件命名为 terraform.tfvars。命令行选项是临时性的,容易忘记。使用默认变量文件更容易预测。
    模块相关:
  • 在每个模块中,添加 Markdown 格式的 README.md 文件。在 README.md 文件中,添加有关模块的基本文档
  • 使用资源各自的文件和描述性名称(例如 network.tf、instance.tf 或 loadbalancer.tf)创建资源的逻辑分组
  • 将示例放在 examples/ 文件夹中,并为每个示例提供单独的子目录。对于每个示例,请添加详细的 README.md 文件。
    命令相关:
  • 使用下划线命名所有配置对象,以分隔多个字词
  • 将资源名称设为单数形式
  • 在资源名称中,请不要重复资源类型。例如避免:resource "google_compute_global_address" "main_global_address" { … }
    输出:
  • 请勿直接通过输入变量传递输出,因为这样做会阻止输出正确地添加到依赖关系图中
    数据源:
  • 将数据源放在引用它们的资源旁边。
  • 如果数据源数量很大,请考虑将它们移动到专用 data.tf 文件中。
    自定义脚本:
  • 限制自定义脚本的使用。Terraform 不会考虑或管理通过脚本创建的资源的状态。
  • 将 Terraform 调用的自定义脚本放在 scripts/ 目录中。
    其他:
  • 建议指定Provider版本:防止Provider更新引入问题,保障稳定性
  • 将静态文件放在单独的目录中
  • 最大限度地减少每个根模块中的资源数量。一般规则:在单个状态下,最好不要超过十几个。
  • 不要手动修改 Terraform 状态

5.6 无网络情况下运行

客户专有云环境,特别是生产环境,基本都是无法连互联网的。
首先在有网络环境的机器把当前目录的插件复制到特定目录:

1
terraform providers mirror /data/terraform/plugins

拷贝到无网络环境的机器,运行时指定插件目录:

1
terraform init -plugin-dir=/data/terraform/plugins

参考资料:《Terraform 101 从入门到实践》 第二章 Providers插件管理 - 南瓜慢说官网

5.7 Terraform故障恢复

和K8S不同,Terraform 不提供自动故障恢复功能,必须依赖故障时运行的脚本。
不过故障的检测能力还是具备的。可以使用terraform state命令,检查Terraform管理的当前资源状态:

1
terraform state list

这将输出一个列表,其中包含了资源类型和资源的唯一标识符(例如aws_instance.example)。
如果想查看某个特定资源的详细状态,可以使用terraform state show命令,并提供该资源的地址作为参数。例如,如果要查看名为example的AWS EC2实例的状态,可以执行:

1
terraform state show aws_instance.example

5.8 快捷命令

用得次数多了之后,也可以自己设置一些alias来简化需要敲的命令:

1
2
3
4
5
6
# terraform
alias tfmt='terraform fmt -recursive'
alias tinit='terraform init'
alias tapply='terraform apply -auto-approve'
alias tdestroy='terraform destroy -auto-approve'
alias tplan='terraform plan'

6. 总结

以上内容都只是Demo测试+搜索后的资料整理,未经历实际项目和客户的实践验证,只能算是纸上谈兵。如果后续有机会在真实客户或项目中实践,会修正其中错误的内容。

X. 参考资料

Terraform入门

什么是Terraform_Terraform(Terraform)-阿里云帮助中心
https://help.aliyun.com/document_detail/95820.html

为什么选择Terraform_资源编排(ROS)-阿里云帮助中心
https://help.aliyun.com/zh/ros/user-guide/overview-2

如何在本地安装和配置Terraform_Terraform(Terraform)-阿里云帮助中心
https://help.aliyun.com/document_detail/95825.html

快速手册

Terraform有哪些常用命令_Terraform(Terraform)-阿里云帮助中心
https://help.aliyun.com/document_detail/145531.html

常见的Terraform模板示例有哪些_资源编排(ROS)-阿里云帮助中心
https://help.aliyun.com/zh/ros/user-guide/examples-of-terraform-templates

Terraform 身份认证-阿里云帮助中心
https://help.aliyun.com/document_detail/2837050.html

《Terraform 101 从入门到实践》 第五章 HCL语法 - 南瓜慢说官网
https://www.pkslow.com/archives/terraform-101-hcl

最佳实践参考

Terraform代码的开发方式和建议是什么_资源编排(ROS)-阿里云帮助中心
https://help.aliyun.com/zh/ros/user-guide/methods-and-suggestions-for-terraform-code-development

使用 Terraform 的最佳实践  |  Google Cloud
https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=zh-cn