Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin/kubernetes: Handle multiple local IPs and bind #3208

Merged
merged 8 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion plugin/backend_lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques
// ... and reset
state.Req.Question[0].Name = old

seen := map[string]bool{}
Copy link
Member Author

@chrisohaver chrisohaver Aug 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added seen to avoid answer duplication in cases where a single answer has multiple additional records.


for _, serv := range services {
what, ip := serv.HostType()
switch what {
Expand All @@ -380,8 +382,13 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques

case dns.TypeA, dns.TypeAAAA:
serv.Host = msg.Domain(serv.Key)
records = append(records, serv.NewNS(state.QName()))
extra = append(extra, newAddress(serv, serv.Host, ip, what))
ns := serv.NewNS(state.QName())
if _, ok := seen[ns.Ns]; ok {
continue
}
seen[ns.Ns] = true
records = append(records, ns)
}
}
return records, extra, nil
Expand Down
10 changes: 4 additions & 6 deletions plugin/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,10 @@ type Kubernetes struct {
Fall fall.F
ttl uint32
opts dnsControlOpts

primaryZoneIndex int
interfaceAddrsFunc func() net.IP
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
TransferTo []string
primaryZoneIndex int
localIPs []net.IP
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
TransferTo []string
}

// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
Expand All @@ -56,7 +55,6 @@ func New(zones []string) *Kubernetes {
k := new(Kubernetes)
k.Zones = zones
k.Namespaces = make(map[string]struct{})
k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("127.0.0.1") }
k.podMode = podModeDisabled
k.ttl = defaultTTL

Expand Down
4 changes: 3 additions & 1 deletion plugin/kubernetes/kubernetes_apex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kubernetes

