DevOps - 從漸進式交付提及(含實踐 Demo)

做者:CODING - 王煒html

1. 開篇

若是讓你主導一款千萬、甚至億級用戶產品的功能迭代,你會怎麼作?你須要面對的挑戰可能來自於:node

商業戰略的變化帶來新的產品訴求,而產品的任何改動哪怕僅是界面調整,都將面臨無數存量用戶的挑戰mysql

這時候,做爲產品負責人,你會選擇穩定壓倒一切?仍是自我革新,繼續追求用戶和市場的價值呢?筆者經過對 Facebook、Twitter 等互聯網巨頭的調研,試圖窺探他們在瞬息萬變的市場中仍然保持「穩定」迭代的祕密 - 漸進式交付nginx

經過本篇文章,你將收穫:git

  1. 什麼是漸進式交付,爲何 DevOps 可以自然與其結合
  2. 爲何漸進式交付能賦予大規模組織下的產品持續交付及穩定迭代的能力
  3. 小項目,大項目一樣適用的實踐經驗

2. 什麼是漸進式交付

移動互聯網時代的爆發,誕生了一大批巨型互聯網企業和項目,部分大型項目的技術複雜程度和組織複雜程度甚至不亞於傳統的工業項目,爲了實現對這些項目的管理和迭代,咱們試圖將目光投向已經完成工業革命的傳統工業尋找答案。web

而「漸進式交付」一詞最先起源於大型、複雜的工業化項目,好比:鐵路、汽車和軍事產業、新基建的 5G 網絡產業等等。算法

它和 DevOps 的目標一致,試圖將複雜的工程化項目進行分階段拆解,經過持續進行小型迭代閉環,下降交付成本和節約交付時間sql

可查詢的資料顯示,「漸進式交付」流行於互聯網產品是在近兩年 Kubernetes 以及雲原生大規模使用以後。這兩項技術的出現,爲「漸進式交付」在互聯網的應用提供了基礎設施和實現方法。數據庫

DevOps 是「漸進式交付」的實現手段,而其中的「流水線」爲「漸進式交付」提供了實現途徑express

在產品的迭代過程,能夠將「漸進式交付」的具體行爲附着在「流水線」中,將整條交付「流水線」看作是產品迭代的一個過程和一次「漸進式交付」週期。

說了這麼多「漸進式交付」的理論基礎,在實踐中又是以哪些技術方法落地呢?

  1. A/B 測試
  2. 金絲雀 / 灰度發佈

以 Facebook 爲例,每次發佈重大功能,都會經歷一次典型的「漸進式交付」過程:

  1. 迭代發佈
  2. 公司全員 A/B 測試
  3. 特定用戶 A/B 測試
  4. 灰度發佈
  5. 全量發佈

這種漸進式交付的好處是,對於新迭代正式推向市場前提供了灰度用戶的數據支撐,幫助決策者充分了解用戶傾向和市場訴求。

在「漸進式交付」的過程當中,A/B 測試環節以及灰度發佈環節均可以根據用戶數據和市場反饋決定是否進入全量發佈,這種方式既可以保證迭代敏捷進行,又可以保證迭代的用戶和市場安全性。

2.1 A/B 測試

例如經過對用戶畫像中地理位置和性別組合條件進行 A/B 測試,使其訪問新版本,而其餘的用戶則繼續訪問舊版。一段時間後,研究用戶行爲數據和用戶體驗報告,決定功能是否繼續進入下一個發佈環節。

2.2 金絲雀 / 灰度發佈

使用特定分流技術使流量由新老版本共同承擔,如典型的「MurmurHash」算法

3. 技術價值和商業價值

從原理上來看,這些技術並非多麼新的技術,好比 A/B 測試,咱們用最原始的方式:業務代碼增長邏輯判斷條件也可實現,但爲何沒有大規模運用呢?

緣由很簡單:純業務代碼的實現依賴於技術開發,需求方沒法自主控制 A/B 測試的環境和條件,這種過分依賴於技術開發並不能帶來規模化的運用。

咱們須要的是一種徹底脫離業務代碼的實現方式,最好能以自動化/半自動化實現,而且儘可能能把這個動做加入到已有的內部流程內

如今,有了雲原生和 Kubernetes 的支持,結合 DevOps 的流水線,自動化的漸進式交付成爲了可能。

