ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基础设施即代码实战:用Terraform构建标准化云资源管理

基础设施即代码实战:用Terraform构建标准化云资源管理 1. 为什么说基础设施即代码是运维的转折点做运维或者搞开发的同学一定经历过这样的阶段服务器是拿Excel表登记的Nginx配置是登录到机器上改的环境是怎么搭的只有当事人自己心里清楚。今天新加一台服务器明天手动部署一套中间件后天数据库连接串又改了几个参数一台机器一套命令环境越滚越乱。几年下来一个最简单的应用可能每个人心里都有一套大概怎么搭起来的版本。我当初接手的一个项目就是这个状态上线靠人肉、回滚靠祈祷、环境差异靠脑补每次线上出问题排查的第一步永远是有没有人记得上次改了什么。后来引入Terraform之后情况才真正开始改变。所谓基础设施即代码用一句话说就是把服务器的申请、网络的配置、数据库的创建这些原本依赖手工控制台和命令行操作的事全部用代码来描述、审查和落地。基础设施变成了像应用代码一样可以提交到Git仓库、可以走代码评审、可以版本回退的东西。Terraform是目前这个领域应用最广的开源工具由HashiCorp公司出品。它最核心的价值是能让你用统一的配置语言HCL声明你想要的最终环境状态剩下的创建、修改、删除都由Terraform去执行。你不需要再对着不同云厂商的控制台逐个点击也不用在公司内部Chaos一样的CMDB表格里挣扎一份代码几行命令整个环境就能完整落地。这篇博文适合谁看说实在的只要你正在操作云主机、想规范化部署流程、或者被多个环境之间的配置差异折磨过都值得花半小时把Terraform核心概念和工作方式摸一遍。这篇文章会带着你从零开始搭一套能用的配置把那些文档里不会细讲的选型逻辑和踩坑经验一并讲透。2. Terraform的核心概念理解HCL、Provider、Resource和State的关系2.1 HCL语法声明式配置到底在声明什么Terraform使用HCLHashiCorp Configuration Language作为配置语言这跟Python、Shell这类命令式语言有着本质区别。命令式语言表达的是怎么做你先执行A再执行B中间有判断分支、有循环逻辑而HCL表达的是最终长什么样——你描述目标状态Terraform自己决定用什么顺序去达成。这样说可能有点抽象我结合一个经典场景讲其实特别容易理解。假设你要在云上创建一台ECS云服务器resource alicloud_instance web { instance_type ecs.c6.large instance_name web-server vswitch_id alicloud_vswitch.vsw.id image_id centos_7_9_x64_20G_alibase_20241120.vhd security_groups [alicloud_security_group.sg.id] }这就是声明式。你没有写调用API去创建实例然后等待返回IP再绑定安全组你只是说我要一台这个配置的机器并且这台机器属于那个交换机。Terraform会读取你的声明对照当前实际状态生成一组操作动作然后执行。哪怕中途失败了你重新跑一次apply它也能接着把没做完的事干完。这就是期望状态带来的最直接好处。HCL的语法结构并不复杂核心就是块block和参数argument。resource、variable、provider、module这些都是块类型块内部是键值对。写多了你会发现HCL读起来比JSON友好得多嵌套关系可以用层级表达注释也跟代码一样方便这一点对团队维护特别重要。2.2 Provider机制为什么Terraform能一套语言管所有云Provider是Terraform最关键的扩展机制整个生态的繁荣都建立在它之上。每一个Provider本质上是一个插件把某个平台的API操作封装成Terraform统一的资源类型。比如阿里云有alicloud Provider、AWS有aws Provider、Kubernetes有kubernetes Provider就连本地虚拟化平台也有对应的Provider。用Terraform管理不同平台不需要切换工具只需要在配置文件里声明你要用哪些Provider然后每个资源依赖相应的Provider即可terraform { required_providers { alicloud { source aliyun/alicloud version ~ 1.220.0 } } } provider alicloud { region cn-hangzhou }这个设计带来的最大收益是心智负担大幅降低。你不需要既学云厂商的控制台操作、又学各家的CLI命令一套配置语言可以覆盖几乎全部基础设施场景。更重要的是多家云厂商的Provider接口是统一的今天用阿里云明天切到华为云你只需要改Provider声明和资源类型名称底层的设计思路和工作流完全不变。对于多云或混合云架构的团队这个统一的抽象层弥足珍贵。2.3 State文件Terraform的记账本也是安全隐患的源头State状态文件是Terraform运行时的核心数据说白了就是它的记账本。当你第一次执行terraform apply创建了一台服务器Terraform会把这台服务器的ID、IP、所属VPC等信息记录在terraform.tfstate文件里。之后每一次执行plan或apply它会读取这个文件再与真实环境做对比判断哪些资源需要新增、修改或删除。正是因为有State的存在Terraform才能做到精准操作。没有它Terraform连这台机器是不是我创建的都无法判断更别说增量更新了。但State文件也是一把双刃剑。如果它在本地磁盘上只被你一个人看到那没问题一旦多人协作它的隔离、共享、锁定就变得非常棘手。假如两个同事同时执行apply都基于同一份旧State做计划后执行的人就可能把前一个人刚创建的资源覆盖掉。所以生产环境的团队几乎无一例外会把State存放在远程后端如OSS、S3、Consul配合后端锁机制来避免并发写入。还有一点必须强调State文件里往往包含敏感信息比如资源ID、内网IP甚至某些情况下会有明文密码或密钥如果配置里没做好保护所以绝对不要把State文件提交到Git仓库。网上因为tfstate泄露导致云资源被入侵的案例比比皆是这个问题再怎么重视都不为过。3. 动手搭一套Terraform配置从环境准备到资源创建3.1 安装Terraform版本管理和二进制安装的两种姿势Terraform本身是Go语言编译的单个二进制文件安装非常简单。如果你用的是macOSbrew install hashicorp/tap/terraform一条命令搞定Linux下可以到官方下载页面直接拉二进制包解压到/usr/local/bin。不过我更推荐用tfenv这类版本管理器。为什么推荐版本管理器因为Terraform的版本升级速度很快而不同的Provider、不同的 Module 可能对Terraform版本有不同要求。如果你的机器上固定装一个最新版很可能哪天拉别人写的配置时发现语法不兼容或者Provider版本冲突。用tfenv可以随时切换全局或目录级别的Terraform版本我在项目里就常用.terraform-version文件锁定版本谁拉到代码都会自动切到相应版本从源头避免在我机器上能跑的尴尬。安装完之后先验证一下tfenv install 1.7.5 tfenv use 1.7.5 terraform version能正常输出版本号就说明环境OK了。另外Terraform CLI会在执行命令时自动下载Provider插件并缓存到本地目录~/.terraform.d/plugin-cache你不需要手动安装插件。这个设计对新手友好但团队内部如果网络受限就需要配置plugin_cache并预先填充缓存目录否则terraform init会在拉插件阶段卡很久。3.2 编写第一份配置用云主机加安全组串起初级阶段下面我以阿里云为例从零写一套一台Web服务器加一个安全组的配置这是Terraform入门最典型的场景。先建一个项目目录里面放两个文件。main.tf是主要资源定义variables.tf用来声明变量。目录结构大概是这样terraform-demo/ ├── main.tf ├── variables.tf └── terraform.tfvarsmain.tf的核心内容如下terraform { required_version 1.5 required_providers { alicloud { source aliyun/alicloud version ~ 1.220.0 } } backend oss { bucket my-project-tfstate key demo/terraform.tfstate region cn-hangzhou } } provider alicloud { region var.region } # 创建VPC resource alicloud_vpc vpc { vpc_name var.project_name cidr_block 172.16.0.0/16 } # 创建交换机 resource alicloud_vswitch vsw { vpc_id alicloud_vpc.vpc.id cidr_block 172.16.0.0/24 zone_id cn-hangzhou-b } # 创建安全组 resource alicloud_security_group sg { name ${var.project_name}-sg vpc_id alicloud_vpc.vpc.id } # 创建安全组规则只放行22端口和80端口 resource alicloud_security_group_rule allow_ssh { type ingress ip_protocol tcp nic_type intranet policy accept port_range 22/22 priority 1 cidr_ip 0.0.0.0/0 security_group_id alicloud_security_group.sg.id } resource alicloud_security_group_rule allow_http { type ingress ip_protocol tcp nic_type intranet policy accept port_range 80/80 priority 1 cidr_ip 0.0.0.0/0 security_group_id alicloud_security_group.sg.id } # 创建ECS实例 resource alicloud_instance web { instance_type ecs.c6.large image_id centos_7_9_x64_20G_alibase_20241120.vhd instance_name ${var.project_name}-web vswitch_id alicloud_vswitch.vsw.id security_groups [alicloud_security_group.sg.id] user_data -EOF #!/bin/bash yum install -y nginx systemctl start nginx EOF }variables.tf里声明我们要在运行时传入的参数variable region { description 部署区域 type string default cn-hangzhou } variable project_name { description 项目名称 type string default terraform-demo }terraform.tfvars则用来存实际值region cn-hangzhou project_name myweb这段配置里面有几个值得留意的地方。安全组规则用了cidr_ip 0.0.0.0/0意味着所有人都能访问SSH和HTTP这在真实生产环境肯定要收敛的。我这里只是为了演示方便。还有user_data它会在机器首次启动时执行一段Shell脚本把Nginx装上。这也是基础设施即代码的一个典型应用——配合云厂商的初始化机制一台机器从启动到可用整个过程不需要任何人登录操作。3.3 初始化、预览、应用必须严格遵循三步走配置写完之后所有的操作都必须按照init→plan→apply的顺序来。很多人一上来就apply大概率会得到一顿报错因为Provider插件还没装。第一步terraform init这个命令会读取你的配置确定需要哪些Provider去官方源下载它们并初始化后端配置。输出里会清楚显示Terraform has been successfully initialized。如果你配置了OSS后端它还会检查远端Bucket是否可用。到这里我想额外提醒一句初次执行init的时候网络如果不好很容易卡在下载插件上。国内访问官方源经常超时解决方案一般有两种一是配置Terraform的Provider镜像把~/.terraformrc里的plugin_cache_dir和镜源配置好二是让网络环境尽量稳定一些。团队内部如果有统一的制品库也可以自建Provider镜像。总之这一步提前做好后面会顺畅很多。第二步terraform planplan命令只做分析、不实际修改。它会读取当前State文件跟你的配置目标做比较输出一份详细的执行计划哪些资源要创建哪些要修改~哪些要删除-。这个步骤的意义非常大。我每次在真实项目里操作都会认真人肉过一遍plan的输出因为有时候资源变更的副作用可能超出预期。比如某个参数改一下Terraform可能无法原地更新只能销毁重建——如果你的数据库磁盘附在服务器上那数据就没了。这种风险在plan阶段是能提前发现的。第三步terraform apply确认计划无误后执行terraform apply。它会重新计算一次计划然后等你在交互提示里输入yes确认才开始真正创建资源。你也可以用-auto-approve参数跳过交互确认但我个人建议除非是在自动化流水线里否则不要图省事手动看一遍计划再确认多花的那几秒时间能帮你避免灾难。3.4 变量的传递方式别把所有值硬编码到文件里在上面例子里我把区域和项目名都抽成了变量这看起来有点多此一举实际项目里作用很大。比如你需要在多个环境之间切换配置用变量就能轻松做到而不需要改主配置代码。Terraform变量传递有几种方式优先级从低到高分别是环境变量TF_VAR_开头、terraform.tfvars文件、terraform.tfvars.json文件、*.auto.tfvars文件、命令行-var参数。每条规则都有它的适用场景比如密码这类敏感信息就不建议写进版本库可以通过环境变量或-var来传普通配置项放在terraform.tfvars里即可。我知道有些同学图省事会把所有值都直接写在配置里。一旦配置开始变多这种写法就会让仓库变得非常嘈杂。把可变参数抽出来主配置保持纯净等你要做多环境部署或者写自动化脚本时会体会到这个习惯的巨大好处。4. 资源生命周期从创建到更新再到销毁的底层逻辑4.1 理解Terraform的依赖图和顺序执行逻辑在第一份配置里VPC、交换机、安全组、ECS实例之间是有依赖关系的——ECS依赖交换机和安全组交换机依赖VPC。Terraform会分析这种依赖关系自动构建一张依赖图然后按图顺序执行。这也是它能自动完成资源编排的核心能力。依赖并不只是硬编码在配置里的引用关系。Terraform会追踪资源块之间的引用表达式比如alicloud_vpc.vpc.id从而知道谁必须先创建。这意味着你不需要手动指定创建顺序只要把引用关系写对剩下的交给Terraform的图执行引擎。如果引用关系写错或者缺失你可能会得到能通过的配置但实际无法创建的实例所以写配置的时候引用一定要精确清晰。4.2 更新与销毁为什么有时候Terraform会先删后建Terraform里有个很重要的概念叫就地更新in-place update和替换更新replacement。某些资源属性支持原地修改比如安全组的规则改了之后Terraform直接调用API更新但有些属性改了之后云厂商不允许直接变更Terraform就只能先创建一个新资源再删掉旧资源比如EC2换成新的实例规格类型或者某些只读字段被修改。这个逻辑通常都能在plan阶段看到。要特别警惕先删后建的情况因为如果这个资源是有状态的服务比如数据库实例、存储了用户数据的磁盘销毁重建就意味着数据丢失。在写lifecycle相关配置时可以给关键资源加上保护机制resource alicloud_instance db { # ... 配置省略 lifecycle { prevent_destroy true } }prevent_destroy true能确保在你执行terraform destroy的时候该资源会被强制阻止删除必须手动去掉这个设置才能拆除。这对生产环境数据库这种资源来说是最后一道安全防线。我见过不止一个团队因为没配置这个属性一条destroy命令下去整个测试环境的数据库被清空只能靠备份恢复。更新逻辑里还有一个值得注意的点那就是user_data的变更。很多云厂商对于实例的user_data修改是不支持热更新的必须要重启实例或者重建实例才生效。所以如果只是改了启动脚本plan输出却显示要替换实例不要觉得奇怪这是平台本身的限制。4.3 资源导入把遗落在Terraform管理之外的资产纳入体系现实情况往往是团队早期已经手工创建了大量云资源再全面引入Terraform时你不可能让所有资源重新创建一遍。这时就需要terraform import。terraform import的作用是把一个已经存在的云资源纳入到Terraform的State文件里来管理。比如你有一台手工建的服务器ID是i-abc123想要让Terraform接管它可以这样操作terraform import alicloud_instance.web i-abc123导入之后Terraform的State里就有了这个资源但你的.tf配置文件里还没有对应的resource块。接下来要做的是运行terraform plan它会报告配置和真实资源之间的差异你照着plan的输出把配置文件补写完整。这个步骤多少有些枯燥因为它需要你不断对照资源属性来补全代码但这是过渡期最稳妥的做法。需要提醒的是import在某些Provider里的支持程度不同有些资源可能没有实现导入函数这时就只能手动重建或者继续维护一个例外清单。所以我的建议是能用Terraform统一管理的尽量统一资源少的时候早点收口越拖越难。5. 模块化设计让Terraform代码从能用到好用5.1 为什么模块化是团队协作的基础就像写代码不提倡把所有逻辑塞进一个文件里一样Terraform配置也需要做抽象和复用。模块就是一组资源的集合你可以把它理解成可复用的基础设施组件。举个非常实际的例子你的团队同时维护着Web应用、消息队列、定时任务这三类服务都需要VPC、交换机、安全组、负载均衡和主机实例但它们对配置的细节需求又各不相同。如果每个服务的目录里都复制一份VPC和安全组定义那就是经典的副本地狱。哪天安全组端口调整你得改三个地方的代码漏改一个就是隐患。模块化的做法是定义一个通用的基础网络模块把VPC、交换机、安全组封装在里面暴露几个变量如vpc_cidr、environment然后各个服务去引用module base_network { source ./modules/base_network environment prod vpc_cidr 10.20.0.0/16 }模块源码只维护一份参数通过变量差异化。Terraform会自动把模块展开成其中的所有资源纳入依赖图统一管理。5.2 模块来源的三种选择本地文件、Git仓库、公共注册中心模块可以通过不同的source来引用。source的值决定Terraform去哪里拉取模块代码。最常用的三种一是本地路径引用就是上一个例子里的./modules/base_network适合同一仓库内的模块复用。二是Git仓库地址引用比如git::https://gitlab.com/devops/terraform-modules/vpc.git?refv0.2.0这种适合跨团队共享模块配合Git标签做版本管理非常方便。三是公共注册中心的模块比如Terraform Registry上现成的开源模块。我个人的经验是小团队用本地路径就好结构简单、改动直观团队一旦超过五个人模块就需要有独立的仓库和版本管理。公共注册中心的模块虽然开箱即用但要谨慎因为模块里封装了具体云服务商的API行为如果作者维护不积极版本升级很容易踩坑。引用第三方模块前最好把源码拉下来读一遍起码要知道它创建了哪些资源、有哪些默认行为。5.3 模块的输出值怎么把内部资源信息暴露给外部使用模块内部创建的资源默认对外部是不可见的。如果你需要在外面引用模块创建出的资源ID或属性就得在模块里定义output。# modules/base_network/outputs.tf output vpc_id { value alicloud_vpc.vpc.id } output vswitch_ids { value [alicloud_vswitch.vsw.id] }然后在外部调用处就能这样引用module base_network { source ./modules/base_network } resource alicloud_instance web { vswitch_id module.base_network.vswitch_ids[0] }输出值有点像代码里函数的返回值合理使用可以让模块之间的衔接更清晰。但要注意一点输出值会暴露模块内部信息当你使用公共模块时要关注模块的输出内容是否存在敏感泄露建议配合output的sensitive参数使用。5.4 编写模块时应该避开的3个常见误区模块化虽然好但我也见过不少团队把它用歪了。最典型的三个误区我一个个说。第一个误区是过度抽象。明明整个项目里只有一个地方用到的资源也非要做成模块结果抽象层次越叠越多调用关系和变量传递变得晦涩难懂。正确做法是先让代码在具体场景里跑通等真的出现第二个使用方再考虑抽模块。第二个误区是把环境差异写在模块内部。有些模块会在里面用var.environment做各种条件分支导致模块内部逻辑极其复杂。更好的做法核心流程保持一致环境之间的差异通过变量传入而不是把分支逻辑散落在模块内部。第三个误区是模块不做版本管理。用本地路径引用的模块没有版本概念改了就改依赖它的配置可能悄悄行为变化等到发现时环境已经出了偏差。如果你在写可持续演进的模块建议把版本号或Git标签纳入引用规范。6. 多环境管理与后端配置从单机玩到生产级部署6.1 基于目录结构切分环境dev、staging、prod分开管理环境管理是多环境部署的核心难题。我们日常工作中至少会有开发环境、测试环境、预发布环境和生产环境它们之间需要的资源规格不同配置参数也不同。用Terraform管理这些环境最常用的做法是目录结构切分。terraform-repo/ ├── environments/ │ ├── dev/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── terraform.tfvars │ ├── staging/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── terraform.tfvars │ └── prod/ │ ├── main.tf │ ├── variables.tf │ └── terraform.tfvars不同环境的相同部分可以抽成公共模块各环境目录只负责传入自己的参数和特有的资源配置。这样每个环境都有独立的State文件互不干扰执行apply时只需要进入对应环境的目录跑命令就能对指定环境进行变更。这个模式的优点是简单清晰任何人看到一个环境目录就明白它是干什么的。缺点是如果环境数量特别多目录会显得重复但这在实际操作中的体验已经足够好了。更复杂的方案是使用Terraform Workspace做环境隔离但有得必有失Workspace虽然能复用同一套配置State管理却容易混乱。我个人在团队里更倾向于目录化因为它足够直观不需要额外的心智负担。6.2 远程State后端别把State文件放在本地了前面提到了State文件的重要性这里展开讲一下远程State的配置和选型。Terraform支持多种后端Backend本地local、OSS、S3、Consul、Azure Storage、GCS等。选择后端主要看两件事一是你要用的云厂商是哪家配套的对象存储自然是最顺手的选择二是团队协作是否需要锁机制这个非常关键。以阿里云为例常用OSS后端加上的锁机制能避免多人同时apply的冲突。OSS后端的配置很简单terraform { backend oss { bucket my-company-tfstate key prod/network/terraform.tfstate region cn-hangzhou # 如果要启用State文件加密 encrypt true } }配置好之后每次init时Terraform会自动把本地State迁移到远端此后你执行plan和apply都会先读取远端State。远程后端相当于给团队提供了一个集中的、可靠的记账本状态不再依赖某一个人的电脑。还需要强调一句OSS桶本身应开启版本控制这样即使误操作导致State文件损坏或误删还有机会从历史版本中恢复。同时要给OSS设置合理权限只让运维同学有读写权限其他人只读甚至不可见。State文件是一个宝藏也是一个火药桶访问控制必须到位。6.3 State文件里发生了什么几次并发事故带来的教训我经历过一次挺有意思的事故。那会儿刚把State迁到远端OSS但后端配置里没有启用锁功能。结果两个同事几乎同时跑了apply一个在创建安全组规则一个在调整ECS实例规格。等他们都跑完State文件已经出现了互相覆盖的情况实际环境里资源都在但Terraform的State里有一部分记录消失了。之后再跑planTerraform认为安全组规则不存在了生成了一批看似要重建资源的计划可资源实际还在。我们只好手工核对所有资源ID用terraform state rm和terraform import把丢失的记录恢复回来。处理完这个烂摊子得出的结论很简单生产环境必须开启后端的锁机制。不管是OSS配合函数计算的组件还是Consul的锁机制总之没有锁的State就是一盘散沙。另外建议定期对State做备份。有些团队会把State文件同步到另一个存储桶或者下载到本地归档以防最坏情况发生。State文件的大小通常不会很大多存几份完全不吃亏。7. 进阶技巧从会用Terraform到用得好Terraform7.1 多个子命令配合使用的日常操作清单Terraform全家桶里除了init、plan、apply、destroy四个高频命令还有几个在特定场景下非常好用的子命令。我梳理了一个实用清单。terraform validate只校验语法和配置结构不访问远端State和Provider。适合在本地写完配置快速自查或者集成到CI流程里做合规检查。terraform fmt自动格式化HCL代码让团队风格统一。提交代码前跑一遍能省下不少代码评审时的格式争论。terraform state list和terraform state show查看State里管理的资源列表以及某个资源的详细信息。排查资源是不是被Terraform管理时很有用。terraform output查看配置里定义的输出值方便直接把IP、ID等信息取出来用。terraform graph生成依赖图的DOT格式描述可视化资源之间的依赖关系。当配置复杂到看不出先后顺序时这个命令能帮你理清思路。terraform taint和terraform untaint标记某个资源需要重建。比如一台机器坏了你不想写一堆复杂的变更逻辑只需把它标记为污染下次apply时Terraform就会强制替换它。这些命令不用全背但要知道它们的存在。真正遇到对应场景时能想起来用哪个就是效率和经验的差别。7.2 使用data数据源读取已有资源data数据源是Terraform里容易被忽视但非常实用的功能。它允许你在配置里读取云上已经存在的资源属性这些资源可能不是你Terraform创建的但你需要引用它们的信息来配合新资源的创建。举个例子你想在已有的VPC里创建一个新的交换机但你不知道VPC的ID或者不想在配置里硬编码。这时候可以用数据源查询data alicloud_vpcs existing { vpc_name legacy-vpc } resource alicloud_vswitch new_vsw { vpc_id data.alicloud_vpcs.existing.vpcs[0].id zone_id cn-hangzhou-b cidr_block 172.16.1.0/24 }数据源还有一个很常见的用途就是查询镜像、实例规格等元数据。例如根据地域动态获取可用的镜像ID而不用每次换区域都要去控制台翻找。通过数据源可以把Terraform配置和真实世界已有的基础设施打通。7.3 敏感信息的处理杜绝在代码里写明文密码基础设施的管理绕不开密码和密钥。但Terraform配置文件通常要进Git仓库如果里面写了明文密码那等于把钥匙插在门上还拍了张照发到群里。处理敏感信息有几条基本规则第一绝对不要把静态密码写在资源定义里。如果你的数据库密码在配置里写死了先改掉再说。第二秘密值应该通过变量传入并配合variable块的sensitive true标记variable db_password { type string sensitive true }这样在terraform plan的输出中该变量的值会被脱敏显示。但要注意这只影响CLI输出State文件里依然会以明文存储。如果你用的是远程State务必开启后端加密。即便开启了后端加密也要控制State文件的访问范围我们前面已经反复强调过。第三动态获取密钥可以借助云厂商的密钥管理服务。比如AWS的Secrets Manager、阿里云的KMSTerraform通过数据源读取执行时动态取值不落盘不落地。这里还涉及一个日常习惯Terraform的执行日志。有些云厂商Provider会在DEBUG模式下打印详细请求内容里面可能包含敏感字段。排查问题时可以临时设置TF_LOGDEBUG但排查完要立刻关掉别让日志默默留在CI里。7.4 用plan和apply实现CI/CD一体化Terraform在流水线中的实践Terraform和CI/CD结合能够实现传说中的基础设施自动交付。基本的流水线设计是代码变更合并到主干后CI自动跑terraform fmt -check、terraform validate然后执行terraform plan把计划输出到流水线日志里人工评审通过后再触发terraform apply -auto-approve。这样做最大的好处是所有基础设施变都有迹可循且必须经过代码评审。生产环境的修改不再是某个人远程敲命令的结果而是有记录、有审批、有审计的正式变更。对需要合规审计的团队这一点几乎是刚需。流水线中我建议把terraform plan的产出物做一个归档存成文件。这样每次变更的内容都有留存事后追溯问题时可以回放当时的执行计划。还有一点apply这步务必加上锁机制后端的锁防止流水线与人操作并行冲突。自动化很好但不能给混乱提供更快发生的机会。8. 常见问题和踩坑经验这些坑我替你提前踩了8.1 高频报错及排查思路速查表我根据实际使用经验整理了一份Terraform常见报错的排查对照新手遇到问题时可以先对着查一遍。报错现象根本原因排查与修复Missing required argument配置里缺少资源必需的参数看文档确认资源的必填参数云厂商的OpenAPI文档也有帮助Insufficient or invalid fields参数格式不对或用了旧版本字段检查当前版本的Provider文档旧字段可能已被废弃Error acquiring the state lock后端锁被其他进程持有一般等数分钟自动释放如果长时间不释放手动到State后端删除锁记录前先确认没有apply在跑Provider produced inconsistent final planProvider的计算结果和实际请求不一致多为Provider版本过旧或缓存异常升级Provider版本并重新initResource already exists资源实际存在但State未记录用terraform import把资源纳入StateBackend initialization failed后端配置错误或网络不通检查Bucket是否存在、权限是否匹配、密钥是否正确这里想多说一句很多报错看着吓人其实最笨也最有效的排查方法就是打开TF_LOGDEBUG重跑一次命令看日志里最后一次API请求的参数和响应。大多数问题在那个时刻就真相大白了。8.2 destroy运行前后需要做好的安全检查清单terraform destroy大概是Terraform里最危险的命令一条命令说完整片环境可能就灰飞烟灭。我每次执行销毁之前都会过一遍自己总结出来的检查清单确认当前目录的State是不是目标环境的State尤其在不同环境的目录结构下千万别在prod目录里敲出了针对某个State的destroy。检查是否有生产资源混在这个State里比如共享数据库、消息队列、对象存储桶。如果有先配置prevent_destroy或把它们从当前State中排除。确认备份或数据导出已经完成。数据库要dump快照文件存储要同步到归档。检查是否有其他团队或系统仍然依赖这套资源。比如某些资源的内网IP被其他服务的配置引用销毁后可能导致新的故障。先在plan阶段查看将要删除的资源列表再决定是否执行。destroy本质上是apply的一个特例计划完全可以用terraform plan -destroy来预览。8.3 团队协作中的三个共识创可贴我对Terraform团队协作最大的感悟是工具的上限不取决于工具本身取决于团队的约定是否清晰。哪怕技术再强大如果每个人都有自己的一套用法最后还是可能一片混乱。第一个共识是State必须远程存储且统一。谁也别在本地维护一份自己的State副本不要给个人电脑上的State文件特殊照顾。要是有人觉得本地开发方便悄悄改用local后端那等于把团队协同的根基挖掉一块。第二个共识是所有云资源必须通过Terraform变更。有些同事遇到紧急问题直接登录控制台修了一把资源状态就和State产生了偏差之后Terraform可能误判。这里倒不是说绝对禁止控制台操作而是说紧急修改后要及时用terraform import或terraform plan的diff把State和实际环境对齐避免差异越来越大。第三个共识是模块代码的改动要像应用代码一样走评审。模块涉及面广一个参数的变化可能影响所有引用的项目。随意改模块比随意改应用代码的风险大得多。这个约定一开始可能会让人觉得繁琐但实践久了你会发现这是避免返工的铠甲。8.4 从零到一的落地建议先小后大先边缘后核心如果你们团队是第一次在生产环境中引入Terraform我个人建议千万别一上来就试图把全部资源纳管。这变革做过头了会非常痛苦项目停滞的风险很高。更稳妥的方式是从一个风险较低的新项目开始比如把某个测试环境的Kubernetes集群、或者某个无状态应用的主机编排全管起来。跑顺了之后再逐步把周边的网络、存储、数据库等资源纳入管理。人手一个项目同时铺开效果常常不如集中火力把一个项目做成标杆。有一个具体的参考路径先建一个网络模块管理VPC和交换机然后建一个计算模块管理ECS和伸缩组再把数据库模块做起来每一步都经过不同的环境验证。延展速度取决于团队的信心而信心是从一个个跑通的场景积累起来的。9. 最后想分享的一点经验写Terraform这件事看起来只是换了一种管理云资源的方式但真正改变的是整个团队对变更的态度。以前改配置靠口头沟通和历史经验现在改配置靠代码评审和计划预览以前环境出了问题要人肉找原因现在可以回放任何一次执行计划追溯每一步的变化。这个转变的背后不只是工具升级而是工作方式从人治走向代码治理。我在实际使用中最大的感受是Terraform入门其实不难难的是持续地克制自己抄近路的冲动。不要因为紧急就绕过plan直接apply不要为了省事就把敏感信息写进仓库不要因为资源只有一台就觉得State不需要远程管理。这些看似很小的自我要求才是整个体系的基石。最后再分享一个小技巧如果你的配置文件里有多个环境共用的部分可以用terraform workspace做轻量切换或者更简单地用-var-file指定不同环境的tfvars文件terraform apply -var-fileenvironments/dev.tfvars这样不用进入不同目录也不用复制配置一份代码配合多个变量文件也能优雅地管理环境差异。找到最适合你团队的那条路把它跑通、跑稳就是这套工具链带给你最大的回报。
返回列表