关于Terraform的话题,其实很早就想找时间专门写一篇单独聊一下。早在一年多以前,笔者有幸参与一个软件交付项目,接触到了Terraform这款基础设施即代码软件。即便是在当时已经从事了多年DevOps专业工作的情况下,仍然被这款软件所蕴含的工程思想所触动。
在此前,笔者也曾主导过多个不同技术栈的软件项目从持续构建到持续部署的标准化方案落地。回顾之前从基础设施资源交付在到代码上线发布的流程管理,很少思考关于基础设施的状态管理,大量的配置变更依然处在“原始社会”,依赖于工程师的人肉执行以及那“薛定谔的文档”。而每一次新项目上线过程中的资源交付,更像是如临大敌般手忙脚乱。往往从开发、测试、再到预生产和生产环境的资源交付,需要几个环境通过不断试错,跌跌撞撞,在带有一些幸运属性去完成项目的交付。
在真正使用Infrastructure as Code(IaC:基础设施即代码)交付资源之前。笔者所工作过的绝大多数组织,都没有在基础设施管理上做的更好,直到今天,仍可以毫不夸张的说,绝大部分组织对于云上资源生命周期的管理依然是非常滞后的,如果能通过云厂商所提供的API接口去创建服务器、数据库等,那已经是一家所谓不错的“创新性互联网公司了”,可资源在创建后到销毁前的配置变更管理,并没有得到有效管理。
而笔者的观点则是Terraform在软件资源交付流程中的作用被大大的低估了。在与许多同行交流过程中,也曾不遗余力的推荐使用。但得到的反馈往往充满偏见。有人认为Terraform本质上只是对云厂商的API进行了封装,和自己对各云产品开发一套API没什么不同。这样的看法稍显片面,通过封装云厂商的API接口实现云平台资源全生命周期管理实际上是需要投入大量的人力成本,而这些所谓的“自研云管平台”往往也只是将云控制台的相关少量云服务配置参数简单封装之后,便搬到了组织内部,可能做了部分的定制,比如资源的批量创建等,但投入产出比非常不乐观,其边际效益并没有随着平台的成熟而获得更大的收益,相反可能会陷入无止境的后期迭代和维护成本。
也要人认为Terraform是好用的,通过一些特定领域的配置语言简化了基础设施管理平台的开发成本,但和Ansible并没有什么不同。
Terraform和Ansible最初的设计初衷都是为了解决在软件交付过程中的配置自动化问题。经过长时间生产环境验证,两者都被证明了是配置系统的最佳选择。然而,它们之间的配置过程及面向对象并不完全相同。
Terraform是一种声明式的模型工具,因此在某些方面使用(比如应用发布)时会带来一定的复杂性,不便于管理。当它作为整个交付流的一环,在执行某些偏操作系统及应用层的行为时实际上是无力的。比如说代码发布管理、安装软件补丁、日常维护等需要制定执行过程的操作行为。Ansible则可以非常容易的配置服务器、应用等,因此在整个端到端交付的过程中,往往给人的感觉是Ansible才被认为是DevOps流水线中工具的最佳选择。事实上,Terraform真正擅长的是它对于云上基础设施的管理,而Ansible在管理云上资源变更的过程中,稍显孱弱。
因此,两者面向的管理对象本质上是不同的。用Terraform管理云资源,应用层管理则使用Ansible,被看作是DevOps的一个最佳实践。
关于Terraform的工具优势,笔者认为主要有如下三点:
1. 一致性:
Terraform通过其状态管理机制,统一管理其创建的资源,同时所有变更操作必须通过修改Terraform代码配置生效,从而杜绝了任何非计划内的临时性配置变更,保证了所有配置变更均可追溯、可回滚。
另一方面,Terraform的一致性还体现在了基础设施资源能够被轻松复制上。无论是在原有环境下创建一台服务器、还是要将开发环境的基础设施克隆到测试环境,甚至是要将一个云账号下的所有资源均复制到另一个账号内,能够保证每个环境的资源配置都是一致的,并且整个环境的构建时间能在数分钟以内完成,极大地降低了实施成本。
2. 中立性:
相较于阿里云的ROC、AWS的Cloudformation,Terraform的另一优势在于跨厂商使用,这也意味着未来即使组织需要选择使用第二个云厂商资源、或者将应用迁移到另一朵公有云时,对于运维管理人员而言,无需付出额外的学习成本去考虑如何使用单一云厂商所提供的配置管理工具去管理云上资源。Terraform就像是电源转接口提供了一种电源管理的标准,出门旅游时不管是走到哪一个国家,去往任意一座城市,不用关心这座城市用的是220V电压还是110V电压,用的是三眼插座还是两眼插座,只要将手机的充电线点到这个转接口上,便能够顺利的给手机充电,而不用下了飞机第一件事就是寻找对应电压的电源插座。
3. 管理成本:
在国内的大环境下,绝大多数组织在构建应用架构时,依然在使用云上托管PaaS产品时存在较多顾虑。除了成本上的考量,在运维管理上的也同样存在创建过程繁琐,需要具备较高的云管理专业能力。在过往的一个项目里,笔者所交付的项目用到了数据库、网关、监控、日志、消息队列、中间件等大量的PaaS层云原生服务,每个应用所关联的云服务将近20个,每上线一个新的环境,便要将这些服务重新创建交付一遍。若整个过程完全依赖于手工创建,则对于管理人员的技术门槛要求极高,且交付过程及其痛苦。在此情况下,不同环境的交付质量便可想而知。
在项目初期,Terraform的使用使得整个交付流程中资源准备的过程有了质的提升。然而,随着团队规模逐渐扩大,先前在本地执行的模式逐渐开始陷入瓶颈,团队几乎每天都要花上大量时间精力应付资源创建过程中的各种报错。于是,便开始寻找如何在大规模场景下在生产环境使用Terraform的解决方案。
Terraform的底层执行逻辑是当用户运行Terraform时,会自动创建一个Terraform状态文件,并将基础设施信息记录在内。默认情况下,该文件以terraform.tfstate命名被创建在执行目录下。文件信息包含了一个格式化的自定义JSON,用于记录真实环境下terraform模板所创建的资源对象。举例来说,资源模板包含以下信息:
在执行过terraform apply之后,将会得到如下terraform.tfstate文件。
通过这样一个简单的JSON格式文件,便能够知道alicloud_oss_bucket实例在用户的阿里云账号下对应的id是 terraform-test-20201027。每当用户执行Terraform,状态文件能够获取到最新的状态,然后基于用户当前最新的Terraform配置模板,判断是否要执行这个变更操作。
从存储形态上Terraform状态文件分为本地存储和远端存储两种。
1. local:本地存储
资源状态存放在本地的一个state文件中,默认为执行目录下的名为terraform.tfstate文件。此方式也是Terraform默认的存储形式。
2. remote:远端存储
资源状态存放在远端的一个服务中,例如阿里云的OSS Terraform Cloud,HashiCorp Consul等。远端存储带来的好处是实现了与资源定义模板管理的解耦,可以让State脱离本地磁盘而存储,在提升State安全性的同时,团队协作可以不再受制于Terraform的执行环境、执行目录和多人执行时间的限制,提升了管理的灵活性。
对于个人项目而言,本地存储terraform.tfstate状态文件并没有什么问题,但在真实规模化场景下,当一个交付团队对于同一云服务共同维护terraform时,前提是务必要解决以下几个问题:
问题一:共享状态文件
在使用Terraform去更新基础设施时,团队的每个成员均需要使用同样的Terraform 状态文件,这意味着用户需要将这些状态文件存储在一个共享的区域。
关于跨团队管理文件最通用的方案是将它们放入到版本管理平台(比如Git)。然而对于Terraform来说,有两个原因使之成为一个非常糟糕的选择。
1. 人为错误
在实际的工作过程中,实在太容易在运行Terraform代码前忘记拉取最新版本,又或是在运行代码后忘记提交版本了。一旦发生团队内某个人提交了过时的配置信息,其结果将会导致突然的回滚或是重复之前的部署。
2. 秘钥管理
由于所有的数据都会以文本格式存储在Terraform状态文件里,这就导致了所有敏感信息存在被泄露的风险。举个例子,如果用户创建了一台阿里云上的RDS关系型数据库资源,Terraform会将用户名及密码以明文信息存储在状态文件中。将敏感秘钥以明文信息存储任何情况下都是一个灾难,包括版本管理。
为此,管理状态文件的最佳实践是使用远端存储管理状态配置文件。使用terraform远端配置命令,用户能够每次从远端拉取或存储状态文件。远端存储带来的好处是实现了与资源定义模板管理的解耦,可以让状态文件脱离本地磁盘而存储,在提升状态文件安全性的同时,团队协作可以不再受制于Terraform的执行环境、执行目录和多人执行时间的限制,提升了管理的灵活性。
问题二:锁定状态文件
只要数据是共享的,则必然又会陷入另一个问题:即如何锁定配置,确保状态文件的一致性。假设有两个团队在同一时刻运行Terraform,多个Terraform进程之前则必然会导致在并行更新状态文件时发生冲突,造成数据丢失,状态文件损坏。
开启远端状态能够解决了如何共享这些状态文件给团队其他成员,但也带来了两个新的问题。
1. 每个开发人员都必须要记得运行terraform remote config命令。
2. 将远端存储状态文件共享在指定区域,并不意味着能够锁定这个区域。因此,两个管理人员在同一时刻使用同一个状态文件从而发生冲突是十分可能的。
Backend是存储State的机制,它决定了State数据的加载逻辑和Terraform命令执行之后State的数据的更新过程。Terraform生命周期中与Backend的交互过程,如下图所示。
Terraform通过init命令完成Backend的加载和配置。在执行plan和apply命令,加载资源模板的同时,通过Backend加载State中的存量数据(如果有),命令结束后,将Provider或Provisioner响应中的数据通过Backend更新到状态State中,最终达到状态数据与模板定义的一致。官方文档可参考如下地址:https://www.terraform.io/docs/language/settings/backends/index.html
从功能实现级别的角度,Backend可以分为两种:
1. Standard
State管理的标准化实现,覆盖标准的功能点State存储和State加锁。这种Backend目前只适用于在本地系统上运行所有操作的场景,尽管也实现了对State的远程存储,但Backend的执行逻辑仍发生在本地并通过直接调用API请求来完成。
2. Enhanced
可以看作是Standard的加强版,即Backend的执行逻辑不仅可以发生在本地,还可以通过API或者CLI的方式发生在远端。目前这种Backend的种类有两种,一是local,另一个是只支持Terraform Cloud的remote 。
由于Enhanced backend仅支持Terraform Cloud的remote模式,故在此不展开讨论。而Standard Backends模式,官方目前已支持14种,其中部分支持状态加锁,加锁的方式也各有不同,以AWS和阿里云为例,通过其云服务表格存储结合对象存储的方式实现。AWS的实现方式为DynamoDB+S3,阿里云的实现方式则通过Tablestore和OSS实现。
OSS Backend是基于阿里云的表格存储服务(Tablestore)和对象存储服务(OSS)实现的Standard Backend,其中Tablestore用来存储运行过程中产生的“Locking”,保证State的正确性和完整性;OSS用来存储最终的State文件。接下来将详细介绍OSS Backend。
1. 工作原理
OSS Backend的工作流程可以分为加锁、存储State、释放锁三步,下图是一个简单的工作流程图:
主要包含以下几个部分:
运行Terraform命令后,Backend首先会从Tablestore中获取LockID,如果已经存在,表明State被损坏或者有人正在操作,返回报错,否则,自动生成一个LockID并存储在Tablestore中。
如果是init命令,初次会生成一个新的state文件并存储在OSS的特定目录下,并释放LockID。
如果是plan、apply、destroy等涉及到修改State的命令,会在命令结束后将最新的数据同步更新到State文件中,并释放LockID。
如果是 state、show 等不涉及修改的操作,会直接读取State内容并返回。
2. 模板定义
和Provider和Provisioner一样,Backend在使用时同样需要在模板中定义。Backend 通过关键字backend来声明。如下代码声明了一个oss backend,其state存储在名为terraform-remote-backend-c9ff230f-db1b-ffd3-acdd的bucket中,对应的文件为prod/terraform.tfstate,并声明state文件为只读和加密;锁信息存储在一个名为terraform-remote-backend-c9ff230f-db1b-ffd3-acdd的表格中,这个表格位于杭州的Tablestore实例tf-oss-backend中。
terraform { backend "oss" { bucket = "terraform-remote-backend-c9ff230f-db1b-ffd3-acdd" prefix = "env:" key = "prod/terraform.tfstate" acl = "private" region = "cn-hangzhou" encrypt = "true" tablestore_endpoint = "https://haier-backend.cn-hangzhou.ots.aliyuncs.com" tablestore_table = "terraform_remote_backend_lock_table_c9ff230f_db1b_ffd3_acdd" } }
其中Terraform为运行主体,定义了Backend的操作主体。Backend的逻辑实现是存放在Terraform仓库中的,服务于所有Provider和Provisioner,因此它的运行主体是terraform ,而不是具体某个Provider。
上一节中主要讲解了关于Terraform的一些实现机制,下文中会演示从0开始构建整个整个环境。为了更快捷的编写OSS Backend模板,阿里云提供了一个Terraform Module:https://registry.terraform.io/modules/terraform-alicloud-modules/remote-backend/alicloud/latest
本文中所使用terraform版本为v.0.15.3
1. 新建工程目录,并在主目录下创建main.tf文件,内容如下:
module "remote_state" { source = "terraform-alicloud-modules/remote-backend/alicloud" create_backend_bucket = true create_ots_lock_instance = true create_ots_lock_table = true backend_ots_lock_instance = "OTS-NAME" region = "cn-hangzhou" state_name = "prod/terraform.tfstate" encrypt_state = true }
remote_state为阿里云官方已封装完成模板。使用Terraform语法关键字module可直接使用模板已定义完成的相关变量参数。需要注意的是backend_ots_lock_instance参数所对应的表格存储在同一region内,命名全局唯一,且长度要求3-16位,因此务必修改带特定标识的名称避免重名。
2. 在开始整个的执行过程之前,需要在环境变量中加载对应的阿里云ACCESS_KEY及SECRET_KEY。否则在创建对应的OSS和OTS资源时会因为权限不足无法创建成功。
3. 由于当前工程项目仅main.tf文件,因此尚无法直接加载执行该模板,还需要使用terraform get命令,从terraform官方仓库获取该模块所依赖的包文件。执行完成后如下图所示,会在当前项目目录下获取一个modules目录,对应remote_state所对应的模块。
在定义好了模板初始化参数之后,学习一下remote_state中的main.tf文件内容:
如果对terraform语法不是很懂也没有太大关系,仅需了解到这段配置定义了remote_state相关的变量参数,由图可知,默认的OSS存储桶名为terraform-remote-backend-${随机生成的uuid}。
4. 完成上述工作之后,通过执行terraform init命令获取到providers所使用到的依赖包即可。过程类似于java在做编译时去中央仓库下载依赖包的过程。由于providers目录包含200M大小的官方依赖包,故在第一次拉取编译包时间会比较长,也可能会存在拉取超时的问题。笔者的习惯是一般在创建一个新的工程时,从老的工程内将providers目录拷贝到当前工程,直接使用,从而省去不必要的时间成本。在工程内执行完teraform init命令后,便能够在当前工程中看到以下文件。其中local和random由于在modules中所使用到,因此也需要从公网上拉取。
5. 在整个工程初始化工作完成后,便可以执行terraform plan测试语法是否存在错误。若没有报错即可执行terraform apply在云环境中使用。运行完成后,会在当前目录下生成一个名为terraform.tf的配置文件,文件内容即为已经配置好的OSS Backend:
terraform { backend "oss" { bucket = "terraform-remote-backend-c9ff230f-db1b-ffd3-acdd" prefix = "env:" key = "prod/terraform.tfstate" acl = "private" region = "cn-hangzhou" encrypt = "true" tablestore_endpoint = "https://xxx-backend.cn-hangzhou.ots.aliyuncs.com" tablestore_table = "terraform_remote_backend_lock_table_c9ff230f_db1b_ffd3_acdd" } }
生成后的terraform.tf可以直接跟目标模板放在同一个目录下,以用于后续State的远端存储。如果想把生成terraform.tf的state也存放在上述的OSS Backend中,只需再次运行terraform init命令,本地目录下的local state将会自动同步到OSS Backend。
6. 最后去阿里云控制台上可以查看到所创建的相关资源,确认整个执行过程已成功完成。
OSS bucket:
OTS表格存储:
本文参考文档:
1.https://registry.terraform.io/modules/terraform-alicloud-modules/remote-backend/alicloud/latest
2.https://help.aliyun.com/document_detail/145541.html?spm=5176.10695662.1996646101.searchclickresult.466171fdtdmlbm
更多关于云计算、DevOps、SRE内容请关注公众号: