使用Istio控制服务之间的流量
前言
现在有一个场景,在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/tokenHello API
url: http://192.168.100.83:31176/api/helloCall 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,拒绝访问。
Discussion
New Comments
暂无评论。 成为第一个!