liveness與readiness的探針工作方式源碼解析
liveness和readiness作為k8s的探針,可以對應用進行健康探測。
二者支持的探測方式相同。主要的探測方式支持http探測,執行命令探測,以及tcp探測。
探測均是由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與readiness有什么區別?首先,二者能夠起到的作用不同。
func (m *kubeGenericRuntimeManager) computePodContainerChanges(pod *v1.Pod, podStatus *kubecontainer.PodStatus) podContainerSpecChanges {
......
liveness, found := m.livenessManager.Get(containerStatus.ID)
if !found || liveness == proberesults.Success {
changes.ContainersToKeep[containerStatus.ID] = index
continue
}
......
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是無法觸發容器重啟的。
因為二者的作用不同,在實際使用中,可以根據實際的需求將二者進行配合使用。