Commit 3527be6c authored by Brian Akins's avatar Brian Akins Committed by John Belamaric

Add option to use pod name rather than IP address for Kubernetes (#1190)

Change to use a new 'endpoints' directive and use a constant

Add initial docs for 'endpoints' directive

Add tests to Kubernetes setup for endpoints

Changes based on PR feedback

endpoint_pod_names is a boolean config option. Chahanged docs to reflect this.

Add a test when endpoints_pod_names is not set

Update README.md

Remove endpointNameModeName as it is no longer used
parent c6ce769f
...@@ -31,6 +31,7 @@ kubernetes [ZONES...] { ...@@ -31,6 +31,7 @@ kubernetes [ZONES...] {
namespaces NAMESPACE... namespaces NAMESPACE...
labels EXPRESSION labels EXPRESSION
pods POD-MODE pods POD-MODE
endpoint_pod_names
upstream ADDRESS... upstream ADDRESS...
ttl TTL ttl TTL
fallthrough fallthrough
...@@ -65,6 +66,16 @@ kubernetes [ZONES...] { ...@@ -65,6 +66,16 @@ kubernetes [ZONES...] {
option requires substantially more memory than in insecure mode, since it will maintain a watch option requires substantially more memory than in insecure mode, since it will maintain a watch
on all pods. on all pods.
* `endpoint_pod_names` Use the pod name of the pod targeted by the endpoint as
the endpoint name in A records, e.g.
`endpoint-name.my-service.namespace.svc.cluster.local. in A 1.2.3.4`
By default, the endpoint-name name selection is as follows: Use the hostname
of the endpoint, or if hostname is not set, use the dashed form of the endpoint
ip address (e.g. `1-2-3-4.my-service.namespace.svc.cluster.local.`)
If this directive is included, then name selection for endpoints changes as
follows: Use the hostname of the endpoint, or if hostname is not set, use the
pod name of the pod targeted by the endpoint. If there is no pod targeted by
the endpoint, use the dashed ip address form.
* `upstream` **ADDRESS [ADDRESS...]** defines the upstream resolvers used for resolving services * `upstream` **ADDRESS [ADDRESS...]** defines the upstream resolvers used for resolving services
that point to external hosts (External Services). **ADDRESS** can be an ip, an ip:port, or a path that point to external hosts (External Services). **ADDRESS** can be an ip, an ip:port, or a path
to a file structured like resolv.conf. to a file structured like resolv.conf.
......
...@@ -39,6 +39,7 @@ type Kubernetes struct { ...@@ -39,6 +39,7 @@ type Kubernetes struct {
APIConn dnsController APIConn dnsController
Namespaces map[string]bool Namespaces map[string]bool
podMode string podMode string
endpointNameMode bool
Fallthrough bool Fallthrough bool
ttl uint32 ttl uint32
...@@ -276,10 +277,13 @@ func (k *Kubernetes) Records(state request.Request, exact bool) ([]msg.Service, ...@@ -276,10 +277,13 @@ func (k *Kubernetes) Records(state request.Request, exact bool) ([]msg.Service,
return services, err return services, err
} }
func endpointHostname(addr api.EndpointAddress) string { func endpointHostname(addr api.EndpointAddress, endpointNameMode bool) string {
if addr.Hostname != "" { if addr.Hostname != "" {
return strings.ToLower(addr.Hostname) return strings.ToLower(addr.Hostname)
} }
if endpointNameMode && addr.TargetRef != nil && addr.TargetRef.Name != "" {
return addr.TargetRef.Name
}
if strings.Contains(addr.IP, ".") { if strings.Contains(addr.IP, ".") {
return strings.Replace(addr.IP, ".", "-", -1) return strings.Replace(addr.IP, ".", "-", -1)
} }
...@@ -375,7 +379,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. ...@@ -375,7 +379,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
// See comments in parse.go parseRequest about the endpoint handling. // See comments in parse.go parseRequest about the endpoint handling.
if r.endpoint != "" { if r.endpoint != "" {
if !match(r.endpoint, endpointHostname(addr)) { if !match(r.endpoint, endpointHostname(addr, k.endpointNameMode)) {
continue continue
} }
} }
...@@ -385,7 +389,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. ...@@ -385,7 +389,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
continue continue
} }
s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl} s := msg.Service{Host: addr.IP, Port: int(p.Port), TTL: k.ttl}
s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name, endpointHostname(addr)}, "/") s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name, endpointHostname(addr, k.endpointNameMode)}, "/")
err = nil err = nil
......
...@@ -37,12 +37,18 @@ func TestEndpointHostname(t *testing.T) { ...@@ -37,12 +37,18 @@ func TestEndpointHostname(t *testing.T) {
ip string ip string
hostname string hostname string
expected string expected string
podName string
endpointNameMode bool
}{ }{
{"10.11.12.13", "", "10-11-12-13"}, {"10.11.12.13", "", "10-11-12-13", "", false},
{"10.11.12.13", "epname", "epname"}, {"10.11.12.13", "epname", "epname", "", false},
{"10.11.12.13", "", "10-11-12-13", "hello-abcde", false},
{"10.11.12.13", "epname", "epname", "hello-abcde", false},
{"10.11.12.13", "epname", "epname", "hello-abcde", true},
{"10.11.12.13", "", "hello-abcde", "hello-abcde", true},
} }
for _, test := range tests { for _, test := range tests {
result := endpointHostname(api.EndpointAddress{IP: test.ip, Hostname: test.hostname}) result := endpointHostname(api.EndpointAddress{IP: test.ip, Hostname: test.hostname, TargetRef: &api.ObjectReference{Name: test.podName}}, test.endpointNameMode)
if result != test.expected { if result != test.expected {
t.Errorf("Expected endpoint name for (ip:%v hostname:%v) to be '%v', but got '%v'", test.ip, test.hostname, test.expected, result) t.Errorf("Expected endpoint name for (ip:%v hostname:%v) to be '%v', but got '%v'", test.ip, test.hostname, test.expected, result)
} }
......
...@@ -42,7 +42,7 @@ func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service { ...@@ -42,7 +42,7 @@ func (k *Kubernetes) serviceRecordForIP(ip, name string) []msg.Service {
for _, eps := range ep.Subsets { for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses { for _, addr := range eps.Addresses {
if addr.IP == ip { if addr.IP == ip {
domain := strings.Join([]string{endpointHostname(addr), ep.ObjectMeta.Name, ep.ObjectMeta.Namespace, Svc, k.primaryZone()}, ".") domain := strings.Join([]string{endpointHostname(addr, k.endpointNameMode), ep.ObjectMeta.Name, ep.ObjectMeta.Namespace, Svc, k.primaryZone()}, ".")
return []msg.Service{{Host: domain}} return []msg.Service{{Host: domain}}
} }
} }
......
...@@ -104,6 +104,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) { ...@@ -104,6 +104,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
case "endpoint_pod_names":
args := c.RemainingArgs()
if len(args) > 0 {
return nil, opts, c.ArgErr()
}
k8s.endpointNameMode = true
continue
case "pods": case "pods":
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 1 { if len(args) == 1 {
......
...@@ -471,3 +471,66 @@ func TestKubernetesParse(t *testing.T) { ...@@ -471,3 +471,66 @@ func TestKubernetesParse(t *testing.T) {
} }
} }
} }
func TestKubernetesEndpointsParse(t *testing.T) {
tests := []struct {
input string // Corefile data as string
shouldErr bool // true if test case is exected to produce an error.
expectedErrContent string // substring from the expected error. Empty for positive cases.
expectedEndpointMode bool
}{
// valid endpoints mode
{
`kubernetes coredns.local {
endpoint_pod_names
}`,
false,
"",
true,
},
// endpoints invalid
{
`kubernetes coredns.local {
endpoint_pod_names giant_seed
}`,
true,
"rong argument count or unexpected",
false,
},
// endpoint not set
{
`kubernetes coredns.local {
}`,
false,
"",
false,
},
}
for i, test := range tests {
c := caddy.NewTestController("dns", test.input)
k8sController, _, err := kubernetesParse(c)
if test.shouldErr && err == nil {
t.Errorf("Test %d: Expected error, but did not find error for input '%s'. Error was: '%v'", i, test.input, err)
}
if err != nil {
if !test.shouldErr {
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
continue
}
if !strings.Contains(err.Error(), test.expectedErrContent) {
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
}
continue
}
// Endpoints
foundEndpointNameMode := k8sController.endpointNameMode
if foundEndpointNameMode != test.expectedEndpointMode {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with endpoints mode '%v'. Instead found endpoints mode '%v' for input '%s'", i, test.expectedEndpointMode, foundEndpointNameMode, test.input)
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment