《Terraform 101 从入门到实践》本手册在南瓜慢话官网和GitHub上同步更新。书中的示例代码也放在了GitHub上,供大家参考。兵书共十二卷,每卷上都有主人的名字。为什么需要状态管理Terraform的主要功能是管理云平台上的资源,通过声明式的HCL配置来映射资源。如果云平台上没有资源,则需要创建,如果有,则不使用。Terraform有很多方法可以实现这个功能。一种是每次执行apply命令都会调用API接口,检查远程云资源是否与配置文件一致。如果没有,请创建它。如果存在但不同,则需要修改。如果存在且相同,则无需更改。该机制可以保证云平台的资源与HCL配置一致。劣势也非常明显。每次都需要调用API查看远程资源,效率很低,尤其是资源很多的时候。另一种方式是每次资源变化时创建一个映射文件,保存云平台资源的状态。这样每次执行apply命令时,只需要检查HCL配置和映射文件的区别即可。Terraform选择了第二种方式,通过映射文件来保存资源状态,在Terraform世界中称为状态文件。Terraform这样做基于以下考虑:云平台真实状态的映射,通过解析状态文件可以知道真实情况。元数据存储,比如资源之间的依赖关系,需要通过依赖关系知道创建或销毁的顺序。为了提高性能,尤其是在大型云平台上,多次调用API查询资源状态非常耗时。同步状态,通过远程状态文件同步状态,这也是Terraform的最佳实践。说到这里,我已经回答了第一章留下的思考问题:再次执行apply会不会重新创建一个文件?或者创建失败是因为文件已经存在?为什么?答:不会创建,因为变化是通过状态文件记录的,Terraform判断不再需要创建。状态管理示例为了更加关注状态管理,我们还是使用最简单的示例local_file,具体代码如下:resource"local_file""terraform-introduction"{content="https://www.pkslow.com"filename="${path.root}/terraform-guides-by-pkslow.txt"}我们用实际操作和现象来解释状态文件的作用和工作原理:操作现象和描述terraformapply生成资源:第一timeGeneratingterraformapply无变化:生成状态文件,无需创建terraformdestroy删除资源:删除terraformapply根据状态文件内容生成资源:状态显示无资源,再次生成删除状态文件不改变terraformapply生成资源:无状态文件,直接生成资源和状态文件(插件已经做了容错处理,存在也会生成新的覆盖)。删除状态文件不改变terraformdestroy不能删除资源,没有资源存在的状态。这是真的。首先,它的默认文件名为terraform.tfstate,默认会放在当前目录下。它是以json格式存储的信息。示例中的内容如下:{"version":4,"terraform_version":"1.0.11","serial":1,"lineage":"acb408bb-2a95-65fd-02e6-c23487f7a3f6","outputs":{},"resources":[{"mode":"managed","type":"local_file","name":"test-file","provider":"provider[\"registry.terraform.io/hashicorp/local\"]","instances":[{"schema_version":0,"attributes":{"content":"https://www.pkslow.com","content_base64":null,"directory_permission":"0777","file_permission":"0777","filename":"./terraform-guides-by-pkslow.txt","id":"6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1","sensitive_content":null,"source":null},"sensitive_attributes":[],"private":"bnVsbA=="}]}]}可以看到记录了Terraform的版本信息,并且资源的详细信息:包括类型、名称、插件、属性等。有了这些信息,就可以直接从状态文件中解析出具体的资源。状态管理命令可以通过terraformstate做一些状态管理:displaystatelist:$terraformstatelistlocal_file.test-file查看具体资源的状态信息:$terraformstateshowlocal_file.test-file#local_file.test-file:resource“local_file”“测试文件”{content=“https://www.pkslow.com”directory_permission=“0777”file_permission=“0777”filename=“./terraform-guides-by-pkslow.txt”id=“6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1"}显示当前状态信息:$terraformstatepull重命名:$terraformstatemvlocal_file.test-filelocal_file.pkslow-fileMove"local_file.test-file"to"local_file.pkslow-file"成功移动了1个对象.$terraformstatelistlocal_file.pkslow-file需要注意的是这里只修改了state文件的名字,代码中的HCL不会修改。删除状态中的资源:$terraformstatermlocal_file.pkslow-fileRemovedlocal_file.pkslow-fileSuccessfullyremoved1resourceinstance(s).默认的远程状态状态文件是本地目录中的terraform.tfstate文件。在团队使用中,每个人的电脑环境都是独立的,所以无法保证每个人的当前状态文件都是最新的,并与真实的资源相对应。而状态不一致带来的灾难也是极其可怕的。因此,状态文件最好存放在每个人都可以访问的独立位置。对于状态管理的配置,Terraform称之为Backends。后端有两种模式,本地和远程。本地模式很好理解,就是使用本地路径来存放状态文件。配置示例如下:terraform{backend"local"{path="pkslow.tfstate"}}这样配置后,不再使用默认的terraform.tfstate文件,而是使用自定义文件名pkslow.tfstate。对于远程模式,有多种配置方法。Terraform支持:s3gcsossetcdpghttpkubernetes等,可以满足主流云平台的需求。各个配置可以参考官网。我本地使用的是数据库postgresql,方便大家快速实验。我通过Docker启动PostgreSQL,命令如下:$dockerrun-itd\--nameterraform-postgres\-ePOSTGRES_DB=terraform\-ePOSTGRES_USER=pkslow\-ePOSTGRES_PASSWORD=pkslow\-p5432:5432\postgres:13.在terraform块中配置后端。在这里指定数据库连接信息即可。更多参数请参考:https://www.terraform.io/lang...terraform{backend"pg"{conn_str="postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable"}}当然,敏感信息直接放在代码中是不合适的,可以直接在命令行传递参数:terraforminit-backend-config="conn_str=postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable"执行完init和apply后,连接数据库查看,会创建一个名为terraform_remote_state的Schema,其下有一张states表,用于存储对应的状态信息,如下:table是namespace,data是具体的状态信息,如下:{"version":4,"terraform_version":"1.0.11","serial":0,"lineage":"de390d13-d0e0-44dc-8738-d95b6d8f1868","输出":{},"资源":[{"模式":"管理d","type":"local_file","name":"test-file","provider":"provider[\"registry.terraform.io/hashicorp/local\"]","instances":[{“schema_version”:0,“attributes”:{“content”:“https://www.pkslow.com”,“content_base64”:null,“directory_permission”:“0777”,“file_permission”:“0777”,“filename":"./terraform-guides-by-pkslow.txt","id":"6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1","sensitive_content":null,"source":null},"sensitive_attributes":[],"private":"bnVsbA=="}]}]}Workspace工作区如果我们使用Terraform代码生变成了dev环境,现在需要uat环境,怎么处理?首先,不同环境下的变量一般是不同的。我们需要定义dev.tfvars、uat.tfvars、prod.tfvars等各种变量文件,但是只有各自的变量是不够的,因为还有状态。状态也必须是隔离的,Workspace是Terraform隔离状态的方式。默认工作空间为default,不指定则表示在默认工作空间工作。并且当指定工作空间时,状态文件将绑定到工作空间。创建一个工作空间并切换:$terraformworkspacenewpkslow切换到一个已经存在的工作空间:$terraformworkspaceselectpkslow当我们在一个工作空间中时,我们可以得到工作空间的名称,引用为:${terraform.workspace},示例如下:resource"aws_instance""example"{count="${terraform.workspace=="default"?5:1}"#...otherarguments}如前所述,默认状态文件名为terraform。状态;并且在多个工作空间的情况下(只要您创建了一个非默认工作空间),状态文件将存在于terraform.tfstate.d目录中。在remotestate的情况下,也会有一个mapping,Key是工作空间的名称,Value一般是state的内容。敏感数据本地状态文件以明文形式存储状态信息,因此请保护您自己的状态文件。对于远程状态文件,一些存储解决方案支持加密和加密敏感数据。状态锁本地状态文件下不需要状态锁,因为只有一个人在进行更改。在远程状态的情况下,可能存在竞争。比如一个人在申请,一个人在破坏,那就乱了。状态锁可以保证远程状态文件只能被一个人使用。但是并不是所有的remotestate方法都支持锁,常用的都支持锁,比如GCS、S3。因此,每当我们执行更改时,Terraform总是会先尝试获取锁,如果锁失败,命令就会失败。它可以强制解锁,但要非常小心。一般只建议在知道是安全的情况下才使用,比如死锁。共享状态-数据源由于远程状态文件可以共享,所以状态信息也可以共享。这样做的一个好处是即使两个根模块也可以共享信息。比如我们在根模块A中创建了一个数据库,根模块B需要使用IP等数据库信息,这样就可以将远程状态文件共享给根模块B。注意我这里强调的是根模块,因为如果A和B在同一个根模块下,则不需要通过远程状态共享状态。远程状态示例:data"terraform_remote_state""vpc"{backend="remote"config={organization="hashicorp"workspaces={name="vpc-prod"}}}resource"aws_instance""foo"{#...subnet_id=data.terraform_remote_state.vpc.outputs.subnet_id}本地状态示例:data"terraform_remote_state""vpc"{backend="local"config={path="..."}}resource"aws_instance""foo"{#...subnet_id=data.terraform_remote_state.vpc.outputs.subnet_id}需要注意的是,只能共享根模块的输出变量,不能获取子模块。
