【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

服务器太多了怎么管理?腾讯云 Terraform 开发实践

  • 2019-10-25
  • 本文字数:6229 字

    阅读完需:约 20 分钟

服务器太多了怎么管理?腾讯云Terraform开发实践

Terraform 是国际著名的开源的资源编排工具,据不完全统计,全球已有超过一百家云厂商及服务提供商支持 Terraform。这篇文章从 Terraform-Provider 系统架构开始,到 Terraform 核心库讲解,到实践 Terraform-Provider 开发,再到单元测试,比较完整的描述了支持 Terraform 的开发全过程


本项目已经发布在Github上,感兴趣的同学欢迎 Star 哟~

Terraform 是什么?

Terraform 是一款基于 Golang 的开源的资源编排工具,可以让用户管理配置任何基础架构,可以管理公有云和私有云服务的基础架构,也可以管理外部服务。


如果你不知道什么叫资源编排,那 AWS 控制台 、腾讯云控制台 你一定知道,你可以在这些控制台管理你的所有云资源,Terraform 和控制台作用一样,本质都是管理你的云资源,只不过,控制台是界面化的操作,而 Terraform 是通过配置文件来实现



当你的基础架构很复杂时,当你在某云厂商采买了规模较大的云资源或云服务时,当你的基础架构是基于混合云时,…,控制台的界面化操作,也许并不是最佳的管理工具,这时候,Terraform 可能就是上古神器了

怎么使用 Terraform 管理基础架构?

在开始开发之前,我们先了解下用户是怎么玩的,这尤其重要,这有助于更好的理解我们后续的开发流程和开发思路


简单来说,用户就是维护一些类似 json 格式的 .tf 配置文件,通过对配置的增删改查,实现对基础架构资源的增删改查。

配置开发环境

Terraform 支持插件模型,并且所有 provider 实际就是插件,插件以 Go 二进制文件的形式分发。虽然技术上可以用另一种语言编写插件,但几乎所有的 Terraform 插件都是用 Golang 编写的。


本文是在下列版本开发和测试的


  • Terraform 0.11.x

  • Go 1.9 (to build the provider plugin)


为了不使本文篇幅太长,环境相关请直接参考我们 Github 上的 README.md,这里就不重复写了,假设你已经准备好了开发环境

Provider 架构

按照 Go 的开发习惯和 Github 路径,我把开发目录放在了



接下来,我们了解下 tencentcloud 的插件目录,以此了解 Provider 架构



结构主要分五部分


  • main.go,插件入口

  • examples,示例目录,因为你的插件最终是给用户用的,一个比较理想的示例,是用户拉到代码后,可以直接跑起来

  • tencentcloud,最重要的目录,也就是我们的插件目录,里面都是 Go 文件,其中

  • provider.go 这是插件的根源,用于描述插件的属性,如:配置的秘钥,支持的资源列表,回调配置等

  • data_source_*.go 定义的一些用于读调用的资源,主要是查询接口

  • resource_*.go 定义的一些写调用的资源,包含资源增删改查接口

  • service_*.go 按资源大类划分的一些公共方法

  • vendor,依赖的第三方库

  • website,文档,重要性同 examples

生命周期

下图是 Terraform 的整个执行过程:



  • ① ~ ④ 是在寻找 Provider,tencentcloud 插件就是这时候加载的

  • ⑤ 是读取用户的配置文件,通过配置文件,可以获得分别属于哪种资源,以及每个资源的状态

  • ⑥ 根据资源的状态,调用不同的函数,Create Update Delete 都属于写操作,而 Read 操作,只在 Update 的时候,作为前置操作


何谓 Create ?

当在 .tf 文件增加一个新的资源配置时,这时候 Terraform 认为是 Create

何谓 Update ?

当在 .tf 文件针对已经创建好的资源,修改其中一个或多个参数时,这时候 Terraform 认为是 Update

何谓 Delete ?

当把 .tf 文件中已经创建好的资源配置删掉后,或执行 terraform destroy 命令时,这时候 Terraform 认为是 Delete

何谓 Read ?

顾名思义,这是一个查询资源的操作,如前述 Read 只在 Update 的时候,作为前置操作,实际作用就是检查资源是否存在,以及更新资源属性到本地。


