Terraform实践
本文简述Terraform HCL语言;提供基于Docker与AWS平台供应基础设施的demo;并对多环境管理进行探索。
模板引擎
文件结构
Terraform识别以下几类特殊文件:
*.tf
:资源声明文件,用于声明所要创建的资源。*_override.tf
:资源后处理文件,用于覆盖已存在资源的某些部分。.terraform.lock.hcl
:锁定provider的版本。terraform.tfvars
:给定已声明变量一组特定值。
HCL语法
HCL全称为HashiCorp Configure Language,是由HashiCorp专门为Terraform设计的声明式配置语言,用来描述如何管理一组的基础设施。
HCL通用语法块为:
|
|
其中
块类型包含以下几种常见类型:
块类型 作用 terraform 声明provider版本及来源 provider 配置provider resource 定义并配置资源对象 data 获取外部资源 variable 定义模块输入变量 output 定义模块输出变量 locals 定义本地变量 module 定义模块引用 资源类型及Arguments由provider定义。
资源名由用户自定义,用来在引用模块内资源,因而避免相同资源同名的问题。并非所有块类型都需要额外的资源名(如variable、output、provider、locals等)
以创建一个redis docker container为例体验一下HCL:
|
|
隐式依赖
HCL中通过资源引用来声明隐式依赖,即R1
的某些输入参数通过引用R2
资源的输出结果建立起了R1
对R2
的隐式依赖。Terraform在进行资源创建时,会先创建R2
再去创建R1
,删除顺序与创建顺序相反。
上例中docker_container.redis.image
引用了docker_image.redis.image_id
,即docker_container.redis
依赖于docker_image.redis
。
|
|
模块
为了增强功能块复用,Terraform提供了强大的模块化能力。
模块通常包含一组一起使用的资源,通常在一个目录下定义多个tf
文件以实现一个模块。
模块通常分为两类:
- 根模块:在工作目录(执行terraform CLI时所在的目录)下的模块。
- 子模块:被其他模块引用的模块,子模块可以是自定义的本地模块,也可以使用registry中第三方提供的模块。模块引用可以嵌套,但嵌套深度不应过深。
模块之间的交互主要有两种方式:
- 父模块通过配置子模块属性向子模块传递input variables。
- 子模块通过output variables向父模块返回相关的配置值供其引用。
Terraform in Docker
Terraform可以构建、修改及移除Docker基础设施。Docker provider提供了network、volume、image、container等资源,以提供对Docker操作的完整支持。
Terraform Docker Tutorial详细描述了如何部署一个nginx docker container,配置与上例类似。
我们以部署一个带存储后端(redis)的留言板(guestbook)为例,探索使用Terraform部署两个具有依赖关系的docker container。
该场景资源依赖关系可以描述为下图:
具体实现可参考示例代码,工作方式与docker compose极为相似。
然而Docker资源属于上层组件,Terraform并不具备管理优势,其核心价值在于公有云基础设施供应。
Terraform in AWS
通常,云提供商为了满足多样的商业需求,会提供与计算、存储、网络相关的多种功能服务。这些厂商提供了易用的UI界面及api供用户管理相关的资源,但存在以下问题:
- 服务种类众多,每个服务又包含数十种配置,给用户带来大量的学习成本。
- 流程繁杂,用户需要按顺序地配置、启动服务,并进行资源关联,易错且低效。
单从创建一个强隔离性的nginx ec2 instance而言,用户不得不手动创建vpc、subnet、internet gateway、route table等网络服务以及ec2、security group、key-pair等计算资源。构建过程需要对这些资源手动创建并正确配置,清理过程同样需要手动移除各个服务。如果考虑在云上维护多个环境,那管理成本无法估量。
Terraform将我们从繁杂的基础设施管理中解救出来:
- 声明式配置简化我们在界面上点点点的操作,并降低出错的概率。
- 资源按依赖顺序并发构建,大幅提升基础设施的部署速度。
- 模块化供应屏蔽了大量内部细节,比如
aws_vpc
module将vpc、subnet、internet gateway、route table等资源封装起来。
示例代码展示了构建一个guestbook ec2 instance的方法,并将相应的公网ip输出出来。
|
|
通过curl 13.231.202.24:3000
可以访问留言板应用。
多环境管理
一般的产品迭代过程分为需求研发、质量测试、功能上线等阶段。在不同的阶段,我们通常需要维护一套独立的环境以达到当前阶段的目的,比如通常我们需要维护dev、stage、prod三个环境。Terraform可以帮助我们简化多环境管理,越来越多的多环境管理的最佳实践被提了出来。
基于Terraform workspace管理
Terraform workspace用于描述存放在同一backend的state文件。某些backend支持存放多个state文件,因此支持通过多个workspace进行环境管理。
Terraform支持workspace子命令,用以列举、创建、切换workspace。一个workspace对应着一个环境,环境的切换对应于workspace的切换,初始仅存在一个default workspace。
为支持多环境使用不同的配置,Terraform提供了特殊的插值表达式terraform.workspace
,Terraform在解析配置文件时,会将此标识替换成当前的workspace名字。下例展示了两种常见的用法:
|
|
示例代码展示了使用多个workspace创建不同的ec2实例,每个workspace管理的基础设施相互独立。(此例workspace并未提交到VCS中,workspace管理于local backend)
使用workspace对多环境管理可以最大程度上复用配置代码,并且是Terraform官方提供的特性。
基于多分支管理
基于git branch对多环境管理是十分常见的策略,大量企业使用不同的release分支管理产品的版本。这最大程度上保证了版本的隔离性,避免不同版本的特性相互污染。但另一方面放弃了代码复用的能力,尤其是当维护的分支规模较大时,一个通用的bugfix会引起cherry-pick风暴。
为支持不同环境使用不同的配置,通常将不变的内容以模板的形式存放在Terraform配置文件中,将可变的配置声明在variable.tf
中,不同分支通过维护一组特殊的配置值(记录在terraform.tfvars
中)来控制环境的配置。
示例仓库创建了dev分支与prod分支分别管理dev环境与prod环境,二者的区别在于对服务监听端口及应用镜像的tag配置不同。
基于Terragrunt管理
Terragrunt是由Gruntwork.io开发的Terraform代码管理工具,其核心宗旨是提供一层thin wrapper实现DRY(Don’t Repeat Yourself)。其核心思想是将不变的配置模板与可变的配置值分开管理,以维护多环境的特异性配置,在设计上与kustomize异曲同工。
使用Terragrunt通常将配置模板和配置值放到不同的目录进行管理,如分别在modules、live目录下:
|
|
在配置模板中,声明所需的输入变量:
|
|
在patch文件中,通过维护不同的参数文件来控制环境的配置:
|
|
在相应的配置目录下执行terragrunt apply
即可实现对应环境的部署。参考完整示例代码。
Terragrunt CLI本质是将分离的配置与模板组合在一起,再去执行对应的Terraform CLI,这也是Terragrunt会被定位为Terraform的thin wrapper的原因。
Terragrunt 结合了workspace与multi-branch管理的优势并解决了二者存在的问题。但是其作为一个外部工具引入了额外的学习成本,并且目前在Terraform Cloud中无法使用。
三种环境管理方式的比较
Workspaces | Branches | Terragrunt | |
---|---|---|---|
Minimize code duplication | ■■■■■ | □□□□□ | ■■■■□ |
See and navigate environments | □□□□□ | ■■■□□ | ■■■■■ |
Different settings in each environment | ■■■■■ | ■■■■□ | ■■■■■ |
Different backends for each environment | □□□□□ | ■■■■□ | ■■■■■ |
Easy to manage multiple backends | □□□□□ | ■■■■□ | ■■■■■ |
Different versions in each environment | □□□□□ | ■■□□□ | ■■■■■ |
Share data between modules | ■■□□□ | ■■□□□ | ■■■■■ |
Work with multiple modules concurrently | □□□□□ | □□□□□ | ■■■■■ |
No extra tooling to learn or use | ■■■■■ | ■■■■■ | □□□□□ |
Works with Terraform Cloud | ■■■■■ | ■■■■■ | ■□□□□ |
黑块越多表示越具优势。引自 https://blog.gruntwork.io/how-to-manage-multiple-environments-with-terraform-32c7bc5d692
多环境与Devops
在DevOps的实践领域中,一种高度自动化的方式是通过pipeline贯穿多个环境,从而实现代码直接上线的能力。
比如下面的案例中,一套代码控制多个环境使用,但通过一条pipeline将环境连接起来,从而实现自动化部署、测试、上线的能力。这依赖了Terraform基础设施构建的一致性。
具体的工作流如下:
- 向VCS提交代码。
- 通过CI将VCS中的模块拷贝发布到制品仓库中。
- CD检测到有新版本的制品发布,会应用到测试环境,并执行自动化测试。
- 测试通过后,自动地(或人工触发)应用到线上环境。
使用Terraform Cloud实现自动化部署
Terraform Cloud是Hashicorp为支持Terraform工作流的平台,提供自动化管理基础设施的能力。其具备权限控制、密钥管理、托管状态文件等能力。
Terraform Cloud使用流程如下:
接入VCS provider,核心是向VCS中注入TC webhook。
创建workspace,可以指定具体的触发条件。
配置变量。包括
tf
文件中声明variable,Access Token相关的环境变量。首次运行。官方明确指出:A workspace with no runs will not accept new runs from a VCS webhook. You must queue at least one run manually。
之后, 便可以根据workspace创建的触发条件完成自动化部署。