krun

krun 查看完整档案

中山编辑广东理工职业学院  |  软件技术 编辑广州九尾信息科技有限公司  |  Java 开发 编辑 krun.dev 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

krun 发布了文章 · 1月12日

[工作随笔] 配置 cron 检查 K8s Pod 存活状态以推送钉钉消息

背景

前段时间接手了一份维护老系统的任务。该系统使用了早期的 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?

查看原文

赞 0 收藏 0 评论 0

krun 回答了问题 · 1月2日

订单状态状态机怎么设计? 逆向流程中断后怎么回到正向流程?

为啥非得只能有一个 "待退款" 状态.

拆成俩: "未发货待退款"、"未收货待退款" .

关注 2 回答 1

krun 回答了问题 · 2020-12-17

Nginx location 中如何基于upstream应用中的请求头是否存在来做不同处理

if ($http_Access_Control_Allow_Orign = '')

关注 2 回答 1

krun 回答了问题 · 2020-12-17

解决云服务器挂载的数据盘和docker的关系

你部署 nextcloud 的时候没有搞卷映射吗?映射到第二个盘就好了.

关注 3 回答 3

krun 回答了问题 · 2020-12-16

基于springboot开发的web程序,有什么好的本地文件存储解决方案?

推荐 MinIO | High Performance, Kubernetes Native Object Storage.

部署容易,对分布式、多租户的扩展也很容易.

关注 3 回答 2

krun 回答了问题 · 2020-12-13

spring boot有没有根据model或者叫entity或者叫repository创建表的库啊?

Spring 全家桶里可以用 Spring Data JPA

JPA 是 Java EE 的规范,上面那个库的针对这个规范的默认实现是 Hibernate ORM.

关注 3 回答 2

krun 回答了问题 · 2020-10-21

jwt报错Token used before issued

用官网的在线解析把 token 解出来看看什么时间戳签发的.

大概率是时区问题.

关注 3 回答 2

krun 回答了问题 · 2020-09-30

java try with resource 难受

如果 getHandler 方法抛异常,你哪来的 api.

关注 4 回答 3

krun 回答了问题 · 2020-09-08

解决Typora 在本地写的网址超链接,在本地能打开吗?

这个跟它类不类似浏览器没关系,即便是浏览器也可以很轻松得禁用 a 标签的自动跳转或是用别的元素模拟出 a 的效果.

不让一点链接就跳转是因为作为编辑器有这样的场景:你发现网址打错了或者想换一个. 如果你一点网址就弹浏览器了那你怎么改,每次都要切到源码模式再改吗.

所以你按着 ctrl 键再去点链接,就会弹浏览器了,这是很多编辑器的通用做法.

关注 2 回答 1

krun 回答了问题 · 2020-09-08

解决Java Spring MVC 中的 `@RequestMapping` 是什么技术(原理 or 语法)?

这个是注解,但 Java 中的注解本身并不提供任何功能,它只是作为一种标记,需要额外的代码去处理它.

无论是编译期还是运行期,通过反射能拿到注解的代理实例,或者说注解的本质是一个接口(JDK 提供的代理 Proxy 只能代理接口). 你在注解里声明的所有属性都会作为接口上的方法由这个代理实例去托管. 这些方法去代理实例托管的一个 Map 里去这个代理实例里取值(同一个注解在不同代码位置上使用会产生不同的代理实例,每个代理实例维护一个 Map 用以保存各自的值).

关于运行时动态修改注解的值,可以参考我以前写的 文章,有的情况下会有限制,而且大多数时候处理注解的代码不一定每次都会从注解的代理实例中获取最新的值(即有缓存),所以也从侧面验证:注解本身并不提供特别的功能.

如果是编译期的注解,想处理就需要编写注解处理器(Annotation Processor),这个可以做到代码生成(lombok 等等)或一些其他基于源码的增强(也包括 AST 级别的代码检测);

如果是运行期的注解,想处理就有很多方式,比如最简单得,直接通过反射访问一个对象实例身上的注解;或是通过切面将注解视为一种标记点.

注解如果单纯视为标记的话其实跟接口从语义上来说的确没有太大区别. 但它的主要语义是『元信息』.

最后,@RequestMapping 的原理,并不是所谓的切面实现. Spring 启动过程中会注入所有的 @Component及相关注解,这是通过包扫描得到的,这些类会被 Spring 进行托管,在处理的过程中会有个初始化处理器、或类身上通过接口获得的 Bean 生命周期方法、或工厂用来对刚建立的对象实例进行增强(比如用代理包起来),其中 @Controller@RestController 这两个注解标记的类会在 RequestMappingHandlerMapping 里被搜集出来,进一步提取里面的公开非静态方法,如果方法上有 @RequestMapping 则称之为 HandlerMethod,然后有其他的步骤用来计算方法与路由的映射关系,最后汇总.

关注 4 回答 4

认证与成就

  • 获得 438 次点赞
  • 获得 17 枚徽章 获得 1 枚金徽章, 获得 5 枚银徽章, 获得 11 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • api-mock-handler

    基于 axios 和 mockjs 的 API 控制工具.

  • domain-mapping

    用于简化 handler method 与路由之间的绑定 emmm sf 说地址太长了所以放了个短链。 github地址:https://github.com/krunZhang/spring-extends/tree/master/domain-mapping

  • js-interface

    在 JS 中提供一些接口的能力

  • Juice

    一个简单的、练手性质的数据库工具

注册于 2017-09-17
个人主页被 5.7k 人浏览