当前位置: 首页 > news >正文

Kubelet 探针如何选择 IP:status.PodIP 溯源与“同 Pod 两个 IP“现象解析

背景与现象

同一个 Pod 的 readiness 和 liveness 探针日志显示连接的 IP 不一致(例如 10.10.6.10:999910.10.6.32:9999)。本文从 kubelet 源码入手,解释探针目标 IP 的来源、为何会出现两个不同 IP,并给出建议与验证方法。

在如下探针配置下:

readinessProbe:initialDelaySeconds: 5periodSeconds: 10tcpSocket:port: 9999timeoutSeconds: 1
livenessProbe:initialDelaySeconds: 60periodSeconds: 15tcpSocket:port: 9999timeoutSeconds: 1

结论摘要

  • tcpSocket.host 未显式设置时,kubelet 探针使用“当下缓存”的 status.PodIP 作为目标主机。

  • readiness 与 liveness 是两个独立的 worker,按各自的周期、初始延迟读取 kubelet 的状态缓存。如果 Pod 在两次读取之间被重建(sandbox 重建、CNI 重新分配 IP),两者可能各自命中旧/新 IP,因此日志出现两个不同 IP。

源码调用链(关键片段)

  • 探针(TCP)如何选择主机:若 TCPSocket.Host 为空,使用 status.PodIP

if p.TCPSocket != nil {port, err := extractPort(p.TCPSocket.Port, container)if err != nil {return probe.Unknown, "", err}host := p.TCPSocket.Hostif host == "" {host = status.PodIP}klog.V(4).InfoS("TCP-Probe Host", "host", host, "port", port, "timeout", timeout)return pb.tcp.Probe(host, port, timeout)
}
  • 探针在每次执行前从 status manager 读取“当下缓存”的 v1.PodStatus

status, ok := w.probeManager.statusManager.GetPodStatus(w.pod.UID)
if !ok {// Either the pod has not been created yet, or it was already deleted.klog.V(3).InfoS("No status for pod", "pod", klog.KObj(w.pod))return true
}
  • status manager 的读取接口(返回缓存的 v1.PodStatus):

func (m *manager) GetPodStatus(uid types.UID) (v1.PodStatus, bool) {m.podStatusesLock.RLock()defer m.podStatusesLock.RUnlock()status, ok := m.podStatuses[types.UID(m.podManager.TranslatePodUID(uid))]return status.status, ok
}
  • kubelet 如何生成 PodIPs/PodIP 并写入 v1.PodStatus(先排序,再取首个作为 PodIP):

podIPs = kl.sortPodIPs(podIPs)
for _, ip := range podIPs {apiPodStatus.PodIPs = append(apiPodStatus.PodIPs, v1.PodIP{IP: ip})
}
if len(apiPodStatus.PodIPs) > 0 {apiPodStatus.PodIP = apiPodStatus.PodIPs[0].IP
}
  • Pod 的 IP 列表来自 CRI 报告的 sandbox 网络状态:

func (m *kubeGenericRuntimeManager) determinePodSandboxIPs(podNamespace, podName string, podSandbox *runtimeapi.PodSandboxStatus) []string {podIPs := make([]string, 0)if podSandbox.Network == nil {klog.InfoS("Pod Sandbox status doesn't have network information, cannot report IPs", "pod", klog.KRef(podNamespace, podName))return podIPs}if len(podSandbox.Network.Ip) != 0 {if net.ParseIP(podSandbox.Network.Ip) == nil {klog.InfoS("Pod Sandbox reported an unparseable primary IP", "pod", klog.KRef(podNamespace, podName), "IP", podSandbox.Network.Ip)return nil}podIPs = append(podIPs, podSandbox.Network.Ip)}for _, podIP := range podSandbox.Network.AdditionalIps {if nil == net.ParseIP(podIP.Ip) {klog.InfoS("Pod Sandbox reported an unparseable additional IP", "pod", klog.KRef(podNamespace, podName), "IP", podIP.Ip)return nil}podIPs = append(podIPs, podIP.Ip)}return podIPs
}
  • 当 sandbox 变化时,kubelet 会覆盖当前的 podIPs

if !kubecontainer.IsHostNetworkPod(pod) {// Overwrite the podIPs passed in the pod status, since we just started the pod sandbox.podIPs = m.determinePodSandboxIPs(pod.Namespace, pod.Name, podSandboxStatus)klog.V(4).InfoS("Determined the ip for pod after sandbox changed", "IPs", podIPs, "pod", klog.KObj(pod))
}
  • 仅从“最新且 READY 的” sandbox 读取 IP:

// Only get pod IP from latest sandbox
if idx == 0 && podSandboxStatus.State == runtimeapi.PodSandboxState_SANDBOX_READY {podIPs = m.determinePodSandboxIPs(namespace, name, podSandboxStatus)
}
  • TCP 探针最终发起连接的位置:

func (pr tcpProber) Probe(host string, port int, timeout time.Duration) (probe.Result, string, error) {return DoTCPProbe(net.JoinHostPort(host, strconv.Itoa(port)), timeout)
}
  • hostNetwork 场景:若 PodIP 为空,用节点 IP 初始化 PodIP/PodIPs

