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

CCA — Cilium 认证助理

启用了 Gateway API,却没有 GatewayClass

在 TT Lab 中继续学习

目标

在 VM 内真正的 k3s + Cilium 1.20.1 中,先以错误的顺序启用 Gateway API 再纠正,然后用真实请求确认 Gateway、HTTPRoute 的路径、请求头、权重分割,以及命名空间边界(ReferenceGrant)。最后在服务列表中找出真正接收请求的是节点的 cilium-envoy。

为什么重要

Ingress 只把路径和主机这一点标准化,其余都交给各实现专有的注解。Gateway API 则划分了角色——基础设施负责人拥有 GatewayClass 和 Gateway,应用团队拥有 HTTPRoute,请求头匹配、权重分割这类功能都写在规范之内,而且要引用其他命名空间,必须由被引用一方通过 ReferenceGrant 允许。

Cilium 在没有 sidecar 的情况下实现了这套 API。L7 处理由每个节点上各运行一个的 envoy 负责,eBPF 把发往网关地址的流量交给该 envoy。如果不了解这种结构,就会花大量时间去找“网关 Pod 在哪里?”。

另外,启用顺序很重要。官方文档要求先安装 CRD。在本实验中把顺序颠倒,亲自看看缺少的是什么,以及为什么仅靠重启填补不上。

环境准备约需 5 分钟。Gateway API CRD 从 github raw 下载。会话结束后,/root/cca-gateway 中的文件会消失。

