Change:
Is this a BUG REPORT or FEATURE REQUEST?: /kind bug What happened: kube-proxy was still binding to ::{port} for a node port service after it was started with the --nodeport-addresses argument What you expected to happen: kube-proxy to only bind to interfaces in the CIDR ranges specified by --nodeport-addresses. How to reproduce it (as minimally and precisely as possible): Start kube-proxy with --nodeport-addresses "127.0.0.1,::1" Create a service of type NodePort Observe kube-proxy bind to all listening interfaces Anything else we need to know?: In the proposal for the --nodeport-addresses feature, it specifies being able to have other services answering or listening on a given port. Since kube-proxy binds to all interfaces, this is still not possible so it appears one of the initial intents of the proposal was net met with the existing solution. [] Doesn't set an interface. Given that nodeport-addresses is specified as CIDR ranges extra work must be done to determine which IP's on a host exist that could be bound to. Alternatively, a flag such as --nodeport-addresses-bind could allow an operator to set an explicit value. To test functionality I was able to hardcode an address here and observe the desired behavior could be achieved. Environment: Kubernetes version:Cloud provider or hardware configuration: AWS OS (e.g. from /etc/os-release): Kernel (e.g. ): Install tools: Custom fabricated Others: As expected, this behaves the same way regardless of iptables or ipvs being used as the forwarding mechanism.
/sig network
/assign
Nice catch! I will fix it ASAP~
Please check

Discussion:
if svcInfo.NodePort != 0 { // Hold the local port open so no other process can open it // (because the socket might open but it would never work). <del> lp := utilproxy.LocalPort{ Description: "nodePort for " + svcNameString, IP:          "", Port:        svcInfo.NodePort, Protocol:    protocol, </del> <ins> addresses, err := utilproxy.GetNodeAddresses(proxier.nodePortAddresses, proxier.networkInterfacer) if err != nil { glog.Errorf("Failed to get node ip address matching nodeport cidr: %v", err) continue </ins> } <del> if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] } else { socket, err := proxier.portMapper.OpenLocalPort(&lp) if err != nil { glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) continue </del> <ins> lps := make([]utilproxy.LocalPort, 0) for address := range addresses { lp := utilproxy.LocalPort{ Description: "nodePort for " + svcNameString, IP:          address, Port:        svcInfo.NodePort, Protocol:    protocol, } if utilproxy.IsZeroCIDR(address) { // Empty IP address means all lp.IP = "" lps = append(lps, lp) // If we encounter a zero CIDR, then there is no point in processing the rest of the addresses. break </ins> } <del> if lp.Protocol == "udp" { // TODO: We might have multiple services using the same port, and this will clear conntrack for all of them. // This is very low impact. The NodePort range is intentionally obscure, and unlikely to actually collide with real Services. // This only affects UDP connections, which are not common. // See issue: https://github.com/kubernetes/kubernetes/issues/49881 err := conntrack.ClearEntriesForPort(proxier.exec, lp.Port, isIPv6, v1.ProtocolUDP) </del> <ins> lps = append(lps, lp) } // For ports on node IPs, open the actual port and hold it. for _, lp := range lps { if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] } else { socket, err := proxier.portMapper.OpenLocalPort(&lp) </ins> if err != nil { <del> glog.Errorf("Failed to clear udp conntrack for port %d, error: %v", lp.Port, err) </del> <ins> glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) continue } if lp.Protocol == "udp" { // TODO: We might have multiple services using the same port, and this will clear conntrack for all of them. // This is very low impact. The NodePort range is intentionally obscure, and unlikely to actually collide with real Services. // This only affects UDP connections, which are not common. // See issue: https://github.com/kubernetes/kubernetes/issues/49881 err := conntrack.ClearEntriesForPort(proxier.exec, lp.Port, isIPv6, v1.ProtocolUDP) if err != nil { glog.Errorf("Failed to clear udp conntrack for port %d, error: %v", lp.Port, err) } </ins> } <ins> replacementPortsMap[lp] = socket </ins> } <del> replacementPortsMap[lp] = socket </del> } if hasEndpoints {

} if svcInfo.NodePort != 0 { <del> lp := utilproxy.LocalPort{ Description: "nodePort for " + svcNameString, IP:          "", Port:        svcInfo.NodePort, Protocol:    protocol, </del> <ins> addresses, err := utilproxy.GetNodeAddresses(proxier.nodePortAddresses, proxier.networkInterfacer) if err != nil { glog.Errorf("Failed to get node ip address matching nodeport cidr: %v", err) continue </ins> } <del> if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] } else { socket, err := proxier.portMapper.OpenLocalPort(&lp) if err != nil { glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) continue </del> <ins> lps := make([]utilproxy.LocalPort, 0) for address := range addresses { lp := utilproxy.LocalPort{ Description: "nodePort for " + svcNameString, IP:          address, Port:        svcInfo.NodePort, Protocol:    protocol, </ins> } <del> if lp.Protocol == "udp" { isIPv6 := utilnet.IsIPv6(svcInfo.ClusterIP) conntrack.ClearEntriesForPort(proxier.exec, lp.Port, isIPv6, clientv1.ProtocolUDP) </del> <ins> if utilproxy.IsZeroCIDR(address) { // Empty IP address means all lp.IP = "" lps = append(lps, lp) // If we encounter a zero CIDR, then there is no point in processing the rest of the addresses. break </ins> } <del> replacementPortsMap[lp] = socket } // We're holding the port, so it's OK to install ipvs rules. </del> <ins> lps = append(lps, lp) } // For ports on node IPs, open the actual port and hold it. for _, lp := range lps { if proxier.portsMap[lp] != nil { glog.V(4).Infof("Port %s was open before and is still needed", lp.String()) replacementPortsMap[lp] = proxier.portsMap[lp] } else { socket, err := proxier.portMapper.OpenLocalPort(&lp) if err != nil { glog.Errorf("can't open %s, skipping this nodePort: %v", lp.String(), err) continue } if lp.Protocol == "udp" { isIPv6 := utilnet.IsIPv6(svcInfo.ClusterIP) conntrack.ClearEntriesForPort(proxier.exec, lp.Port, isIPv6, clientv1.ProtocolUDP) } replacementPortsMap[lp] = socket } // We're holding the port, so it's OK to install ipvs rules. } </ins> // Nodeports need SNAT, unless they're local. // ipset call

// Build ipvs kernel routes for each node ip address nodeIPs := make([]net.IP, 0) <del> addresses, err := utilproxy.GetNodeAddresses(proxier.nodePortAddresses, proxier.networkInterfacer) if err != nil { glog.Errorf("Failed to get node ip address matching nodeport cidr") continue } </del> for address := range addresses { if !utilproxy.IsZeroCIDR(address) { nodeIPs = append(nodeIPs, net.ParseIP(address))