Skip to content

Commit da56738

Browse files
committed
feat: add support for ingress backed GlooEdge Gateway
1 parent 6e9d459 commit da56738

File tree

4 files changed

+269
-20
lines changed

4 files changed

+269
-20
lines changed

docs/annotations/annotations.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,14 @@ If the annotation is not present, use the domains from both the spec and annotat
150150

151151
## external-dns.alpha.kubernetes.io/ingress
152152

153-
This annotation allows ExternalDNS to work with Istio Gateways that don't have a public IP.
153+
This annotation allows ExternalDNS to work with Istio/GlooEdge Gateways that don't have a public IP.
154154

155-
It can be used to address a specific architectural pattern, when a Kubernetes Ingress directs all public traffic to the Istio Gateway:
155+
It can be used to address a specific architectural pattern, when a Kubernetes Ingress directs all public traffic to the Istio/GlooEdge Gateway:
156156

157157
- **The Challenge**: By default, ExternalDNS sources the public IP address for a DNS record from a Service of type LoadBalancer.
158-
However, in some service mesh setups, the Istio Gateway's Service is of type ClusterIP, with all public traffic routed to it via a separate Kubernetes Ingress object. This setup leaves the Gateway without a public IP that ExternalDNS can discover.
158+
However, in some setups, the Gateway's Service is of type ClusterIP, with all public traffic routed to it via a separate Kubernetes Ingress object. This setup leaves the Gateway without a public IP that ExternalDNS can discover.
159159

160-
- **The Solution**: The annotation on the Istio Gateway tells ExternalDNS to ignore the Gateway's Service IP. Instead, it directs ExternalDNS to a specified Ingress resource to find the target LoadBalancer IP address.
160+
- **The Solution**: The annotation on the Istio/GlooEdge Gateway tells ExternalDNS to ignore the Gateway's Service IP. Instead, it directs ExternalDNS to a specified Ingress resource to find the target LoadBalancer IP address.
161161

162162
## external-dns.alpha.kubernetes.io/internal-hostname
163163

