diff --git a/apis/v1alpha2/nginxproxy_types.go b/apis/v1alpha2/nginxproxy_types.go index 43b509d06d..e923d4a7ca 100644 --- a/apis/v1alpha2/nginxproxy_types.go +++ b/apis/v1alpha2/nginxproxy_types.go @@ -479,6 +479,11 @@ type ContainerSpec struct { // +optional Lifecycle *corev1.Lifecycle `json:"lifecycle,omitempty"` + // HostPorts are the list of ports to expose on the host. + // + // +optional + HostPorts []HostPort `json:"hostPorts,omitempty"` + // VolumeMounts describe the mounting of Volumes within a container. // // +optional @@ -608,3 +613,16 @@ type NodePort struct { // kubebuilder:validation:Maximum=65535 ListenerPort int32 `json:"listenerPort"` } + +// HostPort exposes an nginx container port on the host. +type HostPort struct { + // Port to expose on the host. + // kubebuilder:validation:Minimum=1 + // kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` + + // ContainerPort is the port on the nginx container to map to the HostPort. + // kubebuilder:validation:Minimum=1 + // kubebuilder:validation:Maximum=65535 + ContainerPort int32 `json:"containerPort"` +} diff --git a/charts/nginx-gateway-fabric/README.md b/charts/nginx-gateway-fabric/README.md index c5149900f9..c84f34f98f 100644 --- a/charts/nginx-gateway-fabric/README.md +++ b/charts/nginx-gateway-fabric/README.md @@ -264,9 +264,10 @@ The following table lists the configurable parameters of the NGINX Gateway Fabri | `certGenerator.ttlSecondsAfterFinished` | How long to wait after the cert generator job has finished before it is removed by the job controller. | int | `30` | | `clusterDomain` | The DNS cluster domain of your Kubernetes cluster. | string | `"cluster.local"` | | `gateways` | A list of Gateway objects. View https://gateway-api.sigs.k8s.io/reference/spec/#gateway for full Gateway reference. | list | `[]` | -| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","resolver":"","secretName":"nplus-license","skipVerify":false}}` | +| `nginx` | The nginx section contains the configuration for all NGINX data plane deployments installed by the NGINX Gateway Fabric control plane. | object | `{"config":{},"container":{"hostPorts":[]},"debug":false,"image":{"pullPolicy":"Always","repository":"ghcr.io/nginx/nginx-gateway-fabric/nginx","tag":"edge"},"imagePullSecret":"","imagePullSecrets":[],"kind":"deployment","plus":false,"pod":{},"replicas":1,"service":{"externalTrafficPolicy":"Local","loadBalancerClass":"","loadBalancerIP":"","loadBalancerSourceRanges":[],"nodePorts":[],"type":"LoadBalancer"},"usage":{"caSecretName":"","clientSSLSecretName":"","endpoint":"","resolver":"","secretName":"nplus-license","skipVerify":false}}` | | `nginx.config` | The configuration for the data plane that is contained in the NginxProxy resource. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{}` | -| `nginx.container` | The container configuration for the NGINX container. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{}` | +| `nginx.container` | The container configuration for the NGINX container. This is applied globally to all Gateways managed by this instance of NGINX Gateway Fabric. | object | `{"hostPorts":[]}` | +| `nginx.container.hostPorts` | A list of HostPorts to expose on the host. This configuration allows containers to bind to a specific port on the host node, enabling external network traffic to reach the container directly through the host's IP address and port. Use this option when you need to expose container ports on the host for direct access, such as for debugging, legacy integrations, or when NodePort/LoadBalancer services are not suitable. Note: Using hostPort may have security and scheduling implications, as it ties pods to specific nodes and ports. | list | `[]` | | `nginx.debug` | Enable debugging for NGINX. Uses the nginx-debug binary. The NGINX error log level should be set to debug in the NginxProxy resource. | bool | `false` | | `nginx.image.repository` | The NGINX image to use. | string | `"ghcr.io/nginx/nginx-gateway-fabric/nginx"` | | `nginx.imagePullSecret` | The name of the secret containing docker registry credentials. Secret must exist in the same namespace as the helm release. The control plane will copy this secret into any namespace where NGINX is deployed. | string | `""` | diff --git a/charts/nginx-gateway-fabric/templates/_helpers.tpl b/charts/nginx-gateway-fabric/templates/_helpers.tpl index 01155eb707..bedf7bc8a1 100644 --- a/charts/nginx-gateway-fabric/templates/_helpers.tpl +++ b/charts/nginx-gateway-fabric/templates/_helpers.tpl @@ -102,5 +102,7 @@ Filters out empty fields from a struct. {{- $result = merge $result (dict $key $value) }} {{- end }} {{- end }} -{{- $result | toYaml }} +{{- if $result }} + {{- $result | toYaml }} +{{- end }} {{- end }} diff --git a/charts/nginx-gateway-fabric/templates/nginxproxy.yaml b/charts/nginx-gateway-fabric/templates/nginxproxy.yaml index b5e33292c8..45d8a8763a 100644 --- a/charts/nginx-gateway-fabric/templates/nginxproxy.yaml +++ b/charts/nginx-gateway-fabric/templates/nginxproxy.yaml @@ -18,8 +18,8 @@ spec: {{- toYaml .Values.nginx.pod | nindent 8 }} {{- end }} container: - {{- if .Values.nginx.container }} - {{- toYaml .Values.nginx.container | nindent 8 }} + {{- with .Values.nginx.container }} + {{- include "filterEmptyFields" . | nindent 8 }} {{- end }} image: {{- toYaml .Values.nginx.image | nindent 10 }} @@ -34,8 +34,8 @@ spec: {{- toYaml .Values.nginx.pod | nindent 8 }} {{- end }} container: - {{- if .Values.nginx.container }} - {{- toYaml .Values.nginx.container | nindent 8 }} + {{- with .Values.nginx.container }} + {{- include "filterEmptyFields" . | nindent 8 }} {{- end }} image: {{- toYaml .Values.nginx.image | nindent 10 }} diff --git a/charts/nginx-gateway-fabric/values.schema.json b/charts/nginx-gateway-fabric/values.schema.json index 5dbb8c0e37..f19c0b0f9a 100644 --- a/charts/nginx-gateway-fabric/values.schema.json +++ b/charts/nginx-gateway-fabric/values.schema.json @@ -313,6 +313,32 @@ }, "container": { "description": "The container configuration for the NGINX container. This is applied globally to all Gateways managed by this\ninstance of NGINX Gateway Fabric.", + "properties": { + "hostPorts": { + "description": "A list of HostPorts to expose on the host.\nThis configuration allows containers to bind to a specific port on the host node,\nenabling external network traffic to reach the container directly through the host's IP address and port.\nUse this option when you need to expose container ports on the host for direct access,\nsuch as for debugging, legacy integrations, or when NodePort/LoadBalancer services are not suitable.\nNote: Using hostPort may have security and scheduling implications, as it ties pods to specific nodes and ports.", + "items": { + "properties": { + "containerPort": { + "maximum": 65535, + "minimum": 1, + "required": [], + "type": "integer" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "required": [], + "type": "integer" + } + }, + "required": [], + "type": "object" + }, + "required": [], + "title": "hostPorts", + "type": "array" + } + }, "required": [], "title": "container", "type": "object" diff --git a/charts/nginx-gateway-fabric/values.yaml b/charts/nginx-gateway-fabric/values.yaml index 39bdc19ace..daf3b9e33c 100644 --- a/charts/nginx-gateway-fabric/values.yaml +++ b/charts/nginx-gateway-fabric/values.yaml @@ -399,7 +399,33 @@ nginx: # -- The container configuration for the NGINX container. This is applied globally to all Gateways managed by this # instance of NGINX Gateway Fabric. - container: {} + container: + # @schema + # type: array + # items: + # type: object + # properties: + # port: + # type: integer + # required: true + # minimum: 1 + # maximum: 65535 + # containerPort: + # type: integer + # required: true + # minimum: 1 + # maximum: 65535 + # @schema + # -- A list of HostPorts to expose on the host. + # This configuration allows containers to bind to a specific port on the host node, + # enabling external network traffic to reach the container directly through the host's IP address and port. + # Use this option when you need to expose container ports on the host for direct access, + # such as for debugging, legacy integrations, or when NodePort/LoadBalancer services are not suitable. + # Note: Using hostPort may have security and scheduling implications, as it ties pods to specific nodes and ports. + hostPorts: [] + # - port: 80 + # containerPort: 80 + # -- The resource requirements of the NGINX container. # resources: {} diff --git a/config/crd/bases/gateway.nginx.org_nginxproxies.yaml b/config/crd/bases/gateway.nginx.org_nginxproxies.yaml index 9947e34eb7..122c038d6a 100644 --- a/config/crd/bases/gateway.nginx.org_nginxproxies.yaml +++ b/config/crd/bases/gateway.nginx.org_nginxproxies.yaml @@ -337,6 +337,24 @@ spec: StopSignal can only be set for Pods with a non-empty .spec.os.name type: string type: object + hostPorts: + description: |- + List of ports to expose on the host. + items: + properties: + port: + description: |- + Number of port to expose on the host. + type: integer + containerPort: + description: |- + ContainerPort is the port on the nginx container to map to the HostPort. + type: integer + required: + - port + - containerPort + type: object + type: array resources: description: Resources describes the compute resource requirements. @@ -3723,6 +3741,24 @@ spec: StopSignal can only be set for Pods with a non-empty .spec.os.name type: string type: object + hostPorts: + description: |- + List of ports to expose on the host. + items: + properties: + port: + description: |- + Number of port to expose on the host. + type: integer + containerPort: + description: |- + ContainerPort is the port on the nginx container to map to the HostPort. + type: integer + required: + - port + - containerPort + type: object + type: array resources: description: Resources describes the compute resource requirements. diff --git a/deploy/crds.yaml b/deploy/crds.yaml index 7517ce1c4a..d6c18bb867 100644 --- a/deploy/crds.yaml +++ b/deploy/crds.yaml @@ -922,6 +922,24 @@ spec: StopSignal can only be set for Pods with a non-empty .spec.os.name type: string type: object + hostPorts: + description: |- + List of ports to expose on the host. + items: + properties: + port: + description: |- + Number of port to expose on the host. + type: integer + containerPort: + description: |- + ContainerPort is the port on the nginx container to map to the HostPort. + type: integer + required: + - port + - containerPort + type: object + type: array resources: description: Resources describes the compute resource requirements. @@ -4308,6 +4326,24 @@ spec: StopSignal can only be set for Pods with a non-empty .spec.os.name type: string type: object + hostPorts: + description: |- + List of ports to expose on the host. + items: + properties: + port: + description: |- + Number of port to expose on the host. + type: integer + containerPort: + description: |- + ContainerPort is the port on the nginx container to map to the HostPort. + type: integer + required: + - port + - containerPort + type: object + type: array resources: description: Resources describes the compute resource requirements. diff --git a/internal/controller/provisioner/objects.go b/internal/controller/provisioner/objects.go index 3b43ac8b66..f9140ef48f 100644 --- a/internal/controller/provisioner/objects.go +++ b/internal/controller/provisioner/objects.go @@ -780,6 +780,15 @@ func (p *NginxProvisioner) buildNginxPodTemplateSpec( container.Command = append(container.Command, "/agent/entrypoint.sh") container.Args = append(container.Args, "debug") } + + for _, hostPort := range containerSpec.HostPorts { + for i, port := range container.Ports { + if hostPort.ContainerPort == port.ContainerPort { + container.Ports[i].HostPort = hostPort.Port + } + } + } + spec.Spec.Containers[0] = container } } diff --git a/internal/controller/provisioner/objects_test.go b/internal/controller/provisioner/objects_test.go index 96710f8902..632b5c437c 100644 --- a/internal/controller/provisioner/objects_test.go +++ b/internal/controller/provisioner/objects_test.go @@ -257,6 +257,11 @@ func TestBuildNginxResourceObjects_NginxProxyConfig(t *testing.T) { Name: "gw", Namespace: "default", }, + Spec: gatewayv1.GatewaySpec{ + Listeners: []gatewayv1.Listener{ + {Name: "port-8443", Port: 8443, Protocol: "tcp"}, + }, + }, } resourceName := "gw-nginx" @@ -293,6 +298,7 @@ func TestBuildNginxResourceObjects_NginxProxyConfig(t *testing.T) { corev1.ResourceCPU: resource.Quantity{Format: "100m"}, }, }, + HostPorts: []ngfAPIv1alpha2.HostPort{{ContainerPort: int32(8443), Port: int32(8443)}}, }, }, }, @@ -344,6 +350,12 @@ func TestBuildNginxResourceObjects_NginxProxyConfig(t *testing.T) { g.Expect(container.ImagePullPolicy).To(Equal(corev1.PullAlways)) g.Expect(container.Resources.Limits).To(HaveKey(corev1.ResourceCPU)) g.Expect(container.Resources.Limits[corev1.ResourceCPU].Format).To(Equal(resource.Format("100m"))) + + g.Expect(container.Ports).To(ContainElement(corev1.ContainerPort{ + ContainerPort: 8443, + Name: "port-8443", + HostPort: 8443, + })) } func TestBuildNginxResourceObjects_Plus(t *testing.T) {