侧边栏壁纸
  • 累计撰写 223 篇文章
  • 累计创建 205 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

在 Istio Service Mesh 中接入 Apache APISIX:实践与思考

zhanjie.me
2024-10-26 / 0 评论 / 0 点赞 / 1 阅读 / 0 字

1. 引言

当微服务体系接入 Service Mesh(例如 Istio)后,东西向流量可以实现统一治理,但体系外的南北向流量依然需要 API 网关。
我们团队目前在用的 API 网关是 Apache APISIX

问题在于:

  • APISIX 本身不是基于 Envoy 架构,对 Istio 的原生支持有限;
  • 业内也有替代方案,例如阿里开源的 Higress(基于 Envoy + Istio),能无缝融入 Istio,但迁移成本大,会引入风险。

因此,我们的目标是:保持 APISIX 作为前置反向代理不变,同时让其具备接入 Istio Mesh 的能力,从而获得 Istio 的治理和 mTLS 安全能力。


2. 问题与挑战

2.1 为什么不是直接换 Higress?

  • Higress 优点:与 Istio 原生无缝融合,生态一致;
  • 问题:现有大量 APISIX 插件和路由规则需要迁移,成本和风险太高。

2.2 APISIX 与 Istio 的脱节

  • APISIX 自身具备路由、负载均衡等功能,但这些能力与 Istio 重叠;
  • 如果两者叠加,可能出现“双重治理”,规则冲突,带来运维复杂性。

2.3 两种可选方案

  1. 在 APISIX 与服务之间增加 Istio IngressGateway

    • 流量走 APISIX → IngressGateway → 服务网格;
    • 缺点:多一跳流量转发,链路更长,架构复杂度增加。
  2. 在 APISIX Pod 注入 Istio Sidecar

    • Envoy 接管 APISIX 出站流量;
    • APISIX 仅负责入口代理,放弃上游治理;
    • 流量在 APISIX → Envoy 出站时进入 Istio Mesh;
    • 可直接启用 mTLS,统一安全。

📌 最终选择了 方案 2,因为它能实现零信任安全(mTLS),并与现有架构改动最小。


3. 技术选型与设计思路

3.1 APISIX 注入 Sidecar 的差异

普通微服务注入 Sidecar 时,会让 Envoy 劫持入站 + 出站流量。
APISIX 作为网关入口,需要排除自身监听的 80/443 端口,避免入站流量被二次代理。

annotations:
  traffic.sidecar.istio.io/excludeInboundPorts: "80,443"

同样,部分端口或 IP 范围(如 OTel 上报端口 4317/4318)也需要排除。


3.2 Service 与 FQDN 对齐

  • 微服务必须暴露为 ClusterIP Service
  • APISIX 的 upstream 使用该 Service 的 FQDN;
  • Envoy 基于 RDS(Route Discovery Service)匹配 host,才能触发 VirtualService 规则。

3.3 APISIX 与 Envoy 职责划分

flowchart LR Client --> APISIX APISIX --> EnvoySidecar EnvoySidecar --> Service
  • APISIX:只做入口代理、前置 header 注入、简单重写。
  • Envoy:负责服务发现、路由、熔断、mTLS、安全策略。

这样可以避免功能重叠,让 APISIX 专注在 API 层,Envoy 专注在 Mesh 治理层。


4. 实施步骤

4.1 APISIX Pod 注入 Sidecar

配置 Pod 注解,排除入口端口:

  • traffic.sidecar.istio.io/excludeInboundPorts: 入站流量劫持排除端口
  • traffic.sidecar.istio.io/excludeOutboundIPRanges: 出站流量劫持排除IP
  • traffic.sidecar.istio.io/excludeOutboundPorts: 出站流量劫持排除端口
  • istio.io/rev: default: 开启sidecar注入
metadata:
  annotations:
    traffic.sidecar.istio.io/excludeInboundPorts: "80,443"
    traffic.sidecar.istio.io/excludeOutboundPorts: "4317,4318"
    traffic.sidecar.istio.io/excludeOutboundIPRanges: "192.168.49.18/32"
  labels:
    istio.io/rev: default

4.2 微服务 ClusterIP Service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: seller
    run_env: test1
    svc_type: tourism
  name: tourism-seller
  namespace: test1-microsvc
spec:
  type: ClusterIP
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  ports:
    - name: service
      port: 8080
      protocol: TCP
      targetPort: 8080
  selector:
    app: seller
    run_env: test1
    svc_type: tourism

4.3 APISIX Upstream 配置

将APISIX的upstream从基于k8s的服务注册与发现切换成基于节点的域名解析, upstream 指向微服务的 FQDN,并在 header 中注入 baggage

{
  "uri": "/api/*",
  "name": "xxxx",
  "methods": [
    "GET",
    "POST"
  ],
  "host": "istio.example.com",
  "plugins": {
    "proxy-rewrite": {
      "headers": {
        "trace_id": "$http_traceparent",
        "baggage": "canary=true,tenant=foo"
      },
      "scheme": "http"
    },
    "redirect": {
      "http_to_https": true
    }
  },
  "upstream": {
    "nodes": [
      {
        "host": "tourism-seller.test1-microsvc.svc.cluster.local",
        "port": 8080,
        "weight": 1
      }
    ],
    "retries": 0,    // 把重试交给 Envoy(若走 Ingress 或入网格)
    "timeout": {
      "connect": 2,  // 内网直连 2s 足够
      "send": 10,
      "read": 70     // >= VS 总预算(例如 60s)稍加余量
    },
    "type": "roundrobin",
    "scheme": "http",
    "pass_host": "rewrite",
    "upstream_host": "tourism-seller.test1-microsvc.svc.cluster.local",
    "keepalive_pool": {
      "idle_timeout": 60,
      "requests": 1000,
      "size": 320
    }
  }
}

