文章目錄
-
- Istio流量劫持機制
- 環境介紹
-
- 注入Sidecar
- Init container
- istio-proxy container
- 用戶端請求
-
- Iptables規則
- Envoy Config
- 服務端接收請求
- Service Mesh 涉及的網絡棧
Istio流量劫持機制
在Service Mesh架構中,Istio通過流量劫持機制,讓Pod中的應用通路其他服務時都經過Istio代理,實作對流量的控制。基于Istio可以實作請求路由、服務發現和負載均衡、故障處理、故障注入和規則配置等功能。,因為所有Pod中的進出流量都經過Istio的資料面,是以也可以實作日志記錄和追蹤。如下是Istio的架構圖:
下邊通過一個示例操作,看下Istio是怎麼實作流量劫持的,流量被劫持後又是怎麼轉到目的地的。
環境介紹
部署兩個Pod,分别表示用戶端和服務端,通過用戶端發起請求,然後看請求整個鍊路的轉發過程。
# 建立一個namespace
kubectl create ns sidecar
# 注入sidecar
kubectl label ns sidecar istio-injection=enabled
# 部署nginx pod
kubectl apply -f nginx.yaml -n sidecar
# 部署toolbox pod
kubectl apply -f toolbox.yaml -n sidecar
- nginx.yaml
部署一個nginx pod,并建立service。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
---
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
- toolbox.yaml
toolbox其實就是一個centos,可以在其中執行curl發送請求。
apiVersion: apps/v1
kind: Deployment
metadata:
name: toolbox
spec:
replicas: 1
selector:
matchLabels:
app: toolbox
template:
metadata:
labels:
app: toolbox
access: "true"
spec:
containers:
- name: toolbox
image: centos
command:
- tail
- -f
- /dev/null
執行
kubectl get pod,deployment,svc,endpoints -n sidecar -o wide
看下環境情況:
- 可以看到兩個Pod分别為 nginx-deployment-85b98978db-48m47(IP:10.10.1.5) 和 toolbox-78555898fb-b9qxq(IP:10.10.1.6)
- 建立的Service的IP為10.102.37.221
-
Service的Endpoints目前有一個10.10.1.5,就是運作Nginx的那個Pod,當然,也可以建立多個服務,統一提供服務。
因為我現在登入在Kubernetes叢集的Master節點,其實作在就可以執行
通路Nginx服務。curl http://10.102.37.221
注入Sidecar
剛才建立namespace後,執行了
kubectl label ns sidecar istio-injection=enabled
可以看下sidecar namespace的配置,執行
kubectl get ns sidecar -oyaml
:
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2022-09-24T08:12:14Z"
labels:
istio-injection: enabled
kubernetes.io/metadata.name: sidecar
name: sidecar
resourceVersion: "8032"
uid: 3fdb6ab1-71b6-44ae-83c8-636dfa266147
spec:
finalizers:
- kubernetes
status:
phase: Active
通過把istio-injection設定為enabled,這樣在本namespace中的pod都會注入Sidecar。注入Sidecar後Pod有什麼變化呢,可以看下 toolbox-78555898fb-b9qxq 這個pod的運作狀态:
其中READY中都是2/2,說明Pod中有兩個容器,并且狀态都是running,檢視Pod中都是什麼容器。
檢視Pod裡初始化容器:
kubectl get pods toolbox-78555898fb-b9qxq -n sidecar -o jsonpath={.spec.initContainers[*].name}
輸出為:istio-init
檢視Pod裡業務容器:
kubectl get pods toolbox-78555898fb-b9qxq -n sidecar -o jsonpath={.spec.containers[*].name}
輸出:toolbox 和 istio-proxy
Init container
通過檢視 toolbox-78555898fb-b9qxq 這個pod的配置,在配置中可以看到istio-init的容器配置資訊:
容器中執行了
istio-iptables
配置了iptables規則,也正是因為這個配置劫持了Pod進出的流量,下邊我們再仔細看規則的内容。istio-init 在Pod啟動時執行,執行完就自動退出了。
istio-proxy container
istio-proxy在Pod主要做流量代理,通過運作Envoy,可以根據Envoy配置實作流量的靈活控制。配置内容比較多這裡就不展示了。是以 toolbox-78555898fb-b9qxq 中展示READY的兩個容器就是toolbox 和 istio-proxy。
用戶端請求
在 toolbox-78555898fb-b9qxq 中通路 nginx-deployment-85b98978db-48m47,這樣在用戶端和服務端中都會發生流量劫持。
[email protected]:~$ kubectl exec -it toolbox-78555898fb-b9qxq -n sidecar sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
sh-4.4# curl http://10.102.37.221
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/" target="_blank" rel="external nofollow" >nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/" target="_blank" rel="external nofollow" >nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
執行上邊操作後,請求從用戶端到服務端的大概流程如下:
其中有兩個地方被iptables規則劫持:
- 發起curl請求預設請求的是80端口,但是會被iptables OUTPUT劫持轉發到envoy監聽的15001端口
- 請求到達Nginx時,目标端口為80,但是會被iptables PREROUTING劫持轉到envoy監聽的15006端口。
Iptables規則
下邊看下iptables規則的内容是什麼,因為Pod中的指令不全,是以使用nsenter指令在主控端上進入容器檢視。首先擷取容器的PID
我的Kubernetes環境分為master和node節點,容器是運作在node節點,是以就在node節點操作
根據上圖,toolbox的容器Pid為36009:
sudo nsenter -t 36009 -n iptables-legacy-save
檢視到的iptables規則如下:
# Generated by iptables-save v1.8.7 on Sat Sep 24 23:53:39 2022
*nat
:PREROUTING ACCEPT [9019:541231]
:INPUT ACCEPT [9012:540720]
:OUTPUT ACCEPT [722:64866]
:POSTROUTING ACCEPT [725:65046]
:ISTIO_INBOUND - [0:0]
:ISTIO_IN_REDIRECT - [0:0]
:ISTIO_OUTPUT - [0:0]
:ISTIO_REDIRECT - [0:0]
# 入流量比對這條,走到ISTIO_INBOUND
-A PREROUTING -p tcp -j ISTIO_INBOUND
# 出流量比對這條,走到ISTIO_OUTPUT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp -m tcp --dport 15008 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp -m tcp --dport 15020 -j RETURN
# 入流量比對這條,走到ISTIO_IN_REDIRECT
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
# 目标端口轉換為 15006,針對curl請求目标端口 80 --> 15006
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A ISTIO_OUTPUT -s 127.0.0.6/32 -o lo -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT ! -d 127.0.0.1/32 -o lo -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
# 以上的都不比對,走到ISTIO_REDIRECT
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
# 目标端口轉換為15001,針對curl請求目标端口 80 --> 15001
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
COMMIT
# Completed on Sat Sep 24 23:53:39 2022
在toolbox中發起
curl http://10.102.37.221
,預設的端口為80,經過iptables OUTPUT規則目标端口轉換為15001,請求被轉到envoy,在envoy中有listener監聽15001。
Envoy Config
- listener 15001
請求到達Envoy,被監聽的15001接收,下邊看看Envoy listener 15001的配置,同樣配置較多,隻展示關鍵部分:
istioctl pc listener -n sidecar toolbox-78555898fb-b9qxq --port 15001 -ojson
{
"name": "virtualOutbound",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 15001
}
},
"useOriginalDst": true
}
配置中的useOriginalDst設定為true,表示擷取原始目的端口,就是被iptables規則轉換前的80端口,看到這個listener的配置為virtualOutbound,就像它的名字一樣是個虛拟機,是以擷取出來原始的端口80後,就會再被listener 80配置處理。
- listener 80
同樣,我們看下listener 80的配置:
istioctl pc listener -n sidecar toolbox-78555898fb-b9qxq --port 80 -ojson
隻看關鍵配置:
{
"name": "0.0.0.0_80",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 80
}
},
"routeConfigName": "80"
}
經過listener 80後,比對routeConfigName “80”,繼續看route。
- route “80”
執行
istioctl pc route -n sidecar toolbox-78555898fb-b9qxq --name=80 -ojson
檢視如下:
{
"name": "nginx.sidecar.svc.cluster.local:80",
"domains": [
"nginx.sidecar.svc.cluster.local",
"nginx.sidecar.svc.cluster.local:80",
"nginx",
"nginx:80",
"nginx.sidecar.svc",
"nginx.sidecar.svc:80",
"nginx.sidecar",
"nginx.sidecar:80",
"10.102.37.221",
"10.102.37.221:80"
],
"routes": [
{
"name": "default",
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|80||nginx.sidecar.svc.cluster.local",
根據上邊的配置,請求會選擇
"cluster": "outbound|80||nginx.sidecar.svc.cluster.local"
處理。現在我們就看下選擇的cluster正式的endpoint是什麼。
- endpoints
執行如下指令過濾出我們需要的endpoints:
istioctl pc endpoints -n sidecar toolbox-78555898fb-b9qxq --cluster="outbound|80||nginx.sidecar.svc.cluster.local" -ojson
根據配置可以看到最終選擇後端為10.10.1.5,就是我們部署的nginx Pod的位址。
{
"name": "outbound|80||nginx.sidecar.svc.cluster.local",
"addedViaApi": true,
"hostStatuses": [
{
"address": {
"socketAddress": {
"address": "10.10.1.5",
"portValue": 80
}
},
"stats": [...],
"healthStatus": {
"edsHealthStatus": "HEALTHY"
},
"weight": 1,
"locality": {}
}
],
"circuitBreakers": {...},
"observabilityName": "outbound|80||nginx.sidecar.svc.cluster.local"
}
經過以上流程,請求就從toolbox Pod中出去了,請求的目标位址變為 10.10.1.5,目标端口為80。
服務端接收請求
請求到達服務端一樣會經過iptables規則和Envoy代理,才能最終到達Nginx。分析過程和用戶端一樣,就不再贅述。
Service Mesh 涉及的網絡棧
上邊的請求流程,在兩個Pod中都經過了多次協定棧的處理。
就像上圖一樣,請求一次要經過多次網絡棧的處理,對性能肯定會有影響,既然有問題,就會出現技術去解決,Cilium就可以實作加速,具體架構圖如下:
Cilium底層基于Linux核心的新技術eBPF,保持好奇心,多多探索吧,哈哈。