0%

在 k8s 环境中使用 Istio

上篇文章我们介绍了 k8s 的 Gateway API,也介绍了k8s 的 GAMMA(Gateway API For Mesh Management and Administration)倡议,旨在将 Kubernetes Gateway API 扩展到服务网格领域。这篇文章我们将继续在 k8s 环境中实际部署 Istio 这个服务网格实现。

Istio 简介

在 GAMMA 出现之前,Kubernetes 的网络管理处于 割裂 状态:

  • Gateway API (南北向): 主要负责集群外部流量进入集群(Ingress 的继任者)。
  • Service Mesh (东西向): 主要负责集群内部服务之间的通信、治理、加密(如 Istio, Linkerd)

每个服务网格实现都有自己的私有 API(比如 Istio 的 VirtualService),这导致了用户在切换网格工具时面临巨大的迁移成本。GAMMA 的目标就是标准化服务网格的配置,让东西向流量也使用 Gateway API。GAMMA 并不是要取代服务网格,而是为服务网格提供一套通用的控制面标准,它使用标准的 Gateway API 资源(如 HTTPRoute)来管理服务网格内的东西向流量(服务间通信)。

Istio 是目前功能最全、生态最丰富的服务网格实现。它采用了典型的控制面(Control Plane)与数据面(Data Plane)分离的架构。

  • 数据面 (Envoy Proxy): Istio 默认在每个服务 Pod 中注入一个 Envoy 代理作为 Sidecar。所有的进出流量都会被 Envoy 劫持。Envoy 负责实际的负载均衡、熔断、限流和指标收集
  • 控制面 (Istiod): 负责策略下发。它将你写的 YAML 配置(如 VirtualService)转换成 Envoy 能够理解的配置,并通过 xDS 协议推送到各个 Sidecar

Istio 提供了以下核心功能:

  • 流量管理 (Traffic Management):
    • 动态路由: 可以实现非常精细的流量切分,轻松实现金丝雀发布或蓝绿部署
    • 弹性能力: 内置了重试(Retry)、超时(Timeout)、熔断(Circuit Breaker)和限流功能,这些逻辑在网格层实现,不需要在业务代码中实现
  • 安全 (Security)
    • mTLS (双向 TLS): 自动为服务间的通信进行加密和身份验证
    • 策略控制: 可以定义 只有 Service A 能访问 Service B 的 /admin 接口 这种细粒度的授权规则
  • 可观测性 (Observability):
  • 策略执行: 支持配额管理和黑白名单

Istio 目前存在两种模式:Sidecar 模式Ambient 模式

对于 Sidecar 模式:

  • 在 Sidecar 模式下,Istio 在每个业务 Pod 中注入一个 Envoy 代理容器。
  • 所有的进出流量都必须通过 iptables 强制劫持到这个 Envoy 代理中
  • 流量在到达业务代码前,在同一个 Pod 内完成 L4(传输层)加密和 L7(应用层)路由。
  • 代理和业务强绑定生命周期

对于 Ambient 模式:

  • 不再将代理塞进 Pod 内部,而是将功能拆分为两个独立层级
  • L4 层级:Ztunnel (Secure Overlay):
    • 每个节点(Node)部署一个共享的 ztunnel
    • 只负责 L4 流量的零信任加密(mTLS)、节点流量透明拦截、HBONE 隧道等
  • L7 层级:Waypoint Proxy
    • 可选的 Envoy 实例,按需开启
    • 处理复杂的 L7 逻辑,如重试、限流、熔断、基于路径的路由等
  • 业务 Pod 纯业务、无任何代理容器、无需重启注入

Ambient 模式是一种全新改进的模型,旨在弥补 Sidecar 模式的不足。Istio 社区的大部分精力都投入到了 Ambient 模式的改进上, 尽管 Sidecar 模式仍然得到全面支持。一般来说,建议新用户从 Ambient 模式开始。它速度更快、 成本更低,而且更易于管理。

Istio 部署

Istio 安装

接下来我们来实际安装 Istio,首先通过如下命令下载安装 Istio release:

1
2
3
4
# curl -L https://istio.io/downloadIstio | sh -

Downloading istio-1.29.2 from https://github.com/istio/istio/releases/download/1.29.2/istio-1.29.2-linux-amd64.tar.gz ...
Istio 1.29.2 download complete!
  • release 包默认直接存放在当前目录下,因此需要设置 PATH 环境变量
