Adds Support for Node Resource IPv6 Addressing

Adds support for the following:

1. A node resource to be assigned an IPv6 address.
2. Expands IPv4/v6 address validation checks.

Which issue this PR fixes:
fixes #44848 in combination with PR #45116

Special notes for your reviewer:

Release note:
With this PR, nodes can be assigned an IPv6 address. An IPv4 address is
preferred over an IPv6 address. IP address validation has been expanded
to check for multicast, link-local and unspecified addresses.
This commit is contained in:
Daneyon Hansen 2017-05-03 10:24:58 -05:00
parent 7c8596a95f
commit 7ac6fe9c5d
4 changed files with 95 additions and 29 deletions

View File

@ -359,8 +359,13 @@ func (kl *Kubelet) GetClusterDNS(pod *v1.Pod) ([]string, []string, []string, boo
// local machine". A nameserver setting of localhost is equivalent to // local machine". A nameserver setting of localhost is equivalent to
// this documented behavior. // this documented behavior.
if kl.resolverConfig == "" { if kl.resolverConfig == "" {
hostDNS = []string{"127.0.0.1"}
hostSearch = []string{"."} hostSearch = []string{"."}
switch {
case kl.nodeIP == nil || kl.nodeIP.To4() != nil:
hostDNS = []string{"127.0.0.1"}
case kl.nodeIP.To16() != nil:
hostDNS = []string{"::1"}
}
} else { } else {
hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod) hostSearch = kl.formDNSSearchForDNSDefault(hostSearch, pod)
} }

View File

@ -99,28 +99,56 @@ func TestNoOpHostSupportsLegacyFeatures(t *testing.T) {
} }
func TestNodeIPParam(t *testing.T) { func TestNodeIPParam(t *testing.T) {
testKubelet := newTestKubelet(t, false /* controllerAttachDetachEnabled */) type test struct {
defer testKubelet.Cleanup()
kubelet := testKubelet.kubelet
tests := []struct {
nodeIP string nodeIP string
success bool success bool
testName string testName string
}{ }
tests := []test{
{ {
nodeIP: "", nodeIP: "",
success: true, success: false,
testName: "IP not set", testName: "IP not set",
}, },
{ {
nodeIP: "127.0.0.1", nodeIP: "127.0.0.1",
success: false, success: false,
testName: "loopback address", testName: "IPv4 loopback address",
}, },
{ {
nodeIP: "FE80::0202:B3FF:FE1E:8329", nodeIP: "::1",
success: false, success: false,
testName: "IPv6 address", testName: "IPv6 loopback address",
},
{
nodeIP: "224.0.0.1",
success: false,
testName: "multicast IPv4 address",
},
{
nodeIP: "ff00::1",
success: false,
testName: "multicast IPv6 address",
},
{
nodeIP: "169.254.0.1",
success: false,
testName: "IPv4 link-local unicast address",
},
{
nodeIP: "fe80::0202:b3ff:fe1e:8329",
success: false,
testName: "IPv6 link-local unicast address",
},
{
nodeIP: "0.0.0.0",
success: false,
testName: "Unspecified IPv4 address",
},
{
nodeIP: "::",
success: false,
testName: "Unspecified IPv6 address",
}, },
{ {
nodeIP: "1.2.3.4", nodeIP: "1.2.3.4",
@ -128,9 +156,31 @@ func TestNodeIPParam(t *testing.T) {
testName: "IPv4 address that doesn't belong to host", testName: "IPv4 address that doesn't belong to host",
}, },
} }
addrs, err := net.InterfaceAddrs()
if err != nil {
assert.Error(t, err, fmt.Sprintf(
"Unable to obtain a list of the node's unicast interface addresses."))
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
break
}
successTest := test{
nodeIP: ip.String(),
success: true,
testName: fmt.Sprintf("Success test case for address %s", ip.String()),
}
tests = append(tests, successTest)
}
for _, test := range tests { for _, test := range tests {
kubelet.nodeIP = net.ParseIP(test.nodeIP) err := validateNodeIP(net.ParseIP(test.nodeIP))
err := kubelet.validateNodeIP()
if test.success { if test.success {
assert.NoError(t, err, "test %s", test.testName) assert.NoError(t, err, "test %s", test.testName)
} else { } else {

View File

@ -437,7 +437,7 @@ func (kl *Kubelet) recordNodeStatusEvent(eventType, event string) {
// Set IP and hostname addresses for the node. // Set IP and hostname addresses for the node.
func (kl *Kubelet) setNodeAddress(node *v1.Node) error { func (kl *Kubelet) setNodeAddress(node *v1.Node) error {
if kl.nodeIP != nil { if kl.nodeIP != nil {
if err := kl.validateNodeIP(); err != nil { if err := validateNodeIP(kl.nodeIP); err != nil {
return fmt.Errorf("failed to validate nodeIP: %v", err) return fmt.Errorf("failed to validate nodeIP: %v", err)
} }
glog.V(2).Infof("Using node IP: %q", kl.nodeIP.String()) glog.V(2).Infof("Using node IP: %q", kl.nodeIP.String())
@ -503,7 +503,8 @@ func (kl *Kubelet) setNodeAddress(node *v1.Node) error {
// 1) Use nodeIP if set // 1) Use nodeIP if set
// 2) If the user has specified an IP to HostnameOverride, use it // 2) If the user has specified an IP to HostnameOverride, use it
// 3) Lookup the IP from node name by DNS and use the first non-loopback ipv4 address // 3) Lookup the IP from node name by DNS and use the first valid IPv4 address.
// If the node does not have a valid IPv4 address, use the first valid IPv6 address.
// 4) Try to get the IP from the network interface used as default gateway // 4) Try to get the IP from the network interface used as default gateway
if kl.nodeIP != nil { if kl.nodeIP != nil {
ipAddr = kl.nodeIP ipAddr = kl.nodeIP
@ -511,11 +512,16 @@ func (kl *Kubelet) setNodeAddress(node *v1.Node) error {
ipAddr = addr ipAddr = addr
} else { } else {
var addrs []net.IP var addrs []net.IP
addrs, err = net.LookupIP(node.Name) addrs, _ = net.LookupIP(node.Name)
for _, addr := range addrs { for _, addr := range addrs {
if !addr.IsLoopback() && addr.To4() != nil { if err = validateNodeIP(addr); err == nil {
ipAddr = addr if addr.To4() != nil {
break ipAddr = addr
break
}
if addr.To16() != nil && ipAddr == nil {
ipAddr = addr
}
} }
} }
@ -991,17 +997,22 @@ func (kl *Kubelet) defaultNodeStatusFuncs() []func(*v1.Node) error {
} }
// Validate given node IP belongs to the current host // Validate given node IP belongs to the current host
func (kl *Kubelet) validateNodeIP() error { func validateNodeIP(nodeIP net.IP) error {
if kl.nodeIP == nil {
return nil
}
// Honor IP limitations set in setNodeStatus() // Honor IP limitations set in setNodeStatus()
if kl.nodeIP.IsLoopback() { if nodeIP.To4() == nil && nodeIP.To16() == nil {
return fmt.Errorf("nodeIP must be a valid IP address")
}
if nodeIP.IsLoopback() {
return fmt.Errorf("nodeIP can't be loopback address") return fmt.Errorf("nodeIP can't be loopback address")
} }
if kl.nodeIP.To4() == nil { if nodeIP.IsMulticast() {
return fmt.Errorf("nodeIP must be IPv4 address") return fmt.Errorf("nodeIP can't be a multicast address")
}
if nodeIP.IsLinkLocalUnicast() {
return fmt.Errorf("nodeIP can't be a link-local unicast address")
}
if nodeIP.IsUnspecified() {
return fmt.Errorf("nodeIP can't be an all zeros address")
} }
addrs, err := net.InterfaceAddrs() addrs, err := net.InterfaceAddrs()
@ -1016,9 +1027,9 @@ func (kl *Kubelet) validateNodeIP() error {
case *net.IPAddr: case *net.IPAddr:
ip = v.IP ip = v.IP
} }
if ip != nil && ip.Equal(kl.nodeIP) { if ip != nil && ip.Equal(nodeIP) {
return nil return nil
} }
} }
return fmt.Errorf("Node IP: %q not found in the host's network interfaces", kl.nodeIP.String()) return fmt.Errorf("Node IP: %q not found in the host's network interfaces", nodeIP.String())
} }

View File

@ -144,7 +144,7 @@ func TestNodeStatusWithCloudProviderNodeIP(t *testing.T) {
Spec: v1.NodeSpec{}, Spec: v1.NodeSpec{},
} }
// TODO : is it possible to mock kubelet.validateNodeIP() to avoid relying on the host interface addresses ? // TODO : is it possible to mock validateNodeIP() to avoid relying on the host interface addresses ?
addrs, err := net.InterfaceAddrs() addrs, err := net.InterfaceAddrs()
assert.NoError(t, err) assert.NoError(t, err)
for _, addr := range addrs { for _, addr := range addrs {