咱們參考 Facebook 的發佈方式,設計了這個 Pipeline Demo

它主要實現了:

  1. 提交代碼後自動執行單元測試,並構建 Docker 鏡像
  2. 將 Docker 鏡像推送到私有制品庫,自動觸發流水線
  3. 執行 K8S Job Migrate 數據庫(若是有改動),並部署新版到預發佈環境
  4. 人工確認:發佈生產環境前是否進行 A/B 測試
  5. A/B 測試經過後,設置灰度發佈的比例,自動灰度發佈
  6. 人工確認:是否全量發佈生產環境
  7. 生產環境自動配置限流和熔斷策略,保證生產穩定

最終實現的效果圖:

1. 提交代碼後自動構建鏡像、單元測試、推送到鏡像倉庫並觸發 CD 流水線。


2. 執行 K8S Job 對預發佈環境數據庫自動 Migrate,併發布到預發佈環境。

3. 此時經過訪問 http://dev.coding 能夠訪問到新發布的服務。

4. 人工確認:發佈生產環境前是否進行 A/B 測試。

5. 在本例,發佈後以 Header 包含 location=shenzhen 做爲區分 A/B 測試流量。

在瀏覽器內直接請求 http://pro.coding,流量仍然分流到生產環境。在 Postman 攜帶 location=shenzhen 的 Header 請求,能夠發現流量被分流到「A/B 測試環境」。

6. 人工確認:設置指望的灰度發佈比例,自動灰度發佈,如選擇灰度發佈比例爲 50%。

7. 請求 http://pro.coding ,灰度發佈環境和生產環境將以 1:1 的流量比例對外提供服務。

8. 人工確認:全量發佈生產環境。

9. 此時請求 http://pro.coding 訪問的是生產環境新版本。

10. 自動配置限流和熔斷策略,保證生產穩定性。經過壓力測試可看到併發請求有一部分被限流。

HttpCode=429,表明 Too Many Requests ,拒絕服務。

對於開發人員,這種漸進式交付通過多輪的灰度、A/B 測試,最大程度減小代碼 BUG 發佈到生產環境

對於運維人員,這種幾乎全自動的交付方式改變了手動修改 yaml 文件,手動 apply 的麻煩,最大程度減小發布產生的人爲錯誤。經過自動觸發的方式,減少了與開發的溝通成本

對於產品經理和運營人員,產品迭代再也不是靠內部團隊「YY」,而是基於實際用戶體驗數據,瞭解新功能對於用戶和市場的反饋,最大程度減少新功能的用戶和市場風險

4. 動手實踐

4.1 概覽

  1. 準備一個 K8S 集羣,推薦使用騰訊雲容器服務
  2. K8S 集羣部署 Traefik 替換 nginx-Ingress 做爲 Ingress Gateway,提供更好的流量治理能力;
  3. 開通 CODING DevOps,提供鏡像構建和流水線的部署能力;
  4. 克隆示例代碼並推送到本身的 CODING 代碼倉庫;
  5. 複製模板建立部署流水線;
  6. 盡情測試。

4.2 實踐步驟

4.2.1 克隆源代碼並建立構建計劃

  1. 克隆源代碼並推送到本身的 CODING 倉庫

    git clone https://e.coding.net/wangweicoding/cd-production.git
    git remote remove origin
    git remote add origin 你的 CODING 倉庫地址
    git push origin master
  2. 建立構建計劃

  • 使用 cd-production 代碼倉庫內的 Jenkinsfile 建立構建計劃

4.2.2 開通騰訊雲容器服務

  • 開通集羣,並在 CODING DevOps 新建 K8S 集羣憑證(若有必要,請容許集羣外網訪問)

4.2.3 經過 CODING DevOps 初始化 Traefik

  • 複製代碼倉庫 cd-productioncoding-templates/traefik.json 內容,並在 部署控制檯 建立 pipeline,點擊「編輯 JSON 配置」,將內容複製到輸入框。


點擊「Update Pipeline」後,自動建立了對應的 Pipeline。

注意請將每個階段的雲帳號修改成本身的雲帳號。
再點擊「保存」即完成 Traefik 初始化的 Pipeline 建立,返回後,點擊「當即啓動」完成集羣 Traefik 的初始化。

進入「騰訊雲」容器服務,打開集羣Service,點擊命名空間 traefik-system ,找到名爲 traefik-ingress 的 IP 地址,並在本機新建兩個 Host 規則:

IP地址 dev.coding
IP地址 pro.coding

這樣在本地經過訪問 dev.coding 就能夠訪問發佈的服務了。

4.2.4 經過 CODING DevOps 初始化 Mysql

  • 以上述一樣的操做複製代碼倉庫 cd-productioncoding-templates/mysql.json 內容,並在每個階段修改成本身的「雲帳號」,建立 Pipeline 並啓動完成集羣 Mysql 的初始化。

4.2.5 建立漸進式交付流水線

在建立漸進式交付流水線以前,請先開通 CODING 製品庫,開通完成後,請按照指引在本地使用 cd-productionDockerfile 構建鏡像並推送至「製品庫」

隨後以上述一樣的操做複製代碼倉庫 cd-productioncoding-templates/devops.json 內容,建立漸進式交付的 Pipeline。

請將「配置」階段的「啓動所需製品」修改成本身的 CODING 項目Git 倉庫、鏡像倉庫、鏡像。
請將「配置」階段的「自動觸發器」修改成本身的 CODING 項目、鏡像倉庫、鏡像。
請將每個階段的雲帳號修改成本身的雲帳號。

嘗試修改項目 index.html 並推送,觸發流水線。

5. 項目說明與核心原理

5.1 項目說明

├── Dockerfile
├── Jenkinsfile  # CODING CI 構建腳本
├── Pipfile
├── Pipfile.lock
├── README.md
├── app.py
├── coding-templates
│   ├── devops.json  # CODING CD 漸進式交付模板
│   ├── mysql.json    # CODING CD Mysql 初始化模板
│   └── traefik.json   # CODING CD Traefik 初始化模板
├── config.py
├── database_version.py
├── devops
│   ├── README.md
│   ├── mysql
│   │   ├── dev
│   │   │   ├── mysql-deployment.yaml
│   │   │   └── mysql-pv.yaml
│   │   └── pro
│   │       ├── mysql-deployment.yaml
│   │       └── mysql-pv.yaml
│   └── traefik
│       ├── deployment
│       │   ├── configmap.yaml
│       │   └── deployment.yaml
│       ├── deployment.yaml
│       ├── open-treafik.yaml
│       └── router
│           ├── dev
│           │   └── flask-dev.yaml      # Dev 環境的 Traefik IngressRoute 規則
│           └── pro
│               ├── circuitbreaker.yaml  # Pro 環境的 Traefik 熔斷規則
│               ├── flask-abtest.yaml    # Pro 環境的 A/B Testcase
│               ├── flask-pro-all.yaml   # Pro 環境的 Traefik IngressRoute 規則
│               ├── flask-pro.yaml
│               ├── mysql-ratelimit.yaml
│               ├── mysql-tcp-router.yaml
│               └── ratelimit.yaml         # Pro 環境的 Traefik 限流規則
├── flask_test.py
├── k8s-canary                             # 灰度發佈環境的 K8S Manifest
│   ├── deployment.yaml                
│   └── nodeport-canary.yaml
├── k8s-dev                                 # Dev 環境的 K8S Manifest
│   ├── deployment.yaml                
│   ├── migrate-mysql-job.yaml     # Dev 環境的 Migrate Database K8S Job
│   ├── nodeport-service.yaml
│   └── service.yaml
├── k8s-pro                                 # Pro 環境的 K8S Manifest
│   ├── deployment.yaml
│   ├── migrate-mysql-job.yaml
│   └── nodeport-service.yaml
├── manage.py
├── migrations
│   ├── README
│   ├── alembic.ini
│   ├── env.py
│   ├── script.py.mako
│   └── versions
│       ├── 95585fe4b611_initial_migration.py
│       └── fece98dad497_second_migrate.py
├── requirements.txt
└── templates
  └── index.html                           # 項目發佈首頁

5.2 核心原理

在這個例子中,咱們使用了 Traefik 做爲集羣網關,使用 Router 對 Host dev.codingpro.coding 進行匹配,使流量按照不一樣發佈階段進行不一樣的分配。

5.2.1 Dev 環境架構圖

訪問 dev.coding 時,Router 匹配到此 Host 規則,將流量轉發到名爲k8s-flask-nodeportService(即 Dev 環境的 Service)。

