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

CNPE — 云原生平台工程师

设计平台 API 并立起围栏

在 TT Lab 中继续学习

目标

把名为 AppClaim 的自助服务 API 设计为 CRD 并应用,用 schema 和 CEL 拒绝错误的请求,用 RBAC 划分租户能做和不能做的事,然后为发放数量设置上限。

为什么重要

平台 API 和自助服务是 CNPE 中涉及的核心领域。而这个领域所考察的,不是你是否会创建 CRD,而是你是否懂得决定让 API 服务器拒绝什么。

这个判断之所以重要,是因为拒绝的位置只有两处。如果 API 服务器拒绝,用户会立即看到原因。如果控制器稍后失败,用户会以为已经受理了,并在那里等待。能在前面设置的拦截,如果推迟到后面,就会让人多走弯路。

本实验中没有把 AppClaim 转换为真实应用的控制器。但 kube-apiserver 是真实的,所以 CRD 注册、OpenAPI 验证、CEL 评估、RBAC 判定、ResourceQuota 准入,全部都是真实运行的。 因此,在这里确认的拒绝和允许,与真实集群中发生的是一样的。

工作目录是 /root/cnpe-api,租户命名空间是 tenant-blue。

步骤

  1. 在 /root/cnpe-api/appclaim-crd.yaml 中编写 CRD。组是 platform.labhub.io,种类是 AppClaim,复数形式是 appclaims,范围是 Namespaced。版本有 v1alpha1 和 v1 两个,都是 served,存储版本是 v1。在 v1alpha1 和 v1 中都设置 status 子资源和显示 .spec.tier 的输出列。在两个版本的根部把 spec 设为必需,并在 spec 内把 tier、replicas、maxReplicas 设为必需。tier 是字符串,两个数量是 1 以上的整数。写完之后进行 apply。
  2. 在 v1alpha1 和 v1 各自的 spec 中加入验证。tier 只允许 bronze、silver、gold,并用 CEL 规则阻止 replicas 超过 maxReplicas。拒绝消息中必须包含 maxReplicas 这个字段名。在两个版本中,允许 bronze(1/1)、silver(2/6)、gold(3/3)的正常请求,并用服务器 dry-run 确认:整个 spec 缺失、null、空对象、必需字段缺失、错误的数据类型、0 和负数会被拒绝。括号内依次为 replicas/maxReplicas。
  3. 创建命名空间 tenant-blue,并在 /root/cnpe-api/claim-checkout.yaml 中编写 AppClaim checkout。tier 是 silver,replicas 是 2,maxReplicas 是 6。apply 之后,确认用两个版本查询得到的 UID 和 spec 相同。
  4. 在 v1alpha1 和 v1 各自的 spec 中加入转换规则,使 tier 不可变。规则必须引用 oldSelf,拒绝消息中必须包含 tier。保持 tier 不变、只修改 replicas 的变更必须继续通过。
  5. 在 /root/cnpe-api/tenant-rbac.yaml 中编写 ServiceAccount blue-dev、Role appclaim-author、RoleBinding appclaim-author。Role 对 AppClaim 允许 get、list、watch、create、update、patch。绑定必须指向该 ServiceAccount。
  6. 用 kubectl auth can-i 确认:Role 中没有 delete,不能创建 resourcequotas、roles、rolebindings,不能读取 secrets。AppClaim status 的 patch 和 update,以及 kube-system 中 AppClaim 的 list,也必须被拒绝。要核对正常的 get 是 yes,不要把通信或认证错误解读为 no。Role 的 verbs、resources、apiGroups 中任何位置都不能有 *。
  7. 再创建一个 AppClaim search,并在 /root/cnpe-api/claim-quota.yaml 中编写 ResourceQuota blue-claims。count/appclaims.platform.labhub.io 的上限是 2。确认配额的 status.used 被填为 2,如果是空的,就诊断 CRD Established、API discovery 和配额控制器并重新确认,然后确认第三个 claim 真的被阻止。
  8. 在 /root/cnpe-api/api-report.txt 中,以 키=값(占位符依次为键和值)格式写入 storage_version、claims、quota_hard、tenant_can_delete 四行。四个值都必须是直接从集群查询得到的。

参考

确定 CRD 的骨架与两个版本

在 /root/cnpe-api/appclaim-crd.yaml 中编写 CRD。组是 platform.labhub.io,种类是 AppClaim,复数形式是 appclaims,范围是 Namespaced。版本有 v1alpha1 和 v1 两个,都是 served,存储版本是 v1。在 v1alpha1 和 v1 中都设置 status 子资源和显示 .spec.tier 的输出列。在两个版本的根部把 spec 设为必需,并在 spec 内把 tier、replicas、maxReplicas 设为必需。tier 是字符串,两个数量是 1 以上的整数。写完之后进行 apply。