1
2
3
# cd istio-1.29.2/
# export PATH=$PWD/bin:$PATH

  • 运行安装前检查:istioctl x precheck,检查集群是否满足 Istio 安装要求,包括:Kubernetes 版本、资源配额、网络配置等
1
2
3
# istioctl x precheck
✔ No issues found when checking the cluster. Istio is safe to install or upgrade!
To get started, check out https://istio.io/latest/docs/setup/getting-started/.
  • 安装 Istio:
1
2
3
4
5
6
# istioctl install --set profile=default -y

✔ Istio core installed ⛵️
✔ Istiod installed 🧠
✔ Ingress gateways installed 🛬
✔ Installation complete
组件 说明
Istiod Istio 控制面,负责配置下发(xDS)、证书管理等
istio-ingressgateway Istio 入口网关,处理南北向流量
Sidecar Proxy 数据面代理(Envoy),注入到每个 Pod
  • 检查安装状态
1
2
3
4
5
# kubectl get pods -n istio-system

NAME READY STATUS RESTARTS AGE
istio-ingressgateway-65ccf95474-qcgjk 1/1 Running 0 18s
istiod-54856f99c-rghgx 1/1 Running 0 34s
  • 如果 k8s 环境中还没有安装 Gateway API CRD,还需要手动安装。我的环境之前测试 Envoy Gateway 时已经安装过了,这里就不用手动安装
