Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add gloo proxy source #1693

Merged
merged 1 commit into from
Mar 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions docs/tutorials/gloo-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Configuring ExternalDNS to use the Gloo Proxy Source
This tutorial describes how to configure ExternalDNS to use the Gloo Proxy source.
It is meant to supplement the other provider-specific setup tutorials.

### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)
- --provider=aws
- --registry=txt
- --txt-owner-id=my-identifier
```

### Manifest (for clusters with RBAC enabled)
Could be change if you have mulitple sources

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: external-dns
rules:
Hugome marked this conversation as resolved.
Show resolved Hide resolved
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: ["gloo.solo.io"]
resources: ["proxies"]
verbs: ["get","watch","list"]
- apiGroups: ["gateway.solo.io"]
resources: ["virtualservices"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
# update this to the desired external-dns version
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)
- --provider=aws
- --registry=txt
- --txt-owner-id=my-identifier
```

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func main() {
CFUsername: cfg.CFUsername,
CFPassword: cfg.CFPassword,
ContourLoadBalancerService: cfg.ContourLoadBalancerService,
GlooNamespace: cfg.GlooNamespace,
SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion,
RequestTimeout: cfg.RequestTimeout,
}
Expand Down
8 changes: 6 additions & 2 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Config struct {
KubeConfig string
RequestTimeout time.Duration
ContourLoadBalancerService string
GlooNamespace string
SkipperRouteGroupVersion string
Sources []string
Namespace string
Expand Down Expand Up @@ -167,6 +168,7 @@ var defaultConfig = &Config{
KubeConfig: "",
RequestTimeout: time.Second * 30,
ContourLoadBalancerService: "heptio-contour/contour",
GlooNamespace: "gloo-system",
SkipperRouteGroupVersion: "zalando.org/v1",
Sources: nil,
Namespace: "",
Expand Down Expand Up @@ -328,12 +330,14 @@ func (cfg *Config) ParseFlags(args []string) error {
// Flags related to Contour
app.Flag("contour-load-balancer", "The fully-qualified name of the Contour load balancer service. (default: heptio-contour/contour)").Default("heptio-contour/contour").StringVar(&cfg.ContourLoadBalancerService)

// Flags related to Gloo
app.Flag("gloo-namespace", "Gloo namespace. (default: gloo-system)").Default("gloo-system").StringVar(&cfg.GlooNamespace)

// Flags related to Skipper RouteGroup
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)

// Flags related to processing sources
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host")

app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host")
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently only supported by source CRD").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter)
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
KubeConfig: "",
RequestTimeout: time.Second * 30,
ContourLoadBalancerService: "heptio-contour/contour",
GlooNamespace: "gloo-system",
SkipperRouteGroupVersion: "zalando.org/v1",
Sources: []string{"service"},
Namespace: "",
Expand Down Expand Up @@ -114,6 +115,7 @@ var (
KubeConfig: "/some/path",
RequestTimeout: time.Second * 77,
ContourLoadBalancerService: "heptio-contour-other/contour-other",
GlooNamespace: "gloo-not-system",
SkipperRouteGroupVersion: "zalando.org/v2",
Sources: []string{"service", "ingress", "connector"},
Namespace: "namespace",
Expand Down Expand Up @@ -220,6 +222,7 @@ func TestParseFlags(t *testing.T) {
"--kubeconfig=/some/path",
"--request-timeout=77s",
"--contour-load-balancer=heptio-contour-other/contour-other",
"--gloo-namespace=gloo-not-system",
"--skipper-routegroup-groupversion=zalando.org/v2",
"--source=service",
"--source=ingress",
Expand Down Expand Up @@ -317,6 +320,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_KUBECONFIG": "/some/path",
"EXTERNAL_DNS_REQUEST_TIMEOUT": "77s",
"EXTERNAL_DNS_CONTOUR_LOAD_BALANCER": "heptio-contour-other/contour-other",
"EXTERNAL_DNS_GLOO_NAMESPACE": "gloo-not-system",
"EXTERNAL_DNS_SKIPPER_ROUTEGROUP_GROUPVERSION": "zalando.org/v2",
"EXTERNAL_DNS_SOURCE": "service\ningress\nconnector",
"EXTERNAL_DNS_NAMESPACE": "namespace",
Expand Down
200 changes: 200 additions & 0 deletions source/gloo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
Copyright 2020n The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package source

import (
"context"
"encoding/json"
"strings"

log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"

"sigs.k8s.io/external-dns/endpoint"
)

var (
proxyGVR = schema.GroupVersionResource{
Group: "gloo.solo.io",
Version: "v1",
Resource: "proxies",
}
virtualServiceGVR = schema.GroupVersionResource{
Group: "gateway.solo.io",
Version: "v1",
Resource: "virtualservices",
}
)

// Basic redefinition of "Proxy" CRD : https://github.com/solo-io/gloo/blob/v1.4.6/projects/gloo/pkg/api/v1/proxy.pb.go
type proxy struct {
metav1.TypeMeta `json:",inline"`
Metadata metav1.ObjectMeta `json:"metadata,omitempty"`
Spec proxySpec `json:"spec,omitempty"`
}

type proxySpec struct {
Listeners []proxySpecListener `json:"listeners,omitempty"`
}

type proxySpecListener struct {
HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
}

type proxySpecHTTPListener struct {
VirtualHosts []proxyVirtualHost `json:"virtualHosts,omitempty"`
}

type proxyVirtualHost struct {
Domains []string `json:"domains,omitempty"`
Metadata proxyVirtualHostMetadata `json:"metadata,omitempty"`
}

type proxyVirtualHostMetadata struct {
Source []proxyVirtualHostMetadataSource `json:"sources,omitempty"`
}

type proxyVirtualHostMetadataSource struct {
Kind string `json:"kind,omitempty"`
Name string `json:"name,omitempty"`
Namespace string `json:"namespace,omitempty"`
}

type glooSource struct {
dynamicKubeClient dynamic.Interface
kubeClient kubernetes.Interface
glooNamespace string
}

// NewGlooSource creates a new glooSource with the given config
func NewGlooSource(dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, glooNamespace string) (Source, error) {
return &glooSource{
dynamicKubeClient,
kubeClient,
glooNamespace,
}, nil
}

func (gs *glooSource) AddEventHandler(ctx context.Context, handler func()) {
}

// Endpoints returns endpoint objects
func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
endpoints := []*endpoint.Endpoint{}

proxies, err := gs.dynamicKubeClient.Resource(proxyGVR).Namespace(gs.glooNamespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
for _, obj := range proxies.Items {
proxy := proxy{}
jsonString, err := obj.MarshalJSON()
if err != nil {
return nil, err
}
err = json.Unmarshal(jsonString, &proxy)
if err != nil {
return nil, err
}
log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name)
proxyTargets, err := gs.proxyTargets(ctx, proxy.Metadata.Name)
if err != nil {
return nil, err
}
log.Debugf("Gloo[%s]: Find %d target(s) (%+v)", proxy.Metadata.Name, len(proxyTargets), proxyTargets)
proxyEndpoints, err := gs.generateEndpointsFromProxy(ctx, &proxy, proxyTargets)
if err != nil {
return nil, err
}
log.Debugf("Gloo[%s]: Generate %d endpoint(s)", proxy.Metadata.Name, len(proxyEndpoints))
endpoints = append(endpoints, proxyEndpoints...)
}
return endpoints, nil
}

func (gs *glooSource) generateEndpointsFromProxy(ctx context.Context, proxy *proxy, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
endpoints := []*endpoint.Endpoint{}
for _, listener := range proxy.Spec.Listeners {
for _, virtualHost := range listener.HTTPListener.VirtualHosts {
annotations, err := gs.annotationsFromProxySource(ctx, virtualHost)
if err != nil {
return nil, err
}
ttl, err := getTTLFromAnnotations(annotations)
if err != nil {
return nil, err
}
providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations)
for _, domain := range virtualHost.Domains {
endpoints = append(endpoints, endpointsForHostname(strings.TrimSuffix(domain, "."), targets, ttl, providerSpecific, setIdentifier)...)
}
}
}
return endpoints, nil
}

func (gs *glooSource) annotationsFromProxySource(ctx context.Context, virtualHost proxyVirtualHost) (map[string]string, error) {
annotations := map[string]string{}
for _, src := range virtualHost.Metadata.Source {
kind := sourceKind(src.Kind)
if kind != nil {
source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.Namespace).Get(ctx, src.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
for key, value := range source.GetAnnotations() {
annotations[key] = value
}
}
}
return annotations, nil
}

func (gs *glooSource) proxyTargets(ctx context.Context, name string) (endpoint.Targets, error) {
svc, err := gs.kubeClient.CoreV1().Services(gs.glooNamespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}

var targets endpoint.Targets
switch svc.Spec.Type {
case corev1.ServiceTypeLoadBalancer:
for _, lb := range svc.Status.LoadBalancer.Ingress {
if lb.IP != "" {
targets = append(targets, lb.IP)
}
if lb.Hostname != "" {
targets = append(targets, lb.Hostname)
}
}
default:
log.WithField("gateway", name).WithField("service", svc).Warn("Gloo: Proxy service type not supported")
}
return targets, nil
}

func sourceKind(kind string) *schema.GroupVersionResource {
switch kind {
case "*v1.VirtualService":
Hugome marked this conversation as resolved.
Show resolved Hide resolved
return &virtualServiceGVR
}
return nil
}
Loading