1 引言
我们知道 Kubernetes(k8s)是用于自动部署、扩缩和管理容器化应用程序的一个开源系统,K8s如何知道容器的运行的状态呢?不能仅仅通过容器的启动脚本启动成功则认为容器成功,启动失败则认为失败,因为有时启动脚本会后台启动服务,后台服务是否启动正常K8s可能并不知道(因为启动脚本没有返回异常的信息),有些容器启动后需要向其它用户或者进程提供服务,那他们是如何知道容器的服务是否正常呢?
因此在容器启动后,需要对容器及服务进行健康检测,健康检测通过K8s提供的3个探针(Probe)功能来实现。K8s中有三种探针:StartupProbe(容器启动探针)、LivenessProbe探针(容器存活探针)、ReadinessProbe(服务就绪探针)。一般常用的事后两种探针,第一种探针对于首次启动需要进行比较耗时的操作的容器比较合适。
2 探针的自述
2.1 StartupProbe 容器启动探针
我是一种用来检测容器内的应用是否已经成功启动并完成初始化任务的探针,我是从 Kubernetes 1.16 版本开始引入的新特性,我的诞生主要是用于解决慢启动应用或复杂启动流程中 LivenessProbe 和 ReadinessProbe 探针可能面临的挑战。例如有些应用在启动时会进行初始化的操作,如访问数据库初始化库表及数据、访问网络其它服务等,这个时间非常不稳定,也许很快,有时有很慢,如果使用LivenessProbe探针,将 timeoutSeconds 设置的过小会导致启动初始化失败,如果设置过大服务中间的可用性就会降低(期间导致的服务不可用可能探测不到),所以我属于首次启动执行时会派上用场的,如果我返回了Success就认为容器启动成功了,剩下的工作就交给勤劳的LivenessProbe探针了,我就下班休息了。
2.2 LivenessProbe 容器存活探针
我主要用于判断容器是否处于存活(Running状态)状态,当检测到容器处于不健康状态,则 kubelet 大哥将会 kill 掉该容器,之后会根据容器设置的重启策略执行相应的处理。如果容器没有请我出场,kubelet大哥就会认为该容器启动成功后(StartupProbe探针告诉他的)我一直给的的应答是Success。我会在容器的生命周期内一直勤劳的坚守在岗位上。当时我并不负责判断其中的服务运行是否正常,这个比较专业的工作是下一位跟我一样勤劳的同事所做的。
2.3 ReadinessProbe 服务就绪探针
我会比较在意一些细节,我主要用来判断容器的服务是否可用(Ready状态),如果Pod处于Ready状态才可以接受到请求。对于设置了Service(svc)的Pod,他们与Pod的关联关系也是基于Pod是否Ready进行设置的。当在运行的过程中Ready变为了False,K8s会自动将其从Service的后端Endpoint列表中隔离出去,如果后续Pod状态恢复到了Ready,又会重启添加回Service的后端Endpoint列表。这样就能保证客户端访问Service是不会被转发到Pod服务不可用的连接上。我会默默在 Pod的生命周期中定期进行查看服务下服务的状态。
3 探针的定义
在 K8s的 k8s.io/api/core/v1/types.go 中可以在这里看到探针的源码(v0.26.0),已省略了非关注的部分代码。
3.1 Container
// 希望在pod中运行的单个应用的容器
type Container struct {
...
// 容器存活的定期执行的探针。
// 如果探针返回失败容器将会重新启动。
// Cannot be updated.
// 详见: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,10,opt,name=livenessProbe"`
// 容器服务是否就绪的定期执行的探针。
// 如果探针返回失败,容器将会从服务的Endpoint中移除。
// Cannot be updated.
// 详见: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
// StartupProbe表名Pod是否已经初始化成功。
// 如果指定了,则在该探针成功完成之前不会执行其他探测。
// 如果探针返回失败了,就像livenessProbe探针失败一样,将会重启Pod。
// 这可以用于在Pod生命周期开始时提供不同的探测参数,此时加载数据或预热缓存可能需要较稳态运行时更长的时间。
// This cannot be updated.
// 详见: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
StartupProbe *Probe `json:"startupProbe,omitempty" protobuf:"bytes,22,opt,name=startupProbe"`
...
}
3.2 Probe
从上面可以看到 LivenessProbe
、ReadinessProbe
、StartupProbe
的类型都是 *Probe
,Probe类型的定义如下:
// Probe 是描述要对容器执行的健康检查,以确定它是存活的还是可准备接收流量的。
type Probe struct {
// 确定容器的运行状况而采取的操作
ProbeHandler `json:",inline" protobuf:"bytes,1,opt,name=handler"`
// 在启动存活探针之前,容器启动延迟的秒数。
// 详见: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty" protobuf:"varint,2,opt,name=initialDelaySeconds"`
// 探针超时的秒数。
// 默认为1秒,最小值为1
// 详见: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes
// +optional
TimeoutSeconds int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,3,opt,name=timeoutSeconds"`
// 执行探针的频率,以秒为单位。
// 默认为10秒,最小值为1。
// +optional
PeriodSeconds int32 `json:"periodSeconds,omitempty" protobuf:"varint,4,opt,name=periodSeconds"`
// 探测失败后,被认为成功的最小连续成功的次数。
// 默认为1。对于LivenessProbe和StartupProbe必须为1。最小值为1。
// +optional
SuccessThreshold int32 `json:"successThreshold,omitempty" protobuf:"varint,5,opt,name=successThreshold"`
// 探测成功后,被认为失败的最小连续失败的次数。
// 默认为3。最小值为1。
// +optional
FailureThreshold int32 `json:"failureThreshold,omitempty" protobuf:"varint,6,opt,name=failureThreshold"`
// 可选的持续时间,以秒为单位,当pod需要在探测失败时优雅地终止时。
// 宽限期是指在pod中运行的进程收到终止信号后的持续时间(以秒为单位),以及使用kill信号强制停止进程的时间。
// 将此值设置为比进程的预期清理时间更长。
// 如果该值为nil,将使用pod的terminationGracePeriodSeconds。否则,该值将覆盖pod规范提供的值。
// 取值必须为非负整数。值0表示通过kill信号立即停止(没有机会关闭)。
// 这是一个测试字段,需要启用ProbeTerminationGracePeriod特性开关。
// 最小值为1。如果未设置,则使用spec.terminationGracePeriodSeconds。
// +optional
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,7,opt,name=terminationGracePeriodSeconds"`
}
3.3 ProbeHandler
在探针的结构体中的定义中,我们看到有 ProbeHandler 的定义被注入进来,他确定了确定容器状态时执行的操作,下面是 ProbeHandler 结构体的定义:
// ProbeHandler定义了应该在探测中执行的特定操作。
// 使用探针时必须指定,并且只能指定其中一个字段。
type ProbeHandler struct {
// Exec指定了要执行的操作命令。
// +optional
Exec *ExecAction `json:"exec,omitempty" protobuf:"bytes,1,opt,name=exec"`
// HTTPGet指定了要执行的HTTP请求。
// +optional
HTTPGet *HTTPGetAction `json:"httpGet,omitempty" protobuf:"bytes,2,opt,name=httpGet"`
// TCPSocket指定了涉及到TCP相关的端口的操作。
// +optional
TCPSocket *TCPSocketAction `json:"tcpSocket,omitempty" protobuf:"bytes,3,opt,name=tcpSocket"`
// GRPC指定了设计到GRPC端口的操作。
// +featureGate=GRPCContainerProbe
// +optional
GRPC *GRPCAction `json:"grpc,omitempty" protobuf:"bytes,4,opt,name=grpc"`
}
3.3.1 Exec
Exec的类型为ExecAction,查看ExecAction结构体的定义可以看到其中只有一个字段,其定义了要执行的命令的数组。
// ExecAction描述了"在容器中运行"的操作
type ExecAction struct {
// Command是要在容器内执行的命令行,该命令的工作目录是容器中文件系统中的根目录('/')。
// 该命令只是简单地执行,而不是在shell中运行的,所以传统的shell指令('|'等)不起作用。
// 要使用shell,需要显式调用该shell。
// 退出状态为0则被视为存活/健康,非0被视为不健康。
// +optional
Command []string `json:"command,omitempty" protobuf:"bytes,1,rep,name=command"`
}
3.3.2 HTTPGet
HTTPGet的类型为 HTTPGetAction,查看 HTTPGetAction 结构体定义如下。其中字段Scheme的类型是一个string类型的别名URIScheme,同时在K8s中为一个枚举,有两种值:URISchemeHTTP、URISchemeHTTPS。
// HTTPGetAction描述了一个基于HTTPGet请求的操作。
type HTTPGetAction struct {
// HTTP服务访问的Path。
// +optional
Path string `json:"path,omitempty" protobuf:"bytes,1,opt,name=path"`
// 要访问的容器的端口名或端口号。
// 端口好必须在 1 到 65535。
// 端口名必须是一个 IANA_SVC_NAME。
Port intstr.IntOrString `json:"port" protobuf:"bytes,2,opt,name=port"`
// 要连接的主机名,默认为pod IP。
// 你可能需要在httpHeaders中设置"Host"。
// +optional
Host string `json:"host,omitempty" protobuf:"bytes,3,opt,name=host"`
// 连接到host时使用的Scheme。
// 默认为 HTTP。
// +optional
Scheme URIScheme `json:"scheme,omitempty" protobuf:"bytes,4,opt,name=scheme,casttype=URIScheme"`
// 要在请求中设置的自定义的header。HTTP允许重复的header。
// +optional
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty" protobuf:"bytes,5,rep,name=httpHeaders"`
}
// URIScheme标识用于连接到host的Get操作的Scheme
// +enum
type URIScheme string
const (
// URISchemeHTTP意味着scheme将会使用http://
URISchemeHTTP URIScheme = "HTTP"
// URISchemeHTTPS意味着scheme将会使用https://
URISchemeHTTPS URIScheme = "HTTPS"
)
// HTTPHeader描述了在HTTP探针中使用的自定义header
type HTTPHeader struct {
// header字段名
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// header字段的值
Value string `json:"value" protobuf:"bytes,2,opt,name=value"`
}
Port的类型为intstr.IntOrString
,其定义可以在 k8s.io/apimachinery/pkg/util/intstr/intstr.go 中看到:
// IntOrString是一个可以保存int32或string类型的类型。
// 当在JSON或YAML数据marshalling和unmarshalling中使用时,它产生或使用内部类型。
// 例如,这允许你拥有一个可以接受名称或数字的JSON字段。
// TODO: 重命名为Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:openapi-gen=true
type IntOrString struct {
Type Type `protobuf:"varint,1,opt,name=type,casttype=Type"`
IntVal int32 `protobuf:"varint,2,opt,name=intVal"`
StrVal string `protobuf:"bytes,3,opt,name=strVal"`
}
3.3.3 TCPSocket
TCPSocket的类型为 TCPSocketAction,查看 TCPSocketAction 结构体定义如下。
// TCPSocketAction描述了一个基于开启socket的操作
type TCPSocketAction struct {
// 要访问的容器的端口名或端口号。
// 端口好必须在 1 到 65535。
// 端口名必须是一个 IANA_SVC_NAME。
Port intstr.IntOrString `json:"port" protobuf:"bytes,1,opt,name=port"`
// 可选的: 要连接的主机名,默认为pod IP。
// +optional
Host string `json:"host,omitempty" protobuf:"bytes,2,opt,name=host"`
}
3.3.4 GRPC
GRPC的类型为 GRPCAction,查看 GRPCAction 结构体定义如下。
type GRPCAction struct {
// gRPC服务端口号。取值范围为1 到 65535。
Port int32 `json:"port" protobuf:"bytes,1,opt,name=port"`
// Service是要放置在gRPC HealthCheckRequest中的服务的名称
// (见 https://github.com/grpc/grpc/blob/master/doc/health-checking.md)。
//
// 如果不指定,则默认行为由gRPC定义。
// +optional
// +default=""
Service *string `json:"service" protobuf:"bytes,2,opt,name=service"`
}
4 小示例
ProbeHandler为Exec。在这个示例中探针会执行 /home/ranger/health.sh 脚本,根据传入的第一个参数执行不同的检测方式。容器在启动时先执行 startupProbe 探针,如果该探针返回成功后表名容器已经启动成功,之后在容器的生命周期内定期执行 livenessProbe 和 readinessProbe 探针。startupProbe探针中为每5秒执行一次(periodSeconds), 每次执行脚本的超时时间为 30秒(timeoutSeconds),脚本执行健康检测时如果有一次返回成功状态就为成功(successThreshold),如果单次执行失败次数到达36次后表级位失败(failureThreshold),也就是容器启动探针设置了一个3分钟的启动时间(periodSeconds*failureThreshold
),如果3分钟内有一次脚本检测返回了成功,就认为容器启动成功了。
livenessProbe:
exec:
command:
- /bin/bash
- -c
- /home/ranger/health.sh liveness
failureThreshold: 5
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /home/ranger/health.sh readiness
failureThreshold: 5
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
startupProbe:
exec:
command:
- /bin/bash
- -c
- /home/ranger/health.sh startup
failureThreshold: 36
initialDelaySeconds: 60
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 30
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。