开两扇门,就要站两个守卫
目标
在真实的 Istio 网格中,用 Istio Gateway 和 Gateway API 两种方式打开入口(ingress),并通过响应和 Envoy 配置,确认 TLS 终止、权重分配和网关授权策略实际作用在哪里。
为什么重要
入口是网格的第一道关卡,所以在这里出错,外面马上就能看到。而在 Istio 与 Gateway API 并用的今天,同一个服务很容易出现两个入口,这两个入口是不同的 Pod,证书和策略也各自独立。仅凭清单“已应用”看不出这种差异,所以要用真实请求和网关 Envoy 收到的配置来确认。
步骤
- 用
kubectl apply -f /opt/fixtures/istlab/ingress-app.yaml在命名空间mall中部署 web-v1 和 web-v2,等待两个 Deployment 就绪。然后在/root/istlab-ingress/01-gateway.txt中写入入口网关的ingress_cluster_ip=(服务istio-system/istio-ingressgateway的 ClusterIP)和ingress_pod=(该网关 Pod 的名称)两行。 - 在
/root/istlab-ingress/gateway.yaml中写入两个资源并应用——命名空间mall中的 IstioGatewaymall-gw(选择器istio: ingressgateway,端口 80 HTTP,主机mall.example.com)和VirtualServicemall-ingress(主机mall.example.com,网关mall-gw,目标web-v1.mall.svc.cluster.local端口 80)。应用后,用Host: mall.example.com访问网关的 ClusterIP,应返回v1。 - 用
openssl生成CN=mall.example.com、SAN 为DNS:mall.example.com的自签名证书和密钥,保存为/root/istlab-ingress/tls/mall.crt和/root/istlab-ingress/tls/mall.key,并以 TLS Secretmall-cred的形式放入istio-system。然后在/root/istlab-ingress/gateway.yaml的 Gateway 中加入端口 443 的 HTTPS 服务器(主机mall.example.com,tls.mode: SIMPLE,credentialName: mall-cred),重新应用。curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/应返回v1。 - 在
/root/istlab-ingress/gwapi.yaml中写入两个资源并应用——命名空间mall中的 ConfigMapmall-api-options(data.service为spec: {type: ClusterIP})和 Gateway API 的Gatewaymall-api(gatewayClassName: istio,通过infrastructure.parametersRef引用该 ConfigMap,监听器http端口 80 HTTP,主机名api.mall.example.com,只允许同一命名空间的路由)。等待 Gateway 变为Programmed。 - 在
/root/istlab-ingress/httproute.yaml中写入并应用HTTPRouteapi-split——父资源为 Gatewaymall-api,主机名api.mall.example.com,两条规则:请求头x-canary: yes时转到web-v2(端口 80),其余请求按web-v180、web-v220 的权重分配。向mall-api-istio服务的 ClusterIP 发送x-canary: yes请求,应始终返回v2。 - 在
/root/istlab-ingress/06-compare.txt中写入四行——Istio Gateway 所选网关 Pod 的命名空间istio_gateway_ns=,Gateway API 所创建网关 Pod 的命名空间gwapi_gateway_ns=,其 Deployment 名称gwapi_deployment=,其 Service 的类型gwapi_service_type=。 - 在
/root/istlab-ingress/deny-admin.yaml中写入并应用istio-system的AuthorizationPolicydeny-admin——选择器istio: ingressgateway,action: DENY,路径/admin和/admin/*。应用后,在/root/istlab-ingress/07-scope.txt中写入istio_gateway_admin=(mall.example.com/admin的状态码)和gwapi_admin=(通过 Gateway API 网关访问api.mall.example.com/admin的状态码)两行。 - 在
/root/istlab-ingress/08-report.md中写入五行——tls_fingerprint_match=(网关在 443 上出示的证书的 SHA-256 指纹与/root/istlab-ingress/tls/mall.crt相同则为 yes)、gwapi_namespace=、weights=(v1/v2 权重,例如50/50)、admin_via_istio_gateway=、admin_via_gateway_api=——并在其下写出学到的内容,至少四行。
参考
- 这台 VM 的准备需要 2–4 分钟。
kubectl和istioctl(1.31.0)在登录 shell 中可以直接使用。 - 访问网关时用 ClusterIP,而不是 LoadBalancer 地址——
kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.spec.clusterIP}'。VM 的 shell 可以访问集群的服务地址。 kubectl get gateway无法确定指的是 Istio 还是 Gateway API。要像gateways.networking.istio.io、gateways.gateway.networking.k8s.io那样把 API 组也写上。- 修改配置之后,要过几秒钟才会扩散到网关。不要一次就下结论,请多重复几次。
- 常见错误——把 TLS Secret 创建在
mall命名空间中。它必须位于网关 Pod 所在的istio-system中。
在网格中部署两套应用并找到守门人
用 kubectl apply -f /opt/fixtures/istlab/ingress-app.yaml 在命名空间 mall 中部署 web-v1 和 web-v2,等待两个 Deployment 就绪。然后在 /root/istlab-ingress/01-gateway.txt 中写入入口网关的 ingress_cluster_ip=(服务 istio-system/istio-ingressgateway 的 ClusterIP)和 ingress_pod=(该网关 Pod 的名称)两行。
default profile 会在 istiod 之外预先建好 istio-ingressgateway。这个网关是独立运行的 Envoy,而不是 sidecar,现在它没有收到任何配置,所以对任何请求都返回 404。服务类型是 LoadBalancer,k3s 会给它挂上节点地址,但实验中用不会变动的 ClusterIP 来访问。Pod 名称用 -l istio=ingressgateway 来选取。
用 Istio Gateway 和 VirtualService 打开入口
在 /root/istlab-ingress/gateway.yaml 中写入两个资源并应用——命名空间 mall 中的 Istio Gateway mall-gw(选择器 istio: ingressgateway,端口 80 HTTP,主机 mall.example.com)和 VirtualService mall-ingress(主机 mall.example.com,网关 mall-gw,目标 web-v1.mall.svc.cluster.local 端口 80)。应用后,用 Host: mall.example.com 访问网关的 ClusterIP,应返回 v1。
Istio 的 Gateway 只是用选择器选中已经在运行的网关 Pod,告诉它“用这个端口、这个主机来接收”,至于往哪里转发,它并不知道。这由在 gateways 中写了该 Gateway 名称的 VirtualService 来决定。两者缺一就是 404。生效需要几秒钟,请重复尝试直到响应发生变化。Kubernetes 中有两个名为 gateway 的资源(Istio 的和 Gateway API 的),所以查询时像 kubectl get gateways.networking.istio.io 那样连 API 组一起写会更稳妥。
在网关上终止 TLS
用 openssl 生成 CN=mall.example.com、SAN 为 DNS:mall.example.com 的自签名证书和密钥,保存为 /root/istlab-ingress/tls/mall.crt 和 /root/istlab-ingress/tls/mall.key,并以 TLS Secret mall-cred 的形式放入 istio-system。然后在 /root/istlab-ingress/gateway.yaml 的 Gateway 中加入端口 443 的 HTTPS 服务器(主机 mall.example.com,tls.mode: SIMPLE,credentialName: mall-cred),重新应用。curl --cacert /root/istlab-ingress/tls/mall.crt --resolve mall.example.com:443:<ClusterIP> https://mall.example.com/ 应返回 v1。
网关不会挂载证书文件。credentialName 指向的 Secret 由 istiod 通过 SDS 下发给网关 Envoy,修改 Secret 后无需重启网关就会使用新证书。所以 Secret 要放在与网关 Pod 相同的命名空间(istio-system)中。curl --resolve 不依赖 DNS,把名称绑定到地址上,让你同时确认 SNI 和证书验证。网关收到了什么,用 istioctl proxy-config secret -n istio-system deploy/istio-ingressgateway 查看。
Gateway API 会新建网关
在 /root/istlab-ingress/gwapi.yaml 中写入两个资源并应用——命名空间 mall 中的 ConfigMap mall-api-options(data.service 为 spec: {type: ClusterIP})和 Gateway API 的 Gateway mall-api(gatewayClassName: istio,通过 infrastructure.parametersRef 引用该 ConfigMap,监听器 http 端口 80 HTTP,主机名 api.mall.example.com,只允许同一命名空间的路由)。等待 Gateway 变为 Programmed。
与 Istio 的 Gateway 不同,Gateway API 的 Gateway 会自己创建网关的 Deployment 和 Service,名称为 <Gateway 이름>-istio(占位符为 Gateway 名称)。默认服务类型是 LoadBalancer,而这台 VM 的 k3s 已经把 80 端口让给了入口网关,所以新的 LoadBalancer 拿不到地址,一直停在 <pending>——这样 Gateway 就不会变成 Programmed(实测)。请按官方文档的方法,通过 infrastructure.parametersRef 指向 ConfigMap 来修改服务类型。状态在 kubectl -n mall get gateways.gateway.networking.k8s.io mall-api -o yaml 的 conditions 中。
用 HTTPRoute 按请求头和权重分流
在 /root/istlab-ingress/httproute.yaml 中写入并应用 HTTPRoute api-split——父资源为 Gateway mall-api,主机名 api.mall.example.com,两条规则:请求头 x-canary: yes 时转到 web-v2(端口 80),其余请求按 web-v1 80、web-v2 20 的权重分配。向 mall-api-istio 服务的 ClusterIP 发送 x-canary: yes 请求,应始终返回 v2。
HTTPRoute 通过 parentRefs 自己声明要挂到哪个 Gateway 上(与 Istio 的 VirtualService 通过 gateways 声明的方向相同)。更具体的规则(请求头条件)优先。权重靠几次请求很难确认,所以请在网关 Envoy 实际收到的路由表——istioctl proxy-config routes deploy/mall-api-istio -n mall -o json 的 weightedClusters——中确认。
两个网关各在什么位置
在 /root/istlab-ingress/06-compare.txt 中写入四行——Istio Gateway 所选网关 Pod 的命名空间 istio_gateway_ns=,Gateway API 所创建网关 Pod 的命名空间 gwapi_gateway_ns=,其 Deployment 名称 gwapi_deployment=,其 Service 的类型 gwapi_service_type=。
Istio Gateway 是选用已有的网关,Gateway API 则是在 Gateway 所在的命名空间中建立网关。所以各团队可以在自己的命名空间里设置网关,并单独扩缩。作为代价,网关增多,资源也随之增加。创建出来的对象会带上标签 gateway.networking.k8s.io/gateway-name=mall-api。
在入口前拦住 /admin——以及拦不住的那个入口
在 /root/istlab-ingress/deny-admin.yaml 中写入并应用 istio-system 的 AuthorizationPolicy deny-admin——选择器 istio: ingressgateway,action: DENY,路径 /admin 和 /admin/*。应用后,在 /root/istlab-ingress/07-scope.txt 中写入 istio_gateway_admin=(mall.example.com/admin 的状态码)和 gwapi_admin=(通过 Gateway API 网关访问 api.mall.example.com/admin 的状态码)两行。
网关也是 Envoy 工作负载,所以可以用选择器挂上授权策略,在网关上拦下,请求就不会进入网格内部。但选择器选中的只有 istio-ingressgateway Pod。Gateway API 建立的网关是另一个 Pod,这条策略对它不起作用——这意味着第二个入口一打开,第一个入口的守卫就不再守着这个入口。请把这个事实用数字记录下来。
写下两个入口时要确认什么
在 /root/istlab-ingress/08-report.md 中写入五行——tls_fingerprint_match=(网关在 443 上出示的证书的 SHA-256 指纹与 /root/istlab-ingress/tls/mall.crt 相同则为 yes)、gwapi_namespace=、weights=(v1/v2 权重,例如 50/50)、admin_via_istio_gateway=、admin_via_gateway_api=——并在其下写出学到的内容,至少四行。
指纹只要把 openssl s_client -connect <ClusterIP>:443 -servername mall.example.com 得到的证书和文件,用 openssl x509 -noout -fingerprint -sha256 并排比较即可。其余的从第 6、7 步的记录中抄过来。