1
2
kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
{ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.4.0" | kubectl apply -f -; }

启用 Sidecar 自动注入

为命名空间添加标签,启用自动注入

1
# kubectl label namespace default istio-injection=enabled --overwrite

这条命令的核心作用是监视 default 命名空间,并在部署新应用时,自动把 Sidecar 代理(Envoy)塞进去:

  • kubectl label: 这是 Kubernetes 的标准命令,用于给资源添加或修改 标签(Label)
  • namespace default: 指定操作的对象类型是 namespace(命名空间),对象名称是 default
  • istio-injection=enabled: 这是关键的键值对
    • istio-injection 是 Istio 控制面(istiod)预设的一个特殊 Key
    • enabled 表示开启自动注入
  • --overwrite:如果该键值对已经存在,则覆盖原有值;如果不存在,则新增该键值对。这样可以确保默认命名空间总是开启自动注入

这样当你执行 kubectl apply -f deployment.yaml 部署新应用时:

  • API Server 发现命名空间有 istio-injection=enabled 标签,API Server 就会把 Pod 的定义发给 Istiod
  • Istiod 在 Pod 的容器列表中插入一个新的 istio-proxy 容器(即 Sidecar),并配置好 iptables 初始化容器。
  • API Server 接收修改后的 Pod 定义,最后由 Kubelet 启动这个 双容器 Pod

检查命名空间标签是否设置成功:

1
2
3
# kubectl get ns default --show-labels
NAME STATUS AGE LABELS
default Active 8d istio-injection=enabled,kubernetes.io/metadata.name=default

应用测试

部署测试应用

接下来创建如下配置文件 manifests/gamma/echo-services-mesh.yaml,用于配置测试应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: curlimages/curl:8.9.1
command: ["sleep", "365d"]
---
apiVersion: v1
kind: Service
metadata:
name: sleep
spec:
selector:
app: sleep
ports:
- port: 80
targetPort: 80
---
# backend-v1 Deployment + Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-v1
spec:
replicas: 1
selector:
matchLabels:
app: backend
version: v1
template:
metadata:
labels:
app: backend
version: v1
spec:
containers:
- name: backend
image: hashicorp/http-echo:0.2.3
args:
- "-text=backend-v1"
- "-listen=:80"
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend-v1
spec:
selector:
app: backend
version: v1
ports:
- port: 80
targetPort: 80
---
# backend-v2 Deployment + Service (类似结构)
# backend-alt Deployment + Service (类似结构)

服务说明:

服务 说明 返回内容
sleep 客户端服务,用于发起测试请求 -
backend-v1 后端服务 v1 版本 backend-v1
backend-v2 后端服务 v2 版本 backend-v2
backend-alt 备用后端服务 backend-alt

应用配置文件:

1
kubectl apply -f manifests/gamma/echo-services-mesh.yaml

等待 Pod 就绪:

1
2
kubectl wait --timeout=90s --for=condition=Ready pods -l app=sleep
kubectl wait --timeout=90s --for=condition=Ready pods -l app=backend

查看 Pod 状态:

1
kubectl get pods -o wide

输出:

1
2
3
4
5
NAME                          READY   STATUS    RESTARTS   AGE   IP           NODE
backend-alt-97f4745c8-2rcf5 2/2 Running 0 21s 10.244.2.4 k8s-cluster-worker2
backend-v1-bbdbb648f-42b2t 2/2 Running 0 21s 10.244.1.6 k8s-cluster-worker
backend-v2-54f94c5cc9-9bnqw 2/2 Running 0 21s 10.244.1.7 k8s-cluster-worker
sleep-7cd7b75dd-s8wg9 2/2 Running 0 21s 10.244.1.5 k8s-cluster-worker

这里尤其需要注意:

  • READY 2/2: 表示 Pod 中有 2 个容器(应用容器 + Istio Sidecar)
  • Sidecar 已成功注入到所有 Pod

验证基础连通性:

1
2
3
4
5
6
7
8
# kubectl exec deploy/sleep -- curl -s backend-v1:80/
backend-v1

# kubectl exec deploy/sleep -- curl -s backend-v2:80/
backend-v2

#kubectl exec deploy/sleep -- curl -s backend-alt:80/
backend-alt

场景 1:基础 Mesh HTTPRoute 测试

接下来测试使用 HTTPRoute 将流量从 backend-v1 重定向到 backend-alt

创建 manifests/gamma/httproute-mesh-basic.yaml 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-route-basic
spec:
parentRefs:
- name: backend-v1 # GAMMA 关键: parentRefs 指向 Service
kind: Service
group: ""
port: 80
hostnames:
- "backend-v1.default.svc.cluster.local"
rules:
- backendRefs:
- name: backend-alt # 流量重定向到 backend-alt
port: 80
  • parentRefs: GAMMA 的核心概念

    • 传统 Gateway API: parentRefs 指向 Gateway(南北向流量)
    • GAMMA: parentRefs 指向 Service(东西向流量)
    • Istio 监听 Service parentRefs 的 HTTPRoute,并在 Sidecar 中配置路由
  • hostnames: 匹配目标服务的 DNS 名称

    • 格式: {service}.{namespace}.svc.cluster.local
  • backendRefs: 实际处理请求的后端服务

应用 HTTPRoute:

1
kubectl apply -f manifests/gamma/httproute-mesh-basic.yaml

查看 HTTPRoute 详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# kubectl get httproute mesh-route-basic -o yaml
......
spec:
hostnames:
- backend-v1.default.svc.cluster.local
parentRefs:
- group: ""
kind: Service
name: backend-v1
port: 80
rules:
- backendRefs:
- group: ""
kind: Service
name: backend-alt
port: 80
weight: 1
matches:
- path:
type: PathPrefix
value: /

接下来测试路由:

1
2
# kubectl exec deploy/sleep -- curl -s backend-v1:80/
backend-alt

可以看到,通过 HTTPRoute 成功将集群内发往 backend-v1 的流量重定向到 backend-alt

场景 2:服务间流量分割测试

接下来测试使用 HTTPRoute 实现服务间流量按权重分配(金丝雀发布场景)。

使用如下配置文件 manifests/gamma/backend-service.yaml 创建父 Service:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend-placeholder # 无实际 Pod,仅作为路由入口
ports:
- port: 80
targetPort: 80

使用如下配置文件 manifests/gamma/httproute-mesh-split.yaml 来通过 HTTPRoute 实现流量分割:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-route-split
spec:
parentRefs:
- name: backend # 父 Service
kind: Service
group: ""
port: 80
hostnames:
- "backend.default.svc.cluster.local"
rules:
- backendRefs:
- name: backend-v1 # 80% 流量到 v1
port: 80
weight: 80
- name: backend-v2 # 20% 流量到 v2
port: 80
weight: 20

概念说明:

  • weight: 流量权重比例

    • 权重是相对值,实际比例为 weight/(总weight)
    • 80:20 表示 80% 流量到 backend-v1,20% 到 backend-v2
  • 流量分割应用场景:

    • 金丝雀发布:新版本先接收少量流量验证
    • A/B 测试:不同版本接收不同流量比例
    • 蓝绿部署:快速切换流量比例

应用这两个配置:

1
2
kubectl apply -f manifests/gamma/backend-service.yaml
kubectl apply -f manifests/gamma/httproute-mesh-split.yaml

同样可以通过如下命令查看路由状态:

1
kubectl get httproute mesh-route-split -o yaml

接下来实际测试,判断流量是否按预期分配:

1
2
3
# for i in {1..20}; do kubectl exec deploy/sleep -- curl -s backend:80/; done | sort | uniq -c
17 backend-v1
3 backend-v2

场景 3:Header 匹配路由测试

接下来测试基于 HTTP Header 的路由匹配(版本选择场景)。首先同样创建创建一个父 Service,配置文件 manifests/gamma/backend-header-service.yaml 如下:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Service
metadata:
name: backend-header
spec:
selector:
app: backend-header-placeholder
ports:
- port: 80
targetPort: 80

创建如下基于 Header 的 HTTPRoute 配置文件 manifests/gamma/httproute-mesh-header.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: mesh-route-header
spec:
parentRefs:
- name: backend-header
kind: Service
group: ""
port: 80
hostnames:
- "backend-header.default.svc.cluster.local"
rules:
- matches:
- headers:
- name: x-version
type: Exact
value: v2
backendRefs:
- name: backend-v2 # 带 x-version: v2 header → backend-v2
port: 80
- backendRefs:
- name: backend-v1 # 无匹配 header → backend-v1
port: 80
  • headers 匹配: 基于 HTTP Header 进行路由

    • type: Exact: Header 值精确匹配
    • type: RegularExpression: Header 值正则匹配
  • 匹配逻辑:

    • 规则 1: 带有 x-version: v2 header → 路由到 backend-v2
    • 规则 2 (默认): 无匹配 header → 路由到 backend-v1
  • 应用场景:

    • 内部测试:开发人员带特定 header 访问新版本
    • 灰度发布:特定用户群体访问新版本
    • 多租户:不同 header 路由到不同租户服务

应用配置:

1
2
kubectl apply -f manifests/gamma/backend-header-service.yaml
kubectl apply -f manifests/gamma/httproute-mesh-header.yaml

实际测试路由:

1
2
3
4
# kubectl exec deploy/sleep -- curl -s backend-header:80/
backend-v1
## kubectl exec deploy/sleep -- curl -s -H "x-version: v2" backend-header:80/
backend-v2

可以看到基于 Header 的路由匹配成功。

流程总结

通过上面的几个测试场景,我们展示了如何基于 Istio 来实现 k8s 的 GAMMA,直接通过 Gateway API 定义路由规则,从而实现服务间流量管理。下图概要展示了流程总结:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
┌─────────────────────────────────────────────────────────────────────────────┐
│ GAMMA 配置流程 │
│ │
│ ┌─────────────────┐ │
│ │ 用户创建资源 │ │
│ │ HTTPRoute │ │
│ │ (parentRefs: │ │
│ │ Service) │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Istiod (控制面) │ │
│ │ │ │
│ │ 1. 监听 Kubernetes API Server │ │
│ │ - Watch HTTPRoute (关注 Service parentRefs) │ │
│ │ │ │
│ │ 2. 翻译 HTTPRoute → Envoy 配置 │ │
│ │ - parentRefs.Service → 监听器入口 │ │
│ │ - hostnames → VirtualHost │ │
│ │ - backendRefs → Cluster + Endpoint │ │
│ │ - weight → 负载均衡权重 │ │
│ │ - headers → 路由匹配规则 │ │
│ │ │ │
│ │ 3. 通过 xDS 下发配置 │ │
│ │ - xDS API: LDS/RDS/CDS/EDS │ │
│ │ - 配置同步到所有 Sidecar │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────────────────┐ │
│ │ Envoy Sidecar (数据面) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ 配置生效 │ │ │
│ │ │ - 拦截发往 parentRefs.Service 的请求 │ │ │
│ │ │ - 根据 HTTPRoute 规则路由 │ │ │
│ │ │ - 负载均衡到 backendRefs │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
  • GAMMA 实现采用 Service 作为 parentRefs
  • Istiod 监听所有 HTTPRoute,过滤出 parentRefs.kind=Service 的路由
  • 对于每个 Service parentRefs,Istiod 在 Envoy 中配置拦截该 Service 的流量
  • 流量被拦截后,根据 HTTPRoute 规则进行路由决策
  • 最终转发到 backendRefs 指定的服务

另外我们可以通过 istioctl proxy-status 命令来查看 Sidecar 的配置状态。

小结

这篇文章我们通过部署 Istio 来实际测试了 k8s 的 GAMMA 功能,并通过 Gateway API 来定义 HTTPRoute 实现集群内服务间流量(东西相流量)管理,以进一步加深对 k8s Gateway API 的理解。