细心的你一定注意到了 tencentcloud-sdk-go 这个 package,tencentcloud-sdk-go 是我们封装的一个独立于 Terraform 之外的基于 Tencent Cloud API 的 Go 版 SDK,其作用就是负责调用 Tencent Cloud API,当然,你也可以不用它,直接在你的 terraform-provider 里组装参数、发送请求,但我们不建议这么做,使用 SDK 方式,可以让你的代码更加优雅,可以实现对出入参、HTTP 请求的集中管理,可以让你的常用接口更好的复用,减少代码冗余

定义资源

Terraform 官网有个从 main.go 入口开始编写自定义 Provider 的指引 Writing Custom Providers,建议先浏览一遍。


成为 Terraform 提供商(开发 Terraform 插件),实际是对上游 API 的抽象,而所谓的资源就是我们的服务,比如云主机、私有网络、NAT 网关。按惯例,我们要把每个资源放在自己的插件目录下,并以资源命名,前缀为 resource_ 或 data_source_,比如


tencentcloud/resource_tc_nat_gateway.go



这里实际就是返回了一个 schema.Resource 类型的结构体,结构体中我们定义了资源参数和 CRUD 操作


  • Create

  • Read

  • Update

  • Delete

  • Schema


其中 Schema 就是定义的资源参数,是 map[string]*schema.Schema 类型的嵌套数组,这是一个非常重要的数组,在 Terraform 里,你也理解为这些就是一个资源的属性


在我们本次的示例中,就是一个 NAT 网关的所有属性(这些属性,我们可以在 NAT 网关的云 API 中看到)


每个属性,它的值都是一个结构体,包含了若干属性,这些属性,都是围绕资源属性值的,下面逐一介绍


Type schema.ValueType


定义这个属性的值的数据类型,可选值及对应的数据类型


  • TypeBool - bool

  • TypeInt - int

  • TypeFloat - float64

  • TypeString - string

  • TypeList - []interface{}

  • TypeMap - mapstringinterface{}

  • TypeSet - *schema.Set


Required bool


也就我们经常在 API 里说的 参数是否必填,默认 false,当设置为 true 后,用户对资源增删改操作时,都需要配置该参数


Optional bool


是否可选的,和 Required 互斥的,不能同时配置 Required 和 Optional,即一个属性(参数)要么必填,要么可选


ForceNew bool


如果设置为 true,当资源属性值发生变化时,不会触发修改动作,而是删除该资源,再创建新的资源,即:


修改 = 删除 + 创建


这是一个非常有用的属性,我们很多云资源的很多属性都不支持修改,比如


  • 一个 CVM 实例创建时指定的子网,创建后,是不支持修改的

  • 一个 NAT 网关创建时指定的 VPC,创建后,是无法修改的


在控制台可以通过前端技术实现这样的限制,Terraform 同样可以做到这样的限制,但 ForceNew 实现了更高级的用法,给用户提供了更多选择,


一个有趣的事情,如果某种云资源的所有属性,都是 Required,并且属性联合起来,具有唯一性,比如路由表的路由策略、DNAT 规则、KeyPair、…,都是这类特性,这时候你修改一个属性,实际就等价于删除旧资源,创建新资源 这时候,你就可以把所有属性的 ForceNew 设为 true,然后不用实现 Update 函数了,因为无论用户修改哪个属性,都是走 Delete - Create 的流程,根本不会走到 Update 的流程里,但实现的效果,都是一样的,用户是无感知的


ValidateFunc SchemaValidateFunc


属性值的扩展验证函数,验证 IP 合法性示例:



MinItems、MaxItems int


当 Type 为 TypeSet 或 TypeList 类型时,可以给 MinItems 和 MaxItems 赋值,限定属性值元素的最小个数和最大个数,上述代码中,我们限定了 NAT 网关的关联 EIP 个数范围是 1~10 个


CRUD 操作


这 4 个操作 Create Read Update Delete,指向的是 4 个函数,也是我们重点要实现的。


在”生命周期”一节中,我们知道了 Terraform 是根据资源的模式和状态,来决定是否需要创建新资源,更新现有资源或销毁资源的,而最终就是调用这 4 个函数来实现的