📌 注意:

  • 重试交给 Envoy,不再由 APISIX 处理;
  • baggage header 用于后续 Istio VirtualService 做流量染色。

4.4 Istio 路由配置

DestinationRule:定义负载均衡、连接池、熔断策略。
VirtualService:基于 baggage header 分流。

flowchart LR APISIX --> Envoy -->|baggage=canary| CanarySubset Envoy -->|default| StableSubset

4.4.1 创建 DestinationRuleVirtualService

DestinationRuleVirtualService 的host要和APISIX的upstream配置对齐

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: tourism-seller
  namespace: test1-microsvc
spec:
  host: tourism-seller.test1-microsvc.svc.cluster.local

  trafficPolicy:
    # 端口级:8080 明文(HTTP/1.1),按照是否使用 mTLS 进行放开
    #portLevelSettings:
    #  - port:
    #      number: 8080
    #    tls:
    #      mode: DISABLE

    # 负载均衡(HTTP/1.1 下推荐,减轻排队热点)
    loadBalancer:
      simple: LEAST_REQUEST   # 如需最简单可改为 ROUND_ROBIN

    # 连接池(HTTP/1.1 特化;显式禁用 h2 升级)
    connectionPool:
      tcp:
        connectTimeout: 3s
        maxConnections: 1024
        tcpKeepalive:
          time: 60s
          interval: 30s
          probes: 3
      http:
        h2UpgradePolicy: DO_NOT_UPGRADE  # 确认走 HTTP/1.1
        http1MaxPendingRequests: 1000    # 入队上限,防止过度排队
        maxRequestsPerConnection: 100    # 合理复用,避免长连接过大
        idleTimeout: 65s                 # 空闲断开时间,略大于常用 perTryTimeout

    # 熔断/逐出(使用新字段)
    outlierDetection:
      consecutive5xxErrors: 5
      consecutiveGatewayErrors: 5
      interval: 5s
      baseEjectionTime: 30s
      maxEjectionPercent: 50

  subsets:
    - name: stable
      labels:
        traffic.microsvc.io/track: stable
    - name: canary
      labels:
        traffic.microsvc.io/track: canary
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: tourism-seller
  namespace: test1-microsvc
spec:
  hosts:
    - tourism-seller
    - tourism-seller.test1-microsvc.svc
    - tourism-seller.test1-microsvc.svc.cluster.local
  gateways:
    - mesh
  http:
    - name: canary-by-baggage
      match:
        - headers:
            baggage:
              regex: '.*(^|[,])\s*canary=true(\s*(;[^,]+)?)(,|$).*'
      timeout: 60s
      retries:
        attempts: 2
        perTryTimeout: 5s
        retryOn: "5xx,gateway-error,connect-failure,refused-stream,reset"
      route:
        - destination:
            host: tourism-seller.test1-microsvc.svc.cluster.local
            subset: canary
          weight: 100

    - name: default-stable
      timeout: 60s
      retries:
        attempts: 2
        perTryTimeout: 5s
        retryOn: "5xx,gateway-error,connect-failure,refused-stream,reset"
      route:
        - destination:
            host: tourism-seller.test1-microsvc.svc.cluster.local
            subset: stable
          weight: 100

5. 流量治理与测试

5.1 流量分流

  • 携带 baggage=canary=true → 路由到 canary 子集;
  • 无 baggage → 路由到 stable 子集。

5.2 mTLS 验证

  • 确认 APISIX → Envoy → 服务链路使用双向 TLS;
  • Istio PeerAuthentication 策略生效,流量拒绝非 mTLS。

5.3 熔断与重试

  • 模拟 canary Pod 异常,Envoy 会逐出实例并重试请求。

6. 拓展与思考

6.1 APISIX vs Higress

  • Higress 更贴合 Istio,但迁移成本高;
  • APISIX + Sidecar 是 平滑演进方案

6.2 南北向与东西向流量统一

  • APISIX 管理南北向流量;
  • Istio 管理东西向流量;
  • 双方通过 Sidecar 集成,形成统一治理面。

6.3 后续演进

  • 可以逐步把部分 APISIX 插件迁移到 Istio EnvoyFilter;
  • 最终形成 API 网关 + Service Mesh 一体化治理

7. 总结

通过在 APISIX Pod 注入 Istio Sidecar,我们实现了:

  • 让 APISIX 保持网关角色不变;
  • 让出站流量进入 Istio Mesh;
  • 统一应用了 Istio 的 mTLS、熔断、路由、流量染色等能力。

这是一个 低成本、低风险的演进方案

  • 短期:保留现有 APISIX 生态,快速接入 Istio;
  • 长期:可以平滑过渡到更深度的 Istio 网关方案(如 Higress)。

📌 总结一句:我们没有替换 APISIX,而是让它“入网格”,成为 Mesh 的一部分。

0

评论区