天天看點

基于泳道技術生成“無數”個測試環境

作者:閃念基因
基于泳道技術生成“無數”個測試環境

“按需求動态生成測試環境”最開始是我們QA團隊提出的需求,一開始接到這個需求的時候,第一反應就是,怎麼會有這麼迫(bian)切(tai)的想法呢?

這裡還得介紹一下我們的分支管理政策:

基于泳道技術生成“無數”個測試環境

通過走訪調研,最終确定遵循一個需求一個開發分支的原則,友善管理且可追溯 ,并行開發,互不幹擾。

基于泳道技術生成“無數”個測試環境

既然研發小哥哥的代碼分支都是面向需求的,那測試小姐姐希望測試環境也能面向需求,就不過分了,一點都不過分!真心的!

接到這個需求後,最先想到的實作方式是使用docker、jenkins、gitlab等,給每一個需求都建構一個完整的全量項目的環境!

這是最簡單的方式,但也是最不可行的方式!

一個疊代有十幾個甚至幾十個需求,每個需求一個全量的環境,那伺服器資源就得翻好幾十倍了,建構時間也無法接受。

基于泳道技術生成“無數”個測試環境

作為一名苦逼的運維仔,

為了混口飯吃,再難也得頂着上!

落地方案往下看!

設計原理:

基于泳道技術生成“無數”個測試環境

首先固定一條主泳道,包含系統完整的服務,其餘泳道都對應一個需求分支,如果需求改動了一個或多個服務,那麼相應泳道也将包含一個或多個服務。

然後将每條泳道都定義好一個指定的入口,如 feature-id.test.project.oa.com ,域名和泳道是一對一的關系,通路指定域名時會優先通路對應泳道中存在的服務,如果該泳道中不存在的服務則會fallback到主泳道上,以此完成整個鍊路的調用關系。

實作步驟:

一. 使用容器和容器編排技術來部署服務

(泳道共享技術從原理上來講,不使用容器也能實作,但是借助容器和容器編排技術能讓整個實作過程變得更簡單,是以我們直接講解基于容器的實作方式)

  • 為什麼要使用容器呢?

首先,docker容器具備較高的隔離性,安全性,鏡像建構後可以放到到任意一台安裝了docker的伺服器快速運作起來,能較大程度提高服務部署效率和遷移的便捷性。

其次,作為一名苦逼的運維仔,其實我從來都不相信伺服器是可靠的,總會有各種各樣的原因讓伺服器崩掉,容器化部署——讓服務和伺服器無關才是王道!

基于泳道技術生成“無數”個測試環境

(我們的docker鏡像包含了服務運作時所需要的所有東西)

  • 為什麼要使用容器編排呢?

服務容器化之後,看似節省了不少部署工作,但仍存在需要人工幹預的地方,而容器編排系統則可以完全實作自動化。我們來對比兩個場景的處理方式:

A:伺服器負載過高,需要擴容服務節點的時候

無容器編排系統的處理方式:列出叢集中所有伺服器,檢視cpu、記憶體、網絡、負載、IO使用等資料,然後人肉判斷選出合适的伺服器,在選中的伺服器進行部署,部署完成之後服,從LB出将流量引入到該服務中。

有容器編排系統的處理方式:定義好擴容門檻值和範圍後,負載過高的時候自動選擇合适的伺服器節點,自動擴容服務,自動引入流量。

B:當某個伺服器出現故障或者需要下架維護伺服器的時候

無容器編排系統的處理方式:從LB層切掉所有發送到該機器的流量,停掉上面部署的服務,同時重複一遍上面人肉選伺服器的過程,部署完成之後人肉導入流量。

有容器編排系統的處理方式:當伺服器出現故障不通時,編排系統将自動從現有正常的伺服器叢集中選擇合适的伺服器自動部署确實的服務節點,而針對伺服器下架,則可以直接執行驅逐指令,即可把指定伺服器的服務平滑遷移走,友善且快捷。

我們選用容器編排系統是k8s,因為k8s在過去一段時間和各大編排系統的競争中脫穎而出,一舉奪下容器編排系統界寶座,同時随着k8s的流行,生态圈也越來越完善。

基于泳道技術生成“無數”個測試環境

(kubenetes的叢集架構圖)

二. 使用Helm進行編排配置管理

編排系統的好處諸多,但采用了k8s編排系統就能一勞永逸嗎?

先科普一下k8s的編排配置:

每個服務在放入k8s編排系統之前都需要準備好一份配置,告訴k8s怎麼做編排,就好比我們擁有了一個廠房,裡面有各種各樣的自動化車床流水線,但是啟動流水線之前需要有一份圖紙去告訴車床怎麼運作,而這個圖紙就是我們所說的編排配置。

那麼問題來了:

假如每個服務都有一份編排配置,突然有個需求,需要改掉所有服務都有的一個參數,正常的做法是所有的服務編排配置都修改一遍,然而每個服務都對應着4個環境(開發、測試、預釋出、生産),每個環境對應着一份配置...

簡單來說就是假如我們有100個微服務,那就要人工修改400次!!!

哎,摸了摸稀疏的頭頂,發現我的時間不多了!!

基于泳道技術生成“無數”個測試環境

如何高效管理K8S的編排配置呢?

首先,将服務抽象分類,分為前端、中間層、dubbo後端、springboot後端等,每類都寫一份能相容到各個環境的抽象編排配置模闆。

相似的地方通過抽象的方式包裝起來,那些不同的、和服務相關的特性則存放在一個單獨的yaml配置檔案。

配置檔案片段:

# dubbo provider
zhenai-crm-login-provider:
  base:
    group: crm
    project: zhenai-crm-login
    service_path: zhenai-crm-login-provider
    type: dubbo
  image:
    from_image: inner.harbor.oa.com/crm/openjdk-8u171-jdk-alpine:latest
    files:
      - 'target/zhenai-crm-login-provider.zip:/usr/local/app:unzip'
    workdir: /usr/local/app/zhenai-crm-login-provider
    entrypoint: sh /dubbo_start.sh
  k8s:
    helm_template_name: zhenai-crm-demo-provider
    livenessProbe:
      tcpSocket:
        port: 9089


# tomcat server
zhenai-crm-center-server:
  base:
    group: crm
    project: zhenai-crm-center
    service_path: zhenai-crm-center-manager
    type: tomcat
  image:
    from_image: inner.harbor.oa.com/crm/tomcat-8.0.53-jre8-alpine:latest
    files:
      - 'target/zhenai-crm-center-web.war:/data/web-app/:unzip'
    workdir: /usr/local/tomcat
  k8s:
    helm_template_name: zhenai-crm-demo-server
    livenessProbe:
      httpGet:
        path: /_health.do
        port: 8080           

base部分:

項目的基本資訊,指定了屬于哪個項目(project),該服務在這個項目中的路徑

image部分:

主要是對建構docker鏡像的參數做了一個抽像,從什麼基礎鏡像建構,建構的時候需要把什麼檔案放到鏡像中,還可以指定workdir,entrypoint等。

k8s部分:

記錄了使用哪個helm模闆來渲染helm配置和健康檢測相關資訊。

另外,我們引入helm來管理k8s的配置,将每一類的配置都做成helm chart,并将存放于helm倉庫ChartMuseum。

helm chart目錄結構如下:

zhenai-crm-demo-server
├── Chart.yaml
├── Chart_tpl.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   └── tests
│       └── test-connection.yaml
├── values.yaml
└── values_tpl.yaml


2 directories, 10 files           

Chart.yaml記錄了項目的基本項目(名字、版本等),values.yaml記錄了項目之間不一樣的特性變量(如健康檢測端口,URL,副本數量等)。

而Chart_tpl.yaml、values_tpl.yaml都是模闆檔案,會根據服務的特性分别渲染成 Chart.yaml 和 values.yaml。

這樣下來,即便有幾百個微服務配置也隻需幾份模闆就能搞定!

強調一下,保證項目結構的統一性非常重要!!!

三. 泳道間服務路由實作