CRUD 实现

了解了用户行为、Terraform 执行流程、资源管理逻辑,现在就是实现这些功能的时候了


因为这块内容较多,这里继续用 NAT 网关作为示例,详述一个资源 CURD 的实现


开始之前,我们需要引入更多的包,都是我们后面要用到的



上述代码中,我们看到,我们要实现的资源管理函数,出参都是 error 类型,说明 Terraform 都是根据 error 来判断成功与否的,返回 nil 时表示操作成功,否则就报错


入参都是 *schema.ResourceData 类型的参数 d,和 interface{} 类型的参数 meta,具体这两个参数有什么用呢?


这是我们这节的关键!


参数 d 是我们开发过程中用的最多的参数,它的数据类型是个对象,包含了非常的方法,下面我们介绍几个常用的方法


func (*ResourceData) Get



用来获取给定 Key 的数据,如果给定的 Key 不存在,会返回 nil


通过 Set 方法设置的数据,以及用户配置的参数,都可以通过这个方法获得


一般,我们在 Create 资源的时候,用的比较多


func (*ResourceData) GetOk



检查给定的 Key 是否设置为一个非 0 的值,一般我们在获取 Optional 类型的属性值的时候,会用到


func (*ResourceData) SetId



Terraform 对资源的管理都是围绕 ID 实现的,每个资源都有一个唯一 ID,一个 ID 代表一个资源,因此,当创建资源后,需要调用这个方法写入资源 ID,一般服务端都会返回资源唯一 ID,比如我们的示例中,这个 ID 就是 NAT 网关的 ID,eg: nat-79r5e43i


这时候,你是不是有一个疑惑?我们的资源没有唯一 ID 怎么办?


对于没有唯一 ID 的资源,比如路由策略、安全组规则的增删改查,我们就需要自己构造 ID 了。


可以用某个参数作为 ID;也可以多个参数联合起来;也可以自己实现一个算法生成 ID。


前提条件就是一定要唯一 ,然后我们在用到 ID 的时候,再反解出来,这就间接实现了我们所需要的唯一 ID


func (*ResourceData) Id



获取当前的资源 ID,也就是 SetId 方法写入的值,比如我们在 Read Update Delete 的时候,都需要用到 ID,映射到对应的资源,从而完成对某个资源的读取,修改,删除


func (*ResourceData) Set



给某个 Key 设置值,设置后,可以用 Get 方法获取,一般用于 Read 操作,从服务端 Read 完数据后,会将资源的属性 Set 到本地,用于后续的其他资源管理操作


func (*ResourceData) HasChange



想象一下,当用户修改了他的配置文件(也就是修改资源的属性),我们的程序是怎么知道的?


这时候,就需要用到 HasChange 了,检查给定的 Key 是否发生变化,一个非常有用而且经常会用到的方法,一般在 Update 操作的时候,我们需要监控用户的配置文件,发生变化时,我们就触发变更操作


func (*ResourceData) GetChange



这个方法就是当我们在使用 HasChange 方法知道数据发生变化时,用这个方法可以获取到变化前后的数据,即旧数据和新数据


比如用户修改了 NAT 网关的关联弹性 IP,这时候,我们就需要将对比新旧数据,将用户删减的弹性 IP,从服务端解绑,用户增加的弹性 IP,绑定到 NAT 网关


func (*ResourceData) Partial



一般我们的资源属性,有非常多属性是支持修改的,比如我们这次示例中 NAT 网关,其中 NAT 网关的名称 name、最大并发连接数 max_concurrent、带宽上限 bandwidth、关联弹性 IP assigned_eip_set 都是支持修改的。


对用户来说,这些都是 NAT 网关的属性值而已,但对我们开发人员来说,涉及到的后端接口却是不一样的,这时候,如果用户修改了多个属性值,按照文档流的执行方式,如果前面执行的修改成功了,后面执行的失败了,这时候如果退出程序,给用户报错,就不合理了,因为实际我们的后端,已经修改了其中部分属性值。


这时候,服务端的数据和用户本地的数据,也不一致了,后续的其他操作,也会出现比较严重的问题


