新聞中心
大家好,我是喬克。

成都創(chuàng)新互聯(lián)主要從事成都網(wǎng)站制作、做網(wǎng)站、外貿(mào)營(yíng)銷網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)房縣,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來(lái)電咨詢建站服務(wù):13518219792
今天是元宵,祝大家元宵節(jié)快樂(lè)!
在日常的工作中,我們會(huì)經(jīng)常對(duì)應(yīng)用進(jìn)行發(fā)版升級(jí),在互聯(lián)網(wǎng)公司尤為頻繁,主要是為了滿足快速的業(yè)務(wù)發(fā)展。我們經(jīng)常用到的發(fā)布方式有滾動(dòng)更新、藍(lán)綠發(fā)布、灰度發(fā)布。
- 滾動(dòng)更新:依次進(jìn)行新舊替換,直到舊的全部被替換為止。
- 藍(lán)綠發(fā)布:兩套獨(dú)立的系統(tǒng),對(duì)外提供服務(wù)的稱為綠系統(tǒng),待上線的服務(wù)稱為藍(lán)系統(tǒng),當(dāng)藍(lán)系統(tǒng)里面的應(yīng)用測(cè)試完成后,用戶流量接入藍(lán)系統(tǒng),藍(lán)系統(tǒng)將稱為綠系統(tǒng),以前的綠系統(tǒng)就可以銷毀。
- 灰度發(fā)布:在一套集群中存在穩(wěn)定和灰度兩個(gè)版本,灰度版本可以限制只針對(duì)部分人員可用,待灰度版本測(cè)試完成后,可以將灰度版本升級(jí)為穩(wěn)定版本,舊的穩(wěn)定版本就可以下線了,我們也稱之為金絲雀發(fā)布。
這里主要給大家分享如果通過(guò)ingress-nginx controller實(shí)現(xiàn)灰度發(fā)布。
本文大綱如下。
如何通過(guò)ingress-nginx實(shí)現(xiàn)灰度發(fā)布
ingress-nginx是Kubernetes官方推薦的ingress controller,它是基于nginx實(shí)現(xiàn)的,增加了一組用于實(shí)現(xiàn)額外功能的Lua插件。
為了實(shí)現(xiàn)灰度發(fā)布,ingress-nginx通過(guò)定義annotation來(lái)實(shí)現(xiàn)不同場(chǎng)景的灰度發(fā)布,其支持的規(guī)則如下:
- nginx.ingress.kubernetes.io/canary-by-header:基于 Request Header 的流量切分,適用于灰度發(fā)布以及 A/B 測(cè)試。當(dāng) Request Header 設(shè)置為 always時(shí),請(qǐng)求將會(huì)被一直發(fā)送到 Canary 版本;當(dāng) Request Header 設(shè)置為 never時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他 Header 值,將忽略 Header,并通過(guò)優(yōu)先級(jí)將請(qǐng)求與其他金絲雀規(guī)則進(jìn)行優(yōu)先級(jí)的比較。
- nginx.ingress.kubernetes.io/canary-by-header-value:要匹配的 Request Header 的值,用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。當(dāng) Request Header 設(shè)置為此值時(shí),它將被路由到 Canary 入口。該規(guī)則允許用戶自定義 Request Header 的值,必須與上一個(gè) annotation (即:canary-by-header)一起使用。
- nginx.ingress.kubernetes.io/canary-weight:基于服務(wù)權(quán)重的流量切分,適用于藍(lán)綠部署,權(quán)重范圍 0 - 100 按百分比將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)。權(quán)重為 0 意味著該金絲雀規(guī)則不會(huì)向 Canary 入口的服務(wù)發(fā)送任何請(qǐng)求。權(quán)重為 100 意味著所有請(qǐng)求都將被發(fā)送到 Canary 入口。
- nginx.ingress.kubernetes.io/canary-by-cookie:基于 Cookie 的流量切分,適用于灰度發(fā)布與 A/B 測(cè)試。用于通知 Ingress 將請(qǐng)求路由到 Canary Ingress 中指定的服務(wù)的cookie。當(dāng) cookie 值設(shè)置為 always時(shí),它將被路由到 Canary 入口;當(dāng) cookie 值設(shè)置為 never時(shí),請(qǐng)求不會(huì)被發(fā)送到 Canary 入口;對(duì)于任何其他值,將忽略 cookie 并將請(qǐng)求與其他金絲雀規(guī)則進(jìn)行優(yōu)先級(jí)的比較。
我們也是通過(guò)上面的annotation來(lái)實(shí)現(xiàn)灰度發(fā)布,其思路如下:
在集群中部署兩套系統(tǒng),一套是stable版本,一套是canary版本,兩個(gè)版本都有自己的service
定義兩個(gè)ingress配置,一個(gè)正常提供服務(wù),一個(gè)增加canary的annotation
待canary版本無(wú)誤后,將其切換成stable版本,并且將舊的版本下線,流量全部接入新的stable版本
發(fā)布場(chǎng)景介紹
上面介紹了ingress-nginx實(shí)現(xiàn)灰度發(fā)布的方法以及咱們自己的實(shí)現(xiàn)思路,這里來(lái)探討一下灰度發(fā)布有哪些發(fā)布場(chǎng)景。
基于權(quán)重的發(fā)布場(chǎng)景
假如在生產(chǎn)上已經(jīng)運(yùn)行了A應(yīng)用對(duì)外提供服務(wù),此時(shí)開(kāi)發(fā)修復(fù)了一些Bug,需要發(fā)布A2版本將其上線,但是我們又不希望直接的將所有流量接入到新的A2版本,而是希望將10%的流量進(jìn)入到A2中,待A2穩(wěn)定后,才會(huì)將所有流量接入進(jìn)來(lái),再下線原來(lái)的A版本。
image.png
要實(shí)現(xiàn)這種,只需要在canary的ingress中添加如下annotation。
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
其中nginx.ingress.kubernetes.io/canary表示開(kāi)啟canary,nginx.ingress.kubernetes.io/canary-weight表示我們?cè)O(shè)置的權(quán)重大小。
基于用戶請(qǐng)求的發(fā)布場(chǎng)景
基于權(quán)重的發(fā)布場(chǎng)景比較粗糙,它是所有用戶中的20%,無(wú)法限制具體的用戶。
我們有時(shí)候會(huì)有這樣的需求,比如我們有廣東、北京、四川這三個(gè)地區(qū)的用戶,并且已經(jīng)有A版本的應(yīng)用為這三個(gè)地區(qū)提供服務(wù),由于更新了需求,我們需要發(fā)布A2應(yīng)用,但是我們不想所有地區(qū)都訪問(wèn)A2應(yīng)用,而是希望只有四川的用戶可以訪問(wèn),待四川地區(qū)反饋沒(méi)問(wèn)題后,才開(kāi)放其他地區(qū)。
image.png
對(duì)于這種我們需要在canary的ingress中添加如下annotation。
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "region"
nginx.ingress.kubernetes.io/canary-by-header-value: "sichuan"
主要就是上面兩種發(fā)布場(chǎng)景,下面會(huì)針對(duì)這兩種場(chǎng)景分別進(jìn)行實(shí)驗(yàn)。
灰度發(fā)布具體實(shí)現(xiàn)
我這里準(zhǔn)備了兩個(gè)鏡像,一個(gè)是穩(wěn)定stable版本,一個(gè)是灰度canary版本。
- registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1
- registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v2
由于兩個(gè)場(chǎng)景只有在ingress處的配置不一致,其他都一樣的,所以這里先將兩個(gè)版本的應(yīng)用都部署好。
(1)stable版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-server-stable
spec:
selector:
matchLabels:
app: go-test
version: stable
replicas: 1
template:
metadata:
labels:
app: go-test
version: stable
spec:
containers:
- name: app-server
image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: app-server-stable-svc
spec:
selector:
app: go-test
version: stable
ports:
- name: http
port: 8080
訪問(wèn)效果如下:
# curl 10.97.112.137:8080
{"data":"hello world","version":"v1"}
(2)canary版本
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-server-canary
spec:
selector:
matchLabels:
app: go-test
version: canary
replicas: 1
template:
metadata:
labels:
app: go-test
version: canary
spec:
containers:
- name: app-server
image: registry.cn-hangzhou.aliyuncs.com/rookieops/go-test:v2
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: app-server-canary-svc
spec:
selector:
app: go-test
version: canary
ports:
- name: http
port: 8080
訪問(wèn)效果如下:
# curl 10.110.178.174:8080
{"data":"hello SB","version":"v2"}
上面已經(jīng)將應(yīng)用部署好了,下面將針對(duì)權(quán)重和用戶請(qǐng)求兩個(gè)場(chǎng)景進(jìn)行測(cè)試。
基于權(quán)重的發(fā)布場(chǎng)景
(1)配置stable版本的ingress
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-server-stable-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: joker.coolops.cn
http:
paths:
- path:
backend:
serviceName: app-server-stable-svc
servicePort: 8080
(2)配置canary版本的ingress
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-server-canary-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: joker.coolops.cn
http:
paths:
- path:
backend:
serviceName: app-server-canary-svc
servicePort: 8080
然后我們通過(guò)訪問(wèn)測(cè)試,效果如下:
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello SB","version":"v2"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
基本保持在9:1的比例。
基于用戶請(qǐng)求的發(fā)布場(chǎng)景
(1)配置stable版本的ingress
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-server-stable-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: joker.coolops.cn
http:
paths:
- path:
backend:
serviceName: app-server-stable-svc
servicePort: 8080
(2)配置canary版本的ingress
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app-server-canary-ingress
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "region"
nginx.ingress.kubernetes.io/canary-by-header-value: "sichuan"
spec:
rules:
- host: joker.coolops.cn
http:
paths:
- path:
backend:
serviceName: app-server-canary-svc
servicePort: 8080
當(dāng)我們?cè)L問(wèn)的時(shí)候不帶header,則只會(huì)訪問(wèn)stable版本應(yīng)用,如下:
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
# curl joker.coolops.cn
{"data":"hello world","version":"v1"}
如果我們?cè)谠L問(wèn)的時(shí)候帶上region: sichuan 的header,則只會(huì)訪問(wèn)到canary版本應(yīng)用,如下:
# curl joker.coolops.cn -H "region: sichuan"
{"data":"hello SB","version":"v2"}
# curl joker.coolops.cn -H "region: sichuan"
{"data":"hello SB","version":"v2"}
實(shí)現(xiàn)是不是很簡(jiǎn)單?
我們現(xiàn)在來(lái)想另外一個(gè)問(wèn)題,上面的所有操作都是手動(dòng)的,我們應(yīng)該如何進(jìn)行自動(dòng)化?應(yīng)該怎樣來(lái)設(shè)計(jì)流水線?
下面來(lái)說(shuō)說(shuō)我個(gè)人的想法。
關(guān)于灰度發(fā)布流水線設(shè)計(jì)的想法
首先來(lái)捋捋過(guò)程:
- 發(fā)布canary版本應(yīng)用進(jìn)行測(cè)試
- 測(cè)試完成將canary版本替換成stable版本
- 刪除canary版本的ingress配置
- 刪除老的stable版本
整個(gè)過(guò)程很簡(jiǎn)單,但是對(duì)于已經(jīng)部署好的deployment是不允許直接修改labels標(biāo)簽的。這時(shí)候是不是可以在canary版本測(cè)試i完成后直接更新stable版本的鏡像?當(dāng)然這種情況會(huì)存在滾動(dòng)更新的一個(gè)過(guò)程。
那我們流水線可以這樣設(shè)計(jì),如下:
這樣設(shè)計(jì)存在一個(gè)問(wèn)題,那就是無(wú)法確定等待的時(shí)間,如果等待的時(shí)間很長(zhǎng),不僅很耗資源,也可能自動(dòng)超時(shí)退出。
那我們是不是可以將其拆分為兩條流水線?流程如下:
我比較傾向第二種,這種方式流水線跑完了就退出,不會(huì)占用額外的資源。
在開(kāi)發(fā)流水線之前,我們需要先定義好命名標(biāo)準(zhǔn),這樣在操作的時(shí)候更加方便。
流水線名字格式如下:
-stable -canary
deployment的名字格式如下:
-stable -canary
service的名字格式如下:
-stable-svc -canary-svc
ingress的名字格式如下:
-stable-ingress -canary-ingress
標(biāo)準(zhǔn)定義好之后,在實(shí)現(xiàn)的時(shí)候就簡(jiǎn)單多了。
代碼位置:https://gitee.com/coolops/gary-devops.git
我定義了兩個(gè)Jenkinsfile,一個(gè)叫canary.Jenkinsfile,一個(gè)叫stable.Jenkinsfile,他們分別用來(lái)部署canary和stable版本。
然后我們會(huì)創(chuàng)建兩條流水線,如下:
其中joker-gary-devops-canary是用來(lái)部署canary版本,另外一個(gè)是用來(lái)部署stable版本。
現(xiàn)在在集群里運(yùn)行著stable版本,如下:
# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}
我們修改了需求,更改了代碼,變動(dòng)如下:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
g := gin.Default()
g.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"version": "v1",
"data": "hello Joker!",
})
})
_ = g.Run(":8080")
}
首先發(fā)布canary流水線,待流水線發(fā)布完成,可以在集群中看到canary版本的pod以及ingress等,如下:
# kubectl get po| grep canary
gray-devops-canary-59c88846dc-j2vlc 1/1 Running 0 55s
# kubectl get svc| grep canary
gray-devops-canary-svc ClusterIP 10.233.18.2358080/TCP 3h14m
# kubectl get ingress| grep canary
gray-devops-canary-ingress joker.coolops.cn 192.168.100.61 80 63s
查看一下canary-ingress的內(nèi)容,看是否是我們需要的,如下:
# kubectl get ingress gray-devops-canary-ingress -o yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
creationTimestamp: "2022-02-15T05:43:32Z"
generation: 1
name: gray-devops-canary-ingress
namespace: default
resourceVersion: "412247041"
selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/gray-devops-canary-ingress
uid: fe13b38d-1f6f-45fb-8d89-504b4b8288ea
spec:
rules:
- host: joker.coolops.cn
http:
paths:
- backend:
serviceName: gray-devops-canary-svc
servicePort: 8080
status:
loadBalancer:
ingress:
- ip: 192.168.100.61
可以發(fā)現(xiàn)跟我們預(yù)設(shè)的一樣。
訪問(wèn)測(cè)試也沒(méi)問(wèn)題,如下:
# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello Joker!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello Joker!","version":"v1"}[root@master ~]# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello world!","version":"v1"}
現(xiàn)在就可以發(fā)布stable版本了,運(yùn)行stable版本的流水線。發(fā)布完成后,集群里就只有stable版本的應(yīng)用了,如下:
# kubectl get po | grep gray
gray-devops-stable-7f977bb6cf-8jzgt 1/1 Running 0 35s
# kubectl get ingress | grep gray
gray-devops-stable-ingress joker.coolops.cn
通過(guò)域名訪問(wèn)也是符合預(yù)期的。
# curl -H "Host: joker.coolops.cn" http://192.168.100.61
{"data":"hello Joker!","version":"v1"}
到此基本實(shí)現(xiàn)了自己的想法。
說(shuō)明:Jenkinsfile中涉及的用戶名和密碼都保存在Jenkins的憑據(jù)中,插件需要安裝kubernetes deploy插件,到插件中心搜索就行。
最后
上面我們基本實(shí)現(xiàn)了灰度發(fā)布的過(guò)程,也只是僅僅將手動(dòng)的變成了自動(dòng)。但是你有沒(méi)有發(fā)現(xiàn)什么問(wèn)題?
首先需要切換流水線進(jìn)行發(fā)布,其次是發(fā)布控制方面也不是很友好,比如要增加canary版本的節(jié)點(diǎn),就需要我們手動(dòng)去做。
其實(shí)我更推薦使用argo-rollouts結(jié)合argocd進(jìn)行灰度發(fā)布,argo-rollouts自定義了一套CRD用于控制發(fā)布流程,可以省去很多手動(dòng)操作過(guò)程,argocd是基于gitops實(shí)現(xiàn)的一套軟件,便于我們進(jìn)行CD控制,也提供了UI面板進(jìn)行操作。不過(guò)要用這套就需要更改現(xiàn)有的發(fā)布方式以及應(yīng)用模板,不復(fù)雜,但是存在一定的風(fēng)險(xiǎn),需要進(jìn)行一定程度的測(cè)試。
當(dāng)前標(biāo)題:如何通過(guò) Ingress-Nginx 實(shí)現(xiàn)應(yīng)用灰度發(fā)布?
文章網(wǎng)址:http://m.fisionsoft.com.cn/article/djdhjsh.html


咨詢
建站咨詢
