当SRS遇到K8s:如何实现高可用、回滚与灰度发布?
Photo by?Luis Quintero?from?Pexels
本文來自流媒體直播集群SRS的官方wiki(https://github.com/ossrs/srs/wiki/v4_CN_K8s),由SRS的創始作者楊成立授權發布。
文 / 楊成立
相關文章:
當SRS遇到K8s:快速構建高并發直播集群
當SRS遇到K8s:如何構建海量推流源站?
?
服務的更新、回滾和灰度,是個簡單的問題,如果加上一個條件"不中斷服務的前提下",那么就是一個難題,如果再加上"大規模",那么就是K8S要解決的核心問題之一。壞消息是這個難搞的問題還真是流媒體服務的核心的、關鍵的、不可忽視的關鍵能力之一,好消息是K8S和云計算讓這個難題稍微好一點點了。
我們在什么場景下會遇到更新、回滾和灰度的問題:
SRS需要升級新版本,如何知道升級后對現有業務沒有影響?如果選擇業務量小升級,那一般常態會是半夜三更、凌晨三四點,還要不要頭發了呢?
改進了新的功能或優化,根據業務定制了新的東西(完全直接使用SRS也得有自己的業務服務器),如何只在一部分機器發布,看看效果有沒有達到預期?
更新新版本后,如果發現有問題,影響了用戶服務,如何在最短時間內回滾到之前的版本?問題出現時首先是要確認問題后(若由升級引起則)回滾,而不是很費時間的找Bug。
在這個場景下,對比K8S和傳統部署方式的差異:
對比項 | ECS | K8S | 說明 |
部署 | 安裝包 | 鏡像 | Docker鏡像可回滾,開發和生產環境一致,可Cache,高效率和高密度,高可移植性,資源隔離可預測程序性能 |
看門狗 | 手動 | 自動 | SRS異常退出由看門狗重新拉起,非K8S需要手動安裝,K8S自動管理和拉起服務 |
更新 | 手動 | 自動 | 傳統方式用腳本下載和更新二進制,人工分批更新,K8S自動Rolling Update,自動下載鏡像和分批更新 |
灰度 | 手動 | 自動 | 傳統方式手動操作SLB決定切量比例,K8S通過Replicas控制比例,自動切量 |
回滾 | 手動 | 自動 | 傳統方式手動回滾,K8S有版本管理和回滾機制 |
Note:平滑更新的關鍵是平滑退出,重點是邊緣集群的更新,對于源站集群我們可以選擇直接重啟,因為一般會有邊緣集群作為代理,源站斷開后邊緣會重試,不影響用戶,參考#1579(https://github.com/ossrs/srs/issues/1579#issuecomment-587233844)。
我們重點關注邊緣集群的平滑退出,SRS邊緣屬于長連接無狀態服務。和Nginx一樣,SRS使用SIGQUIT作為信號,同時配置force_grace_quit認為SIGTERM也是平滑退出,收到SIGQUIT信號后,會等待grace_start_wait指定的時間,然后關閉Listeners新的連接不會分配到這個服務器,然后開始清理并等待現有連接退出,所有連接退出后還會等待grace_final_wait指定的時間,才會退出。
以之前部署的SRS源站和邊緣集群為例,參考SRS Origin Cluster for a Large Number of Streams,SRS邊緣的Pod的配置,需要指定平滑退出的參數,例如:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
?name: srs-edge-config
data:
?srs.conf: |-
???listen????????????? 1935;
???max_connections???? 1000;
???daemon????????????? off;
???grace_start_wait??? 700;
???grace_final_wait??? 800;
???force_grace_quit??? on;
???http_api {
???????enabled???????? on;
???????listen????????? 1985;
??? }
???http_server {
???????enabled???????? on;
???????listen????????? 8080;
??? }
???vhost __defaultVhost__ {
???????cluster {
???????????mode??????????? remote;
???????????origin????????? srs-origin-0.socssrs-origin-1.socs srs-origin2.socs;
???????}
???????http_remux {
???????????enabled???? on;
???????}
??? }
EOF
Remark:一定要開啟force_grace_quit,不開啟(默認)將使用暴力更新,直接斷開現有的連接,參考#1579(https://github.com/ossrs/srs/issues/1579#issuecomment-587233844)。
Note:在K8S中開始刪除Pod時,會快速從Service刪除Pod,所以我們將grace_start_wait和grace_final_wait設置時間短一些,只需要幾百毫秒就足夠了。
SRS邊緣的配置,也需要在lifecycle.preStop事件時啟動平滑退出,并設置terminationGracePeriodSeconds等待時間,例如:
cat <<EOF | kubectl apply --record -f -
apiVersion: apps/v1
kind: Deployment
metadata:
?name: srs-edge-deploy
?labels:
???app: srs-edge
spec:
?replicas: 2
?revisionHistoryLimit: 10
?selector:
???matchLabels:
?????app: srs-edge
?template:
???metadata:
?????labels:
???????app: srs-edge
???spec:
?????volumes:
?????- name: config-volume
???????configMap:
?????????name: srs-edge-config
?????containers:
?????- name: srs
???????image: ossrs/srs:v4.0.5
???????imagePullPolicy: IfNotPresent
???????ports:
???????- containerPort: 1935
???????- containerPort: 1985
???????- containerPort: 8080
???????volumeMounts:
???????- name: config-volume
?????????mountPath: /usr/local/srs/conf
???????lifecycle:
?????????preStop:
???????????exec:
????????????? command:["/usr/local/srs/etc/init.d/srs", "grace"]
?????terminationGracePeriodSeconds: 120
EOF
Note:?kubectl apply增加了一個參數--record,后面回滾會用到。
Note:?terminationGracePeriodSeconds等待退出時間我們設置2分鐘,線上服務可以設置更長,比如12小時。
Remark:為了更好體現平滑更新的邏輯,我們設置Replicas=2可以更容易演示。
Remark:我們使用SRS4演示,例如v4.0.5,實際上SRS3也可以的比如v3.0-b1等。
我們停掉了之前srs-demo-deploy推的兩個DEMO流,采用手動推流到Edge,方便演示升級時有長連接需要服務的情況:
ffmpeg -re -i ./doc/source.200kbps.768x320.flv-c copy \
??? -fflv rtmp://28.170.32.118/live/livestream
Note:請將上面的EIP換成你自己的,可用命令kubectlget svc/srs-edge-service查看你的EIP。
咱們可以看到目前啟動了2個Edge,可以看下它的版本,是通過Pod(z9gbm)推流:
kubectl get po|grep edge
srs-edge-deploy-58d9999b7c-pnr2f?????? 1/1????Running?? 0????????? 16s
srs-edge-deploy-58d9999b7c-z9gbm?????? 1/1????Running?? 0????????? 16s
?
kubectl exec srs-edge-deploy-58d9999b7c-pnr2f-- ./objs/srs -v
4.0.5
kubectl exec srs-edge-deploy-58d9999b7c-pnr2f-- yum install -y net-tools
kubectl exec srs-edge-deploy-58d9999b7c-pnr2f-- netstat -anp|grep 1935
tcp???????0????? 0 0.0.0.0:1935??????????? 0.0.0.0:*?????????????? LISTEN????? 1/./objs/srs
?
kubectl exec srs-edge-deploy-58d9999b7c-z9gbm-- ./objs/srs -v
4.0.5
kubectl exec srs-edge-deploy-58d9999b7c-z9gbm-- yum install -y net-tools
kubectl exec srs-edge-deploy-58d9999b7c-z9gbm-- netstat -anp|grep 1935
tcp???????0????? 0 0.0.0.0:1935??????????? 0.0.0.0:*?????????????? LISTEN????? 1/./objs/srs
tcp???????0????? 0 172.20.0.62:46482?????? 172.20.0.41:1935??????? ESTABLISHED 1/./objs/srs
tcp???????0????? 0 172.20.0.62:1935??????? 172.20.0.1:12066??????? ESTABLISHED 1/./objs/srs
Note:我們只推流一個流,會有兩個連接,一個是客戶端到Edge的連接,一個是Edge回源到Origin的連接。
下面我們會分幾個部分,看發布中遇到的問題:
SRS Cluster Rolling Update: 在平滑退出基礎上的滾動更新,集群更新的基礎機制。
SRS Cluster Rolling Back: 在平滑退出基礎上的發布回滾,發布遇到問題首先考慮回滾。
SRS Cluster Canary Release: 金絲雀升級,可精確控制的流量控制和回滾。
SRS Cluster Rolling Update
K8S的更新是Rolling Update,也就是修改和更新Pods時,會分批次執行。比如,上面的例子中SRS邊緣的版本是v4.0.5,若我們現在需要更新到4.0.6,鏡像已經打好了ossrs/srs:v4.0.6,那么我們可以用命令更新:
kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6 --record
可以看這兩個Pod的日志,沒有連接的Pod很快就退出了,而有連接的Pod經過了一定的時間才退出(若客戶端連接主動斷開會更快退出):
kubectl exec srs-edge-deploy-58d9999b7c-pnr2f -- tail -f objs/srs.log
[2020-02-19 11:07:20.818][Trace][1][937]sig=3, user start gracefully quit
[2020-02-19 11:07:20.960][Trace][1][937]force gracefully quit, signo=15
[2020-02-19 11:07:21.772][Trace][1][932]cleanup for quitsignal fast=0, grace=1
[2020-02-19 11:07:21.772][Warn][1][932][11]main cycle terminated, system quit normally.
commandterminated with exit code137
?
kubectl exec srs-edge-deploy-58d9999b7c-z9gbm -- tail -f objs/srs.log
[2020-02-19 11:07:23.095][Trace][1][1009]sig=3, user start gracefully quit
[2020-02-19 11:07:23.316][Trace][1][1009]force gracefully quit, signo=15
[2020-02-19 11:07:23.784][Trace][1][1004]cleanup for quitsignal fast=0, grace=1
[2020-02-1911:07:23.784][Warn][1][1004][11] main cycle terminated, system quit normally.
[2020-02-19 11:07:24.784][Trace][1][1004] waitfor 1 conns to quit
[2020-02-19 11:07:26.968][Trace][1][1010] <- CPB time=120041497, okbps=0,0,0,ikbps=252,277,0, mr=0/350, p1stpt=20000, pnt=5000
[2020-02-19 11:08:26.791][Trace][1][1004] waitfor 1 conns to quit
[2020-02-19 11:08:52.602][Trace][1][1010]edge change from 200 to state 0 (init).
[2020-02-19 11:08:52.792][Trace][1][1004] waitfor 0 conns to quit
commandterminated with exit code137
?
kubectl get po |grep edge
NAME?????????????????????????????????? READY?? STATUS???????RESTARTS?? AGE
srs-edge-deploy-58d9999b7c-z9gbm?????? 0/1????Terminating?? 0????????? 3m52s
srs-edge-deploy-76fcbfb848-z5rmn?????? 1/1????Running?????? 0????????? 104s
srs-edge-deploy-76fcbfb848-zt4wv?????? 1/1????Running?????? 0????????? 106s
Remark:注意我們現在是有一個Pod有客戶端在推流的。同樣,我們指定了參數--record,會在后面回滾時用得著。
若RollingUpdate期間,我們需要暫停更新,可以用kubectl rollout暫停和恢復:
kubectl rollout pausedeploy/srs-edge-deploy
kubectl rollout resumedeploy/srs-edge-deploy
SRS Cluster Rolling Back
每次發布K8S都會記錄一個Revision,若我們傳遞了--record參數(正如前面我們做的),則會記錄更詳細的CHANGE-CAUSE,比如:
kubectl rollout history deploy/srs-edge-deploy
REVISION?CHANGE-CAUSE
1????????kubectl apply --record=true --filename=-
2 ?????? kubectl set imagedeploy/srs-edge-deploy srs=ossrs/srs:v4.0.6 --record=true
Note:默認ACK只保留10個Revision,可以通過設置revisionHistoryLimit增加可回滾的版本。
若出現異常,可以回滾到之前的版本,例如:
kubectl rollout undo deploy/srs-edge-deploy--to-revision=1
實際上回滾的過程也是Rolling Update的過程,只是不用指定修改什么配置,而是指定的哪個歷史版本的配置?;貪L后,新增了一個版本3,和1是一樣的:
REVISION?CHANGE-CAUSE
1????????kubectl apply --record=true --filename=-
2 ?????? kubectl set image deploy/srs-edge-deploy srs=ossrs/srs:v4.0.6--record=true
3????????kubectl apply --record=true --filename=-
Note:可以在阿里云控制臺來選擇回滾到哪個版本。
SRS Cluster Canary Release
Canary是金絲雀發布,指試探性的發布一些版本,沒有問題就繼續擴大比例。由于涉及到具體的發布比例,所以我們要在RollingUpdate基礎上,能控制新老Pods的數目,這就需要使用SLB了,參考Kubernetes集群中使用阿里云 SLB 實現四層金絲雀發布。
Note:關于金絲雀發布,最初發布的版本就好比金絲雀,在以前煤礦中會把金絲雀先送下去,如果缺氧雀兒就掛了。
以上面的Edge集群為例,假設目前版本是v4.0.5,有三個Edge Pod在運行,通過SLB對外提供服務:
cat <<EOF | kubectl apply --record -f -
apiVersion: apps/v1
kind: Deployment
metadata:
?name: srs-edge-r5-deploy
?labels:
???run: srs-edge-r5
spec:
?replicas: 3
?selector:
???matchLabels:
?????run: srs-edge-r5
?template:
???metadata:
?????labels:
???????run: srs-edge-r5
???????app: srs-edge
???spec:
?????volumes:
?????- name: config-volume
???????configMap:
?????????name: srs-edge-config
?????containers:
?????- name: srs
???????image: ossrs/srs:v4.0.5
???????imagePullPolicy: IfNotPresent
???????ports:
???????- containerPort: 1935
???????- containerPort: 1985
???????- containerPort: 8080
???????volumeMounts:
???????- name: config-volume
?????????mountPath: /usr/local/srs/conf
???????lifecycle:
?????????preStop:
???????????exec:
????????????? command:["/usr/local/srs/etc/init.d/srs", "grace"]
?????terminationGracePeriodSeconds: 120
EOF
Remark:注意Pod的labels有兩個,一個是run:srs-edge-r5是這個應用所使用的,另外一個是app: srs-edge是Service用的,新老的SRS都有這個標簽這樣Service就可以都轉發了。
執行命令后,可以看到三個Pod在運行:
kubectl get po
NAME?????????????????????????????????? READY?? STATUS???RESTARTS?? AGE
srs-edge-r5-deploy-6c84cdc77b-q2j97??? 1/1????Running?? 0????????? 3m15s
srs-edge-r5-deploy-6c84cdc77b-s6pzh??? 1/1????Running?? 0????????? 3m15s
srs-edge-r5-deploy-6c84cdc77b-wjdtl??? 1/1????Running?? 0????????? 3m15s
如果我們要升級到v4.0.6,但是只想先升級一臺,這臺就是金絲雀了。我們可以創建另外一個Deployment,他們的name不一樣,但使用同樣的Service:
cat <<EOF | kubectl apply --record -f -
apiVersion: apps/v1
kind: Deployment
metadata:
?name: srs-edge-r6-deploy
?labels:
???run: srs-edge-r6
spec:
?replicas: 1
?selector:
???matchLabels:
?????run: srs-edge-r6
?template:
???metadata:
?????labels:
???????run: srs-edge-r6
???????app: srs-edge
???spec:
?????volumes:
?????- name: config-volume
???????configMap:
?????????name: srs-edge-config
?????containers:
?????- name: srs
???????image: ossrs/srs:v4.0.6
???????imagePullPolicy: IfNotPresent
???????ports:
???????- containerPort: 1935
???????- containerPort: 1985
???????- containerPort: 8080
???????volumeMounts:
???????- name: config-volume
?????????mountPath: /usr/local/srs/conf
???????lifecycle:
?????????preStop:
???????????exec:
????????????? command:["/usr/local/srs/etc/init.d/srs", "grace"]
?????terminationGracePeriodSeconds: 120
EOF
Remark:注意Pod的labels有兩個,一個是run:srs-edge-r6是這個應用所使用的,另外一個是app: srs-edge是Service用的,和之前的老版本是一樣的,這樣Service就可以都轉發了。
執行命令后,可以看到四個Pod在運行,三個老的,一個新的,這樣就灰度了25%的流量到了新版本:
kubectl get po
NAME?????????????????????????????????? READY?? STATUS???RESTARTS?? AGE
srs-edge-r5-deploy-6c84cdc77b-q2j97??? 1/1????Running?? 0????????? 3m30s
srs-edge-r5-deploy-6c84cdc77b-s6pzh??? 1/1????Running?? 0????????? 3m30s
srs-edge-r5-deploy-6c84cdc77b-wjdtl??? 1/1????Running?? 0????????? 3m30s
srs-edge-r6-deploy-598f4698d-kkfnb???? 1/1????Running?? 0????????? 6s
?
whiletrue;do ffmpeg -f flv -irtmp://r.ossrs.net/live/livestream 2>&1|grep server_version; sleep 1;done
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.6 # 這是新版本
???server_version? : 4.0.5
???server_version? : 4.0.5
???server_version? : 4.0.6 # 這是新版本
那么接下來,只需要調整新老的Deployment的Replicas,就能調整流量的比例了,比如我們增加新版本比重,只留一臺老的:
kubectl scale --replicas=3deploy/srs-edge-r6-deploy
kubectl scale --replicas=1deploy/srs-edge-r5-deploy
可以看到經過Gracefully Quit平滑升級和退出,最終變成了我們聲明的那個樣子,對業務不影響:
kubectl get po
NAME?????????????????????????????????? READY?? STATUS???RESTARTS?? AGE
nginx-origin-deploy-85f4695685-gn2df?? 3/3????Running?? 0????????? 5h31m
srs-edge-r5-deploy-6c84cdc77b-s6pzh??? 1/1????Running?? 0????????? 25m
srs-edge-r6-deploy-f6b59c6c6-ddgxw???? 1/1????Running?? 0????????? 2m59s
srs-edge-r6-deploy-f6b59c6c6-gvnd8???? 1/1????Running?? 0????????? 2m54s
srs-edge-r6-deploy-f6b59c6c6-j46b5???? 1/1????Running?? 0????????? 2m58s
?
whiletrue;do ffmpeg -f flv -irtmp://r.ossrs.net/live/livestream 2>&1|grep server_version; sleep 1;done
?? ?server_version?: 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.5 # 這是老版本
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.6
???server_version? : 4.0.5 # 這是老版本
???server_version? : 4.0.6
???server_version? : 4.0.6
最終我們只要把老的Replicas設為0,然后就可以刪除老的應用srs-edge-r5-deploy了,系統全部變成新的版本了,如下圖所示:
親,爽嗎?干凈利落,談笑間,強擼灰飛湮滅啦。
LiveVideoStackCon 2020
上海/北京/舊金山 講師招募
2020年LiveVideoStackCon將持續迭代,LiveVideoStackCon將分別在上海(6月13-14日),北京(9月11-12日)和舊金山(11月)舉行。歡迎將你的技術實踐、踩坑與填坑經歷、技術與商業創業的思考分享出來,獨樂不如眾樂。請將個人資料和話題信息郵件到 speaker@livevideostack.com 或點擊【閱讀原文】了解成為LiveVideoStackCon講師的權益與義務,我們會在48小時內回復。
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的当SRS遇到K8s:如何实现高可用、回滚与灰度发布?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实时远程医学影像服务质量保障与网络优化
- 下一篇: 音视频技术开发周刊 | 132(FFmp