所以,我们应该不难理解这个方法的用途,就是用来设置是否 允许修改部分属性 的方法,默认 false,当开启 允许修改部分属性 后,使用了 SetPartial 方法设置的属性,即便 Update 出现错误,已经修改成功的属性,也会将状态同步到本地,程序下次执行时,就不会认为是要更新的了


总结三个字就是 “非事务”


func (*ResourceData) SetPartial



这个方法就是配合 Partial 方法使用的,经过这个方法设置的属性,允许修改部分属性 的逻辑才有效

创建资源

这里就是创建 NAT 网关



上述代码中 PollingVpcBillResult,我们说到了轮询,其实在 Terraform 开发中,轮询这个操作,是用的很频繁的,主要适用于异步的服务端接口,比如当前示例的 NAT 网关创建,还有后面会讲到的修改带宽,又如一些资源删除也都是异步的。


服务端只返回一个任务 ID,这时候需要我们在客户端轮询任务,直到结果返回,我们才能直到这个资源的真正的状态!


这个方法位于 service_vpc.go,并且是作为 *TencentCloudClient 对象的一个方法,核心是用到了 Terraform 官方的 resource 库,直接来看下这个方法吧,


读取资源

在 Create 的代码末尾,我们看到了 SetId,而 Read 操作,我们就是要根据资源 ID,查询资源,然后调用 Set 方法回写本地



我们在代码 15 行,留了个疑问,这也是很多开发,初次开发 Terraform 时,不太理解的地方!


当从服务端查询没有数据时,我们并不直接报错,而是把 ID 置空,并且返回 nil,这样做的目的是因为我们的云资源管理行为,不只在 Terraform,还有控制台,也可能基于云 API 的其他工具,倘若不是因为你的代码 Bug 导致查询失败而未找到数据,那就是在其他工具删除了该资源导致资源为找到,这时候


  • 返回 nil,是为了不让程序退出,让程序不认为这是错误

  • 把 ID 置空,是为了改变资源状态,前面我们提到 Terraform,对于资源的管理,是完全基于 ID 的,当我们把 ID 置空,Terraform 未找到资源 ID,就会认为这是一个新资源,这也是我们所预期的

修改资源

我们在生命周期那一节,讲到了 Update 操作前,Terraform 实际会先调用 Read,为什么呢?


因为 Terraform 判断一个资源状态,是依据本地的 terraform.tfstate 文件,这里记录所有配置(即资源)的状态,但是状态并非实时的,所以 Terraform 在做 Update 操作之前,会先从服务器 Read 数据,用最新的数据和本地做对比,获取最新的资源状态



主要思路,概括下就是:


  • 调用 Partial 方法开启 允许部分属性修改 功能

  • 调用 HasChange 方法检查是否变化,

  • 调用 SetPartial 方法把该属性加入到部分属性修改的集合里

  • 调用 GetChange 方法获取新旧数据(也可以直接 Get 最新数据)

  • 提交修改

  • 调用 Partial 方法关闭 允许部分属性修改 功能

删除资源

删除资源就是根据资源 ID,从服务端将对应的资源删除



示例是一个最简单的删除操作,在实际应用中,如果你的资源删除是异步的,或者删除操作,还依赖其他资源删除,比如当删除一个私有网络资源时,如果网络内还有其他资源,比如子网、VPN 等,调用删除接口时,会报错,导致删除失败! 遇到这些场景,我们还需要用到前面提到的重试操作, 就是当删除失败,特定原因下(一般就是有依赖关系)我们要执行重试,因为 Terraform 在删除资源时,是有次序的,直接删除有可能删不掉,而重试,当依赖关系都删完后,就能删除最顶层的被依赖的资源了


至此,一个基本的资源管理程序就算写完了!最后你还需要将资源管理函数配置到 provider.go 的 ResourcesMap 映射关系列表中,才能真正被使用

编写单元测试用例

到了测试环节,你可以自己编写 tf 文件,编译插件



然后测试你的程序



但我们非常不鼓励你这么做,我们强烈建议你自己编写单元测试用例,测试你的程序,在前面的 Provider 架构 章节中,你可以看到许多的 *_test.go 这就是我们的单元测试用例


如果要成为 Terraform 官方认证的 provider,单元测试用例,也是必不可少的


我们先来看下 Terraform 的单元测试系统流程图



