背景

前段时间接手了一份维护老系统的任务。该系统使用了早期的 Spring Cloud 全家桶,其中有一个微服务随着时间运行会出现大量 CLOSE_WAIT 状态的 socket 连接以至于堵塞网关,检查后发现与 HttpClient 相关(可参考 解决:HttpClient导致应用出现过多Close_Wait的问题 这篇博文),但是由于没有完整的源码,无法通过博文里提到的方法解决。因此考虑通过外部手段检测并重启服务来恢复网关与服务的通讯,简单的检测手段是通过发起 HTTP 请求看超时情况:

$ curl --connect-timeout 10 -m 10 <host>:<port>

配置 Kubernetes 存活探测

我们使用了 Kubernetes 作为部署环境,它使用存活探测器来知道什么时候要重启容器。存活探测器有三种类型:

  • 存活命令;
  • HTTP 存活探测,发起 HTTP GET 请求以探测容器是否存活;
  • TCP 存活探测,发起 socket 连接以探测容器是否存活。

由于我们需要检测的服务的问题是容器内存在大量的 CLOSE_WAIT 状态连接,此时新的 socket 连接已经无法连通,使用 HTTP 存活探测时其超时检查无法作用于 socket 超时,因此应该使用 TCP 存活探测。

参照 Kubernetes 官方文档提供的示例即可配置相关探测器:

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20
注意

更多信息参见 定义 TCP 的存活探测

配置 cron 执行脚本以检查 K8s Pod 存活状态

经过一段的运行后,Kubernetes 自带的存活探测工作良好,但是客户希望能获悉服务实例重启的信息,即每当服务实例重启时发送消息至群聊。

一开始考虑使用 BOTKUBE 收集服务实例重启信息,但是有几个小问题:

  • BOTKUBE 原生仅支持 Slack、Mattermost、Microsoft Teams、Elastic Search、Webhook 五种方式;
  • BOTKUBE 是被动收集指定负载的相关事件。

但是客户希望能在钉钉群里中获悉 “N 个实例中重启了 M 个” 信息以进行评估稳定性。

如果通过 Webhook 接入 BOTKUBE,则需要在一个短暂的周期内维护服务的总实例数与周期内重启实例数量。相关编码工作量太大,于是我们通过编写简单的脚本并配置 cron 定时任务来完成该需求。

脚本思路

假定我们要检查的服务名称为 service
export KUBECONFIG=/path/to/your/kubernetes.yaml
pods=""
total=`/usr/local/bin/kubectl --kubeconfig=$KUBECONFIG get pods -o wide | grep service | sed -n '$='` # 1
for pod in `/usr/local/bin/kubectl --kubeconfig=$KUBECONFIG get pods -o wide | grep service | awk '{print $1 "_" $6}'`    # 2
do
        name=`echo $pod | awk -F_ '{print $1}'`
        ip=`echo $pod | awk -F_ '{print $2}'`
        sname=`echo $name | awk -F- '{print $5}'`
        curl -s --connect-timeout 10 -m 10 $ip:8672 > /dev/null # 3
        if [ $? -ne 0 ]; then # 4
                /usr/local/bin/kubectl delete pod $name
                pods="$pods$sname ×, "
        else
                pods="$pods$sname √, "
        fi
done
pods="${pods%??}" # 5
success=`echo $pods | awk -F"√" '{print NF-1}'` # 6
if [ $success -ne $total ]; then
        # 7
        curl 'https://oapi.dingtalk.com/robot/send?access_token=***' \
                        -H 'Content-Type: application/json' \
                        -d '{ "msgtype": "text", "text": { "content": "检查结果 ['"$success"'/'"$total"'] :\n'"${pods}"'" } }'
fi
  1. 统计当前正在运行的容器实例数量
  2. 我们需要 pod 的名称以在必要的时候通过 kubectl delete 删除它,还需要 pod 的虚拟 ip 地址以通过 curl 测试连接情况;
  3. 我们不需要 curl 的连接状态信息和连接成功后的资源下载进度信息,因此通过 -s 参数和重定向到空设备来 静音
  4. curl 因 socket 连接超时返回非 0 值时删除该容器;
  5. 截断多余的 ,<空格>
  6. 统计尚在正常运行的容器数量;
  7. 仅当删除了一个或以上的容器时,发送构造好的报告信息至钉钉机器人。

效果:

检查结果 [11/12] :
wdqdd ×, 5xwpz √, rgmc7 √, 8cf4f √, spttn √, dvw2l √, tg9lw √, kzrc2 √, fpk9s √, 9plpt √, dpkpf √, gnhrl √

配置 cron 定时任务

我们使用的是 Ubuntu Server 18,通过 crontab -e 配置定时任务:

*/10 8-22 * * * /path/to/your/script.sh

使用 cron 执行脚本需要注意几个问题:

  • cron 与脚本的权限问题;
  • 设置脚本的可执行权限(chmod +x script.sh);
  • cron 执行脚本时传递的是最小集环境变量,因此需要指定二进制执行文件的路径,推荐在脚本开头使用 export 指定路径,但我们内部使用且从简处理,选择直接指定绝对路径 /usr/local/bin/kubectl
  • 系统的时区设置,如果修改了时区,需要重启 cron 服务。

更多关于配置 cron 的注意事项可以参考 Why crontab scripts are not working?


krun
6.9k 声望11.3k 粉丝