docs/sources/gloo-proxy.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,52 @@ spec:
104104
- --registry=txt
105105
- --txt-owner-id=my-identifier
106106
```
107+
108+
## Gateway Annotation
109+
110+
To support setups where an Ingress resource is used provision an external LB you can add the following annotation to your Gateway
111+
112+
**Note:** The Ingress namespace can be omitted if its in the same namespace as the gateway
113+
114+
```bash
115+
$ cat <<EOF | kubectl apply -f -
116+
apiVersion: gloo.solo.io/v1
117+
kind: Proxy
118+
metadata:
119+
labels:
120+
created_by: gloo-gateway
121+
name: gateway-proxy
122+
namespace: gloo-system
123+
spec:
124+
listeners:
125+
- bindAddress: '::'
126+
metadataStatic:
127+
sources:
128+
- resourceKind: '*v1.Gateway'
129+
resourceRef:
130+
name: gateway-proxy
131+
namespace: gloo-system
132+
---
133+
apiVersion: gateway.solo.io/v1
134+
kind: Gateway
135+
metadata:
136+
annotations:
137+
external-dns.alpha.kubernetes.io/ingress: "$ingressNamespace/$ingressName"
138+
labels:
139+
app: gloo
140+
name: gateway-proxy
141+
namespace: gloo-system
142+
spec: {}
143+
---
144+
apiVersion: networking.k8s.io/v1
145+
kind: Ingress
146+
metadata:
147+
labels:
148+
gateway-proxy-id: gateway-proxy
149+
gloo: gateway-proxy
150+
name: gateway-proxy
151+
namespace: gloo-system
152+
spec:
153+
ingressClassName: alb
154+
EOF
155+
```

source/gloo_proxy.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ import (
3333
"sigs.k8s.io/external-dns/source/annotations"
3434
)
3535

36+
// GlooGatewayIngressSource is the annotation used to determine if the gateway is implemented by an Ingress object
37+
// instead of a standard LoadBalancer service type
38+
const GlooGatewayIngressSource = annotations.Ingress
39+
3640
var (
3741
proxyGVR = schema.GroupVersionResource{
3842
Group: "gloo.solo.io",
@@ -44,6 +48,11 @@ var (
4448
Version: "v1",
4549
Resource: "virtualservices",
4650
}
51+
gatewayGVR = schema.GroupVersionResource{
52+
Group: "gateway.solo.io",
53+
Version: "v1",
54+
Resource: "gateways",
55+
}
4756
)
4857

4958
// Basic redefinition of "Proxy" CRD : https://github.com/solo-io/gloo/blob/v1.4.6/projects/gloo/pkg/api/v1/proxy.pb.go
@@ -58,7 +67,22 @@ type proxySpec struct {
5867
}
5968

6069
type proxySpecListener struct {
61-
HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
70+
HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
71+
MetadataStatic proxyMetadataStatic `json:"metadataStatic,omitempty"`
72+
}
73+
74+
type proxyMetadataStatic struct {
75+
Source []proxyMetadataStaticSource `json:"sources,omitempty"`
76+
}
77+
78+
type proxyMetadataStaticSource struct {
79+
ResourceKind string `json:"resourceKind,omitempty"`
80+
ResourceRef proxyMetadataStaticSourceResourceRef `json:"resourceRef,omitempty"`
81+
}
82+
83+
type proxyMetadataStaticSourceResourceRef struct {
84+
Name string `json:"name,omitempty"`
85+
Namespace string `json:"namespace,omitempty"`
6286
}
6387

6488
type proxySpecHTTPListener struct {
@@ -136,12 +160,22 @@ func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
136160
log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name)
137161

138162
proxyTargets := annotations.TargetsFromTargetAnnotation(proxy.Metadata.Annotations)
163+
if len(proxyTargets) == 0 {
164+
proxyTargets, err = gs.targetsFromGatewayIngress(ctx, &proxy)
165+
if err != nil {
166+
log.Error(err)
167+
return nil, err
168+
}
169+
}
170+
139171
if len(proxyTargets) == 0 {
140172
proxyTargets, err = gs.proxyTargets(ctx, proxy.Metadata.Name, ns)
141173
if err != nil {
142174
return nil, err
143175
}
144176
}
177+
// Getting targets from gateway's ingress
178+
145179
log.Debugf("Gloo[%s]: Find %d target(s) (%+v)", proxy.Metadata.Name, len(proxyTargets), proxyTargets)
146180

147181
proxyEndpoints, err := gs.generateEndpointsFromProxy(ctx, &proxy, proxyTargets)
@@ -228,6 +262,48 @@ func (gs *glooSource) proxyTargets(ctx context.Context, name string, namespace s
228262
return targets, nil
229263
}
230264

265+
func (gs *glooSource) targetsFromGatewayIngress(ctx context.Context, proxy *proxy) (endpoint.Targets, error) {
266+
targets := make(endpoint.Targets, 0)
267+
268+
for _, listener := range proxy.Spec.Listeners {
269+
for _, source := range listener.MetadataStatic.Source {
270+
if source.ResourceKind != "*v1.Gateway" {
271+
log.Debugf("Unsupported listener source. Expecting '*v1.Gateway', got (%s)", source.ResourceKind)
272+
continue
273+
}
274+
gateway, err := gs.dynamicKubeClient.Resource(gatewayGVR).Namespace(source.ResourceRef.Namespace).Get(ctx, source.ResourceRef.Name, metav1.GetOptions{})
275+
if err != nil {
276+
return nil, err
277+
}
278+
ingressStr, ok := gateway.GetAnnotations()[GlooGatewayIngressSource]
279+
if ok && ingressStr != "" {
280+
namespace, name, err := ParseIngress(ingressStr)
281+
if err != nil {
282+
return nil, fmt.Errorf("failed to parse Ingress annotation on Gateway (%s/%s): %w", gateway.GetNamespace(), gateway.GetName(), err)
283+
}
284+
if namespace == "" {
285+
namespace = gateway.GetNamespace()
286+
}
287+
288+
ingress, err := gs.kubeClient.NetworkingV1().Ingresses(namespace).Get(ctx, name, metav1.GetOptions{})
289+
if err != nil {
290+
log.Error(err)
291+
return nil, err
292+
}
293+
294+
for _, lb := range ingress.Status.LoadBalancer.Ingress {
295+
if lb.IP != "" {
296+
targets = append(targets, lb.IP)
297+
} else if lb.Hostname != "" {
298+
targets = append(targets, lb.Hostname)
299+
}
300+
}
301+
}
302+
}
303+
}
304+
return targets, nil
305+
}
306+
231307
func sourceKind(kind string) *schema.GroupVersionResource {
232308
if kind == "*v1.VirtualService" {
233309
return &virtualServiceGVR

0 commit comments

Comments
 (0)