Kubernetes 一個重要特性是Pod具備自愈能力。當Pod發(fā)生故障后會能通過自愈機制對Pod自動重啟,另外也可以通過Liveness和Readiness的探測機制進行更精細的故障監(jiān)控排查。
先了解Kubernetes 默認的自檢機制
每個容器啟動時都會執(zhí)行一個進程,此進程由Dockerfile的CMD或ENTRYPOINT指定。如果進程退出返回碼為非0,則認為容器發(fā)生故障,Kubernetes會根據(jù)restartPolicy重啟容器。
#創(chuàng)建應(yīng)用測試
#本地終端,進入server節(jié)點容器的命令
multipass shell server
#進入容器后,先創(chuàng)建文件:pod-monitorer.yml
sudo vi pod-monitorer.yml
#pod-monitorer.yml 的內(nèi)容
apiVersion: v1
kind: Pod
metadata:
name: monitorer
labels:
app: monitorer
spec:
restartPolicy: OnFailure #Pod的restartPolicy設(shè)置為OnFailure,默認策略為Always
containers:
- name: monitorer
image: busybox
args:
- /bin/sh
- -c
- sleep 20;exit 1
執(zhí)行Pod
#導(dǎo)入yml
sudo kubectl apply -f pod-monitorer.yml
#輸出結(jié)果
pod/monitorer created
然后我們來觀察效果
#我們馬上執(zhí)行pod 的查詢,發(fā)現(xiàn)pod 正常,RESTARTS 次數(shù)為0
ubuntu@server:~$ sudo kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
monitorer 1/1 Running 0 16s 10.42.2.52 node2 <none> <none>
#20秒后,我們再觀察發(fā)現(xiàn)RESTARTS次數(shù)加1了
ubuntu@server:~$ sudo kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
monitorer 1/1 Running 1 (19s ago) 42s 10.42.2.52 node2 <none> <none>
每過20秒,容器就會因為進程返回非0,自動重啟1次。
容器進程返回值非0,Kubernetes認為容器發(fā)生故障,需要重啟,有不少場景下發(fā)生故障,但進程不會退出,比如訪問web服務(wù)時發(fā)生500內(nèi)部錯誤,可能是負載過高,也可能是資源死鎖,此時httpd進程并沒有異常退出,在這種情況下重啟容器可能是最直接、最有效的解決方案,處理此類場景那就用到Liveness探測。
Liveness探測
Liveness探測讓用戶可以添加自定義檢測條件,判斷容器是否健壯的情況,如果檢測失敗,Kubernetes 就會重啟Pod容器。
先建立一個測試使用的NodeJS Demo
在第六章節(jié)時,我們已經(jīng)建立了一個能訪問MongoDB的NodeJS Demo,現(xiàn)在需要在這基礎(chǔ)上增加Liveness監(jiān)控的代碼。
這里我對示例做一個簡單的說明, 我希望pod 能夠每10秒時間監(jiān)控NodeJS的健康狀態(tài),我通過API:/checkLiveness 模擬獲取這個健康情況,假設(shè)改變數(shù)據(jù)表的數(shù)據(jù)來模擬崩潰了即代表NodeJS有異常,Pod 需要捕捉這個情況并且重啟。
API:/changeLivenessStatus 則是改變數(shù)據(jù)表的健康值。
#1、修改app/controller/home.js
//改變?nèi)萜鹘】禒顟B(tài)(測試)
async changeLivenessStatus(){
const status=this.ctx.paramValue('status','open');
await this.ctx.app.mongo.insertOne('liveness', {
doc: {
status,
create_time:(new Date()).toLocaleString()
}
});
}
//容器是否健康
async checkLiveness(){
const liveness=await this.app.mongo.find('liveness', {
sort:{
_id:-1
},
});
if(liveness.length>0)
console.log(`new status:${liveness[0].status}`);
if(liveness.length>0 && liveness[0].status=='close'){
throw new Error('Catch me');
}
else{
this.ctx.body="it is ok.";
}
}
#2、修改/app/router.js
#添加API路由
router.get('/changeLivenessStatus',controller.home.changeLivenessStatus);
router.get('/checkLiveness', controller.home.checkLiveness);
#3、檢查代碼錯誤,這里略
#4、記得檢查是否編譯了。
npm install --production
使用Dockerfile生成鏡像(不明白的請回顧第六章節(jié))。
docker build -t k3s-test:latest .
#容器測試
docker run -p 8080:7001 --name k3s-test -e host_param="192.168.64.2" -e port_param="30017" -e db_param="db_test" -e user_param="test" -e pwd_param="test1234" k3s-test:latest
#測試
curl http://localhost:8080
#輸出內(nèi)容
Hello K8s!%
#再連接進行檢查。
curl https://localhost:7001/changeLivenessStatus?status=open
curl https://localhost:7001/checkLiveness
后面就是回顧第二章節(jié)的內(nèi)容把鏡像 打包到鏡像倉庫,獲得一個鏡像地址。這里就不再復(fù)述了,有興趣的朋友請看回第二章節(jié) ,同時為了讀者測試方便,我把做好的鏡像放到,方便各位使用。
使用yaml 編寫Deployment
所有的示例均在Multipass內(nèi)執(zhí)行。
#本地終端,進入server節(jié)點容器的命令
multipass shell server
#進入容器后,先創(chuàng)建文件:pod-liveness.yml
sudo vi pod-liveness.yml
#pod-liveness.yml 的內(nèi)容
apiVersion: apps/v1
kind: Pod
metadata:
name: liveness
labels:
app: liveness
spec:
restartPolicy: OnFailure
containers:
- name: liveness
image: registry.cn-hangzhou.aliyuncs.com/dawson-project/k3s-test-v2:latest
env:
- name: host_param
value: 192.168.64.2
- name: port_param
value: "30017"
- name: db_param
value: db_test
- name: user_param
value: test
- name: pwd_param
value: test1234
livenessProbe:
httpGet:
scheme: HTTP #指定協(xié)議,默認為HTTP
path: /checkLiveness #訪問路徑
port: 7001
periodSeconds: 5 #指每5秒執(zhí)行一次Liveness探測
#192.168.64.2 等參數(shù)為本人測試數(shù)據(jù)庫地址。
引入yaml 文件
ubuntu@server:~$ sudo kubectl apply -f pod-liveness.yml
pod/liveness created
觀察Liveness 監(jiān)控效果吧
我們通過kubectl 監(jiān)控pod時,pod 運行一切正常。
ubuntu@server:~$ sudo kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
liveness-deployment-684f6f6f66-wjpr2 1/1 Running 0 11m 10.42.1.81 node1 <none> <none>
接下來,我們試著往數(shù)據(jù)表liveness 插入一條關(guān)閉命令。
#調(diào)用pod 的關(guān)閉命令
ubuntu@server:~$ curl http://10.42.1.81:7001/changeLivenessStatus?status=close
大約過了20秒后,我們再重新監(jiān)控pod 時,RESTARTS的次數(shù)已經(jīng)改變了。證明Liveness 監(jiān)控到應(yīng)用發(fā)生了異常,激活了重啟機制。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
liveness-deployment-684f6f6f66-wjpr2 1/1 CrashLoopBackOff 1 (24s ago) 14m 10.42.1.81 node1 <none> <none>
Readiness探測
與Liveness探測有所區(qū)別。Readiness探測告訴Kubernetes什么時候可以將Pod容器加入到Service負載均衡池中,對外提供服務(wù)。
這兩種探測配置方法完全一樣,支持的配置參數(shù)也一樣,不同在于探測失敗后,Liveness探測是重啟容器,Readiness探測則將容器設(shè)置為不可用,不接收service轉(zhuǎn)發(fā)的請求。
調(diào)整NodeJS Demo
與Liveness探測的Demo一致,我們先調(diào)整代碼,加上可以專屬Readiness的監(jiān)管的代碼。
#1、修改app/controller/home.js
//獲取本機ip
getLocalIPAddress(){
let interfaces=os.networkInterfaces();
for(let devName in interfaces){
let iface=interfaces[devName];
for(let alias of iface){
if(alias.family=== 'IPv4' && alias.family!='127.0.0.1' && !alias.internal){
return alias.address;
}
}
}
return 'no address';
}
//改變?nèi)萜骷尤氲絪ervice的狀態(tài)(測試)
async changeReadinessStatus(){
const status=this.ctx.paramValue('status','open');
await this.ctx.app.mongo.insertOne('rediness', {
doc: {
ip:this.getLocalIPAddress(),
status,
create_time:(new Date()).toLocaleString()
}
});
}
//容器是否允許加入到service(測試)
async checkReadiness(){
const rediness=await this.app.mongo.find('rediness', {
query:{
ip:this.getLocalIPAddress()
},
sort:{
_id:-1
},
});
if(rediness.length>0)
console.log(`new status:${rediness[0].status}`);
if(rediness.length>0 && rediness[0].status=='close'){
throw new Error('Catch me');
}
else{
this.ctx.body="it is ok.";
}
}
#2、修改/app/router.js
#添加API路由
router.get('/changeReadinessStatus',controller.home.changeReadinessStatus);
router.get('/checkReadiness', controller.home.checkReadiness);
#3、檢查代碼錯誤,這里略
#4、記得檢查是否編譯了。
npm install --production
然后打包到鏡像倉庫。
使用yaml編寫Deployment
#本地終端,進入server節(jié)點容器的命令
multipass shell server
#進入容器后,先創(chuàng)建文件:pod-readiness.yml
sudo vi pod-readiness.yml
#pod-readiness.yml的內(nèi)容
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: readiness-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: readiness
spec:
containers:
- name: readiness
image: registry.cn-hangzhou.aliyuncs.com/dawson-project/k3s-test-v2:latest
env:
- name: host_param
value: 192.168.64.2
- name: port_param
value: "30017"
- name: db_param
value: db_test
- name: user_param
value: test
- name: pwd_param
value: test1234
ports:
- containerPort: 7001
readinessProbe:
httpGet:
scheme: HTTP #指定協(xié)議,默認為HTTP
path: /checkReadiness #訪問路徑
port: 7001
periodSeconds: 10 #每10秒進行一次檢測
---
apiVersion: v1
kind: Service
metadata:
name: readiness-service
spec:
selector:
app: readiness
ports:
- protocol: TCP
port: 7001
targetPort: 80
#192.168.64.2 等參數(shù)為本人測試數(shù)據(jù)庫地址。
導(dǎo)入yaml文件
ubuntu@server:~$ sudo kubectl apply -f pod-readiness.yml
deployment.apps/readiness-deployment created
service/readiness-service created
測試場景的邏輯大致是這樣:
- 容器啟動后開始探測。如果http://[container_ip]:7001/checkReadiness返回碼不是200~400,表示容器沒有就緒,不接收Service web-svc的請求。開始時都會正常返回200
- 然后我們可以手動調(diào)整某些Pod的狀態(tài),只要調(diào)用http://[container_ip]:7001/changeReadinessStatus?status=close即可。
- 容器會每隔10秒再探測一次
- 直到某個容器的返回碼為非200后,模擬出容器在崩潰狀態(tài),web-svc的負載會自動排除異常容器,開始處理請求。