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
This commit is contained in:
parent
c6ce769fc6
commit
3527be6c00
6 changed files with 114 additions and 23 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue