使用Istio控制服务之间的流量

2025年8月6日

前言

现在有一个场景,在Kubernetes 部署了两个Pod,  两个Pod都有一个API给互相调用,但互相调用的 API 不希望暴露给外部,两个 Pod 本身的其他 API 是需要对外暴露的。
可以使用 “Istio" 来实现. 首先我们先了解 Istio 是什么,可以做什么事。
Istio 是一个开源的 服务网格(Service Mesh) 平台,可以 Kubernetes 中在实现:

功能说明
1.流量管理控制服务之间的流量,支持 A/B 测试、金丝雀发布、灰度发布等
2.安全通信实现服务间 双向 TLS(mTLS)加密通信,服务身份验证,细粒度的访问控制
3.可观测性提供详细的日志、追踪、指标,支持 Prometheus、Grafana、Jaeger、Kiali 等集成
4.策略控制 控制请求的访问策略,如 RBAC(基于角色的访问控制)
故障恢复支持熔断、重试、超时等服务容错策略

这里我们使用 Istio 的流量管理的功能。

1. 需求设计

需要对外暴露的API路径我们以 “/api" 开头,内部调用的API路径我们以 “/internal" 开头

接口路径来源是否鉴权是否暴露外部
/api/*外部访问需要
/internal/*集群内服务访问不需要

外部访问非 “/api/*" 的API都拒绝访问,同时允许内部访问 “/internal/*” 下的接口,实现精准控制。

2. 安装 Istio,开启Istio sidecar 注入

2.1 Supported Kubernetes Versions

我们使用Istioctl 工具来Istio, 需要注意请选择对应 支持Kubernetes 的版本,点击查看最新

我的 Kubernetes 版本为 1.28.2, 所以我这里选择 1.24.x 版本的 Istio安装.

2.2 Install Istio

先安装 Istioctl工具,以下命令会默认安装最新版本的 Istio版本。

curl -L https://istio.io/downloadIstio | sh -

因为我需要安装 1.24.x 版本的 Istio, 所以我需要指定安装版本,先查看 1.24.x 版本

指定安装 1.24.6 版本

export ISTIO_VERSION=1.24.6
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.24.6
export PATH=$PWD/bin:$PATH

确认版本

istioctl Version

正式安装

istioctl install --set profile=demo -y

开启 namespace 的 sidecar 自动注入,后续部署的Pod Istio 会自动注入 Istio-proxy 容器, 之前部署的Pod需要重新部署

kubectl label namespace python istio-injection=enabled

验证 Istio sidecar 注入是否成功

kubectl describe pod fastapi-jwt-01-5b6f8bf8f8-68dxg -n python

在 Pod 的 Containers 中看到 istio-proxy ,代表注入成功

3. Istio Gateway + VirtualService 配置

使用Istio Gateway + VirtualService 配置使/api/* 对外暴露、其余拒绝,/internal/* 仅允许同一NameSpace下的Pod访问

Dataway 配置:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: python-gateway
  namespace: python
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

解释:

number: 80:表示 Ingress Gateway 监听端口号是 80,就是你对外访问的入口端口
name: http:端口的名称
protocol: HTTP:说明这个端口接收的是 HTTP 请求(也可以是 HTTPS、TLS 等)
hosts: "*": 表示匹配所有主机名, 也可以写域名

VirtualService 配置:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: python-vs
  namespace: python
spec:
  hosts:
  - "*"
  gateways:
  - python-gateway
  http:
  - match:
    - uri:
        prefix: /api/
    route:
    - destination:
        host: fastapi-jwt-svc-01
        port:
          number: 80

解释:

 gateways:
  - python-gateway

绑定到python-gateway

prefix: /api/

只运行访问 /api/* 接口路径

- destination:
        host: fastapi-jwt-svc-01

转发到对应的 service

port:
  number: 80

请注意这里的port 是 service内部访问的端口,不是service Nodeport端口

4. AuthorizationPolicy 实现访问控制

4.1 拒绝所有未匹配规则(默认拒绝)

未匹配到VirtualService规则,默认都拒绝掉

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: python
spec:
  {}

4.2 允许外部调用 /api/*

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-api-from-ingress
  namespace: python
spec:
  selector:
    matchLabels:
      app: fastapi-jwt-01
  action: ALLOW
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
    to:
    - operation:
        paths: ["/api/*"]

解释:

principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]

服务账号是 istio-ingressgateway-service-account,Istio Ingress Gateway 默认使用它

4.3 允许同一NameSpace下 Pod 访问 /internal/*

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: allow-internal
  namespace: python
spec:
  selector:
    matchLabels:
      app: fastapi-jwt-02
  action: ALLOW
  rules:
  - from:
    - source:
        namespaces: ["python"]
    to:
    - operation:
        paths: ["/internal/*"]

解释:

matchLabels:
      app: fastapi-jwt-02

注意这里因为是Pod A 想要 访问 Pod B , 所以 selector.matchLabels 应该匹配 Pod B 的标签.

5. 测试

我们先查看Ingress Dateway 端口

smolx@k8s-master:~/config/python-ptoject-config$ kubectl get svc -n istio-system
NAME                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
istio-egressgateway    ClusterIP   10.108.217.219   <none>        80/TCP,443/TCP                                                               33h
istio-ingressgateway   NodePort    10.106.142.146   <none>        15021:31935/TCP,80:31176/TCP,443:31999/TCP,31400:30264/TCP,15443:30130/TCP   33h
istiod                 ClusterIP   10.106.39.220    <none>        15010/TCP,15012/TCP,443/TCP,15014/TCP                                        33h
smolx@k8s-master:~/config/python-ptoject-config$

可以看到istio-ingressgateway 80端口映射为31176,这里安装好 istio-ingressgateway type 默认为 LoadBalancer, 因为我不是云环境,云环境会自动分配公网IP,这里我修改为了Nodeport方式,不用修改应该也可以,80端口已经映射出来了。

kubectl -n istio-system patch svc istio-ingressgateway \
  -p '{"spec": {"type": "NodePort"}}'

所以我们通过 http://<任意集群节点IP>:31176去调用 API

测试我为了方便,简单写了几个接口,api下有三个接口:

/api/token: get Token
/api/hello: 验证token是否有效,拿到payload里面的信息
/api/call: 这里去另外一个pod的internal的接口

我部署两个两个Pod,代码一模一样,/api/call 通过 service name 固定调用fastapi-jwt-svc-02的internal接口,方便测试

@router.get("/call")
def call(user: dict = Depends(get_current_user)):
    url = "http://fastapi-jwt-svc-02:80/internal/send/1"
    try:
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            return {"message": "success!", "code": response.status_code, "data": data}

    except Timeout:
        return {"message": "请求超时!", "code": 10001, "data": None}
    except HTTPError as e:
        return {"message": f"HTTP 错误!+ {e}", "code": 10002, "data": None}
    except ConnectionError:
        return {"message": "连接失败!", "code": 10003, "data": None}
    except RequestException as e:
        return {"message": f"请求异常! + {e}", "code": 10004, "data": None}
@router.get("/send/{item_id}")
def send(item_id: int):
    print(item_id)
    return {"item_id": item_id}

好,现在通过调用 fastapi-jwt-01的 api 接口来测试以下

Get Token API

url: http://192.168.100.83:31176/api/token

Hello API

url: http://192.168.100.83:31176/api/hello

Call API

url: http://192.168.100.83:31176/api/call

这里可以看到调用成功了,我们看一下两个Pod的日志, 从日志可以看出 fastapi-jwt-01 pod 成功调用到了fastapi-jwt-02 pod的接口

查看 Dataway 日志

kubectl logs -l istio=ingressgateway -c istio-proxy -n istio-system

可以看到所有通过 Gateway 的请求,包括 URI、状态码、请求时间等

我们看一下如果直接请求 /internal/ 下的接口会怎么样

url: http://192.168.100.83:31176/internal/send/1

会直接提示 404,访问

我们也可以直接在浏览器访问FastAPI自带的SwaggerUI页面

也是一样提示404,无法访问

fastapi-jwt-01 通过Nodeport方式暴露端口,端口为 31953,我们也可以试试 http://<节点IP>:31953 直接调用接口

直接提示 RBAC: access denied,拒绝访问。

Kubernetes

Posted by Thomas