ccw 的 Shell CookBook 系列第一篇,本系列主要是记录 ccw 的一些 shell 使用 case,并非各种命令的教学。

本篇为大家带来一条 Watchdog,主要功能是定时监控系统运行是否正常,其本体是一段 Bash Script。另外 Watchdog 这个词是一念之间造出来的(也可能是潜意识的记忆,但不知道出处了),因为它有问题就会叫(报警),以看门狗来形容还算贴切。

前言

时代在前进,工具在进步,这年头能熟练使用 Shell 的人,我感觉大概不多了。我也不熟,所以需要用文字记录来确保将来想起来的时候能找到。

就我来说,有一些场景我是不会用 Shell 的,尤其是处理字符串,比如处理 JSON 我会直接上 JavaScript(这很合理,它能直接读 JSON),统计去重后条数我会用原生支持 set 的语言临时整个然后取长度(这很经典,但我确实不能保证能打对sortuniq的各种参数组合,也因此被人吐槽过),还有根据规则做替换(sed 和 awk 啥的很有名,但是确实不是首选。关于 sed,主体的语法在 vim 里大概会用,然而在外面带上参数就不熟了;如果字符串或文件不算大,直接贴到 VSCode 里操作更快)

以我目前的认识,Shell 的精髓在于组合各种程序,是比 Python 更胶水的胶水;此外,操作文件也非常地方便快捷。如果这两个痛点都中(即你需要用各种程序来处理文件?),使用 Shell 来解决问题是当仁不让的。

背景

某内网环境,不希望投入部署成本搞一套监控系统,需要搞一个土制的。同时监控的要求也不算高,能捕捉到小时级的状态异常就可以接受。

有限开放对公网的访问,得通过一个 http 代理。可以通过跳板机访问该环境。

被监控系统部署在 Kubernetes 上。跳板机可以操作其集群,当然是通过kubectl

系统确定不再更新,因此也不存在内嵌逻辑来做报警的可能。

有 hdfs,可以在系统内使用命令来读写,但在跳板机上不行。

经过在系统上进行一定的配置后,可以每小时在 check 某个条件通过后,向 hdfs 中的某个文件追加内容,具体是上个小时的时间戳和实际执行时间的时间戳。可以认为如果该小时这个 hdfs 内的文件被追加了正确的内容,那么系统端到端都是正常的。

思路

铺垫已经铺好了,其实就是需要在外部定时检测那个被写的 hdfs 文件。定时执行我们选择信任crontab,它可以帮我们定时拉起一个程序或脚本。于是问题就变成了这个脚本怎么写。

我们选择在每个小时的55分拉起脚本来做检测,如果检测不通过就往外部的通讯工具发消息。这里为什么是55分,因为系统追加 hdfs 文件内容的行为并非是每个小时准点做的,会等待某个条件达成后才开始。我们可以接受这个条件的达成时间比小时准点晚一会,当然不会晚太多;到了这个小时快要结束的时候,一定是达成了的。

往外部的通讯工具发消息,是通过一个在公网的 api 接口,因此这里会用curl并通过代理去请求。这个是在脚本开发前先要验证通过的。

而具体的检测逻辑,一方面是算出当前所处小时上一小时的时间戳。date有相关参数可以完成。另一方面是需要去获取 hdfs 文件内容的末尾,拿到系统打印的那个时间戳。在该环境下,需要kubectlhdfs命令结合来做。之后两个比较,如果不一致,就该报警了。比如这个小时系统没有把前一小时的时间戳正常写 hdfs,最后一条就会是前两个小时的时间戳,这就产生了不一致。

实施

首先先把最基本的单元搞起,比如先把 crontab 配好。为了方便观察,这边还打了日志。

1
2
$ crontab -e
55 * * * * sudo /root/watchdog.sh >> /root/watchlog 2>&1

然后是通过代理请求公网的curl命令。这个代理使用了用户名和密码来做认证。具体参数请参考 cURL man page。不过主要部分都是从 Postman 拷的就是了,只加了下 proxy 相关的参数。

1
curl -x http://proxy:1234 --proxy-user usr:pwd --location --request POST 'https://api/' --header 'Content-Type: application/json' --data-raw '{"text": "汪汪汪"}'

使用date算出当前所在小时的时间戳。有点绕 but it works,大概的意思是先拼出上一个0点,之后再换成时间戳。

1
date -d "$(date "+%Y-%m-%d %H:00:00" -d last-hour)" +%s

使用kubectl通过系统的 pod 去访问hdfs,这里需要分三步:

  • 从 K8s 集群里捞出目标系统的 pod
    • 先列出所有的 pod,但是需要筛掉额外的信息
    • kubectl get pod | grep target-system | head -n 1 | awk '{print $1}'
  • 在该容器上执行hdfs命令,并把结果拉回
    • 直接在跳板机上输出,而不是在 pod 里外拷来拷去
    • 需要用--使kubectl意识到后面的参数都要带到 pod 里面去
    • kubectl exec pod-xxxxx -- hdfs dfs -tail /user/me/checktime
  • 拉回来的结果处理一下
    • 拿最后一行的第一列
    • kubectl blabla | tail -n 1 | awk '{print $1}'
1
kubectl exec `kubectl get pod | grep target-system | head -n 1 | awk '{print $1}'` -- hdfs dfs -tail /user/me/checktime | tail -n 1 | awk '{print $1}'

最终

关键代码都在上面了,最后当然是要把逻辑和过程套上了。

话说每次写 Bash 都要查一下 if 咋写,这个也得反省一下 TvT。

冷知识(?): [ xxx ]test xxx命令的语法糖(这样就能理解为什么中括号前后得有空格了)。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

lasthour=$(date -d "$(date "+%Y-%m-%d %H:00:00" -d last-hour)" +%s)
tasktime=$(kubectl exec `kubectl get pod | grep target-system | head -n 1 | awk '{print $1}'` -- hdfs dfs -tail /user/me/checktime | tail -n 1 | awk '{print $1}')
echo $lasthour $tasktime

if [ $lasthour == $tasktime ]; then
echo "ok"
else
echo "gg"
curl -x http://proxy:1234 --proxy-user usr:pwd --location --request POST 'https://api/' --header 'Content-Type: application/json' --data-raw '{"text": "汪汪汪"}'
fi

这样就完成了一个土制的监控系统 watchdog。