Skip to content

Commit f0bae36

Browse files
authored
Set Service address in Gateway Status (#1141)
Problem: NGF would only set the Gateway Status to the IP address of the NGF Pod. However, a Service will generally be the entrypoint, so we need to set that address in the status for application developers. Solution: Using a CLI argument to identify the Service that is linked to this NGF Pod, we will get the Service and set the Addresses accordingly. LoadBalancer Service will set to the ingress IP and/or Hostname. If no Service exists or there is an error, then the Pod IP is used. We also shouldn't deploy a Service for conformance tests now, since they could be run in an environment where NodePort doesn't work (like kind). These tests will use the Pod IP.
1 parent cd8ba03 commit f0bae36

24 files changed

+748
-80
lines changed

cmd/gateway/commands.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func createStaticModeCommand() *cobra.Command {
6969
const (
7070
gatewayFlag = "gateway"
7171
configFlag = "config"
72+
serviceFlag = "service"
7273
updateGCStatusFlag = "update-gatewayclass-status"
7374
metricsDisableFlag = "metrics-disable"
7475
metricsSecureFlag = "metrics-secure-serving"
@@ -86,6 +87,9 @@ func createStaticModeCommand() *cobra.Command {
8687
configName = stringValidatingValue{
8788
validator: validateResourceName,
8889
}
90+
serviceName = stringValidatingValue{
91+
validator: validateResourceName,
92+
}
8993
disableMetrics bool
9094
metricsSecure bool
9195
metricsListenPort = intValidatingValue{
@@ -150,10 +154,13 @@ func createStaticModeCommand() *cobra.Command {
150154
Logger: logger,
151155
AtomicLevel: atom,
152156
GatewayClassName: gatewayClassName.value,
153-
PodIP: podIP,
154-
Namespace: namespace,
155157
GatewayNsName: gwNsName,
156158
UpdateGatewayClassStatus: updateGCStatus,
159+
GatewayPodConfig: config.GatewayPodConfig{
160+
PodIP: podIP,
161+
ServiceName: serviceName.value,
162+
Namespace: namespace,
163+
},
157164
HealthConfig: config.HealthConfig{
158165
Enabled: !disableHealth,
159166
Port: healthListenPort.value,
@@ -196,6 +203,13 @@ func createStaticModeCommand() *cobra.Command {
196203
` Lives in the same Namespace as the controller.`,
197204
)
198205

206+
cmd.Flags().Var(
207+
&serviceName,
208+
serviceFlag,
209+
`The name of the Service that fronts this NGINX Gateway Fabric Pod.`+
210+
` Lives in the same Namespace as the controller.`,
211+
)
212+
199213
cmd.Flags().BoolVar(
200214
&updateGCStatus,
201215
updateGCStatusFlag,

cmd/gateway/commands_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ func TestStaticModeCmdFlagValidation(t *testing.T) {
117117
args: []string{
118118
"--gateway=nginx-gateway/nginx",
119119
"--config=nginx-gateway-config",
120+
"--service=nginx-gateway",
120121
"--update-gatewayclass-status=true",
121122
"--metrics-port=9114",
122123
"--metrics-disable",
@@ -166,6 +167,22 @@ func TestStaticModeCmdFlagValidation(t *testing.T) {
166167
wantErr: true,
167168
expectedErrPrefix: `invalid argument "!@#$" for "-c, --config" flag: invalid format`,
168169
},
170+
{
171+
name: "service is set to empty string",
172+
args: []string{
173+
"--service=",
174+
},
175+
wantErr: true,
176+
expectedErrPrefix: `invalid argument "" for "--service" flag: must be set`,
177+
},
178+
{
179+
name: "service is set to invalid string",
180+
args: []string{
181+
"--service=!@#$",
182+
},
183+
wantErr: true,
184+
expectedErrPrefix: `invalid argument "!@#$" for "--service" flag: invalid format`,
185+
},
169186
{
170187
name: "update-gatewayclass-status is set to empty string",
171188
args: []string{

conformance/Makefile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ TAG = latest
1010
PREFIX = conformance-test-runner
1111
NGF_MANIFEST=../deploy/manifests/nginx-gateway.yaml
1212
CRDS=../deploy/manifests/crds/
13-
SERVICE_MANIFEST=../deploy/manifests/service/nodeport.yaml
1413
STATIC_MANIFEST=provisioner/static-deployment.yaml
1514
PROVISIONER_MANIFEST=provisioner/provisioner.yaml
1615
NGINX_IMAGE=$(shell yq '.spec.template.spec.containers[1].image as $$nginx_ver | $$nginx_ver' $(STATIC_MANIFEST))
@@ -52,7 +51,6 @@ prepare-ngf-dependencies: update-ngf-manifest ## Install NGF dependencies on con
5251
kubectl wait --for=condition=available --timeout=60s deployment gateway-api-admission-server -n gateway-system
5352
kubectl apply -f $(CRDS)
5453
kubectl apply -f $(NGF_MANIFEST)
55-
kubectl apply -f $(SERVICE_MANIFEST)
5654

5755
.PHONY: deploy-updated-provisioner
5856
deploy-updated-provisioner: ## Update provisioner manifest and deploy to the configured kind cluster

conformance/provisioner/static-deployment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ spec:
2727
- --gateway-ctlr-name=gateway.nginx.org/nginx-gateway-controller
2828
- --gatewayclass=nginx
2929
- --config=nginx-gateway-config
30+
- --service=nginx-gateway
3031
- --metrics-disable
3132
- --health-port=8081
3233
- --leader-election-lock-name=nginx-gateway-leader-election

deploy/helm-chart/templates/deployment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ spec:
3030
- --gateway-ctlr-name={{ .Values.nginxGateway.gatewayControllerName }}
3131
- --gatewayclass={{ .Values.nginxGateway.gatewayClassName }}
3232
- --config={{ include "nginx-gateway.config-name" . }}
33+
- --service={{ include "nginx-gateway.fullname" . }}
3334
{{- if .Values.metrics.enable }}
3435
- --metrics-port={{ .Values.metrics.port }}
3536
{{- if .Values.metrics.secure }}

deploy/manifests/nginx-gateway.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ spec:
139139
- --gateway-ctlr-name=gateway.nginx.org/nginx-gateway-controller
140140
- --gatewayclass=nginx
141141
- --config=nginx-gateway-config
142+
- --service=nginx-gateway
142143
- --metrics-port=9113
143144
- --health-port=8081
144145
- --leader-election-lock-name=nginx-gateway-leader-election

docs/cli-help.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Flags:
2020
| `gatewayclass` | `string` | The name of the GatewayClass resource. Every NGINX Gateway Fabric must have a unique corresponding GatewayClass resource. |
2121
| `gateway` | `string` | The namespaced name of the Gateway resource to use. Must be of the form: `NAMESPACE/NAME`. If not specified, the control plane will process all Gateways for the configured GatewayClass. However, among them, it will choose the oldest resource by creation timestamp. If the timestamps are equal, it will choose the resource that appears first in alphabetical order by {namespace}/{name}. |
2222
| `config` | `string` | The name of the NginxGateway resource to be used for this controller's dynamic configuration. Lives in the same Namespace as the controller. |
23+
| `service` | `string` | The name of the Service that fronts this NGINX Gateway Fabric Pod. Lives in the same Namespace as the controller. |
2324
| `metrics-disable` | `bool` | Disable exposing metrics in the Prometheus format. (default false) |
2425
| `metrics-listen-port` | `int` | Sets the port where the Prometheus metrics are exposed. Format: `[1024 - 65535]` (default `9113`) |
2526
| `metrics-secure-serving` | `bool` | Configures if the metrics endpoint should be secured using https. Please note that this endpoint will be secured with a self-signed certificate. (default false) |

docs/gateway-api-compatibility.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Fields:
6868
> Support Levels:
6969
>
7070
> - Core: Supported.
71-
> - Extended: Not supported.
71+
> - Extended: Partially supported.
7272
> - Implementation-specific: Not supported.
7373
7474
NGINX Gateway Fabric supports only a single Gateway resource. The Gateway resource must reference NGINX Gateway
@@ -91,7 +91,7 @@ Fields:
9191
- `allowedRoutes` - supported.
9292
- `addresses` - not supported.
9393
- `status`
94-
- `addresses` - Pod IPAddress supported.
94+
- `addresses` - partially supported. LoadBalancer and Pod IP.
9595
- `conditions` - supported (Condition/Status/Reason):
9696
- `Accepted/True/Accepted`
9797
- `Accepted/True/ListenersNotValid`

docs/installation.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ page.
6464
## Expose NGINX Gateway Fabric
6565

6666
You can gain access to NGINX Gateway Fabric by creating a `NodePort` Service or a `LoadBalancer` Service.
67+
This Service must live in the same Namespace as the controller. The name of this Service is provided in
68+
the `--service` argument to the controller.
6769

6870
> Important
6971
>
@@ -72,6 +74,9 @@ You can gain access to NGINX Gateway Fabric by creating a `NodePort` Service or
7274
> configured for those ports. If you'd like to use different ports in your listeners,
7375
> update the manifests accordingly.
7476
77+
NGINX Gateway Fabric will use this Service to set the Addresses field in the Gateway Status resource. A LoadBalancer
78+
Service sets the status field to the IP address and/or Hostname. If no Service exists, the Pod IP address is used.
79+
7580
### Create a NodePort Service
7681

7782
Create a Service with type `NodePort`:

internal/framework/controller/predicate/service.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package predicate
22

33
import (
44
apiv1 "k8s.io/api/core/v1"
5+
"k8s.io/apimachinery/pkg/types"
56
"k8s.io/apimachinery/pkg/util/intstr"
7+
"sigs.k8s.io/controller-runtime/pkg/client"
68
"sigs.k8s.io/controller-runtime/pkg/event"
79
"sigs.k8s.io/controller-runtime/pkg/predicate"
810
)
@@ -63,3 +65,54 @@ func (ServicePortsChangedPredicate) Update(e event.UpdateEvent) bool {
6365

6466
return len(newPortSet) > 0
6567
}
68+
69+
// GatewayServicePredicate implements predicate functions for this Pod's Service.
70+
type GatewayServicePredicate struct {
71+
predicate.Funcs
72+
NSName types.NamespacedName
73+
}
74+
75+
// Update implements the default UpdateEvent filter for the Gateway Service.
76+
func (gsp GatewayServicePredicate) Update(e event.UpdateEvent) bool {
77+
if e.ObjectOld == nil {
78+
return false
79+
}
80+
if e.ObjectNew == nil {
81+
return false
82+
}
83+
84+
oldSvc, ok := e.ObjectOld.(*apiv1.Service)
85+
if !ok {
86+
return false
87+
}
88+
89+
newSvc, ok := e.ObjectNew.(*apiv1.Service)
90+
if !ok {
91+
return false
92+
}
93+
94+
if client.ObjectKeyFromObject(newSvc) != gsp.NSName {
95+
return false
96+
}
97+
98+
if oldSvc.Spec.Type != newSvc.Spec.Type {
99+
return true
100+
}
101+
102+
if newSvc.Spec.Type == apiv1.ServiceTypeLoadBalancer {
103+
oldIngress := oldSvc.Status.LoadBalancer.Ingress
104+
newIngress := newSvc.Status.LoadBalancer.Ingress
105+
106+
if len(oldIngress) != len(newIngress) {
107+
return true
108+
}
109+
110+
for i, ingress := range oldIngress {
111+
if ingress.IP != newIngress[i].IP || ingress.Hostname != newIngress[i].Hostname {
112+
return true
113+
}
114+
}
115+
}
116+
117+
return false
118+
}

0 commit comments

Comments
 (0)