TT Lab
开始
学习 学习路径 课程

用 Kubespray 与 Terraform 搭建集群

Terraform 与 Kubespray 的边界 — 前面是模板,中间是包装,后面是声明

在 TT Lab 中继续学习

一句话总结

Terraform 是用 state 记住“应该存在什么”,并且只改变差异的工具,所以界线是:在 kubespray 之前,让它负责生成 inventory;在 kubespray 之后,让它负责声明集群之上的资源并捕捉 drift;中间的安装交给 kubespray。

为什么需要它

kubespray 的输入是 inventory,而 inventory 中的地址和名称,创建节点的一方最清楚。如果在云上用 Terraform 创建了 VM,那么用它的输出(IP、名称、角色)来生成 inventory 是很自然的,kubespray 仓库的 contrib/terraform 中也有 aws、gcp、openstack、vsphere 等各云的示例。另一端也是一样。集群搭建完成之后,为每个团队安装命名空间、权限和基础应用的工作是重复性的,而且必须能发现有人手动改动了什么。Terraform 的 state 和 plan 做的正是这件事。问题出在中间。Kubernetes 的安装是需要数百个任务遵守顺序的流程,而且 kubespray 已经以幂等的方式做好了。把它重新写成 Terraform 资源,等于做了两遍工作。

工作原理

前:把 inventory 做成模板。 templatefile() 读取一个文件,并用变量填充。把节点映射(name → {control_plane, worker, connection})作为变量,用 %{ for } 和 %{ if } 指令来填充各个组,这样增加节点,就变成在映射中加一行。local_file 写出文件,并把它的内容保留在 state 中。所以如果有人手动修改了文件,下一次 plan 就会提出要把它改回去——这意味着这个文件的所有者变成了 Terraform,这也是为版本(kube_version)这类容易被写在多个地方的值确定唯一所有者的办法。

中:只做包装。 terraform_data 是一个不创建任何基础设施的内置资源。放入 triggers_replace 的值一旦变化,它就会被重新创建,而在创建时,local-exec provisioner 会执行命令。把 inventory、版本、host_vars 的文件内容放进去,并把命令设为 ansible-playbook cluster.yml,就成了“只有在声明发生变化时才调用 kubespray”。如果执行失败,资源会保持 tainted,下次 apply 时会再次调用。

局限也会在这里显现。把版本从 1.35.8 改为 1.36.4,plan 会提出要重新创建版本文件和 terraform_data,那么被调用的就是 cluster.yml。而 kubespray 的升级必须是 upgrade-cluster.yml(cordon、drain、逐台一格一格地进行)。Terraform 虽然知道“什么变了”,却不知道“应该用什么流程来应用这个变化”。所以安装可以包装,而升级、移除节点这类流程不同的任务,最好由流水线或人来选择调用哪个 kubespray playbook。

后:用声明管理集群之上。 kubernetes provider 通过 kubeconfig 连接 API 服务器,创建 kubernetes_namespace_v1、kubernetes_service_account_v1、kubernetes_role_v1、kubernetes_role_binding_v1 之类的资源,helm provider 则用 helm_release 安装 Chart。在 helm provider 3.x 中,Kubernetes 连接配置是 kubernetes = { ... } 属性,值是 set = [{ name, value }] 列表。最好把创建集群的根模块与这个模块分开——如果在一个模块中创建集群,并在同一次运行中用该集群来设置 provider,首次 plan 时就必须读取尚不存在的 kubeconfig,顺序就会乱掉。

drift。 tofu plan 会先读取实际状态来刷新 state(refresh),再与声明比较。如果有人用 kubectl label 改了命名空间标签,plan 就会提示“将按声明改回去”,并且 -detailed-exitcode 会返回 2。把这个退出码接入定期任务,就成了 drift 告警。不过 helm_release 比较的是 release 元数据(Chart、值),所以对于 Chart 所创建的 Deployment 在外部被修改这类情况,并不总能捕捉到。必须知道什么由哪个工具来监视。

在现场相遇的样子

常见的设计是拆成三个仓库。创建节点的 Terraform(按云账户划分)、接收 inventory 并调用 kubespray 的流水线、声明集群之上内容的 Terraform(按团队划分)。本实验把这三者压缩在一台 VM 中。创建节点的第一层不在这里做——在这个平台上,那一层是创建 VM 的虚拟化 API,如果让实验在内部调用那个 API,学员之间的隔离就会被破坏。在现场,这个位置是云 provider 或者 vSphere、OpenStack provider。

state 也是需要小心的对象。本实验把 state 放在本地文件中,但如果是团队使用,就需要远程后端和锁,而且 state 中会以明文形式保存文件内容和资源属性,所以不能放入机密。

下一项实验要做什么

用节点映射和模板生成 inventory、host_vars 和版本文件并 apply,观察这些文件的所有者变成 Terraform。用 terraform_data 包装 kubespray 来搭建集群,并通过 plan 确认修改版本之后会重新调用什么。在另一个根模块中声明命名空间、RBAC 和 helm release,并通过 plan 捕捉在外部修改的标签,再把它改回去。