流量鏡像
流量鏡像,也稱為影子流量,流量鏡像提供一種盡可能低的風險為生産帶來變化的強大功能。鏡像會将實時流量的副本發送到鏡像服務。鏡像流量發生在主服務的關鍵請求路徑之外。
在非生産或者測試環境中,嘗試通路一個服務所有可能的測試用例組合是個非常不現實的任務。 在某些情況下,編寫這些用例的所有工作也可能與實際生産所需的用例不比對。在理想情況下,可以使用實時的生産用例和流量來幫助完善在測試環境中錯過的功能區域。
一旦我們能夠可靠地鏡像流量,就可以開始做一些有價值的事情,例如通過請求流量對比工具Diffy,可以将引入測試叢集的流量與生産叢集中的預期行為進行比較。例如,我們可能想比較請求結果與預期結果間的偏差,或是API協定中的資料損壞情況,以便更好地相容。
除此之外,需要注意:
- 當流量鏡像到不同的服務時,會發生在請求的關鍵路徑之外;
- 忽略對任何鏡像流量的響應; 流量被視為“即發即忘”;
流量對比
此處,插入一個代理就可以負責此類流量的協調,并對其進行有趣的比較。
Diffy就是一款這樣的代理工具。Diffy啟動一個代理服務(例如監聽端口8880),再根據使用者設定的primary、secondary兩個舊服務位址(primary和secondary代碼完全相同,目的是為了減少噪音幹擾)、candidate新服務位址。
它還能夠檢測結果中的噪音,并通過先調用兩個實時服務的執行個體來忽略它們(例如時間戳,單調遞增計數器等提示),總結來說就是檢測,然後在測試服務中忽略掉這部分。
Diffy還提供了一個不錯的頁面可以用來檢視調用結果、對比情況、和基于某些特征的過濾。它還有一個很好的管理控制台,可以檢視有關調用比較結果的功能名額(metrics)和統計資料(statistics)。
建立用于Istio流量鏡像的服務
在此任務中,将首先強制所有流量到 v1 版本的服務。然後,将使用規則将一部分流量鏡像到 v2版本。
首先部署兩個版本的示例服務。
版本1的部署使用了Docker鏡像httpbin,提供常見的http請求通路:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mirrorservice-sample-v1
spec:
replicas: 1
template:
metadata:
labels:
app: mirrorservice-sample
version: v1
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: mirrorservice-sample
command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:44134", "httpbin:app"]
ports:
- containerPort: 44134
版本2的部署使用了自定義的Docker鏡像,對應的Dockerfile如下:
FROM nginx:latest
COPY default.conf /etc/nginx/conf.d/
EXPOSE 80
所需的nginx 配置檔案:
server {
listen 44134;
server_name localhost;
location / {
proxy_pass http://httpbin-diffy.diffy:8880/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
版本2的部署作為Istio的流量鏡像目标,在接收到流量之後會轉發到Diffy的代理中。目前沒有直接将Diffy代理作為Isito流量鏡像目标,原因是Diffy代理與Envoy代理目前本身有沖突,無法正常流量轉發,是以需要此部署中轉一下。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mirrorservice-sample-v2
spec:
replicas: 1
template:
metadata:
labels:
app: mirrorservice-sample
version: v2
spec:
containers:
- name: mirrorservice-sample
image: registry.cn-beijing.aliyuncs.com/wangxining/mirrorservice:0.1
imagePullPolicy: Always
ports:
- containerPort: 44134
對應的Kubernetes service:
apiVersion: v1
kind: Service
metadata:
name: mirrorservice-sample
spec:
type: ClusterIP
ports:
- name: http
port: 44134
selector:
app: mirrorservice-sample
建立流量鏡像的Istio政策
預設情況下,Kubernetes 在服務的兩個版本之間進行負載均衡。建立如下流量鏡像規則将 100% 的流量發送到 v1, 同時指定流量鏡像到v2。當流量被鏡像時,請求将通過其主機/授權報頭發送到鏡像服務附上 -shadow 。
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: mirrorservice-sample
spec:
host: mirrorservice-sample
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: mirrorservice-sample
spec:
hosts:
- mirrorservice-sample
http:
- route:
- destination:
host: mirrorservice-sample
subset: v1
weight: 100
#- destination:
# host: mirrorservice-sample
# subset: v2
# weight: 0
mirror:
host: mirrorservice-sample
subset: v2
搭建Diffy用于請求流量對比
Diffy可以作為代理,截取請求并發送至所有運作的服務執行個體,通過對比響應結果來發現每次疊代代碼中可能存在的問題。其中,Diffy上運作了三類代碼執行個體:
- 線上穩定版本:一個運作線上穩定版本代碼的節點
- 線上穩定版本備份:同樣運作了線上的穩定版本,用于消除噪音
- 測試版本:待上線的測試版本,用于和線上環境代碼進行對比
在實際Diffy測試中,會發現大部分的接口都會有一定差異,原因是這些響應中存在了噪音,噪音可能包括:
- server響應中生成的時間戳
- 随機生成的數字
- 系統服務間的有條件競争
Diffy能夠通過一定的方式,清除這類噪音,保證分析結果不被影響。
建立Diffy及示例服務
通過以下YAML建立Diffy服務:
apiVersion: v1
kind: Service
metadata:
name: httpbin-diffy
labels:
app: httpbin-diffy
spec:
ports:
- name: http-proxy
port: 8880
- name: http-admin
port: 8881
- name: http-console
port: 8888
selector:
app: httpbin-diffy
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
app: httpbin-diffy
version: v2
name: httpbin-diffy-v2
spec:
replicas: 1
selector:
matchLabels:
app: httpbin-diffy
version: v2
template:
metadata:
labels:
app: httpbin-diffy
version: v2
spec:
containers:
- image: lordofthejars/diffy:1.0
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- curl
- localhost:8888
initialDelaySeconds: 10
periodSeconds: 60
timeoutSeconds: 1
name: httpbin-diffy
args: ["-candidate=httpbin-candidate:8080", "-master.primary=httpbin-master:8080", "-master.secondary=httpbin-master:8080", "-service.protocol=http", "-serviceName=httpbin", "-proxy.port=:8880", "-admin.port=:8881", "-http.port=:8888", "-rootUrl='localhost:8888'"]
ports:
- containerPort: 8888
name: http-console
protocol: TCP
- containerPort: 8880
name: http-proxy
protocol: TCP
- containerPort: 8881
name: http-admin
protocol: TCP
readinessProbe:
exec:
command:
- curl
- localhost:8888
initialDelaySeconds: 10
periodSeconds: 60
timeoutSeconds: 1
securityContext:
privileged: false
通過以下YAML建立示例所用的primary、secondary(目前示例中與primary相同)與candidate服務:
apiVersion: v1
kind: Service
metadata:
name: httpbin-master
labels:
app: httpbin-master
spec:
ports:
- name: http
port: 8080
selector:
app: httpbin
version: v1
---
apiVersion: v1
kind: Service
metadata:
name: httpbin-candidate
labels:
app: httpbin-candidate
spec:
ports:
- name: http
port: 8080
selector:
app: httpbin
version: v2
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: httpbin-v1
spec:
replicas: 1
template:
metadata:
labels:
app: httpbin
version: v1
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:8080", "httpbin:app"]
ports:
- containerPort: 8080
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: httpbin-v2
spec:
replicas: 1
template:
metadata:
labels:
app: httpbin
version: v2
spec:
containers:
- image: docker.io/kennethreitz/httpbin
imagePullPolicy: IfNotPresent
name: httpbin
command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:8080", "httpbin:app"]
ports:
- containerPort: 8080
發送流量進行鏡像驗證
啟動 sleep 服務,這樣就可以使用 curl 來提供負載:
cat <<EOF | istioctl kube-inject -f - | kubectl create -f -
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: tutum/curl
command: ["/bin/sleep","infinity"]
imagePullPolicy: IfNotPresent
EOF
進入到SLEEP_POD, 具體POD名稱根據實際指派。
kubectl exec -it $SLEEP_POD -c sleep sh
發送流量:
curl -v http://mirrorservice-sample:44134/headers
可以檢視 v1的通路日志記錄,如下所示建立的請求100%指向了v1。
與此同時,檢視Diffy的Web界面,可以看到建立的請求也被鏡像到Diffy Proxy:
結論
流量鏡像提供一種盡可能低的風險為生産帶來變化的強大功能。鏡像會将實時流量的副本發送到鏡像服務,鏡像流量發生在主服務的關鍵請求路徑之外。一旦我們能夠可靠地鏡像流量,就可以開始做一些有價值的事情,例如通過請求流量對比工具Diffy,可以将引入測試叢集的流量與生産叢集中的預期行為進行比較。
支援流量鏡像隻是 Istio 的衆多功能之一,它将使基于大型微服務的應用程式的生産部署與管理變得更加簡單。歡迎大家使用阿裡雲上的容器服務,快速搭建微服務的開放治理平台Istio,比較簡單地內建到自己項目的微服務開發中。