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...] {
namespaces NAMESPACE...
labels EXPRESSION
pods POD-MODE
endpoint_pod_names
upstream ADDRESS...
ttl TTL
fallthrough
......@@ -65,6 +66,16 @@ kubernetes [ZONES...] {
option requires substantially more memory than in insecure mode, since it will maintain a watch
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
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.
......
......@@ -28,19 +28,20 @@ import (
// Kubernetes implements a plugin that connects to a Kubernetes cluster.
type Kubernetes struct {
Next plugin.Handler
Zones []string
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
APIServerList []string
APIProxy *apiProxy
APICertAuth string
APIClientCert string
APIClientKey string
APIConn dnsController
Namespaces map[string]bool
podMode string
Fallthrough bool
ttl uint32
Next plugin.Handler
Zones []string
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
APIServerList []string
APIProxy *apiProxy
APICertAuth string
APIClientCert string
APIClientKey string
APIConn dnsController
Namespaces map[string]bool
podMode string
endpointNameMode bool
Fallthrough bool
ttl uint32
primaryZoneIndex int
interfaceAddrsFunc func() net.IP
......@@ -276,10 +277,13 @@ func (k *Kubernetes) Records(state request.Request, exact bool) ([]msg.Service,
return services, err
}
func endpointHostname(addr api.EndpointAddress) string {
func endpointHostname(addr api.EndpointAddress, endpointNameMode bool) string {
if addr.Hostname != "" {
return strings.ToLower(addr.Hostname)
}
if endpointNameMode && addr.TargetRef != nil && addr.TargetRef.Name != "" {
return addr.TargetRef.Name
}
if strings.Contains(addr.IP, ".") {
return strings.Replace(addr.IP, ".", "-", -1)
}
......@@ -375,7 +379,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
// See comments in parse.go parseRequest about the endpoint handling.
if r.endpoint != "" {
if !match(r.endpoint, endpointHostname(addr)) {
if !match(r.endpoint, endpointHostname(addr, k.endpointNameMode)) {
continue
}
}
......@@ -385,7 +389,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg.
continue
}
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
......
......@@ -34,15 +34,21 @@ func TestWildcard(t *testing.T) {
func TestEndpointHostname(t *testing.T) {
var tests = []struct {
ip string
hostname string
expected string
ip string
hostname string
expected string
podName string
endpointNameMode bool
}{
{"10.11.12.13", "", "10-11-12-13"},
{"10.11.12.13", "epname", "epname"},
{"10.11.12.13", "", "10-11-12-13", "", false},
{"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 {
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 {
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 {
for _, eps := range ep.Subsets {
for _, addr := range eps.Addresses {
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}}
}
}
......
......@@ -104,6 +104,13 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, dnsControlOpts, error) {
for c.NextBlock() {
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":
args := c.RemainingArgs()
if len(args) == 1 {
......
......@@ -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