k8s timeoutSeconds无效且没有按照periodSeconds的间隔时间来执行健康检查

发布时间 2023-07-30 23:26:16作者: 王景迁

健康检查日志没有严格按照periodSeconds间隔时间来打印。

核心代码如下: 
pkg/kubelet/prober/worker.go

pkg/kubelet/prober/prober.go
runProbe方法(kubelet健康检查有3种方式)

httpGet
发送HTTP 请求,返回码介于 200~400 之间(前闭后开)时检查成功。
exec
容器中执行命令,当命令执行成功(返回码为 0)时检查成功。
tcpSocket
当 TCP 连接容器中的指定 TCP 端口成功时,检查成功。

doProbe函数的调用过程是串行的,一旦执行了健康检查,必须等到结果才能执行下一次健康检查。如果健康检查执行时间比较长,那么上一次健康检查执行完,很快就会触发下一次健康检查。

模拟的无状态应用yaml文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        args:
        - /bin/sh
        - -c
        - touch /root/wjq.log; touch /root/wjq.sh; chmod -R 777 /root/*; echo "echo 'begin' >> /root/wjq.log; date '+%Y-%m-%d %H:%M:%S' >> /root/wjq.log; sleep 300; date '+%Y-%m-%d %H:%M:%S' >> /root/wjq.log; echo 'end' >> /root/wjq.log" >> /root/wjq.sh; sleep 3000
        livenessProbe:
          exec:
            command:
            - bash
            - /root/wjq.sh
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 2
          failureThreshold: 3
        ports:
        - containerPort: 80
        securityContext:
          runAsUser: 0

  k8s v1.19.0版本日志输出
情况1:健康检查脚本耗时300s
脚本间隔122s执行,只考虑开始执行时间,忽略end

打印开始执行时间

情况2:健康检查脚本耗时30s
sleep 300改成sleep 30,结果和k8s 1.15.0版本保持一致

k8s v1.15.0版本日志输出(脚本执行时间达到30s,包含了多个5s的periodSeconds,紧挨着执行)

begin 
2023-07-30 07:51:46 
2023-07-30 07:52:16 
end 
begin 
2023-07-30 07:52:16 
2023-07-30 07:52:46 
end 
begin 
2023-07-30 07:52:47 
2023-07-30 07:53:17 
end 
begin 
2023-07-30 07:53:17 
2023-07-30 07:53:47 
end

逻辑改变
pkg/probe/exec/exec.go
Probe方法
直接Run一直等命令结束->先start再wait超时后强制命令结束

pkg/kubelet/cri/remote/remote_runtime.go
ExecSync方法
超时时间是2分钟+自定义的超时时间即122s,使得看上去不是挨着执行

改成不加2分钟

脚本耗时30s,按照配置2秒就中断

类似于CommandContext方法

package main

import (
	"context"
	"fmt"
	"os/exec"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	cmd := exec.CommandContext(ctx, "/bin/sleep", "10")
	out, err := cmd.CombinedOutput()
	fmt.Printf("ctx.Err : [%v]\n", ctx.Err())
	fmt.Printf("error   : [%v]\n", err)
	fmt.Printf("out     : [%s]\n", string(out))
}