Kubernetes 一个重要特性是Pod具备自愈能力。当Pod发生故障后会能通过自愈机制对Pod自动重启,另外也可以通过Liveness和Readiness的探测机制进行更精细的故障监控排查。
先了解Kubernetes 默认的自检机制
每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。如果进程退出返回码为非0,则认为容器发生故障,Kubernetes会根据restartPolicy重启容器。
#创建应用测试
#本地终端,进入server节点容器的命令
multipass shell server
#进入容器后,先创建文件:pod-monitorer.yml
sudo vi pod-monitorer.yml
#pod-monitorer.yml 的内容
apiVersion: v1
kind: Pod
metadata:
name: monitorer
labels:
app: monitorer
spec:
restartPolicy: OnFailure #Pod的restartPolicy设置为OnFailure,默认策略为Always
containers:
- name: monitorer
image: busybox
args:
- /bin/sh
- -c
- sleep 20;exit 1
执行Pod
#导入yml
sudo kubectl apply -f pod-monitorer.yml
#输出结果
pod/monitorer created
然后我们来观察效果
#我们马上执行pod 的查询,发现pod 正常,RESTARTS 次数为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秒后,我们再观察发现RESTARTS次数加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认为容器发生故障,需要重启,有不少场景下发生故障,但进程不会退出,比如访问web服务时发生500内部错误,可能是负载过高,也可能是资源死锁,此时httpd进程并没有异常退出,在这种情况下重启容器可能是最直接、最有效的解决方案,处理此类场景那就用到Liveness探测。
Liveness探测
Liveness探测让用户可以添加自定义检测条件,判断容器是否健壮的情况,如果检测失败,Kubernetes 就会重启Pod容器。
先建立一个测试使用的NodeJS Demo
在第六章节时,我们已经建立了一个能访问MongoDB的NodeJS Demo,现在需要在这基础上增加Liveness监控的代码。
这里我对示例做一个简单的说明, 我希望pod 能够每10秒时间监控NodeJS的健康状态,我通过API:/checkLiveness 模拟获取这个健康情况,假设改变数据表的数据来模拟崩溃了即代表NodeJS有异常,Pod 需要捕捉这个情况并且重启。
API:/changeLivenessStatus 则是改变数据表的健康值。
#1、修改app/controller/home.js
//改变容器健康状态(测试)
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生成镜像(不明白的请回顾第六章节)。
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
#输出内容
Hello K8s!%
#再连接进行检查。
curl https://localhost:7001/changeLivenessStatus?status=open
curl https://localhost:7001/checkLiveness
后面就是回顾第二章节的内容把镜像 打包到镜像仓库,获得一个镜像地址。这里就不再复述了,有兴趣的朋友请看回第二章节 ,同时为了读者测试方便,我把做好的镜像放到,方便各位使用。
使用yaml 编写Deployment
所有的示例均在Multipass内执行。
#本地终端,进入server节点容器的命令
multipass shell server
#进入容器后,先创建文件:pod-liveness.yml
sudo vi pod-liveness.yml
#pod-liveness.yml 的内容
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 #指定协议,默认为HTTP
path: /checkLiveness #访问路径
port: 7001
periodSeconds: 5 #指每5秒执行一次Liveness探测
#192.168.64.2 等参数为本人测试数据库地址。
引入yaml 文件
ubuntu@server:~$ sudo kubectl apply -f pod-liveness.yml
pod/liveness created
观察Liveness 监控效果吧
我们通过kubectl 监控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>
接下来,我们试着往数据表liveness 插入一条关闭命令。
#调用pod 的关闭命令
ubuntu@server:~$ curl http://10.42.1.81:7001/changeLivenessStatus?status=close
大约过了20秒后,我们再重新监控pod 时,RESTARTS的次数已经改变了。证明Liveness 监控到应用发生了异常,激活了重启机制。
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探测有所区别。Readiness探测告诉Kubernetes什么时候可以将Pod容器加入到Service负载均衡池中,对外提供服务。
这两种探测配置方法完全一样,支持的配置参数也一样,不同在于探测失败后,Liveness探测是重启容器,Readiness探测则将容器设置为不可用,不接收service转发的请求。
调整NodeJS Demo
与Liveness探测的Demo一致,我们先调整代码,加上可以专属Readiness的监管的代码。
#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';
}
//改变容器加入到service的状态(测试)
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节点容器的命令
multipass shell server
#进入容器后,先创建文件:pod-readiness.yml
sudo vi pod-readiness.yml
#pod-readiness.yml的内容
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 #指定协议,默认为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 等参数为本人测试数据库地址。
导入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
- 然后我们可以手动调整某些Pod的状态,只要调用http://[container_ip]:7001/changeReadinessStatus?status=close即可。
- 容器会每隔10秒再探测一次
- 直到某个容器的返回码为非200后,模拟出容器在崩溃状态,web-svc的负载会自动排除异常容器,开始处理请求。