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

Add istio-virtualservice source #1358

Closed
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ lint:
.PHONY: verify test

test:
go test -v -race $(shell go list ./... | grep -v /vendor/)
go test -v -race $(shell go list ./...)

# The build targets allow to build the binary and docker image
.PHONY: build build.docker build.mini
Expand Down
76 changes: 65 additions & 11 deletions docs/tutorials/istio.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Configuring ExternalDNS to use the Istio Gateway Source
This tutorial describes how to configure ExternalDNS to use the Istio Gateway source.
# Configuring ExternalDNS to use the Istio Gateway or VirtualService Source
This tutorial describes how to configure ExternalDNS to use the Istio Gateway or VirtualService source.
It is meant to supplement the other provider-specific setup tutorials.

**Note:** Using the Istio Gateway source requires Istio >=1.0.0.
Expand Down Expand Up @@ -32,7 +32,8 @@ spec:
args:
- --source=service
- --source=ingress
- --source=istio-gateway
- --source=istio-gateway # choose one
- --source=istio-virtualservice # or both
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
Expand Down Expand Up @@ -63,7 +64,8 @@ rules:
resources: ["nodes"]
verbs: ["list"]
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
# choose one or both; using the VirtualService source requires both:
resources: ["gateways", "virtualservices"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
Expand Down Expand Up @@ -101,7 +103,8 @@ spec:
args:
- --source=service
- --source=ingress
- --source=istio-gateway
- --source=istio-gateway # choose one
- --source=istio-virtualservice # or both
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
- --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
Expand Down Expand Up @@ -130,8 +133,7 @@ kubectl patch clusterrole external-dns --type='json' \
-p='[{"op": "add", "path": "/rules/4", "value": { "apiGroups": [ "networking.istio.io"], "resources": ["gateways"],"verbs": ["get", "watch", "list" ]} }]'
```

### Verify External DNS works (Gateway example)

### Verify External DNS works
Follow the [Istio ingress traffic tutorial](https://istio.io/docs/tasks/traffic-management/ingress/)
to deploy a sample service that will be exposed outside of the service mesh.
The following are relevant snippets from that tutorial.
Expand All @@ -147,13 +149,16 @@ Otherwise:
$ kubectl apply -f <(istioctl kube-inject -f https://raw.githubusercontent.com/istio/istio/release-1.0/samples/httpbin/httpbin.yaml)
```

#### Create an Istio Gateway:
#### Using a Gateway as a source

##### Create an Istio Gateway:
```bash
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
Expand All @@ -163,11 +168,11 @@ spec:
name: http
protocol: HTTP
hosts:
- "httpbin.example.com"
- "httpbin.example.com" # this is used by external-dns to extract DNS names
EOF
```

#### Configure routes for traffic entering via the Gateway:
##### Configure routes for traffic entering via the Gateway:
```bash
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
Expand All @@ -178,7 +183,56 @@ spec:
hosts:
- "httpbin.example.com"
gateways:
- httpbin-gateway
- istio-system/httpbin-gateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOF
```

#### Using a VirtualService as a source

##### Create an Istio Gateway:
```bash
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
EOF
```

##### Configure routes for traffic entering via the Gateway:
```bash
$ cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com" # this is used by external-dns to extract DNS names
gateways:
- istio-system/httpbin-gateway
http:
- match:
- uri:
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ func handleSigterm(stopChan chan struct{}) {
func serveMetrics(address string) {
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
_, _ = w.Write([]byte("OK"))
})

http.Handle("/metrics", promhttp.Handler())
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func (cfg *Config) ParseFlags(args []string) error {
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, cloudfoundry, contour-ingressroute, crd, empty, skipper-routegroup)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "cloudfoundry", "contour-ingressroute", "fake", "connector", "crd", "empty", "skipper-routegroup")
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, crd, empty, skipper-routegroup)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "fake", "connector", "crd", "empty", "skipper-routegroup")

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)
Expand Down
73 changes: 37 additions & 36 deletions source/gateway.go → source/istio_gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ import (
"sigs.k8s.io/external-dns/endpoint"
)

// gatewaySource is an implementation of Source for Istio Gateway objects.
// istioGatewaySource is an implementation of Source for Istio Gateway objects.
// The gateway implementation uses the spec.servers.hosts values for the hostnames.
// Use targetAnnotationKey to explicitly set Endpoint.
type gatewaySource struct {
type istioGatewaySource struct {
kubeClient kubernetes.Interface
istioClient istiomodel.ConfigStore
namespace string
Expand All @@ -52,7 +52,7 @@ type gatewaySource struct {
serviceInformer coreinformers.ServiceInformer
}

// NewIstioGatewaySource creates a new gatewaySource with the given config.
// NewIstioGatewaySource creates a new istioGatewaySource with the given config.
func NewIstioGatewaySource(
kubeClient kubernetes.Interface,
istioClient istiomodel.ConfigStore,
Expand Down Expand Up @@ -101,7 +101,7 @@ func NewIstioGatewaySource(
return nil, fmt.Errorf("failed to sync cache: %v", err)
}

return &gatewaySource{
return &istioGatewaySource{
kubeClient: kubeClient,
istioClient: istioClient,
namespace: namespace,
Expand All @@ -115,7 +115,7 @@ func NewIstioGatewaySource(

// Endpoints returns endpoint objects for each host-target combination that should be processed.
// Retrieves all gateway resources in the source's namespace(s).
func (sc *gatewaySource) Endpoints() ([]*endpoint.Endpoint, error) {
func (sc *istioGatewaySource) Endpoints() ([]*endpoint.Endpoint, error) {
configs, err := sc.istioClient.List(istiomodel.Gateway.Type, sc.namespace)
if err != nil {
return nil, err
Expand All @@ -126,7 +126,7 @@ func (sc *gatewaySource) Endpoints() ([]*endpoint.Endpoint, error) {
return nil, err
}

endpoints := []*endpoint.Endpoint{}
var endpoints []*endpoint.Endpoint

for _, config := range configs {
// Check controller annotation to see if we are responsible.
Expand Down Expand Up @@ -173,10 +173,10 @@ func (sc *gatewaySource) Endpoints() ([]*endpoint.Endpoint, error) {
return endpoints, nil
}

func (sc *gatewaySource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
func (sc *istioGatewaySource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
}

func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*endpoint.Endpoint, error) {
func (sc *istioGatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*endpoint.Endpoint, error) {
// Process the whole template string
var buf bytes.Buffer
err := sc.fqdnTemplate.Execute(&buf, config)
Expand All @@ -191,13 +191,9 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en
log.Warn(err)
}

targets := getTargetsFromTargetAnnotation(config.Annotations)

if len(targets) == 0 {
targets, err = sc.targetsFromGatewayConfig(config)
if err != nil {
return nil, err
}
targets, err := targetsFromGatewayConfig(config, sc.serviceInformer)
if err != nil {
return nil, err
}

providerSpecific, setIdentifier := getProviderSpecificAnnotations(config.Annotations)
Expand All @@ -213,7 +209,7 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en
}

// filterByAnnotations filters a list of configs by a given annotation selector.
func (sc *gatewaySource) filterByAnnotations(configs []istiomodel.Config) ([]istiomodel.Config, error) {
func (sc *istioGatewaySource) filterByAnnotations(configs []istiomodel.Config) ([]istiomodel.Config, error) {
labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
if err != nil {
return nil, err
Expand All @@ -228,7 +224,7 @@ func (sc *gatewaySource) filterByAnnotations(configs []istiomodel.Config) ([]ist
return configs, nil
}

filteredList := []istiomodel.Config{}
var filteredList []istiomodel.Config

for _, config := range configs {
// convert the annotations to an equivalent label selector
Expand All @@ -243,30 +239,39 @@ func (sc *gatewaySource) filterByAnnotations(configs []istiomodel.Config) ([]ist
return filteredList, nil
}

func (sc *gatewaySource) setResourceLabel(config istiomodel.Config, endpoints []*endpoint.Endpoint) {
func (sc *istioGatewaySource) setResourceLabel(config istiomodel.Config, endpoints []*endpoint.Endpoint) {
for _, ep := range endpoints {
ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("gateway/%s/%s", config.Namespace, config.Name)
}
}

func (sc *gatewaySource) targetsFromGatewayConfig(config *istiomodel.Config) (targets endpoint.Targets, err error) {
gateway := config.Spec.(*istionetworking.Gateway)
labelSelector, err := metav1.ParseToLabelSelector(labels.Set(gateway.Selector).String())
if err != nil {
return nil, err
func gatewaySelectorMatchesServiceSelector(gwSelector, svcSelector map[string]string) bool {
for k, v := range gwSelector {
if lbl, ok := svcSelector[k]; !ok || lbl != v {
return false
}
}
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
return nil, err
return true
}

func targetsFromGatewayConfig(config *istiomodel.Config, serviceInformer coreinformers.ServiceInformer) (targets endpoint.Targets, err error) {
gateway := config.Spec.(*istionetworking.Gateway)

targets = getTargetsFromTargetAnnotation(config.Annotations)
if len(targets) > 0 {
return
}

services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(selector)
services, err := serviceInformer.Lister().Services(config.Namespace).List(labels.Everything())
if err != nil {
log.Error(err)
return
}

for _, service := range services {
if !gatewaySelectorMatchesServiceSelector(gateway.Selector, service.Spec.Selector) {
continue
}

for _, lb := range service.Status.LoadBalancer.Ingress {
if lb.IP != "" {
targets = append(targets, lb.IP)
Expand All @@ -281,21 +286,17 @@ func (sc *gatewaySource) targetsFromGatewayConfig(config *istiomodel.Config) (ta
}

// endpointsFromGatewayConfig extracts the endpoints from an Istio Gateway Config object
func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([]*endpoint.Endpoint, error) {
func (sc *istioGatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint

ttl, err := getTTLFromAnnotations(config.Annotations)
if err != nil {
log.Warn(err)
}

targets := getTargetsFromTargetAnnotation(config.Annotations)

if len(targets) == 0 {
targets, err = sc.targetsFromGatewayConfig(&config)
if err != nil {
return nil, err
}
targets, err := targetsFromGatewayConfig(&config, sc.serviceInformer)
if err != nil {
return nil, err
}

gateway := config.Spec.(*istionetworking.Gateway)
Expand Down
Loading