import (
"context"
"net"
"testing"

"github.com/coredns/coredns/plugin/pkg/dnstest"
Expand Down Expand Up @@ -63,6 +64,7 @@ func TestServeDNSApex(t *testing.T) {
k := New([]string{"cluster.local."})
k.APIConn = &APIConnServeTest{}
k.Next = test.NextHandler(dns.RcodeSuccess, nil)
k.localIPs = []net.IP{net.ParseIP("127.0.0.1")}
ctx := context.TODO()

for i, tc := range kubeApexCases {
Expand All @@ -85,7 +87,7 @@ func TestServeDNSApex(t *testing.T) {
}

if err := test.SortAndCheck(resp, tc); err != nil {
t.Error(err)
t.Errorf("Test %d: %v", i, err)
}
}
}
40 changes: 25 additions & 15 deletions plugin/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,20 +310,28 @@ func TestServicesAuthority(t *testing.T) {
key string
}
type svcTest struct {
interfaceAddrs func() net.IP
qname string
qtype uint16
answer *svcAns
localIPs []net.IP
qname string
qtype uint16
answer []svcAns
}
tests := []svcTest{
{interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA, answer: &svcAns{host: "127.0.0.1", key: "/" + coredns + "/test/interwebs/dns/ns"}},
{interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA},
{interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA},
{interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA, answer: &svcAns{host: "::1", key: "/" + coredns + "/test/interwebs/dns/ns"}},
{localIPs: []net.IP{net.ParseIP("1.2.3.4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "1.2.3.4", key: "/" + coredns + "/test/interwebs/dns/ns"}}},
{localIPs: []net.IP{net.ParseIP("1.2.3.4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA},
{localIPs: []net.IP{net.ParseIP("1:2::3:4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA},
{localIPs: []net.IP{net.ParseIP("1:2::3:4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA, answer: []svcAns{{host: "1:2::3:4", key: "/" + coredns + "/test/interwebs/dns/ns"}}},
{
localIPs: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("1:2::3:4")},
qname: "ns.dns.interwebs.test.",
qtype: dns.TypeNS, answer: []svcAns{
{host: "1.2.3.4", key: "/" + coredns + "/test/interwebs/dns/ns"},
{host: "1:2::3:4", key: "/" + coredns + "/test/interwebs/dns/ns"},
},
},
}

for i, test := range tests {
k.interfaceAddrsFunc = test.interfaceAddrs
k.localIPs = test.localIPs

state := request.Request{
Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
Expand All @@ -334,7 +342,7 @@ func TestServicesAuthority(t *testing.T) {
t.Errorf("Test %d: got error '%v'", i, e)
continue
}
if test.answer != nil && len(svcs) != 1 {
if test.answer != nil && len(svcs) != len(test.answer) {
t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs))
continue
}
Expand All @@ -347,11 +355,13 @@ func TestServicesAuthority(t *testing.T) {
continue
}

if test.answer.host != svcs[0].Host {
t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host)
}
if test.answer.key != svcs[0].Key {
t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key)
for i, answer := range test.answer {
if answer.host != svcs[i].Host {
t.Errorf("Test %d, expected host '%v', got '%v'", i, answer.host, svcs[i].Host)
}
if answer.key != svcs[i].Key {
t.Errorf("Test %d, expected key '%v', got '%v'", i, answer.key, svcs[i].Key)
}
}
}
}
49 changes: 31 additions & 18 deletions plugin/kubernetes/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,54 @@ package kubernetes

import (
"net"

"github.com/caddyserver/caddy"
"github.com/coredns/coredns/core/dnsserver"
)

func localPodIP() net.IP {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
// boundIPs returns the list of non-loopback IPs that CoreDNS is bound to
func boundIPs(c *caddy.Controller) (ips []net.IP) {
conf := dnsserver.GetConfig(c)
hosts := conf.ListenHosts
if hosts == nil || hosts[0] == "" {
hosts = nil
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
for _, addr := range addrs {
hosts = append(hosts, addr.String())
}
}

for _, addr := range addrs {
ip, _, _ := net.ParseCIDR(addr.String())
for _, host := range hosts {
ip, _, _ := net.ParseCIDR(host)
ip4 := ip.To4()
if ip4 != nil && !ip4.IsLoopback() {
return ip4
ips = append(ips, ip4)
continue
}
ip6 := ip.To16()
if ip6 != nil && !ip6.IsLoopback() {
return ip6
ips = append(ips, ip6)
}
}
return nil
return ips
}

// LocalNodeName is exclusively used in federation plugin, will be deprecated later.
Copy link
Member

@johnbelamaric johnbelamaric Aug 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this comment true?

At least partly ...

"LocalNodeName is exclusively used in federation plugin": True, only used by FederationsFunc in the federation plugin.

"... will be deprecated later": Maybe - depends on what "remove" means in #3041. I thought it means deprecate/remove... but maybe it just means to move it to an external repo (since thats all that was done, and the issue was closed).

func (k *Kubernetes) LocalNodeName() string {
localIP := k.interfaceAddrsFunc()
if localIP == nil {
if len(k.localIPs) == 0 {
return ""
}

// Find endpoint matching localIP
for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
if localIP.Equal(net.ParseIP(addr.IP)) {
return addr.NodeName
// Find fist endpoint matching any localIP
for _, localIP := range k.localIPs {
for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
if localIP.Equal(net.ParseIP(addr.IP)) {
return addr.NodeName
}
}
}
}
Expand Down
21 changes: 13 additions & 8 deletions plugin/kubernetes/ns.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR {
svcIPs []net.IP
)

// Find the CoreDNS Endpoint
localIP := k.interfaceAddrsFunc()
endpoints := k.APIConn.EpIndexReverse(localIP.String())
// Find the CoreDNS Endpoints
for _, localIP := range k.localIPs {
endpoints := k.APIConn.EpIndexReverse(localIP.String())

// If the CoreDNS Endpoint is not found, use the locally bound IP address
if len(endpoints) == 0 {
svcNames = []string{defaultNSName + zone}
svcIPs = []net.IP{localIP}
} else {
// Collect IPs for all Services of the Endpoints
for _, endpoint := range endpoints {
svcs := k.APIConn.SvcIndex(object.ServiceKey(endpoint.Name, endpoint.Namespace))
Expand Down Expand Up @@ -59,6 +54,16 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR {
}
}

// If no local IPs matched any endpoints, use the localIPs directly
if len(svcIPs) == 0 {
svcIPs = make([]net.IP, len(k.localIPs))
svcNames = make([]string, len(k.localIPs))
for i, localIP := range k.localIPs {
svcNames[i] = defaultNSName + zone
svcIPs[i] = localIP
}
}

// Create an RR slice of collected IPs
var rrs []dns.RR
rrs = make([]dns.RR, len(svcIPs))
Expand Down
2 changes: 1 addition & 1 deletion plugin/kubernetes/ns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestNsAddrs(t *testing.T) {

k := New([]string{"inter.webs.test."})
k.APIConn = &APIConnTest{}
k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("10.244.0.20") }
k.localIPs = []net.IP{net.ParseIP("10.244.0.20")}

cdrs := k.nsAddrs(false, k.Zones[0])

Expand Down
7 changes: 6 additions & 1 deletion plugin/kubernetes/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ func setup(c *caddy.Controller) error {
return k
})

// get locally bound addresses
c.OnStartup(func() error {
k.localIPs = boundIPs(c)
return nil
})

return nil
}

Expand Down Expand Up @@ -113,7 +119,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {

k8s := New([]string{""})
k8s.interfaceAddrsFunc = localPodIP
k8s.autoPathSearch = searchFromResolvConf()

opts := dnsControlOpts{
Expand Down