講完了容器和容器編排,下面我們講一下如何基于這些技術實作泳道間服務路由

去掉lb、nginx、zk等元件,我們的服務可以抽象成兩層:

第一層:包括前端和dubbo consumer(restful api)

第二層:dubbo provider(供consumer調用)

服務層次抽象出來大概長這個樣子

基于泳道技術生成“無數”個測試環境
  • 前端和consumer層的服務路由實作

這是服務接入的最外層,每個環境建構完成的時候都将生成一個唯一的域名,如:tapd-123.test.project.oa.com,當我們通路這個域名的時候,流量将進入到指定的nginx-ingress,這個nginx-ingress則為服務流量做了第一層的路由,将流量優先打到該環境的服務,如果該環境沒有對應的服務,則打到主泳道也就是我們的主測試環境。

如其中的一個ingress配置(片段):

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: project-ingress-tapd-123
  namespace: project-crm
  kubernetes.io/ingress.class: nginx
  nginx.ingress.kubernetes.io/configuration-snippet: |
    proxy_set_header zone tapd-123;
spec:
  rules:
  - host: tapd-123.test.project.oa.com
    http:
      paths:
      - backend:
          serviceName: zhenai-crm-invite-web-tapd-123
          servicePort: http
        path: /invite
      - backend:
          serviceName: zhenai-crm-app-server-test
          servicePort: http
        path: /api/app           
  • dubbo provider層的服務路由實作

dubbo架構有自己的服務注冊中心,k8s并不能幹預其調用政策,是以,還需要對dubbo的服務調用機制動動手腳才行。

dubbo的consumer選擇provider的時候會調用loadbalancer接口,是以我們要做的就是重寫dubbo的loadbalancer接口,讓它的排程也能符合我們的政策。

是以思路就是,provider注冊的時候帶上自己的泳道資訊,告訴注冊中心自己是屬于哪個需求環境的,然後consumer在調用provider的時候,根據流量辨別去選擇相應的provider,如果沒找到相應的後端,同樣也fallback到主泳道上。

另外,provider之間互調的情況,上面說了http可以通過header的方式記錄流量的辨別,那dubbo的rpc協定怎麼維持流量辨別呢?可以重寫dubbo filter API,攔截rpc請求,再通過dubbo的隐式傳參(RpcContext.getContext().setAttachment、RpcContext.getContext().getAttachment)實作參數傳遞。

加了服務路由後調用關系如下圖所示

基于泳道技術生成“無數”個測試環境

(服務調用過程.gif)

動圖的服務名字看不清,特地奉上一張超高清大圖給各位大佬

基于泳道技術生成“無數”個測試環境

(服務調用過程.jpg)

四. 使用Jenkins Pipeline自動化建構及部署服務

我們整個Jenkins pipeline可以用一張圖來描述

基于泳道技術生成“無數”個測試環境
  1. 代碼建構階段:根據項目編寫語言使用對應的建構工具進行建構
  2. check階段:做代碼檢測和代碼掃描
  3. 鏡像建構階段:把代碼建構産物和基礎鏡像合成業務鏡像
  4. k8s編排配置生成階段:主要做編排配置渲染
  5. k8s叢集更新階段:利用上一階段生成的配置對叢集的服務進行滾動更新

環境建立過程:

  1. QA同學在建構界面輸入一個需求ID
  2. 自動通過這個ID找到所有關聯的服務
  3. 并發式的對比對的服務進行編譯、建構鏡像和部署
  4. 完成部署後,會生成一個對該需求環境通路的URL
  5. 點選URL就能進入到由該需求建構出來的環境了

pipeline建構過程

基于泳道技術生成“無數”個測試環境

由于并發建構,是以整個環境生成僅需2-3分鐘完成!

效果展示:

建構完成後會生成需求ID對應的測試環境:

基于泳道技術生成“無數”個測試環境

搞定!

趕緊讓小花同學驗收下!

作者:财俊 Jonny

來源-微信公衆号:珍愛網技術團隊

出處:https://mp.weixin.qq.com/s/EUacgzfTsweOHHYWxjpMKg