k8s各个组件功能

● apiserver提供了资源操作的唯一入口,并提供认证、授权、访问控制、API注册和发现等机制;
● controller manager负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;
● scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上;
● kubelet负责维护容器的生命周期,pod健康检查等,同时也负责Volume(CVI)和网络(CNI)的管理;
● Container runtime负责镜像管理以及Pod和容器的真正运行(CRI);
● kube-proxy负责为Service提供cluster内部的服务发现和负载均衡。

liveness和readiness的探针工作方式和源码解析

liveness和readiness作为k8s的探针,可以对应用进行健康探测。 二者支持的探测方式相同。主要的探测方式支持http探测,执行命令探测,以及tcp探测。 探测均是由部署在node侧的kubelet执行。

执行命令探针

func (pb *prober) runProbe(p *v1.Probe, pod *v1.Pod, status v1.PodStatus, container v1.Container, containerID kubecontainer.ContainerID) (probe.Result, string, error) {
.....        
        command := kubecontainer.ExpandContainerCommandOnlyStatic(p.Exec.Command, container.Env)
		return pb.exec.Probe(pb.newExecInContainer(container, containerID, command, timeout))
......
        
func (pb *prober) newExecInContainer(container v1.Container, containerID kubecontainer.ContainerID, cmd []string, timeout time.Duration) exec.Cmd {
	return execInContainer{func() ([]byte, error) {
		return pb.runner.RunInContainer(containerID, cmd, timeout)
	}}
}
        
......
func (m *kubeGenericRuntimeManager) RunInContainer(id kubecontainer.ContainerID, cmd []string, timeout time.Duration) ([]byte, error) {
	stdout, stderr, err := m.runtimeService.ExecSync(id.ID, cmd, 0)
	return append(stdout, stderr...), err
}

由kubelet,通过CRI接口的ExecSync接口,在对应容器内执行拼装好的cmd命令。获取返回值。

func (pr execProber) Probe(e exec.Cmd) (probe.Result, string, error) {
	data, err := e.CombinedOutput()
	glog.V(4).Infof("Exec probe response: %q", string(data))
	if err != nil {
		exit, ok := err.(exec.ExitError)
		if ok {
			if exit.ExitStatus() == 0 {
				return probe.Success, string(data), nil
			} else {
				return probe.Failure, string(data), nil
			}
		}
		return probe.Unknown, "", err
	}
	return probe.Success, string(data), nil
}

kubelet是根据执行命令的退出码来决定是否探测成功。当执行命令的退出码为0时,认为执行成功,否则为执行失败。如果执行超时,则状态为Unknown。

http探测

func DoHTTPProbe(url *url.URL, headers http.Header, client HTTPGetInterface) (probe.Result, string, error) {
	req, err := http.NewRequest("GET", url.String(), nil)
	......
    if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest {
		glog.V(4).Infof("Probe succeeded for %s, Response: %v", url.String(), *res)
		return probe.Success, body, nil
	}
	......

http探测是通过kubelet请求容器的指定url,并根据response来进行判断。 当返回的状态码在200到400(不含400)之间时,也就是状态码为2xx和3xx,认为探测成功。否则认为失败。

tcp探测

func DoTCPProbe(addr string, timeout time.Duration) (probe.Result, string, error) {
	conn, err := net.DialTimeout("tcp", addr, timeout)
	if err != nil {
		// Convert errors to failures to handle timeouts.
		return probe.Failure, err.Error(), nil
	}
	err = conn.Close()
	if err != nil {
		glog.Errorf("Unexpected error closing TCP probe socket: %v (%#v)", err, err)
	}
	return probe.Success, "", nil
}

tcp探测是通过探测指定的端口。如果可以连接,则认为探测成功,否则认为失败。

探测失败的可能原因

  • 执行命令探测失败的原因主要可能是容器未成功启动,或者执行命令失败。当然也可能docker或者docker-shim存在故障。

  • 由于http和tcp都是从kubelet自node节点上发起的,向容器的ip进行探测。 所以探测失败的原因除了应用容器的问题外,还可能是从node到容器ip的网络不通。

liveness与readiness的原理区别

liveness主要用来确定何时重启容器。liveness探测的结果会存储在livenessManager中。 kubelet在syncPod时,发现该容器的liveness探针检测失败时,会将其加入待启动的容器列表中,在之后的操作中会重新创建该容器。

readiness主要来确定容器是否已经就绪。只有当Pod中的容器都处于就绪状态,也就是pod的condition里的Ready为true时,kubelet才会认定该Pod处于就绪状态。而pod是否处于就绪状态的作用是控制哪些Pod应该作为service的后端。如果Pod处于非就绪状态,那么它们将会被从service的endpoint中移除。

func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) {
	    ......
    	containerStatus.Ready = ready
        ......
    	readyCondition := GeneratePodReadyCondition(&pod.Spec, status.ContainerStatuses, status.Phase)
    	......
    	m.updateStatusInternal(pod, status, false)
}

readiness检查结果会通过SetContainerReadiness函数,设置到pod的status中,从而更新pod的ready condition。

liveness和readiness除了最终的作用不同,另外一个很大的区别是它们的初始值不同。

    switch probeType {
	case readiness:
		w.spec = container.ReadinessProbe
		w.resultsManager = m.readinessManager
		w.initialValue = results.Failure
	case liveness:
		w.spec = container.LivenessProbe
		w.resultsManager = m.livenessManager
		w.initialValue = results.Success
	}

liveness的初始值为成功。这样防止在应用还没有成功启动前,就被误杀。如果在规定时间内还未成功启动,才将其设置为失败,从而触发容器重建。

而readiness的初始值为失败。这样防止应用还没有成功启动前就向应用进行流量的导入。如果在规定时间内启动成功,才将其设置为成功,从而将流量向应用导入。

liveness与readiness二者作用不能相互替代。

例如只配置了liveness,那么在容器启动,应用还没有成功就绪之前,这个时候pod是ready的(因为容器成功启动了)。那么流量就会被引入到容器的应用中,可能会导致请求失败。虽然在liveness检查失败后,重启容器,此时pod的ready的condition会变为false。但是前面会有一些流量因为错误状态导入。

当然只配置了readiness是无法触发容器重启的。

因为二者的作用不同,在实际使用中,可以根据实际的需求将二者进行配合使用。

liveness 和 readiness的正确使用方法

liveness 探针和readiness 探针应该分开

  • liveness
  1. liveness探针应该主要设计为判断自身服务是否运行正常,而不用关系上下游和中间件,如果自身有问题才重启。
  2. readiness 探针应该设计成用来判断相关依赖是否正常,如果依赖关系出问题,则将自身设置成不可用,而不用重启.
  • readiness
1.readiness探针会持续到整个容器生命周期,即容器运行中会一直检测
2.readiness探针失败会将pod状态改成notready,此时pod不再接收流量
3.readiness探针应该设计为用于探测pod依赖的相关服务,中间是否有问题,如果有问题则将自身暂时标记为不可用
4.就绪探针超时时间应该设置为大于该依赖关系的最大响应时间
5.默认failureThreshold计数为三,因此可能需要调整重试次数。

转载自