存储版本只有一个版本为 true。旧版本也要设为 served,才能接收该版本的请求。spec 内的 required 并不会使 spec 本身成为必需,所以根部的 required 也是必要的。status 子资源把状态写入分离为单独的路径。

约束值的集合与字段之间的关系

在 v1alpha1 和 v1 各自的 spec 中加入验证。tier 只允许 bronze、silver、gold,并用 CEL 规则阻止 replicas 超过 maxReplicas。拒绝消息中必须包含 maxReplicas 这个字段名。在两个版本中,允许 bronze(1/1)、silver(2/6)、gold(3/3)的正常请求,并用服务器 dry-run 确认:整个 spec 缺失、null、空对象、必需字段缺失、错误的数据类型、0 和负数会被拒绝。括号内依次为 replicas/maxReplicas。

单个值的形态由 enum 把关,两个字段之间的关系由 CEL 把关。规则通过 x-kubernetes-validations 挂在 spec 属性之下。拒绝消息是用户真正会读到的唯一一句话,所以请加上字段名。

受理第一个自助服务请求

创建命名空间 tenant-blue,并在 /root/cnpe-api/claim-checkout.yaml 中编写 AppClaim checkout。tier 是 silver,replicas 是 2,maxReplicas 是 6。apply 之后,确认用两个版本查询得到的 UID 和 spec 相同。

并不是两个版本创建了不同的对象。请比较 UID 和 spec。开启 status 子资源之后,普通创建和修改请求中的 status 会被忽略。本实验中没有 AppClaim 控制器,所以不要把受理成功解读为真实应用部署完成。

创建一个一旦确定就无法更改的字段

在 v1alpha1 和 v1 各自的 spec 中加入转换规则,使 tier 不可变。规则必须引用 oldSelf,拒绝消息中必须包含 tier。保持 tier 不变、只修改 replicas 的变更必须继续通过。

如果规则引用了 oldSelf,这条规则就只在变更时才会被评估。不过,如果把整个 spec 与 oldSelf 比较,连副本数也无法修改,就变成了冻结。请用名称来指明要约束的字段。

确定租户可以自己做的事

在 /root/cnpe-api/tenant-rbac.yaml 中编写 ServiceAccount blue-dev、Role appclaim-author、RoleBinding appclaim-author。Role 对 AppClaim 允许 get、list、watch、create、update、patch。绑定必须指向该 ServiceAccount。

Role 只是权限的定义,必须加上绑定才会产生权限。创建之后,一定要用 kubectl auth can-i 确认判定。光读 Role 是无法知道最终结果的。

用判定来确认被关上的那一边

用 kubectl auth can-i 确认:Role 中没有 delete,不能创建 resourcequotas、roles、rolebindings,不能读取 secrets。AppClaim status 的 patch 和 update,以及 kube-system 中 AppClaim 的 list,也必须被拒绝。要核对正常的 get 是 yes,不要把通信或认证错误解读为 no。Role 的 verbs、resources、apiGroups 中任何位置都不能有 *。

开放的部分在前面的步骤中已经看过了,所以这里只看被关闭的一侧。删除、配额、Role、Secret、status 变更、查询其他命名空间,都必须是明确的 no,并且 Role 中任何位置都不能有星号。只要有一个星号,上面的判定就会全部被推翻。

给发放设置上限,并观察它是否被阻止

再创建一个 AppClaim search,并在 /root/cnpe-api/claim-quota.yaml 中编写 ResourceQuota blue-claims。count/appclaims.platform.labhub.io 的上限是 2。确认配额的 status.used 被填为 2,如果是空的,就诊断 CRD Established、API discovery 和配额控制器并重新确认,然后确认第三个 claim 真的被阻止。

RBAC 回答的是“能不能做”,而不回答“最多做几个”。个数由配额来统计。不要仅凭 status.used 为空这一事实就断定原因。刚注册之后要等待生效,如果一直为空,就确认 Established、API discovery 和控制器的状态。如果不分青红皂白地删除配额,就等于移除了限制。

把平台 API 的当前状态留成数值

在 /root/cnpe-api/api-report.txt 中,以 키=값(占位符依次为键和值)格式写入 storage_version、claims、quota_hard、tenant_can_delete 四行。四个值都必须是直接从集群查询得到的。

四个值都要通过查询来写。存储版本来自 CRD,claim 的数量和上限来自命名空间,能否删除来自权限判定。评分器会重新计算同样的值并进行核对。