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

用 Kubespray 与 Terraform 搭建集群

运维就是重复运行同一个 playbook

在 TT Lab 中继续学习

一句话总结

cluster.yml 是按主机准备 → 运行时 → 下载 → etcd → kubelet → 控制平面 → CNI → 附加组件的顺序运行的 15 个 play,并且它被设计为:用同一个 inventory 无论重新运行多少次,得到的都是同一个集群。

为什么需要它

用 kubeadm 搭建一台节点,几行命令就结束了。难的是之后。三个月后,有人需要修改 containerd 的一项配置,这个人必须知道当初按什么顺序做了什么,并且每个节点都要得出同样的结果。Shell 脚本第一次运行得很好,但第二次就会撞上已经存在的文件和已经在运行的守护进程。kubespray 把安装写成了“查看当前状态,只补上缺少的东西”的 Ansible 任务,所以安装命令和运维命令是同一个。无论是修改配置,还是增加节点,都是重新运行同一个 cluster.yml。

工作原理

用 --list-tasks 展开,v2.32.0 的 cluster.yml 有 15 个 play(实测 4 秒,不会改变任何东西)。顺序是有理由的。

#1-2   Ansible 판 확인, 인벤토리 검사(boilerplate)            — 틀린 인벤토리는 여기서 1초 만에 멈춘다
#4-5   호스트 부트스트랩, 사실(facts) 수집
#6     etcd 준비: preinstall(스왑·sysctl·패키지) → 컨테이너 런타임 → 받기(download)
#8     etcd 설치 — 기본은 컨테이너가 아니라 호스트의 systemd 서비스(etcd_deployment_type: host)
#9     kubelet 설치
#10    컨트롤 플레인 — 안에서 kubeadm init 을 부른다
#11    kubeadm 마무리, 노드 라벨·테인트, CNI(기본 calico)
#14    애드온 — CoreDNS, nodelocaldns, (켰다면) metrics-server 등
#15    클러스터 DNS 가 뜬 뒤 resolv.conf 정리

该代码块中的韩文说明依次为:第 1–2 个 play 确认 Ansible 版本并检查 inventory(boilerplate),写错的 inventory 会在这里 1 秒内停住;第 4–5 个是主机引导和 facts 收集;第 6 个是 etcd 准备——preinstall(swap、sysctl、软件包)→ 容器运行时 → 下载(download);第 8 个安装 etcd,默认不是容器,而是主机上的 systemd 服务(etcd_deployment_type: host);第 9 个安装 kubelet;第 10 个是控制平面,其中会调用 kubeadm init;第 11 个是 kubeadm 收尾、节点标签与污点、CNI(默认 calico);第 14 个是附加组件——CoreDNS、nodelocaldns,以及(如果打开了的话)metrics-server 等;第 15 个是在集群 DNS 启动之后整理 resolv.conf。

etcd 排在控制平面之前,是因为没有存储 API 服务器就无法启动;CNI 排在附加组件之前,是因为没有 Pod 网络 CoreDNS 就无法启动。了解了这个顺序,只看失败的位置,就能推测出什么已经存在、什么还没有。

日志要从末尾往前读。 PLAY RECAP 的一行就是判定——ok、changed、unreachable、failed、skipped。仓库中的 ansible.cfg 打开了 profile_tasks 回调,所以在它下面会附加 TASKS RECAP,标题行的累计时间就是总时间,下面的列表是按耗时长短排列的任务。中间的 fatal: 并不是判定。例如,首次安装时 Get currently-deployed etcd version 因为 etcd 还不存在,失败才是正常的,kubespray 会用 ...ignoring 跳过这个结果。

幂等并不等于 changed=0。 Ansible 的大多数模块是按“比较期望状态与当前状态,只在不同时才修改”的方式工作的,所以第二次运行时大多以 ok 结束。然而用 command 和 shell 调用的任务没有办法比较,要么每次运行都会产生 changed,要么就是有意用 changed_when 把它压下去,二者必居其一。所以第二次运行的 changed 列表,就是“每次都会变化的东西”的列表,只有了解这一点,才能判断第三次运行中新出现的 changed 是不是我自己的变更。

可以用标签只运行一部分。 kubespray 文档的标签表中有 containerd、etcd、network、apps、coredns、metrics_server 之类的名称,给出 --tags containerd 后,只有带这个标签的任务和 always 标签的任务(inventory 检查、facts)会运行。文档警告说,只有在“100% 确定它会做什么”时才使用标签。因为 role 之间的依赖(例如,修改 containerd 配置会触发重启 handler)如果不在标签范围之内,就会被遗漏。

在现场相遇的样子

这是制作本课程时在 medium VM(4 vCPU · 4 GiB)上的实测值。在干净的 VM 上,cluster.yml 首次运行用了 341 秒(changed 132),同一条命令的第二次运行用了 183 秒(changed 27)。安装期间整台 VM 的内存使用(MemTotal − MemAvailable)最高为 1.52GiB,其中 Ansible 进程最多占用 345MB。这说明 4 GiB 已经足够。下载的内容来自 github.com release、dl.k8s.io、registry.k8s.io、quay.io 和 Ubuntu apt 仓库,全部只通过 HTTPS(443)或 HTTP(80)下载。

实测中摔倒了两次,两次都是在现场会原样遇到的形态。第一,为了节省根分区,把 /usr/local 整个绑定挂载到了另一块磁盘,结果 /usr/local/share/ca-certificates 被遮住,etcd 证书步骤以 Destination directory ... does not exist 停住了。第二,在 HOME 为空的环境(systemd 单元)中运行 playbook,kubespray 的 kube 模块没有带 --kubeconfig 就调用了 kubectl,于是去连接 localhost:8080,cluster_roles 步骤重试了 1 分钟后停住了。这第二次失败发生在安装 CNI 之前,在那个状态下重新运行 cluster.yml,这次 “Wait for new control plane nodes to be Ready” 等待 130 秒后失败了。因为在 kubeadm 已经运行过的节点上,没有 CNI 就等待 Ready 的任务会先出现。搭建到一半的集群,重新运行未必能修好,这时用 reset.yml 清除并从头搭建更快(reset 实测 78 秒)。

下一项实验要做什么

先展开 play 的顺序,然后运行 cluster.yml 搭建集群。从日志末尾读取结果和耗时,再把同一条命令多运行一次,汇总第二次运行改变了哪些任务。最后用 group_vars 修改 containerd 的一个配置值,并用 --tags containerd 只重新应用那一部分,确认配置文件和守护进程都发生了变化。