mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
feat(services): implement Kubernetes services source
This commit is contained in:
parent
786055f3b3
commit
84910c4844
6
endpoint/endpoint.go
Normal file
6
endpoint/endpoint.go
Normal file
@ -0,0 +1,6 @@
|
||||
package endpoint
|
||||
|
||||
type Endpoint struct {
|
||||
DNSName string
|
||||
Target string
|
||||
}
|
63
source/service.go
Normal file
63
source/service.go
Normal file
@ -0,0 +1,63 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
)
|
||||
|
||||
const (
|
||||
// The annotation used for figuring out which controller is responsible
|
||||
controllerAnnotationKey = "external-dns.kubernetes.io/controller"
|
||||
// The annotation used for defining the desired hostname
|
||||
hostnameAnnotationKey = "external-dns.kubernetes.io/hostname"
|
||||
// The value of the controller annotation so that we feel resposible
|
||||
controllerAnnotationValue = "dns-controller"
|
||||
)
|
||||
|
||||
// ServiceSource is an implementation of Source for Kubernetes service objects.
|
||||
// It will find all services that are under our jurisdiction, i.e. annotated
|
||||
// desired hostname and matching or no controller annotation. For each of the
|
||||
// matches services' external entrypoints it will return a corresponding
|
||||
// Endpoint object.
|
||||
type ServiceSource struct {
|
||||
Client kubernetes.Interface
|
||||
}
|
||||
|
||||
// Endpoints returns endpoint objects for each service that should be processed.
|
||||
func (sc *ServiceSource) Endpoints() ([]endpoint.Endpoint, error) {
|
||||
services, err := sc.Client.Core().Services(v1.NamespaceAll).List(v1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints := []endpoint.Endpoint{}
|
||||
|
||||
for _, svc := range services.Items {
|
||||
// Check controller annotation to see if we are responsible.
|
||||
controller, exists := svc.Annotations[controllerAnnotationKey]
|
||||
if exists && controller != controllerAnnotationValue {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the desired hostname of the service from the annotation.
|
||||
hostname, exists := svc.Annotations[hostnameAnnotationKey]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create an endpoint matching the desired hostname.
|
||||
endpoint := endpoint.Endpoint{
|
||||
DNSName: hostname,
|
||||
}
|
||||
|
||||
// Create a corresponding endpoint for each configured external entrypoint.
|
||||
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||
endpoint.Target = lb.IP
|
||||
endpoints = append(endpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
201
source/service_test.go
Normal file
201
source/service_test.go
Normal file
@ -0,0 +1,201 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
|
||||
"github.com/kubernetes-incubator/external-dns/endpoint"
|
||||
)
|
||||
|
||||
// TestEndpoints tests that various services generate the correct endpoints.
|
||||
func TestEndpoints(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
namespace string
|
||||
name string
|
||||
annotations map[string]string
|
||||
lbs []string
|
||||
expected []endpoint.Endpoint
|
||||
}{
|
||||
// Completely opted-out: no endpoints returned.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{},
|
||||
[]string{"1.2.3.4"},
|
||||
[]endpoint.Endpoint{},
|
||||
},
|
||||
// Opt-in by setting desired hostname.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{"1.2.3.4"},
|
||||
[]endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
},
|
||||
},
|
||||
// Opt-in by setting desired hostname and this controller.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/controller": "dns-controller",
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{"1.2.3.4"},
|
||||
[]endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
},
|
||||
},
|
||||
// Opt-out by setting a different controller.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/controller": "some-other-tool",
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{"1.2.3.4"},
|
||||
[]endpoint.Endpoint{},
|
||||
},
|
||||
// Make sure services are found in all namespaces.
|
||||
{
|
||||
"other-testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{"1.2.3.4"},
|
||||
[]endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
},
|
||||
},
|
||||
// No external entrypoints lead to no endpoints.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{},
|
||||
[]endpoint.Endpoint{},
|
||||
},
|
||||
// Multiple external entrypoints lead to multiple endpoints.
|
||||
{
|
||||
"testing",
|
||||
"foo",
|
||||
map[string]string{
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
[]string{"1.2.3.4", "8.8.8.8"},
|
||||
[]endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", Target: "1.2.3.4"},
|
||||
{DNSName: "foo.example.org", Target: "8.8.8.8"},
|
||||
},
|
||||
},
|
||||
} {
|
||||
// Create a Kubernetes testing client
|
||||
kubernetes := fake.NewSimpleClientset()
|
||||
|
||||
// Create a service to test against
|
||||
ingresses := []v1.LoadBalancerIngress{}
|
||||
for _, lb := range tc.lbs {
|
||||
ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
|
||||
}
|
||||
|
||||
service := &v1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: tc.namespace,
|
||||
Name: tc.name,
|
||||
Annotations: tc.annotations,
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: ingresses,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := kubernetes.Core().Services(service.Namespace).Create(service)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client := &ServiceSource{
|
||||
Client: kubernetes,
|
||||
}
|
||||
|
||||
endpoints, err := client.Endpoints()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Validate returned endpoints against desired endpoints.
|
||||
validateEndpoints(t, endpoints, tc.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEndpoints(b *testing.B) {
|
||||
kubernetes := fake.NewSimpleClientset()
|
||||
|
||||
service := &v1.Service{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "testing",
|
||||
Name: "foo",
|
||||
Annotations: map[string]string{
|
||||
"external-dns.kubernetes.io/hostname": "foo.example.org",
|
||||
},
|
||||
},
|
||||
Status: v1.ServiceStatus{
|
||||
LoadBalancer: v1.LoadBalancerStatus{
|
||||
Ingress: []v1.LoadBalancerIngress{
|
||||
{IP: "1.2.3.4"},
|
||||
{IP: "8.8.8.8"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := kubernetes.Core().Services(service.Namespace).Create(service)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
client := &ServiceSource{
|
||||
Client: kubernetes,
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := client.Endpoints()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test helper functions
|
||||
|
||||
func validateEndpoints(t *testing.T, endpoints, expected []endpoint.Endpoint) {
|
||||
if len(endpoints) != len(expected) {
|
||||
t.Fatalf("expected %d endpoints, got %d", len(expected), len(endpoints))
|
||||
}
|
||||
|
||||
for i := range endpoints {
|
||||
validateEndpoint(t, endpoints[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
func validateEndpoint(t *testing.T, endpoint, expected endpoint.Endpoint) {
|
||||
if endpoint.DNSName != expected.DNSName {
|
||||
t.Errorf("expected %s, got %s", expected.DNSName, endpoint.DNSName)
|
||||
}
|
||||
|
||||
if endpoint.Target != expected.Target {
|
||||
t.Errorf("expected %s, got %s", expected.Target, endpoint.Target)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user