最近遇到一个有趣的场景,当业务方有一个只运行异步任务的容器,这意味着它逻辑简单,即从上游服务中获取内容进行数据处理,但应用本身不提供任何方式判断当前服务状态。当服务运行出现阻塞时,我们该如何在Kubernetes中来实现探针管理呢?很多同学都使用过存活探针、就绪探针和启动探针,不过它们有一个共同的属性就是需要应用本身提供一个Http/TCP接口或一个Command来评估服务当前是否健康。在不具备上述条件的情况下,我们就只能通过捕获容器的控制台日志输出来判断容器运行是否健康了
不要问我业务应用阻塞的原因,总之一言难尽。
也不要问我靠容器打印的日志来判断是否健康是否不太可靠。但凡研发要求应用稳定会不写探针?
虽然作为平台接锅侠的我们,在应用出现阻塞时,大部分情况下都是在K8S中将出问题的应用杀掉重启
。久而久之,具备丰富delete容器的经验驱使我们应该且需要将这类任务交给K8S自行处理。
首先,要解决的是如何在容器内捕获自己的控制台日志
当一个K8S集群部署完成后,在default命名空间内有一个叫kubernetes的默认service。它的主要作用就是供集群内容器调取k8s api使用的地址。我们可以在容器内通过https://kubernetes.default.svc.cluster.local
访问k8s api。那我们调取容器自身控制台的日志,就可以用如下接口:
https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods/$HOSTNAME/log
这里我们就需要将容器的namespace元数据传入到环境变量,方式如下:
containers:
- env:
- name: NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
先来请求看下情况:
forbidden: User system:anonymous
看来还得加上RABC相关的权限设置。我们可以为应用单独创建一个ServiceAccount,如下:
apiVersion: v1
kind: ServiceAccount
metadata:
name: log-capture
labels:
app.kubernetes.io/name: app
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/name: app
name: log-capture
rules:
- apiGroups: [""]
resources: ["pods","pods/log"]
verbs: ["get","list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/name: app
name: log-capture
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: log-capture
subjects:
- kind: ServiceAccount
name: log-capture
再将该SA引进至Workerload下,如例:
spec:
containers: {}
serviceAccount: log-capture
serviceAccountName: log-capture
如果你天性不羁,也一些可以将RoleBinding做在default上
除此之外,我还需要在请求里带上自己的认证信息。默认情况下容器内的SA Token放在/var/run/secrets/kubernetes.io/serviceaccount/token
路径下。我们再来请求看看
ok,现在我们在容器内能捕获到自己的日志了。
其次,建立K8S探针与控制台日志的关系
明眼的小伙伴可能看出问题了,只通过调取日志接口的方式并不能判断应用是否出现阻塞,因为容器的控制台日志是持久化到node节点的,通过判断日志是否有输出的话,那结果永远会是为真
我们应该给日志接口加上?sinceSeconds=
参数,这样我们就能捕获最近一段时间的日志了。
接下来我们只需要把以上逻辑用shell实现并放在镜像或者configmap里面
用liveness探针去执行即可。这里小白在探测脚本里面加了个探针失败的计数器,来递增扩大捕获日志的时间,可以参考如下:
#!/bin/bash
if [[ -f /tmp/ProbeFailedTimes ]]; then
COUNT=$(cat /tmp/ProbeFailedTimes)
let Probe_Seconds=(1+${COUNT})*${COUNT}/2*60
else
COUNT="0"
Probe_Seconds="60"
fi
STDOUT=`timeout 10 curl -s https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods/$HOSTNAME/log?sinceSeconds=${Probe_Seconds} -k -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"`
if [[ "$STDOUT" == "" ]]; then
let COUNT=${COUNT}+1
echo "$COUNT" > /tmp/ProbeFailedTimes
echo "Now $(cat /tmp/ProbeFailedTimes)"
exit 1
else
rm -rf /tmp/ProbeFailedTimes
echo "Now 0"
exit 0
fi
然后我们再将workerload中添加liveness探针:
containers:
livenessProbe:
exec:
command:
- /bin/bash
- -c
- /probe.sh
failureThreshold: 15
initialDelaySeconds: 120
periodSeconds: 60
successThreshold: 1
timeoutSeconds: 10
这样,liveness检查容器日志的逻辑就变成每60s检查输出,如果没有下次检查180s内,在下次就是360s日志,直到第15次检查2小时前的日志
,刨除探针本身15分钟的时间,最终满足的容器再过去105分钟内无日志输出便任务失败,k8s重启pod。探针检测中途一旦有日志打印,则计数器重置。
为什么要用计数器?灵活调整容器内请求日志的时间范围,避免探针出现在两次打印的中间,出现探测失败
最后不要问如果我的应用连容器日志也没打印该怎么办,我只能说那自求多福吧😂
关注「云原生小白」,进入Loki学习群