Code change:
return &serverList[0], nil } <del> func findAddrs(netblob interface{}) []string { // Run-time types for the win :( ret := []string{} list, ok := netblob.([]interface{}) if !ok { return ret } for _, item := range list { props, ok := item.(map[string]interface{}) if !ok { continue } tmp, ok := props["addr"] </del> <ins> func getAddressesByName(client *gophercloud.ServiceClient, name string) ([]api.NodeAddress, error) { srv, err := getServerByName(client, name) if err != nil { return nil, err } addrs := []api.NodeAddress{} for network, netblob := range srv.Addresses { list, ok := netblob.([]interface{}) </ins> if !ok { continue } <del> addr, ok := tmp.(string) if !ok { continue </del> <ins> for _, item := range list { var addressType api.NodeAddressType props, ok := item.(map[string]interface{}) if !ok { continue } extIPType, ok := props["OS-EXT-IPS:type"] if (ok && extIPType == "floating") || (!ok && network == "public") { addressType = api.NodeExternalIP } else { addressType = api.NodeInternalIP } tmp, ok := props["addr"] if !ok { continue } addr, ok := tmp.(string) if !ok { continue } api.AddToNodeAddresses(&addrs, api.NodeAddress{ Type:    addressType, Address: addr, }, ) </ins> } <del> ret = append(ret, addr) </del> } <del> return ret </del> <ins> // AccessIPs are usually duplicates of "public" addresses. if srv.AccessIPv4 != "" { api.AddToNodeAddresses(&addrs, api.NodeAddress{ Type:    api.NodeExternalIP, Address: srv.AccessIPv4, }, ) } if srv.AccessIPv6 != "" { api.AddToNodeAddresses(&addrs, api.NodeAddress{ Type:    api.NodeExternalIP, Address: srv.AccessIPv6, }, ) } return addrs, nil </ins> } <del> func getAddressByName(api *gophercloud.ServiceClient, name string) (string, error) { srv, err := getServerByName(api, name) </del> <ins> func getAddressByName(client *gophercloud.ServiceClient, name string) (string, error) { addrs, err := getAddressesByName(client, name) </ins> if err != nil { return "", err <ins> } else if len(addrs) == 0 { return "", ErrNoAddressFound </ins> } <del> var s string if s == "" { if tmp := findAddrs(srv.Addresses["private"]); len(tmp) >= 1 { s = tmp[0] } } if s == "" { if tmp := findAddrs(srv.Addresses["public"]); len(tmp) >= 1 { s = tmp[0] </del> <ins> for _, addr := range addrs { if addr.Type == api.NodeInternalIP { return addr.Address, nil </ins> } } <del> if s == "" { s = srv.AccessIPv4 } if s == "" { s = srv.AccessIPv6 } if s == "" { return "", ErrNoAddressFound } return s, nil </del> <ins> return addrs[0].Address, nil </ins> } // Implementation of Instances.CurrentNodeName

Related discussion:
text: I think in practice it's probably ok to continue to prefer "internal" to "external" for loadbalancer setup, but I can contrive hypothetical cases that need more flexibility here too. Again, if you have a concrete need for such a thing, please file a bug so we have some idea of the flexibility required. Complicating all this is that Kubernetes never actually defined what an "internal" address was. just says "Generally the IP address of the node that is routable only within the cluster" which isn't a sufficiently strict definition. Looking at where Kubernetes actually uses it: just uses the first address reported (preferring InternalIPs to ExternalIPs) and the Kubernetes master configures an "SSH Tunnel Check" which just uses the first ExternalIP ... which kind of makes it pointless to ever even bother reporting anything beyond the first address. In both cases, it's assumed that this address is reachable by the Kubernetes master nodes, which makes the Internal vs External distinction a bit pointless. Re (2): is meant to return more than one IP, and the loadbalancer setup can only use a single IP for each node (or would unnecessarily track/healthceck multiple paths to the same backend for multiple addresses). If you're seeing single addresses returned by then you should file a bug. All the above treats IPv4 and IPv6 as equally important, if the OpenStack cloud has both, and the "first" address used for the loadbalancer will be an arbitrary choice between the two protocols (not quite true: will use AccessIPv4 before AccessIPv6). I think that's how it should be (the rest of the code should be protocol agnostic), but I wouldn't be surprised if there's some part of Kubernetes that doesn't support IPv6 properly. There certainly used to be lots of places that assumed IPv4 in Kubernetes, and GCE doesn't support IPv6 iirc so it gets less testing - if you've found something that breaks with IPv6, please file bugs. Now this bug: what would you like done here? Would you like the current situation to be documented further? Is the "private"/"public" naming thing too restrictive for your usecase? Something else?