plugin/kubernetes: Handle multiple local IPs and bind (#3208)
* use all local IPs * mult/bind ips * gofmt + boundIPs fix * fix no matching endpoint case * don't duplicate NS records in answer * fix answer dedup * fix comment * add multi local ip test case
This commit is contained in:
parent
d79562842a
commit
630d3d60b9
8 changed files with 91 additions and 51 deletions
|
@ -372,6 +372,8 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques
|
||||||
// ... and reset
|
// ... and reset
|
||||||
state.Req.Question[0].Name = old
|
state.Req.Question[0].Name = old
|
||||||
|
|
||||||
|
seen := map[string]bool{}
|
||||||
|
|
||||||
for _, serv := range services {
|
for _, serv := range services {
|
||||||
what, ip := serv.HostType()
|
what, ip := serv.HostType()
|
||||||
switch what {
|
switch what {
|
||||||
|
@ -380,8 +382,13 @@ func NS(ctx context.Context, b ServiceBackend, zone string, state request.Reques
|
||||||
|
|
||||||
case dns.TypeA, dns.TypeAAAA:
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
serv.Host = msg.Domain(serv.Key)
|
serv.Host = msg.Domain(serv.Key)
|
||||||
records = append(records, serv.NewNS(state.QName()))
|
|
||||||
extra = append(extra, newAddress(serv, serv.Host, ip, what))
|
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
|
return records, extra, nil
|
||||||
|
|
|
@ -43,11 +43,10 @@ type Kubernetes struct {
|
||||||
Fall fall.F
|
Fall fall.F
|
||||||
ttl uint32
|
ttl uint32
|
||||||
opts dnsControlOpts
|
opts dnsControlOpts
|
||||||
|
primaryZoneIndex int
|
||||||
primaryZoneIndex int
|
localIPs []net.IP
|
||||||
interfaceAddrsFunc func() net.IP
|
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
|
||||||
autoPathSearch []string // Local search path from /etc/resolv.conf. Needed for autopath.
|
TransferTo []string
|
||||||
TransferTo []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
|
// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
|
||||||
|
@ -56,7 +55,6 @@ func New(zones []string) *Kubernetes {
|
||||||
k := new(Kubernetes)
|
k := new(Kubernetes)
|
||||||
k.Zones = zones
|
k.Zones = zones
|
||||||
k.Namespaces = make(map[string]struct{})
|
k.Namespaces = make(map[string]struct{})
|
||||||
k.interfaceAddrsFunc = func() net.IP { return net.ParseIP("127.0.0.1") }
|
|
||||||
k.podMode = podModeDisabled
|
k.podMode = podModeDisabled
|
||||||
k.ttl = defaultTTL
|
k.ttl = defaultTTL
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
|
@ -63,6 +64,7 @@ func TestServeDNSApex(t *testing.T) {
|
||||||
k := New([]string{"cluster.local."})
|
k := New([]string{"cluster.local."})
|
||||||
k.APIConn = &APIConnServeTest{}
|
k.APIConn = &APIConnServeTest{}
|
||||||
k.Next = test.NextHandler(dns.RcodeSuccess, nil)
|
k.Next = test.NextHandler(dns.RcodeSuccess, nil)
|
||||||
|
k.localIPs = []net.IP{net.ParseIP("127.0.0.1")}
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
for i, tc := range kubeApexCases {
|
for i, tc := range kubeApexCases {
|
||||||
|
@ -85,7 +87,7 @@ func TestServeDNSApex(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := test.SortAndCheck(resp, tc); err != nil {
|
if err := test.SortAndCheck(resp, tc); err != nil {
|
||||||
t.Error(err)
|
t.Errorf("Test %d: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -310,20 +310,28 @@ func TestServicesAuthority(t *testing.T) {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
type svcTest struct {
|
type svcTest struct {
|
||||||
interfaceAddrs func() net.IP
|
localIPs []net.IP
|
||||||
qname string
|
qname string
|
||||||
qtype uint16
|
qtype uint16
|
||||||
answer *svcAns
|
answer []svcAns
|
||||||
}
|
}
|
||||||
tests := []svcTest{
|
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"}},
|
{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"}}},
|
||||||
{interfaceAddrs: func() net.IP { return net.ParseIP("127.0.0.1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA},
|
{localIPs: []net.IP{net.ParseIP("1.2.3.4")}, qname: "ns.dns.interwebs.test.", qtype: dns.TypeAAAA},
|
||||||
{interfaceAddrs: func() net.IP { return net.ParseIP("::1") }, qname: "ns.dns.interwebs.test.", qtype: dns.TypeA},
|
{localIPs: []net.IP{net.ParseIP("1:2::3:4")}, 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.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 {
|
for i, test := range tests {
|
||||||
k.interfaceAddrsFunc = test.interfaceAddrs
|
k.localIPs = test.localIPs
|
||||||
|
|
||||||
state := request.Request{
|
state := request.Request{
|
||||||
Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
|
Req: &dns.Msg{Question: []dns.Question{{Name: test.qname, Qtype: test.qtype}}},
|
||||||
|
@ -334,7 +342,7 @@ func TestServicesAuthority(t *testing.T) {
|
||||||
t.Errorf("Test %d: got error '%v'", i, e)
|
t.Errorf("Test %d: got error '%v'", i, e)
|
||||||
continue
|
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))
|
t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -347,11 +355,13 @@ func TestServicesAuthority(t *testing.T) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.answer.host != svcs[0].Host {
|
for i, answer := range test.answer {
|
||||||
t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host)
|
if answer.host != svcs[i].Host {
|
||||||
}
|
t.Errorf("Test %d, expected host '%v', got '%v'", i, answer.host, svcs[i].Host)
|
||||||
if test.answer.key != svcs[0].Key {
|
}
|
||||||
t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key)
|
if answer.key != svcs[i].Key {
|
||||||
|
t.Errorf("Test %d, expected key '%v', got '%v'", i, answer.key, svcs[i].Key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,41 +2,54 @@ package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/caddyserver/caddy"
|
||||||
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func localPodIP() net.IP {
|
// boundIPs returns the list of non-loopback IPs that CoreDNS is bound to
|
||||||
addrs, err := net.InterfaceAddrs()
|
func boundIPs(c *caddy.Controller) (ips []net.IP) {
|
||||||
if err != nil {
|
conf := dnsserver.GetConfig(c)
|
||||||
return nil
|
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 _, host := range hosts {
|
||||||
for _, addr := range addrs {
|
ip, _, _ := net.ParseCIDR(host)
|
||||||
ip, _, _ := net.ParseCIDR(addr.String())
|
|
||||||
ip4 := ip.To4()
|
ip4 := ip.To4()
|
||||||
if ip4 != nil && !ip4.IsLoopback() {
|
if ip4 != nil && !ip4.IsLoopback() {
|
||||||
return ip4
|
ips = append(ips, ip4)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
ip6 := ip.To16()
|
ip6 := ip.To16()
|
||||||
if ip6 != nil && !ip6.IsLoopback() {
|
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.
|
// LocalNodeName is exclusively used in federation plugin, will be deprecated later.
|
||||||
func (k *Kubernetes) LocalNodeName() string {
|
func (k *Kubernetes) LocalNodeName() string {
|
||||||
localIP := k.interfaceAddrsFunc()
|
if len(k.localIPs) == 0 {
|
||||||
if localIP == nil {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find endpoint matching localIP
|
// Find fist endpoint matching any localIP
|
||||||
for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) {
|
for _, localIP := range k.localIPs {
|
||||||
for _, eps := range ep.Subsets {
|
for _, ep := range k.APIConn.EpIndexReverse(localIP.String()) {
|
||||||
for _, addr := range eps.Addresses {
|
for _, eps := range ep.Subsets {
|
||||||
if localIP.Equal(net.ParseIP(addr.IP)) {
|
for _, addr := range eps.Addresses {
|
||||||
return addr.NodeName
|
if localIP.Equal(net.ParseIP(addr.IP)) {
|
||||||
|
return addr.NodeName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR {
|
||||||
svcIPs []net.IP
|
svcIPs []net.IP
|
||||||
)
|
)
|
||||||
|
|
||||||
// Find the CoreDNS Endpoint
|
// Find the CoreDNS Endpoints
|
||||||
localIP := k.interfaceAddrsFunc()
|
for _, localIP := range k.localIPs {
|
||||||
endpoints := k.APIConn.EpIndexReverse(localIP.String())
|
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
|
// Collect IPs for all Services of the Endpoints
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
svcs := k.APIConn.SvcIndex(object.ServiceKey(endpoint.Name, endpoint.Namespace))
|
svcs := k.APIConn.SvcIndex(object.ServiceKey(endpoint.Name, endpoint.Namespace))
|
||||||
|
@ -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
|
// Create an RR slice of collected IPs
|
||||||
var rrs []dns.RR
|
var rrs []dns.RR
|
||||||
rrs = make([]dns.RR, len(svcIPs))
|
rrs = make([]dns.RR, len(svcIPs))
|
||||||
|
|
|
@ -111,7 +111,7 @@ func TestNsAddrs(t *testing.T) {
|
||||||
|
|
||||||
k := New([]string{"inter.webs.test."})
|
k := New([]string{"inter.webs.test."})
|
||||||
k.APIConn = &APIConnTest{}
|
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])
|
cdrs := k.nsAddrs(false, k.Zones[0])
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,12 @@ func setup(c *caddy.Controller) error {
|
||||||
return k
|
return k
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// get locally bound addresses
|
||||||
|
c.OnStartup(func() error {
|
||||||
|
k.localIPs = boundIPs(c)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +119,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
|
func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
|
|
||||||
k8s := New([]string{""})
|
k8s := New([]string{""})
|
||||||
k8s.interfaceAddrsFunc = localPodIP
|
|
||||||
k8s.autoPathSearch = searchFromResolvConf()
|
k8s.autoPathSearch = searchFromResolvConf()
|
||||||
|
|
||||||
opts := dnsControlOpts{
|
opts := dnsControlOpts{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue