做者:CODING - 王煒html
若是讓你主導一款千萬、甚至億級用戶產品的功能迭代,你會怎麼作?你須要面對的挑戰可能來自於:node
商業戰略的變化帶來新的產品訴求,而產品的任何改動哪怕僅是界面調整,都將面臨無數存量用戶的挑戰mysql
這時候,做爲產品負責人,你會選擇穩定壓倒一切?仍是自我革新,繼續追求用戶和市場的價值呢?筆者經過對 Facebook、Twitter 等互聯網巨頭的調研,試圖窺探他們在瞬息萬變的市場中仍然保持「穩定」迭代的祕密 - 漸進式交付nginx
經過本篇文章,你將收穫:git
移動互聯網時代的爆發,誕生了一大批巨型互聯網企業和項目,部分大型項目的技術複雜程度和組織複雜程度甚至不亞於傳統的工業項目,爲了實現對這些項目的管理和迭代,咱們試圖將目光投向已經完成工業革命的傳統工業尋找答案。web
而「漸進式交付」一詞最先起源於大型、複雜的工業化項目,好比:鐵路、汽車和軍事產業、新基建的 5G 網絡產業等等。算法
它和 DevOps 的目標一致,試圖將複雜的工程化項目進行分階段拆解,經過持續進行小型迭代閉環,下降交付成本和節約交付時間sql
可查詢的資料顯示,「漸進式交付」流行於互聯網產品是在近兩年 Kubernetes 以及雲原生大規模使用以後。這兩項技術的出現,爲「漸進式交付」在互聯網的應用提供了基礎設施和實現方法。數據庫
DevOps 是「漸進式交付」的實現手段,而其中的「流水線」爲「漸進式交付」提供了實現途徑express
在產品的迭代過程,能夠將「漸進式交付」的具體行爲附着在「流水線」中,將整條交付「流水線」看作是產品迭代的一個過程和一次「漸進式交付」週期。
說了這麼多「漸進式交付」的理論基礎,在實踐中又是以哪些技術方法落地呢?
以 Facebook 爲例,每次發佈重大功能,都會經歷一次典型的「漸進式交付」過程:
這種漸進式交付的好處是,對於新迭代正式推向市場前提供了灰度用戶的數據支撐,幫助決策者充分了解用戶傾向和市場訴求。
在「漸進式交付」的過程當中,A/B 測試環節以及灰度發佈環節均可以根據用戶數據和市場反饋決定是否進入全量發佈,這種方式既可以保證迭代敏捷進行,又可以保證迭代的用戶和市場安全性。
例如經過對用戶畫像中地理位置和性別組合條件進行 A/B 測試,使其訪問新版本,而其餘的用戶則繼續訪問舊版。一段時間後,研究用戶行爲數據和用戶體驗報告,決定功能是否繼續進入下一個發佈環節。
使用特定分流技術使流量由新老版本共同承擔,如典型的「MurmurHash」算法
從原理上來看,這些技術並非多麼新的技術,好比 A/B 測試,咱們用最原始的方式:業務代碼增長邏輯判斷條件也可實現,但爲何沒有大規模運用呢?
緣由很簡單:純業務代碼的實現依賴於技術開發,需求方沒法自主控制 A/B 測試的環境和條件,這種過分依賴於技術開發並不能帶來規模化的運用。
咱們須要的是一種徹底脫離業務代碼的實現方式,最好能以自動化/半自動化實現,而且儘可能能把這個動做加入到已有的內部流程內
如今,有了雲原生和 Kubernetes 的支持,結合 DevOps 的流水線,自動化的漸進式交付成爲了可能。
咱們參考 Facebook 的發佈方式,設計了這個 Pipeline Demo
它主要實現了:
最終實現的效果圖:
http://dev.coding
能夠訪問到新發布的服務。Header
包含 location=shenzhen 做爲區分 A/B 測試流量。在瀏覽器內直接請求 http://pro.coding
,流量仍然分流到生產環境。在 Postman 攜帶 location=shenzhen 的 Header 請求,能夠發現流量被分流到「A/B 測試環境」。
http://pro.coding
,灰度發佈環境和生產環境將以 1:1 的流量比例對外提供服務。http://pro.coding
訪問的是生產環境新版本。HttpCode=429,表明 Too Many Requests ,拒絕服務。
對於開發人員,這種漸進式交付通過多輪的灰度、A/B 測試,最大程度減小代碼 BUG 發佈到生產環境
對於運維人員,這種幾乎全自動的交付方式改變了手動修改 yaml 文件,手動 apply 的麻煩,最大程度減小發布產生的人爲錯誤。經過自動觸發的方式,減少了與開發的溝通成本
對於產品經理和運營人員,產品迭代再也不是靠內部團隊「YY」,而是基於實際用戶體驗數據,瞭解新功能對於用戶和市場的反饋,最大程度減少新功能的用戶和市場風險
克隆源代碼並推送到本身的 CODING 倉庫
git clone https://e.coding.net/wangweicoding/cd-production.git git remote remove origin git remote add origin 你的 CODING 倉庫地址 git push origin master
建立構建計劃
cd-production
代碼倉庫內的 Jenkinsfile 建立構建計劃cd-production
的 coding-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
就能夠訪問發佈的服務了。
cd-production
的 coding-templates/mysql.json
內容,並在每個階段修改成本身的「雲帳號」,建立 Pipeline 並啓動完成集羣 Mysql 的初始化。在建立漸進式交付流水線以前,請先開通
CODING 製品庫
,開通完成後,請按照指引在本地使用cd-production
的Dockerfile
構建鏡像並推送至「製品庫」
隨後以上述一樣的操做複製代碼倉庫 cd-production
的 coding-templates/devops.json
內容,建立漸進式交付的 Pipeline。
請將「配置」階段的「啓動所需製品」修改成本身的 CODING 項目
、Git
倉庫、鏡像倉庫、鏡像。
請將「配置」階段的「自動觸發器」修改成本身的 CODING 項目
、鏡像倉庫、鏡像。
請將每個階段的雲帳號
修改成本身的雲帳號。
嘗試修改項目 index.html
並推送,觸發流水線。
├── 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 # 項目發佈首頁
在這個例子中,咱們使用了 Traefik 做爲集羣網關,使用 Router
對 Host dev.coding
和 pro.coding
進行匹配,使流量按照不一樣發佈階段進行不一樣的分配。
訪問 dev.coding
時,Router
匹配到此 Host
規則,將流量轉發到名爲k8s-flask-nodeport
的 Service
(即 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
訪問 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
訪問 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,即全部流量都轉發到生產環境。
在生產環境,咱們通常使用限流和熔斷技術來應對流量激增,犧牲部分用戶的體驗來保證生產環境的穩定。
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 時熔斷
Kubernetes 和 Service Mesh 的出現,給 DevOps 帶來了更多可能,漸進式交付只是一種藉助其便利性的比較典型的發佈方式。
咱們藉助了 Traefik 做爲集羣網關,經過分流技術實現了 A/B 測試和灰度發佈,固然,你也能夠引入 Service Mesh
使用 Istio 管理集羣流量,藉助 Virtual Service
和 Destination Rule
實現一樣效果。
藉助 CODING DevOps 的能力,咱們將「推送代碼」、「構建鏡像」、「觸發部署流程」進行打通,實現了自動化的 DevOps 能力。
固然,還能夠作到更多有價值的發佈流程,好比:
Wait
階段實現灰度比例隨着時間推移自動增長;