From 110df756b36b39bbaed17d96420551ba7deb6e98 Mon Sep 17 00:00:00 2001 From: Jon Torre <78599298+Jcahilltorre@users.noreply.github.com> Date: Wed, 29 Nov 2023 16:11:54 +0000 Subject: [PATCH 1/5] Migrate docs to docs.nginx.com (#1226) Change docs structure and add the required files to build documentation Hugo and publish with Netlify Rewrite existing docs to work with Hugo --------- Co-authored-by: Travis Martin Co-authored-by: Travis Martin <33876974+travisamartin@users.noreply.github.com> Co-authored-by: Kate Osborn <50597707+kate-osborn@users.noreply.github.com> Co-authored-by: Alan Dooley Co-authored-by: Alan Dooley --- .markdownlint-cli2.yaml | 5 +- README.md | 16 +- docs/README.md | 23 +- docs/architecture.md | 160 ------- docs/cli-help.md | 49 --- docs/developer/documentation.md | 165 +++++++ docs/developer/implementing-a-feature.md | 9 +- docs/guides/README.md | 14 - docs/guides/routing-traffic-to-your-app.md | 405 ------------------ docs/guides/upgrade-apps-without-downtime.md | 173 -------- docs/images/route-all-traffic-config.png | Bin 50370 -> 0 bytes docs/images/route-all-traffic-flow.png | Bin 58438 -> 0 bytes docs/installation.md | 297 ------------- docs/monitoring.md | 106 ----- docs/troubleshooting.md | 11 - site/.gitignore | 3 + site/Makefile | 92 ++++ site/config/_default/config.toml | 68 +++ site/config/development/config.toml | 3 + site/config/production/config.toml | 3 + site/config/staging/config.toml | 3 + site/content/_index.md | 7 + site/content/changelog.md | 8 + site/content/how-to/_index.md | 9 + site/content/how-to/configuration/_index.md | 9 + .../control-plane-configuration.md | 14 +- site/content/how-to/maintenance/_index.md | 9 + .../upgrade-apps-without-downtime.md | 125 ++++++ site/content/how-to/monitoring/_index.md | 9 + site/content/how-to/monitoring/monitoring.md | 117 +++++ .../how-to/monitoring/troubleshooting.md | 23 + .../how-to/traffic-management/_index.md | 9 + .../traffic-management}/advanced-routing.md | 84 ++-- .../integrating-cert-manager.md | 149 +++---- .../routing-traffic-to-your-app.md | 371 ++++++++++++++++ site/content/includes/index.md | 3 + .../delay-pod-termination-overview.md | 9 + .../termination-grace-period.md | 9 + .../installation/helm/pulling-the-chart.md | 12 + .../helm/uninstall-gateway-api-resources.md | 18 + .../installation/next-step-expose-fabric.md | 5 + site/content/installation/_index.md | 9 + .../installation}/building-the-images.md | 13 +- .../expose-nginx-gateway-fabric.md | 71 +++ .../installation/installing-ngf/_index.md | 9 + .../installation/installing-ngf/helm.md | 184 ++++++++ .../installation/installing-ngf/manifests.md | 188 ++++++++ .../content/installation}/running-on-kind.md | 12 +- site/content/overview/_index.md | 9 + .../overview}/gateway-api-compatibility.md | 12 +- site/content/overview/gateway-architecture.md | 97 +++++ .../content/overview}/resource-validation.md | 10 +- site/content/reference/_index.md | 9 + site/content/reference/cli-help.md | 53 +++ .../reference/technical-specifications.md | 13 + site/content/releases.md | 13 + site/go.mod | 5 + site/go.sum | 6 + site/layouts/shortcodes/call-out.html | 3 + site/layouts/shortcodes/custom-styles.html | 43 ++ site/md-linkcheck-config.json | 13 + site/mdlint_conf.json | 19 + site/netlify.toml | 34 ++ .../static/img}/advanced-routing.png | Bin .../img}/cert-manager-gateway-workflow.png | Bin .../static/img}/ngf-high-level.png | Bin {docs/images => site/static/img}/ngf-pod.png | Bin .../static/img}/route-all-traffic-app.png | Bin site/static/img/route-all-traffic-config.png | Bin 0 -> 40397 bytes site/static/img/route-all-traffic-flow.png | Bin 0 -> 47364 bytes .../images => site/static/img}/src/README.md | 0 .../static/img}/src/advanced-routing.mermaid | 0 .../img}/src/route-all-traffic-app.mermaid | 0 .../img}/src/route-all-traffic-config.mermaid | 0 .../img}/src/route-all-traffic-flow.mermaid | 0 75 files changed, 2020 insertions(+), 1409 deletions(-) delete mode 100644 docs/architecture.md delete mode 100644 docs/cli-help.md create mode 100644 docs/developer/documentation.md delete mode 100644 docs/guides/README.md delete mode 100644 docs/guides/routing-traffic-to-your-app.md delete mode 100644 docs/guides/upgrade-apps-without-downtime.md delete mode 100644 docs/images/route-all-traffic-config.png delete mode 100644 docs/images/route-all-traffic-flow.png delete mode 100644 docs/installation.md delete mode 100644 docs/monitoring.md delete mode 100644 docs/troubleshooting.md create mode 100644 site/.gitignore create mode 100644 site/Makefile create mode 100644 site/config/_default/config.toml create mode 100644 site/config/development/config.toml create mode 100644 site/config/production/config.toml create mode 100644 site/config/staging/config.toml create mode 100644 site/content/_index.md create mode 100644 site/content/changelog.md create mode 100644 site/content/how-to/_index.md create mode 100644 site/content/how-to/configuration/_index.md rename {docs => site/content/how-to/configuration}/control-plane-configuration.md (87%) create mode 100644 site/content/how-to/maintenance/_index.md create mode 100644 site/content/how-to/maintenance/upgrade-apps-without-downtime.md create mode 100644 site/content/how-to/monitoring/_index.md create mode 100644 site/content/how-to/monitoring/monitoring.md create mode 100644 site/content/how-to/monitoring/troubleshooting.md create mode 100644 site/content/how-to/traffic-management/_index.md rename {docs/guides => site/content/how-to/traffic-management}/advanced-routing.md (69%) rename {docs/guides => site/content/how-to/traffic-management}/integrating-cert-manager.md (50%) create mode 100644 site/content/how-to/traffic-management/routing-traffic-to-your-app.md create mode 100644 site/content/includes/index.md create mode 100644 site/content/includes/installation/delay-pod-termination/delay-pod-termination-overview.md create mode 100644 site/content/includes/installation/delay-pod-termination/termination-grace-period.md create mode 100644 site/content/includes/installation/helm/pulling-the-chart.md create mode 100644 site/content/includes/installation/helm/uninstall-gateway-api-resources.md create mode 100644 site/content/includes/installation/next-step-expose-fabric.md create mode 100644 site/content/installation/_index.md rename {docs => site/content/installation}/building-the-images.md (70%) create mode 100644 site/content/installation/expose-nginx-gateway-fabric.md create mode 100644 site/content/installation/installing-ngf/_index.md create mode 100644 site/content/installation/installing-ngf/helm.md create mode 100644 site/content/installation/installing-ngf/manifests.md rename {docs => site/content/installation}/running-on-kind.md (76%) create mode 100644 site/content/overview/_index.md rename {docs => site/content/overview}/gateway-api-compatibility.md (97%) create mode 100644 site/content/overview/gateway-architecture.md rename {docs => site/content/overview}/resource-validation.md (97%) create mode 100644 site/content/reference/_index.md create mode 100644 site/content/reference/cli-help.md create mode 100644 site/content/reference/technical-specifications.md create mode 100644 site/content/releases.md create mode 100644 site/go.mod create mode 100644 site/go.sum create mode 100644 site/layouts/shortcodes/call-out.html create mode 100644 site/layouts/shortcodes/custom-styles.html create mode 100644 site/md-linkcheck-config.json create mode 100644 site/mdlint_conf.json create mode 100644 site/netlify.toml rename {docs/images => site/static/img}/advanced-routing.png (100%) rename {docs/images => site/static/img}/cert-manager-gateway-workflow.png (100%) rename {docs/images => site/static/img}/ngf-high-level.png (100%) rename {docs/images => site/static/img}/ngf-pod.png (100%) rename {docs/images => site/static/img}/route-all-traffic-app.png (100%) create mode 100644 site/static/img/route-all-traffic-config.png create mode 100644 site/static/img/route-all-traffic-flow.png rename {docs/images => site/static/img}/src/README.md (100%) rename {docs/images => site/static/img}/src/advanced-routing.mermaid (100%) rename {docs/images => site/static/img}/src/route-all-traffic-app.mermaid (100%) rename {docs/images => site/static/img}/src/route-all-traffic-config.mermaid (100%) rename {docs/images => site/static/img}/src/route-all-traffic-flow.mermaid (100%) diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml index 0503c1fec..416fbcedb 100644 --- a/.markdownlint-cli2.yaml +++ b/.markdownlint-cli2.yaml @@ -5,10 +5,7 @@ config: style: dash no-hard-tabs: false no-multiple-blanks: false - line-length: - line_length: 120 - code_blocks: false - tables: false + line-length: false blanks-around-headers: false no-duplicate-heading: siblings_only: true diff --git a/README.md b/README.md index cf92fdfc5..d0f7a0fba 100644 --- a/README.md +++ b/README.md @@ -10,19 +10,21 @@ and `UDPRoute` -- to configure an HTTP or TCP/UDP load balancer, reverse-proxy, on Kubernetes. NGINX Gateway Fabric supports a subset of the Gateway API. For a list of supported Gateway API resources and features, see -the [Gateway API Compatibility](docs/gateway-api-compatibility.md) doc. +the [Gateway API Compatibility](https://docs.nginx.com/nginx-gateway-fabric/gateway-api-compatibility.md) doc. -Learn about our [design principles](/docs/developer/design-principles.md) and [architecture](/docs/architecture.md). +Learn about our [design principles](/docs/developer/design-principles.md) and [architecture](https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture.md). ## Getting Started -1. [Quick Start on a kind cluster](docs/running-on-kind.md). -2. [Install](docs/installation.md) NGINX Gateway Fabric. -3. [Build](docs/building-the-images.md) an NGINX Gateway Fabric container image from source or use a pre-built image +1. [Quick Start on a kind cluster](https://docs.nginx.com/nginx-gateway-fabric/installation/running-on-kind.md). +2. [Install](https://docs.nginx.com/nginx-gateway-fabric/installation/) NGINX Gateway Fabric. +3. [Build](https://docs.nginx.com/nginx-gateway-fabric/installation/building-the-images.md) an NGINX Gateway Fabric container image from source or use a pre-built image available on [GitHub Container Registry](https://github.com/nginxinc/nginx-gateway-fabric/pkgs/container/nginx-gateway-fabric). 4. Deploy various [examples](examples). -5. Read our [guides](/docs/guides). +5. Read our [How-to guides](https://docs.nginx.com/nginx-gateway-fabric/how-to/). + +You can find the comprehensive NGINX Gateway Fabric user documentation on the [NGINX Documentation](https://docs.nginx.com/nginx-gateway-fabric/) website. ## NGINX Gateway Fabric Releases @@ -99,7 +101,7 @@ docker buildx imagetools inspect ghcr.io/nginxinc/nginx-gateway-fabric:edge --fo ## Troubleshooting -For troubleshooting help, see the [Troubleshooting](/docs/troubleshooting.md) document. +For troubleshooting help, see the [Troubleshooting](https://docs.nginx.com/nginx-gateway-fabric/how-to/monitoring/troubleshooting.md) document. ## Contacts diff --git a/docs/README.md b/docs/README.md index d93ff1c5a..516d7c910 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,27 +1,10 @@ # NGINX Gateway Fabric Documentation -This directory contains all of the documentation relating to NGINX Gateway Fabric. +This directory contains the developer documentation and the enhancement proposals relating to NGINX Gateway Fabric. -## Contents - -- [Architecture](architecture.md): An overview of the architecture and design principles of NGINX Gateway Fabric. -- [Gateway API Compatibility](gateway-api-compatibility.md): Describes which Gateway API resources NGINX Gateway -Fabric supports and the extent of that support. -- [Installation](installation.md): Walkthrough on how to install NGINX Gateway Fabric on a generic Kubernetes cluster. -- [Resource Validation](resource-validation.md): Describes how NGINX Gateway Fabric validates Gateway API -resources. -- [Control Plane Configuration](control-plane-configuration.md): Describes how to dynamically update the NGINX -Gateway Fabric control plane configuration. -- [Building the Images](building-the-images.md): Steps on how to build the NGINX Gateway Fabric container images -yourself. -- [Running on Kind](running-on-kind.md): Walkthrough on how to run NGINX Gateway Fabric on a `kind` cluster. -- [CLI Help](cli-help.md): Describes the commands available in the `gateway` binary of `nginx-gateway-fabric` -container. -- [Monitoring](monitoring.md): Information on monitoring NGINX Gateway Fabric using Prometheus metrics. -- [Troubleshooting](troubleshooting.md): Troubleshooting guide for common or known issues. +_Please note: You can find the user documentation for NGINX Gateway Fabric in the [NGINX Documentation](https://docs.nginx.com/nginx-gateway-fabric/) website._ -### Directories +## Contents -- [Guides](guides): Guides about configuring NGINX Gateway Fabric for various use cases. - [Developer](developer/): Docs for developers of the project. Contains guides relating to processes and workflows. - [Proposals](proposals/): Enhancement proposals for new features. diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 86f1f2177..000000000 --- a/docs/architecture.md +++ /dev/null @@ -1,160 +0,0 @@ -# Architecture - -This document provides an overview of the architecture and design principles of the NGINX Gateway Fabric. The target -audience includes the following groups: - -- *Cluster Operators* who would like to know how the software works and also better understand how it can fail. -- *Developers* who would like to [contribute][contribute] to the project. - -We assume that the reader is familiar with core Kubernetes concepts, such as Pods, Deployments, Services, and Endpoints. -Additionally, we recommend reading [this blog post][blog] for an overview of the NGINX architecture. - -[contribute]: https://github.com/nginxinc/nginx-gateway-fabric/blob/main/CONTRIBUTING.md - -[blog]: https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/ - -## What is NGINX Gateway Fabric? - -The NGINX Gateway Fabric is a component in a Kubernetes cluster that configures an HTTP load balancer according to -Gateway API resources created by Cluster Operators and Application Developers. - -> If you’d like to read more about the Gateway API, refer to [Gateway API documentation][sig-gateway]. - -This document focuses specifically on the NGINX Gateway Fabric, also known as NGF, which uses NGINX as its data -plane. - -[sig-gateway]: https://gateway-api.sigs.k8s.io/ - -## NGINX Gateway Fabric at a High Level - -To start, let's take a high-level look at the NGINX Gateway Fabric (NGF). The accompanying diagram illustrates an -example scenario where NGF exposes two web applications hosted within a Kubernetes cluster to external clients on the -internet: - -![NGF High Level](/docs/images/ngf-high-level.png) - -The figure shows: - -- A *Kubernetes cluster*. -- Users *Cluster Operator*, *Application Developer A* and *Application Developer B*. These users interact with the -cluster through the Kubernetes API by creating Kubernetes objects. -- *Clients A* and *Clients B* connect to *Applications A* and *B*, respectively. This applications have been deployed by -the corresponding users. -- The *NGF Pod*, [deployed by *Cluster Operator*](/docs/installation.md) in the Namespace *nginx-gateway*. For -scalability and availability, you can have multiple replicas. This Pod consists of two containers: `NGINX` and `NGF`. -The *NGF* container interacts with the Kubernetes API to retrieve the most up-to-date Gateway API resources created -within the cluster. It then dynamically configures the *NGINX* container based on these resources, ensuring proper -alignment between the cluster state and the NGINX configuration. -- *Gateway AB*, created by *Cluster Operator*, requests a point where traffic can be translated to Services within the -cluster. This Gateway includes a listener with a hostname `*.example.com`. Application Developers have the ability to -attach their application's routes to this Gateway if their application's hostname matches `*.example.com`. -- *Application A* with two Pods deployed in the *applications* Namespace by *Application Developer A*. To expose the -application to its clients (*Clients A*) via the host `a.example.com`, *Application Developer A* creates *HTTPRoute A* -and attaches it to `Gateway AB`. -- *Application B* with one Pod deployed in the *applications* Namespace by *Application Developer B*. To expose the -application to its clients (*Clients B*) via the host `b.example.com`, *Application Developer B* creates *HTTPRoute B* -and attaches it to `Gateway AB`. -- *Public Endpoint*, which fronts the *NGF* Pod. This is typically a TCP load balancer (cloud, software, or hardware) -or a combination of such load balancer with a NodePort Service. *Clients A* and *B* connect to their applications via -the *Public Endpoint*. - -The connections related to client traffic are depicted by the yellow and purple arrows, while the black arrows represent -access to the Kubernetes API. The resources within the cluster are color-coded based on the user responsible for their -creation. For example, the Cluster Operator is denoted by the color green, indicating that they have created and manage -all the green resources. - -> Note: For simplicity, many necessary Kubernetes resources like Deployment and Services aren't shown, -> which the Cluster Operator and the Application Developers also need to create. - -Next, let's explore the NGF Pod. - -## The NGINX Gateway Fabric Pod - -The NGINX Gateway Fabric consists of two containers: - -1. `nginx`: the data plane. Consists of an NGINX master process and NGINX worker processes. The master process controls -the worker processes. The worker processes handle the client traffic and load balance the traffic to the backend -applications. -2. `nginx-gateway`: the control plane. Watches Kubernetes objects and configures NGINX. - -These containers are deployed in a single Pod as a Kubernetes Deployment. - -The `nginx-gateway`, or the control plane, is a [Kubernetes controller][controller], written with -the [controller-runtime][runtime] library. It watches Kubernetes objects (Services, Endpoints, Secrets, and Gateway API -CRDs), translates them to NGINX configuration, and configures NGINX. This configuration happens in two stages. First, -NGINX configuration files are written to the NGINX configuration volume shared by the `nginx-gateway` and `nginx` -containers. Next, the control plane reloads the NGINX process. This is possible because the two -containers [share a process namespace][share], which allows the NGF process to send signals to the NGINX master process. - -The diagram below provides a visual representation of the interactions between processes within the `nginx` and -`nginx-gateway` containers, as well as external processes/entities. It showcases the connections and relationships between -these components. - -![NGF pod](/docs/images/ngf-pod.png) - -The following list provides a description of each connection, along with its corresponding type indicated in -parentheses. To enhance readability, the suffix "process" has been omitted from the process descriptions below. - -1. (HTTPS) - - Read: *NGF* reads the *Kubernetes API* to get the latest versions of the resources in the cluster. - - Write: *NGF* writes to the *Kubernetes API* to update the handled resources' statuses and emit events. If there's - more than one replica of *NGF* and [leader election](/deploy/helm-chart/README.md#configuration) is enabled, only - the *NGF* Pod that is leading will write statuses to the *Kubernetes API*. -2. (HTTP, HTTPS) *Prometheus* fetches the `controller-runtime` and NGINX metrics via an HTTP endpoint that *NGF* exposes. - The default is :9113/metrics. Note: Prometheus is not required by NGF, the endpoint can be turned off. -3. (File I/O) - - Write: *NGF* generates NGINX *configuration* based on the cluster resources and writes them as `.conf` files to the - mounted `nginx-conf` volume, located at `/etc/nginx/conf.d`. It also writes *TLS certificates* and *keys* - from [TLS Secrets][secrets] referenced in the accepted Gateway resource to the `nginx-secrets` volume at the - path `/etc/nginx/secrets`. - - Read: *NGF* reads the PID file `nginx.pid` from the `nginx-run` volume, located at `/var/run/nginx`. *NGF* - extracts the PID of the nginx process from this file in order to send reload signals to *NGINX master*. -4. (File I/O) *NGF* writes logs to its *stdout* and *stderr*, which are collected by the container runtime. -5. (HTTP) *NGF* fetches the NGINX metrics via the unix:/var/run/nginx/nginx-status.sock UNIX socket and converts it to - *Prometheus* format used in #2. -6. (Signal) To reload NGINX, *NGF* sends the [reload signal][reload] to the **NGINX master**. -7. (File I/O) - - Write: The *NGINX master* writes its PID to the `nginx.pid` file stored in the `nginx-run` volume. - - Read: The *NGINX master* reads *configuration files* and the *TLS cert and keys* referenced in the configuration when - it starts or during a reload. These files, certificates, and keys are stored in the `nginx-conf` and `nginx-secrets` - volumes that are mounted to both the `nginx-gateway` and `nginx` containers. -8. (File I/O) - - Write: The *NGINX master* writes to the auxiliary Unix sockets folder, which is located in the `/var/lib/nginx` - directory. - - Read: The *NGINX master* reads the `nginx.conf` file from the `/etc/nginx` directory. This [file][conf-file] contains - the global and http configuration settings for NGINX. In addition, *NGINX master* - reads the NJS modules referenced in the configuration when it starts or during a reload. NJS modules are stored in - the `/usr/lib/nginx/modules` directory. -9. (File I/O) The *NGINX master* sends logs to its *stdout* and *stderr*, which are collected by the container runtime. -10. (File I/O) An *NGINX worker* writes logs to its *stdout* and *stderr*, which are collected by the container runtime. -11. (Signal) The *NGINX master* controls the [lifecycle of *NGINX workers*][lifecycle] it creates workers with the new - configuration and shutdowns workers with the old configuration. -12. (HTTP) To consider a configuration reload a success, *NGF* ensures that at least one NGINX worker has the new - configuration. To do that, *NGF* checks a particular endpoint via the unix:/var/run/nginx/nginx-config-version.sock - UNIX socket. -13. (HTTP,HTTPS) A *client* sends traffic to and receives traffic from any of the *NGINX workers* on ports 80 and 443. -14. (HTTP,HTTPS) An *NGINX worker* sends traffic to and receives traffic from the *backends*. - -[controller]: https://kubernetes.io/docs/concepts/architecture/controller/ - -[runtime]: https://github.com/kubernetes-sigs/controller-runtime - -[secrets]: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets - -[reload]: https://nginx.org/en/docs/control.html - -[lifecycle]: https://nginx.org/en/docs/control.html#reconfiguration - -[conf-file]: https://github.com/nginxinc/nginx-gateway-fabric/blob/main/internal/mode/static/nginx/conf/nginx.conf - -[share]: https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/ - -## Pod Readiness - -The `nginx-gateway` container includes a readiness endpoint available via the `/readyz` path. This endpoint -is periodically checked by a [readiness probe][readiness] on startup, and returns a 200 OK response when the Pod is -ready to accept traffic for the data plane. The Pod will become Ready after the control plane successfully starts. -If there are relevant Gateway API resources in the cluster, the control plane will also generate the first NGINX -configuration and successfully reload NGINX before the Pod is considered Ready. - -[readiness]: (https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) diff --git a/docs/cli-help.md b/docs/cli-help.md deleted file mode 100644 index e3b795ae2..000000000 --- a/docs/cli-help.md +++ /dev/null @@ -1,49 +0,0 @@ -# Command-line Help - -This document describes the commands available in the `gateway` binary of the `nginx-gateway` container. - -## Static Mode - -This command configures NGINX in the scope of a single Gateway resource. - -Usage: - -```text - gateway static-mode [flags] -``` - -Flags: - -| Name | Type | Description | -|------------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `gateway-ctlr-name` | `string` | The name of the Gateway controller. The controller name must be of the form: `DOMAIN/PATH`. The controller's domain is `gateway.nginx.org`. | -| `gatewayclass` | `string` | The name of the GatewayClass resource. Every NGINX Gateway Fabric must have a unique corresponding GatewayClass resource. | -| `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}. | -| `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. | -| `service` | `string` | The name of the Service that fronts this NGINX Gateway Fabric Pod. Lives in the same Namespace as the controller. | -| `metrics-disable` | `bool` | Disable exposing metrics in the Prometheus format. (default false) | -| `metrics-listen-port` | `int` | Sets the port where the Prometheus metrics are exposed. Format: `[1024 - 65535]` (default `9113`) | -| `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) | -| `update-gatewayclass-status` | `bool` | Update the status of the GatewayClass resource. (default true) | -| `health-disable` | `bool` | Disable running the health probe server. (default false) | -| `health-port` | `int` | Set the port where the health probe server is exposed. Format: `[1024 - 65535]` (default `8081`) | -| `leader-election-disable` | `bool` | Disable leader election. Leader election is used to avoid multiple replicas of the NGINX Gateway Fabric reporting the status of the Gateway API resources. If disabled, all replicas of NGINX Gateway Fabric will update the statuses of the Gateway API resources. (default false) | -| `leader-election-lock-name` | `string` | The name of the leader election lock. A Lease object with this name will be created in the same Namespace as the controller. (default "nginx-gateway-leader-election-lock") | - -## Sleep - -This command sleeps for specified duration and exits. - -Usage: - -```text -Usage: - gateway sleep [flags] -``` - -| Name | Type | Description | -|----------|-----------------|-------------------------------------------------------------------------------------------------------| -| duration | `time.Duration` | Set the duration of sleep. Must be parsable by [`time.ParseDuration`][parseDuration]. (default `30s`) | - - -[parseDuration]:https://pkg.go.dev/time#ParseDuration diff --git a/docs/developer/documentation.md b/docs/developer/documentation.md new file mode 100644 index 000000000..f927fa59b --- /dev/null +++ b/docs/developer/documentation.md @@ -0,0 +1,165 @@ +# NGINX Gateway Fabric Docs + +The `/site` directory contains the user documentation for NGINX Gateway Fabric and the requirements for linting, building, and publishing the docs. Run all the `hugo` commands below from this directory. + +We use [Hugo](https://gohugo.io/) to build the docs for NGINX, with the [nginx-hugo-theme](https://github.com/nginxinc/nginx-hugo-theme). + +Docs should be written in Markdown. + +In the `/site` directory, you will find the following files: + +- a [Netlify](https://netlify.com) configuration file; +- configuration files for [markdownlint](https://github.com/DavidAnson/markdownlint/) and [markdown-link-check](https://github.com/tcort/markdown-link-check) +- a `./config` directory that contains the [Hugo](https://gohugo.io) configuration. + +## Git Guidelines + +See the [Pull Request Guide](pull-request.md) for specfic instructions on how to submit a pull request. + +### Branching and Workflow + +This repo uses a [forking workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow). See our [Branching and Workflow](branching-and-workflow.md) documentation for more information. + +### Publishing Documentation Updates + +**`main`** is the default branch in this repo. All the latest content updates are merged into this branch. + +The documentation is published from the latest public release branch, (for example, `release-4.0`). Work on your docs in a feature branch in your fork of the repo. Open pull requests into the `main` branch when you are ready to merge your work. + +If you are working on content for immediate publication in the docs site, cherrypick your changes to the current public release branch. + +If you are working on content for a future release, make sure that you **do not** cherrypick them to the current public release branch, as this will publish them automatically. See the [Release Process documentation](release-process.md) for more information. + + +## Setup + +### Golang + +Follow the instructions here to install Go: https://golang.org/doc/install + +> To support the use of Hugo mods, you need to install Go v1.15 or newer. + +### Hugo + +Follow the instructions here to install Hugo: [Hugo Installation](https://gohugo.io/installation/) + +> **NOTE:** We are currently running [Hugo v0.115.3](https://github.com/gohugoio/hugo/releases/tag/v0.115.3) in production. + +### Markdownlint + +We use markdownlint to check that Markdown files are correctly formatted. You can use `npm` to install markdownlint-cli: + +```shell +npm install -g markdownlint-cli +``` + +## How to write docs with Hugo + +### Add a new doc + +- To create a new doc that contains all of the pre-configured Hugo front-matter and the docs task template: + + `hugo new /.` + + e.g., + + hugo new install.md + + > The default template -- task -- should be used in most docs. +- To create other types of docs, you can add the `--kind` flag: + `hugo new tutorials/deploy.md --kind tutorial` + + +The available kinds are: + +- Task: Enable the customer to achieve a specific goal, based on use case scenarios. +- Concept: Help a customer learn about a specific feature or feature set. +- Reference: Describes an API, command line tool, config options, etc.; should be generated automatically from source code. +- Troubleshooting: Helps a customer solve a specific problem. +- Tutorial: Walk a customer through an example use case scenario; results in a functional PoC environment. + +### Format internal links + +Format links as [Hugo relrefs](https://gohugo.io/content-management/cross-references/). + +> Note: Using file extensions when linking to internal docs with `relref` is optional. + +- You can use relative paths or just the filename. We recommend using the filename +- Paths without a leading `/` are first resolved relative to the current page, then to the remainder of the site. +- Anchors are supported. + +For example: + +```md +To install NGINX Gateway Fabric, refer to the [installation instructions]({{< relref "/installation/install.md#section-1" >}}). +``` + +### Add images + +You can use the `img` [shortcode](#use-hugo-shortcodes to insert images into your documentation. + +1. Add the image to the static/img directory. + DO NOT include a forward slash at the beginning of the file path. This will break the image when it's rendered. + See the docs for the [Hugo relURL Function](https://gohugo.io/functions/relurl/#input-begins-with-a-slash) to learn more. + +1. Add the img shortcode: + + {{< img src="img/" >}} + +> Note: The shortcode accepts all of the same parameters as the [Hugo figure shortcode](https://gohugo.io/content-management/shortcodes/#figure). + +### Use Hugo shortcodes + +You can use Hugo [shortcodes](https://gohugo.io/content-management/shortcodes) to do things like format callouts, add images, and reuse content across different docs. + +For example, to use the note callout: + +```md +{{< note >}}Provide the text of the note here. {{< /note >}} +``` + +The callout shortcodes also support multi-line blocks: + +```md +{{< caution >}} +You should probably never do this specific thing in a production environment. If you do, and things break, don't say we didn't warn you. +{{< /caution >}} +``` + +Supported callouts: + +- caution +- important +- note +- see-also +- tip +- warning + +A few more useful shortcodes: + +- collapse: makes a section collapsible +- table: adds scrollbars to wide tables when viewed in small browser windows or mobile browsers +- fa: inserts a Font Awesome icon +- include: include the content of a file in another file (requires the included file to be in the /includes directory) +- link: makes it possible to link to a static file and prepend the path with the Hugo baseUrl +- openapi: loads an OpenAPI spec and renders as HTML using ReDoc +- raw-html: makes it possible to include a block of raw HTML +- readfile: includes the content of another file in the current file; useful for adding code examples + +## How to build docs locally + +To view the docs in a browser, run the Hugo server. This will reload the docs automatically so you can view updates as you work. + +> Note: The docs use build environments to control the baseURL that will be used for things like internal references and resource (CSS and JS) loading. +> You can view the config for each environment in the [config](./config) directory of this repo. +When running the Hugo server, you can specify the environment and baseURL if desired, but it's not necessary. + +For example: + +```shell +hugo server +``` + +```shell +hugo server -e development -b "http://127.0.0.1/nginx-gateway-fabric/" +``` diff --git a/docs/developer/implementing-a-feature.md b/docs/developer/implementing-a-feature.md index 262a93ef7..c56d5dd7e 100644 --- a/docs/developer/implementing-a-feature.md +++ b/docs/developer/implementing-a-feature.md @@ -32,18 +32,19 @@ practices to ensure a successful feature development process. the [testing](/docs/developer/testing.md#unit-test-guidelines) documentation. 9. **Manually verify your changes**: Refer to the [manual testing](/docs/developer/testing.md#manual-testing) section of the testing documentation for instructions on how to manually test your changes. -10. **Update any relevant documentation**: Here are some guidelines for updating documentation: +10. **Update any relevant documentation**: See the [documentation](/docs/developer/documentation.md) guide for in-depth information about the workflow to update the docs and how we publish them. + Here are some basic guidelines for updating documentation: - **Gateway API Feature**: If you are implementing a Gateway API feature, make sure to update - the [Gateway API Compatibility](/docs/gateway-api-compatibility.md) documentation. + the [Gateway API Compatibility](/site/content/concepts/gateway-api-compatibility.md) documentation. - **New Use Case:** If your feature introduces a new use case, add an example of how to use it in the [examples](/examples) directory. This example will help users understand how to leverage the new feature. > For security, a Docker image used in an example must be either managed by F5/NGINX or be an [official image](https://docs.docker.com/docker-hub/official_images/). - **Installation Changes**: If your feature involves changes to the installation process of NGF, update - the [installation](/docs/installation.md) documentation. + the [installation](/site/content/how-to/installation/installation.md) documentation. - **Helm Changes**: If your feature introduces or changes any values of the NGF Helm Chart, update the [Helm README](/deploy/helm-chart/README.md). - **Command-line Changes**: If your feature introduces or changes a command-line flag or subcommand, update - the [cli help](/docs/cli-help.md) documentation. + the [cli help](/site/content/reference/cli-help.md) documentation. - **Other Documentation Updates**: For any other changes that affect the behavior, usage, or configuration of NGF, review the existing documentation and update it as necessary. Ensure that the documentation remains accurate and up to date with the latest changes. diff --git a/docs/guides/README.md b/docs/guides/README.md deleted file mode 100644 index 6869f9883..000000000 --- a/docs/guides/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Guides - -This directory contains guides for configuring NGINX Gateway Fabric for various use cases. - -## Contents - -- [Routing Traffic to Your Application](routing-traffic-to-your-app.md): How to use NGINX Gateway Fabric to route - all Ingress traffic to your Kubernetes application. -- [Routing to Applications Using HTTP Matching Conditions](advanced-routing.md): Guide on how to deploy multiple - applications and HTTPRoutes with request conditions such as paths, methods, headers, and query parameters. -- [Securing Traffic using Let's Encrypt and Cert-Manager](integrating-cert-manager.md): Shows how to secure - traffic from clients to NGINX Gateway Fabric with TLS using Let's Encrypt and Cert-Manager. -- [Using NGINX Gateway Fabric to Upgrade Applications without Downtime](upgrade-apps-without-downtime.md): - Explains how to use NGINX Gateway Fabric to upgrade applications without downtime. diff --git a/docs/guides/routing-traffic-to-your-app.md b/docs/guides/routing-traffic-to-your-app.md deleted file mode 100644 index 9842492a5..000000000 --- a/docs/guides/routing-traffic-to-your-app.md +++ /dev/null @@ -1,405 +0,0 @@ -# Routing Traffic to Your Application - -In this guide, you will learn how to route external traffic to your Kubernetes applications using the Gateway API and -NGINX Gateway Fabric. Whether you're managing a web application or a REST backend API, you can use NGINX Gateway -Fabric to expose your application outside the cluster. - -## Prerequisites - -- [Install](/docs/installation.md) NGINX Gateway Fabric. -- [Expose NGINX Gateway Fabric](/docs/installation.md#expose-nginx-gateway-fabric) and save the public IP - address and port of NGINX Gateway Fabric into shell variables: - - ```text - GW_IP=XXX.YYY.ZZZ.III - GW_PORT= - ``` - -## The Application - -The application we are going to use in this guide is a simple coffee application comprised of one Service and two Pods: - -![coffee app](/docs/images/route-all-traffic-app.png) - -With this architecture, the coffee application is not accessible outside the cluster. We want to expose this application -on the hostname `cafe.example.com` so that clients outside the cluster can access it. - -To do this, we will install NGINX Gateway Fabric and create two Gateway API resources: -a [Gateway](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.Gateway) and -an [HTTPRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.HTTPRoute). -With these resources, we will configure a simple routing rule to match all HTTP traffic with the -hostname `cafe.example.com` and route it to the coffee Service. - -## Setup - -Create the coffee application in Kubernetes by copying and pasting the following into your terminal: - -```yaml -kubectl apply -f - < 80/TCP 77s -``` - -## Application Architecture with NGINX Gateway Fabric - -To route traffic to the coffee application, we will create a Gateway and HTTPRoute. The following diagram shows the -configuration we'll be creating in the next step: - -![Configuration](/docs/images/route-all-traffic-config.png) - -We need a Gateway to create an entry point for HTTP traffic coming into the cluster. The `cafe` Gateway we are going to -create will open an entry point to the cluster on port 80 for HTTP traffic. - -To route HTTP traffic from the Gateway to the coffee Service, we need to create an HTTPRoute named `coffee` and attach -to the Gateway. This HTTPRoute will have a single routing rule that routes all traffic to the -hostname `cafe.example.com` from the Gateway to the coffee Service. - -Once NGINX Gateway Fabric processes the `cafe` Gateway and `coffee` HTTPRoute, it will configure its dataplane, NGINX, -to route all HTTP requests to `cafe.example.com` to the Pods that the `coffee` Service targets: - -![Traffic Flow](/docs/images/route-all-traffic-flow.png) - -The coffee Service is omitted from the diagram above because the NGINX Gateway Fabric routes directly to the Pods -that the coffee Service targets. - -> **Note** -> In the diagrams above, all resources that are the responsibility of the cluster operator are shown in green. -> The orange resources are the responsibility of the application developers. -> See the [roles and personas](https://gateway-api.sigs.k8s.io/concepts/roles-and-personas/#roles-and-personas_1) -> Gateway API document for more information on these roles. - -## Create the Gateway API Resources - -To create the `cafe` Gateway, copy and paste the following into your terminal: - -```yaml -kubectl apply -f - < **Note** -> Your clients should be able to resolve the domain name `cafe.example.com` to the public IP of the -> NGINX Gateway Fabric. In this guide we will simulate that using curl's `--resolve` option. - - -First, let's send a request to the path `/`: - -```shell -curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/ -``` - -We should get a response from one of the coffee Pods: - -```text -Server address: 10.12.0.18:8080 -Server name: coffee-7dd75bc79b-cqvb7 -``` - -Since the `cafe` HTTPRoute routes all traffic on any path to the coffee application, the following requests should also -be handled by the coffee Pods: - -```shell -curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/some-path -``` - -```text -Server address: 10.12.0.18:8080 -Server name: coffee-7dd75bc79b-cqvb7 -``` - -```shell -curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/some/path -``` - -```text -Server address: 10.12.0.19:8080 -Server name: coffee-7dd75bc79b-dett3 -``` - -Requests to hostnames other than `cafe.example.com` should _not_ be routed to the coffee application, since the `cafe` -HTTPRoute only matches requests with the `cafe.example.com` hostname. To verify this, send a request to the hostname -`pub.example.com`: - -```shell -curl --resolve pub.example.com:$GW_PORT:$GW_IP http://pub.example.com:$GW_PORT/ -``` - -You should receive a 404 Not Found error: - -```text - -404 Not Found - -

404 Not Found

-
nginx/1.25.2
- - -``` - -## Troubleshooting - -If you have any issues while testing the configuration, try the following to debug your configuration and setup: - -- Make sure you set the shell variables $GW_IP and $GW_PORT to the public IP and port of the NGINX Gateway Fabric - Service. Instructions for finding those values are [here](/docs/installation.md#expose-nginx-gateway-fabric). - -- Check the status of the Gateway: - - ```shell - kubectl describe gateway cafe - ``` - - The Gateway status should look similar to this: - - ```text - Status: - Addresses: - Type: IPAddress - Value: 10.244.0.85 - Conditions: - Last Transition Time: 2023-08-15T20:57:21Z - Message: Gateway is accepted - Observed Generation: 1 - Reason: Accepted - Status: True - Type: Accepted - Last Transition Time: 2023-08-15T20:57:21Z - Message: Gateway is programmed - Observed Generation: 1 - Reason: Programmed - Status: True - Type: Programmed - Listeners: - Attached Routes: 1 - Conditions: - Last Transition Time: 2023-08-15T20:57:21Z - Message: Listener is accepted - Observed Generation: 1 - Reason: Accepted - Status: True - Type: Accepted - Last Transition Time: 2023-08-15T20:57:21Z - Message: Listener is programmed - Observed Generation: 1 - Reason: Programmed - Status: True - Type: Programmed - Last Transition Time: 2023-08-15T20:57:21Z - Message: All references are resolved - Observed Generation: 1 - Reason: ResolvedRefs - Status: True - Type: ResolvedRefs - Last Transition Time: 2023-08-15T20:57:21Z - Message: No conflicts - Observed Generation: 1 - Reason: NoConflicts - Status: False - Type: Conflicted - Name: http - ``` - - Check that the conditions match and that the attached routes for the `http` listener equals 1. If it is 0, there may - be an issue with the HTTPRoute. - -- Check the status of the HTTPRoute: - - ```shell - kubectl describe httproute coffee - ``` - - The HTTPRoute status should look similar to this: - - ```text - Status: - Parents: - Conditions: - Last Transition Time: 2023-08-15T20:57:21Z - Message: The route is accepted - Observed Generation: 1 - Reason: Accepted - Status: True - Type: Accepted - Last Transition Time: 2023-08-15T20:57:21Z - Message: All references are resolved - Observed Generation: 1 - Reason: ResolvedRefs - Status: True - Type: ResolvedRefs - Controller Name: gateway.nginx.org/nginx-gateway-controller - Parent Ref: - Group: gateway.networking.k8s.io - Kind: Gateway - Name: cafe - Namespace: default - ``` - - Check for any error messages in the conditions. - -- Check the generated nginx config: - - ```shell - kubectl exec -it -n nginx-gateway -c nginx -- nginx -T - ``` - - The config should contain a server block with the server name `cafe.example.com` that listens on port 80. This - server block should have a single location `/` that proxy passes to the coffee upstream: - - ```nginx configuration - server { - listen 80; - - server_name cafe.example.com; - - location / { - ... - proxy_pass http://default_coffee_80$request_uri; # the upstream is named default_coffee_80 - ... - } - } - ``` - - There should also be an upstream block with a name that matches the upstream in the `proxy_pass` directive. This - upstream block should contain the Pod IPs of the coffee Pods: - - ```nginx configuration - upstream default_coffee_80 { - ... - server 10.12.0.18:8080; # these should be the Pod IPs of the coffee Pods - server 10.12.0.19:8080; - ... - } - ``` - - > **Note** - > The entire configuration is not shown because it is subject to change. - > Ellipses indicate that there's configuration not shown. - -If your issue persists, [contact us](https://github.com/nginxinc/nginx-gateway-fabric#contacts). - -## Further Reading - -To learn more about the Gateway API and the resources we created in this guide, check out the following resources: - -- [Gateway API Overview](https://gateway-api.sigs.k8s.io/concepts/api-overview/) -- [Deploying a simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/) -- [HTTP Routing](https://gateway-api.sigs.k8s.io/guides/http-routing/) diff --git a/docs/guides/upgrade-apps-without-downtime.md b/docs/guides/upgrade-apps-without-downtime.md deleted file mode 100644 index bbc5646fe..000000000 --- a/docs/guides/upgrade-apps-without-downtime.md +++ /dev/null @@ -1,173 +0,0 @@ -# Using NGINX Gateway Fabric to Upgrade Applications without Downtime - -This guide explains how to use NGINX Gateway Fabric to upgrade applications without downtime. - -Multiple upgrade methods are mentioned, assuming existing familiarity: this guide focuses primarily on how to use NGINX -Gateway Fabric to accomplish them. - -> See the [Architecture document](../architecture.md) to learn more about NGINX Gateway Fabric architecture. - -## NGINX Gateway Fabric Functionality - -To understand the upgrade methods, you should be aware of the NGINX features that help prevent application downtime: -graceful configuration reloads and upstream server updates. - -### Graceful Configuration Reloads - -If a relevant Gateway API or built-in Kubernetes resource is changed, NGINX Gateway Fabric will update NGINX by -regenerating the NGINX configuration. NGINX Gateway Fabric then sends a reload signal to the master NGINX process to -apply the new configuration. - -We call such an operation a reload, during which client requests are not dropped - which defines it as a graceful reload. - -This process is further explained in [NGINX's documentation](https://nginx.org/en/docs/control.html?#reconfiguration). - -### Upstream Server Updates - -Endpoints frequently change during application upgrades: Kubernetes creates Pods for the new version of an application -and removes the old ones, creating and removing the respective Endpoints as well. - -NGINX Gateway Fabric detects changes to Endpoints by watching their corresponding [EndpointSlices][endpoint-slices]. - -[endpoint-slices]:https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/ - -In NGINX configuration, a Service is represented as an [upstream][upstream], and an Endpoint as an -[upstream server][upstream-server]. - -[upstream]:https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream - -[upstream-server]:https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server - -Two common cases are adding and removing Endpoints: - -- If an Endpoint is added, NGINX Gateway Fabric adds an upstream server to NGINX that corresponds to the Endpoint, - then reload NGINX. After that, NGINX will start proxying traffic to that Endpoint. -- If an Endpoint is removed, NGINX Gateway Fabric removes the corresponding upstream server from NGINX. After - a reload, NGINX will stop proxying traffic to it. However, it will finish proxying any pending requests to that - server before switching to another Endpoint. - -As long as you have more than one ready Endpoint, the clients should not experience any downtime during upgrades. - -> It is good practice to configure a [Readiness probe][readiness-probe] in the Deployment so that a Pod can advertise -> when it is ready to receive traffic. Note that NGINX Gateway Fabric will not add any Endpoint to NGINX that is not -> ready. - -[readiness-probe]:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ - -## Before You Begin - -For the upgrade methods covered in the next sections, we make the following assumptions: - -- You deploy your application as a [Deployment][deployment]. -- The Pods of the Deployment belong to a [Service][service] so that Kubernetes creates an [Endpoint][endpoints] for - each Pod. -- You expose the application to the clients via an [HTTPRoute][httproute] resource that references that Service. - -[deployment]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ - -[service]:https://kubernetes.io/docs/concepts/services-networking/service/ - -[httproute]:https://gateway-api.sigs.k8s.io/api-types/httproute/ - -[endpoints]:https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoints-v1/ - -For example, an application can be exposed using a routing rule like below: - -```yaml -- matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: my-app - port: 80 -``` - -> See the [Cafe example](../../examples/cafe-example) for a basic example. - -The upgrade methods in the next sections cover: - -- Rolling Deployment Upgrades -- Blue-green Deployments -- Canary Releases - -## Rolling Deployment Upgrade - -To start a [rolling Deployment upgrade][rolling-upgrade], you update the Deployment to use the new version tag of -the application. As a result, Kubernetes terminates the Pods with the old version and create new ones. By default, -Kubernetes also ensures that some number of Pods always stay available during the upgrade. - -[rolling-upgrade]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-update-deployment - -Such an upgrade will add new upstream servers to NGINX and remove the old ones. As long as the number -of Pods (ready Endpoints) during an upgrade does not reach zero, NGINX will be able to proxy traffic, and thus prevent -any downtime. - -This method does not require you to update the HTTPRoute. - -## Blue-Green Deployments - -With this method, you deploy a new version of the application (blue version) as a separate Deployment, -while the old version (green) keeps running and handling client traffic. Next, you switch the traffic from the -green version to the blue. If the blue works as expected, you terminate the green. Otherwise, you switch the traffic -back to the green. - -There are two ways to switch the traffic: - -- Update the Service selector to select the Pods of the blue version instead of the green. As a result, NGINX Gateway - Fabric removes the green upstream servers from NGINX and add the blue ones. With this approach, it is not - necessary to update the HTTPRoute. -- Create a separate Service for the blue version and update the backend reference in the HTTPRoute to reference this - Service, which leads to the same result as with the previous option. - -## Canary Releases - -To support canary releases, you can implement an approach with two Deployments behind the same Service (see -[Canary deployment][canary] in the Kubernetes documentation). However, this approach lacks precision for defining the -traffic split between the old and the canary version. You can greatly influence it by controlling the number of Pods -(for example, four Pods of the old version and one Pod of the canary). However, note that NGINX Gateway Fabric uses -[`random two least_conn`][random-method] load balancing method, which doesn't guarantee an exact split based on the -number of Pods (80/20 in the given example). - -[canary]:https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#canary-deployment -[random-method]:https://nginx.org/en/docs/http/ngx_http_upstream_module.html#random - -A more flexible and precise way to implement canary releases is to configure a traffic split in an HTTPRoute. In this -case, you create a separate Deployment for the new version with a separate Service. For example, for the rule below, -NGINX will proxy 95% of the traffic to the old version Endpoints and only 5% to the new ones. - -```yaml -- matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: my-app-old - port: 80 - weight: 95 - - name: my-app-new - port: 80 - weight: 5 -``` - -> There is no stickiness for the requests from the same client. NGINX will independently split each request among -> the backend references. - -By updating the rule you can further increase the share of traffic the new version gets and finally completely switch -to the new version: - -```yaml -- matches: - - path: - type: PathPrefix - value: / - backendRefs: - - name: my-app-old - port: 80 - weight: 0 - - name: my-app-new - port: 80 - weight: 1 -``` - -See the [Traffic splitting example](/examples/traffic-splitting) from our repository. diff --git a/docs/images/route-all-traffic-config.png b/docs/images/route-all-traffic-config.png deleted file mode 100644 index 38c4e08a6e161433eb63682531e7896cc4fbc8c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50370 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 Ii`RYr2iNuJ1poj5 diff --git a/docs/images/route-all-traffic-flow.png b/docs/images/route-all-traffic-flow.png deleted file mode 100644 index 9fc2089c73adcb4bef46b4c3e7adb50e7d1aa691..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58438 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

!vh3 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 **Note** -> -> By default, NGINX Gateway Fabric (NGF) will be installed into the nginx-gateway Namespace. -> It is possible to run NGF in a different Namespace, although you'll need to make modifications to the installation -> manifests. - -1. To install the Gateway API CRDs from [the Gateway API repo](https://github.com/kubernetes-sigs/gateway-api), run: - - ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml - ``` - - If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: - - ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` - - > **Important** - > - > The validating webhook is not needed if you are running Kubernetes 1.25+. Validation is done using CEL on the - > CRDs. See the [resource validation doc](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/docs/resource-validation.md) - > for more information. - -2. Deploy the NGINX Gateway Fabric CRDs: - - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml - ``` - -3. Deploy the NGINX Gateway Fabric: - - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml - ``` - -4. Confirm the NGINX Gateway Fabric is running in `nginx-gateway` namespace: - - ```shell - kubectl get pods -n nginx-gateway - ``` - - ```text - NAME READY STATUS RESTARTS AGE - nginx-gateway-5d4f4c7db7-xk2kq 2/2 Running 0 112s - ``` - -## Expose NGINX Gateway Fabric - -You can gain access to NGINX Gateway Fabric by creating a `NodePort` Service or a `LoadBalancer` Service. -This Service must live in the same Namespace as the controller. The name of this Service is provided in -the `--service` argument to the controller. - -> **Important** -> -> The Service manifests expose NGINX Gateway Fabric on ports 80 and 443, which exposes any -> Gateway [Listener](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.Listener) -> configured for those ports. If you'd like to use different ports in your listeners, -> update the manifests accordingly. -> -> Additionally, NGINX Gateway Fabric will not listen on any ports until you configure a -[Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/#gateway) resource with a valid listener. - -NGINX Gateway Fabric will use this Service to set the Addresses field in the Gateway Status resource. A LoadBalancer -Service sets the status field to the IP address and/or Hostname. If no Service exists, the Pod IP address is used. - -### Create a NodePort Service - -Create a Service with type `NodePort`: - -```shell -kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/nodeport.yaml -``` - -A `NodePort` Service will randomly allocate one port on every Node of the cluster. To access NGINX Gateway Fabric, -use an IP address of any Node in the cluster along with the allocated port. - -### Create a LoadBalancer Service - -Create a Service with type `LoadBalancer` using the appropriate manifest for your cloud provider. - -- For GCP or Azure: - - ```shell - kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/loadbalancer.yaml - ``` - - Lookup the public IP of the load balancer, which is reported in the `EXTERNAL-IP` column in the output of the - following command: - - ```shell - kubectl get svc nginx-gateway -n nginx-gateway - ``` - - Use the public IP of the load balancer to access NGINX Gateway Fabric. - -- For AWS: - - ```shell - kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/loadbalancer-aws-nlb.yaml - ``` - - In AWS, the NLB DNS name will be reported by Kubernetes in lieu of a public IP in the `EXTERNAL-IP` column. To get the - DNS name run: - - ```shell - kubectl get svc nginx-gateway -n nginx-gateway - ``` - - In general, you should rely on the NLB DNS name, however for testing purposes you can resolve the DNS name to get the - IP address of the load balancer: - - ```shell - nslookup - ``` - -## Upgrading NGINX Gateway Fabric - -> **Note** -> -> See [below](#configure-delayed-termination-for-zero-downtime-upgrades) for instructions on how to configure delayed -> termination if required for zero downtime upgrades in your environment. - -### Upgrade NGINX Gateway Fabric from Manifests - -1. Upgrade the Gateway Resources - - Before you upgrade, ensure the Gateway API resources are the correct version as supported by the NGINX Gateway - Fabric - [see the Technical Specifications](/README.md#technical-specifications). - The [release notes](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.0.0) of the new version of the - Gateway API might include important upgrade-specific notes and instructions. We advise to check the release notes of - all versions between the one you're using and the new one. - - To upgrade the Gateway CRDs from [the Gateway API repo](https://github.com/kubernetes-sigs/gateway-api), run: - - ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml - ``` - - If you are running on Kubernetes 1.23 or 1.24, you also need to update the validating webhook. To do so, run: - - ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` - - If you are running on Kubernetes 1.25 or newer and have the validating webhook installed, you should remove the - webhook. To do so, run: - - ```shell - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` - -2. Upgrade the NGINX Gateway Fabric CRDs - - Run the following command to upgrade the NGINX Gateway Fabric CRDs: - - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml - ``` - -3. Upgrade NGINX Gateway Fabric Deployment - - Run the following command to upgrade NGINX Gateway Fabric: - - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml - ``` - -### Upgrade NGINX Gateway Fabric using Helm - -To upgrade NGINX Gateway Fabric when the deployment method is Helm, please follow the instructions -[here](/deploy/helm-chart/README.md#upgrading-the-chart). - -### Configure Delayed Termination for Zero Downtime Upgrades - -To achieve zero downtime upgrades (meaning clients will not see any interruption in traffic while a rolling upgrade is -being performed on NGF), you may need to configure delayed termination on the NGF Pod, depending on your environment. - -> **Note** -> -> When proxying Websocket or any long-lived connections, NGINX will not terminate until that connection is closed -> by either the client or the backend. This means that unless all those connections are closed by clients/backends -> before or during an upgrade, NGINX will not terminate, which means Kubernetes will kill NGINX. As a result, the -> clients will see the connections abruptly closed and thus experience downtime. - -#### Configure Delayed Termination Using Manifests - -Edit the `nginx-gateway.yaml` to include the following: - -1. Add `lifecycle` prestop hooks to both the nginx and the nginx-gateway container definitions: - - ```yaml - <...> - name: nginx-gateway - <...> - lifecycle: - preStop: - exec: - command: - - /usr/bin/gateway - - sleep - - --duration=40s # This flag is optional, the default is 30s - <...> - name: nginx - <...> - lifecycle: - preStop: - exec: - command: - - /bin/sleep - - "40" - <...> - ``` - -2. Ensure the `terminationGracePeriodSeconds` matches or exceeds the `sleep` value from the `preStopHook` (the default - is 30). This is to ensure Kubernetes does not terminate the Pod before the `preStopHook` is complete. - -> **Note** -> -> More information on container lifecycle hooks can be found -> [here](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks) and a detailed -> description of Pod termination behavior can be found in -> [Termination of Pods](https://kubernetes.io/docs/concepts/workloads/Pods/Pod-lifecycle/#Pod-termination). - -#### Configure Delayed Termination Using Helm - -To configure delayed termination on the NGF Pod when the deployment method is Helm, please follow the instructions -[here](/deploy/helm-chart/README.md#configure-delayed-termination-for-zero-downtime-upgrades). - -## Uninstalling NGINX Gateway Fabric - -### Uninstall NGINX Gateway Fabric from Manifests - -1. Uninstall the NGINX Gateway Fabric: - - ```shell - kubectl delete -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml - ``` - - ```shell - kubectl delete -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml - ``` - -2. Uninstall the Gateway API CRDs: - - >**Warning** - > - > This command will delete all the corresponding custom resources in your cluster across all namespaces! - > Please ensure there are no custom resources that you want to keep and there are no other Gateway API - > implementations running in the cluster! - - ```shell - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml - ``` - - If you are running on Kubernetes 1.23 or 1.24, you also need to delete the validating webhook. To do so, run: - - ```shell - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` - -### Uninstall NGINX Gateway Fabric using Helm - -To uninstall NGINX Gateway Fabric when the deployment method is Helm, please follow the instructions -[here](/deploy/helm-chart/README.md#uninstalling-the-chart). diff --git a/docs/monitoring.md b/docs/monitoring.md deleted file mode 100644 index e2dfe3c26..000000000 --- a/docs/monitoring.md +++ /dev/null @@ -1,106 +0,0 @@ -# Monitoring - -The NGINX Gateway Fabric exposes a number of metrics in the [Prometheus](https://prometheus.io/) format. Those -include NGINX and the controller-runtime metrics. These are delivered using a metrics server orchestrated by the -controller-runtime package. Metrics are enabled by default, and are served via http on port `9113`. - -> **Note** -> By default metrics are served via http. Please note that if serving metrics via https is enabled, this -> endpoint will be secured with a self-signed certificate. Since the metrics server is using a self-signed certificate, -> the Prometheus Pod scrape configuration will also require the `insecure_skip_verify` flag set. See -> [the Prometheus documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config). - -## Changing the default Metrics configuration - -### Using Helm - -If you're using *Helm* to install the NGINX Gateway Fabric, set the `metrics.*` parameters to the required values -for your environment. See the [Helm README](/deploy/helm-chart/README.md). - -### Using Manifests - -If you're using *Kubernetes manifests* to install NGINX Gateway Fabric, you can modify the -[manifest](/deploy/manifests/nginx-gateway.yaml) to change the default metrics configuration: - -#### Disabling metrics - -1. Set the `-metrics-disable` [command-line argument](/docs/cli-help.md) to `true` and remove the other `-metrics-*` - command line arguments. - -2. Remove the metrics port entry from the list of the ports of the NGINX Gateway Fabric container in the template - of the NGINX Gateway Fabric Pod: - - ```yaml - - name: metrics - containerPort: 9113 - ``` - -3. Remove the following annotations from the template of the NGINX Gateway Fabric Pod: - - ```yaml - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9113" - ``` - -#### Changing the default port - -1. Set the `-metrics-port` [command-line argument](/docs/cli-help.md) to the required value. - -2. Change the metrics port entry in the list of the ports of the NGINX Gateway Fabric container in the template - of the NGINX Gateway Fabric Pod: - - ```yaml - - name: metrics - containerPort: - ``` - -3. Change the following annotation in the template of the NGINX Gateway Fabric Pod: - - ```yaml - annotations: - <...> - prometheus.io/port: "" - <...> - ``` - -#### Enable serving metrics via https - -1. Set the `-metrics-secure-serving` [command-line argument](/docs/cli-help.md) to `true`. - -2. Add the following annotation in the template of the NGINX Gateway Fabric Pod: - - ```yaml - annotations: - <...> - prometheus.io/scheme: "https" - <...> - ``` - -## Available Metrics - -NGINX Gateway Fabric exports the following metrics: - -- NGINX metrics: - - Exported by NGINX. Refer to the [NGINX Prometheus Exporter developer docs](https://github.com/nginxinc/nginx-prometheus-exporter#metrics-for-nginx-oss) - - These metrics have the namespace `nginx_gateway_fabric`, and include the label `class` which is set to the - Gateway class of NGF. For example, `nginx_gateway_fabric_connections_accepted{class="nginx"}`. - -- NGINX Gateway Fabric metrics: - - nginx_reloads_total. Number of successful NGINX reloads. - - nginx_reload_errors_total. Number of unsuccessful NGINX reloads. - - nginx_stale_config. 1 means NGF failed to configure NGINX with the latest version of the configuration, which means - NGINX is running with a stale version. - - nginx_last_reload_milliseconds. Duration in milliseconds of NGINX reloads (histogram). - - event_batch_processing_milliseconds: Duration in milliseconds of event batch processing (histogram), which is the - time it takes NGF to process batches of Kubernetes events (changes to cluster resources). Note that NGF processes - events in batches, and while processing the current batch, it accumulates events for the next batch. - - These metrics have the namespace `nginx_gateway_fabric`, and include the label `class` which is set to the - Gateway class of NGF. For example, `nginx_gateway_fabric_nginx_reloads_total{class="nginx"}`. - -- [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) metrics. These include: - - Total number of reconciliation errors per controller - - Length of reconcile queue per controller - - Reconciliation latency - - Usual resource metrics such as CPU, memory usage, file descriptor usage - - Go runtime metrics such as number of Go routines, GC duration, and Go version information diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md deleted file mode 100644 index 4c97cd4ec..000000000 --- a/docs/troubleshooting.md +++ /dev/null @@ -1,11 +0,0 @@ -# Troubleshooting - -This document contains common or known issues and how to troubleshoot them. - -## failed to reload NGINX: failed to send the HUP signal to NGINX main: operation not permitted - -Depending on your environment's configuration, the control plane may not have the proper permissions to reload -NGINX. If NGINX configuration is not applied and you see the above error in the `nginx-gateway` logs, you will need -to set `allowPrivilegeEscalation` to `true`. If using Helm, you can set the -`nginxGateway.securityContext.allowPrivilegeEscalation` value. -If using the manifests directly, you can update this field under the `nginx-gateway` container's `securityContext`. diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 000000000..919b131ed --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,3 @@ +public +.hugo_build.lock +resources diff --git a/site/Makefile b/site/Makefile new file mode 100644 index 000000000..b5231e351 --- /dev/null +++ b/site/Makefile @@ -0,0 +1,92 @@ +HUGO?=hugo +# the officially recommended unofficial docker image +HUGO_IMG?=hugomods/hugo:0.115.3 + +THEME_MODULE = github.com/nginxinc/nginx-hugo-theme +## Pulls the current theme version from the Netlify settings +THEME_VERSION = $(NGINX_THEME_VERSION) +NETLIFY_DEPLOY_URL = ${DEPLOY_PRIME_URL} + +# if there's no local hugo, fallback to docker +ifeq (, $(shell ${HUGO} version 2> /dev/null)) +ifeq (, $(shell docker version 2> /dev/null)) + $(error Docker and Hugo are not installed. Hugo (<0.91) or Docker are required to build the local preview.) +else + HUGO=docker run --rm -it -v ${CURDIR}:/src -p 1313:1313 ${HUGO_IMG} hugo --bind 0.0.0.0 -p 1313 +endif +endif + +MARKDOWNLINT?=markdownlint +MARKDOWNLINT_IMG?=ghcr.io/igorshubovych/markdownlint-cli:latest + +# if there's no local markdownlint, fallback to docker +ifeq (, $(shell ${MARKDOWNLINT} version 2> /dev/null)) +ifeq (, $(shell docker version 2> /dev/null)) +ifneq (, $(shell $(NETLIFY) "true")) + $(error Docker and markdownlint are not installed. markdownlint or Docker are required to lint.) +endif +else + MARKDOWNLINT=docker run --rm -i -v ${CURDIR}:/src --workdir /src ${MARKDOWNLINT_IMG} +endif +endif + +MARKDOWNLINKCHECK?=markdown-link-check +MARKDOWNLINKCHECK_IMG?=ghcr.io/tcort/markdown-link-check:stable +# if there's no local markdown-link-check, fallback to docker +ifeq (, $(shell ${MARKDOWNLINKCHECK} --version 2> /dev/null)) +ifeq (, $(shell docker version 2> /dev/null)) +ifneq (, $(shell $(NETLIFY) "true")) + $(error Docker and markdown-link-check are not installed. markdown-link-check or Docker are required to check links.) +endif +else + MARKDOWNLINKCHECK=docker run --rm -it -v ${CURDIR}:/site --workdir /site ${MARKDOWNLINKCHECK_IMG} +endif +endif + + +.PHONY: all all-staging all-dev all-local clean hugo-mod build-production build-staging build-dev docs-drafts docs deploy-preview + +all: hugo-mod build-production + +all-staging: hugo-mod build-staging + +all-dev: hugo-mod build-dev + +all-local: clean hugo-mod build-production + +# Removes the public directory generated by the `hugo` command +clean: + if [[ -d ${PWD}/public ]] ; then rm -rf ${PWD}/public && echo "Removed public directory" ; else echo "Did not find a public directory to remove" ; fi + + +docs-drafts: + ${HUGO} server -D --disableFastRender + +docs-local: clean + ${HUGO} + +docs: + ${HUGO} server --disableFastRender + +lint-markdown: + ${MARKDOWNLINT} -c .markdownlint.yaml -- content + +link-check: + ${MARKDOWNLINKCHECK} $(shell find content -name '*.md') + + +## commands for use in Netlify CI +hugo-mod: + hugo mod get $(THEME_MODULE)@v$(THEME_VERSION) + +build-production: + hugo --gc -e production + +build-staging: + hugo --gc -e staging + +build-dev: + hugo --gc -e development + +deploy-preview: hugo-mod + hugo --gc -b ${NETLIFY_DEPLOY_URL} diff --git a/site/config/_default/config.toml b/site/config/_default/config.toml new file mode 100644 index 000000000..e269c1d8d --- /dev/null +++ b/site/config/_default/config.toml @@ -0,0 +1,68 @@ +title = "NGINX Gateway Fabric" +enableGitInfo = false +baseURL = "/" +publishDir = "public/nginx-gateway-fabric" +staticDir = ["static"] +languageCode = "en-us" +description = "NGINX Gateway Fabric." +refLinksErrorLevel = "ERROR" +enableRobotsTXT = "true" +#canonifyURLs = true +pluralizeListTitles = false +pygmentsCodeFences = true +pygmentsUseClasses = true + +[caches] + [caches.modules] + maxAge = -1 + +[module] +[[module.imports]] + path="github.com/nginxinc/nginx-hugo-theme" + +[markup] + [markup.highlight] + codeFences = true + guessSyntax = true + hl_Lines = "" + lineNoStart = 1 + lineNos = false + lineNumbersInTable = true + noClasses = true + style = "monokai" + tabWidth = 4 + [markup.goldmark] + [markup.goldmark.extensions] + definitionList = true + footnote = true + linkify = true + strikethrough = true + table = true + taskList = true + typographer = true + [markup.goldmark.parser] + attribute = true + autoHeadingID = true + autoHeadingIDType = "gitlab" + [markup.goldmark.renderer] + hardWraps = false + unsafe = true + xhtml = false + +[params] + useSectionPageLists = "false" + buildtype = "webdocs" + RSSLink = "/index.xml" + author = "NGINX Inc." # add your company name + github = "nginxinc" # add your github profile name + twitter = "@nginx" # add your twitter profile + #email = "" + noindex_kinds = [ + "taxonomy", + "taxonomyTerm" + ] + logo = "NGINX-product-icon.svg" + +sectionPagesMenu = "docs" + +ignoreFiles = [ "\\.sh$", "\\.DS_Store$", "\\.git.*$", "\\.txt$", "\\/config\\/.*", "README\\.*"] diff --git a/site/config/development/config.toml b/site/config/development/config.toml new file mode 100644 index 000000000..937119dff --- /dev/null +++ b/site/config/development/config.toml @@ -0,0 +1,3 @@ +baseURL = "https://docs-dev.nginx.com/nginx-gateway-fabric" +title = "DEV -- NGINX Gateway Fabric" +canonifyURLs = false diff --git a/site/config/production/config.toml b/site/config/production/config.toml new file mode 100644 index 000000000..f818400d2 --- /dev/null +++ b/site/config/production/config.toml @@ -0,0 +1,3 @@ +baseURL = "/nginx-gateway-fabric" +title = "NGINX Gateway Fabric" +canonifyURLs = false diff --git a/site/config/staging/config.toml b/site/config/staging/config.toml new file mode 100644 index 000000000..251ca5fdf --- /dev/null +++ b/site/config/staging/config.toml @@ -0,0 +1,3 @@ +baseURL = "https://docs-staging.nginx.com/nginx-gateway-fabric" +title = "STAGING -- NGINX Gateway Fabric" +canonifyURLs = false diff --git a/site/content/_index.md b/site/content/_index.md new file mode 100644 index 000000000..999aa3155 --- /dev/null +++ b/site/content/_index.md @@ -0,0 +1,7 @@ +--- +title: "Welcome to the NGINX Gateway Fabric documentation" +description: +weight: 300 +linkTitle: "NGINX Gateway Fabric" +menu: docs +--- diff --git a/site/content/changelog.md b/site/content/changelog.md new file mode 100644 index 000000000..cd22389b7 --- /dev/null +++ b/site/content/changelog.md @@ -0,0 +1,8 @@ +--- +title: "Changelog" +description: "No description" +weight: 10000 +toc: true +draft: true +docs: "DOCS-1358" +--- diff --git a/site/content/how-to/_index.md b/site/content/how-to/_index.md new file mode 100644 index 000000000..969045d4d --- /dev/null +++ b/site/content/how-to/_index.md @@ -0,0 +1,9 @@ +--- +title: "How-To Guides" +description: +weight: 300 +linkTitle: "Guides" +menu: + docs: + parent: NGINX Gateway Fabric +--- diff --git a/site/content/how-to/configuration/_index.md b/site/content/how-to/configuration/_index.md new file mode 100644 index 000000000..cd5e67c0e --- /dev/null +++ b/site/content/how-to/configuration/_index.md @@ -0,0 +1,9 @@ +--- +title: "Configuration" +description: +weight: 200 +linkTitle: "Configuration" +menu: + docs: + parent: How-To Guides +--- diff --git a/docs/control-plane-configuration.md b/site/content/how-to/configuration/control-plane-configuration.md similarity index 87% rename from docs/control-plane-configuration.md rename to site/content/how-to/configuration/control-plane-configuration.md index 2d2a13d03..89f4016ba 100644 --- a/docs/control-plane-configuration.md +++ b/site/content/how-to/configuration/control-plane-configuration.md @@ -1,6 +1,10 @@ -# Control Plane Configuration - -This document describes how to dynamically update the NGINX Gateway Fabric control plane configuration. +--- +title: "Control Plane Configuration" +description: "Learn how to dynamically update the NGINX Gateway Fabric control plane configuration." +weight: 100 +toc: true +docs: "DOCS-000" +--- ## Overview @@ -20,15 +24,19 @@ to reflect whether it is valid or not. ### Spec +{{< bootstrap-table "table table-striped table-bordered" >}} | name | description | type | required | |---------|-----------------------------------------------------------------|--------------------------|----------| | logging | Logging defines logging related settings for the control plane. | [logging](#speclogging) | no | +{{< /bootstrap-table >}} ### Spec.Logging +{{< bootstrap-table "table table-striped table-bordered" >}} | name | description | type | required | |-------|------------------------------------------------------------------------|--------|----------| | level | Level defines the logging level. Supported values: info, debug, error. | string | no | +{{< /bootstrap-table >}} ## Viewing and Updating the Configuration diff --git a/site/content/how-to/maintenance/_index.md b/site/content/how-to/maintenance/_index.md new file mode 100644 index 000000000..5c33e95bb --- /dev/null +++ b/site/content/how-to/maintenance/_index.md @@ -0,0 +1,9 @@ +--- +title: "Maintenance and Upgrades" +description: +weight: 400 +linkTitle: "Maintenance and Upgrades" +menu: + docs: + parent: How-To Guides +--- diff --git a/site/content/how-to/maintenance/upgrade-apps-without-downtime.md b/site/content/how-to/maintenance/upgrade-apps-without-downtime.md new file mode 100644 index 000000000..c39fb0e07 --- /dev/null +++ b/site/content/how-to/maintenance/upgrade-apps-without-downtime.md @@ -0,0 +1,125 @@ +--- +title: "Upgrade applications without downtime" +description: "Learn how to use NGINX Gateway Fabric to upgrade applications without downtime." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Overview + +{{< note >}} See the [Architecture document]({{< relref "/overview/gateway-architecture.md" >}}) to learn more about NGINX Gateway Fabric architecture.{{< /note >}} + +NGINX Gateway Fabric allows upgrading applications without downtime. To understand the upgrade methods, you need to be familiar with the NGINX features that help prevent application downtime: Graceful configuration reloads and upstream server updates. + +### Graceful configuration reloads + +If a relevant gateway API or built-in Kubernetes resource is changed, NGINX Gateway Fabric will update NGINX by regenerating the NGINX configuration. NGINX Gateway Fabric then sends a reload signal to the master NGINX process to apply the new configuration. + +We call such an operation a "reload", during which client requests are not dropped - which defines it as a graceful reload. + +This process is further explained in the [NGINX configuration documentation](https://nginx.org/en/docs/control.html?#reconfiguration). + +### Upstream server updates + +Endpoints frequently change during application upgrades: Kubernetes creates pods for the new version of an application and removes the old ones, creating and removing the respective endpoints as well. + +NGINX Gateway Fabric detects changes to endpoints by watching their corresponding [EndpointSlices](https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/). + +In an NGINX configuration, a service is represented as an [upstream](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream), and an endpoint as an [upstream server](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server). + +Adding and removing endpoints are two of the most common cases: + +- If an endpoint is added, NGINX Gateway Fabric adds an upstream server to NGINX that corresponds to the endpoint, then reloads NGINX. Next, NGINX will start proxying traffic to that endpoint. +- If an endpoint is removed, NGINX Gateway Fabric removes the corresponding upstream server from NGINX. After a reload, NGINX will stop proxying traffic to that server. However, it will finish proxying any pending requests to that server before switching to another endpoint. + +As long as you have more than one endpoint ready, clients won't experience downtime during upgrades. + +{{< note >}}It is good practice to configure a [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) in the deployment so that a pod can report when it is ready to receive traffic. Note that NGINX Gateway Fabric will not add any endpoint to NGINX that is not ready.{{< /note >}} + +## Prerequisites + +- You have deployed your application as a [deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) +- The pods of the deployment belong to a [service](https://kubernetes.io/docs/concepts/services-networking/service/) so that Kubernetes creates an [endpoint](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoints-v1/) for each pod. +- You have exposed the application to the clients via an [HTTPRoute](https://gateway-api.sigs.k8s.io/api-types/httproute/) resource that references that service. + +For example, an application can be exposed using a routing rule like below: + +```yaml +- matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: my-app + port: 80 +``` + +{{< note >}}See the [Cafe example](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/examples/cafe-example) for a basic example.{{< /note >}} + +The upgrade methods in the next sections cover: + +- Rolling deployment upgrades +- Blue-green deployments +- Canary releases + +## Rolling deployment upgrade + +To start a [rolling deployment upgrade](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-update-deployment), you update the deployment to use the new version tag of the application. As a result, Kubernetes terminates the pods with the old version and create new ones. By default, Kubernetes also ensures that some number of pods always stay available during the upgrade. + +This upgrade will add new upstream servers to NGINX and remove the old ones. As long as the number of pods (ready endpoints) during an upgrade does not reach zero, NGINX will be able to proxy traffic, and therefore prevent any downtime. + +This method does not require you to update the **HTTPRoute**. + +## Blue-green deployments + +With this method, you deploy a new version of the application (blue version) as a separate deployment, while the old version (green) keeps running and handling client traffic. Next, you switch the traffic from the green version to the blue. If the blue works as expected, you terminate the green. Otherwise, you switch the traffic back to the green. + +There are two ways to switch the traffic: + +- Update the service selector to select the pods of the blue version instead of the green. As a result, NGINX Gateway Fabric removes the green upstream servers from NGINX and adds the blue ones. With this approach, it is not necessary to update the **HTTPRoute**. +- Create a separate service for the blue version and update the backend reference in the **HTTPRoute** to reference this service, which leads to the same result as with the previous option. + +## Canary releases + +Canary releases involve gradually introducing a new version of your application to a subset of nodes in a controlled manner, splitting the traffic between the old are new (canary) release. This allows for monitoring and testing the new release's performance and reliability before full deployment, helping to identify and address issues without impacting the entire user base. + +To support canary releases, you can implement an approach with two deployments behind the same service (see [Canary deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#canary-deployment) in the Kubernetes documentation). However, this approach lacks precision for defining the traffic split between the old and the canary version. You can greatly influence it by controlling the number of pods (for example, four pods of the old version and one pod of the canary). However, note that NGINX Gateway Fabric uses [`random two least_conn`](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#random) load balancing method, which doesn't guarantee an exact split based on the number of pods (80/20 in the given example). + +A more flexible and precise way to implement canary releases is to configure a traffic split in an **HTTPRoute**. In this case, you create a separate deployment for the new version with a separate service. For example, for the rule below, NGINX will proxy 95% of the traffic to the old version endpoints and only 5% to the new ones. + +```yaml +- matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: my-app-old + port: 80 + weight: 95 + - name: my-app-new + port: 80 + weight: 5 +``` + +{{< note >}}Every request coming from the same client won't necessarily be sent to the same backend. NGINX will independently split each request among the backend references.{{< /note >}} + +By updating the rule you can further increase the share of traffic the new version gets and finally completely switch to the new version: + +```yaml +- matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: my-app-old + port: 80 + weight: 0 + - name: my-app-new + port: 80 + weight: 1 +``` + +See the [Traffic splitting example](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/examples/traffic-splitting) from our repository. diff --git a/site/content/how-to/monitoring/_index.md b/site/content/how-to/monitoring/_index.md new file mode 100644 index 000000000..0213ee98e --- /dev/null +++ b/site/content/how-to/monitoring/_index.md @@ -0,0 +1,9 @@ +--- +title: "Monitoring and Troubleshooting" +description: +weight: 500 +linkTitle: "Monitoring and Troubleshooting" +menu: + docs: + parent: How-To Guides +--- diff --git a/site/content/how-to/monitoring/monitoring.md b/site/content/how-to/monitoring/monitoring.md new file mode 100644 index 000000000..de75eef20 --- /dev/null +++ b/site/content/how-to/monitoring/monitoring.md @@ -0,0 +1,117 @@ +--- +title: "Monitoring NGINX Gateway Fabric" +description: "Learn how to monitor your NGINX Gateway Fabric effectively. This guide provides easy steps for configuring monitoring settings and understanding key performance metrics." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Overview + + +NGINX Gateway Fabric metrics are displayed in [Prometheus](https://prometheus.io/) format, simplifying monitoring. You can track NGINX and controller-runtime metrics through a metrics server orchestrated by the controller-runtime package. These metrics are enabled by default and can be accessed on HTTP port `9113`. + + +{{}} +Metrics are served over HTTP by default. Enabling HTTPS will secure the metrics endpoint with a self-signed certificate. When using HTTPS, adjust the Prometheus Pod scrape settings by adding the `insecure_skip_verify` flag to handle the self-signed certificate. For further details, refer to the [Prometheus documentation](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#tls_config). +{{}} + +## How to change the default metrics configuration + +Configuring NGINX Gateway Fabric for monitoring is straightforward. You can change metric settings using Helm or Kubernetes manifests, depending on your setup. + +### Using Helm + +If you're setting up NGINX Gateway Fabric with Helm, you can adjust the `metrics.*` parameters to fit your needs. For detailed options and instructions, see the [Helm README](/deploy/helm-chart/README.md). + +### Using Kubernetes manifests + +For setups using Kubernetes manifests, change the metrics configuration by editing the [NGINX Gateway manifest](/deploy/manifests/nginx-gateway.yaml). + +#### Disabling metrics + +If you need to disable metrics: + +1. Set the `-metrics-disable` [command-line argument]({{< relref "reference/cli-help.md">}}) to `true` in the NGINX Gateway Fabric Pod's configuration. Remove any other `-metrics-*` arguments. +2. In the Pod template for NGINX Gateway Fabric, delete the metrics port entry from the container ports list: + + ```yaml + - name: metrics + containerPort: 9113 + ``` + +3. Also, remove the following annotations from the NGINX Gateway Fabric Pod template: + + ```yaml + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "9113" + ``` + +#### Changing the default port + +To change the default port for metrics: + +1. Update the `-metrics-port` [command-line argument]({{< relref "reference/cli-help.md">}}) in the NGINX Gateway Fabric Pod's configuration to your chosen port number. +2. In the Pod template, change the metrics port entry to reflect the new port: + + ```yaml + - name: metrics + containerPort: + ``` + +3. Modify the `prometheus.io/port` annotation in the Pod template to match the new port: + + ```yaml + annotations: + <...> + prometheus.io/port: "" + <...> + ``` + +#### Enabling HTTPS for metrics + +For enhanced security with HTTPS: + +1. Enable HTTPS security by setting the `-metrics-secure-serving` [command-line argument]({{< relref "reference/cli-help.md">}}) to `true` in the NGINX Gateway Fabric Pod's configuration. + +2. Add an HTTPS scheme annotation to the Pod template: + + ```yaml + annotations: + <...> + prometheus.io/scheme: "https" + <...> + ``` + +## Available metrics in NGINX Gateway Fabric + +NGINX Gateway Fabric provides a variety of metrics to assist in monitoring and analyzing performance. These metrics are categorized as follows: + +### NGINX metrics + +NGINX metrics, essential for monitoring specific NGINX operations, include details like the total number of accepted client connections. For a complete list of available NGINX metrics, refer to the [NGINX Prometheus Exporter developer docs](https://github.com/nginxinc/nginx-prometheus-exporter#metrics-for-nginx-oss). + +These metrics use the `nginx_gateway_fabric` namespace and include the `class` label, indicating the NGINX Gateway class. For example, `nginx_gateway_fabric_connections_accepted{class="nginx"}`. + +### NGINX Gateway Fabric metrics + +Metrics specific to the NGINX Gateway Fabric include: + +- `nginx_reloads_total`: Counts successful NGINX reloads. +- `nginx_reload_errors_total`: Counts NGINX reload failures. +- `nginx_stale_config`: Indicates if NGINX Gateway Fabric couldn't update NGINX with the latest configuration, resulting in a stale version. +- `nginx_last_reload_milliseconds`: Time in milliseconds for NGINX reloads. +- `event_batch_processing_milliseconds`: Time in milliseconds to process batches of Kubernetes events. + +All these metrics are under the `nginx_gateway_fabric` namespace and include a `class` label set to the Gateway class of NGINX Gateway Fabric. For example, `nginx_gateway_fabric_nginx_reloads_total{class="nginx"}`. + +### Controller-runtime metrics + +Provided by the [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) library, these metrics cover a range of aspects: + +- General resource usage like CPU and memory. +- Go runtime metrics such as the number of Go routines, garbage collection duration, and Go version. +- Controller-specific metrics, including reconciliation errors per controller, length of the reconcile queue, and reconciliation latency. diff --git a/site/content/how-to/monitoring/troubleshooting.md b/site/content/how-to/monitoring/troubleshooting.md new file mode 100644 index 000000000..5c68a3288 --- /dev/null +++ b/site/content/how-to/monitoring/troubleshooting.md @@ -0,0 +1,23 @@ +--- +title: "Troubleshooting" + +weight: 200 +toc: true +docs: "DOCS-000" +--- + +{{< custom-styles >}} + +This topic describes possible issues users might encounter when using NGINX Gateway Fabric. When possible, suggested workarounds are provided. + +### NGINX fails to reload + +#### Description + +Depending on your environment's configuration, the control plane may not have the proper permissions to reload NGINX. The NGINX configuration will not be applied and you will see the following error in the _nginx-gateway_ logs: `failed to reload NGINX: failed to send the HUP signal to NGINX main: operation not permitted` + +#### Resolution +To resolve this issue you will need to set `allowPrivilegeEscalation` to `true`. + +- If using Helm, you can set the `nginxGateway.securityContext.allowPrivilegeEscalation` value. +- If using the manifests directly, you can update this field under the `nginx-gateway` container's `securityContext`. diff --git a/site/content/how-to/traffic-management/_index.md b/site/content/how-to/traffic-management/_index.md new file mode 100644 index 000000000..8bf8679ea --- /dev/null +++ b/site/content/how-to/traffic-management/_index.md @@ -0,0 +1,9 @@ +--- +title: "Traffic Management" +description: +weight: 300 +linkTitle: "Traffic Management" +menu: + docs: + parent: How-To Guides +--- diff --git a/docs/guides/advanced-routing.md b/site/content/how-to/traffic-management/advanced-routing.md similarity index 69% rename from docs/guides/advanced-routing.md rename to site/content/how-to/traffic-management/advanced-routing.md index 81f67fdf0..b47ff1607 100644 --- a/docs/guides/advanced-routing.md +++ b/site/content/how-to/traffic-management/advanced-routing.md @@ -1,23 +1,23 @@ -# Routing to Applications Using HTTP Matching Conditions +--- +title: "Routing to Applications Using HTTP Matching Conditions" +description: "Learn how to deploy multiple applications and HTTPRoutes with request conditions such as paths, methods, headers, and query parameters" +weight: 200 +toc: true +docs: "DOCS-000" +--- -In this guide we will configure advanced routing rules for multiple applications. These rules will showcase request -matching by path, headers, query parameters, and method. For an introduction to exposing your application, it is -recommended to go through the [basic guide](/docs/guides/routing-traffic-to-your-app.md) first. +In this guide we will configure advanced routing rules for multiple applications. These rules will showcase request matching by path, headers, query parameters, and method. For an introduction to exposing your application, we recommend that you follow the [basic guide]({{< relref "/how-to/traffic-management/routing-traffic-to-your-app.md" >}}) first. The following image shows the traffic flow that we will be creating with these rules. -![Traffic Flow Diagram](/docs/images/advanced-routing.png) +{{Traffic Flow Diagram}} -The goal is to create a set of rules that will result in client requests being sent to specific backends based on -the request attributes. In this diagram, we have two versions of the `coffee` service. Traffic for v1 needs to be -directed to the old application, while traffic for v2 needs to be directed towards the new application. We also -have two `tea` services, one that handles GET operations and one that handles POST operations. Both the `tea` -and `coffee` applications share the same Gateway. +The goal is to create a set of rules that will result in client requests being sent to specific backends based on the request attributes. In this diagram, we have two versions of the `coffee` service. Traffic for v1 needs to be directed to the old application, while traffic for v2 needs to be directed towards the new application. We also have two `tea` services, one that handles GET operations and one that handles POST operations. Both the `tea` and `coffee` applications share the same Gateway. ## Prerequisites -- [Install](/docs/installation.md) NGINX Gateway Fabric. -- [Expose NGINX Gateway Fabric](/docs/installation.md#expose-nginx-gateway-fabric) and save the public IP +- [Install]({{< relref "/installation/" >}}) NGINX Gateway Fabric. +- [Expose NGINX Gateway Fabric]({{< relref "installation/expose-nginx-gateway-fabric.md" >}}) and save the public IP address and port of NGINX Gateway Fabric into shell variables: ```text @@ -25,9 +25,7 @@ and `coffee` applications share the same Gateway. GW_PORT= ``` -> **Note** -> In a production environment, you should have a DNS record for the external IP address that is exposed, -> and it should refer to the hostname that the gateway will forward for. +{{< note >}}In a production environment, you should have a DNS record for the external IP address that is exposed, and it should refer to the hostname that the gateway will forward for.{{< /note >}} ## Coffee Applications @@ -41,8 +39,7 @@ kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric ### Deploy the Gateway API Resources for the Coffee Applications -The [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/) resource is typically deployed by the -[cluster operator][roles-and-personas]. To deploy the Gateway: +The [gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/) resource is typically deployed by the [cluster operator](https://gateway-api.sigs.k8s.io/concepts/roles-and-personas/#roles-and-personas_1). To deploy the gateway: ```yaml kubectl apply -f - < **Note** -> If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that -> hostname, without needing to resolve. +{{< note >}}If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that hostname, without needing to resolve.{{< /note >}} ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee @@ -138,8 +128,7 @@ Server address: 10.244.0.9:8080 Server name: coffee-v2-68bd55f798-s9z5q ``` -If we want our request to be routed to `coffee-v2`, then we need to meet the defined conditions. We can include -a header: +If we want our request to be routed to `coffee-v2`, then we need to meet the defined conditions. We can include a header: ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/coffee -H "version:v2" @@ -160,8 +149,7 @@ Server name: coffee-v2-68bd55f798-s9z5q ## Tea Applications -Let's deploy a different set of applications now called `tea` and `tea-post`. These applications will -have their own set of rules, but will still attach to the same Gateway listener as the `coffee` apps. +Let's deploy a different set of applications now called `tea` and `tea-post`. These applications will have their own set of rules, but will still attach to the same gateway listener as the `coffee` apps. ### Deploy the Tea Applications @@ -171,7 +159,7 @@ kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric ### Deploy the HTTPRoute for the Tea Services -We are reusing the previous Gateway for these applications, so all we need to create is the HTTPRoute. +We are reusing the previous gateway for these applications, so all we need to create is the HTTPRoute. ```yaml kubectl apply -f - < **Note** -> If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that -> hostname, without needing to resolve. +{{< note >}}If you have a DNS record allocated for `cafe.example.com`, you can send the request directly to that hostname, without needing to resolve.{{< /note >}} ```shell curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/tea @@ -242,16 +227,14 @@ Server address: 10.244.0.7:8080 Server name: tea-post-b59b8596b-g586r ``` -This request should receive a response from the `tea-post` Pod. Any other type of method, such as PATCH, will -result in a `404 Not Found` response. +This request should receive a response from the `tea-post` pod. Any other type of method, such as PATCH, will result in a `404 Not Found` response. ## Troubleshooting If you have any issues while sending traffic, try the following to debug your configuration and setup: -- Make sure you set the shell variables $GW_IP and $GW_PORT to the public IP and port of the NGINX Gateway Fabric - Service. Instructions for finding those values are [here](/docs/installation.md#expose-nginx-gateway-fabric). +- Make sure you set the shell variables $GW_IP and $GW_PORT to the public IP and port of the NGINX Gateway Fabric service. Refer to the topic [Expose NGINX Gateway Fabric]({{< relref "installation/expose-nginx-gateway-fabric.md" >}}) for instructions on finding those values. - Check the status of the Gateway: @@ -309,8 +292,7 @@ If you have any issues while sending traffic, try the following to debug your co Name: http ``` - Check that the conditions match and that the attached routes for the `http` listener equals 2. If it is less than - 2, there may be an issue with the routes. + Check that the conditions match and that the attached routes for the `http` listener equals 2. If it is less than 2, there may be an issue with the routes. - Check the status of the HTTPRoutes: @@ -352,7 +334,7 @@ If you have any issues while sending traffic, try the following to debug your co ## Further Reading -To learn more about the Gateway API and the resources we created in this guide, check out the following resources: +To learn more about the Gateway API and the resources we created in this guide, check out the following Kubernetes documentation resources: - [Gateway API Overview](https://gateway-api.sigs.k8s.io/concepts/api-overview/) - [Deploying a simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/) diff --git a/docs/guides/integrating-cert-manager.md b/site/content/how-to/traffic-management/integrating-cert-manager.md similarity index 50% rename from docs/guides/integrating-cert-manager.md rename to site/content/how-to/traffic-management/integrating-cert-manager.md index 78b5b78b7..d417edec3 100644 --- a/docs/guides/integrating-cert-manager.md +++ b/site/content/how-to/traffic-management/integrating-cert-manager.md @@ -1,60 +1,44 @@ -# Securing Traffic using Let's Encrypt and Cert-Manager +--- +title: "Securing Traffic using Let's Encrypt and Cert-Manager" +description: "Learn how to issue and mange certificates using Let's Encrypt and cert-manager." +weight: 300 +toc: true +docs: "DOCS-000" +--- -Securing client server communication is a crucial part of modern application architectures. One of the most important -steps in this process is implementing HTTPS (HTTP over TLS/SSL) for all communications. This encrypts the data -transmitted between the client and server, preventing eavesdropping and tampering. To do this, you need an SSL/TLS -certificate from a trusted Certificate Authority (CA). However, issuing and managing certificates can be a complicated -manual process. Luckily, there are many services and tools available to simplify and automate certificate issuance and -management. +Securing client server communication is a crucial part of modern application architectures. One of the most important steps in this process is implementing HTTPS (HTTP over TLS/SSL) for all communications. This encrypts the data transmitted between the client and server, preventing eavesdropping and tampering. To do this, you need an SSL/TLS certificate from a trusted Certificate Authority (CA). However, issuing and managing certificates can be a complicated manual process. Luckily, there are many services and tools available to simplify and automate certificate issuance and management. -This guide will demonstrate how to: +Follow the steps in this guide to: -- Configure HTTPS for your application using a [Gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/). +- Configure HTTPS for your application using a [gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/). - Use [Let’s Encrypt](https://letsencrypt.org) as the Certificate Authority (CA) issuing the TLS certificate. - Use [cert-manager](https://cert-manager.io) to automate the provisioning and management of the certificate. ## Prerequisities -1. Administrator access to a Kubernetes cluster. -2. [Helm](https://helm.sh) and [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) must be installed locally. -3. Deploy NGINX Gateway Fabric (NGF) following the [deployment instructions](/docs/installation.md). -4. A DNS resolvable domain name is required. It must resolve to the public endpoint of the NGF deployment, and this - public endpoint must be an external IP address or alias accessible over the internet. The process here will depend - on your DNS provider. This DNS name will need to be resolvable from the Let’s Encrypt servers, which may require - that you wait for the record to propagate before it will work. +- Administrator access to a Kubernetes cluster. +- [Helm](https://helm.sh) and [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) must be installed locally. +- [NGINX Gateway Fabric deployed]({{< relref "/installation/" >}}) in the Kubernetes cluster. +- A DNS-resolvable domain name is required. It must resolve to the public endpoint of the NGINX Gateway Fabric deployment, and this public endpoint must be an external IP address or alias accessible over the internet. The process here will depend on your DNS provider. This DNS name will need to be resolvable from the Let’s Encrypt servers, which may require that you wait for the record to propagate before it will work. ## Overview +{{cert-manager ACME challenge and certificate management with Gateway API}} -![cert-manager ACME Challenge and certificate management with Gateway API](/docs/images/cert-manager-gateway-workflow.png) - -The diagram above shows a simplified representation of the cert-manager ACME Challenge and certificate issuance process -using Gateway API. Please note that not all of the Kubernetes objects created in this process are represented in -this diagram. +The diagram above shows a simplified representation of the cert-manager ACME challenge and certificate issuance process using Gateway API. Please note that not all of the kubernetes objects created in this process are represented in this diagram. At a high level, the process looks like this: -1. We deploy cert-manager and create a ClusterIssuer which specifies Let’s Encrypt as our CA and Gateway as our ACME - HTTP01 Challenge solver. -2. We create a Gateway resource for our domain (cafe.example.com) and configure cert-manager integration using an - annotation. -3. This kicks off the certificate issuance process – cert-manager contacts Let’s Encrypt to obtain a certificate, and - Let’s Encrypt starts the ACME challenge. As part of this challenge, a temporary HTTPRoute resource is created by - cert-manager which directs the traffic through NGF to verify we control the domain name in the certificate request. -4. Once the domain has been verified, the certificate is issued. Cert-manager stores the keypair in a Kubernetes secret - that is referenced by the Gateway resource. As a result, NGINX is configured to terminate HTTPS traffic from clients - using this signed keypair. -5. We deploy our application and our HTTPRoute which defines our routing rules. The routing rules defined configure - NGINX to direct requests to https://cafe.example.com/coffee to our coffee-app application, and to use the https - Listener defined in our Gateway resource. -6. When the client connects to https://cafe.example.com/coffee, the request is routed to the coffee-app application - and the communication is secured using the signed keypair contained in the cafe-secret Secret. -7. The certificate will be automatically renewed when it is close to expiry, the Secret will be updated using the new - Certificate, and NGF will dynamically update the keypair on the filesystem used by NGINX for HTTPS termination once - the Secret is updated. - -## Details - -### Step 1 – Deploy cert-manager +1. We deploy cert-manager and create a ClusterIssuer which specifies Let’s Encrypt as our CA and gateway as our ACME HTTP01 challenge solver. +1. We create a gateway resource for our domain (cafe.example.com) and configure cert-manager integration using an annotation. +1. This starts the certificate issuance process – cert-manager contacts Let’s Encrypt to obtain a certificate, and Let’s Encrypt starts the ACME challenge. As part of this challenge, cert-manager creates a temporary HTTPRoute resource which directs the traffic through NGINX Gateway Fabric to verify we control the domain name in the certificate request. +1. Once the domain has been verified, the certificate is issued. Cert-manager stores the keypair in a Kubernetes secret that is referenced by the gateway resource. As a result, NGINX is configured to terminate HTTPS traffic from clients using this signed keypair. +1. We deploy our application and our HTTPRoute which defines our routing rules. The routing rules defined configure NGINX to direct requests to https://cafe.example.com/coffee to our coffee-app application, and to use the HTTPS listener defined in our gateway resource. +1. When the client connects to https://cafe.example.com/coffee, the request is routed to the coffee-app application and the communication is secured using the signed keypair contained in the cafe-secret secret. +1. The certificate will be automatically renewed when it is close to expiry, the secret will be updated using the new certificate, and NGINX Gateway Fabric will dynamically update the keypair on the filesystem used by NGINX for HTTPS termination once the secret is updated. + +## Securing Traffic + +### Deploy cert-manager The first step is to deploy cert-manager onto the cluster. @@ -77,18 +61,11 @@ The first step is to deploy cert-manager onto the cluster. --set "extraArgs={--feature-gates=ExperimentalGatewayAPISupport=true}" ``` -### Step 2 – Create a ClusterIssuer +### Create a ClusterIssuer -Next we need to create a [ClusterIssuer](https://cert-manager.io/docs/concepts/issuer/), a Kubernetes resource that -represents the certificate authority (CA) that will generate the signed certificates by honouring certificate signing -requests. +Next we need to create a [ClusterIssuer](https://cert-manager.io/docs/concepts/issuer/), a Kubernetes resource that represents the certificate authority (CA) that will generate the signed certificates by honouring certificate signing requests. -We are using the ACME Issuer type, and Let's Encrypt as the CA server. In order for Let's Encypt to verify that we own -the domain a certificate is being requested for, we must complete "challenges". This is to ensure clients are -unable to request certificates for domains they do not own. We will configure the Issuer to use a HTTP01 challenge, and -our Gateway resource that we will create in the next step as the solver. To read more about HTTP01 challenges, see -[here](https://cert-manager.io/docs/configuration/acme/http01/). Use the following YAML definition to create the -resource, but please note the `email` field must be updated to your own email address. +We are using the ACME Issuer type, and Let's Encrypt as the CA server. In order for Let's Encypt to verify that we own the domain a certificate is being requested for, we must complete "challenges". This is to ensure clients are unable to request certificates for domains they do not own. We will configure the issuer to use a HTTP01 challenge, and our gateway resource that we will create in the next step as the solver. To read more about HTTP01 challenges, see the [cert-manager documentation](https://cert-manager.io/docs/configuration/acme/http01/). Use the following YAML definition to create the resource, but please note the `email` field must be updated to your own email address. ```yaml apiVersion: cert-manager.io/v1 @@ -105,7 +82,7 @@ spec: privateKeySecretRef: # Secret resource that will be used to store the account's private key. name: issuer-account-key - # Add a single challenge solver, HTTP01 using NGF + # Add a single challenge solver, HTTP01 using NGINX Gateway Fabric solvers: - http01: gatewayHTTPRoute: @@ -115,10 +92,9 @@ spec: kind: Gateway ``` -### Step 3 – Deploy our Gateway with the cert-manager annotation +### Deploy our Gateway with the cert-manager annotation -Next we need to deploy our Gateway. Use can use the below YAML manifest, updating the `spec.listeners[1].hostname` -field to the required value for your environment. +Next we need to deploy our gateway. You can use the YAML manifest below, updating the `spec.listeners[1].hostname` field to the required value for your environment. ```yaml apiVersion: gateway.networking.k8s.io/v1 @@ -147,22 +123,13 @@ spec: It's worth noting a couple of key details in this manifest: -- The cert-manager annotation is present in the metadata – this enables the cert-manager integration, and tells - cert-manager which ClusterIssuer configuration it should use for the certificates. -- There are two Listeners configured, an HTTP Listener on port 80, and an HTTPS Listener on port 443. - - The http Listener on port 80 is required for the HTTP01 ACME challenge to work. This is because as part of the - HTTP01 Challenge, a temporary HTTPRoute will be created by cert-manager to solve the ACME challenge, and this - HTTPRoute requires a Listener on port 80. See the [HTTP01 Gateway API solver documentation](https://cert-manager.io/docs/configuration/acme/http01/#configuring-the-http-01-gateway-api-solver) - for more information. - - The https Listener on port 443 is the Listener we will use in our HTTPRoute in the next step. Cert-manager will - create a Certificate for this Listener block. -- The hostname needs to set to the required value. A new certificate will be issued from the `letsencrypt-prod` - ClusterIssuer for the domain, e.g. "cafe.example.com", once the ACME challenge is successful. - -Once the certificate has been issued, cert-manager will create a Certificate resource on the cluster and the -`cafe-secret` Secret containing the signed keypair in the same Namespace as the Gateway. We can verify the Secret has -been created successfully using `kubectl`. Note it will take a little bit of time for the Challenge to complete and the -Secret to be created: +- The cert-manager annotation is present in the metadata – this enables the cert-manager integration, and tells cert-manager which ClusterIssuer configuration it should use for the certificates. +- There are two listeners configured, an HTTP listener on port 80, and an HTTPS listener on port 443. + - The HTTP listener on port 80 is required for the HTTP01 ACME challenge to work. This is because as part of the HTTP01 challenge, a temporary HTTPRoute will be created by cert-manager to solve the ACME challenge, and this HTTPRoute requires a listener on port 80. See the [HTTP01 Gateway API solver documentation](https://cert-manager.io/docs/configuration/acme/http01/#configuring-the-http-01-gateway-api-solver) for more information. + - The HTTPS listener on port 443 is the listener we will use in our HTTPRoute in the next step. Cert-manager will create a certificate for this listener block. +- The hostname needs to set to the required value. A new certificate will be issued from the `letsencrypt-prod` ClusterIssuer for the domain, e.g. "cafe.example.com", once the ACME challenge is successful. + +Once the certificate has been issued, cert-manager will create a certificate resource on the cluster and the `cafe-secret` Secret containing the signed keypair in the same Namespace as the gateway. We can verify the secret has been created successfully using `kubectl`. Note it will take a little bit of time for the challenge to complete and the secret to be created: ```shell kubectl get secret cafe-secret @@ -173,9 +140,8 @@ NAME TYPE DATA AGE cafe-secret kubernetes.io/tls 2 20s ``` -### Step 4 – Deploy our application and HTTPRoute -Now we can create our coffee Deployment and Service, and configure the routing rules. You can use the following manifest -to create the Deployment and Service: +### Deploy our application and HTTPRoute +Now we can create our coffee deployment and service, and configure the routing rules. You can use the following manifest to create the deployment and service: ```yaml apiVersion: apps/v1 @@ -212,8 +178,7 @@ spec: app: coffee ``` -Deploy our HTTPRoute to configure our routing rules for the coffee application. Note the `parentRefs` section in the -spec refers to the Listener configured in the previous step. +Deploy our HTTPRoute to configure our routing rules for the coffee application. Note the `parentRefs` section in the spec refers to the listener configured in the previous step. ```yaml apiVersion: gateway.networking.k8s.io/v1 @@ -238,14 +203,14 @@ spec: ## Testing -To test everything has worked correctly, we can use curl to the navigate to our endpoint, e.g. -https://cafe.example.com/coffee. To verify using curl, we can use the `-v` option to increase verbosity and inspect the -presented certificate. The output will look something like this: +To test everything has worked correctly, we can use curl to the navigate to our endpoint, for example, https://cafe.example.com/coffee. To verify using curl, we can use the `-v` option to increase verbosity and inspect the presented certificate. ```shell curl https://cafe.example.com/coffee -v ``` +The output will look similar to this: + ```text * Trying 54.195.47.105:443... * Connected to cafe.example.com (54.195.47.105) port 443 (#0) @@ -293,14 +258,10 @@ Request ID: e64c54a2ac253375ac085d48980f000a ## Troubleshooting -- For troubeshooting anything related to the cert-manager installation or Issuer setup, see - [the cert-manager troubleshooting guide](https://cert-manager.io/docs/troubleshooting/). -- For troubleshooting the HTTP01 ACME Challenge, please see the cert-manager - [ACME troubleshooting guide](https://cert-manager.io/docs/troubleshooting/acme/). - - Note that for the HTTP01 Challenge to work using the Gateway resource, HTTPS redirect must not be configured. - - The temporary HTTPRoute created by cert-manager routes the traffic between cert-manager and the Let's Encrypt server - through NGF. If the Challenge is not successful, it may be useful to inspect the NGINX logs to see the ACME - Challenge requests. You should see something like the following: +- To troubleshoot any issues related to the cert-manager installation or issuer setup, see [the cert-manager troubleshooting guide](https://cert-manager.io/docs/troubleshooting/). +- To troubleshoot the HTTP01 ACME challenge, please see the cert-manager [ACME troubleshooting guide](https://cert-manager.io/docs/troubleshooting/acme/). + - Note that for the HTTP01 challenge to work using the gateway resource, HTTPS redirect must not be configured. + - The temporary HTTPRoute created by cert-manager routes the traffic between cert-manager and the Let's Encrypt server through NGINX Gateway Fabric. If the challenge is not successful, it may be useful to inspect the NGINX logs to see the ACME challenge requests. You should see something like the following: ```shell kubectl logs -n nginx-gateway -c nginx @@ -318,8 +279,8 @@ Request ID: e64c54a2ac253375ac085d48980f000a ## Links -- Gateway docs: https://gateway-api.sigs.k8s.io -- Cert-manager Gateway usage: https://cert-manager.io/docs/usage/gateway/ -- Cert-manager ACME: https://cert-manager.io/docs/configuration/acme/ -- Let’s Encrypt: https://letsencrypt.org -- NGINX HTTPS docs: https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/ +- [Gateway docs](https://gateway-api.sigs.k8s.io) +- [Cert-manager gateway usage](https://cert-manager.io/docs/usage/gateway/) +- [Cert-manager ACME](https://cert-manager.io/docs/configuration/acme/) +- [Let’s Encrypt](https://letsencrypt.org) +- [NGINX HTTPS docs](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/) diff --git a/site/content/how-to/traffic-management/routing-traffic-to-your-app.md b/site/content/how-to/traffic-management/routing-traffic-to-your-app.md new file mode 100644 index 000000000..21c0c3f94 --- /dev/null +++ b/site/content/how-to/traffic-management/routing-traffic-to-your-app.md @@ -0,0 +1,371 @@ +--- +title: "Routing Traffic to Your Application" +description: "Learn how to route external traffic to your Kubernetes applications using NGINX Gateway Fabric." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Overview + +You can route traffic to your Kubernetes applications using the Gateway API and NGINX Gateway Fabric. Whether you're managing a web application or a REST backend API, you can use NGINX Gateway Fabric to expose your application outside the cluster. + +## Prerequisites + +- [Install]({{< relref "installation/" >}}) NGINX Gateway Fabric. +- [Expose NGINX Gateway Fabric]({{< relref "installation/expose-nginx-gateway-fabric.md" >}}) and save the public IP address and port of NGINX Gateway Fabric into shell variables: + + ```text + GW_IP=XXX.YYY.ZZZ.III + GW_PORT= + ``` + +## Example application + +The application we are going to use in this guide is a simple **coffee** application comprised of one service and two pods: + +{{coffee app}} + +Using this architecture, the **coffee** application is not accessible outside the cluster. We want to expose this application on the hostname "cafe.example.com" so that clients outside the cluster can access it. + +Install NGINX Gateway Fabric and create two Gateway API resources: a [gateway](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.Gateway) and an [HTTPRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.HTTPRoute). + +Using these resources we will configure a simple routing rule to match all HTTP traffic with the hostname "cafe.example.com" and route it to the **coffee** service. + +## Set up + +Create the **coffee** application in Kubernetes by copying and pasting the following block into your terminal: + +```yaml +kubectl apply -f - < 80/TCP 77s +``` + +## Application architecture with NGINX Gateway Fabric + +To route traffic to the **coffee** application, we will create a gateway and HTTPRoute. The following diagram shows the configuration we are creating in the next step: + +{{Configuration}} + +We need a gateway to create an entry point for HTTP traffic coming into the cluster. The **cafe** gateway we are going to create will open an entry point to the cluster on port 80 for HTTP traffic. + +To route HTTP traffic from the gateway to the **coffee** service, we need to create an HTTPRoute named **coffee** and attach it to the gateway. This HTTPRoute will have a single routing rule that routes all traffic to the hostname "cafe.example.com" from the gateway to the **coffee** service. + +Once NGINX Gateway Fabric processes the **cafe** gateway and **coffee** HTTPRoute, it will configure its data plane (NGINX) to route all HTTP requests sent to "cafe.example.com" to the pods that the **coffee** service targets: + +{{Traffic Flow}} + +The **coffee** service is omitted from the diagram above because the NGINX Gateway Fabric routes directly to the pods that the **coffee** service targets. + +{{< note >}}In the diagrams above, all resources that are the responsibility of the cluster operator are shown in blue. The orange resources are the responsibility of the application developers. + +See the [roles and personas](https://gateway-api.sigs.k8s.io/concepts/roles-and-personas/#roles-and-personas_1) Gateway API document for more information on these roles.{{< /note >}} + +## Create the Gateway API resources + +To create the **cafe** gateway, copy and paste the following into your terminal: + +```yaml +kubectl apply -f - <}}Your clients should be able to resolve the domain name "cafe.example.com" to the public IP of the NGINX Gateway Fabric. In this guide we will simulate that using curl's `--resolve` option. {{< /note >}} + + +First, let's send a request to the path "/": + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/ +``` + +We should get a response from one of the **coffee** pods: + +```text +Server address: 10.12.0.18:8080 +Server name: coffee-7dd75bc79b-cqvb7 +``` + +Since the **cafe** HTTPRoute routes all traffic on any path to the **coffee** application, the following requests should also be handled by the **coffee** pods: + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/some-path +``` + +```text +Server address: 10.12.0.18:8080 +Server name: coffee-7dd75bc79b-cqvb7 +``` + +```shell +curl --resolve cafe.example.com:$GW_PORT:$GW_IP http://cafe.example.com:$GW_PORT/some/path +``` + +```text +Server address: 10.12.0.19:8080 +Server name: coffee-7dd75bc79b-dett3 +``` + +Requests to hostnames other than "cafe.example.com" should _not_ be routed to the coffee application, since the **cafe** HTTPRoute only matches requests with the "cafe.example.com"need hostname. To verify this, send a request to the hostname "pub.example.com": + +```shell +curl --resolve pub.example.com:$GW_PORT:$GW_IP http://pub.example.com:$GW_PORT/ +``` + +You should receive a 404 Not Found error: + +```text + +404 Not Found + +

404 Not Found

+
nginx/1.25.2
+ + +``` + +## Troubleshooting + +If you have any issues while testing the configuration, try the following to debug your configuration and setup: + +- Make sure you set the shell variables $GW_IP and $GW_PORT to the public IP and port of the NGINX Gateway Fabric Service. Instructions for finding those values are in the [Expose NGINX Gateway Fabric]({{< relref "installation/expose-nginx-gateway-fabric.md" >}}) guide. + +- Check the status of the gateway: + + ```shell + kubectl describe gateway cafe + ``` + + The gateway status should look similar to this: + + ```text + Status: + Addresses: + Type: IPAddress + Value: 10.244.0.85 + Conditions: + Last Transition Time: 2023-08-15T20:57:21Z + Message: Gateway is accepted + Observed Generation: 1 + Reason: Accepted + Status: True + Type: Accepted + Last Transition Time: 2023-08-15T20:57:21Z + Message: Gateway is programmed + Observed Generation: 1 + Reason: Programmed + Status: True + Type: Programmed + Listeners: + Attached Routes: 1 + Conditions: + Last Transition Time: 2023-08-15T20:57:21Z + Message: Listener is accepted + Observed Generation: 1 + Reason: Accepted + Status: True + Type: Accepted + Last Transition Time: 2023-08-15T20:57:21Z + Message: Listener is programmed + Observed Generation: 1 + Reason: Programmed + Status: True + Type: Programmed + Last Transition Time: 2023-08-15T20:57:21Z + Message: All references are resolved + Observed Generation: 1 + Reason: ResolvedRefs + Status: True + Type: ResolvedRefs + Last Transition Time: 2023-08-15T20:57:21Z + Message: No conflicts + Observed Generation: 1 + Reason: NoConflicts + Status: False + Type: Conflicted + Name: http + ``` + + Check that the conditions match and that the attached routes for the **http** listener equals 1. If it is 0, there may be an issue with the HTTPRoute. + +- Check the status of the HTTPRoute: + + ```shell + kubectl describe httproute coffee + ``` + + The HTTPRoute status should look similar to this: + + ```text + Status: + Parents: + Conditions: + Last Transition Time: 2023-08-15T20:57:21Z + Message: The route is accepted + Observed Generation: 1 + Reason: Accepted + Status: True + Type: Accepted + Last Transition Time: 2023-08-15T20:57:21Z + Message: All references are resolved + Observed Generation: 1 + Reason: ResolvedRefs + Status: True + Type: ResolvedRefs + Controller Name: gateway.nginx.org/nginx-gateway-controller + Parent Ref: + Group: gateway.networking.k8s.io + Kind: Gateway + Name: cafe + Namespace: default + ``` + + Check for any error messages in the conditions. + +- Check the generated nginx config: + + ```shell + kubectl exec -it -n nginx-gateway -c nginx -- nginx -T + ``` + + The config should contain a server block with the server name "cafe.example.com" that listens on port 80. This server block should have a single location `/` that proxy passes to the coffee upstream: + + ```nginx configuration + server { + listen 80; + + server_name cafe.example.com; + + location / { + ... + proxy_pass http://default_coffee_80$request_uri; # the upstream is named default_coffee_80 + ... + } + } + ``` + + There should also be an upstream block with a name that matches the upstream in the **proxy_pass** directive. This upstream block should contain the pod IPs of the **coffee** pods: + + ```nginx configuration + upstream default_coffee_80 { + ... + server 10.12.0.18:8080; # these should be the pod IPs of the coffee pods + server 10.12.0.19:8080; + ... + } + ``` + +{{< note >}}The entire configuration is not shown because it is subject to change. Ellipses indicate that there's configuration not shown.{{< /note >}} + +If your issue persists, [contact us](https://github.com/nginxinc/nginx-gateway-fabric#contacts). + +## Further Reading + +To learn more about the Gateway API and the resources we created in this guide, check out the following resources: + +- [Gateway API Overview](https://gateway-api.sigs.k8s.io/concepts/api-overview/) +- [Deploying a simple Gateway](https://gateway-api.sigs.k8s.io/guides/simple-gateway/) +- [HTTP Routing](https://gateway-api.sigs.k8s.io/guides/http-routing/) diff --git a/site/content/includes/index.md b/site/content/includes/index.md new file mode 100644 index 000000000..ca03031f1 --- /dev/null +++ b/site/content/includes/index.md @@ -0,0 +1,3 @@ +--- +headless: true +--- diff --git a/site/content/includes/installation/delay-pod-termination/delay-pod-termination-overview.md b/site/content/includes/installation/delay-pod-termination/delay-pod-termination-overview.md new file mode 100644 index 000000000..4da3c375a --- /dev/null +++ b/site/content/includes/installation/delay-pod-termination/delay-pod-termination-overview.md @@ -0,0 +1,9 @@ +--- +docs: +--- + +To avoid client service interruptions when upgrading NGINX Gateway Fabric, you can configure [`PreStop` hooks](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/) to delay terminating the NGINX Gateway Fabric pod, allowing the pod to complete certain actions before shutting down. This ensures a smooth upgrade without any downtime, also known as a zero downtime upgrade. + +For an in-depth explanation of how Kubernetes handles pod termination, see the [Termination of Pods](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-termination) topic on their official website. + +{{}}Keep in mind that NGINX won't shut down while WebSocket or other long-lived connections are open. NGINX will only stop when these connections are closed by the client or the backend. If these connections stay open during an upgrade, Kubernetes might need to shut down NGINX forcefully. This sudden shutdown could interrupt service for clients.{{}} diff --git a/site/content/includes/installation/delay-pod-termination/termination-grace-period.md b/site/content/includes/installation/delay-pod-termination/termination-grace-period.md new file mode 100644 index 000000000..0d45f6691 --- /dev/null +++ b/site/content/includes/installation/delay-pod-termination/termination-grace-period.md @@ -0,0 +1,9 @@ +--- +docs: +--- + +Set `terminationGracePeriodSeconds` to a value that is equal to or greater than the `sleep` duration specified in the `preStop` hook (default is `30`). This setting prevents Kubernetes from terminating the pod before before the `preStop` hook has completed running. + + ```yaml + terminationGracePeriodSeconds: 50 + ``` diff --git a/site/content/includes/installation/helm/pulling-the-chart.md b/site/content/includes/installation/helm/pulling-the-chart.md new file mode 100644 index 000000000..a2495ef06 --- /dev/null +++ b/site/content/includes/installation/helm/pulling-the-chart.md @@ -0,0 +1,12 @@ +--- +docs: +--- + +Pull the latest stable release of the NGINX Gateway Fabric chart: + + ```shell + helm pull oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric --untar + cd nginx-gateway-fabric + ``` + + If you want the latest version from the **main** branch, add `--version 0.0.0-edge` to your pull command. diff --git a/site/content/includes/installation/helm/uninstall-gateway-api-resources.md b/site/content/includes/installation/helm/uninstall-gateway-api-resources.md new file mode 100644 index 000000000..0753799dd --- /dev/null +++ b/site/content/includes/installation/helm/uninstall-gateway-api-resources.md @@ -0,0 +1,18 @@ +--- +docs: +--- + +To uninstall the Gateway API resources, including the CRDs and the validating webhook, run: + + {{}}This will remove all corresponding custom resources in your entire cluster, across all namespaces. Double-check to make sure you don't have any custom resources you need to keep, and confirm that there are no other Gateway API implementations active in your cluster.{{}} + + ```shell + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + ``` + + +If you are running on Kubernetes 1.23 or 1.24, you also need to delete the validating webhook. To do so, run: + + ```shell + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` diff --git a/site/content/includes/installation/next-step-expose-fabric.md b/site/content/includes/installation/next-step-expose-fabric.md new file mode 100644 index 000000000..eecadbb83 --- /dev/null +++ b/site/content/includes/installation/next-step-expose-fabric.md @@ -0,0 +1,5 @@ +--- +docs: +--- + +After installing NGINX Gateway Fabric, the next step is to make it accessible. Detailed instructions can be found in [Expose the NGINX Gateway Fabric]({{< relref "installation/expose-nginx-gateway-fabric.md" >}}). diff --git a/site/content/installation/_index.md b/site/content/installation/_index.md new file mode 100644 index 000000000..3144876a5 --- /dev/null +++ b/site/content/installation/_index.md @@ -0,0 +1,9 @@ +--- +title: "Installation" +description: +weight: 200 +linkTitle: "Installation" +menu: + docs: + parent: NGINX Gateway Fabric +--- diff --git a/docs/building-the-images.md b/site/content/installation/building-the-images.md similarity index 70% rename from docs/building-the-images.md rename to site/content/installation/building-the-images.md index c43f471cb..5c8614722 100644 --- a/docs/building-the-images.md +++ b/site/content/installation/building-the-images.md @@ -1,4 +1,15 @@ -# Building the Images +--- +title: "Building NGINX Gateway Fabric and NGINX Images" +weight: 300 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Overview + +While most users will install NGINX Gateway Fabric [with Helm]({{< relref "/installation/installing-ngf/helm.md" >}}) or [Kubernetes manifests]({{< relref "/installation/installing-ngf/manifests.md" >}}), manually building the [NGINX Gateway Fabric and NGINX images]({{< relref "/overview/gateway-architecture.md#the-nginx-gateway-fabric-pod" >}}) can be helpful for testing and development purposes. Follow the steps in this document to build the NGINX Gateway Fabric and NGINX images. ## Prerequisites diff --git a/site/content/installation/expose-nginx-gateway-fabric.md b/site/content/installation/expose-nginx-gateway-fabric.md new file mode 100644 index 000000000..7e92c1de2 --- /dev/null +++ b/site/content/installation/expose-nginx-gateway-fabric.md @@ -0,0 +1,71 @@ +--- +title: "Expose NGINX Gateway Fabric" +description: "" +weight: 300 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Overview + +Gain access to NGINX Gateway Fabric by creating either a **NodePort** service or a **LoadBalancer** service in the same namespace as the controller. The service name is specified in the `--service` argument of the controller. + +{{}}The service manifests configure NGINX Gateway Fabric on ports `80` and `443`, affecting any gateway [listeners](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.Listener) on these ports. To use different ports, update the manifests. NGINX Gateway Fabric requires a configured [gateway](https://gateway-api.sigs.k8s.io/api-types/gateway/#gateway) resource with a valid listener to listen on any ports.{{}} + +NGINX Gateway Fabric uses the created service to update the **Addresses** field in the **Gateway Status** resource. Using a **LoadBalancer** service sets this field to the IP address and/or hostname of that service. Without a service, the pod IP address is used. + +This gateway is associated with the NGINX Gateway Fabric through the **gatewayClassName** field. The default installation of NGINX Gateway Fabric creates a **GatewayClass** with the name **nginx**. NGINX Gateway Fabric will only configure gateways with a **gatewayClassName** of **nginx** unless you change the name via the `--gatewayclass` [command-line flag](/docs/cli-help.md#static-mode). + +## Create a NodePort service + +To create a **NodePort** service run the following command: + +```shell +kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/nodeport.yaml +``` + +A **NodePort** service allocates a port on every cluster node. Access NGINX Gateway Fabric using any node's IP address and the allocated port. + +## Create a LoadBalancer Service + +To create a **LoadBalancer** service, use the appropriate manifest for your cloud provider: + +### GCP (Google Cloud Platform) and Azure + +1. Run the following command: + + ```shell + kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/loadbalancer.yaml + ``` + +2. Lookup the public IP of the load balancer, which is reported in the `EXTERNAL-IP` column in the output of the following command: + + ```shell + kubectl get svc nginx-gateway -n nginx-gateway + ``` + +3. Use the public IP of the load balancer to access NGINX Gateway Fabric. + +### AWS (Amazon Web Services) + +1. Run the following command: + + ```shell + kubectl apply -f https://raw.githubusercontent.com/nginxinc/nginx-gateway-fabric/v1.0.0/deploy/manifests/service/loadbalancer-aws-nlb.yaml + ``` + +2. In AWS, the NLB (Network Load Balancer) DNS (directory name system) name will be reported by Kubernetes instead of a public IP in the `EXTERNAL-IP` column. To get the DNS name, run: + + ```shell + kubectl get svc nginx-gateway -n nginx-gateway + ``` + + {{< note >}} We recommend using the NLB DNS whenever possible, but for testing purposes, you can resolve the DNS name to get the IP address of the load balancer: + + ```shell + nslookup + ``` + + {{< /note >}} diff --git a/site/content/installation/installing-ngf/_index.md b/site/content/installation/installing-ngf/_index.md new file mode 100644 index 000000000..e37f66847 --- /dev/null +++ b/site/content/installation/installing-ngf/_index.md @@ -0,0 +1,9 @@ +--- +title: "Installing NGINX Gateway Fabric" +description: +weight: 200 +linkTitle: "Installing NGINX Gateway Fabric" +menu: + docs: + parent: Installation +--- diff --git a/site/content/installation/installing-ngf/helm.md b/site/content/installation/installing-ngf/helm.md new file mode 100644 index 000000000..9ed7337ed --- /dev/null +++ b/site/content/installation/installing-ngf/helm.md @@ -0,0 +1,184 @@ +--- +title: "Installation with Helm" +description: "Learn how to install, upgrade, and uninstall NGINX Gateway Fabric in a Kubernetes cluster with Helm." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Prerequisites + +To complete this guide, you'll need to install: + +- [kubectl](https://kubernetes.io/docs/tasks/tools/), a command-line tool for managing Kubernetes clusters. +- [Helm 3.0 or later](https://helm.sh/docs/intro/install/), for deploying and managing applications on Kubernetes. + + +## Deploy NGINX Gateway Fabric + +### Install from the OCI registry + +- To install the latest stable release of NGINX Gateway Fabric in the **nginx-gateway** namespace, run the following command: + + ```shell + helm install oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric --create-namespace --wait -n nginx-gateway + ``` + + Change `` to the name you want for your release. If the namespace already exists, you can omit the optional `--create-namespace` flag. If you want the latest version from the **main** branch, add `--version 0.0.0-edge` to your install command. + +### Install from sources {#install-from-sources} + +1. {{}} + +2. To install the chart into the **nginx-gateway** namespace, run the following command. + + ```shell + helm install . --create-namespace --wait -n nginx-gateway + ``` + + Change `` to the name you want for your release. If the namespace already exists, you can omit the optional `--create-namespace` flag. + +## Upgrade NGINX Gateway Fabric + +{{}}For guidance on zero downtime upgrades, see the [Delay Pod Termination](#configure-delayed-pod-termination-for-zero-downtime-upgrades) section below.{{}} + +To upgrade NGINX Gateway Fabric and get the latest features and improvements, take the following steps: + +### Upgrade Gateway resources + +To upgrade your Gateway API resources, take the following steps: + +- Verify the Gateway API resources are compatible with your NGINX Gateway Fabric version. Refer to the [Technical Specifications]({{< relref "reference/technical-specifications.md" >}}) for details. +- Review the [release notes](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.0.0) for any important upgrade-specific information. +- To upgrade the Gateway API resources, run: + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + ``` + +### Upgrade NGINX Gateway Fabric CRDs + +Helm's upgrade process does not automatically upgrade the NGINX Gateway Fabric CRDs (Custom Resource Definitions). + +To upgrade the CRDs, take the following steps: + +1. {{}} + +2. Upgrade the CRDs: + + ```shell + kubectl apply -f crds/ + ``` + + {{}}Ignore the following warning, as it is expected.{{}} + + ``` text + Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply. + ``` + +### Upgrade NGINX Gateway Fabric release + +#### Upgrade from the OCI registry + +- To upgrade to the latest stable release of NGINX Gateway Fabric, run: + + ```shell + helm upgrade oci://ghcr.io/nginxinc/charts/nginx-gateway-fabric -n nginx-gateway + ``` + + Replace `` with your chosen release name. + +#### Upgrade from sources + +1. {{}} + +1. To upgrade, run: the following command: + + ```shell + helm upgrade . -n nginx-gateway + ``` + + Replace `` with your chosen release name. + +## Delay pod termination for zero downtime upgrades {#configure-delayed-pod-termination-for-zero-downtime-upgrades} + +{{< include "installation/delay-pod-termination/delay-pod-termination-overview.md" >}} + +Follow these steps to configure delayed pod termination: + +1. Open the `values.yaml` for editing. + +1. **Add delayed shutdown hooks**: + + - In the `values.yaml` file, add `lifecycle: preStop` hooks to both the `nginx` and `nginx-gateway` container definitions. These hooks instruct the containers to delay their shutdown process, allowing time for connections to close gracefully. Update the `sleep` value to what works for your environment. + + ```yaml + nginxGateway: + <...> + lifecycle: + preStop: + exec: + command: + - /usr/bin/gateway + - sleep + - --duration=40s # This flag is optional, the default is 30s + + nginx: + <...> + lifecycle: + preStop: + exec: + command: + - /bin/sleep + - "40" + ``` + +1. **Set the termination grace period**: + + - {{}} + +1. Save the changes. + +{{}} +For additional information on configuring and understanding the behavior of containers and pods during their lifecycle, refer to the following Kubernetes documentation: + +- [Container Lifecycle Hooks](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks) +- [Pod Lifecycle](https://kubernetes.io/docs/concepts/workloads/Pods/Pod-lifecycle/#Pod-termination) + +{{}} + + +## Uninstall NGINX Gateway Fabric + +Follow these steps to uninstall NGINX Gateway Fabric and Gateway API from your Kubernetes cluster: + +1. **Uninstall NGINX Gateway Fabric:** + + - To uninstall NGINX Gateway Fabric, run: + + ```shell + helm uninstall -n nginx-gateway + ``` + + Replace `` with your chosen release name. + +2. **Remove namespace and CRDs:** + + - To remove the **nginx-gateway** namespace and its custom resource definitions (CRDs), run: + + ```shell + kubectl delete ns nginx-gateway + kubectl delete crd nginxgateways.gateway.nginx.org + ``` + +3. **Remove the Gateway API resources:** + + - {{}} + +## Next steps + +### Expose NGINX Gateway Fabric + +{{}} diff --git a/site/content/installation/installing-ngf/manifests.md b/site/content/installation/installing-ngf/manifests.md new file mode 100644 index 000000000..fdba9604b --- /dev/null +++ b/site/content/installation/installing-ngf/manifests.md @@ -0,0 +1,188 @@ +--- +title: "Installation with Kubernetes manifests" +description: "Learn how to install, upgrade, and uninstall NGINX Gateway Fabric using Kubernetes manifests." +weight: 200 +toc: true +docs: "DOCS-000" +--- + +{{}} + +## Prerequisites + +To complete this guide, you'll need to install: + +- [kubectl](https://kubernetes.io/docs/tasks/tools/), a command-line interface for managing Kubernetes clusters. + + +## Deploy NGINX Gateway Fabric + +Deploying NGINX Gateway Fabric with Kubernetes manifests takes only a few steps. With manifests, you can configure your deployment exactly how you want. Manifests also make it easy to replicate deployments across environments or clusters, ensuring consistency. + +{{}}By default, NGINX Gateway Fabric is installed in the **nginx-gateway** namespace. You can deploy in another namespace by modifying the manifest files.{{}} + +1. **Install the Gateway API resources:** + + - Install the Gateway API CRDs from [the Gateway API repo](https://github.com/kubernetes-sigs/gateway-api): + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + ``` + + - If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` + + {{< important >}}The validating webhook is not needed if you are running Kubernetes 1.25+. Validation is done using CEL on the CRDs. See the [resource validation doc]({{< relref "/overview/resource-validation.md" >}}) for more information. {{< /important >}} + +1. **Deploy the NGINX Gateway Fabric CRDs:** + + - Next, deploy the NGINX Gateway Fabric CRDs: + + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml + ``` + +1. **Deploy NGINX Gateway Fabric:** + + - Then, deploy NGINX Gateway Fabric: + + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml + ``` + +1. **Verify the Deployment:** + - To confirm that NGINX Gateway Fabric is running, check the pods in the `nginx-gateway` namespace: + + ```shell + kubectl get pods -n nginx-gateway + ``` + + The output should look similar to this (note that the pod name will include a unique string): + + ```text + NAME READY STATUS RESTARTS AGE + nginx-gateway-5d4f4c7db7-xk2kq 2/2 Running 0 112s + ``` + + +## Upgrade NGINX Gateway Fabric + +{{}}For guidance on zero downtime upgrades, see the [Delay Pod Termination](#configure-delayed-pod-termination-for-zero-downtime-upgrades) section below.{{}} + +To upgrade NGINX Gateway Fabric and get the latest features and improvements, take the following steps: + +1. **Upgrade Gateway API resources:** + + - Verify that your NGINX Gateway Fabric version is compatible with the Gateway API resources. Refer to the [Technical Specifications]({{< relref "reference/technical-specifications.md" >}}) for details. + - Review the [release notes](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.0.0) for any important upgrade-specific information. + - To upgrade the Gateway API resources, run: + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + ``` + + - If you are running on Kubernetes 1.23 or 1.24, you also need to update the validating webhook: + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` + + - If you are running on Kubernetes 1.25 or newer and have the validating webhook installed, you should remove the + webhook: + + ```shell + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` + +1. **Upgrade NGINX Gateway Fabric CRDs:** + - To upgrade the Custom Resource Definitions (CRDs), run: + + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml + ``` + +1. **Upgrade NGINX Gateway Fabric deployment:** + - To upgrade the deployment, run: + + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml + ``` + +## Delay pod termination for zero downtime upgrades {#configure-delayed-pod-termination-for-zero-downtime-upgrades} + +{{< include "installation/delay-pod-termination/delay-pod-termination-overview.md" >}} + +Follow these steps to configure delayed pod termination: + +1. Open the `nginx-gateway.yaml` for editing. + +1. **Add delayed shutdown hooks**: + + - In the `nginx-gateway.yaml` file, add `lifecycle: preStop` hooks to both the `nginx` and `nginx-gateway` container definitions. These hooks instruct the containers to delay their shutdown process, allowing time for connections to close gracefully. Update the `sleep` value to what works for your environment. + + ```yaml + <...> + name: nginx-gateway + <...> + lifecycle: + preStop: + exec: + command: + - /usr/bin/gateway + - sleep + - --duration=40s # This flag is optional, the default is 30s + <...> + name: nginx + <...> + lifecycle: + preStop: + exec: + command: + - /bin/sleep + - "40" + <...> + ``` + +1. **Set the termination grace period**: + + - {{}} + +1. Save the changes. + +{{}} +For additional information on configuring and understanding the behavior of containers and pods during their lifecycle, refer to the following Kubernetes documentation: + +- [Container Lifecycle Hooks](https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks) +- [Pod Lifecycle](https://kubernetes.io/docs/concepts/workloads/Pods/Pod-lifecycle/#Pod-termination) + +{{}} + + +## Uninstall NGINX Gateway Fabric + +Follow these steps to uninstall NGINX Gateway Fabric and Gateway API from your Kubernetes cluster: + +1. **Uninstall NGINX Gateway Fabric:** + + - To remove NGINX Gateway Fabric and its custom resource definitions (CRDs), run: + + ```shell + kubectl delete -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml + ``` + + ```shell + kubectl delete -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml + ``` + +1. **Remove the Gateway API resources:** + + - {{}} + +## Next steps + +### Expose NGINX Gateway Fabric + +{{}} diff --git a/docs/running-on-kind.md b/site/content/installation/running-on-kind.md similarity index 76% rename from docs/running-on-kind.md rename to site/content/installation/running-on-kind.md index db181b3d1..01e6cf734 100644 --- a/docs/running-on-kind.md +++ b/site/content/installation/running-on-kind.md @@ -1,6 +1,10 @@ -# Running on `kind` - -This guide walks you through how to run NGINX Gateway Fabric on a [kind](https://kind.sigs.k8s.io/) cluster. +--- +title: "Running on kind" +description: "Learn how to run NGINX Gateway Fabric on a kind cluster." +weight: 300 +toc: true +docs: "DOCS-000" +--- ## Prerequisites @@ -19,7 +23,7 @@ make create-kind-cluster ## Deploy NGINX Gateway Fabric -Follow the [installation](./installation.md) instructions to deploy NGINX Gateway Fabric on your Kind cluster. +Follow the [installation](./how-to/installation/installation.md) instructions to deploy NGINX Gateway Fabric on your Kind cluster. ## Access NGINX Gateway Fabric diff --git a/site/content/overview/_index.md b/site/content/overview/_index.md new file mode 100644 index 000000000..fb6a7d7d8 --- /dev/null +++ b/site/content/overview/_index.md @@ -0,0 +1,9 @@ +--- +title: "Overview" +description: +weight: 100 +linkTitle: "Overview" +menu: + docs: + parent: NGINX Gateway Fabric +--- diff --git a/docs/gateway-api-compatibility.md b/site/content/overview/gateway-api-compatibility.md similarity index 97% rename from docs/gateway-api-compatibility.md rename to site/content/overview/gateway-api-compatibility.md index 32380fbaa..c748ab37f 100644 --- a/docs/gateway-api-compatibility.md +++ b/site/content/overview/gateway-api-compatibility.md @@ -1,9 +1,14 @@ -# Gateway API Compatibility - -This document describes which Gateway API resources NGINX Gateway Fabric supports and the extent of that support. +--- +title: "Gateway API Compatibility" +description: "Learn which Gateway API resources NGINX Gateway Fabric supports and the extent of that support." +weight: 700 +toc: true +docs: "DOCS-000" +--- ## Summary +{{< bootstrap-table "table table-striped table-bordered" >}} | Resource | Core Support Level | Extended Support Level | Implementation-Specific Support Level | API Version | |-------------------------------------|--------------------|------------------------|---------------------------------------|-------------| | [GatewayClass](#gatewayclass) | Supported | Not supported | Not Supported | v1 | @@ -14,6 +19,7 @@ This document describes which Gateway API resources NGINX Gateway Fabric support | [TLSRoute](#tlsroute) | Not supported | Not supported | Not Supported | N/A | | [TCPRoute](#tcproute) | Not supported | Not supported | Not Supported | N/A | | [UDPRoute](#udproute) | Not supported | Not supported | Not Supported | N/A | +{{< /bootstrap-table >}} ## Terminology diff --git a/site/content/overview/gateway-architecture.md b/site/content/overview/gateway-architecture.md new file mode 100644 index 000000000..ecfdf3036 --- /dev/null +++ b/site/content/overview/gateway-architecture.md @@ -0,0 +1,97 @@ +--- +title: "Gateway Architecture" +description: "Learn about the architecture and design principles of F5 NGINX Gateway Fabric." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +The intended audience for this information is primarily the two following groups: + +- _Cluster Operators_ who would like to know how the software works and understand how it can fail. +- _Developers_ who would like to [contribute](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/CONTRIBUTING.md) to the project. + +The reader needs to be familiar with core Kubernetes concepts, such as pods, deployments, services, and endpoints. For an understanding of how NGINX itself works, you can read the ["Inside NGINX: How We Designed for Performance & Scale"](https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/) blog post. + +## What is NGINX Gateway Fabric? + +NGINX Gateway Fabric is a Kubernetes cluster component that uses Gateway API Resources to configure an HTTP load balancer with NGINX as its data plane. + +{{< note >}} To learn more about the Gateway API, you can read the [official Kubernetes documentation](https://gateway-api.sigs.k8s.io/). {{< /note >}} + +## NGINX Gateway Fabric at a high level + +This figure depicts an example of NGINX Gateway Fabric exposing two web applications within a Kubernetes cluster to clients on the internet: + +{{}} + +{{< note >}} The figure does not show many of the necessary Kubernetes resources the Cluster Operators and Application Developers need to create, like deployment and services. {{< /note >}} + +The figure shows: + +- A _Kubernetes cluster_. +- Users _Cluster Operator_, _Application Developer A_ and _Application Developer B_. These users interact with the cluster through the Kubernetes API by creating Kubernetes objects. +- _Clients A_ and _Clients B_ connect to _Applications A_ and _B_, respectively, which they have deployed. +- The _NGF Pod_, [deployed by _Cluster Operator_]({{< relref "installation">}}) in the namespace _nginx-gateway_. For scalability and availability, you can have multiple replicas. This pod consists of two containers: `NGINX` and `NGF`. The _NGF_ container interacts with the Kubernetes API to retrieve the most up-to-date Gateway API resources created within the cluster. It then dynamically configures the _NGINX_ container based on these resources, ensuring proper alignment between the cluster state and the NGINX configuration. +- _Gateway AB_, created by _Cluster Operator_, requests a point where traffic can be translated to Services within the cluster. This Gateway includes a listener with a hostname `*.example.com`. Application Developers have the ability to attach their application's routes to this Gateway if their application's hostname matches `*.example.com`. +- _Application A_ with two pods deployed in the _applications_ namespace by _Application Developer A_. To expose the application to its clients (_Clients A_) via the host `a.example.com`, _Application Developer A_ creates _HTTPRoute A_ and attaches it to `Gateway AB`. +- _Application B_ with one pod deployed in the _applications_ namespace by _Application Developer B_. To expose the application to its clients (_Clients B_) via the host `b.example.com`, _Application Developer B_ creates _HTTPRoute B_ and attaches it to `Gateway AB`. +- _Public Endpoint_, which fronts the _NGF_ pod. This is typically a TCP load balancer (cloud, software, or hardware) or a combination of such load balancer with a NodePort service. _Clients A_ and _B_ connect to their applications via the _Public Endpoint_. + +The yellow and purple arrows represent connections related to the client traffic, and the black arrows represent access to the Kubernetes API. The resources within the cluster are color-coded based on the user responsible for their creation. + +For example, the Cluster Operator is denoted by the color green, indicating they create and manage all the green resources. + +## The NGINX Gateway Fabric pod + +NGINX Gateway Fabric consists of two containers: + +1. `nginx`: the data plane. Consists of an NGINX master process and NGINX worker processes. The master process controls the worker processes. The worker processes handle the client traffic and load balance traffic to the backend applications. +1. `nginx-gateway`: the control plane. Watches Kubernetes objects and configures NGINX. + +These containers are deployed in a single pod as a Kubernetes Deployment. + +The `nginx-gateway`, or the control plane, is a [Kubernetes controller](https://kubernetes.io/docs/concepts/architecture/controller/), written with the [controller-runtime](https://github.com/kubernetes-sigs/controller-runtime) library. It watches Kubernetes objects (services, endpoints, secrets, and Gateway API CRDs), translates them to NGINX configuration, and configures NGINX. + +This configuration happens in two stages: + +1. NGINX configuration files are written to the NGINX configuration volume shared by the `nginx-gateway` and `nginx` containers. +1. The control plane reloads the NGINX process. + +This is possible because the two containers [share a process namespace](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/), allowing the NGF process to send signals to the NGINX main process. + +The following diagram represents the connections, relationships and interactions between process with the `nginx` and `nginx-gateway` containers, as well as external processes/entities. + +{{}} + +The following list describes the connections, preceeded by their types in parentheses. For brevity, the suffix "process" has been omitted from the process descriptions. + +1. (HTTPS) + - Read: _NGF_ reads the _Kubernetes API_ to get the latest versions of the resources in the cluster. + - Write: _NGF_ writes to the _Kubernetes API_ to update the handled resources' statuses and emit events. If there's more than one replica of _NGF_ and [leader election](https://github.com/nginxinc/nginx-gateway-fabric/tree/main/deploy/helm-chart#configuration) is enabled, only the _NGF_ pod that is leading will write statuses to the _Kubernetes API_. +1. (HTTP, HTTPS) _Prometheus_ fetches the `controller-runtime` and NGINX metrics via an HTTP endpoint that _NGF_ exposes (`:9113/metrics` by default). Prometheus is **not** required by NGF, and its endpoint can be turned off. +1. (File I/O) + - Write: _NGF_ generates NGINX _configuration_ based on the cluster resources and writes them as `.conf` files to the mounted `nginx-conf` volume, located at `/etc/nginx/conf.d`. It also writes _TLS certificates_ and _keys_ from [TLS secrets](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets) referenced in the accepted Gateway resource to the `nginx-secrets` volume at the path `/etc/nginx/secrets`. + - Read: _NGF_ reads the PID file `nginx.pid` from the `nginx-run` volume, located at `/var/run/nginx`. _NGF_ extracts the PID of the nginx process from this file in order to send reload signals to _NGINX master_. +1. (File I/O) _NGF_ writes logs to its _stdout_ and _stderr_, which are collected by the container runtime. +1. (HTTP) _NGF_ fetches the NGINX metrics via the unix:/var/run/nginx/nginx-status.sock UNIX socket and converts it to _Prometheus_ format used in #2. +1. (Signal) To reload NGINX, _NGF_ sends the [reload signal](https://nginx.org/en/docs/control.html) to the **NGINX master**. +1. (File I/O) + - Write: The _NGINX master_ writes its PID to the `nginx.pid` file stored in the `nginx-run` volume. + - Read: The _NGINX master_ reads _configuration files_ and the _TLS cert and keys_ referenced in the configuration when it starts or during a reload. These files, certificates, and keys are stored in the `nginx-conf` and `nginx-secrets` volumes that are mounted to both the `nginx-gateway` and `nginx` containers. +1. (File I/O) + - Write: The _NGINX master_ writes to the auxiliary Unix sockets folder, which is located in the `/var/lib/nginx` + directory. + - Read: The _NGINX master_ reads the `nginx.conf` file from the `/etc/nginx` directory. This [file](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/internal/mode/static/nginx/conf/nginx.conf) contains the global and http configuration settings for NGINX. In addition, _NGINX master_ reads the NJS modules referenced in the configuration when it starts or during a reload. NJS modules are stored in the `/usr/lib/nginx/modules` directory. +1. (File I/O) The _NGINX master_ sends logs to its _stdout_ and _stderr_, which are collected by the container runtime. +1. (File I/O) An _NGINX worker_ writes logs to its _stdout_ and _stderr_, which are collected by the container runtime. +1. (Signal) The _NGINX master_ controls the [lifecycle of _NGINX workers_](https://nginx.org/en/docs/control.html#reconfiguration) it creates workers with the new configuration and shutdowns workers with the old configuration. +1. (HTTP) To consider a configuration reload a success, _NGF_ ensures that at least one NGINX worker has the new configuration. To do that, _NGF_ checks a particular endpoint via the unix:/var/run/nginx/nginx-config-version.sock UNIX socket. +1. (HTTP, HTTPS) A _client_ sends traffic to and receives traffic from any of the _NGINX workers_ on ports 80 and 443. +1. (HTTP, HTTPS) An _NGINX worker_ sends traffic to and receives traffic from the _backends_. + +## Pod readiness + +The `nginx-gateway` container includes a readiness endpoint available through the path `/readyz`. A [readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) periodically checks the endpoint on startup, returning a `200 OK` response when the pod can accept traffic for the data plane. Once the control plane successfully starts, the pod becomes ready. + +If there are relevant Gateway API resources in the cluster, the control plane will generate the first NGINX configuration and successfully reload NGINX before the pod is considered ready. diff --git a/docs/resource-validation.md b/site/content/overview/resource-validation.md similarity index 97% rename from docs/resource-validation.md rename to site/content/overview/resource-validation.md index 6c8881ee6..26407c431 100644 --- a/docs/resource-validation.md +++ b/site/content/overview/resource-validation.md @@ -1,6 +1,10 @@ -# Gateway API Resource Validation - -This document describes how NGINX Gateway Fabric (NGF) validates Gateway API resources. +--- +title: "Gateway API Resource Validation" +description: "Learn how NGINX Gateway Fabric validates Gateway API resources." +weight: 800 +toc: true +docs: "DOCS-000" +--- ## Overview diff --git a/site/content/reference/_index.md b/site/content/reference/_index.md new file mode 100644 index 000000000..e2262acf2 --- /dev/null +++ b/site/content/reference/_index.md @@ -0,0 +1,9 @@ +--- +title: "Reference" +description: +weight: 400 +linkTitle: "Reference" +menu: + docs: + parent: NGINX Gateway Fabric +--- diff --git a/site/content/reference/cli-help.md b/site/content/reference/cli-help.md new file mode 100644 index 000000000..5bc5ae1c9 --- /dev/null +++ b/site/content/reference/cli-help.md @@ -0,0 +1,53 @@ +--- +title: "Command-line Reference Guide" +description: "Learn about the commands available for the executable file of the NGINX Gateway Fabric container." +weight: 100 +toc: true +docs: "DOCS-000" +--- + +## Static Mode + +This command configures NGINX for a single NGINX Gateway Fabric resource. + +_Usage_: + +```shell + gateway static-mode [flags] +``` + +### Flags + +{{< bootstrap-table "table table-bordered table-striped table-responsive" >}} +| Name | Type | Description | +|------------------------------|----------|-------------| +| _gateway-ctlr-name_ | _string_ | The name of the Gateway controller. The controller name must be in the form: `DOMAIN/PATH`. The controller's domain is `gateway.nginx.org`. | +| _gatewayclass_ | _string_ | The name of the GatewayClass resource. Every NGINX Gateway Fabric must have a unique corresponding GatewayClass resource. | +| _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. 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}. | +| _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. | +| _service_ | _string_ | The name of the service that fronts this NGINX Gateway Fabric pod. Lives in the same namespace as the controller. | +| _metrics-disable_ | _bool_ | Disable exposing metrics in the Prometheus format (Default: `false`). | +| _metrics-listen-port_ | _int_ | Sets the port where the Prometheus metrics are exposed. An integer between 1024 - 65535 (Default: `9113`) | +| _metrics-secure-serving_ | _bool_ | Configures if the metrics endpoint should be secured using https. Note that this endpoint will be secured with a self-signed certificate (Default `false`). | +| _update-gatewayclass-status_ | _bool_ | Update the status of the GatewayClass resource (Default: `true`). | +| _health-disable_ | _bool_ | Disable running the health probe server (Default: `false`). | +| _health-port_ | _int_ | Set the port where the health probe server is exposed. An integer between 1024 - 65535 (Default: `8081`). | +| _leader-election-disable_ | _bool_ | Disable leader election, which is used to avoid multiple replicas of the NGINX Gateway Fabric reporting the status of the Gateway API resources. If disabled, all replicas of NGINX Gateway Fabric will update the statuses of the Gateway API resources (Default: `false`). | +| _leader-election-lock-name_ | _string_ | The name of the leader election lock. A lease object with this name will be created in the same namespace as the controller (Default: `"nginx-gateway-leader-election-lock"`). | +{{% /bootstrap-table %}} + +## Sleep + +This command sleeps for specified duration, then exits. + +_Usage_: + +```shell + gateway sleep [flags] +``` + +{{< bootstrap-table "table table-bordered table-striped table-responsive" >}} +| Name | Type | Description | +|----------|-----------------|-------------------------------------------------------------------------------------------------------| +| duration | `time.Duration` | Set the duration of sleep. Must be parsable by [`time.ParseDuration`](https://pkg.go.dev/time#ParseDuration). (default `30s`) | +{{% /bootstrap-table %}} diff --git a/site/content/reference/technical-specifications.md b/site/content/reference/technical-specifications.md new file mode 100644 index 000000000..7a31f83dc --- /dev/null +++ b/site/content/reference/technical-specifications.md @@ -0,0 +1,13 @@ +--- +title: "Technical Specifications" +draft: false +description: "NGINX Gateway Fabric technical specifications." +weight: 200 +toc: true +tags: [ "docs" ] +docs: "DOCS-000" +--- + +See the NGINX Gateway Fabric technical specifications page: + +https://github.com/nginxinc/nginx-gateway-fabric#technical-specifications diff --git a/site/content/releases.md b/site/content/releases.md new file mode 100644 index 000000000..92f630639 --- /dev/null +++ b/site/content/releases.md @@ -0,0 +1,13 @@ +--- +title: "Releases" +draft: false +description: "NGINX Gateway Fabric releases." +weight: 1200 +toc: true +tags: [ "docs" ] +docs: "DOCS-1359" +--- + +See the NGINX Gateway Fabric changelog page: + +https://github.com/nginxinc/nginx-gateway-fabric/blob/main/CHANGELOG.md diff --git a/site/go.mod b/site/go.mod new file mode 100644 index 000000000..36179c78d --- /dev/null +++ b/site/go.mod @@ -0,0 +1,5 @@ +module github.com/nginxinc/nginx-gateway-fabric/site + +go 1.21 + +require github.com/nginxinc/nginx-hugo-theme v0.40.0 // indirect diff --git a/site/go.sum b/site/go.sum new file mode 100644 index 000000000..ef95ed80d --- /dev/null +++ b/site/go.sum @@ -0,0 +1,6 @@ +github.com/nginxinc/nginx-hugo-theme v0.35.0 h1:7XB2GMy6qeJgKEJy9wOS3SYKYpfvLW3/H+UHRPLM4FU= +github.com/nginxinc/nginx-hugo-theme v0.35.0/go.mod h1:DPNgSS5QYxkjH/BfH4uPDiTfODqWJ50NKZdorguom8M= +github.com/nginxinc/nginx-hugo-theme v0.39.0 h1:P1hOPpityVUOM5OyIpQZa1UJyuUunGSmz0oZh/GYSJM= +github.com/nginxinc/nginx-hugo-theme v0.39.0/go.mod h1:DPNgSS5QYxkjH/BfH4uPDiTfODqWJ50NKZdorguom8M= +github.com/nginxinc/nginx-hugo-theme v0.40.0 h1:YP0I0+bRKcJ5WEb1s/OWcnlcvNvIcKscagJkCzsa+Vs= +github.com/nginxinc/nginx-hugo-theme v0.40.0/go.mod h1:DPNgSS5QYxkjH/BfH4uPDiTfODqWJ50NKZdorguom8M= diff --git a/site/layouts/shortcodes/call-out.html b/site/layouts/shortcodes/call-out.html new file mode 100644 index 000000000..d8e591d83 --- /dev/null +++ b/site/layouts/shortcodes/call-out.html @@ -0,0 +1,3 @@ +
+
{{ .Get 1 }}
{{ .Inner | markdownify }}
+
diff --git a/site/layouts/shortcodes/custom-styles.html b/site/layouts/shortcodes/custom-styles.html new file mode 100644 index 000000000..8ec2a0bbf --- /dev/null +++ b/site/layouts/shortcodes/custom-styles.html @@ -0,0 +1,43 @@ + diff --git a/site/md-linkcheck-config.json b/site/md-linkcheck-config.json new file mode 100644 index 000000000..aff372717 --- /dev/null +++ b/site/md-linkcheck-config.json @@ -0,0 +1,13 @@ +{ + "replacementPatterns": [ + { + "pattern": "^/", + "replacement": "/" + } + ], + "ignorePatterns": [ + { + "pattern": "^.+localhost.+$|/.+yaml" + } + ] +} diff --git a/site/mdlint_conf.json b/site/mdlint_conf.json new file mode 100644 index 000000000..c35d58405 --- /dev/null +++ b/site/mdlint_conf.json @@ -0,0 +1,19 @@ +{ + "MD009": false, + "MD012": false, + "MD010": false, + "MD013": false, + "MD004": { + "style": "dash" + }, + "MD022": false, + "MD033": false, + "MD041": false, + "MD003": false, + "MD002": false, + "MD024": { + "siblings_only": true + }, + "MD046": false, + "MD001": false +} diff --git a/site/netlify.toml b/site/netlify.toml new file mode 100644 index 000000000..480908355 --- /dev/null +++ b/site/netlify.toml @@ -0,0 +1,34 @@ +[build] + base = "site/" + publish = "public" + +[context.production] + command = "make all" + +[context.docs-development] + command = "make all-dev" + +[context.docs-staging] + command = "make all-staging" + +[context.branch-deploy] + command = "make deploy-preview" + +[context.deploy-preview] + command = "make deploy-preview" + +[[headers]] + for = "/*" + [headers.values] + Access-Control-Allow-Origin = "https://docs.nginx.com" + +[[redirects]] + from = "/" + to = "/nginx-gateway-fabric/" + status = 301 + force = true + +[[redirects]] + from = "/nginx-gateway-fabric/*" + to = "/nginx-gateway-fabric/404.html" + status = 404 diff --git a/docs/images/advanced-routing.png b/site/static/img/advanced-routing.png similarity index 100% rename from docs/images/advanced-routing.png rename to site/static/img/advanced-routing.png diff --git a/docs/images/cert-manager-gateway-workflow.png b/site/static/img/cert-manager-gateway-workflow.png similarity index 100% rename from docs/images/cert-manager-gateway-workflow.png rename to site/static/img/cert-manager-gateway-workflow.png diff --git a/docs/images/ngf-high-level.png b/site/static/img/ngf-high-level.png similarity index 100% rename from docs/images/ngf-high-level.png rename to site/static/img/ngf-high-level.png diff --git a/docs/images/ngf-pod.png b/site/static/img/ngf-pod.png similarity index 100% rename from docs/images/ngf-pod.png rename to site/static/img/ngf-pod.png diff --git a/docs/images/route-all-traffic-app.png b/site/static/img/route-all-traffic-app.png similarity index 100% rename from docs/images/route-all-traffic-app.png rename to site/static/img/route-all-traffic-app.png diff --git a/site/static/img/route-all-traffic-config.png b/site/static/img/route-all-traffic-config.png new file mode 100644 index 0000000000000000000000000000000000000000..01ae2210304c71631536c948869431d4673caef2 GIT binary patch literal 40397 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�CA6`O}&U=MF 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 aF;f*bo#cF_?+c%Z^H4+@l_B)x#s2{v$!9wN literal 0 HcmV?d00001 diff --git a/site/static/img/route-all-traffic-flow.png b/site/static/img/route-all-traffic-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..04a09c94cc80b73b3180b763d1c112ddd4fecd81 GIT binary patch literal 47364 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

w0sO$9`5zb@#%S_8!0b)m{~xTDm_&@JzqV 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 literal 0 HcmV?d00001 diff --git a/docs/images/src/README.md b/site/static/img/src/README.md similarity index 100% rename from docs/images/src/README.md rename to site/static/img/src/README.md diff --git a/docs/images/src/advanced-routing.mermaid b/site/static/img/src/advanced-routing.mermaid similarity index 100% rename from docs/images/src/advanced-routing.mermaid rename to site/static/img/src/advanced-routing.mermaid diff --git a/docs/images/src/route-all-traffic-app.mermaid b/site/static/img/src/route-all-traffic-app.mermaid similarity index 100% rename from docs/images/src/route-all-traffic-app.mermaid rename to site/static/img/src/route-all-traffic-app.mermaid diff --git a/docs/images/src/route-all-traffic-config.mermaid b/site/static/img/src/route-all-traffic-config.mermaid similarity index 100% rename from docs/images/src/route-all-traffic-config.mermaid rename to site/static/img/src/route-all-traffic-config.mermaid diff --git a/docs/images/src/route-all-traffic-flow.mermaid b/site/static/img/src/route-all-traffic-flow.mermaid similarity index 100% rename from docs/images/src/route-all-traffic-flow.mermaid rename to site/static/img/src/route-all-traffic-flow.mermaid From a5ae669b41302bb082d339f626bcc9c689f0d854 Mon Sep 17 00:00:00 2001 From: Jon Torre <78599298+Jcahilltorre@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:23:38 +0000 Subject: [PATCH 2/5] fix: fix links in github docs to d-n-c (#1291) --- CONTRIBUTING.md | 4 ++-- README.md | 10 +++++----- conformance/provisioner/README.md | 2 +- deploy/helm-chart/README.md | 2 +- examples/advanced-routing/README.md | 2 +- examples/cafe-example/README.md | 2 +- examples/cross-namespace-routing/README.md | 2 +- examples/http-header-filter/README.md | 3 ++- examples/traffic-splitting/README.md | 2 +- tests/graceful-recovery/graceful-recovery.md | 2 +- tests/zero-downtime-upgrades/zero-downtime-upgrades.md | 4 ++-- 11 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17777fa81..c4937f6bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ Reserve GitHub issues for feature requests and bugs rather than general question ## Getting Started -Follow our [Installation Instructions](/docs/installation.md) to get the NGINX Gateway Fabric up and running. +Follow our [Installation Instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/) to get the NGINX Gateway Fabric up and running. ### Project Structure @@ -91,7 +91,7 @@ Before beginning development, familiarize yourself with the following documents: outlining guidelines and best practices to ensure smooth and efficient pull request processes. - [Go Style Guide](/docs/developer/go-style-guide.md): A coding style guide for Go. Contains best practices and conventions to follow when writing Go code for the project. -- [Architecture](/docs/architecture.md): A high-level overview of the project's architecture. +- [Architecture](https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture/): A high-level overview of the project's architecture. - [Design Principles](/docs/developer/design-principles.md): An overview of the project's design principles. ## Contributor License Agreement diff --git a/README.md b/README.md index d0f7a0fba..8d5f25818 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,15 @@ and `UDPRoute` -- to configure an HTTP or TCP/UDP load balancer, reverse-proxy, on Kubernetes. NGINX Gateway Fabric supports a subset of the Gateway API. For a list of supported Gateway API resources and features, see -the [Gateway API Compatibility](https://docs.nginx.com/nginx-gateway-fabric/gateway-api-compatibility.md) doc. +the [Gateway API Compatibility](https://docs.nginx.com/nginx-gateway-fabric/gateway-api-compatibility/) doc. -Learn about our [design principles](/docs/developer/design-principles.md) and [architecture](https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture.md). +Learn about our [design principles](/docs/developer/design-principles.md) and [architecture](https://docs.nginx.com/nginx-gateway-fabric/overview/gateway-architecture/). ## Getting Started -1. [Quick Start on a kind cluster](https://docs.nginx.com/nginx-gateway-fabric/installation/running-on-kind.md). +1. [Quick Start on a kind cluster](https://docs.nginx.com/nginx-gateway-fabric/installation/running-on-kind/). 2. [Install](https://docs.nginx.com/nginx-gateway-fabric/installation/) NGINX Gateway Fabric. -3. [Build](https://docs.nginx.com/nginx-gateway-fabric/installation/building-the-images.md) an NGINX Gateway Fabric container image from source or use a pre-built image +3. [Build](https://docs.nginx.com/nginx-gateway-fabric/installation/building-the-images/) an NGINX Gateway Fabric container image from source or use a pre-built image available on [GitHub Container Registry](https://github.com/nginxinc/nginx-gateway-fabric/pkgs/container/nginx-gateway-fabric). 4. Deploy various [examples](examples). @@ -101,7 +101,7 @@ docker buildx imagetools inspect ghcr.io/nginxinc/nginx-gateway-fabric:edge --fo ## Troubleshooting -For troubleshooting help, see the [Troubleshooting](https://docs.nginx.com/nginx-gateway-fabric/how-to/monitoring/troubleshooting.md) document. +For troubleshooting help, see the [Troubleshooting](https://docs.nginx.com/nginx-gateway-fabric/how-to/monitoring/troubleshooting/) document. ## Contacts diff --git a/conformance/provisioner/README.md b/conformance/provisioner/README.md index 0eb42a9b3..ff1d0dfe2 100644 --- a/conformance/provisioner/README.md +++ b/conformance/provisioner/README.md @@ -26,7 +26,7 @@ manifest and **re-build** NGF. How to deploy: -1. Follow the [installation](/docs/installation.md) instructions up until the Deploy the NGINX Gateway Fabric step +1. Follow the [installation](https://docs.nginx.com/nginx-gateway-fabric/installation/) instructions up until the Deploy the NGINX Gateway Fabric step to deploy prerequisites for both the static mode Deployments and the provisioner. 1. Deploy provisioner: diff --git a/deploy/helm-chart/README.md b/deploy/helm-chart/README.md index c11358de0..77fa2056d 100644 --- a/deploy/helm-chart/README.md +++ b/deploy/helm-chart/README.md @@ -52,7 +52,7 @@ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/downloa > **Important** > > The validating webhook is not needed if you are running Kubernetes 1.25+. Validation is done using CEL on the -> CRDs. See the [resource validation doc](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/docs/resource-validation.md) +> CRDs. See the [resource validation doc](https://docs.nginx.com/nginx-gateway-fabric/overview/resource-validation/) > for more information. ## Installing the Chart diff --git a/examples/advanced-routing/README.md b/examples/advanced-routing/README.md index 1f7dc0a9b..fc1cb56e6 100644 --- a/examples/advanced-routing/README.md +++ b/examples/advanced-routing/README.md @@ -1,3 +1,3 @@ # Advanced Routing -This directory contains the YAML files used in the [Advanced Routing](/docs/guides/advanced-routing.md) guide. +This directory contains the YAML files used in the [Advanced Routing](https://docs.nginx.com/nginx-gateway-fabric/how-to/traffic-management/advanced-routing/) guide. diff --git a/examples/cafe-example/README.md b/examples/cafe-example/README.md index fba0e30db..e0254de81 100644 --- a/examples/cafe-example/README.md +++ b/examples/cafe-example/README.md @@ -7,7 +7,7 @@ to route traffic to that application using HTTPRoute resources. ## 1. Deploy NGINX Gateway Fabric -1. Follow the [installation instructions](/docs/installation.md) to deploy NGINX Gateway Fabric. +1. Follow the [installation instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/) to deploy NGINX Gateway Fabric. 1. Save the public IP address of NGINX Gateway Fabric into a shell variable: diff --git a/examples/cross-namespace-routing/README.md b/examples/cross-namespace-routing/README.md index d8c2ba2e4..3e774cff4 100644 --- a/examples/cross-namespace-routing/README.md +++ b/examples/cross-namespace-routing/README.md @@ -7,7 +7,7 @@ in a different namespace from our HTTPRoutes. ## 1. Deploy NGINX Gateway Fabric -1. Follow the [installation instructions](/docs/installation.md) to deploy NGINX Gateway Fabric. +1. Follow the [installation instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/) to deploy NGINX Gateway Fabric. 1. Save the public IP address of NGINX Gateway Fabric into a shell variable: diff --git a/examples/http-header-filter/README.md b/examples/http-header-filter/README.md index 6bc85d0f3..11b32c43e 100644 --- a/examples/http-header-filter/README.md +++ b/examples/http-header-filter/README.md @@ -3,11 +3,12 @@ In this example we will deploy NGINX Gateway Fabric and configure traffic routing for a simple echo server. We will use HTTPRoute resources to route traffic to the echo server, using the `RequestHeaderModifier` filter to modify headers to the request. + ## Running the Example ## 1. Deploy NGINX Gateway Fabric -1. Follow the [installation instructions](/docs/installation.md) to deploy NGINX Gateway Fabric. +1. Follow the [installation instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/) to deploy NGINX Gateway Fabric. 1. Save the public IP address of NGINX Gateway Fabric into a shell variable: diff --git a/examples/traffic-splitting/README.md b/examples/traffic-splitting/README.md index 02c3d2fef..0479722cf 100644 --- a/examples/traffic-splitting/README.md +++ b/examples/traffic-splitting/README.md @@ -9,7 +9,7 @@ and `coffee-v2`. ## 1. Deploy NGINX Gateway Fabric -1. Follow the [installation instructions](/docs/installation.md) to deploy NGINX Gateway Fabric. +1. Follow the [installation instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/) to deploy NGINX Gateway Fabric. 1. Save the public IP address of NGINX Gateway Fabric into a shell variable: diff --git a/tests/graceful-recovery/graceful-recovery.md b/tests/graceful-recovery/graceful-recovery.md index b99ad303d..feeab637e 100644 --- a/tests/graceful-recovery/graceful-recovery.md +++ b/tests/graceful-recovery/graceful-recovery.md @@ -136,7 +136,7 @@ if the configuration and version were correctly updated. 1. Switch over to a one-Node Kind cluster. Can run `make create-kind-cluster` from main directory. 2. Run steps 4-11 of the [Setup](#setup) section above using -[this guide](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/docs/running-on-kind.md) for running on Kind. +[this guide](https://docs.nginx.com/nginx-gateway-fabric/installation/running-on-kind/) for running on Kind. 3. Ensure NGF and NGINX container logs are set up and traffic flows through the example application correctly. 4. Drain the Node of its resources. diff --git a/tests/zero-downtime-upgrades/zero-downtime-upgrades.md b/tests/zero-downtime-upgrades/zero-downtime-upgrades.md index c37d50c31..4dd57e757 100644 --- a/tests/zero-downtime-upgrades/zero-downtime-upgrades.md +++ b/tests/zero-downtime-upgrades/zero-downtime-upgrades.md @@ -118,7 +118,7 @@ Notes: ### Upgrade -1. Follow the [upgrade instructions](/docs/installation.md#upgrade-nginx-gateway-fabric-from-manifests) to: +1. Follow the [upgrade instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/installing-ngf/manifests/) to: 1. Upgrade Gateway API version to the one that matches the supported version of new release. 2. Upgrade NGF CRDs. 2. Start sending traffic using wrk from tester VMs for 1 minute: @@ -149,7 +149,7 @@ Notes: ``` 3. **Immediately** upgrade NGF manifests by - following [upgrade instructions](/docs/installation.md#upgrade-nginx-gateway-fabric-from-manifests). + following [upgrade instructions](https://docs.nginx.com/nginx-gateway-fabric/installation/installing-ngf/manifests/). > Don't forget to modify the manifests to have 2 replicas and Pod affinity. 4. Ensure the new Pods are running and the old ones terminate. From e886318ca7784b0c3e3e6ce5380eefbd5da55111 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Thu, 30 Nov 2023 09:09:24 -0600 Subject: [PATCH 3/5] Fix installation docs for mismatched versions (#1287) Problem: Our installation docs suggested installing Gateway API v1 and NGF v1, which are not compatible. Solution: Fix the docs to mention both stable and edge versions, with proper versioning. Also updated the release process doc to ensure these are updated at release time as needed. --- deploy/helm-chart/README.md | 29 +++--- docs/developer/release-process.md | 25 +++--- .../install-gateway-api-resources.md | 30 +++++++ .../uninstall-gateway-api-resources.md | 18 +++- .../installation/installing-ngf/helm.md | 6 +- .../installation/installing-ngf/manifests.md | 88 ++++++++++--------- 6 files changed, 129 insertions(+), 67 deletions(-) create mode 100644 site/content/includes/installation/install-gateway-api-resources.md rename site/content/includes/installation/{helm => }/uninstall-gateway-api-resources.md (50%) diff --git a/deploy/helm-chart/README.md b/deploy/helm-chart/README.md index 77fa2056d..2801de2a9 100644 --- a/deploy/helm-chart/README.md +++ b/deploy/helm-chart/README.md @@ -32,22 +32,29 @@ This chart deploys the NGINX Gateway Fabric in your Kubernetes cluster. > **Note** > -> The Gateway API resources from the standard channel must be installed -> before deploying NGINX Gateway Fabric. If they are already installed in your cluster, please ensure they are -> the correct version as supported by the NGINX Gateway Fabric - +> The [Gateway API resources](https://github.com/kubernetes-sigs/gateway-api) from the standard channel must be +> installed before deploying NGINX Gateway Fabric. If they are already installed in your cluster, please ensure +> they are the correct version as supported by the NGINX Gateway Fabric - > [see the Technical Specifications](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/README.md#technical-specifications). -To install the Gateway API CRDs from [the Gateway API repo](https://github.com/kubernetes-sigs/gateway-api), run: +If installing the latest stable release of NGINX Gateway Fabric, ensure you are deploying its supported version of +the Gateway API resources: -```shell -kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml -``` + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.8.1/standard-install.yaml + ``` -If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: +If you are installing the edge version of NGINX Gateway Fabric: -```shell -kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml -``` + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + ``` + + If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: + + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` > **Important** > diff --git a/docs/developer/release-process.md b/docs/developer/release-process.md index ddf8fe90d..966512eb8 100644 --- a/docs/developer/release-process.md +++ b/docs/developer/release-process.md @@ -36,26 +36,29 @@ To create a new release, follow these steps: URLs to point at `vX.Y.Z`, and bump the `version`. 2. Adjust the `VERSION` variable in the [Makefile](/Makefile) and the `TAG` in the [conformance tests Makefile](/conformance/Makefile) to `X.Y.Z`. - 3. Update the tag of NGF container images used in the Helm [values.yaml](/deploy/helm-chart/values.yaml) file, the - [provisioner manifest](/conformance/provisioner/provisioner.yaml), and all docs to `X.Y.Z`. + 3. Update the tag of NGF container images used in the Helm [values.yaml](/deploy/helm-chart/values.yaml) file, + the [provisioner manifest](/conformance/provisioner/provisioner.yaml), and all docs to `X.Y.Z`. 4. Ensure that the `imagePullPolicy` is `IfNotPresent` in the Helm [values.yaml](/deploy/helm-chart/values.yaml) file. 5. Generate the installation manifests by running `make generate-manifests`. 6. Modify any `git clone` instructions to use `vX.Y.Z` tag. 7. Modify any docs links that refer to `main` to instead refer to `vX.Y.Z`. - 8. Update the [README](/README.md) to include information about the release. - 9. Update the [changelog](/CHANGELOG.md). The changelog includes only important (from the user perspective) - changes to NGF. This is in contrast with the autogenerated full changelog, which is created in the next step. As - a starting point, copy the important features, bug fixes, and dependencies from the autogenerated draft of the - full changelog. This draft can be found under - the [GitHub releases](https://github.com/nginxinc/nginx-gateway-fabric/releases) after the release branch is - created. Use the previous changelog entries for formatting and content guidance. + 8. Update any installation instructions to ensure that the supported Gateway API and NGF versions are correct. + Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`. + 9. Update the [README](/README.md) to include information about the release. + 10. Update the [changelog](/CHANGELOG.md). The changelog includes only important (from the user perspective) + changes to NGF. This is in contrast with the autogenerated full changelog, which is created in the next + step. As a starting point, copy the important features, bug fixes, and dependencies from the autogenerated + draft of the full changelog. This draft can be found under + the [GitHub releases](https://github.com/nginxinc/nginx-gateway-fabric/releases) after the release branch is + created. Use the previous changelog entries for formatting and content guidance. 7. Create and push the release tag in the format `vX.Y.Z`. As a result, the CI/CD pipeline will: - Build NGF container images with the release tag `X.Y.Z` and push it to the registry. - Package and publish the Helm chart to the registry. - Create a GitHub release with an autogenerated changelog and attached release artifacts. -8. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about the - latest release and also the [changelog](/CHANGELOG.md). +8. Prepare and merge a PR into the main branch to update the [README](/README.md) to include the information about + the latest release and also the [changelog](/CHANGELOG.md). Also update any installation instructions to ensure + that the supported Gateway API and NGF versions are correct. Specifically, helm README and `site/content/includes/installation/install-gateway-api-resources.md`. 9. Close the issue created in Step 1. 10. Ensure that the [associated milestone](https://github.com/nginxinc/nginx-gateway-fabric/milestones) is closed. 11. Verify that published artifacts in the release can be installed properly. diff --git a/site/content/includes/installation/install-gateway-api-resources.md b/site/content/includes/installation/install-gateway-api-resources.md new file mode 100644 index 000000000..8ceb6195f --- /dev/null +++ b/site/content/includes/installation/install-gateway-api-resources.md @@ -0,0 +1,30 @@ +--- +docs: +--- + +{{}}The [Gateway API resources](https://github.com/kubernetes-sigs/gateway-api) from the standard channel must be installed before deploying NGINX Gateway Fabric. If they are already installed in your cluster, please ensure they are the correct version as supported by the NGINX Gateway Fabric - [see the Technical Specifications](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/README.md#technical-specifications).{{}} + +**Stable release** + +If installing the latest stable release of NGINX Gateway Fabric, ensure you are deploying its supported version of +the Gateway API resources: + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.8.1/standard-install.yaml +``` + +**Edge version** + +If installing the edge version of NGINX Gateway Fabric from the **main** branch: + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml +``` + +If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: + +```shell +kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml +``` + +{{< important >}}The validating webhook is not needed if you are running Kubernetes 1.25+. Validation is done using CEL on the CRDs. See the [resource validation doc]({{< relref "/overview/resource-validation.md" >}}) for more information.{{< /important >}} diff --git a/site/content/includes/installation/helm/uninstall-gateway-api-resources.md b/site/content/includes/installation/uninstall-gateway-api-resources.md similarity index 50% rename from site/content/includes/installation/helm/uninstall-gateway-api-resources.md rename to site/content/includes/installation/uninstall-gateway-api-resources.md index 0753799dd..d0f878670 100644 --- a/site/content/includes/installation/helm/uninstall-gateway-api-resources.md +++ b/site/content/includes/installation/uninstall-gateway-api-resources.md @@ -2,16 +2,28 @@ docs: --- -To uninstall the Gateway API resources, including the CRDs and the validating webhook, run: - {{}}This will remove all corresponding custom resources in your entire cluster, across all namespaces. Double-check to make sure you don't have any custom resources you need to keep, and confirm that there are no other Gateway API implementations active in your cluster.{{}} + To uninstall the Gateway API resources, including the CRDs and the validating webhook, run the following: + + **Stable release** + + If you were running the latest stable release version of NGINX Gateway Fabric: + + ```shell + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v0.8.1/standard-install.yaml + ``` + + **Edge version** + + If you were running the edge version of NGINX Gateway Fabric from the **main** branch: + ```shell kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml ``` -If you are running on Kubernetes 1.23 or 1.24, you also need to delete the validating webhook. To do so, run: + If you are running on Kubernetes 1.23 or 1.24, you also need to delete the validating webhook. To do so, run: ```shell kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml diff --git a/site/content/installation/installing-ngf/helm.md b/site/content/installation/installing-ngf/helm.md index 9ed7337ed..33ebb0162 100644 --- a/site/content/installation/installing-ngf/helm.md +++ b/site/content/installation/installing-ngf/helm.md @@ -18,6 +18,10 @@ To complete this guide, you'll need to install: ## Deploy NGINX Gateway Fabric +### Installing the Gateway API resources + +{{}} + ### Install from the OCI registry - To install the latest stable release of NGINX Gateway Fabric in the **nginx-gateway** namespace, run the following command: @@ -175,7 +179,7 @@ Follow these steps to uninstall NGINX Gateway Fabric and Gateway API from your K 3. **Remove the Gateway API resources:** - - {{}} + - {{}} ## Next steps diff --git a/site/content/installation/installing-ngf/manifests.md b/site/content/installation/installing-ngf/manifests.md index fdba9604b..772edf3a1 100644 --- a/site/content/installation/installing-ngf/manifests.md +++ b/site/content/installation/installing-ngf/manifests.md @@ -19,53 +19,59 @@ To complete this guide, you'll need to install: Deploying NGINX Gateway Fabric with Kubernetes manifests takes only a few steps. With manifests, you can configure your deployment exactly how you want. Manifests also make it easy to replicate deployments across environments or clusters, ensuring consistency. -{{}}By default, NGINX Gateway Fabric is installed in the **nginx-gateway** namespace. You can deploy in another namespace by modifying the manifest files.{{}} +### 1. Install the Gateway API resources -1. **Install the Gateway API resources:** +{{}} - - Install the Gateway API CRDs from [the Gateway API repo](https://github.com/kubernetes-sigs/gateway-api): +### 2. Deploy the NGINX Gateway Fabric CRDs + +#### Stable release ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml ``` - - If you are running on Kubernetes 1.23 or 1.24, you also need to install the validating webhook. To do so, run: +#### Edge version ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + git clone https://github.com/nginxinc/nginx-gateway-fabric.git + cd nginx-gateway-fabric ``` - {{< important >}}The validating webhook is not needed if you are running Kubernetes 1.25+. Validation is done using CEL on the CRDs. See the [resource validation doc]({{< relref "/overview/resource-validation.md" >}}) for more information. {{< /important >}} + ```shell + kubectl apply -f deploy/manifests/crds + ``` -1. **Deploy the NGINX Gateway Fabric CRDs:** +### 3. Deploy NGINX Gateway Fabric - - Next, deploy the NGINX Gateway Fabric CRDs: + {{}}By default, NGINX Gateway Fabric is installed in the **nginx-gateway** namespace. You can deploy in another namespace by modifying the manifest files.{{}} - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml - ``` +#### Stable release -1. **Deploy NGINX Gateway Fabric:** + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml + ``` - - Then, deploy NGINX Gateway Fabric: +#### Edge version - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml - ``` + ```shell + kubectl apply -f deploy/manifests/nginx-gateway.yaml + ``` -1. **Verify the Deployment:** - - To confirm that NGINX Gateway Fabric is running, check the pods in the `nginx-gateway` namespace: +### 4. Verify the Deployment - ```shell - kubectl get pods -n nginx-gateway - ``` +To confirm that NGINX Gateway Fabric is running, check the pods in the `nginx-gateway` namespace: - The output should look similar to this (note that the pod name will include a unique string): + ```shell + kubectl get pods -n nginx-gateway + ``` - ```text - NAME READY STATUS RESTARTS AGE - nginx-gateway-5d4f4c7db7-xk2kq 2/2 Running 0 112s - ``` + The output should look similar to this (note that the pod name will include a unique string): + + ```text + NAME READY STATUS RESTARTS AGE + nginx-gateway-5d4f4c7db7-xk2kq 2/2 Running 0 112s + ``` ## Upgrade NGINX Gateway Fabric @@ -77,7 +83,7 @@ To upgrade NGINX Gateway Fabric and get the latest features and improvements, ta 1. **Upgrade Gateway API resources:** - Verify that your NGINX Gateway Fabric version is compatible with the Gateway API resources. Refer to the [Technical Specifications]({{< relref "reference/technical-specifications.md" >}}) for details. - - Review the [release notes](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.0.0) for any important upgrade-specific information. + - Review the [release notes](https://github.com/kubernetes-sigs/gateway-api/releases) for any important upgrade-specific information. - To upgrade the Gateway API resources, run: ```shell @@ -86,30 +92,30 @@ To upgrade NGINX Gateway Fabric and get the latest features and improvements, ta - If you are running on Kubernetes 1.23 or 1.24, you also need to update the validating webhook: - ```shell - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` + ```shell + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` - If you are running on Kubernetes 1.25 or newer and have the validating webhook installed, you should remove the webhook: - ```shell - kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml - ``` + ```shell + kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/webhook-install.yaml + ``` 1. **Upgrade NGINX Gateway Fabric CRDs:** - To upgrade the Custom Resource Definitions (CRDs), run: - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml - ``` + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/crds.yaml + ``` 1. **Upgrade NGINX Gateway Fabric deployment:** - To upgrade the deployment, run: - ```shell - kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml - ``` + ```shell + kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/v1.0.0/nginx-gateway.yaml + ``` ## Delay pod termination for zero downtime upgrades {#configure-delayed-pod-termination-for-zero-downtime-upgrades} @@ -179,7 +185,7 @@ Follow these steps to uninstall NGINX Gateway Fabric and Gateway API from your K 1. **Remove the Gateway API resources:** - - {{}} + - {{}} ## Next steps From ba2b9862a3d88577d089117628243c9a0b012c99 Mon Sep 17 00:00:00 2001 From: Alan Dooley Date: Thu, 30 Nov 2023 16:16:12 +0000 Subject: [PATCH 4/5] Update Control Plane Configuration documentation for Hugo formatting (#1296) This commit updates the control plane configuration documentation to match NGINX's standardised Hugo formatting conventions: specifically, avoiding breaking sentences across line and the use of shortcodes for a note. There are also some minor content changes for product noun conventions and rephrasing a few sentences. --------- Co-authored-by: Jon Torre <78599298+Jcahilltorre@users.noreply.github.com> --- .../control-plane-configuration.md | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/site/content/how-to/configuration/control-plane-configuration.md b/site/content/how-to/configuration/control-plane-configuration.md index 89f4016ba..4be696711 100644 --- a/site/content/how-to/configuration/control-plane-configuration.md +++ b/site/content/how-to/configuration/control-plane-configuration.md @@ -1,6 +1,6 @@ --- title: "Control Plane Configuration" -description: "Learn how to dynamically update the NGINX Gateway Fabric control plane configuration." +description: "Learn how to dynamically update the Gateway Fabric control plane configuration." weight: 100 toc: true docs: "DOCS-000" @@ -8,19 +8,18 @@ docs: "DOCS-000" ## Overview -NGINX Gateway Fabric offers a way to update the control plane configuration dynamically without the need for a -restart. The control plane configuration is stored in the NginxGateway custom resource. This resource is created -during the installation of NGINX Gateway Fabric. +NGINX Gateway Fabric can dynamically update the control plane configuration without restarting. The control plane configuration is stored in the NginxGateway custom resource, created during the installation of NGINX Gateway Fabric. -If using manifests, the default name of the resource is `nginx-gateway-config`. If using Helm, the default name -of the resource is `-config`. It is deployed in the same Namespace as the controller -(default `nginx-gateway`). +NginxGateway is deployed in the same namespace as the controller (Default: `nginx-gateway`). The resource's default name is based on your [installation method]({{}}): -The control plane only watches this single instance of the custom resource. If the resource is invalid per the OpenAPI -schema, the Kubernetes API server will reject the changes. If the resource is deleted or deemed invalid by NGINX -Gateway Fabric, a warning Event is created in the `nginx-gateway` Namespace, and the default values will be used by -the control plane for its configuration. Additionally, the control plane updates the status of the resource (if it exists) -to reflect whether it is valid or not. +- Helm: `-config` +- Manifests: `nginx-gateway-config` + +The control plane only watches this single instance of the custom resource. + +If the resource is invalid to the OpenAPI schema, the Kubernetes API server will reject the changes. If the resource is deleted or deemed invalid by NGINX Gateway Fabric, a warning event is created in the `nginx-gateway` namespace, and the default values will be used by the control plane for its configuration. + +Additionally, the control plane updates the status of the resource (if it exists) to reflect whether it is valid or not. ### Spec @@ -40,8 +39,7 @@ to reflect whether it is valid or not. ## Viewing and Updating the Configuration -> For the following examples, the name `nginx-gateway-config` should be updated to the name of the resource that -> was created by your installation. +{{< note >}} For the following examples, the name `nginx-gateway-config` should be updated to the name of the resource created for your installation. {{< /note >}} To view the current configuration: @@ -55,8 +53,7 @@ To update the configuration: kubectl -n nginx-gateway edit nginxgateways nginx-gateway-config ``` -This will open the configuration in your default editor. You can then update and save the configuration, which is -applied automatically to the control plane. +This will open the configuration in your default editor. You can then update and save the configuration, which is applied automatically to the control plane. To view the status of the configuration: From fdeec8f88bf9bc0c1d1c384d7d938993f4befbd7 Mon Sep 17 00:00:00 2001 From: Jon Torre <78599298+Jcahilltorre@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:27:57 +0000 Subject: [PATCH 5/5] docs: update architecture doc (#1295) * docs: update architecture doc * chore: update architecture description --------- Co-authored-by: Alan Dooley --- site/content/overview/gateway-architecture.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/site/content/overview/gateway-architecture.md b/site/content/overview/gateway-architecture.md index ecfdf3036..cc44836d1 100644 --- a/site/content/overview/gateway-architecture.md +++ b/site/content/overview/gateway-architecture.md @@ -1,6 +1,6 @@ --- title: "Gateway Architecture" -description: "Learn about the architecture and design principles of F5 NGINX Gateway Fabric." +description: "Learn about the architecture and design principles of NGINX Gateway Fabric." weight: 100 toc: true docs: "DOCS-000" @@ -13,11 +13,13 @@ The intended audience for this information is primarily the two following groups The reader needs to be familiar with core Kubernetes concepts, such as pods, deployments, services, and endpoints. For an understanding of how NGINX itself works, you can read the ["Inside NGINX: How We Designed for Performance & Scale"](https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/) blog post. -## What is NGINX Gateway Fabric? +## NGINX Gateway Fabric Overview -NGINX Gateway Fabric is a Kubernetes cluster component that uses Gateway API Resources to configure an HTTP load balancer with NGINX as its data plane. +NGINX Gateway Fabric is an open source project that provides an implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) using [NGINX](https://nginx.org/) as the data plane. The goal of this project is to implement the core Gateway APIs -- _Gateway_, _GatewayClass_, _HTTPRoute_, _TCPRoute_, _TLSRoute_, and _UDPRoute_ -- to configure an HTTP or TCP/UDP load balancer, reverse proxy, or API gateway for applications running on Kubernetes. NGINX Gateway Fabric supports a subset of the Gateway API. -{{< note >}} To learn more about the Gateway API, you can read the [official Kubernetes documentation](https://gateway-api.sigs.k8s.io/). {{< /note >}} +For a list of supported Gateway API resources and features, see the [Gateway API Compatibility]({{< relref "/overview/gateway-api-compatibility.md" >}}) documentation. + +We have more information regarding our [design principles](https://github.com/nginxinc/nginx-gateway-fabric/blob/main/docs/developer/design-principles.md) in the project's GitHub repository. ## NGINX Gateway Fabric at a high level