
1. 为什么我最终选择了 Terraform 来管理云主机第一次接触云主机管理的时候我和大多数人一样登录控制台点几下鼠标选个镜像、挑个配置、确认订单一台机器就开出来了。三五台的时候这么干没问题但当环境一多、项目一杂问题就来了测试环境开了一台机器忘了关月底账单多出几百块生产环境的配置和测试环境不一致排查半天发现是安全组规则少了一条想把整套环境复制一份给新同事用只能靠截图和文档对方照着做还是各种踩坑。这些事经历几次之后我就开始认真考虑用代码来管理基础设施了。Terraform 就是解决这类问题的工具。它的核心思路叫基础设施即代码Infrastructure as Code简称 IaC说白了就是把我要几台机器、什么配置、什么网络这些信息写进文本文件里然后让工具去执行。这个文本文件可以提交到代码仓库、可以做版本对比、可以复制给任何人环境的一致性就有了保障。我这次要做的就是用 Terraform 在腾讯云上创建 CVMCloud Virtual Machine云服务器把整个流程从零跑通一遍。这篇文章适合谁看如果你手上有几台云主机正在被配置不一致、环境难复制、账单失控这些问题困扰那这篇内容就是写给你的。如果你完全没接触过 Terraform也没关系我会从安装开始一步步讲把每个参数为什么这么填都说清楚。整个过程我会用腾讯云作为示例平台因为它的 provider 文档比较完整国内访问速度也稳定适合作为入门练手。需要提前说明的是Terraform 本身是平台无关的学会之后换成别的云平台思路完全一样只是 provider 和资源名称不同。所以这篇内容的价值不只是教你开一台腾讯云主机而是帮你建立一套用代码管理云资源的思维方式。2. Terraform 核心概念与工作原理拆解2.1 Terraform 到底在做什么要理解 Terraform先要理解它和普通脚本的区别。很多人第一反应是我用 shell 脚本调 API 不也能创建机器吗确实可以但脚本有个致命问题它只管创建不管状态。你跑一遍脚本创建了一台机器再跑一遍又创建一台它不知道哪些资源已经存在、哪些需要更新、哪些需要删除。Terraform 的核心价值就在于它维护了一份状态文件state file记录了当前基础设施的真实情况每次执行时它会对比你想要的和现有的然后计算出需要做哪些操作。这个对比过程叫plan执行过程叫apply。你可以把 Terraform 想象成一个特别靠谱的装修队长你给他一张设计图配置文件他先去现场看一遍refresh然后告诉你这面墙要拆、那个插座要加plan你确认没问题了他才动手apply。这个先看再报再动手的流程是 Terraform 相比裸脚本最大的优势也是它能在生产环境放心使用的原因。Terraform 的工作流程可以概括为几个关键命令我先把它们列出来后面会逐个展开命令作用使用时机terraform init初始化工作目录下载 provider 插件每次新建目录或修改 provider 配置后terraform plan预览将要执行的操作每次 apply 之前terraform apply实际执行变更确认 plan 无误后terraform destroy销毁所有管理的资源环境不再需要时terraform fmt格式化配置文件提交代码前terraform validate校验配置语法提交代码前2.2 Provider 机制Terraform 的翻译官Terraform 本身其实不认识腾讯云、也不认识任何云平台。它之所以能操作各种云资源靠的是provider提供者。Provider 是一个插件它把 Terraform 的通用指令翻译成具体云平台的 API 调用。比如你写resource tencentcloud_instance腾讯云的 provider 就知道要去调创建 CVM 的接口。这个设计非常巧妙。Terraform 核心团队只需要维护核心逻辑各个云厂商自己维护 provider这样新服务上线时厂商更新 provider 就行不用等 Terraform 发版。目前主流的云平台都有自己的官方 provider腾讯云的是tencentcloudstack/tencentcloud在 Terraform Registry 上可以查到完整的资源文档。Provider 的版本管理是个容易被忽视但很重要的点。我踩过的坑是本地用了一个版本的 provider 跑通了同事那边装了另一个版本结果某些参数行为不一致plan 出来的结果对不上。所以我的习惯是在配置文件里锁定 provider 版本用version参数指定一个范围避免不同人执行结果不一致。2.3 状态文件Terraform 的记忆状态文件默认叫terraform.tfstate是 Terraform 最核心也最容易被误解的部分。它是一份 JSON 文件记录了 Terraform 管理的所有资源的 ID、属性、依赖关系。每次 plan 和 apply 时Terraform 都会读取这份文件和配置文件对比和云端真实状态对比三方比对后才能算出正确的操作。这里有个新手常犯的错误把状态文件当成配置文件去手动编辑。绝对不要这么做。状态文件是 Terraform 自己维护的手动改会导致状态和现实不一致后续操作可能误删资源。如果确实需要调整应该用terraform state系列命令比如terraform state mv、terraform state rm这些命令会安全地修改状态。另一个关键问题是状态文件的存储位置。默认它存在本地这在个人练习时没问题但团队协作时就是灾难两个人各自持有状态文件互相覆盖资源就乱了。生产环境的做法是把状态文件放到远程后端backend比如对象存储所有人共享同一份状态并且支持状态锁定防止并发操作冲突。腾讯云的对象存储COS就支持作为 Terraform 的 backend配置方式后面会讲。2.4 配置文件的基本结构Terraform 的配置文件用 HCLHashiCorp Configuration Language编写后缀是.tf。它的语法比 JSON 友好支持注释、变量、表达式。一个典型的配置文件包含几个部分terraform 块声明需要的 provider 和版本provider 块配置 provider 的参数比如密钥、地域resource 块定义要创建的资源variable 块定义可变的输入参数output 块定义执行后要输出的信息这种分块设计让配置很清晰。我个人的习惯是把 provider 配置、变量定义、资源定义分到不同文件里比如provider.tf、variables.tf、main.tf、outputs.tf。Terraform 会自动加载目录下所有.tf文件所以文件怎么分不影响执行只影响可读性。对于稍微复杂一点的项目分文件是必须的否则一个几百行的文件找起来很痛苦。3. 环境准备与腾讯云 Provider 配置实操3.1 安装 Terraform 与验证安装 Terraform 最简单的方式是去官网下载对应系统的二进制包解压后把可执行文件放到 PATH 里。以 Linux 为例大致流程是下载压缩包、解压、移动到/usr/local/bin然后执行terraform version验证。macOS 用户可以用 Homebrew一条brew install terraform就搞定。Windows 用户下载 exe 后加到环境变量即可。安装完成后第一件事是确认版本。我建议用 1.0 以上的版本因为 0.12 之前的语法和现在差异很大网上很多老教程还在用旧语法容易混淆。执行terraform version会输出当前版本和最新的 provider 版本提示这个提示很有用能帮你判断是否需要升级。提示不要用包管理器里那些来路不明的 Terraform 包版本可能很旧。认准官方发布的二进制文件或者用官方推荐的包管理方式。3.2 获取腾讯云访问凭证Terraform 要操作腾讯云资源必须有访问凭证。腾讯云用的是 SecretId 和 SecretKey 这一对密钥在控制台的访问管理里可以创建。创建时要注意几点一是这对密钥的权限范围建议遵循最小权限原则只授予需要的权限不要图省事用主账号的密钥二是密钥只会在创建时显示一次 SecretKey一定要当场保存好关掉页面就再也看不到了。拿到密钥后怎么传给 Terraform 是个安全问题。最不推荐的做法是直接写在配置文件里因为配置文件通常要提交到代码仓库密钥就泄露了。我常用的方式有两种一是用环境变量执行前export TENCENTCLOUD_SECRET_IDxxx和export TENCENTCLOUD_SECRET_KEYxxxprovider 会自动读取二是用.tfvars文件存变量然后把这个文件加到.gitignore里不提交。环境变量的方式最省事适合本地开发。但要注意环境变量在同一个终端会话里才有效换个终端就没了。如果你经常需要切换不同的账号可以写个小脚本切换时 source 一下。团队协作时更规范的做法是把密钥放到密钥管理服务里执行时动态获取不过这就属于进阶话题了。3.3 编写 provider 配置Provider 配置块告诉 Terraform 用哪个 provider、什么版本、什么地域。腾讯云 provider 的基本写法是这样terraform { required_providers { tencentcloud { source tencentcloudstack/tencentcloud version ~ 1.81.0 } } } provider tencentcloud { region ap-guangzhou }这里source指定 provider 的来源地址version用~表示允许补丁版本升级但不跨小版本这样既能拿到 bug 修复又不会因为大版本变更导致行为不一致。region指定资源创建的地域广州、上海、北京都是常用选择选离用户近的延迟低。地域这个参数有个细节provider 级别的地域是默认值单个资源也可以覆盖它。比如你大部分资源在广州但有一台机器想放上海可以在那个 resource 里单独指定availability_zone。不过跨地域的资源之间内网不通除非用对等连接所以一般一个项目固定一个地域比较省心。3.4 执行 terraform init 的背后逻辑配置写好后第一步是terraform init。这个命令做几件事扫描配置文件找出需要的 provider从 Registry 下载对应的插件到.terraform目录初始化 backend下载模块。第一次执行会看到下载进度之后如果 provider 没变再执行会很快因为它会检查本地缓存。init有个常见报错是网络问题导致下载失败。国内访问 Terraform Registry 有时会慢可以配置镜像源加速。另一个报错是 provider 版本冲突比如两个模块依赖了不兼容的版本这时需要调整版本约束。init 成功后目录下会多出.terraform文件夹和.terraform.lock.hcl文件后者记录了实际使用的 provider 版本建议提交到代码仓库这样团队所有人用的版本完全一致。注意.terraform目录不要提交到仓库它包含下载的二进制文件体积大且和平台相关。.terraform.lock.hcl则应该提交它是版本锁定的关键。4. 用 Terraform 创建 CVM 的完整实操4.1 定义变量让配置更灵活在写资源之前我习惯先把可变的部分抽成变量。这样同一套配置可以通过不同的变量值创建不同环境不用改代码。变量定义在variables.tf里variable instance_name { description CVM 实例名称 type string default tf-demo-cvm } variable instance_type { description 实例规格 type string default S5.MEDIUM4 } variable image_id { description 镜像 ID type string default img-9qabwvbn } variable password { description 实例密码 type string sensitive true }这里sensitive true很重要它会让 Terraform 在输出时隐藏这个值避免密码出现在日志里。密码这种敏感信息我一般通过TF_VAR_password环境变量传入或者用.tfvars文件配合 gitignore绝不硬编码。实例规格的选择有个经验S5.MEDIUM4表示 S5 系列、2 核 4G。腾讯云的规格命名规则是系列.规格数字部分通常第一位是核数相关具体要查文档。选规格时不要只看核数和内存还要看是标准型、计算型还是内存型不同系列适用的场景不同。跑 Web 服务标准型够用跑数据库要选内存型跑计算密集任务选计算型。4.2 编写 CVM 资源定义核心的资源定义在main.tf里。创建一台 CVM 需要指定几个关键参数可用区、实例规格、镜像、系统盘、网络、安全组、登录方式。下面是一个完整的例子resource tencentcloud_instance demo { instance_name var.instance_name availability_zone ap-guangzhou-6 instance_type var.instance_type image_id var.image_id system_disk_type CLOUD_PREMIUM system_disk_size 50 allocate_public_ip true internet_max_bandwidth_out 5 security_groups [tencentcloud_security_group.demo.id] password var.password tags { Environment demo ManagedBy terraform } }逐个参数说下我的理解。availability_zone是可用区同一个地域下有多个可用区选哪个影响不大但要注意某些规格不是所有可用区都有。system_disk_type里CLOUD_PREMIUM是高性能云硬盘还有CLOUD_SSD和CLOUD_BASIC性能依次递减价格也递减。系统盘 50G 是起步跑 Docker 或者装的东西多的话建议 100G 起。allocate_public_ip true表示分配公网 IP这个参数很关键。如果不分配机器只有内网 IP外网访问不了。internet_max_bandwidth_out是公网出带宽单位 Mbps5 是最低档按流量计费的话这个值影响不大但按带宽计费时就是实际带宽上限。带宽计费方式在创建时如果不指定默认是按流量适合流量小的场景。security_groups引用了一个安全组资源这个安全组也要在同一个配置里定义。安全组是云主机的防火墙不配置的话默认拒绝所有入站机器创建出来也连不上。安全组的定义后面单独讲。tags是标签强烈建议加上。标签能帮你按项目、环境、负责人分类资源账单分析、批量操作时特别有用。我见过太多人资源开了一堆最后分不清哪台是干嘛的只能靠 IP 猜。加上ManagedBy terraform这个标签还能一眼看出哪些资源是 Terraform 管的避免手动误删。4.3 安全组配置别让机器裸奔安全组是云主机的第一道防线。默认安全组通常只开了 22 端口Linux SSH其他都关着。如果你要跑 Web 服务得手动放行 80、443。安全组的配置逻辑是先定义安全组再定义规则规则里指定方向、协议、端口、来源。resource tencentcloud_security_group demo { name tf-demo-sg description Terraform 示例安全组 } resource tencentcloud_security_group_rule ssh { security_group_id tencentcloud_security_group.demo.id type ingress protocol tcp port_range 22 cidr_ip 0.0.0.0/0 description SSH 访问 }这里cidr_ip 0.0.0.0/0表示允许所有 IP 访问 22 端口。这在生产环境是极其危险的等于把 SSH 端口暴露给全世界会被暴力破解。正确做法是限制成你自己的办公网 IP 段比如1.2.3.0/24。如果 IP 不固定可以用跳板机或者密钥登录加端口改非标准值来降低风险。提示安全组规则的方向ingress是入站egress是出站。默认出站全放行一般不用改。入站规则要按需最小化开放能不开的端口坚决不开。4.4 执行 plan 与 apply 的完整过程配置写好后执行顺序是terraform init→terraform plan→terraform apply。init 前面讲过重点说 plan 和 apply。terraform plan会输出一个执行计划用表示新增、-表示删除、~表示修改。第一次执行时你会看到所有资源都是因为都是新建。仔细看这个计划确认要创建的资源数量、规格、配置都对。plan 的输出最后会有一行汇总比如 Plan: 3 to add, 0 to change, 0 to destroy.这个数字要和你预期一致。确认无误后执行terraform apply它会再次显示计划并让你输入yes确认。这个二次确认是防止误操作的重要机制不要用-auto-approve跳过除非是在自动化流水线里。apply 过程中会实时输出每个资源的创建进度创建 CVM 通常需要一两分钟因为要分配资源、初始化系统。apply 完成后Terraform 会把新创建的资源信息写入状态文件。这时候你可以用terraform output查看定义的输出或者直接去控制台确认机器是否创建成功。我习惯在outputs.tf里输出公网 IP方便后续连接output public_ip { description CVM 公网 IP value tencentcloud_instance.demo.public_ip }4.5 验证与连接测试机器创建出来后第一件事是验证能不能连上。用ssh root公网IP密码就是变量里传的那个。如果连不上按这个顺序排查安全组有没有放行 22 端口、公网 IP 是否分配、机器是否还在初始化中刚创建的前几十秒可能 SSH 服务还没起来。连上之后可以跑几个命令确认机器状态uname -a看系统版本、df -h看磁盘、free -h看内存。这些信息和你配置里指定的规格对比一下确认没有偏差。如果发现规格不对可能是镜像和规格不兼容Terraform 会自动选一个可用的这种情况 plan 阶段会有提示。5. 状态管理与团队协作的进阶实践5.1 远程后端配置本地状态文件在个人练习时够用但一旦涉及团队协作或者多台机器操作就必须换成远程后端。腾讯云的对象存储COS可以作为 Terraform 的 backend配置方式是在terraform块里加 backend 配置terraform { backend cos { bucket my-terraform-state-1250000000 region ap-guangzhou prefix cvm-demo/terraform.tfstate } }配置好后执行terraform initTerraform 会提示是否迁移本地状态到远程选 yes 即可。之后所有状态操作都走远程多人协作时状态是共享的还支持锁定防止两个人同时 apply 导致冲突。这里有个坑backend 配置里不能使用变量必须是硬编码的值。这意味着不同环境要用不同的 backend 配置通常的做法是用-backend-config参数在执行时传入或者用不同的目录隔离。我一般按环境分目录每个目录一套配置简单直接。5.2 状态文件的日常维护状态文件虽然不用手动编辑但有些维护操作是必须会的。比如某个资源是手动在控制台创建的现在想纳入 Terraform 管理可以用terraform import命令把它的 ID 导入状态。导入后还要在配置文件里补上对应的 resource 定义否则下次 plan 会认为要删除它。另一个常见操作是terraform state list查看当前管理的所有资源terraform state show 资源地址查看某个资源的详细属性。当 plan 结果和预期不符时这两个命令能帮你快速定位问题。比如你明明改了配置但 plan 显示没变化可能是资源地址写错了state list 一看就知道。注意状态文件包含敏感信息比如密码、密钥所以远程后端的存储桶权限要严格控制只给需要的人访问。本地状态文件也不要随便发给别人。5.3 用模块组织复杂配置当资源多起来之后把所有东西写在一个目录里会变得难以维护。Terraform 的模块module机制可以把一组相关资源打包通过输入输出参数复用。比如你可以写一个标准 Web 服务器模块输入是规格和数量输出是 IP 列表然后在不同项目里调用。模块的调用方式是在配置里写module块指定模块来源本地路径或 Registry和输入变量。模块化之后创建一套完整环境可能只需要十几行配置大大提升了复用性。不过模块也不是越多越好过度抽象会让配置变得难懂我的经验是重复三次以上的配置才值得抽成模块否则直接写更直观。6. 常见问题排查与避坑经验6.1 创建失败的典型原因CVM 创建失败的原因五花八门我整理了几个高频的报错信息原因解决方法InvalidImageId镜像 ID 不存在或地域不匹配确认镜像 ID 和地域一致InvalidInstanceType规格在该可用区不可用换可用区或换规格InsufficientBalance账户余额不足充值ResourceInsufficient该可用区资源售罄换可用区InvalidPassword密码不符合复杂度要求用大小写字母数字符号8位以上密码这个坑我踩过。腾讯云对密码有复杂度要求太简单的会被拒。而且密码里如果有特殊字符在 shell 里传环境变量时可能被转义导致实际传进去的和预期的不一样。我的做法是生成一个符合要求的随机密码避免手动设置。6.2 plan 结果异常的排查思路plan 结果和预期不符通常有几个原因。一是状态漂移就是有人在控制台手动改了资源导致状态文件和现实不一致。这时 plan 会显示要把资源改回去。解决方法是先terraform refresh同步状态再决定是接受手动改动还是让 Terraform 覆盖。二是依赖关系问题。Terraform 会自动分析资源依赖但有时候隐式依赖识别不出来导致创建顺序错误。比如安全组规则依赖安全组如果没写对引用可能规则先创建然后失败。解决方法是显式用depends_on声明依赖。三是provider 版本差异。前面提过不同版本的 provider 对同一参数的处理可能不同。团队协作时锁定版本能避免这个问题。6.3 销毁资源的正确姿势terraform destroy会删除配置里定义的所有资源这个操作不可逆执行前一定要确认。我见过有人想销毁测试环境结果在错误的目录执行把生产环境删了。避免这种事故的方法一是执行前先terraform plan -destroy看看要删什么二是给生产环境的状态文件加保护比如用不同的 backend 和严格的权限控制。销毁时还有个细节有些资源有删除保护比如开启了删除保护的数据库直接 destroy 会失败。需要先在配置里关掉保护apply 一次再 destroy。CVM 一般没有这个限制但如果有挂载的云硬盘要注意数据备份。6.4 成本控制的实用技巧用 Terraform 管理资源后成本控制变得更容易因为所有资源都在代码里一目了然。我的几个习惯一是给所有资源打上Environment标签月底按标签分析账单能清楚看到每个环境花了多少二是测试环境用按量计费不用了就 destroy避免闲置浪费三是定期跑terraform plan如果显示有资源要创建但你不知道说明有人手动改了东西及时处理。还有个技巧是用terraform state把不再管理的资源移出状态但不删除。比如某台机器要转交给别人管理用terraform state rm把它从状态里移除Terraform 就不再管它了机器本身还在运行。这个操作要小心移除后 Terraform 就忘记它了后续不会再对它做任何操作。7. 我在这套流程里踩过的坑和总结的经验回过头看从第一次用 Terraform 到现在踩的坑主要集中在几个方面。最开始是密钥管理图省事把 SecretKey 写在了配置文件里后来意识到要提交代码赶紧改成环境变量还好没造成损失。这件事让我养成了习惯任何配置在写之前先想清楚它会不会进仓库会进仓库的绝不写敏感信息。然后是状态文件。早期不懂远程后端和同事各自维护状态结果两边都 apply 了一次创建出两套资源账单翻倍。后来统一用 COS 做后端问题才解决。这个教训是团队协作的基础设施代码状态管理必须一开始就规划好事后迁移很麻烦。再就是安全组。刚开始为了图方便入站规则直接开0.0.0.0/0全端口结果机器被扫日志里全是暴力破解记录。后来改成只开必要端口SSH 限制来源 IP世界清净了。安全这件事省事的做法往往是最危险的。最后说个正向的经验把 Terraform 配置纳入代码审查。每次修改配置都走 PR 流程让同事看一眼 plan 结果能发现很多自己忽略的问题。比如有人不小心把实例规格改大了plan 里会显示要重建机器审查时就能拦下来。这套流程跑顺之后基础设施的变更变得可控多了再也不会出现谁把生产环境改了这种扯皮的事。如果你刚开始用 Terraform我的建议是先用它管理一两个非关键资源把 init、plan、apply、destroy 这套流程跑熟理解状态文件的作用再逐步扩大范围。别一上来就把生产环境全托管那样出问题的代价太大。等这套工具用顺手了你会发现它带来的确定性和可复现性是手动操作永远给不了的。