步骤

  1. 用 cilium CLI 启用 Gateway API 功能(cilium upgrade --version 1.20.1 --reuse-values --set gatewayAPI.enabled=true)。然后按官方文档的顺序重启 cilium-operator Deployment 和 cilium DaemonSet。暂时不要安装 Gateway API CRD。把那一刻的状态记录到 /root/cca-gateway/no-crd.json——enable_gateway_api(cilium-config 的 enable-gateway-api 字符串)、gatewayclass_api(API 中存在 gatewayclasses 资源为 true,不存在为 false)、operator_log(新的 Operator 记录的、说明没有 CRD 的那一行日志,原样保留)。
  2. 用 kubectl apply --server-side -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.6.1/config/crd/standard/gateway.networking.k8s.io_<자원>.yaml(占位符为资源名)安装 Gateway API v1.6.1 的 7 个标准 CRD(gatewayclasses gateways httproutes referencegrants grpcroutes backendtlspolicies tlsroutes)。之后再次重启 cilium-operator,等新的 Operator Pod 启动后等待 20 秒以上,再统计 GatewayClass 数量。在 /root/cca-gateway/crd-late.json 中记录 crds(已安装的 CRD 的完整名称 7 个的列表)、operator_started(重启后的 Operator Pod 的 status.startTime)、gatewayclasses(当时的数量)、recorded_at(统计数量那一刻的 UTC 时间,date -u +%Y-%m-%dT%H:%M:%SZ)。暂时不重新应用 Cilium 配置。
  3. 在已有 CRD 的状态下,再执行一次第 1 步的 cilium upgrade 命令,让 GatewayClass cilium 创建出来并变为 Accepted=True。在 /root/cca-gateway/gatewayclass.json 中记录 name、controller(spec.controllerName)、accepted(Accepted 条件的 status)、managed_by(标签 app.kubernetes.io/managed-by 的值)。
  4. 用 kubectl apply -f /opt/fixtures/cca-gateway/backends.json 启动后端(cca-gw 的 store-v1、store-v2、admin,cca-shop 的 payments)。在 cca-gw 命名空间中创建 Gateway shop-gw——gatewayClassName 为 cilium,一个监听器(name 为 http,protocol 为 HTTP,port 为 80)。变为 Programmed=True 之后,在 /root/cca-gateway/gateway.json 中记录 address(status.addresses 的值)、programmed、service(自动创建的服务名称)、service_type、proxy_backend(在 agent 的 cilium-dbg service list 中,该地址:80 LoadBalancer frontend 的后端 ip:port)、root_code(在节点上 curl http://<주소>/(占位符为地址)的 HTTP 状态码,数字)、server(该响应的 server 响应头的值)。
  5. 在 cca-gw 中创建 HTTPRoute store。parentRefs 为 shop-gw 一个,规则有两条——(1)path 为 PathPrefix /store 并且请求头为 x-canary: yes 的请求,转到 store-v2:8080;(2)path 为 PathPrefix /store 的其余请求,转到 store-v1:8080。在节点上向网关地址请求 /store/list、带上请求头的 /store/list 和 /storefront,并在 /root/cca-gateway/routes.json 中记录 store(第一个请求的响应 JSON 中的 app)、store_canary(第二个的 app)、storefront(第三个的 HTTP 状态码,数字)。
  6. 在 cca-gw 中创建 HTTPRoute split。parentRefs 为 shop-gw,一条规则——path 为 PathPrefix /checkout,backendRefs 为 store-v1:8080 weight 80 和 store-v2:8080 weight 20。路由生效后,向网关地址的 /checkout 请求 100 次,统计响应中的 app,并在 /root/cca-gateway/split.json 中记录 path、requests(发送的数量)、counts({store-v1: n, store-v2: m})。两个版本都必须被观测到。
  7. 在 cca-gw 中创建 HTTPRoute pay——parentRefs 为 shop-gw,path 为 PathPrefix /pay,backendRefs 指向 namespace 为 cca-shop 的 payments:8080。先在没有授权的情况下应用,观测 route 的 ResolvedRefs 条件(status、reason)和 /pay 的 HTTP 状态码。然后在 cca-shop 中创建 ReferenceGrant,允许 cca-gw 的 HTTPRoute 只引用 Service payments,再次观测。在 /root/cca-gateway/refgrant.json 的 before、after 下分别记录 resolved_refs、reason、code(数字)。
  8. 在 /root/cca-gateway/report.txt 中写入七行 키=값(占位符依次为键和值)形式的内容——gatewayclass_owner(GatewayClass cilium 的 managed-by 标签)、l7_proxy_daemonset(kube-system 中真正处理网关请求的 DaemonSet 名称)、gateway_frontend(地址:80)、gateway_proxy_backend(用与第 4 步相同的方法读取到的当前后端)、envoy_config(cca-gw 中自动创建的 CiliumEnvoyConfig 名称)、split_v2_count(split.json 中 store-v2 的数量)、cross_namespace_without_grant(没有授权时的 HTTP 状态码)。值必须与真实状态和记录一致。

参考

没装 CRD 就先开启网关功能的那一天

用 cilium CLI 启用 Gateway API 功能(cilium upgrade --version 1.20.1 --reuse-values --set gatewayAPI.enabled=true)。然后按官方文档的顺序重启 cilium-operator Deployment 和 cilium DaemonSet。暂时不要安装 Gateway API CRD。把那一刻的状态记录到 /root/cca-gateway/no-crd.json——enable_gateway_api(cilium-config 的 enable-gateway-api 字符串)、gatewayclass_api(API 中存在 gatewayclasses 资源为 true,不存在为 false)、operator_log(新的 Operator 记录的、说明没有 CRD 的那一行日志,原样保留)。

功能开关只是改动 ConfigMap,而 Operator 在启动时会检查是否存在 Gateway API 资源。请在重启后的 Operator 日志中查找 level=error 的行。资源是否存在用 kubectl api-resources --api-group=gateway.networking.k8s.io 确认。用 jq -n 的 --arg/--argjson 生成文件,即使是混有引号的日志行也能安全地放入。

后来才装了 CRD 并重启,GatewayClass 仍然是空的

用 kubectl apply --server-side -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/v1.6.1/config/crd/standard/gateway.networking.k8s.io_<자원>.yaml(占位符为资源名)安装 Gateway API v1.6.1 的 7 个标准 CRD(gatewayclasses gateways httproutes referencegrants grpcroutes backendtlspolicies tlsroutes)。之后再次重启 cilium-operator,等新的 Operator Pod 启动后等待 20 秒以上,再统计 GatewayClass 数量。在 /root/cca-gateway/crd-late.json 中记录 crds(已安装的 CRD 的完整名称 7 个的列表)、operator_started(重启后的 Operator Pod 的 status.startTime)、gatewayclasses(当时的数量)、recorded_at(统计数量那一刻的 UTC 时间,date -u +%Y-%m-%dT%H:%M:%SZ)。暂时不重新应用 Cilium 配置。

Operator 认出 CRD,与 GatewayClass 对象被创建,未必是同一件事。如果数量为 0,就该想想这个 GatewayClass 原本是由谁创建的——下一步通过标签确认。如果看到两个 Pod,creationTimestamp 最晚的那个是新 Pod。评分器不看文件修改时间,而是把 recorded_at 与重启时间以及之后才会出现的 GatewayClass 的创建时间比较,所以请填写记录那一刻的时间。

GatewayClass 是由 Chart 创建的

在已有 CRD 的状态下,再执行一次第 1 步的 cilium upgrade 命令,让 GatewayClass cilium 创建出来并变为 Accepted=True。在 /root/cca-gateway/gatewayclass.json 中记录 name、controller(spec.controllerName)、accepted(Accepted 条件的 status)、managed_by(标签 app.kubernetes.io/managed-by 的值)。

GatewayClass 的标签和注解中写明了谁拥有这个对象。Helm Chart 在渲染时会查看集群中有哪些 API,据此加入或去掉模板。请每隔几秒重新读取,直到条件变为 True。

站在网关地址背后的是 envoy

用 kubectl apply -f /opt/fixtures/cca-gateway/backends.json 启动后端(cca-gw 的 store-v1、store-v2、admin,cca-shop 的 payments)。在 cca-gw 命名空间中创建 Gateway shop-gw——gatewayClassName 为 cilium,一个监听器(name 为 http,protocol 为 HTTP,port 为 80)。变为 Programmed=True 之后,在 /root/cca-gateway/gateway.json 中记录 address(status.addresses 的值)、programmed、service(自动创建的服务名称)、service_type、proxy_backend(在 agent 的 cilium-dbg service list 中,该地址:80 LoadBalancer frontend 的后端 ip:port)、root_code(在节点上 curl http://<주소>/(占位符为地址)的 HTTP 状态码,数字)、server(该响应的 server 响应头的值)。

创建 Gateway 后,控制器会创建 LoadBalancer 服务,在这个 k3s 中由 servicelb 提供节点 IP。请在 agent 的服务列表(-o json)中找到该服务的 frontend,看后端是 Pod IP 还是节点本地地址。还要确认在没有任何路径规则时,由谁以什么状态码来响应。地址刚刚绑定之后,连接可能会短暂失败,所以请用轮询。

路径相同,请求头不同就转到不同版本

在 cca-gw 中创建 HTTPRoute store。parentRefs 为 shop-gw 一个,规则有两条——(1)path 为 PathPrefix /store 并且请求头为 x-canary: yes 的请求,转到 store-v2:8080;(2)path 为 PathPrefix /store 的其余请求,转到 store-v1:8080。在节点上向网关地址请求 /store/list、带上请求头的 /store/list 和 /storefront,并在 /root/cca-gateway/routes.json 中记录 store(第一个请求的响应 JSON 中的 app)、store_canary(第二个的 app)、storefront(第三个的 HTTP 状态码,数字)。

如果在同一条规则的一个 matches 项中同时放 path 和 headers,就必须同时满足两者。Gateway API 规定更具体的条件(带请求头的一方)优先。PathPrefix 不是按字符,而是按以 / 划分的路径元素来比较。后端会以 JSON 返回自己的名称。

统计“只有 20% 流向新版本”的承诺

在 cca-gw 中创建 HTTPRoute split。parentRefs 为 shop-gw,一条规则——path 为 PathPrefix /checkout,backendRefs 为 store-v1:8080 weight 80 和 store-v2:8080 weight 20。路由生效后,向网关地址的 /checkout 请求 100 次,统计响应中的 app,并在 /root/cca-gateway/split.json 中记录 path、requests(发送的数量)、counts({store-v1: n, store-v2: m})。两个版本都必须被观测到。

权重是按比例分配每一个请求,而不是严格地每隔五个请求发送一次的规则。所以样本数量会在 80/20 附近波动。刚创建路由之后,在 envoy 配置传播期间可能混入 404,所以请在两个版本都各出现一次之后再开始统计。评分器会把记录与权重核对,自己也会重新抽样。

隔壁命名空间的支付服务必须获得许可才能接入

在 cca-gw 中创建 HTTPRoute pay——parentRefs 为 shop-gw,path 为 PathPrefix /pay,backendRefs 指向 namespace 为 cca-shop 的 payments:8080。先在没有授权的情况下应用,观测 route 的 ResolvedRefs 条件(status、reason)和 /pay 的 HTTP 状态码。然后在 cca-shop 中创建 ReferenceGrant,允许 cca-gw 的 HTTPRoute 只引用 Service payments,再次观测。在 /root/cca-gateway/refgrant.json 的 before、after 下分别记录 resolved_refs、reason、code(数字)。

ReferenceGrant 要放在被引用一方的命名空间中。from 中写引用方资源的 group、kind、namespace,to 中写被引用资源的 group、kind(要缩小范围时再写 name)。核心 API 的 group 是空字符串。条件请从 status.parents[0].conditions 中按 type 挑选来读取。

开通报告:谁创建了什么,谁接收了请求

在 /root/cca-gateway/report.txt 中写入七行 키=값(占位符依次为键和值)形式的内容——gatewayclass_owner(GatewayClass cilium 的 managed-by 标签)、l7_proxy_daemonset(kube-system 中真正处理网关请求的 DaemonSet 名称)、gateway_frontend(地址:80)、gateway_proxy_backend(用与第 4 步相同的方法读取到的当前后端)、envoy_config(cca-gw 中自动创建的 CiliumEnvoyConfig 名称)、split_v2_count(split.json 中 store-v2 的数量)、cross_namespace_without_grant(没有授权时的 HTTP 状态码)。值必须与真实状态和记录一致。

在没有 sidecar 的 Cilium 中,L7 由每个节点上各运行一个的 envoy 负责。请用 kubectl -n kube-system get ds 和 kubectl get ciliumenvoyconfig -A 找出网关留下的痕迹,并与服务列表中的 127.0.0.1 后端关联起来。