Skip to content

Commit a8c674c

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

File tree

4 files changed

+250
-20
lines changed

4 files changed

+250
-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: 75 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,6 +160,14 @@ 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 {
@@ -228,6 +260,48 @@ func (gs *glooSource) proxyTargets(ctx context.Context, name string, namespace s
228260
return targets, nil
229261
}
230262

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

0 commit comments

Comments
 (0)