? 
編者按:本文源自阿里云云效團隊出品的《阿里巴巴DevOps實踐指南》,前往:https://developer.aliyun.com/topic/devops,下載完整版電子書,了解阿里十年DevOps實踐經驗,
開發一個需求,需要先進行代碼的撰寫和個人驗證,驗證功能符合預期之后,再提交代碼,并進入到集成環境,進行進一步的驗證及驗收,而這個編碼和驗證的程序占據了整個需求交付的大部分時間,因此提高這部分作業的效率就顯得至關重要,
問題
有什么因素降低了開發除錯的效率呢?
給定下面一個系統,其中為了開發某個需求,修改了 A 和 D 這兩個應用(這里的應用指的是一個可提供服務的一組獨立行程加上可選的負載均衡,比如一個 kubernetes 下的 service 及其后端的 deployment),

接下來看看為了本地調測這兩個應用,會遇到什么問題,
本地難以啟動整個系統
我們通常都在開發一個復雜系統中的一個應用,這個應用可能在系統的最前端,也可能在系統的中間位置,有時候為了端到端驗證整個流程,需要把相關的應用都啟動起來,
比如上圖中的應用 A 為最前端應用,應用 D 處在中間位置,而黑框中部分是為了完整的測驗這個需求而涉及到的應用,如果是 Java 應用,開發機上啟動這樣 5 個行程,就已經不堪重負了,而很多時候需要完整啟動的應用數量會遠大于這個數字,
依賴系統不穩定
既然不能把整個系統都在本地啟動起來,那么本地就會一部分依賴于公共測驗環境,雖然前面提到應該本地測驗符合預期之后再把代碼部署到測驗環境,但不可避免的還是會出現一些 bug,導致測驗環境不可用(這也是測驗環境的價值所在,盡早的發現問題),一旦依賴系統不可用,就無法正常的進行測驗,
云原生開發模式下的測驗環境的連通性
在基于 Kubernetes 的基礎設施下,整個系統中大部分的應用通常不需要通過 Ingress 暴露到公網,如果你的測驗環境是獨立的 K8S 集群,那就意味著無法從本地無法訪問到集群內的應用,那么依賴公共測驗環境這件事情都無法進行,比如上圖中 A->C,D->E,D->F 的依賴,
還有另外一種依賴,即上游應用對本地應用的依賴,比如 C->D 的依賴,但因為 C 是公共測驗環境,不可以將所有的 C 對 D 的請求都打到本地來,這就需要某種機制來保證只有特定規則的請求會路由到開發本地的 D 應用,
外部依賴系統到開發環境的連通性
有一些測驗鏈路需要接受一些外部依賴系統的回呼,比如微信或者支付寶的回呼等,而本地應用通常沒有公網地址,這也給除錯帶來了一些困難,
中間件的隔離
分布式系統中經常會用到 RocketMQ 等訊息中間件,如果使用了公共測驗環境,就意味著 MQ 也是共用的,那么 MQ 的訊息到底是應該被測驗環境消費,還是某個個人的開發環境消費呢,這也是需要解決的問題,
高效本地開發
為了進行全流程的高效開發,應該盡量使用反饋比較快的驗證方式,并及早發現問題,逐步進行更加集成,更加真實的測驗,
一般來講,一個開發程序可以經過下面的三個階段:
- 編碼+單元測驗,在小的邏輯單元的層面保證正確性,
- 針對單個應用的集成測驗,可能需要對依賴的應用進行 HTTP 級別的 mock,
- 結合公共測驗環境進行完整的集成測驗,
基于上面的三個階段,可以使用以下的方式來解決前面提到的幾個問題,
- 使用各個語言相應的測驗工具(比如 JUnit)來進行單元測驗,
- 使用 moco 等 HTTP Mock 工具來解決本地隔離驗證的問題,完成單個應用的集成測驗,
- 使用 kt-connect 和 virtual-environment 等工具來解決云原生基礎設施下,本地和測驗環境的互相連通性問題,及 http 請求鏈路的染色和路由,
- 使用 ngrok 等工具解決外部依賴呼叫本地應用的問題,
- 使用“主干穩定環境”作為公共測驗環境,提高其穩定性,
- 使用中間件的染色隔離能力保證 http 請求之外的其它鏈路(比如訊息)的染色和路由,
其中第 1、4 是成熟的技術,這里不再贅述,第 5、6 點會在后面的測驗環境相關的章節中我們詳細講解,本文主要就第 2、3 點展開講解,
單應用的集成測驗方案
比如對應用 D 而言,測驗范圍如下圖的橙色框所示:

應用本身的持久化等依賴使用真實的(一般使用本地 DB),但外部應用(應用 E、F)使用基于 HTTP協議的測驗替身,這樣就可以保證所有的依賴都是穩定的,并且也可以很方便的修改測驗替身的行為,以進行特定場景的測驗,
應用 D 依賴了兩個應用:
- org-service(應用 F):提供查詢組織資訊等能力
- user-service(應用 E):提供查詢用戶資訊等能力
這兩個應用的訪問地址配置在應用 D 的配置項中:
... org-service-host: org-service user-service-host: user-service ...
我們使用 docker compose + moco 的方案來講解如何使用本地測驗替身,
首先創建如下的目錄結構:
├── Dockerfile
├── docker-compose.yml
├── moco-runner.jar
└── services
├── org-service
│ └── config.json
└── user-service
└── config.json
Dockerfile:
FROM openjdk:8-jre-slim
ARG SERVICE
ADD moco-runner.jar moco-runner.jar
COPY services/${SERVICE}/config.json config.json
ENTRYPOINT ["java", "-jar", "moco-runner.jar", "http", "-c", "config.json", "-p", "8080"]
docker-compose.yml:
version: '3.1'
services:
service-f:
ports:
- 8091:8080
build:
context: .
dockerfile: Dockerfile
args:
SERVICE: org-service
service-e:
ports:
- 8092:8080
build:
context: .
dockerfile: Dockerfile
args:
SERVICE: user-service
services/org-service/config.json:
[
{
"request": {
"uri": "/"
},
"response": {
"text": "org service stub"
}
},
{
"request": {
"uri": {
"match": "/orgs/[a-z0-9]{24}"
}
},
"response": {
"json": {
"name": "some org name",
"logo": "http://xx.assets.com/xxx.jpg"
}
}
}
]
services/user-service/config.json:
[
{
"request": {
"uri": "/"
},
"response": {
"text": "user service stub"
}
},
{
"request": {
"uri": {
"match": "/users/[a-z0-9]{24}"
}
},
"response": {
"json": {
"name": "somebody",
"email": "[email protected]"
}
}
}
]
然后使用如下命令來啟動兩個依賴的應用:
docker-compose up --build
驗證下本地測驗替身的行為:
$ curl http://localhost:8092/users/111111111111111111111111
{"name":"somebody","email":"[email protected]"}
$ curl http://localhost:8091/orgs/111111111111111111111111
{"name":"some org name","logo":"http://xx.assets.com/xxx.jpg"}
然后再把應用 D 的依賴配置改成本地測驗替身即可進行測驗:
... org-service-host: localhost:8091 user-service-host: localhost:8092 ...
至此,我們得到了一個穩定的單應用的集成測驗環境,當需要修改依賴的行為時,只需要修改相應應用的config.json 即可,
使用 docker-componse 和 moco 是一種實作單應用集成測驗的方式,你可以根據專案的具體情況選擇合適的工具和方案,
本地和公共測驗環境的互訪及鏈路隔離
完成單應用的集成測驗之后,可以獲得單個應用級別的質量信心,但更大范圍的驗證還是需要和真實的依賴集成在一起進行,

如上圖所示,為了能夠在本地按需啟動應用(A 和 D),并復用測驗環境的其他應用(C),就需要解決兩個問題:
- 本地如何呼叫到公共測驗環境的應用,即 A 如何呼叫到 C
- 公共測驗環境如何呼叫到本地,即 C 如何呼叫到本地的 D
關于第一點,如果本地環境和測驗環境的網路是直接可達的,則直接修改本地應用 A 的配置項即可,如果你使用了云原生的基礎設施,那么就需要類似云效 kt-connect 之類的工具來進行打通,這里不再展開,有需求要的可以參看 kt-connect 的 connect 部分,
關于第二點,需要解決三個問題:
- 從測驗環境的 A 發起的呼叫鏈,應該最終訪問到測驗環境的 D,而從本地環境的 A 發起的呼叫鏈,應該最終訪問到本地環境的 D,互不影響,為了能夠對這兩種呼叫進行區分,需要對呼叫鏈進行“染色”,這里采用的染色的方式是在請求中加入一個額外的 header,
- 根據這個染色的標志,即“染色標”,進行路由,
- 一個呼叫鏈會貫穿多個應用,要保證在呼叫到不同的應用時,染色標要能夠自動的傳遞下去,
關于第一點和第二點,在阿里巴巴內部有一套完整的方案進行染色和路由,這套方案不僅僅適用于 HTTP 鏈路,也適用于 RPC,異步訊息等,而在開源領域,也有基于云原生基礎設施的 kt-connect 可以用,使用kt-connect 的 mesh 功能就可以針對特定染色規則的呼叫鏈進行路由,

kt-connect 基于 istio 的 VirtualService 和 DestinationRule 來進行路由,其基本原理是在集群內新建一個影子副本的 service 和 deployment,然后提交一個應用 D 的 DestinationRule 資源,使得包含“local-env: true”header 的請求被路由到應用 D 的影子副本,然后應用 D 的影子副本再把請求轉發到本地,在這個程序里,除了提交和更新 is t i o 相關資源的操作需要手動進行之外,其他的事情都可以使用ktctl mesh 命令來完成,詳情請參看 mesh 最佳實踐,
接下來解決第三點,染色標傳遞,即需要保證當本地的應用 A 把含有“local-env: true”header 的請求打到測驗環境的應用 C 后,應用 C 繼續訪問應用 D 時候,請求中也應該包含這個 header,
一般的思路是在 Web 層的入口加一個 Interceptor,將染色標記錄下來到一個 ThreadLocal 中,然后再出口的 HttpClient 層再從 ThreadLocal 中把這個染色標取出來,并填充到 Request 物件中,這里有一個需要注意的問題,因為染色是放在 ThreadLocal 中的,因此在一個 web 請求的處理中一旦遇到多執行緒的情況,就需要小心的把這個 ThreadLocal 的值傳遞到相應的子執行緒中,所有的應用都正確的將染色標傳遞下去,就可以保證染色標在全鏈路進行傳遞,
使用 kt-connect 的 mesh 方案加上全鏈路染色標的方案,就可以輕松的在本地按需啟動應用,并進行開發調測,
總結
- 使用單元測驗、單應用集成測驗、端到端集成測驗結合的方式進行本地調測,提高獲得反饋的效率,
- 本地按需啟動應用進行端到端集成測驗的關鍵技術是:全鏈路染色和路由,在不同的基礎設施下可以有不同的實作方式,
【關于云效】
云原生時代一站式DevOps平臺,數十萬企業都在用,支持公共云、專有云和混合云多種部署形態,通過云原生新技術和研發新模式,助力創新創業和數字化轉型企業快速實作研發敏捷和組織敏捷,打造“雙敏”組織,實作多倍效能提升,
立即體驗

?
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/415309.html
標籤:其他