Traefik Router 核心配置代碼爲:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: k8s-flask-dev
  namespace: dev
spec:
  entryPoints:
  - web
    routes:
  - kind: Rule
    match: Host(`dev.coding`)
    services:
    - name: k8s-flask-nodeport
      port: 8080
      namespace: dev

5.2.2 A/B 測試環境架構圖

訪問 pro.coding 時,Router 匹配到此 Host 規則,並檢查 Header 是否匹配,並將根據匹配結果決定將流量轉發到 k8s-flask-canary 或者 k8s-flask 兩個不一樣環境的 Services。

A/B 測試 Traefik Router 核心配置代碼爲:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: k8s-flask-abtest
  namespace: pro
spec:
  entryPoints:
  - web
    routes:
  #A/B test
  - kind: Rule
    match: Host(`pro.coding`) && Headers(`location`, `shenzhen`)
    services:
    - name: k8s-flask-canary
      port: 8080
      namespace: pro

5.2.3 灰度發佈架構圖

訪問 pro.coding 時,Router 匹配到此 Host 規則,並根據配置的 Weight 權重,將流量按比例轉發到 k8s-flask-canary 或者 k8s-flask Service

例如以 1:1 的比例分配灰度比例,Traefik Router 核心配置代碼爲:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: k8s-flask-pro
  namespace: pro
spec:
  entryPoints:
  - web
    routes:
  #canary deploy
  - kind: Rule
    match: Host(`pro.coding`)
    services:
    - name: k8s-flask
      port: 8080
      namespace: pro
      weight: 50    # 權重比例
    - name: k8s-flask-canary
      port: 8080
      namespace: pro
      weight: 50    # 權重比例

當全量發佈生產環境的時候,只須要將 Canary 環境的 Weight 權重設置爲 0,即全部流量都轉發到生產環境。

5.2.4 熔斷和限流架構圖

在生產環境,咱們通常使用限流和熔斷技術來應對流量激增,犧牲部分用戶的體驗來保證生產環境的穩定。

Traefik 內熔斷和限流是經過配置 middlewares 來實現,對流量進行匹配後,再進行中間件二次流量確認。

Traefik Middlewares 限流核心配置:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: flask-k8s-traffic
  namespace: pro
spec:
  rateLimit:
    # 1s 內接收的請求數的平均值不大於500個,高峯最大1000個請求
    burst: 1000
    average: 500

Traefik Middlewares 熔斷核心配置:

# Latency Check
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: k8s-flask-breaker
  namespace: pro
spec:
  circuitBreaker:
    expression: LatencyAtQuantileMS(50.0) > 100
    # 50% 的請求比例響應時間大於 100MS 時熔斷

6. 小結

Kubernetes 和 Service Mesh 的出現,給 DevOps 帶來了更多可能,漸進式交付只是一種藉助其便利性的比較典型的發佈方式。

咱們藉助了 Traefik 做爲集羣網關,經過分流技術實現了 A/B 測試和灰度發佈,固然,你也能夠引入 Service Mesh 使用 Istio 管理集羣流量,藉助 Virtual ServiceDestination Rule 實現一樣效果。

藉助 CODING DevOps 的能力,咱們將「推送代碼」、「構建鏡像」、「觸發部署流程」進行打通,實現了自動化的 DevOps 能力。

固然,還能夠作到更多有價值的發佈流程,好比:

  • 運營、產品人員能夠實現很方便地隨時修改 A/B Testcase 進行分流測試,只須要配置一個修改 A/B Testcase 的 Pipeline,輸入相關的分流指標,並運行便可生效;
  • 除了 A/B Testcase,灰度發佈也變成了實時可控的數值,甚至能夠實現一個「漸進式灰度發佈」的 Pipeline,增長 Wait 階段實現灰度比例隨着時間推移自動增長;
  • 能夠很方便地實現一個自動回滾的 Pipeline,經過輸入版本號就能夠實現自動回滾到對應的版本,如使用數據庫 ORM 產品,甚至能夠實現數據庫的自動回滾;
  • Traefik 提供的熔斷和限流能力,結合 Pipeline 的 Webhook 觸發以及監控系統如 Prometheus 聯動,能夠實現業務系統壓力較大的時候自動觸發熔斷和限流 Pipeline 改變限流策略,保證生產環境的穩定性。

7. 資源連接和參考資料

相關文章
相關標籤/搜索