s.HostIP = hostIPs[0].String()
if kubecontainer.IsHostNetworkPod(pod) && s.PodIP == "" {s.PodIP = hostIPs[0].String()s.PodIPs = []v1.PodIP{{IP: s.PodIP}}if utilfeature.DefaultFeatureGate.Enabled(features.IPv6DualStack) && len(hostIPs) == 2 {s.PodIPs = append(s.PodIPs, v1.PodIP{IP: hostIPs[1].String()})}
}

流程图

为什么会出现两个不同的 IP

  • readiness 与 liveness 的 worker 独立运行、定时时间不同;每次探测都“就地读取”缓存中的 v1.PodStatus

  • 若该 Pod 在两次读取之间 sandbox 重建(IP 变化),一个 worker 可能还读到旧 IP,另一个已经读到新 IP,于是日志显示不同地址。

  • 双栈时,sortPodIPs 会按节点 IP 家族偏好排序,导致 PodIP(主 IP)在不同条件下选择不同家族的地址,也会引起切换。

  • timeoutSeconds=1 对 TCP 探针较苛刻,网络抖动时更易出现超时和重试导致的时序差异。

配置与排查建议

  • 合理的时序参数:为 TCP 探针设置更宽松的 timeoutSecondsfailureThreshold,降低瞬时抖动影响。

  • 显式 host(可选):在明确网络拓扑的前提下设置 tcpSocket.host,避免依赖 status.PodIP 切换窗口。

  • 关注 hostNetwork 与双栈:hostNetwork 以节点 IP 为准;双栈可能改变主 IP 选择。

  • 对齐重建时间线:结合 CNI/runtime 与 kubelet 日志,确认 IP 切换是否由 sandbox 重建触发。

FAQ

  • status.PodIP 存在哪里?

    • 在 kubelet 的内存缓存(status manager)中,以 UID -> versionedPodStatus 记录,探针通过 GetPodStatus 读取。

  • 探针为什么不使用“同一时刻”的统一状态?

    • readiness/liveness 分属不同 goroutine,按各自周期读取缓存的快照,没有全局“同一时刻”的合并视图。

  • 非 hostNetwork Pod 的 PodIP 从何而来?

    • 来自 CRI 的 sandbox 网络状态(primary + additional IPs),经 kubelet 排序、选主后写入。

参考文件清单

官方源码https://github.com/kubernetes/kubernetes/tree/release-1.22

  • pkg/kubelet/prober/prober.go

  • pkg/kubelet/prober/worker.go

  • pkg/kubelet/status/status_manager.go

  • pkg/kubelet/kubelet_pods.go

  • pkg/kubelet/kuberuntime/kuberuntime_manager.go

  • pkg/kubelet/kuberuntime/kuberuntime_sandbox.go

  • pkg/probe/tcp/tcp.go

http://www.dtcms.com/a/322697.html

相关文章:

  • 回答“http协议 ,js组件化,工程化, seo优化策略 ,针对不同平台终端适配 web标注和兼容性”
  • nrm工具管理镜像源
  • 通过 Certimate 统一管理 SSL 证书 支持自动化申请、全平台部署
  • 第八章 SQL编程系列-Oracle慢SQL优化实战:从执行计划到索引设计的深度解析
  • 编程速递:2025 年巴西 Embarcadero 会议,期待您的到来
  • 金融通用智能体(Financial General Agent, FGA)的端到端解决方案
  • 视图是什么?有什么用?什么时候用?MySQL中的视图
  • Swift 实战:秒算两个数组的交集(LeetCode 349)
  • 一周学会Matplotlib3 Python 数据可视化-标注 (Annotations)
  • 力扣-74.搜索二维矩阵
  • [Oracle] MAX()和MIN()函数
  • 深入理解 Gin 框架的路由机制:从基础使用到核心原理
  • Linux系统之lua 详解
  • 版本控制的详细说明介绍(已有github账号版)
  • 记一次奇异的bug
  • 蓝牙技术概览
  • [Oracle] SUBSTR()函数
  • 轻量化阅读应用实践:21MB无广告电子书阅读器测评
  • Spring Boot 应用测试全指南:从单元测试到集成测试的实战之路
  • 密集遮挡场景识别率↑31%!陌讯轻量化部署方案在智慧零售的实战解析
  • ppt 生成视频的 ai 大模型全面解析
  • ORA-600 kcratr_nab_less_than_odr和ORA-600 4194故障处理---惜分飞
  • 书生浦语第五期-L1G4-InternLM 论文分类微调实践(XTuner 版)
  • 机器翻译中的语言学基础详解(包括包括语法、句法和语义学等)
  • HashTable, HashMap, ConcurrentHashMap
  • SpringBoot 集成 MapStruct
  • 10. 怎么实现深拷贝?
  • 大模型SSE流式输出技术
  • C++ 类模板
  • 使用langchain框架开发一个能调用工具的聊天助手Demo