kubernetes 故障排查之contrack

在kube-proxy日志里面有很多报错

E0813 10:47:18.983433  454071 proxier.go:722] conntrack return with error: error looking for path of conntrack: exec: "conntrack": executable file not found in $PATH
E0813 10:47:18.983492  454071 proxier.go:722] conntrack return with error: error looking for path of conntrack: exec: "conntrack": executable file not found in $PATH
E0813 10:47:18.983513  454071 proxier.go:722] conntrack return with error: error looking for path of conntrack: exec: "conntrack": executable file not found in $PATH

很容易理解,这个就是conntrack没有安装,那么这个conntrack是干嘛的很多人可能不太理解,它是一个用户态的命令,用于控制内核中ip_conntrack模块的,该模块是用于处理链路追踪的工具。就是iptables和netfilter的关系。
简单介绍一下ip_conntrack模块,数据包(a -> b)经过网关,发生了SNAT,地址信息成为了(m -> b),虽然发生了nat,(a -> b)和(m -> b)应该是属于同一个数据流conntrack的,ip_conntrack需要作记录,以便将两个流绑定在一起,数据从a到b的方向在网关处成了由m到b的方向,属于一个方向,都是源到目的,发生了SNAT后,数据就可以出去了,既然数据离开了网关,我们也就不必关心它了,我们关心的是从b发出的回应a数据到达网关后如何将之绑定到流conntrack,数据回来后由于发生过snat流标示显然是(b -> m),于是ip_conntrack需要将(b-m)也绑定到conntrack,这样才能将数据返回。由于ip_conntrack帮助,可以将两个连接捆绑到一起,通过ip_conntrack来帮助netfilter进行流量转发。
回到kubernetes的问题上面,proxy在删除UDP的service时候也需要清除这些ip_conntrack连接。否则还会还会把流量导入的废弃的pod上面。来看源代码pkg/util/conntrack/conntrack.go


func ClearEntriesForIP(execer exec.Interface, ip string, protocol v1.Protocol) error {
    parameters := parametersWithFamily(utilnet.IsIPv6String(ip), "-D", "--orig-dst", ip, "-p", protoStr(protocol))
    err := Exec(execer, parameters...)
    if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
        // 删除UDP的服务连接
        return fmt.Errorf("error deleting connection tracking state for UDP service IP: %s, error: %v", ip, err)
    }
    return nil
}

//删除指定端口的连接,主要是为了避免冲突
func ClearEntriesForPort(execer exec.Interface, port int, isIPv6 bool, protocol v1.Protocol) error {
    if port <= 0 {
        return fmt.Errorf("Wrong port number. The port number must be greater than zero")
    }
    parameters := parametersWithFamily(isIPv6, "-D", "-p", protoStr(protocol), "--dport", strconv.Itoa(port))
    err := Exec(execer, parameters...)
    if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
        return fmt.Errorf("error deleting conntrack entries for UDP port: %d, error: %v", port, err)
    }
    return nil
}


func ClearEntriesForNAT(execer exec.Interface, origin, dest string, protocol v1.Protocol) error {
    parameters := parametersWithFamily(utilnet.IsIPv6String(origin), "-D", "--orig-dst", origin, "--dst-nat", dest,
        "-p", protoStr(protocol))
    err := Exec(execer, parameters...)
    if err != nil && !strings.Contains(err.Error(), NoConnectionToDelete) {
        // 删除UDP的连接
        return fmt.Errorf("error deleting conntrack entries for UDP peer {%s, %s}, error: %v", origin, dest, err)
    }
    return nil
}

上面的三个方法都是通过直接调用conntrack完成的。分别介绍一下,第一个方法ClearEntriesForIP
是在pkg/proxy/iptables/proxier.go
同步的代码中使用,删除过期的udp service IP建立的conntrack

for _, svcIP := range staleServices.UnsortedList() {
        if err := conntrack.ClearEntriesForIP(proxier.exec, svcIP, v1.ProtocolUDP); err != nil {
            glog.Errorf("Failed to delete stale service IP %s connections, error: %v", svcIP, err)
        }
    }

而 ClearEntriesForNAT是紧跟着上面的方法执行,回收端到端(service ip 和endpoint ip)的conntrack

func (proxier *Proxier) deleteEndpointConnections(connectionMap []proxy.ServiceEndpoint) {
    for _, epSvcPair := range connectionMap {
        if svcInfo, ok := proxier.serviceMap[epSvcPair.ServicePortName]; ok && svcInfo.GetProtocol() == api.ProtocolUDP {
            endpointIP := utilproxy.IPPart(epSvcPair.Endpoint)
            err := conntrack.ClearEntriesForNAT(proxier.exec, svcInfo.ClusterIPString(), endpointIP, v1.ProtocolUDP)
            if err != nil {
                glog.Errorf("Failed to delete %s endpoint connections, error: %v", epSvcPair.ServicePortName.String(), err)
            }
        }
    }
}

最后一个方法ClearEntriesForPort很有意思,他是在创建方法里面的,读者可能会好奇,创建的时候为啥要删除conntrack呢,因为这是docker本身的机制存在的问题,如果容器重启导致IP变化,iptables规则虽然会更新但conntrack仍然还在,又由于不是第一个数据包,所以如果是UDP会延续之前的转发。详见https://github.com/moby/moby/issues/8795

所以在pkg/proxy/iptables/proxier.go的Add方面会清楚之前建立的conntrack

    if hm.execer != nil && hm.conntrackFound {
        glog.Infof("Starting to delete udp conntrack entries: %v, isIPv6 - %v", conntrackPortsToRemove, isIpv6)
        for _, port := range conntrackPortsToRemove {
            err = conntrack.ClearEntriesForPort(hm.execer, port, isIpv6, v1.ProtocolUDP)
            if err != nil {
                glog.Errorf("Failed to clear udp conntrack for port %d, error: %v", port, err)
            }
        }
    }

修复需要安装conntrack-tools

 yum install conntrack-tools

如果是Ubuntu则使用apt-get 同理。

发布了215 篇原创文章 · 获赞 103 · 访问量 461万+

猜你喜欢

转载自blog.csdn.net/u010278923/article/details/81735970