下面是 NAT 网关资源管理程序的单元测试用例:



开始测试



我们可以看到,用官方的 testAccProviders,除了自动编译,测试流程也更加标准化,全面覆盖 Create Update Delete,针对同一个资源管理程序,你还可以编写很多更复杂的场景,加入到 Steps,或者分成多个测试用例,这样的测试会更加全面!


本文转载自公众号云加社区(ID:QcloudCommunity)。


原文链接:


https://mp.weixin.qq.com/s/ioHvPbFHvqZFO3XczKU0pg


2019-10-25 18:561307

评论

发布
暂无评论
发现更多内容

ESP32-C3入门教程 基础篇(二、GPIO中断、按键驱动测试)

矜辰所致

GPIO ESP32-C3 按键驱动 9月月更

轻量化的灰度发布实践技术方案

Speedoooo

灰度发布 ab测试 轻量化

史上最详细Ajax学习笔记

楠羽

笔记 ajax数据 9月月更

Qt|QGraphicsView架构下实时鼠标绘制图形

中国好公民st

c++ qt 9月月更

JS-内置对象API-Array(数组)-(一)-改变原数组的API-篇

Sam9029

JavaScript 前端 9月月更

ShareSDK 开发过程中常见问题

MobTech袤博科技

ios android sdk

每日算法刷题Day13-在O(1)时间删除链表结点、合并两个排序的链表、把字符串转换成整数

timerring

算法题 9月月更

C++学习---_IO_lock_t的源码学习

桑榆

c++ 源码阅读 9月月更

计算机网络——物理层设备

StackOverflow

编程 计算机网络 9月月更

Flink计算框架概述

阿泽🧸

9月月更 Filnk

中国市场到底有多少国产开源操作系统?

雨果

操作系统 开源操作系统

【字符串函数内功修炼】strncpy + strncat + strncmp(二)

Albert Edison

C语言 9月月更 strncpy strncat strncmp

中国20强游戏公司2022上半年年报分析:复合因素下业绩增长承压,海外新兴市场蕴含增长新趋势

易观分析

游戏 海外市场

什么是数据资产管理?5个角度帮你参透数据资产管理

雨果

数据资产管理

互联网公司员工职级、研发效能度量、OKR与绩效考核

laofo

DevOps cicd 研发效能 持续交付

常见监控分类概述

穿过生命散发芬芳

监控 9月月更

J-Tech & 开源之夏|什么是比快更快的向量搜索

Jina AI

搜索引擎 开源 开源之夏

高并发之降级和熔断

源字节1号

软件开发

python小知识-类全知道

AIWeker

Python python小知识 9月月更

jvm内存结构不同部分的总结

知识浅谈

JVM内存结构 9月月更

前端食堂技术周刊第 53 期:React Router 6.4、VS Code August 2022、2022 Google 谷歌开发者大会、Meta 开源 MemLab、Vue.js 技术内幕

童欧巴

Vue vscode React

带你走近Java虚拟机到底有哪些垃圾收集器

派大星

9月月更

精通高并发与内核 | Linux内核协程解析

小明Java问道之路

线程 进程 协程 Linux内核 9月月更

大数据调度平台Airflow(三):Airflow单机搭建

Lansonli

airflow 9月月更

你以为抓包软件只能抓包吗?看看抓包软件还有啥牛逼功能!

HullQin

CSS JavaScript html 前端 9月月更

PC端小程序引擎,或许不就未来能解决桌面应用兼容性

Speedoooo

小程序 桌面开发 桌面端 桌面应用

[Javaweb]JSON

十八岁讨厌编程

javaWeb 后端开发 9月月更

ESP32-C3入门教程 基础篇(一、ADC采样)

矜辰所致

ESP32-C3 9月月更 ADC采样

Flink Collector Output 接口源码解析

JasonLee实时计算

flink 源码

什么是混合云?与公有云、私有云有啥区别?

wljslmz

云计算 公有云 私有云 混合云 9月月更

极速安装和体验k8s(Minikube)

程序员欣宸

Kubernetes 9月月更

服务器太多了怎么管理?腾讯云Terraform开发实践_文化 & 方法_谢世亮_InfoQ精选文章