From a1a512834d23e6abbaf44534306fcda9bbdb8643 Mon Sep 17 00:00:00 2001 From: Alan Dooley Date: Wed, 5 Nov 2025 10:11:50 +0000 Subject: [PATCH 1/2] feat: Remove NGINX Service Mesh documentation This commit removes the documentation for NGINX Service Mesh, which went to end of sale on July 1st 2023 and was formally archived on March 14th 2024. It removes the entire content folder for the project, as well as static example files for Grafana dashboards. --- content/mesh/_index.md | 16 - content/mesh/about/_index.md | 9 - content/mesh/about/architecture.md | 293 ------- content/mesh/about/mesh-tech-specs.md | 178 ---- content/mesh/about/what-is-nsm.md | 57 -- content/mesh/examples/index.md | 3 - content/mesh/get-started/_index.md | 9 - content/mesh/get-started/install/_index.md | 7 - .../mesh/get-started/install/configuration.md | 321 -------- .../get-started/install/install-with-helm.md | 140 ---- content/mesh/get-started/install/install.md | 167 ---- .../mesh/get-started/platform-setup/_index.md | 8 - .../mesh/get-started/platform-setup/gke.md | 25 - .../get-started/platform-setup/kubeadm.md | 67 -- .../get-started/platform-setup/kubespray.md | 35 - .../get-started/platform-setup/openshift.md | 62 -- .../platform-setup/persistent-storage.md | 60 -- .../mesh/get-started/platform-setup/rke.md | 124 --- .../platform-setup/supported-platforms.md | 25 - content/mesh/get-started/uninstall/_index.md | 6 - .../uninstall/uninstall-with-helm.md | 81 -- .../mesh/get-started/uninstall/uninstall.md | 87 -- content/mesh/get-started/upgrade/_index.md | 7 - .../get-started/upgrade/upgrade-with-helm.md | 160 ---- content/mesh/get-started/upgrade/upgrade.md | 133 --- content/mesh/guides/_index.md | 8 - content/mesh/guides/inject-sidecar-proxy.md | 128 --- content/mesh/guides/monitoring-and-tracing.md | 111 --- content/mesh/guides/private-registry.md | 109 --- content/mesh/guides/production-tuning.md | 56 -- content/mesh/guides/prometheus-metrics.md | 192 ----- content/mesh/guides/secure-traffic-mtls.md | 295 ------- content/mesh/guides/smi-traffic-metrics.md | 208 ----- content/mesh/guides/smi-traffic-policies.md | 378 --------- content/mesh/guides/v1alpha1-ratelimit.md | 84 -- content/mesh/reference/_index.md | 6 - content/mesh/reference/api-usage.md | 49 -- content/mesh/reference/nginx-meshctl.md | 384 --------- content/mesh/reference/permissions.md | 33 - content/mesh/releases/_index.md | 5 - .../mesh/releases/oss-dependencies/index.md | 39 - content/mesh/releases/release-notes-0.5.0.md | 87 -- content/mesh/releases/release-notes-0.6.0.md | 315 -------- content/mesh/releases/release-notes-0.7.0.md | 184 ----- content/mesh/releases/release-notes-0.8.0.md | 242 ------ content/mesh/releases/release-notes-0.9.0.md | 325 -------- content/mesh/releases/release-notes-0.9.1.md | 63 -- content/mesh/releases/release-notes-1.0.0.md | 327 -------- content/mesh/releases/release-notes-1.0.1.md | 23 - content/mesh/releases/release-notes-1.1.0.md | 353 -------- content/mesh/releases/release-notes-1.2.0.md | 403 ---------- content/mesh/releases/release-notes-1.2.1.md | 25 - content/mesh/releases/release-notes-1.3.0.md | 345 -------- content/mesh/releases/release-notes-1.3.1.md | 25 - content/mesh/releases/release-notes-1.4.0.md | 303 ------- content/mesh/releases/release-notes-1.4.1.md | 23 - content/mesh/releases/release-notes-1.5.0.md | 247 ------ content/mesh/releases/release-notes-1.6.0.md | 155 ---- content/mesh/releases/release-notes-1.7.0.md | 141 ---- content/mesh/releases/release-notes-2.0.0.md | 125 --- content/mesh/support/_index.md | 8 - content/mesh/support/contact-support.md | 65 -- content/mesh/tutorials/_index.md | 7 - .../tutorials/accesscontrol-walkthrough.md | 360 --------- content/mesh/tutorials/deploy-example-app.md | 111 --- content/mesh/tutorials/kic/_index.md | 6 - content/mesh/tutorials/kic/deploy-with-kic.md | 476 ----------- .../mesh/tutorials/kic/egress-walkthrough.md | 322 -------- .../tutorials/kic/ingress-udp-walkthrough.md | 184 ----- .../mesh/tutorials/kic/ingress-walkthrough.md | 179 ----- content/mesh/tutorials/observability.md | 85 -- .../mesh/tutorials/ratelimit-walkthrough.md | 760 ------------------ .../tutorials/trafficsplit-deployments.md | 476 ----------- static/examples/grafana/README.md | 43 - static/examples/grafana/dashboard.png | Bin 251004 -> 0 bytes static/examples/grafana/nginx-mesh-top.json | 731 ----------------- .../grafana/nginx-service-mesh-summary.json | 601 -------------- 77 files changed, 12290 deletions(-) delete mode 100644 content/mesh/_index.md delete mode 100644 content/mesh/about/_index.md delete mode 100644 content/mesh/about/architecture.md delete mode 100644 content/mesh/about/mesh-tech-specs.md delete mode 100644 content/mesh/about/what-is-nsm.md delete mode 100644 content/mesh/examples/index.md delete mode 100644 content/mesh/get-started/_index.md delete mode 100644 content/mesh/get-started/install/_index.md delete mode 100644 content/mesh/get-started/install/configuration.md delete mode 100644 content/mesh/get-started/install/install-with-helm.md delete mode 100644 content/mesh/get-started/install/install.md delete mode 100644 content/mesh/get-started/platform-setup/_index.md delete mode 100644 content/mesh/get-started/platform-setup/gke.md delete mode 100644 content/mesh/get-started/platform-setup/kubeadm.md delete mode 100644 content/mesh/get-started/platform-setup/kubespray.md delete mode 100644 content/mesh/get-started/platform-setup/openshift.md delete mode 100644 content/mesh/get-started/platform-setup/persistent-storage.md delete mode 100644 content/mesh/get-started/platform-setup/rke.md delete mode 100644 content/mesh/get-started/platform-setup/supported-platforms.md delete mode 100644 content/mesh/get-started/uninstall/_index.md delete mode 100644 content/mesh/get-started/uninstall/uninstall-with-helm.md delete mode 100644 content/mesh/get-started/uninstall/uninstall.md delete mode 100644 content/mesh/get-started/upgrade/_index.md delete mode 100644 content/mesh/get-started/upgrade/upgrade-with-helm.md delete mode 100644 content/mesh/get-started/upgrade/upgrade.md delete mode 100644 content/mesh/guides/_index.md delete mode 100644 content/mesh/guides/inject-sidecar-proxy.md delete mode 100644 content/mesh/guides/monitoring-and-tracing.md delete mode 100644 content/mesh/guides/private-registry.md delete mode 100644 content/mesh/guides/production-tuning.md delete mode 100644 content/mesh/guides/prometheus-metrics.md delete mode 100644 content/mesh/guides/secure-traffic-mtls.md delete mode 100644 content/mesh/guides/smi-traffic-metrics.md delete mode 100644 content/mesh/guides/smi-traffic-policies.md delete mode 100644 content/mesh/guides/v1alpha1-ratelimit.md delete mode 100644 content/mesh/reference/_index.md delete mode 100644 content/mesh/reference/api-usage.md delete mode 100644 content/mesh/reference/nginx-meshctl.md delete mode 100644 content/mesh/reference/permissions.md delete mode 100644 content/mesh/releases/_index.md delete mode 100644 content/mesh/releases/oss-dependencies/index.md delete mode 100644 content/mesh/releases/release-notes-0.5.0.md delete mode 100644 content/mesh/releases/release-notes-0.6.0.md delete mode 100644 content/mesh/releases/release-notes-0.7.0.md delete mode 100644 content/mesh/releases/release-notes-0.8.0.md delete mode 100644 content/mesh/releases/release-notes-0.9.0.md delete mode 100644 content/mesh/releases/release-notes-0.9.1.md delete mode 100644 content/mesh/releases/release-notes-1.0.0.md delete mode 100644 content/mesh/releases/release-notes-1.0.1.md delete mode 100644 content/mesh/releases/release-notes-1.1.0.md delete mode 100644 content/mesh/releases/release-notes-1.2.0.md delete mode 100644 content/mesh/releases/release-notes-1.2.1.md delete mode 100644 content/mesh/releases/release-notes-1.3.0.md delete mode 100644 content/mesh/releases/release-notes-1.3.1.md delete mode 100644 content/mesh/releases/release-notes-1.4.0.md delete mode 100644 content/mesh/releases/release-notes-1.4.1.md delete mode 100644 content/mesh/releases/release-notes-1.5.0.md delete mode 100644 content/mesh/releases/release-notes-1.6.0.md delete mode 100644 content/mesh/releases/release-notes-1.7.0.md delete mode 100644 content/mesh/releases/release-notes-2.0.0.md delete mode 100644 content/mesh/support/_index.md delete mode 100644 content/mesh/support/contact-support.md delete mode 100644 content/mesh/tutorials/_index.md delete mode 100644 content/mesh/tutorials/accesscontrol-walkthrough.md delete mode 100644 content/mesh/tutorials/deploy-example-app.md delete mode 100644 content/mesh/tutorials/kic/_index.md delete mode 100644 content/mesh/tutorials/kic/deploy-with-kic.md delete mode 100644 content/mesh/tutorials/kic/egress-walkthrough.md delete mode 100644 content/mesh/tutorials/kic/ingress-udp-walkthrough.md delete mode 100644 content/mesh/tutorials/kic/ingress-walkthrough.md delete mode 100644 content/mesh/tutorials/observability.md delete mode 100644 content/mesh/tutorials/ratelimit-walkthrough.md delete mode 100644 content/mesh/tutorials/trafficsplit-deployments.md delete mode 100644 static/examples/grafana/README.md delete mode 100644 static/examples/grafana/dashboard.png delete mode 100644 static/examples/grafana/nginx-mesh-top.json delete mode 100644 static/examples/grafana/nginx-service-mesh-summary.json diff --git a/content/mesh/_index.md b/content/mesh/_index.md deleted file mode 100644 index e28e00d61..000000000 --- a/content/mesh/_index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: F5 NGINX Service Mesh -description: 'NGINX Service Mesh is a fully integrated lightweight service mesh that - leverages a data plane powered by NGINX Plus to manage container traffic in Kubernetes - environments. - - ' -url: /nginx-service-mesh/ -cascade: - noindex: true - nd-banner: - enabled: true - type: deprecation - md: _banners/eos-mesh.md ---- - diff --git a/content/mesh/about/_index.md b/content/mesh/about/_index.md deleted file mode 100644 index 510e7b8da..000000000 --- a/content/mesh/about/_index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: About NGINX Service Mesh -description: Learn the fundamentals of NGINX Service Mesh. -weight: 10 -url: /nginx-service-mesh/about/ -type: -- concept ---- - diff --git a/content/mesh/about/architecture.md b/content/mesh/about/architecture.md deleted file mode 100644 index 39bd9f8b8..000000000 --- a/content/mesh/about/architecture.md +++ /dev/null @@ -1,293 +0,0 @@ ---- -title: Architecture -weight: 200 -description: Learn about F5 NGINX Service Mesh Architecture. -toc: true -nd-docs: DOCS-676 -type: -- concept -- reference ---- - -## Overview - -F5 NGINX Service Mesh is an infrastructure layer designed to decouple application business logic from deep networking concerns. A mesh is designed to provide fast, reliable, and low-latency network connections for modern application architectures. - -## Architecture and Components - -NGINX Service Mesh deploys two primary layers: a **control plane layer** that's responsible for configuration and management, and a **data plane layer** that provides the network functions valuable to distributed applications. - -The control plane comprises multiple subsystems, each of which is explained below. Following the sidecar pattern, data plane elements replicate throughout the application in a 1:1 ratio with application workloads. Each data plane element is an identical instance using configuration data to shape its behavior and customize its relative position within the application topology. - -{{< img src="img/architecture.png" alt="NGINX Service Mesh Architecture" >}} -*NGINX Service Mesh Architecture* - -### NGINX Service Mesh Controllers - -Kubernetes resources and container names: - -- Container: nginx-mesh-controller -- Deployment: deployment/nginx-mesh-controller -- Service: service/nginx-mesh-controller - -NGINX Service Mesh employs the controller pattern to enforce desired states across managed application(s). Controllers are event loops that actuate and enforce configuration inputs. The controllers in the NGINX Service Mesh control plane watch a set of native Kubernetes resources (Services, Endpoints, and Pods). The controllers also watch a collection of custom resources defined by the [Service Mesh Interface specification](https://github.com/servicemeshinterface/smi-spec) and individual resources specific to NGINX Service Mesh (see [Traffic Policies](https://docs.nginx.com/nginx-service-mesh/guides/smi-traffic-policies/)). - -NGINX Service Mesh controllers support advanced configurations. The most basic configuration tuple requires a Kubernetes Service and Pod. Higher-order parent resources--such as Deployments, StatefulSets, DaemonSets, or Jobs--can control Pods. However, as the fundamental workload abstraction, NGINX Service Mesh controllers require access to the Pod configuration. - -Applying the concept of [Dynamic Admission Control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/), NGINX Service Mesh mutates Pod configurations with additive elements through a process known as *injection*. Injection is the mechanism enabling the container sidecar pattern; the NGINX Service Mesh control plane injects an [init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) and a sidecar into each managed Pod. Besides automatic injection, NGINX Service Mesh supports manual injection through the `nginx-meshctl inject` command. Be sure to experiment with manual injection to evaluate the changes made to Pod configurations. - -When new events occur in Kubernetes that NGINX Service Mesh watches for--such as when new applications or traffic policies are created--the control plane builds an internal configuration based on this data. This configuration is sent over a secure [NATS](#nats-message-bus) channel to all application sidecars. The sidecars are designed to understand the structure of this config. - -### NGINX Service Mesh Sidecar - -Kubernetes container names: - -- nginx-mesh-init -- nginx-mesh-sidecar - -Applications that are a part of NGINX Service Mesh are injected with an [init container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) and a sidecar. The sidecar consists of two components: a simple agent and an NGINX Plus instance. - -#### Init Container - -The init container runs before the application or sidecar. This container sets the networking to redirect inbound and outbound traffic from the application to the NGINX Plus instance. For example, `iptables` rules can be examined from within the Pod's network namespace. NGINX Plus then forwards the traffic to the original destination. - -#### Agent - -The agent accepts the NGINX Service Mesh control plane configuration and uses this data to configure the NGINX Plus instance. The agent gets certificate information from [SPIRE](#spire). Upstreams, traffic policies, and global mesh configuration settings are retrieved from the NGINX Service Mesh controller via [NATS](#nats-message-bus). The agent manages the entire configuration lifecycle of the NGINX Plus instance. - -#### NGINX Plus - -NGINX Plus is the "brain" of the data plane. By proxying inbound and outbound traffic to and from your application, NGINX Plus handles mTLS, traffic routing, tracing, and metrics. The upstream servers to NGINX Plus reference all the services in the mesh, ensuring encrypted connections and honoring traffic policies defined in Kubernetes custom resources. Native and custom NGINX modules manage traffic routing and expose tracing and metrics data as requests pass through the proxy. Traffic is redirected by `iptables` rules (defined by the init container) to the NGINX Plus instance before being forwarded to its original destination. - -#### Intercepting Traffic - -NGINX Service Mesh uses destination NAT (DNAT) to redirect all inbound and outbound application traffic to the NGINX Plus sidecar. DNAT operates like the port forwarding feature on home Wi-Fi routers. DNAT changes the destination IP address to the Pod IP address and the destination port to a static port that the NGINX Plus sidecar listens on. NGINX Service Mesh uses separate ports for inbound and outbound traffic. - -NGINX Service Mesh uses an init container to set up iptables `REDIRECT` rules to perform the DNAT redirection. The init container is injected at Pod creation alongside the sidecar. It sets up the iptables rules before starting the application or sidecar to ensure traffic is redirected on startup. The init container must run as root to set up iptables rules. However, the short lifetime of the init container means those root privileges aren't needed for long. As a result, the NGINX Plus sidecar can run as a regular unprivileged user. - -#### Proxying Traffic - -Once the sidecar receives traffic, it's able to do its work. For example, if mTLS is enabled, the sidecar will decrypt or encrypt traffic for inbound or outbound traffic, respectively. Once the sidecar has processed traffic, it forwards the traffic to the original destination. To do this, the sidecar recovers the original destination IP address and port using the Linux `getsockopt` socket API and requests `SO_ORIGINAL_DST`. Linux returns a struct with the original destination IP address and port. The sidecar then proxies the traffic to the original destination. - -#### Packet Flow Examples - -This section steps through some packet flow examples using the following diagram. - -{{< img src="img/networking.png" alt="NGINX Service Mesh Networking Diagram">}} -*NGINX Service Mesh Network Example* - -Key Concepts: - -- Each Pod runs within its own [network namespace](https://man7.org/linux/man-pages/man8/ip-netns.8.html). Each Pod has its own networking stack, routes, firewall rules, and network devices. In the preceding diagram, there are three Pods, each with its own networking namespace. -- Each Pod has its own `eth0` network device. -- On each node, there is a `veth` virtual network device for each Pod on the node, used to connect the Pod's namespace to the root network namespace. -- There is a Layer 2 bridge device on each node, labeled `cbr0` in this example, that's used to link network namespaces on the same node. Pods on the same node use this bridge when they want to talk to each other. The bridge name can change across deployments and versions and may not be called `cbr0`. To see what bridges exist on your node, use the `brctl` command. -- Each node has its own `eth0` in the root network namespace that's used for Pods on different nodes to talk to each other. - -##### TCP Pod-to-Pod Communication on the Same Node - -In this scenario, Pod 1 wants to communicate with Pod 2, and both Pods are on Node 1. Pod 1 sends a packet to its default Ethernet device, `eth0`. The iptables rule in Pod 1's network namespace redirects the packet to the NGINX Plus sidecar. After processing the packet, the NGINX Plus sidecar recovers the original destination IP address and port and then forwards the packet to `eth0`. For Pod 1, `eth0` connects to the root network namespace using `veth1234`. Bridge `cbr0` connects `veth1234` to all of the other virtual devices on the node. Once the packet reaches the bridge, it uses ARP (Address Resolution Protocol) to determine that the correct next hop is `veth4321`. When the packet reaches the virtual device `veth4321`, it's forwarded to `eth0` in Pod 2's network namespace. - -##### TCP Pod-to-Pod Communication on Different Nodes - -In this scenario, Pod 1 on Node 1 wants to communicate with Pod 3 on Node 2. Pod 1 sends a packet to its default Ethernet device, `eth0`. The iptables rule in Pod 1's network namespace redirects the packet to the NGINX Plus sidecar. After processing the packet, the NGINX Plus sidecar recovers the original destination IP address and port and then forwards the packet to `eth0`. For Pod 1, `eth0` connects to the root network namespace using `veth1234`. ARP fails at the `cbr0` bridge because a device isn't connected with the correct MAC address for the packet. `cbr0` then sends the packet out the default route, which is `eth0` in the root network namespace. - -At this point, the packet leaves the node and enters the Host Network. The Host Network routes the packet to Node 2 based on the destination IP address. The packet enters the root network namespace of the destination node, where it's routed through `cbr0` to `veth9876`. When the packet reaches the virtual device `veth9876`, it's forwarded to `eth0` in Pod 3's network namespace. - -##### UDP and eBPF - -{{< call-out "note" >}} -UDP traffic proxying is a beta feature that is turned off by default. You can turn it on at [deploy time]({{< ref "nginx-meshctl.md#deploy" >}}) if desired. Linux kernel 4.18 or greater is required. -{{< /call-out >}} - -NGINX Service Mesh has developed an alternate approach to routing datagrams in answer to particular challenges associated with the UDP protocol. Information is routed via an analogous pathway, however, UDP datagrams are redirected with eBPF functions as opposed to iptables with TCP. - -eBPF is a powerful and customizable construct that runs within the Linux kernel, inside a register-based virtual machine. - -Firstly, the functionality gets outlined using a high-level language such as C, Golang, or Python. Then the code is verified and compiled into BPF bytecode and loaded into the kernel. Verification ensures that the eBPF program is safe to run, and its execution in the virtual machine guarantees it will not cause the kernel to crash. - -Use cases for eBPF include tracing, monitoring, and profiling. In NGINX Service Mesh, eBPF is used to redirect UDP traffic to sidecar proxy. This flow is described in the following sections. See [ebpf.io](https://ebpf.io/) for additional information, including use cases. - - -###### Outgoing eBPF with UDP - -See the flow of a packet from generation to delivery to destination. - -{{< img src="img/udp-egress.jpeg" alt="NGINX Service Mesh UDP Egress Networking Diagram" width="75%" >}} - -Here you can see the flow of a packet from generation by the workload to delivery to the destination workload. This diagram has abstracted away the destination pod networking specifics since that is covered in the above diagram. - -The core functionality needed by the TC program is a redirection of the packet's destination to the sidecar container running in the same pod. In order to do this, a number of considerations need to be made: - -1. The destination IP address and port must be overridden to that of the sidecar container. -1. In order to maintain the packet's original destination IP address and port, we decided to add additional headroom to the packet's payload section using PROXY protocol V2. This keeps track of the packet's original destination and source. -1. We must not trigger any Linux networking checks that may drop the packet. This includes: updating the source IP such that it is not a martian address, handling IP fragmentation, and updating the modified packet's checksums. - -The packet flows through the TC egress filter set up by the init container into the eBPF program loaded into the Linux kernel. Here we add the PROXY protocol V2 header and redirect the packet to the proxy. The PROXY protocol V2 header is then stripped from the payload. Additional processing by NGINX Plus such as load balancing or traffic policy is performed. The packet is then sent to its original destination IP and port through the default Linux networking stack. - -###### Incoming eBPF with UDP - -This section talks about the specifics of UDP networking for outgoing traffic. - -{{< img src="img/udp-ingress.jpeg" alt="NGINX Service Mesh UDP Ingress Networking Diagram" width="75%" >}} - -The diagram shows that the source of the packet is coming from an external source workload. Once the packet finds its way into the network namespace of the destination pod, the `XDP` eBPF hook triggers the execution of the custom eBPF program. This redirects the packet to the proxy container, rather than the workload itself while using the same PROXY protocol V2 header to maintain original destination information. - -Once received by the destination proxy container, the PROXY protocol V2 header is stripped from the payload, any additional processing by NGINX is performed, and the packet is sent from NGINX Plus to the workload. - -### SPIRE - -Kubernetes resources and container names: - -- Containers: spire-server, k8s-workload-registrar, spire-agent -- Resources: statefulset|deployment/spire-server, daemonset/spire-agent -- Services: service/spire-server, service/k8s-workload-registrar - -NGINX Service Mesh uses mutual TLS (mTLS) to encrypt and authenticate data sent between injected Pods. mTLS extends standard TLS by having both the client and server present a certificate and mutually authenticate each other. mTLS is “zero-touch,” meaning developers don't need to retrofit their applications with certificates. - -NGINX Service Mesh integrates [SPIRE](https://github.com/spiffe/spire) as its central Certificate Authority (CA). SPIRE handles the entire lifecycle of the certificate: creation, distribution, and rotation. NGINX Plus uses SPIRE-issued certificates to establish mTLS sessions and encrypt traffic. - -{{< img src="img/mtls.png" alt="NGINX Service Mesh mTLS Architecture Diagram">}} -*NGINX Service Mesh mTLS Architecture* - -The important components in the diagram are: - -- **SPIRE Server**: The SPIRE Server runs as a Kubernetes StatefulSet (or Deployment if no [persistent storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}) is available). It has two containers - the actual `spire-server` and the `k8s-workload-registrar`. - - - **spire-server**: The core of the NGINX Service Mesh mTLS architecture, `spire-server` is the certificate authority (CA) that issues certificates for workloads and pushes them to the SPIRE Agent. `spire-server` can be the root CA for all services in the mesh or an intermediate CA in the trust chain. - - **k8s-workload-registrar**: When new Pods are created, `k8s-workload-registrar` makes API calls to request that `spire-server` generate a new certificate. `k8s-workload-registrar` communicates with `spire-server` through a Unix socket. The `k8s-workload-registrar` is based on a Kubernetes Custom Resource Definition (CRD). - -- **SPIRE Agent**: The SPIRE Agent runs as a Kubernetes DaemonSet, meaning one copy runs per node. The SPIRE Agent has two main functions: - - 1. Receives certificates from the SPIRE Server and stores them in a cache. - - 1. "Attests" each Pod that comes up. The SPIRE Agent asks the Kubernetes system to provide information about the Pod, including its UID, name, and namespace. The SPIRE Agent then uses this information to look up the corresponding certificate. - -- **NGINX Plus**: NGINX Plus consumes the certificates generated and distributed by SPIRE and handles the entire mTLS workflow, exchanging and verifying certificates. - -#### The Certificate Lifecycle - -This section explains how NGINX Service Mesh handles the entire certificate lifecycle, including creation, distribution, usage, and rotation. - -##### Creation - -The first stage in the mTLS workflow is creating the actual certificate: - -1. The Pod is deployed. -1. The NGINX Service Mesh control plane injects the sidecar into the Pod configuration using a mutating webhook. -1. In response to a "Pod Created" event notification, `k8s-workload-registrar` gathers the information needed to create the certificate, such as the `ServiceAccount` name. -1. `k8s-workload-registrar` makes an API call to `spire-server` over a Unix socket to request a certificate for the Pod. -1. `spire-server` mints a certificate for the Pod. - -##### Distribution - -The new certificate needs to be securely distributed to the correct Pod: - -1. The SPIRE Agents fetch the new certificate and store it in their caches. The SPIRE Agents and Server use gRPC to communicate. The communication is secure and encrypted. -1. The injected Pod is scheduled and begins running; this includes the NGINX Plus sidecar. -1. The sidecar connects through a Unix socket to the SPIRE Agent running on the same node. -1. The SPIRE Agent attests the Pod, gathers the correct certificate, and sends it to the sidecar through the Unix socket. - -##### Usage - -Now, NGINX Plus can use the certificate to perform mTLS. What follows is how NGINX Plus uses the certificate when the Pod tries to connect to a server that has a certificate issued by SPIRE. - -1. The application running in the Pod initiates a connection to a service. -1. The NGINX Plus sidecar intercepts the connection using iptables NAT redirect. -1. NGINX Plus initiates a TLS connection to the destination service's sidecar. -1. The server-side NGINX Plus sends its certificate to the client and requests the client's certificate. -1. The client-side NGINX Plus validates the server's certificate up to the trust root and sends its certificate to the server. -1. The server validates the client's certificate up to the trust root. -1. With both sides mutually authenticated, a standard TLS key exchange can occur. - -##### Rotation - -Certificates must be rotated before their expiration dates to keep traffic flowing. When a certificate is close to expiring, `spire-server` issues a new certificate and triggers the rotation process. The new certificate is pushed to the SPIRE Agent. Then the SPIRE Agent forwards the certificate through the Unix socket to the NGINX Plus sidecar. - -#### SPIRE and PKI - -You can use SPIRE as the trust root in your NGINX Service Mesh deployment, or SPIRE can plug into your existing Public Key Infrastructure (PKI). For more information, see [Deploy Using an Upstream Root CA]({{< ref "/mesh/guides/secure-traffic-mtls#deploy-using-an-upstream-root-ca" >}}) - -If you import your own root CA: - -- SPIRE creates an intermediate CA signed against its own local imported CA, and then uses that intermediate CA to handle all agent requests. - -If SPIRE is using an upstream CA: - -- For AWS PCA, the SPIRE server sends a certificate signing request to AWS PCA for signing. It then uses the issued certificate to sign workload certificates. -- For disk, AWS Secrets Manager, and Vault SPIRE Server uses the upstream certificate to create an intermediate certificate for itself. It then signs workload certificates using this intermediate certificate. -- Each upstream CA has a different API for that exchange, and each of those transactional processes are unique to each upstream. -- SPIRE plugins take care of the mechanics of each of those unique upstream CAs. You need to ensure that these plugins are enabled and allowed. You also need to test that it's possible to proxy each one of the auth methods supported by each of the plugins being used. - -{{< call-out "note" >}} -Refer to the [Secure Mesh Traffic using mTLS]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) guide for more information on configuring mTLS. -{{< /call-out>}} - -### NATS Message Bus - -Kubernetes resources and container names: - -- Containers: nats-server, nginx-mesh-cert-reloader, nginx-mesh-cert-reloader-init -- Deployment: deployment/nats-server -- Service: service/nats-server - -NGINX Service Mesh uses the [NATS](https://docs.nats.io/nats-concepts/intro) message bus to communicate between the control plane and data plane. A dedicated connective solution provides reliable performance for highly scalable data planes. - -The control plane sends configuration changes to the data plane using NATS. NATS is a popular solution for microservices because of how it handles modern distributed systems. NATS can manage thousands of connections and uses a publisher/subscriber architecture, allowing the NGINX Service Mesh controller to decouple from the data plane sidecars. The controller can dedicate itself to managing configurations rather than managing direct connections to the sidecars. - -#### Lifecycle - -NATS, a part of the NGINX Service Mesh control plane, is deployed when running `nginx-meshctl deploy`. An init container and sidecar are deployed with the NATS container. The init container loads [SPIRE](#spire) certificates into the NATS container on startup, and the sidecar loads the certificates as they rotate. NATS uses these certificates to deploy a TLS server for sending and receiving secure traffic between publishers and subscribers. NATS verifies the client identities and protects the configuration from bad actors. Since the NGINX Service Mesh controller and sidecars also use SPIRE certificates, they are included in the trust domain. - -The NGINX Service Mesh controller connects to NATS on startup, establishing itself as a publisher. When new events occur in Kubernetes that NGINX Service Mesh watches for--for example, when new services or traffic policies are created--the control plane builds an internal configuration from this data. This configuration is then sent over a secure NATS channel to all subscribers. - -NGINX Service Mesh sidecars (specifically agents) connect to NATS when they are deployed with an application. These agents establish themselves as subscribers and accept configuration messages sent through the secure NATS channel from the control plane. - -### Observability - -NGINX Service Mesh lets you observe application behavior using a combination of internal and third-party solutions to expose metrics and tracing data. - -{{< call-out "note" >}} -See the [Monitoring and Tracing]({{< ref "/mesh/guides/monitoring-and-tracing.md" >}}) guide for more information on integrating your Prometheus, Grafana, and/or tracing backends with NGINX Service Mesh. -{{< /call-out>}} - -#### Metrics - -- [Prometheus](https://prometheus.io/docs/introduction/overview/) is a systems monitoring tool that can scrape NGINX Service Mesh sidecars, where metrics are exposed by NGINX Plus, then store this data in memory. You can access this data by querying Prometheus directly, querying the NGINX Service Mesh metrics server, or viewing a Grafana dashboard. Prometheus also scrapes NGINX Ingress Controller metrics if it's been deployed. - -- [Grafana](https://grafana.com/grafana/) is a dashboard-based tool that can be used to visualize Prometheus metrics data. NGINX Service Mesh provides a [custom dashboard](https://github.com/nginxinc/nginx-service-mesh/tree/main/examples/grafana) that can be imported into your deployment of Grafana. - -- The NGINX Service Mesh metrics server is also a control plane component, extending the Kubernetes API, known as an [aggregation layer](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/). When queried, this server gets metrics data from Prometheus and formats the data adhering to the Service Mesh Interface (SMI) standards. This server provides quick access to the basic metrics data in a standard format defined by SMI. - -{{< call-out "note" >}} -See the [Traffic Metrics]({{< ref "/mesh/guides/smi-traffic-metrics.md" >}}) guide for more information on how to visualize metrics data from NGINX Service Mesh. -{{< /call-out>}} - -#### Tracing - -[OpenTelemetry](https://opentelemetry.io/docs/) is a set of APIs, SDKs, tooling, and integrations that are designed for the creation and management of telemetry data such as traces, metrics, and logs. NGINX Service Mesh sidecars use the [OpenTelemetry NGINX module](https://github.com/open-telemetry/opentelemetry-cpp-contrib/tree/main/instrumentation/nginx) to export tracing data to an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) using the OpenTelemetry Protocol (OTLP). This module creates new tracing spans and propagates existing ones as requests pass through applications. The OpenTelemetry Collector can then be configured to export the tracing data to an upstream collector like Jaeger, DataDog, LightStep, or many others. - -Here is an example of the tracing data flow using both DataDog and LightStep as the final collectors: - -{{< img src="img/opentelemetry.png" alt="OpenTelemetry Data Flow" >}} -*Tracing data flow using the OpenTelemetry Collector* - -### Ingress and Egress Traffic - -You can deploy [NGINX Plus Ingress Controller](https://www.nginx.com/products/nginx-ingress-controller/) with NGINX Service Mesh to provide production-grade control over ingress and egress traffic. Like the NGINX Service Mesh sidecar, NGINX Plus Ingress Controller fetches certificates from [SPIRE](#spire) to authenticate with NGINX Service Mesh workloads. This integration with SPIRE allows NGINX Plus Ingress Controller to communicate with NGINX Service Mesh workloads without being injected with a sidecar. - -You can use [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) or [VirtualServer and VirtualServerRoute](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) resources to expose services in NGINX Service Mesh from outside the Kubernetes cluster. - -Ingress resources create an HTTP/HTTPS load balancer for services in Kubernetes and support host- and path-based routing, as well as TLS/SSL termination. - -VirtualServers and VirtualServerRoutes are NGINX Plus Ingress Controller custom resources that support the Ingress feature set. These resources enable advanced traffic routing such as traffic splitting, access control, and rate-limiting. When you expose an NGINX Service Mesh service with an Ingress or VirtualServer resource, NGINX Plus Ingress Controller creates an HTTP/HTTPS route from outside the cluster to the service deployed in NGINX Service Mesh. The NGINX Plus Ingress Controller terminates the client's TLS/SSL connection if TLS/SSL termination is configured and then establishes an mTLS session with the NGINX Service Mesh workload. - -#### Default Egress Route - -NGINX Plus Ingress Controller lets you control the egress traffic from your cluster. You can configure Pods in NGINX Service Mesh to use the default egress route. This default route directs all egress traffic not destined for NGINX Service Mesh services through NGINX Plus Ingress Controller. - -The NGINX Plus Ingress Controller terminates the mTLS connection from the NGINX Service Mesh workload and routes the request to the egress service. Egress services can be services deployed outside the cluster, or they can be services deployed within the cluster that are not injected with the NGINX Service Mesh sidecar. - -{{< call-out "note" >}} -Refer to the [Deploy with NGINX Plus Ingress Controller]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md" >}}) guide for more information on using NGINX Plus Ingress Controller to route ingress and egress traffic to and from your NGINX Service Mesh workloads. -{{< /call-out>}} diff --git a/content/mesh/about/mesh-tech-specs.md b/content/mesh/about/mesh-tech-specs.md deleted file mode 100644 index 7bcc87d8a..000000000 --- a/content/mesh/about/mesh-tech-specs.md +++ /dev/null @@ -1,178 +0,0 @@ ---- -title: Technical Specifications -weight: 110 -description: Cluster requirements and F5 NGINX Service Mesh footprint. -toc: true -nd-docs: DOCS-677 -type: -- reference ---- - -The following document outlines the software versions and overhead F5 NGINX Service Mesh uses while running. - -## Software Versions - -The following tables list the software versions NGINX Service Mesh supports and uses by default. - -### Supported Versions - -{{% table %}} -| NGINX Service Mesh | Kubernetes | OpenShift | NGINX Ingress Controller | Helm | Rancher | -|--------------------|------------|-----------|--------------------------|----------|----------| -| v1.7.0+ | 1.22+ | 4.9+ | 2.2+ | 3.2+ | 2.5+ | -| v1.6.0 | 1.22+ | 4.9+ | 2.2+ (NGINX Plus only) | 3.2+ | 2.5+ | -{{% /table %}} - -{{% table %}} -| NGINX Service Mesh | SMI Traffic Access | SMI Traffic Metrics | SMI Traffic Specs | SMI Traffic Split | NSM RateLimit | NSM CircuitBreaker | -|--------------------|--------------------|---------------------|-------------------|-------------------|--------------------|--------------------| -| v1.2.0+ | v1alpha2 | v1alpha1\* | v1alpha3 | v1alpha3 | v1alpha1, v1alpha2 | v1alpha1 | -{{% /table %}} - -\* - in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets - -### Components -{{% table %}} -| NGINX Service Mesh | NGINX Plus (sidecar) | SPIRE | NATS | -|--------------------|----------------------|---------|-----------------------| -| v2.0.0+ | R28 | 1.5.6 | nats:2.9-alpine | -| v1.7.0 | R28 | 1.5.4 | nats:2.9-alpine | -| v1.6.0 | R27 | 1.4.4 | nats:2.9.3-alpine3.16 | -{{% /table %}} - -### Images -#### Distributed Images - -- `docker-registry.nginx.com/nsm/nginx-mesh-controller`: NGINX Service Mesh Controller. -- `docker-registry.nginx.com/nsm/nginx-mesh-metrics`: Gets Pod (and other Kubernetes resources) metrics. Refer to [SMI Metrics on GitHub](https://github.com/servicemeshinterface/smi-metrics) and `nginx-meshctl help top` for more information. -- `docker-registry.nginx.com/nsm/nginx-mesh-sidecar`: NGINX Service Mesh sidecar. -- `docker-registry.nginx.com/nsm/nginx-mesh-init`: NGINX Service Mesh sidecar init container. Sets up `iptables` for the sidecar. -- `docker-registry.nginx.com/nsm/nginx-mesh-cert-reloader`: NGINX Service Mesh certificate reloader. Loads and rotates certificates for the NATs server. - -#### Third Party Images -NGINX Service Mesh also pulls the following publicly-accessible third-party container images into your Kubernetes cluster in order to function: - -{{% table %}} -| Component | Image path(s) | Version tag | -|------------|------------------------------------------------------------------------|-------------| -| SPIRE | gcr.io/spiffe-io/spire-server | 1.5.6 | -| | gcr.io/spiffe-io/k8s-workload-registrar | 1.5.6 | -| | gcr.io/spiffe-io/spire-agent | 1.5.6 | -| | curlimages/curl | latest | -| | ubuntu (OpenShift only) | 22.04 | -| | ghcr.io/spiffe/spiffe-csi-driver (OpenShift only) | 0.2.1 | -| | registry.k8s.io/sig-storage/csi-node-driver-registrar (OpenShift only) | v2.7.0 | -| NATS | nats | 2.9-alpine | -| Helm hooks | bitnami/kubectl | latest | -{{% /table %}} - -### Libraries -{{% table %}} -| NGINX Service Mesh | OpenTelemetry C++ | -|--------------------|-------------------| -| v1.7.0+ | 1.8.1 | -| v1.6.0 | 1.4.1 | -{{% /table %}} - -### UDP -Linux kernel 4.18 is required if enabling UDP traffic proxying (disabled by default). Note that Amazon EKS v1.18 does not work with UDP enabled, due to it using Linux kernel 4.14. - -## Recommended Sizing - -A series of automated tests are frequently run to ensure mesh stability and reliability. For deployments less than 100 Pods, a minimum cluster environment is recommended: - -{{% table %}} -| Environment | Machine Type | Number of Nodes | -|-------------|---------------------------------|----------------------| -| GKE | n2-standard-4 (4 vCPU, 16GB) | 3 | -| AKS | Standard_D4s_v3 (4 vCPU, 16GiB) | 3 | -| EKS | t3.xlarge (4 vCPU, 16GiB) | 3 | -| AWS | t3.xlarge (4 vCPU, 16GiB) | 1 Control, 3 Workers | -{{% /table %}} - -## Overhead - -The overhead of NGINX Service Mesh varies depending on the component in the mesh and the type of resources currently deployed. The control plane is responsible for holding the state of all managed resources. Therefore, it scales up linearly with the number of resources being handled - be it Pods, Services, TrafficSplits, or any other resource in NGINX Service Mesh. Spire specifically watches for new workloads, which reside 1:1 in every Pod deployed. As a result, it scales up as more Pods are added to the mesh. - -The data plane sidecar must keep track of the other Services in the mesh as well as any traffic policies that are associated with it. Therefore, the resource load will increase as a function of the number of Services and traffic policies in the mesh. In an attempt to balance the stress on the cluster, we run a nightly test which flexes the most critical components of the mesh. Below are the details of this test, so you may get an idea of the overhead each component is responsible for and size your own cluster accordingly. - -### Stress Test Overhead - -Cluster Information: - -- Environment: GKE -- Node Type: n2-standard-4 (4 vCPU, 16GB) -- Number of nodes: 3 -- Kubernetes Version: 1.18.16 - -Metrics were gathered using the Kubernetes Metrics API. CPU is calculated in terms of the number of *cpu* units, where one cpu is equivalent to 1 vCPU/Core. For more information on the metrics API and how the data is recorded, see [The Metrics API](https://kubernetes.io/docs/tasks/debug-application-cluster/resource-metrics-pipeline/#the-metrics-api) documentation. - -#### CPU - -{{% table %}} -| Num Services | Control Plane (without metrics and tracing) | Control Plane Total | Average Sidecar | -|----------------|---------------------------------------------|---------------------|-----------------| -| 10 (20 Pods) | CPU: 0.075 vCPU | CPU: 0.095 vCPU | CPU: 0.033 vCPU | -| 50 (100 Pods) | CPU: 0.097 vCPU | CPU: 0.431 vCPU | CPU: 0.075 vCPU | -| 100 (200 Pods) | CPU: 0.148 vCPU | CPU: 0.233 vCPU | CPU: 0.050 vCPU | -{{% /table %}} - -#### Memory - -{{% table %}} -| Num Services | Control Plane (without metrics and tracing) | Control Plane Total | Average Sidecar | -|----------------|---------------------------------------------|----------------------|--------------------| -| 10 (20 Pods) | Memory: 168.766 MiB | Memory: 767.500 MiB | Memory: 33.380 MiB | -| 50 (100 Pods) | Memory: 215.289 MiB | Memory: 2347.258 MiB | Memory: 38.542 MiB | -| 100 (200 Pods) | Memory: 272.305 MiB | Memory: 4973.992 MiB | Memory: 52.946 MiB | -{{% /table %}} - -#### Disk Usage - -Spire uses a persistent volume to make restarts more seamless. NGINX Service Mesh automatically allocates 1 GB persistent volume in supported environments (see [Persistent Storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}) setup page for environment requirements). Below is the information on the disk usage within that volume. Disk usage scales directly with the number of Pods in the mesh. - -{{% table %}} -| Num Pods | Disk Usage | -|----------|------------| -| 20 | 4.2 MB | -| 100 | 4.3 MB | -| 200 | 4.6 MB | -{{% /table %}} - -## Ports - -The following table lists the ports and IP addresses the NGINX Service Mesh sidecar binds. - -{{% table %}} -| Port | IP Address | Protocol | Direction | Purpose | -|-------|------------|----------|-----------|---------------------------------------------| -| 8900 | 0.0.0.0 | All | Outgoing | Redirect to virtual server for traffic type 1 | -| 8901 | 0.0.0.0 | All | Incoming | Redirect to virtual server for traffic type 1 | -| 8902 | localhost | All | Outgoing | Redirection error | -| 8903 | localhost | All | Incoming | Redirection error | -| 8904 | localhost | TCP | Incoming | Main virtual server | -| 8905 | localhost | TCP | Incoming | TCP traffic denied by [Access Control policies]( {{< ref "/mesh/guides/smi-traffic-policies.md#access-control" >}}) | -| 8906 | localhost | TCP | Outgoing | Main virtual server | -| 8907 | localhost | TCP | Incoming | Permissive virtual server 2 | -| 8908 | 0.0.0.0 | UDP | Outgoing | Main virtual server | -| 8909 | 0.0.0.0 | UDP | Incoming | Main virtual server | -| 8886 | 0.0.0.0 | HTTP | Control | NGINX Plus API | -| 8887 | 0.0.0.0 | HTTP | Control | Prometheus metrics | -| 8888 | localhost | HTTP | Incoming | Main virtual server | -| 8889 | localhost | HTTP | Outgoing | Main virtual server | -| 8890 | localhost | HTTP | Incoming | Permissive virtual server 2 | -| 8891 | localhost | GRPC | Incoming | Main virtual server | -| 8892 | localhost | GRPC | Outgoing | Main virtual server | -| 8893 | localhost | GRPC | Incoming | Permissive virtual server 2 | -| 8894 | localhost | HTTP | Outgoing | [NGINX Ingress Controller egress traffic]( {{< ref "/mesh/tutorials/kic/egress-walkthrough.md" >}} ) | -| 8895 | 0.0.0.0 | HTTP | Incoming | Redirect health probes 3 | -| 8896 | 0.0.0.0 | HTTP | Incoming | Redirect HTTPS health probes 3 | -{{% /table %}} - -Notes: - -1. All traffic is redirected to these two ports. From there the sidecar determines the traffic type and forwards the traffic to the *Main virtual server* for that traffic type. - -2. The *Permissive virtual server* is used when permissive mTLS is configured. It's used to accept non-mTLS traffic, for example from Pods that aren't injected with a sidecar. See the [Secure Mesh Traffic using mTLS]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) for more information on permissive mTLS. - -3. The Kubernetes `readinessProbe` and `livenessProbe` need dedicated ports as they're not regular in-band mTLS traffic. diff --git a/content/mesh/about/what-is-nsm.md b/content/mesh/about/what-is-nsm.md deleted file mode 100644 index 1ae385795..000000000 --- a/content/mesh/about/what-is-nsm.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: What is NGINX Service Mesh? -weight: 100 -description: Learn about F5 NGINX Service Mesh fundamentals. -toc: true -nd-docs: DOCS-678 -type: -- concept ---- - -## What is a Service Mesh? - -A *service mesh* is an infrastructure layer that's designed to provide fast, reliable, and low-latency network connections for highly distributed applications requiring inter-process communications. A service mesh abstracts lower layer networking concerns away from application developers and business logic. Meshes are often optimized for container environments and seamlessly integrate into the orchestration system, providing consistent and reliable services for the ephemeral, scalable, and dynamic applications built honoring today's modern architectures. Common properties defined by service meshes include service discovery, identity, load balancing, encryption, traffic control, resiliency and availability, and observability. - -Various patterns exist for implementing service meshes, such as node level, service level, and application libraries. While implementations may differ, each service mesh generally serves the same purpose: an infrastructure layer that operates at the network interstitially between an application's distributed components. - -- **Node level**: a single mesh instance on a per-machine basis (cluster node or application host). The node-level mesh provides services for all application workloads via a single proxy per host, routing all application traffic to and from a single process. - -- **Service level**: a network component that resides alongside and close to each individual application service instance (a 1:1 relationship of mesh instance to service runtime instance). Each workload is allocated a dedicated proxy. This implementation may also be referred to as the sidecar pattern and is the most popular implementation style. The advantage of the sidecar (and node level) pattern is a decoupling of mesh and business logic functions. - -- **Application libraries**: each application workload is compiled and linked to libraries that provide network and mesh functions. Application business logic must be designed, developed, and possibly recompiled to use the library SDKs. Service mesh properties are incorporated into the binary and are coupled to the design and code of each business logic entity. - -F5 NGINX Service Mesh implements the service level (sidecar) pattern. Each mesh functional unit resides next to the orchestration system's smallest unit of abstraction (for instance, in a Kubernetes environment, there is a proxy per Pod). Sidecars handle interservice communications, monitoring, and security‑related concerns. In other words, anything that can be abstracted away from the individual services. This way, developers can handle development, support, and maintenance for the services' application code, and operations teams can maintain the service mesh and run the app. - -Service mesh abstractions have been acknowledged as important pieces to enable microservice architectures. - -## Service Mesh Concepts - -Microservices, and in turn, container orchestration systems and service mesh, come with their own terminology for component services and functions: - -1. **Microservices Architecture:** Also known as microservices, it is an architecture design enabling rapid development, quick iteration, independently scalable components, decoupled business functions, and reliable deployment of complex and distributed applications. It emphasizes strong boundaries of responsibility, highly specialized, independent functional components, and the Unix philosophy of design (functional components should be designed to do one thing well; "simple, short, clear, modular, and extensible code"). - -1. **Container orchestration system:** Container workloads and orchestration systems enable microservice architectures. With its focus on small, independent, and scalable components, a microservice architecture leads to a multiplication of processes needing management. In basic terms, a container is a bundle of software and its dependencies packaged together to run isolated in a virtual environment. Container orchestration refers to the lifecycle management, monitoring, and configuration of these individualized software bundles. Various options exist, such as Docker Swarm and Mesosphere DC/OS, with Kubernetes being the current de-facto market standard. NGINX Service Mesh only supports the Kubernetes orchestration system. - -1. **Service Abstraction:** A service can refer to the single running copy -- the host machine process -- of a microservice application; that is, one instance of one component of an aggregated and distributed application. Alternatively, a service may refer to the logical boundary around a functional unit, omitting the individual instances doing the work as they all work together performing identical functions. Kubernetes uses the latter definition: a Service is a configuration abstraction representing the conglomeration of replicated instances. Kubernetes's fundamental unit of work is a Pod, each being a replica. It's noteworthy that the fundamental quantum for Kubernetes is not the container, but the Pod, which can be one or many containers. In Kubernetes, clients rarely communicate with Pods but communicate instead through the Service abstraction. NGINX Service Mesh carries this abstraction forward, requiring functional components to be represented as Services before providing infrastructure access to the individual replicas. - -1. **Controller pattern:** As with control theory, robotics, and automation, Kubernetes uses a controller pattern to regulate the system's state. Controllers are often implemented as event loops reacting to and actuating the desired state of the system. Each individual controller may actuate on one isolated configuration resource with its side-effects internal to the cluster itself. Or the controller may watch and create relationships across multiple configuration objects and make state changes to the cluster itself and external resources throughout the environment. Ultimately, each controller operates in a loop -- actuating, enforcing, and repairing the desired state of the system. NGINX Service Mesh control plane implements this pattern across many Kubernetes configuration primitives and custom extensions. These controllers work together to maintain a stable mesh infrastructure for application components. - -1. **Sidecar pattern:** As mentioned earlier, NGINX Service Mesh uses the container sidecar pattern to steer traffic, enforce policy, provide resiliency, and abstract network concerns from the application business logic. The sidecar pattern places a sibling container "next" to workload containers -- often, this means sharing the net and IPC namespaces, among others -- and enables the augmentation, enhancement, or extension of a process without requiring changes to the original process or application. Kubernetes provides features to allow this pattern. For instance, multiple containers can reside in a single Pod, and configurations can be mutated to add containers before being accepted (this is known as injection). Mesh operators can opt-in and opt-out of this behavior at various layers; but, in practice, each application Pod within the mesh will have a sidecar injected. This sidecar is responsible for the infrastructure properties provided by the mesh as a whole. - -## Service Mesh Properties - -NGINX Service Mesh provides the following properties enabled by its administrative and functional configuration resources. We intend to offer a high-level explanation of important service mesh properties, with each corresponding feature discussed in more detail within our guides and tutorials. - -1. **Service Discovery:** As individual instances appear and disappear from existence, all other running services need to know how to reach them and where. Typically, the instance performs a DNS lookup for this purpose. The container orchestration framework keeps a list of instances that are ready to receive requests and provides the interface for DNS queries. The service mesh does not interfere with this process and maintains the service list while also overlaying other mesh properties to the known services. For example, Kubernetes includes and manages DNS servers within the cluster. DNS entries for each Pod (individual workload instances) and Services (the load balancing and collective functional unit abstraction) are managed according to the resource lifecycle. NGINX Service Mesh maintains an awareness of the same IP address sets, passing traffic to the proper workloads based on its independent load balancing algorithms (see below). - -1. **Identity:** Each registered service and the underlying entities receive an immutable identity. These identities form a trust domain and a simple base authentication layer for included service instances. The identity system works in accord with encryption schemes to build a zero-trust environment and a foundation for more advanced traffic control, shaping, and enforced application topologies when the basic, flat landscape is undesirable. NGINX Service Mesh provides and enforces identity using SPIFFE and the SPIRE runtime (see the Architecture section for details). Workload identity, rooted by Kubernetes ServiceAccounts and verified via the SPIRE runtime, forms the foundation of the NGINX Service Mesh's Access Control features (see the [Services using Access Control]({{< ref "/mesh/tutorials/accesscontrol-walkthrough.md" >}}) tutorial for a hands-on guide to NGINX Service Mesh's authorization solution). - -1. **Load Balancing:** Most orchestration frameworks already provide Layer 4 (transport layer) load balancing. A service mesh implements more sophisticated Layer 7 (application layer) load balancing, with richer algorithms and more powerful traffic management. Load‑balancing parameters can be modified via the API, making it possible to orchestrate blue‑green or canary deployments. NGINX Service Mesh supports multiple load balancing algorithms. Further details are documented in the [Load Balancing]({{< ref "/mesh/get-started/install/configuration.md#load-balancing" >}}) section. - -1. **Encryption:** The service mesh can offload complicated encryption and decryption responsibilities from functional components while also providing PKI management capabilities. The service mesh can add near-universal encryption between application endpoints with relative ease. The service mesh is optimized to provide connection re-use, session persistence, and mutual TLS (mTLS) with little to no administrative input; the generation and distribution of certificates and keys are handled automatically. For more information on securing traffic with NGINX Service Mesh, see the [Secure Mesh Traffic using mTLS]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) guide. - -1. **Traffic Control:** The service mesh makes it possible to control traffic at the application layer (Layer 7). Topologies can be created where tiers of access or specific point-to-point communications are enabled and disabled. The service mesh can provide efficient authorization functionality, allowing transactions at a granular level: endpoints, paths, methods, among others. Application instances can be protected while in development, individual features enabled and tested dynamically, and traffic shaped using blue-green and canary deployment patterns. NGINX Service Mesh supports access control and traffic shaping using [Traffic Policies]({{< ref "/mesh/guides/smi-traffic-policies.md" >}}) for more advanced traffic topologies. - -1. **Resiliency and Availability:** In conjunction with Service Discovery and Load Balancing, the service mesh will optimize for uptime. Many microservices use stateless, "fail fast" design patterns. This allows for service instances to scale up and down quickly, minimizing failure damage and reacting promptly to changing usage and environmental states. The service mesh will perform connection and transaction retries, dynamically updating its known instances while also protecting server-side connections with configurable rate limit and circuit breaker settings. Each NGINX Service Mesh instance provides classic reverse proxy features in addition to other resiliency patterns, our [NGINX SMI Extensions]({{< ref "/mesh/guides/smi-traffic-policies.md#nginx-smi-extensions" >}}) guide discusses these features in greater detail. - -1. **Observability:** As services expand, functional components multiply. As the number of service instances increases, an application can become inscrutable to developers and administrators. The service mesh has a complete view of the system and will provide insights into the application's operation and performance. NGINX Service Mesh will provide discrete metrics and tracing data that can be aggregated by popular projects like Prometheus and Jaeger. Observability (metrics and tracing) is a fundamental mesh property. To learn more about NGINX Service Mesh's offering, see our [Monitoring and Tracing]({{< ref "/mesh/guides/monitoring-and-tracing.md" >}}) guide. diff --git a/content/mesh/examples/index.md b/content/mesh/examples/index.md deleted file mode 100644 index 3d65eaa0f..000000000 --- a/content/mesh/examples/index.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -headless: true ---- \ No newline at end of file diff --git a/content/mesh/get-started/_index.md b/content/mesh/get-started/_index.md deleted file mode 100644 index 4ddf46179..000000000 --- a/content/mesh/get-started/_index.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Getting Started -description: Learn how to install NGINX Service Mesh. -weight: 100 -url: /nginx-service-mesh/get-started/ -type: -- concept ---- - diff --git a/content/mesh/get-started/install/_index.md b/content/mesh/get-started/install/_index.md deleted file mode 100644 index b11a0a3c7..000000000 --- a/content/mesh/get-started/install/_index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Install NGINX Service Mesh -description: "Learn how to install NGINX Service Mesh." -weight: 2 -url: /nginx-service-mesh/get-started/install/ ---- - diff --git a/content/mesh/get-started/install/configuration.md b/content/mesh/get-started/install/configuration.md deleted file mode 100644 index 9803372c5..000000000 --- a/content/mesh/get-started/install/configuration.md +++ /dev/null @@ -1,321 +0,0 @@ ---- -title: Configuration Options -weight: 100 -description: Learn about F5 NGINX Service Mesh features and deployment options. -toc: true -nd-docs: DOCS-679 -type: -- concept ---- - -## Overview - -This document provides an overview of the various options you can configure when deploying F5 NGINX Service Mesh. We strongly recommended that you review all of the available options discussed in this document *before* deploying NGINX Service Mesh. - -{{< call-out "tip" >}} -To manage your configuration after deployment, you can use the NGINX Service Mesh API. - -Refer to the [API Usage Guide]( {{< ref "/mesh/reference/api-usage.md" >}} ) for more information. -{{< /call-out >}} - -{{< call-out "note" >}} -For Helm users, the `nginx-meshctl deploy` command-line options map directly to Helm values. Alongside this guide, check out the [Helm Configuration Options]( {{< ref "/mesh/get-started/install/install-with-helm.md#configuration-options" >}} ). -{{< /call-out >}} - -## Mutual TLS - -For information on the mTLS configuration options--including how to use a custom Upstream Certificate Authority--see how to [Secure Mesh Traffic using mTLS]( {{< ref "/mesh/guides/secure-traffic-mtls.md#usage" >}} ). - -## Access Control - -By default, traffic flow is allowed for all services in the mesh. - -To change this to a closed global policy and only allow traffic to flow between services that have access control policies defined, use the `--access-control-mode` flag when deploying NGINX Service Mesh: - -```bash -nginx-meshctl deploy ... --access-control-mode deny -``` - -If you need to [modify the global access control mode]( {{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}} ) after you've deployed NGINX Service Mesh, you can do so by using the API. - -## Client Max Body Size - -By default, NGINX allows a client request body to be up to 1m in size. - -To change this value to a different size, use the `--client-max-body-size` flag when deploying NGINX Service Mesh: - -```bash -nginx-meshctl deploy ... --client-max-body-size 5m -``` - -Setting the value to "0" allows for an unlimited request body size. - -To configure the client max body size for a specific Pod, add the `config.nsm.nginx.com/client-max-body-size: ` annotation to the *PodTemplateSpec* of your Deployment, StatefulSet, and so on. - -If you need to [modify the global client max body size]( {{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}} ) after you've deployed NGINX Service Mesh, you can do so by using the API. - -{{< call-out "note" >}} -[NGINX core module documentation](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) for `client_max_body_size`. -{{< /call-out>}} - -## Logging - -By default, the NGINX sidecar emits logs at the `warn` level. - -To set the desired log level, use the `--nginx-error-log-level` flag when deploying NGINX Service Mesh: - -```bash -nginx-meshctl deploy ... --nginx-error-log-level debug -``` - -All of the NGINX error log levels are supported, in the order of most to least verbose: - -- `debug`, -- `info`, -- `notice`, -- `warn`, -- `error`, -- `crit`, -- `alert`, -- `emerg` - -By default, the NGINX sidecar emits logs using the `default` format. The [supported formats](https://nginx.org/en/docs/http/ngx_http_log_module.html#log_format) are `default` and `json`. - -To set the NGINX sidecar logging format, use the `--nginx-log-format` flag when deploying NGINX Service Mesh: - -```bash -nginx-meshctl deploy ... --nginx-log-format json -``` - -If you need to [modify the log level or log format]( {{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}} ) after you've deployed NGINX Service Mesh, you can do so by using the API. - -## Load Balancing - -By default, the NGINX sidecar uses the `least_time` load balancing method. - -To set the desired load balancing method, use the `--nginx-lb-method` flag when deploying the -NGINX Service Mesh: - -```bash -nginx-meshctl deploy ... --nginx-lb-method "random two least_conn" -``` - -To configure the load balancing method for a Service, add the `config.nsm.nginx.com/lb-method: ` annotation to the `metadata.annotations` field of your Service. - -The supported methods (used for both `http` and `stream` blocks) are: - -- `round_robin` -- `least_conn` -- `least_time` -- `least_time last_byte` -- `least_time last_byte inflight` -- `random` -- `random two` -- `random two least_conn` -- `random two least_time` -- `random two least_time=last_byte` - -{{< call-out "note" >}} -`least_time` and `random two least_time` are treated as "time to first byte" methods. `stream` blocks with either -of these methods are given the `first_byte` method parameter, and `http` blocks are given the `header` parameter. -{{< /call-out >}} - -For more information on how these load balancing methods work, see [HTTP Load Balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/) and [TCP and UDP Load Balancing](https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/). - -## Monitoring and Tracing - -NGINX Service Mesh can connect to your Prometheus and tracing deployments. Refer to [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) for more information. - -## Sidecar Proxy - -NGINX Service Mesh works by injecting a sidecar proxy into Kubernetes resources. You can choose to inject the sidecar proxy into the YAML or JSON definitions for your Kubernetes resources in the following ways: - -- [Automatic Injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) -- [Manual Injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#manual-proxy-injection" >}} ) - -## Supported Labels and Annotations - -NGINX Service Mesh supports the use of the labels and annotations listed in the tables below. -If not specified, then the global defaults will be used. - -### Namespace Labels - -{{% table %}} -| Label | Values | -|---------------------------------------------------------------------------------------------------------------|-----------------------| -| [injector.nsm.nginx.com/auto-inject]({{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}}) | `enabled`, `disabled` | -{{% /table %}} - -### Pod Labels - -{{% table %}} -| Label | Values | -|---------------------------------------------------------------------------------------------------------------|-----------------------| -| [injector.nsm.nginx.com/auto-inject]({{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}}) | `enabled`, `disabled` | -| [nsm.nginx.com/enable-ingress]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md#secure-communication-between-nginx-plus-ingress-controller-and-nginx-service-mesh" >}}) | `true`, `false` | -| [nsm.nginx.com/enable-egress]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md#enable-egress" >}}) | `true`, `false` | -{{% /table %}} - -### Pod Annotations - -{{% table %}} -| Annotation | Values | Default | -|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|---------------| -| [config.nsm.nginx.com/mtls-mode]({{< ref "/mesh/guides/secure-traffic-mtls.md#change-the-mtls-setting-for-a-resource" >}}) | `off`, `permissive`, `strict` | `permissive` | -| [config.nsm.nginx.com/client-max-body-size](#client-max-body-size) | `0`, `64k`, `10m`, ... | `1m` | -| [config.nsm.nginx.com/ignore-incoming-ports]({{< ref "/mesh/guides/inject-sidecar-proxy.md#ignore-specific-ports" >}}) | list of port strings | "" | -| [config.nsm.nginx.com/ignore-outgoing-ports]({{< ref "/mesh/guides/inject-sidecar-proxy.md#ignore-specific-ports" >}}) | list of port strings | "" | -| [config.nsm.nginx.com/default-egress-allowed]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md#enable-egress" >}}) | `true`, `false` | `false` | -{{% /table %}} - -The Pod labels and annotations should be added to the **PodTemplateSpec** of a Deployment, StatefulSet, and so on, **before** injecting the sidecar proxy. -For example, the following `nginx` Deployment is configured with an `mtls-mode` of `strict`: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - annotations: - config.nsm.nginx.com/mtls-mode: strict - spec: - containers: - - name: nginx - image: nginx - ports: - - containerPort: 80 -``` - -- When you need to update a label or annotation, be sure to edit the Deployment, StatefulSet, and so on; if you edit a Pod, then those edits will be overwritten if the Pod restarts. -- In the case of a standalone Pod, you should edit the Pod definition, then restart the Pod to load the new config. - -### Service Annotations - -{{% table %}} -| Annotation | Values | Default | -|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|---------------| -| [config.nsm.nginx.com/lb-method](#load-balancing) | `least_conn`, `least_time`, | `least_time` | -| | `least_time last_byte`, | | -| | `least_time last_byte inflight`, | | -| | `round_robin`, `random`, `random two`, | | -| | `random two least_conn`, | | -| | `random two least_time`, | | -| | `random two least_time=last_byte` | | -{{% /table %}} - -Service annotations are added to the metadata field of the Service. -For example, the following Service is configured to use the `random` load balancing method: - -```yaml -apiVersion: v1 -kind: Service -metadata: - name: my-service - annotations: - config.nsm.nginx.com/lb-method: random -spec: - selector: - app: MyApp - ports: - - protocol: TCP - port: 80 - targetPort: 80 -``` - -## Supported Protocols - -NGINX Service Mesh supports HTTP and GRPC at the L7 protocol layer. Sidecars can proxy these protocols explicitly. When HTTP and GRPC protocols are configured, a wider range of traffic shaping and traffic control features are available. - -NGINX Service Mesh provides TCP transport support for Services that employ other L7 protocols. Workloads are not limited to communicating via HTTP and GRPC alone. These workloads may not be able to use some of the advanced L7 functionality. - -NGINX Service Mesh provides UDP transport applications that need one-way communication of datagrams. For bidirectional communication we recommend using TCP. - -Protocols will be identified by the Service's port config, `.spec.ports`. - -### Identification Rules - -NGINX Service Mesh uses identification rules both on the incoming and outgoing side of application deployments to identify the kind of traffic that is being sent, as well as what traffic is intended for a particular application. - -#### Outgoing - -In a service spec, if the port config is named, the name will be used to identify the protocol. If the name contains a dash it will be split using the dash as a delimiter and the first portion used, for example, 'http-example' will set protocol 'http'. - -If the port config sets a well-known port (`.spec.ports[].port`), this value will be used to determine protocol, for example, 80 will set protocol 'http'. - -If none of these rules are satisfied the protocol will default to TCP. - -For an example of how this is used, see [Deploy an Example App]( {{< ref "/mesh/tutorials/deploy-example-app.md" >}}) in the tutorials section. - -#### Incoming - -For a particular deployment or pod resource, the `containerPort` (`.spec.containers[].ports.containerPort`) field of the Pod spec is used to determine what traffic should be allowed to access your application. This is particularly important when using [strict mode]( {{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) for denying unwanted traffic. - -For an example of how this is used, see [Deploy an Example App]( {{< ref "/mesh/tutorials/deploy-example-app.md" >}}) in the tutorials section. - -### Protocols - -- HTTP - name 'http', port 80 -- GRPC - name 'grpc' -- TCP - name 'tcp' -- UDP - name 'udp' - -### Unavailable protocols - -- SCTP - -## Traffic Encryption - -NGINX Service Mesh uses SPIRE -- the [SPIFFE](https://spiffe.io/) Runtime Environment -- to manage certificates for secure communication between proxies. - -Refer to [Secure Mesh Traffic using mTLS]( {{< ref "/mesh/guides/secure-traffic-mtls.md">}} ) for more information about configuration options. - -## Traffic Metrics - -NGINX Service Mesh can export metrics to Prometheus, and provides a [custom dashboard](https://github.com/nginxinc/nginx-service-mesh/tree/main/examples/grafana) for visualizing metrics in Grafana. - -Refer to the [Traffic Metrics]( {{< ref "/mesh/guides/smi-traffic-metrics.md">}} ) topic for more information. - -## Traffic Policies - -NGINX Service Mesh supports the SMI spec, which allows for a variety of functionality within the mesh, from traffic shaping to access control. - -Refer to the [SMI GitHub repo](https://github.com/servicemeshinterface/smi-spec) to find out more about the SMI spec and how to configure it. - -Refer to the [Traffic Policies]( {{< ref "smi-traffic-policies.md" >}} ) topic for examples of how you can use the SMI spec in NGINX Service Mesh. - -## Environment - -By default, NGINX Service Mesh deploys with the `kubernetes` configuration. If deploying in an Openshift environment, use the `--environment` flag to specify an alternative environment: - -```bash -nginx-meshctl deploy ... --environment "openshift" -``` - -See [Considerations]({{< ref "/mesh/get-started/platform-setup/openshift.md" >}}) for when you're deploying in an OpenShift cluster. - -## Headless Services - -Avoid configuring traffic policies such as TrafficSplits, RateLimits, and CircuitBreakers for headless services. These policies will not work as expected because NGINX Service Mesh has no way to tie each pod IP address to its headless service. - -When using NGINX Service Mesh, it is necessary to declare the port in a headless service in order for it to be matched. Without this declaration, traffic will not be routed correctly. - -## UDP Proxying - -UDP traffic proxying is turned off by default. You can activate it at deploy time using the `--enable-udp` flag. Linux kernel 4.18 or greater is required. - -NGINX Service Mesh automatically detects and adjusts the `eth0` interface to support the 32 bytes of space required for PROXY Protocol V2. -See the [UDP and eBPF architecture]({{< ref "/mesh/about/architecture.md#udp-and-ebpf" >}}) section for more information. - -NGINX Service Mesh does not detect changes made to the MTU in the pod at runtime. -If adding a CNI changes the MTU of the `eth0` interface of running pods, you should re-roll the affected pods to ensure those changes take place. diff --git a/content/mesh/get-started/install/install-with-helm.md b/content/mesh/get-started/install/install-with-helm.md deleted file mode 100644 index e11f34b93..000000000 --- a/content/mesh/get-started/install/install-with-helm.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Install with Helm -draft: false -toc: true -description: This guide explains how to install F5 NGINX Service Mesh using Helm. -weight: 300 -nd-docs: DOCS-680 -type: -- how-to ---- - -## Prerequisites - -Before installing F5 NGINX Service Mesh, make sure you've completed the following steps. - -- You have Helm version 3.2.0 or newer installed. -- You have a working and [supported]({{< ref "/mesh/about/mesh-tech-specs.md#supported-versions" >}}) Kubernetes or OpenShift cluster. -- You followed the [Kubernetes]( {{< ref "/mesh/get-started/platform-setup/_index.md" >}} ) or [OpenShift]( {{< ref "/mesh/get-started/platform-setup/openshift.md" >}} ) Platform Setup guide to **prepare your cluster** to work with NGINX Service Mesh. -- You have the Kubernetes `kubectl` command-line utility configured on the machine where you want to install NGINX Service Mesh. -- You reviewed the [Configuration Options](#configuration-options). - -## Get the Chart - -When installing with Helm, you can either add the NGINX Service Mesh Helm repository or download the charts from GitHub. - -### Add the Helm Repository - -This step is required if you're installing the chart via the helm repository. - -```bash -helm repo add nginx-stable https://helm.nginx.com/stable -helm repo update -``` - -### Download the Chart from GitHub - -This step is required if you're installing the chart using its sources. Additionally, this step is required for upgrading the NGINX Service Mesh Custom Resource Definitions (CRDs). - -```bash -git clone https://github.com/nginxinc/nginx-service-mesh -cd nginx-service-mesh/helm-chart -git checkout v2.0.0 -``` - -## Install the Chart - -NGINX Service Mesh requires a dedicated namespace for the control plane. -You can create this namespace yourself, or allow Helm to create it for you via the `--create-namespace` flag when installing. -This namespace is dedicated to the NGINX Service Mesh control plane and **should not be used for anything else**. - -NGINX Service Mesh will pull multiple required images into your Kubernetes cluster in order to function, some of which are from publicly-accessible third parties. For a full list refer to the [Technical Specifications]({{< ref "/mesh/about/mesh-tech-specs.md#images" >}}). If you are using a private registry, see our [private registry guide]({{< ref "/mesh/guides/private-registry.md" >}}). - - -If [Persistent Storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}) is not configured in your cluster, disable it in the mesh by adding the `--set mtls.persistentStorage=off` flag to the install commands below. - -OpenShift users must add the `--set environment=openshift` flag to the install commands below. - -### Install via Repository - -To install the chart with the release name `nsm` and namespace `nginx-mesh`, run: - -```bash -helm install nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --create-namespace --wait -``` - -### Install via Source - -To install the chart with the release name `nsm` and namespace `nginx-mesh`, run: - -```bash -helm install nsm . --namespace nginx-mesh --create-namespace --wait -``` - -NGINX Service Mesh control plane Pods may take some time to become Ready once installed. -Some Pods may display error logs during the startup process. -This typically occurs as the Pods attempt to connect to each other. - -OpenShift users may see error events related to security contexts while the NGINX Service Mesh control plane is installing. -These should resolve themselves as each component becomes ready. - -Ensure all control plane Pods are in a Ready state before deploying your applications. - -## Next Steps - -Congratulations! At this point NGINX Service Mesh should be successfully installed in your cluster. - -### Add the Sidecar to Your Workloads - -Now that the control plane is deployed in your cluster, it is time to add the sidecar to your workloads so you can start using the mesh. -Check out the [Sidecar Proxy Injection]({{< ref "/mesh/guides/inject-sidecar-proxy.md" >}}) doc for instructions on how to do that. - -### Troubleshooting - -If the mesh fails to install, review the [Platform Setup]({{< ref "/mesh/get-started/platform-setup/_index.md" >}}) docs for your platform, the installation steps above, and the [Configuration Options]({{< ref "#configuration-options" >}}) to ensure everything is configured correctly. -A couple frequent problem areas are cluster permissions, security contexts (particularly in OpenShift), and [Persistent Storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}). - -If the mesh installation failed or you pressed ctrl-C during deployment, make sure to first [remove the mesh]({{< ref "/mesh/get-started/uninstall/uninstall-with-helm.md" >}}) before attempting to re-install. - -If you are unable to resolve the issues, please reach out to the appropriate [support channel]({{< ref "/mesh/support/contact-support.md" >}}). - -## Configuration Options - -The [values.yaml](https://github.com/nginxinc/nginx-service-mesh/blob/main/helm-chart/values.yaml) file within the `nginx-service-mesh` Helm chart contains the deployment configuration for NGINX Service Mesh. -These configuration fields map directly to the `nginx-meshctl deploy` command-line options mentioned throughout our documentation. -More details about these options can be found in the [Configuration]( {{< ref "/mesh/get-started/install/configuration.md" >}} ) guide. -You can update these fields directly in the `values.yaml` file, or by specifying the `--set` flag when running `helm install`. - -The following table lists the configurable parameters of the NGINX Service Mesh chart and their default values. - -{{% table %}} -| Parameter | Description | Default | -| --- | --- | --- | -| `registry.server` | Hostname:port (if needed) for registry and path to images. Affects: nginx-mesh-controller, nginx-mesh-cert-reloader, nginx-mesh-init, nginx-mesh-metrics, nginx-mesh-sidecar | docker-registry.nginx.com/nsm | -| `registry.imageTag` | Tag used for pulling images from registry. Affects: nginx-mesh-controller, nginx-mesh-cert-reloader, nginx-mesh-init, nginx-mesh-metrics, nginx-mesh-sidecar | 2.0.0 | -| `registry.key` | Contents of your Google Cloud JSON key file. Can be set via `--set-file registry.key=.json`. Cannot be used with username/password. | "" | -| `registry.username` | Username for accessing private registry. Cannot be used with key. | "" | -| `registry.password` | Password for accessing private registry. Cannot be used with key. | "" | -| `registry.disablePublicImages` | Do not pull third party images from public repositories. If true, registry.server is used for all images. | false | -| `registry.imagePullPolicy` | Image pull policy. | IfNotPresent | -| `accessControlMode` | Default access control mode for service-to-service communication. | allow | -| `environment` | Environment to deploy the mesh into. Valid values: "kubernetes", "openshift". | kubernetes | -| `enableUDP` | Enable UDP traffic proxying (beta). Linux kernel 4.18 or greater is required. | false | -| `nginxErrorLogLevel` | NGINX error log level. | warn | -| `nginxLogFormat` | NGINX log format. | default | -| `nginxLBMethod` | NGINX load balancing method. | least_time | -| `clientMaxBodySize` | NGINX client max body size. Setting to "0" disables checking of client request body size. | 1m | -| `prometheusAddress` | The address of a Prometheus server deployed in your Kubernetes cluster. Address should be in the format `.:`. | "" | -| `telemetry.samplerRatio` | The percentage of traces that are processed and exported to the telemetry backend. Float between 0 and 1. | 0.01 | -| `telemetry.exporters` | The configuration of exporters to send telemetry data to. | | -| `telemetry.exporters.otlp` | The configuration for an OTLP gRPC exporter. | | -| `telemetry.exporters.otlp.host` | The host of the OpenTelemetry gRPC exporter to connect to. Must be accessible from within the cluster. | | -| `telemetry.exporters.otlp.port` | The port of the OpenTelemetry gRPC exporter to connect to. | 4317 | -| `mtls.mode` | mTLS mode for pod-to-pod communication. | permissive | -| `mtls.caTTL` | The CA/signing key TTL in hours(h). Min value 24h. Max value 999999h. | 720h | -| `mtls.svidTTL` | The trust domain of the NGINX Service Mesh. Max value is 999999. | 1h | -| `mtls.persistentStorage` | Use persistent storage; "on" assumes that a StorageClass exists. | on | -| `mtls.spireServerKeyManager` | Storage logic for Spire Server's private keys. | disk | -| `mtls.caKeyType` | The key type used for the SPIRE Server CA. Valid values: "ec-p256", "ec-p384", "rsa-2048", "rsa-4096". | ec-p256 | -| `mtls.upstreamAuthority` | Upstream authority settings. If left empty, SPIRE is used as the upstream authority. See [values.yaml](https://github.com/nginxinc/nginx-service-mesh/blob/main/helm-chart/values.yaml) for how to configure. | {} | -{{% /table %}} diff --git a/content/mesh/get-started/install/install.md b/content/mesh/get-started/install/install.md deleted file mode 100644 index 1db35fda2..000000000 --- a/content/mesh/get-started/install/install.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: Install with nginx-meshctl -draft: false -toc: true -weight: 200 -nd-docs: DOCS-681 -type: -- how-to ---- - -## Overview - -This guide contains instructions for downloading and installing F5 NGINX Service Mesh using the `nginx-meshctl` command line tool. - -## Prerequisites - -Before installing NGINX Service Mesh, make sure you've completed the following steps. - -- You have a working and [supported]({{< ref "/mesh/about/mesh-tech-specs.md#supported-versions" >}}) Kubernetes or OpenShift cluster. -- You followed the [Platform Setup]({{< ref "/mesh/get-started/platform-setup/_index.md" >}}) guide to prepare your cluster to work with NGINX Service Mesh. -- You have the Kubernetes `kubectl` command-line utility configured on the machine where you want to install NGINX Service Mesh. - -## Install the CLI - -The following sections describe how to install the CLI on Linux, macOS, and Windows. - -### Download nginx-meshctl - -The NGINX Service Mesh command-line tool -- `nginx-meshctl` -- allows you to deploy, remove, and interact with the NGINX Service Mesh control plane. - -To install NGINX Service Mesh, you need to download the `nginx-meshctl` binary for your architecture. The latest version of `nginx-meshctl` is available on our [Github releases](https://github.com/nginxinc/nginx-service-mesh/releases/latest) page. - -### Install on Linux - -1. Download the appropriate binary for your architecture, either `nginx-meshctl__linux_amd64.tar.gz` or `nginx-meshctl__linux_arm64.tar.gz`. - -1. Unzip the binary. - - ```bash - tar -xvf nginx-meshctl__linux_amd64.tar.gz nginx-meshctl - ``` - -1. Move the `nginx-meshctl` executable in to your PATH. - - ```bash - sudo mv nginx-meshctl /usr/local/bin/nginx-meshctl - ``` - -1. Ensure the `nginx-meshctl` is executable. - - ```bash - sudo chmod +x /usr/local/bin/nginx-meshctl - ``` - -1. Test the installation. - - ```bash - nginx-meshctl - ``` - -### Install on macOS - -1. Download the appropriate binary for your architecture, either `nginx-meshctl__darwin_arm64.tar.gz` for M1 Macs or `nginx-meshctl__darwin_amd64.tar.gz` for Intel based Macs. - -1. Unzip the binary. - - ```bash - tar -xvf nginx-meshctl__darwin_amd64.tar.gz nginx-meshctl - ``` - -1. Move the `nginx-meshctl` executable in to your PATH. - - ```bash - sudo mv nginx-meshctl /usr/local/bin/nginx-meshctl - ``` - -1. Ensure the `nginx-meshctl` is executable. - - ```bash - sudo chmod +x /usr/local/bin/nginx-meshctl - ``` - -1. Test the installation. - - ```bash - nginx-meshctl - ``` - -### Install on Windows - -1. Download the appropriate binary for your architecture, either `nginx-meshctl__windows_amd64.zip` or `nginx-meshctl__windows_arm64.zip`. -1. Extract the binary, `nginx-meshctl.exe`, from the zip file. -1. Add the binary to your PATH. -1. Test the installation. - - ```bash - nginx-meshctl - ``` - -## Install the NGINX Service Mesh Control Plane - -NGINX Service Mesh will pull multiple required images into your Kubernetes cluster in order to function, some of which are from publicly-accessible third parties. For a full list refer to the [Technical Specifications]({{< ref "/mesh/about/mesh-tech-specs.md#images" >}}). If you are using a private registry, see our [private registry guide]({{< ref "/mesh/guides/private-registry.md" >}}). - -Check out the [Configuration Options]({{< ref "/mesh/get-started/install/configuration.md" >}}) to learn about the deployment options. -You can find the full list of options in the [`nginx-meshctl` Reference]( {{< ref "nginx-meshctl.md" >}} ). - -{{< call-out "important" >}} -`nginx-meshctl` creates the namespace for the NGINX Service Mesh control plane. -This namespace is dedicated to the NGINX Service Mesh control plane and **should not be used for anything else**. -If desired, you can specify any name for the namespace via the `--namespace` argument, but do not create this namespace yourself. -{{< /call-out >}} - -Follow the steps below to install the NGINX Service Mesh control plane. - -1. Run the `nginx-meshctl deploy` command using the desired [options]({{< ref "nginx-meshctl.md#deploy" >}}). - - **Examples:** - - Deploy NGINX Service Mesh using all of the default settings for the latest release: - - ```bash - nginx-meshctl deploy - ``` - - OpenShift users must add the `--environment openshift` flag when deploying: - - ```bash - nginx-meshctl deploy --environment openshift - ``` - - Disable [Persistent Storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}) if it is not configured in your cluster: - - ```bash - nginx-meshctl deploy --persistent-storage off - ``` - -1. Verify the pods are running. If running in OpenShift, you will see additional `spiffe-csi-driver` Pods. - - ```bash - $ kubectl get pods -n nginx-mesh - NAME READY STATUS RESTARTS AGE - nats-server-84f8b6f669-xszkc 1/1 Running 0 14m - nginx-mesh-controller-954467945-sc7qh 1/1 Running 0 14m - nginx-mesh-metrics-57464df46d-qskd2 1/1 Running 0 14m - spire-agent-92ktv 1/1 Running 0 15m - spire-agent-9dbn6 1/1 Running 0 15m - spire-agent-z5cq6 1/1 Running 0 15m - spire-server-0 2/2 Running 0 15m - ``` - -## Next Steps - -Congratulations! At this point NGINX Service Mesh should be successfully installed in your cluster. - -### Add the Sidecar to Your Workloads - -Now that the control plane is deployed in your cluster, it is time to add the sidecar to your workloads so you can start using the mesh. -Check out the [Sidecar Proxy Injection]({{< ref "/mesh/guides/inject-sidecar-proxy.md" >}}) doc for instructions on how to do that. - -### Troubleshooting - -If the mesh fails to install, review the [Platform Setup]({{< ref "/mesh/get-started/platform-setup/_index.md" >}}) docs for your platform, the installation steps above, and the [Configuration Options]({{< ref "/mesh/get-started/install/configuration.md" >}}) to ensure everything is configured correctly. -Some frequent problem areas are cluster permissions, security contexts (particularly in OpenShift), and [Persistent Storage]({{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}}). - -If the mesh installation failed or you pressed ctrl-C during deployment, make sure to first [remove the mesh]({{< ref "/mesh/get-started/uninstall/uninstall.md" >}}) before attempting to re-install. - -If you are unable to resolve the issues, please reach out to the appropriate [support channel]({{< ref "/mesh/support/contact-support.md" >}}). diff --git a/content/mesh/get-started/platform-setup/_index.md b/content/mesh/get-started/platform-setup/_index.md deleted file mode 100644 index 06622538f..000000000 --- a/content/mesh/get-started/platform-setup/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Prepare Your Cluster -description: Prepare your Kubernetes or OpenShift cluster for use with NGINX Service - Mesh. -weight: 1 -url: /nginx-service-mesh/get-started/platform-setup/ ---- - diff --git a/content/mesh/get-started/platform-setup/gke.md b/content/mesh/get-started/platform-setup/gke.md deleted file mode 100644 index 0fff8f586..000000000 --- a/content/mesh/get-started/platform-setup/gke.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Google Kubernetes Engine -description: Learn how to set up Google Kubernetes Engine (GKE) for use with F5 NGINX - Service Mesh. -toc: true -nd-docs: DOCS-683 -type: -- how-to ---- - -Google Kubernetes Engine (GKE) is a hosted Kubernetes solution created by Google. To use GKE with F5 NGINX Service Mesh, your Kubernetes user account has to have the `ClusterAdmin` role. - -{{< call-out "warning" >}} -These resources give NGINX Service Mesh administrator access to your cluster. This allows NGINX Service Mesh to access resources across all namespaces in your Kubernetes cluster. -{{< /call-out >}} - -To create a ClusterRole and ClusterRoleBinding for NGINX Service Mesh, run the `kubectl` command shown below: - -```bash -kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value core/account) -``` - -Regardless of which Kubernetes version you are using, if you are installing NGINX Service Mesh v1.6 or greater, you'll also need to install the [gke-gcloud-auth-plugin](https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke). This is because `nginx-meshctl` uses Kubernetes v1.25+ libraries internally. - -You can now deploy NGINX Service Mesh on your GKE cluster. diff --git a/content/mesh/get-started/platform-setup/kubeadm.md b/content/mesh/get-started/platform-setup/kubeadm.md deleted file mode 100644 index 1fd91a23b..000000000 --- a/content/mesh/get-started/platform-setup/kubeadm.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Kubeadm -description: Learn how to set up Kubeadm for use with F5 NGINX Service Mesh. -toc: true -nd-docs: DOCS-684 -type: -- how-to ---- - -Kubeadm is a tool that creates Kubernetes clusters by following best practices. To use kubeadm with F5 NGINX Service Mesh, you need to enable some extra flags on the Kubernetes API Server to enable Service Account Token Volume Projection. See [Service Account Token Volume Projection](#service-account-token-volume-projection) section to learn why this is needed. - -## New cluster - -When creating a new cluster, pass this extra configuration to kubeadm: - -```yaml -apiVersion: kubeadm.k8s.io/v1beta2 -kind: ClusterConfiguration -apiServer: - extraArgs: - service-account-signing-key-file: /etc/kubernetes/pki/sa.key - service-account-issuer: api - service-account-api-audiences: api -``` - -You can use this configuration as-is and save it to a file, or combine it with any other configuration you need. Pass in the config when initializing the cluster. Assuming you've saved the config as kubeadm.config: - -```bash -kubeadm init --config kubeadm.conf -W0817 17:54:27.384011 1526706 configset.go:202] WARNING: kubeadm cannot validate component configs for API groups [kubelet.config.k8s.io kubeproxy.config.k8s.io] -[init] Using Kubernetes version: v1.18.8 -[preflight] Running pre-flight checks -``` - -{{< call-out "note" >}} -You can ignore the warning in the output of `kubeadm init` as we're not providing custom configuration for kubelet or kubeproxy. -{{< /call-out >}} - -## Existing cluster - -If you are using an existing kubeadm cluster, add the following configuration to `/etc/kubernetes/manifests/kube-apiserver.yaml`: - -{{< call-out "note" >}} -This will cause the Kubernetes API Server to restart, which may lead to it being unavailable for a short period of time. Be sure to schedule a downtime window before modifying the Kubernetes API Server configuration. -{{< /call-out >}} - -```yaml -spec: - containers: - - command: - - kube-apiserver - - --service-account-api-audiences=api - - --service-account-issuer=api - - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key -``` - -The configuration will be automatically applied to the kube-api-server. - -## Persistent storage - -Kubeadm doesn't set up any persistent storage for you, but it's required to run NGINX Service Mesh in a production environment. See [Persistent Storage]( {{< ref "persistent-storage.md" >}} ) for more information. - -## Service Account Token Volume Projection - -NGINX Service Mesh requires you to enable [Service Account Token Volume Projection](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#service-account-token-volume-projection). With this feature enabled, kubelet will mount a Service Account Token into each pod that is specific to that pod and has an expiration. These Service Account tokens can be used to uniquely identify a specific pod running on a specific node. Without Service Account Token Volume Projection, Service Account tokens are still available but they're shared by all pods under the same service account. There is no way to uniquely identify which pod provided the token. - -NGINX Service Mesh uses SPIRE to provide identity and distribute certificates within the mesh. SPIRE works by having a server along with agents that run in a DaemonSet, 1 per node. With Service Account Token Volume Projection we can limit the damage a malicious user can do if they're able to deploy a pod using the SPIRE Agent's Service Account. Since we know the node that pod is deployed on, it only has access to certificates that would be distributed to pods running on that node, as opposed to having access to all certificates cluster-wide. diff --git a/content/mesh/get-started/platform-setup/kubespray.md b/content/mesh/get-started/platform-setup/kubespray.md deleted file mode 100644 index 2c4a48126..000000000 --- a/content/mesh/get-started/platform-setup/kubespray.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Kubespray -description: Learn how to set up Kubespray for use with F5 NGINX Service Mesh. -toc: true -nd-docs: DOCS-685 -type: -- how-to ---- - -[Kubespray](https://github.com/kubernetes-sigs/kubespray) is where Kubernetes meets [Ansible](https://www.ansible.com/). It's a composition of Ansible playbooks, provisioning tools, and domain knowledge for creating production-ready Kubernetes clusters. Kubespray builds on top of kubeadm. If you are using Kubespray v2.16.0 or later no changes are needed to deploy F5 NGINX Service Mesh. For older versions you need to enable some extra flags on the Kubernetes API Server to enable Service Account Token Volume Projection. See [Service Account Token Volume Projection]( {{< ref "kubeadm.md#service-account-token-volume-projection" >}} ) section to learn why this is needed. - -## Configuration changes - -{{< call-out "important" >}} -This section only applies to Kubespray versions older than v2.16.0. -{{< /call-out >}} - -When creating a new cluster, you need to pass some extra flags to kubespray using [group_vars](https://github.com/kubernetes-sigs/kubespray/blob/master/docs/ansible/vars.md). Add the following to `inventory//group_vars/k8s-cluster/k8s-cluster.yml`: - -```yaml -kube_kubeadm_apiserver_extra_args: - service-account-issuer: api - service-account-signing-key-file: /etc/kubernetes/ssl/sa.key - service-account-api-audiences: api -``` - -After making the changes, deploy kubespray as you usually would. - -{{< call-out "note" >}} -If you have an existing kubespray deployment, you need to create a new cluster. First make the changes in this section and then deploy a new cluster using the same command when you deployed the cluster before. The new cluster will reflect the new configuration. After deploying the new cluster, you can delete the old one. -{{< /call-out >}} - -## Persistent storage - -Kubespray doesn't set up any persistent storage for you, but it's required to run NGINX Service Mesh in a production environment. See [Persistent Storage]( {{< ref "persistent-storage.md" >}} ) for more information. diff --git a/content/mesh/get-started/platform-setup/openshift.md b/content/mesh/get-started/platform-setup/openshift.md deleted file mode 100644 index fa2769982..000000000 --- a/content/mesh/get-started/platform-setup/openshift.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: OpenShift -description: Learn what is different in OpenShift, and the considerations a user must - make. -toc: true -nd-docs: DOCS-689 -type: -- how-to ---- - -## Introduction - -OpenShift is a security-first platform, locking down privileges and capabilities to ensure that workloads are running securely. OpenShift creates additional security mechanisms in the form of [security context constraints](https://docs.openshift.com/container-platform/4.8/authentication/managing-security-context-constraints.html). These constraints restrict the default permissions a workload is able to operate with. It also needs a user or workload management tool (such as a service mesh) to iteratively build out the specific permissions. - -To provide an increased level of security, F5 NGINX Service Mesh provides the security context constraints needed to run its control plane components as well as the sidecar attached to every workload under its management. Additionally, a mechanism (Container Storage Interface (CSI) Driver) to more securely mount workloads into these sidecars was developed. More information about the CSI Driver can be found [here](https://kubernetes-csi.github.io/docs/introduction.html). This document serves as a small introduction to CSI Drivers, and the considerations for use with NGINX Service Mesh. - -## Installation and Removal - -Using a CSI Driver comes with some considerations for the user on installation and removal. - -### Install - -The NGINX Service Mesh deployment experience is the exact same as in other environments. Simply add the `--environment openshift` flag when deploying the mesh, and the CSI Driver and security context constraints will be set up for you. - -```bash -nginx-meshctl deploy ... --environment openshift" -``` - -When injecting sidecars into your workloads, OpenShift's default security policies do not allow the necessary permissions. To enable the proper permissions for sidecar injection, you can attach your workloads to the `nginx-mesh-sidecar-permissions` SecurityContextConstraint (SCC) by running: - -```bash -oc adm policy add-scc-to-group nginx-mesh-sidecar-permissions system:serviceaccounts: -``` - -### Remove - -When removing NGINX Service Mesh, the CSI Driver should be running until all injected Pods are either re-rolled to remove the sidecar proxy, or terminated. This is because the CSI Driver must unmount and service any injected Pods when they are terminated. - -NGINX Service Mesh makes it easy by detecting whether any of the injected Pods are still running. If no injected Pods are found, the whole mesh is cleanly removed. If it does find any remaining injected Pods, some components are left after the rest of the mesh is removed in order to service them. A Job called `csi-driver-sentinel` is created in the NGINX Service Mesh namespace to watch for all injected Pods to be cleaned up. Once all of the injected Pods are either re-rolled or deleted, the `csi-driver-sentinel` Job will remove all of the remaining NGINX Service Mesh components. - -If any errors occur, removal of the remaining NGINX Service Mesh resources may require manual intervention. In this case, you can run the following commands to ensure all of the mesh components are removed: - -```bash -kubectl delete ns -kubectl delete clusterrole system:openshift:scc:nginx-mesh-spire-agent-permissions -kubectl delete scc nginx-mesh-spire-agent-permissions -kubectl delete clusterrolebinding csi-driver-sentinel.builtin.nsm.nginx -``` - -{{< call-out "note" >}} -To re-install NGINX Service Mesh, it is not necessary to re-roll or delete injected Pods before removing the CSI Driver. Simply run the removal commands listed in the snippet above and deploy NGINX Service Mesh as usual. The new CSI Driver that is deployed will be able to handle any injected Pods leftover from the previous deployment. -{{< /call-out >}} - -## How The CSI Driver Works - -A CSI Driver is powerful as it allows fine-grained volume control within your cluster. In NGINX Service Mesh, we needed a more secure way to publish the Spire Agent workload API socket to the sidecars in the mesh. For more information on the Spire architecture, and how the Spire Agent distributes certificates, see the [Spire]({{< ref "/mesh/about/architecture.md#spire" >}}) section of our architecture doc. Existing techniques involve mounting the socket to the host via a hostPath volume mount. While this works well functionally, it presents some security concerns as it allows workload access to the node. - -For OpenShift -- where container security is paramount -- using a hostPath volume mount on every injected Pod is not reasonable. A CSI Driver provides the ability to control exactly how information is shared between resources in your cluster, without every workload needing to use a hostPath. - -Under a CSI approach, there is only one Pod that needs to run a hostPath volume -- the Spire Agent. The agent requires a hostPath to register itself securely with `kubelet` running on that node. A hostPath is also used to share the directory hosting the workload API socket between the CSI Driver and the Spire Agent. - -Once registered with `kubelet`, every time an injected Pod spins up requesting a spire agent socket, the CSI Driver mounts the socket to the Pod's CSI volume. In addition to this, it registers itself with `kubelet` as being required for the unmounting step on injected Pod termination. Without a CSI Driver to service termination events, `kubelet` would have no way of knowing how to handle unmounting the CSI data. diff --git a/content/mesh/get-started/platform-setup/persistent-storage.md b/content/mesh/get-started/platform-setup/persistent-storage.md deleted file mode 100644 index f3518a099..000000000 --- a/content/mesh/get-started/platform-setup/persistent-storage.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Persistent Storage -description: Learn how to set up persistent storage for use with F5 NGINX Service - Mesh. -weight: 101 -toc: true -nd-docs: DOCS-686 -type: -- how-to ---- - -F5 NGINX Service Mesh generates data that needs to persist across restarts and failures to ensure uninterrupted operations. For example, if the SPIRE Server restarts, the new instance can pick up the the existing database of identities without having to rebuild everything. Depending on the environment, persistent storage may already be set up and ready for use by NGINX Service Mesh. - -The big three hosted Kubernetes environments (Elastic Kubernetes Service (EKS), Azure Kubernetes Service (AKS), and Google Kubernetes Engine (GKE)) all have built-in persistent storage that NGINX Service Mesh will automatically pick up and use. - -{{< call-out "important" >}} -**EKS Users:** in Kubernetes v1.23+ the in-tree to container storage interface (CSI) volume migration feature is enabled for EKS. -This means the Amazon EBS CSI driver must be installed in your cluster in order for persistent storage to work. -If the CSI driver is not installed prior to installing NGINX Service Mesh, the `PersistentVolumeClaim` required by SPIRE Server gets stuck in a pending state and the mesh will fail to install. - -See the [AWS documentation](https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html) for instructions on how to install the EBS CSI driver on your EKS cluster. -If you are unable to install the CSI driver you can disable persistent storage, although this is not recommended for production environments. -Use the `--persistent-storage off` flag if deploying the mesh with `nginx-meshctl` or set the `mtls.persistentStorage` value to `"off"` if using Helm. -{{< /call-out >}} - -## Determining Persistent Storage on your Cluster - -NGINX Service Mesh will automatically use the default Kubernetes `StorageClass` if it's configured. - -```bash -kubectl get storageclass -NAME PROVISIONER AGE -standard (default) kubernetes.io/gce-pd 153d -``` - -In the above output from GKE, NGINX Service Mesh will use the `standard` `StorageClass` to persist data. It's possible to have multiple Storage Classes configured, in that case NGINX Service Mesh will use the one configured as default. Specifying a specific `StorageClass` to use isn't supported at this time. - -## Deploying Without Persistent Storage - -If there is no default `StorageClass` set up, NGINX Service Mesh will still work, but will print the below warning during installation: - -```text -Warning: Deploying without persistent storage, not suitable for production environments. - For production environments ensure a default StorageClass is set. -``` - -Without persistent storage, if SPIRE Server restarts for any reason, the entire identity database will need to be rebuilt, which will significantly increase time to recovery. - -## Setting up Persistent Storage - -Kubernetes has an extensive ecosystem of plugins for persistent storage. These range from vSphere Volumes to Amazon Web Services (AWS) Elastic Block Store. For more details refer to the [Kubernetes Storage Classes documentation](https://kubernetes.io/docs/concepts/storage/storage-classes/). - -{{< call-out "important" >}} -Based on our testing, NFS Storage Classes introduce too much latency and aren't recommended for use with NGINX Service Mesh. -{{< /call-out >}} - - -## Troubleshooting - -By default NGINX Service Mesh detects if a default StorageClass is configured and uses it if present. If the StorageClass is misconfigured NGINX Service Mesh will fail to start. If you suspect persistent storage is misconfigured, try to deploy NGINX Service Mesh with `--persistent-storage off`. diff --git a/content/mesh/get-started/platform-setup/rke.md b/content/mesh/get-started/platform-setup/rke.md deleted file mode 100644 index 001552750..000000000 --- a/content/mesh/get-started/platform-setup/rke.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Rancher Kubernetes Engine -description: Learn how to set up Rancher Kubernetes Engine (RKE) for use with F5 NGINX - Service Mesh. -toc: true -nd-docs: DOCS-687 -type: -- how-to ---- - -Rancher Kubernetes Engine (RKE) is a CNCF-certified Kubernetes distribution that runs entirely within Docker containers. It works on bare-metal and virtualized servers. - -{{< call-out "important" >}} -Before deploying F5 NGINX Service Mesh, ensure that no other service meshes exist in your Kubernetes cluster. -{{< /call-out >}} - -{{< call-out "warning" >}} -Rancher has the option to deploy the community [NGINX Ingress Controller](https://github.com/kubernetes/ingress-nginx) when configuring an RKE cluster. While this ingress controller may work, NGINX Service Mesh does not guarantee support. It is recommended to use the [NGINX Plus Ingress Controller]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md" >}}) in conjunction with NGINX Service Mesh. -{{< /call-out >}} - -## Persistent storage - -RKE doesn't set up any persistent storage for you, but it's required to run NGINX Service Mesh in a production environment. See [Persistent Storage]( {{< ref "persistent-storage.md" >}} ) for more information. - -## Pod Security Policies - -When creating a new cluster with RKE, you can configure it to apply a [PodSecurityPolicy](https://kubernetes.io/docs/concepts/policy/pod-security-policy/). If you choose to do this, NGINX Service Mesh requires a few permissions in order to function properly. The following policy is based on the default `restricted-psp` policy used by RKE, with a few additions and changes to allow the NGINX Service Mesh control plane to work. - -{{< call-out "important" >}} -When running a cluster with a PodSecurityPolicy, all of the following resources need to be created/updated before deploying NGINX Service Mesh. -{{< /call-out >}} - -```yaml -apiVersion: policy/v1beta1 -kind: PodSecurityPolicy -metadata: - annotations: - serviceaccount.cluster.cattle.io/pod-security: restricted - serviceaccount.cluster.cattle.io/pod-security-version: "1696" - labels: - cattle.io/creator: norman - name: restricted-psp-nginx-mesh -spec: - allowPrivilegeEscalation: false - fsGroup: - ranges: - - max: 65535 - min: 1 - rule: MustRunAs - requiredDropCapabilities: - - ALL - runAsUser: - rule: RunAsAny - seLinux: - rule: RunAsAny - supplementalGroups: - ranges: - - max: 65535 - min: 1 - rule: MustRunAs - hostNetwork: true - hostPID: true - volumes: - - configMap - - emptyDir - - projected - - secret - - downwardAPI - - persistentVolumeClaim - - hostPath -``` - -In order for the NGINX Service Mesh sidecar init container to be able to configure `iptables` rules and BPF programs needed for UDP communication, it needs `NET_ADMIN`, `NET_RAW`, `SYS_RESOURCE`, and `SYS_ADMIN` capabilities. - -If a PodSecurityPolicy is applied to your workloads, then the following additions need to be made in order for the sidecar to work properly: - -```yaml -spec: - allowedCapabilities: - - NET_ADMIN - - NET_RAW - - SYS_RESOURCE - - SYS_ADMIN -``` - -{{< call-out "important" >}} -If you have separate PodSecurityPolicies for the control plane and your workloads, ensure that they are [bound to the proper Service Accounts](#bind-the-policy). -{{< /call-out >}} - -### Bind the Policy - -The `restricted-psp-nginx-mesh` policy needs to be bound to the NGINX Service Mesh control plane namespace, using the following resources: - -{{< call-out "note" >}} -The ClusterRoleBinding assumes the default namespace of `nginx-mesh`, but should be changed if you are using a different namespace for the control plane. -{{< /call-out >}} - -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: nginx-mesh-psp-role -rules: -- apiGroups: ['policy'] - resources: ['podsecuritypolicies'] - verbs: ['use'] - resourceNames: - - restricted-psp-nginx-mesh -``` - -```yaml -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: nginx-mesh-psp-binding -roleRef: - kind: ClusterRole - name: nginx-mesh-psp-role - apiGroup: rbac.authorization.k8s.io -subjects: -- kind: Group - apiGroup: rbac.authorization.k8s.io - name: system:serviceaccounts:nginx-mesh - ``` diff --git a/content/mesh/get-started/platform-setup/supported-platforms.md b/content/mesh/get-started/platform-setup/supported-platforms.md deleted file mode 100644 index d5966a1d2..000000000 --- a/content/mesh/get-started/platform-setup/supported-platforms.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Supported Platforms -description: Find out which platforms are supported for use with F5 NGINX Service - Mesh. -weight: 100 -toc: true -nd-docs: DOCS-688 ---- - -## Kubernetes - -The Kubernetes platforms listed below will work with F5 NGINX Service Mesh using the Kubernetes versions listed in the [Technical Specifications]({{< ref "/mesh/about/mesh-tech-specs.md#supported-versions" >}}). Additional Kubernetes platforms may work, although they have not been validated. - -- Azure Kubernetes Service (AKS) -- Elastic Kubernetes Service (EKS) -- [Additional setup required]( {{< ref "persistent-storage.md" >}} ) -- Google Kubernetes Engine (GKE) -- [Additional setup required]( {{< relref "./gke.md" >}} ) -- Rancher Kubernetes Engine (RKE) -- [Additional setup required]( {{< ref "rke.md" >}} ) -- Kubeadm -- [Additional setup required]( {{< relref "./kubeadm.md" >}} ) -- Kubespray -- [Additional setup required]( {{< ref "kubespray.md" >}} ) - -## OpenShift - -Any self-managed RedHat OpenShift environment running with the versions listed in the [Technical Specifications]({{< ref "/mesh/about/mesh-tech-specs.md#supported-versions" >}}) can be used with NGINX Service Mesh. Externally managed environments such as Azure Red Hat OpenShift and Red Hat OpenShift Service on AWS may work, although they have not been validated. - -Before deploying NGINX Service Mesh in OpenShift, see the [OpenShift]({{< ref "/mesh/get-started/platform-setup/openshift.md" >}}) page, which highlights runtime and deployment considerations when using NGINX Service Mesh in OpenShift. diff --git a/content/mesh/get-started/uninstall/_index.md b/content/mesh/get-started/uninstall/_index.md deleted file mode 100644 index 009d190fe..000000000 --- a/content/mesh/get-started/uninstall/_index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Uninstall NGINX Service Mesh -description: "Learn how to uninstall NGINX Service Mesh." -weight: 4 -url: /nginx-service-mesh/get-started/uninstall/ ---- diff --git a/content/mesh/get-started/uninstall/uninstall-with-helm.md b/content/mesh/get-started/uninstall/uninstall-with-helm.md deleted file mode 100644 index a92c64c0b..000000000 --- a/content/mesh/get-started/uninstall/uninstall-with-helm.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Uninstall with Helm -draft: false -toc: true -description: This topic explains how to uninstall F5 NGINX Service Mesh using Helm. -weight: 300 -nd-docs: DOCS-699 -type: -- how-to ---- - -## Uninstalling the Chart - -{{< call-out "important" >}} -OpenShift users: Before uninstalling, read through the [OpenShift considerations]({{< ref "/mesh/get-started/platform-setup/openshift.md#remove" >}}) guide to make sure you understand the implications. -{{< /call-out >}} - -To uninstall the `nsm` release in the `nginx-mesh` namespace, run: - -```bash -helm uninstall nsm --namespace nginx-mesh -``` - -This command removes most of the Kubernetes components associated with the F5 NGINX Service Mesh release. -Helm does **not** remove the following components: - -- CRDs -- `nginx-mesh` namespace -- Spire PersistentVolumeClaim in the `nginx-mesh` namespace - -Run this command to remove the CRDS: - -```bash -kubectl delete crd -l app.kubernetes.io/part-of==nginx-service-mesh -``` - -Deleting the namespace will also delete the PersistentVolumeClaim: - -```bash -kubectl delete namespace nginx-mesh -``` - -After uninstalling, re-roll your injected Deployments, DaemonSets, and StatefulSets to remove the sidecar proxy from Pods. - -```bash -kubectl rollout restart / -``` - -Example: - -```bash -kubectl rollout restart deployment/frontend -``` - -## Troubleshooting - -In some cases, the mesh may fail to uninstall for unexpected reasons due to environmental, network, or timeout errors. If the mesh fails to uninstall continually, manual intervention may be necessary. - -Run this command to see all resources associated with the mesh currently present in your cluster: - -```bash -kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -l app.kubernetes.io/part-of=nginx-service-mesh -A -``` - -### `nginx-mesh` Namespace Stuck "Terminating" - -Use the following script to list and patch all Spiffeid resources: - -```bash -for ns in $(kubectl get ns | awk '{print $1}' | tail -n +2) -do -if [ $(kubectl get spiffeids -n $ns 2>/dev/null | wc -l) -ne 0 ] -then - kubectl patch spiffeid $(kubectl get spiffeids -n $ns | awk '{print $1}' | tail -n +2) --type='merge' -p '{"metadata":{"finalizers":null}}' -n $ns -fi -done -``` - -After patching the Spiffeids the namespace should be removed. - -If you are unable to resolve the issues, please reach out to the appropriate [support channel]({{< ref "/mesh/support/contact-support.md" >}}). diff --git a/content/mesh/get-started/uninstall/uninstall.md b/content/mesh/get-started/uninstall/uninstall.md deleted file mode 100644 index ff718e24b..000000000 --- a/content/mesh/get-started/uninstall/uninstall.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Uninstall with nginx-meshctl -draft: false -toc: true -weight: 200 -nd-docs: DOCS-1481 -type: -- how-to ---- - -## Overview - -This topic explains how to uninstall F5 NGINX Service Mesh using nginx-meshctl. - -## Uninstall - -{{< call-out "important" >}} -OpenShift users: Before uninstalling, read through the [OpenShift considerations]({{< ref "/mesh/get-started/platform-setup/openshift.md#remove" >}}) guide to make sure you understand the implications. -{{< /call-out >}} - -Uninstalling does the following: - -1. Removes the control plane and its contents from Kubernetes. -2. Deletes all NGINX Service Mesh traffic policies. - -The `nginx-meshctl` command-line utility prints a list of resources that contain the sidecar proxies when the uninstall completes. You must re-roll the Deployments in Kubernetes to remove the sidecars. Until you re-roll the resources, the sidecar proxies still exist, but they don't apply any rules to the traffic. - -### Uninstall the Control Plane - -To uninstall the Service Mesh control plane using the `nginx-meshctl` command-line utility, run the command shown below. - -```bash -nginx-meshctl remove -``` - -When prompted for confirmation, specify `y` or `n`. -If you want to skip the confirmation prompt, add the `-y` flag as shown in the example below. - -```bash -nginx-meshctl remove -y -``` - -### Remove the Sidecar Proxy from Deployments - -If your resources support Rolling Updates (Deployments, DaemonSets, and StatefulSets), run the following `kubectl` command for each resource to complete the uninstall. - -```bash -kubectl rollout restart / -``` - -For example: - -```bash -kubectl rollout restart deployment/frontend -``` - -{{< call-out "note" >}} -If you want to redeploy NGINX Service Mesh after removing it, you need to re-roll the resources after the new NGINX Service Mesh is installed. Sidecars from an earlier NGINX Service Mesh installation won't work with a new installation. -{{< /call-out >}} - -## Troubleshooting - -In some cases, the `remove` command may fail for unexpected reasons due to environmental, network, or timeout errors. If the `remove` command fails continually, manual intervention may be necessary. - -Run this command to see all resources associated with the mesh currently present in your cluster: - -```bash -kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl get --show-kind --ignore-not-found -l app.kubernetes.io/part-of=nginx-service-mesh -A -``` - -### `nginx-mesh` Namespace Stuck "Terminating" - -Use the following script to list and patch all Spiffeid resources: - -```bash -for ns in $(kubectl get ns | awk '{print $1}' | tail -n +2) -do -if [ $(kubectl get spiffeids -n $ns 2>/dev/null | wc -l) -ne 0 ] -then - kubectl patch spiffeid $(kubectl get spiffeids -n $ns | awk '{print $1}' | tail -n +2) --type='merge' -p '{"metadata":{"finalizers":null}}' -n $ns -fi -done -``` - -After patching the Spiffeids the namespace should be removed. - -If you are unable to resolve the issues, please reach out to the appropriate [support channel]({{< ref "/mesh/support/contact-support.md" >}}). diff --git a/content/mesh/get-started/upgrade/_index.md b/content/mesh/get-started/upgrade/_index.md deleted file mode 100644 index c7951c5e5..000000000 --- a/content/mesh/get-started/upgrade/_index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Upgrade NGINX Service Mesh -description: "Learn how to upgrade NGINX Service Mesh." -weight: 3 -url: /nginx-service-mesh/get-started/upgrade/ ---- - diff --git a/content/mesh/get-started/upgrade/upgrade-with-helm.md b/content/mesh/get-started/upgrade/upgrade-with-helm.md deleted file mode 100644 index e5b907d5b..000000000 --- a/content/mesh/get-started/upgrade/upgrade-with-helm.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: Upgrade with Helm -draft: false -toc: true -description: This guide explains how to upgrade F5 NGINX Service Mesh using Helm. -weight: 300 -nd-docs: DOCS-700 -type: -- how-to ---- - -You can upgrade to the latest mesh version from the version immediately before it (for example, from v1.6.0 to v1.7.0). F5 NGINX Service Mesh doesn't support skipping versions. - -{{< call-out "important" >}} -Check out the [Version-specific Notes]({{< ref "#version-specific-notes" >}}) section prior to upgrading to see if there are any extra details required for the version you are using. -{{< /call-out >}} - -## Upgrade via Helm - -### 1. Upgrade the CRDs - -Helm does not upgrade the CRDs during a release upgrade. Before you upgrade a release you must download the chart from GitHub and run the following command to upgrade the CRDs: - -```bash -git clone https://github.com/nginxinc/nginx-service-mesh -cd nginx-service-mesh/helm-chart -git checkout v2.0.0 -kubectl apply -f crds/ -``` - -The following warning is expected and can be ignored: `Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply.` - -### 2. Upgrade the Release - -To upgrade the release `nsm` in the `nginx-mesh` namespace: - -#### Upgrade via Repository - -```bash -helm repo update -helm upgrade nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --wait -``` - -#### Upgrade via Source - -```bash -helm upgrade nsm . --namespace nginx-mesh --wait -``` - -Once the upgrade is complete, if your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be deleted and re-created. - -## Manual Upgrade - -This upgrade method is the most disruptive, as it involves fully removing the existing mesh and deploying the newer version. - -If breaking changes are introduced between versions, or you wish to change the [deployment configuration]( {{< ref "/mesh/get-started/install/install-with-helm.md#configuration-options" >}} ), then a manual upgrade strategy is necessary. - -Some deployment configuration fields can be updated after the mesh has already been deployed, avoiding the need for manual upgrades. Those fields are discussed in the [API reference]( {{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}} ) guide. - -### 1. Save Custom Resources -{{< call-out "warning" >}} -When you manually upgrade NGINX Service Mesh, all of your Custom Resources will be deleted. This includes TrafficSplits, TrafficTargets, RateLimits, and so on. -{{< /call-out >}} - -Before you proceed with the upgrade, run the commands shown below to back up your Custom Resources. - -```bash -kubectl get trafficsplits.split.smi-spec.io -A -o yaml > trafficsplits.yaml -kubectl get traffictargets.access.smi-spec.io -A -o yaml > traffictargets.yaml -kubectl get httproutegroups.specs.smi-spec.io -A -o yaml > httproutegroups.yaml -kubectl get tcproutes.specs.smi-spec.io -A -o yaml > tcproutes.yaml -kubectl get ratelimits.specs.smi.nginx.com -A -o yaml > ratelimits.yaml -kubectl get circuitbreakers.specs.smi.nginx.com -A -o yaml > circuitbreakers.yaml -``` - -### 2. Remove NGINX Service Mesh - -1. Uninstall the release `nsm` in the `nginx-mesh` namespace: - - ```bash - helm uninstall nsm --namespace nginx-mesh - ``` - - Change the release or namespace names as necessary for your deployment. - -1. Delete the CRDs: - - ```bash - kubectl delete crd -l app.kubernetes.io/part-of==nginx-service-mesh - ``` - -1. Delete the mesh namespace: - - ```bash - kubectl delete namespace nginx-mesh - ``` - -### 3. Install NGINX Service Mesh - -#### Install via Repository - -```bash -helm repo update -helm install nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --create-namespace --wait -``` - -#### Install via Source - -```bash -helm install nsm . --namespace nginx-mesh --create-namespace --wait -``` - -### 4. Redeploy policies and sidecars - -Use the backups you made earlier to recreate your custom resources: - -```bash -kubectl create -f trafficsplits.yaml -f traffictargets.yaml -f httproutegroups.yaml -f tcproutes.yaml -f ratelimits.yaml -f circuitbreakers.yaml -``` - -If your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be re-created. - - -## Version-specific Notes - -### Telemetry Configurations prior to v1.7.0 - -When upgrading from NGINX Service Mesh prior to v1.7, any tracing settings that do not correspond to an OpenTelemetry configuration will be removed. To deploy OpenTelemetry services and configure your mesh for OpenTelemetry tracing refer to the [Monitoring and Tracing]({{< ref "/mesh/guides/monitoring-and-tracing.md" >}}) guide. - -### Upgrading from v1.6.0 to 1.7.0 in OpenShift - -Due to changes in the CSI Driver in version 1.7.0 of NGINX Service Mesh for OpenShift, a manual upgrade is necessary to ensure volumes are properly unmounted and remounted. - -The CSI volumes must be unmounted from existing applications before redeploying the mesh. - -Follow these steps ***after*** [#2 Removing NGINX Service Mesh]({{< ref "#2-remove-nginx-service-mesh" >}}) in the manual upgrade steps above. - -If your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be deleted. The list of resources that need to be re-rolled are printed out when removing the mesh. - -Once all applications have their sidecars removed, and the `csi-driver-sentinel` Job in the `nginx-mesh` namespace has been automatically deleted, you can proceed to [#3 Install NGINX Service Mesh]({{< ref "#3-install-nginx-service-mesh" >}}) in the manual upgrade steps above. - -For more context on OpenShift and the CSI Driver, see the [OpenShift Considerations]({{< ref "/mesh/get-started/platform-setup/openshift.md" >}}). diff --git a/content/mesh/get-started/upgrade/upgrade.md b/content/mesh/get-started/upgrade/upgrade.md deleted file mode 100644 index 5b1bc52b5..000000000 --- a/content/mesh/get-started/upgrade/upgrade.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: Upgrade with nginx-meshctl -draft: false -toc: true -description: This guide explains how to upgrade F5 NGINX Service Mesh using nginx-meshctl. -weight: 200 -nd-docs: DOCS-1482 -type: -- how-to ---- - -You can upgrade to the latest mesh version from the version immediately before it (for example, from v1.6.0 to v1.7.0). F5 NGINX Service Mesh doesn't support skipping versions. - -{{< call-out "important" >}} -Check out the [Version-specific Notes]({{< ref "#version-specific-notes" >}}) section prior to upgrading to see if there are any extra details required for the version you are using. -{{< /call-out >}} - -## In-Place Upgrade - - -If you wish to change the [deployment configuration]( {{< ref "nginx-meshctl.md#deploy" >}} ) (for example, setting a new flag or changing the value of an existing flag), then you will need to follow the [Manual Upgrade](#manual-upgrade) steps. - -You can upgrade to the latest `nginx-meshctl` release from the version immediately before it (for example, from v1.4.0 to v1.5.0). After you have [installed]({{< ref "/mesh/get-started/install/install.md#install-the-cli" >}}) the latest version of `nginx-meshctl`, you can run: - -```bash -nginx-meshctl upgrade -``` - -This will upgrade the NGINX Service Mesh control plane to the latest version. All user configuration (traffic policies, mesh configuration, deploy configuration) is preserved through the upgrade. New applications will not be injected during the upgrade and existing applications will not receive configuration updates. Existing applications may not function properly until updated. It is recommended that upgrades are only performed during a maintenance window due to the brief downtime. - -By default, the mesh will pull images from the registry that it was originally deployed with. If you would like to pull from a different registry, you can use the`--registry-server` flag. - -Additionally, if you would like to upgrade to a custom image tag you can use the `--image-tag` flag. - -Once the upgrade is complete, if your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be deleted and re-created. - -## Manual Upgrade - -This upgrade method is the most disruptive, as it involves fully removing the existing mesh and deploying the newer version. - -If breaking changes are introduced between versions, or you wish to change the [deployment configuration]( {{< ref "nginx-meshctl.md#deploy" >}} ), then a manual upgrade strategy is necessary. - -Some deployment configuration fields can be updated after the mesh has already been deployed, avoiding the need for manual upgrades. Those fields are discussed in the [API reference]( {{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}} ) guide. - -Before downloading the newer version of `nginx-meshctl`, you will need to remove NGINX Service Mesh using your existing version of `nginx-meshctl`. See the following steps. - -### 1. Save Custom Resources -{{< call-out "warning" >}} -When you manually upgrade NGINX Service Mesh, all of your Custom Resources will be deleted. This includes TrafficSplits, TrafficTargets, RateLimits, and so on. -{{< /call-out >}} - -Before you proceed with the upgrade, run the commands shown below to back up your Custom Resources. - -```bash -kubectl get trafficsplits.split.smi-spec.io -A -o yaml > trafficsplits.yaml -kubectl get traffictargets.access.smi-spec.io -A -o yaml > traffictargets.yaml -kubectl get httproutegroups.specs.smi-spec.io -A -o yaml > httproutegroups.yaml -kubectl get tcproutes.specs.smi-spec.io -A -o yaml > tcproutes.yaml -kubectl get ratelimits.specs.smi.nginx.com -A -o yaml > ratelimits.yaml -kubectl get circuitbreakers.specs.smi.nginx.com -A -o yaml > circuitbreakers.yaml -``` - -### 2. Remove NGINX Service Mesh -Remove NGINX Service Mesh using your existing version of `nginx-meshctl`. - -```bash -nginx-meshctl remove -``` - -At this point, you can discard your old version of `nginx-meshctl`. - -#### helm users - -Uninstall/delete the release `nsm` in the `nginx-mesh` namespace: - -```bash -helm uninstall nsm --namespace nginx-mesh -``` - -Change the release or namespace names as necessary for your deployment. - -### 3. Redeploy NGINX Service Mesh -[Download and install]({{< ref "/mesh/get-started/install/install.md#install-the-cli" >}}) the newer version of `nginx-meshctl`. - -[Redeploy]({{< ref "/mesh/get-started/install/install.md#install-the-nginx-service-mesh-control-plane" >}}) NGINX Service Mesh. - -### 4. Redeploy policies and sidecars - -Use the backups you made earlier to recreate your custom resources: - -```bash -kubectl create -f trafficsplits.yaml -f traffictargets.yaml -f httproutegroups.yaml -f tcproutes.yaml -f ratelimits.yaml -f circuitbreakers.yaml -``` - -If your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be re-created. - -## Version-specific Notes - -### Telemetry Configurations prior to v1.7.0 - -When upgrading from NGINX Service Mesh prior to v1.7, any tracing settings that do not correspond to an OpenTelemetry configuration will be removed. To deploy OpenTelemetry services and configure your mesh for OpenTelemetry tracing refer to the [Monitoring and Tracing]({{< ref "/mesh/guides/monitoring-and-tracing.md" >}}) guide. - -### Upgrading from v1.6.0 to 1.7.0 in OpenShift - -Due to changes in the CSI Driver in version 1.7.0 of NGINX Service Mesh for OpenShift, a manual upgrade is necessary to ensure volumes are properly unmounted and remounted. - -The CSI volumes must be unmounted from existing applications before redeploying the mesh. - -Follow these steps ***after*** [#2 Removing NGINX Service Mesh]({{< ref "#2-remove-nginx-service-mesh" >}}) in the manual upgrade steps above. - -If your applications support rolling updates, re-roll using the following command: - -```bash -kubectl rollout restart / -``` - -Otherwise, the application Pods need to be deleted. The list of resources that need to be re-rolled are printed out when removing the mesh. - -Once all applications have their sidecars removed, and the `csi-driver-sentinel` Job in the `nginx-mesh` namespace has been automatically deleted, you can proceed to [#3 Redeploy NGINX Service Mesh]({{< ref "#3-redeploy-nginx-service-mesh" >}}) in the manual upgrade steps above. - -For more context on OpenShift and the CSI Driver, see the [OpenShift Considerations]({{< ref "/mesh/get-started/platform-setup/openshift.md" >}}). diff --git a/content/mesh/guides/_index.md b/content/mesh/guides/_index.md deleted file mode 100644 index 833f0462f..000000000 --- a/content/mesh/guides/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Guides -weight: 150 -description: Learn how to use NGINX Service Mesh. -draft: false -url: /nginx-service-mesh/guides/ ---- - diff --git a/content/mesh/guides/inject-sidecar-proxy.md b/content/mesh/guides/inject-sidecar-proxy.md deleted file mode 100644 index 187bdb938..000000000 --- a/content/mesh/guides/inject-sidecar-proxy.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Sidecar Proxy Injection -toc: true -description: Learn about the configuration options for F5 NGINX Service Mesh sidecar - proxy injection. -weight: 10 -nd-docs: DOCS-692 -type: -- how-to ---- - -## Overview - -F5 NGINX Service Mesh works by injecting a sidecar proxy into Kubernetes resources. - -A couple important things to note about injected Pods: - -- The sidecar proxy will not be injected into Pods that define multiple container ports with the same port number or for container ports with the SCTP protocol. - UDP and TCP are an exception to this, and may be specified on the same port. -- When you inject the sidecar proxy into a Kubernetes resource, the injected config uses the global mTLS setting. - You can define the global setting when you deploy NGINX Service Mesh, or use the default setting. - Refer to [Secure Mesh Traffic using mTLS]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) for more information. - - -The mesh supports the following Kubernetes resources and API versions for injection: - -{{% table %}} -| Resource Type | API Version | -|-----------------------|-------------| -| Deployment | apps/v1 | -| DaemonSet | apps/v1 | -| StatefulSet | apps/v1 | -| ReplicaSet | apps/v1 | -| ReplicationController | v1 | -| Pod | v1 | -| Job | batch/v1 | -{{% /table %}} - -You can choose to inject the sidecar proxy into the YAML or JSON definitions for your Kubernetes resources in the following ways: - -- [Automatic Injection](#automatic-proxy-injection) -- [Manual Injection](#manual-proxy-injection) - -## Automatic Proxy Injection - -To enable automatic sidecar injection for all Pods in a namespace, add the `injector.nsm.nginx.com/auto-inject=enabled` label to the namespace. - -```bash -kubectl label namespaces injector.nsm.nginx.com/auto-inject=enabled -``` - -For more granular control, you can enable or disable automatic sidecar injection on a per-resource basis. -To do so, add the following label to the resource's **PodTemplateSpec**: `injector.nsm.nginx.com/auto-inject: "enabled|disabled"`. -Pod labels take precedence over namespace labels. - -If you add the auto-inject label to existing resources, you will need to restart the affected Pods in order for the sidecar to be injected. -By the same token if you remove the label or set the Pod label to `disabled`, you will need to restart them to remove the sidecar. - -Use `kubectl rollout restart` to restart your Pods: - -```bash -kubectl rollout restart / -``` - -For example: - -```bash -kubectl rollout restart deployment/frontend -``` - -{{< call-out "note" >}} -See [NGINX Service Mesh Labels and Annotations]( {{< ref "/mesh/get-started/install/configuration.md#supported-labels-and-annotations" >}}) for more information on available labels and annotations. - -Refer to the Kubernetes [`kubectl` Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/#updating-resources) documentation for more information about rolling resources. -{{< /call-out>}} - -## Manual Proxy Injection - -To inject the sidecar proxy into a resource manually, use the `nginx-meshctl inject` command. Provide the path to the resource definition file and your desired output filename. - -```bash -nginx-meshctl inject < > -``` - -For example, the following command will write the updated config for "resource.yaml" to a new file, "resource-injected.yaml": - -```bash -nginx-meshctl inject < resource.yaml > resource-injected.yaml -``` - -## Ignore Specific Ports - -You can set the proxy to ignore ports for either incoming or outgoing traffic. The NGINX Service Mesh applies the configurations at injection time. - -- For automatic injection, add the following annotations to the **PodTemplateSpec** in your resource definition: - - ```yaml - config.nsm.nginx.com/ignore-incoming-ports: "port1, port2, ..., portN" - config.nsm.nginx.com/ignore-outgoing-ports: "port1, port2, ..., portN" - ``` - -- For manual injection, you can use the annotations above or specify the ports when running the `nginx-meshctl inject` command. - - ```bash - nginx-meshctl inject --ignore-incoming-ports "port1,port2,...,portN", --ignore-outgoing-ports "port1,port2,...,portN" < resource.yaml > resource-injected.yaml - ``` - -{{< call-out "note" >}} -Refer to [NGINX Service Mesh Annotations]( {{< ref "/mesh/get-started/install/configuration.md#pod-annotations" >}}) for more information around annotations. -{{< /call-out >}} - -## Default Ignored Ports - -By default, the following ports are ignored by the proxy: - -- 53 (DNS) - -## HTTPGet Health Probe Rewrite - -If mTLS mode is set to `strict`, then readiness, liveness, and startup probes using HTTP GET do not work. This is -due to `kubelet` not having the correct client certificates. To remedy this, application HTTP/S health probes are -rewritten at injection time. The new probes point to an endpoint on the sidecar proxy, which -redirects the health check to the original destination on the application. This allows the health check to bypass -the SSL verification, while still sending the health check to the intended destination. - -## UDP MTU Sizing - -NGINX Service Mesh automatically detects and adjusts the `eth0` interface to support the 32 bytes of space required for PROXY Protocol V2. See the [UDP and eBPF architecture]( {{< ref "/mesh/about/architecture.md#udp-and-ebpf" >}} ) section for more information. diff --git a/content/mesh/guides/monitoring-and-tracing.md b/content/mesh/guides/monitoring-and-tracing.md deleted file mode 100644 index cbf6e5bb3..000000000 --- a/content/mesh/guides/monitoring-and-tracing.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Monitoring and Tracing -toc: true -description: Learn about the monitoring and tracing features available in F5 NGINX - Service Mesh. -weight: 20 -nd-docs: DOCS-693 -type: -- how-to ---- - -## Overview - -F5 NGINX Service Mesh can integrate with your Prometheus, Grafana, and tracing backends for exporting metrics and tracing data. - -If you do not have Prometheus, Grafana, or tracing backends deployed, you can follow the [Observability Tutorial]( {{< ref "/mesh/tutorials/observability.md" >}}) to deploy a basic demo setup. - -{{< call-out "important" >}} -In order to prevent automatic sidecar injection into your Prometheus, Grafana, and tracing deployments, they should be deployed in a namespace where auto-injection is disabled. Alternatively, you can disable auto-injection for these deployments specifically by adding the `injector.nsm.nginx.com/auto-inject: disabled` label to the *PodTemplateSpec* of the deployments. -{{< /call-out >}} - -### Prometheus - -{{< call-out "warning" >}} -We do not currently support Prometheus deployments running with TLS encryption. -{{< /call-out >}} - -To use NGINX Service Mesh with your Prometheus deployment: - -1. Connect your existing Prometheus Deployment to NGINX Service Mesh: - - The expected address format is `.:` - - - *At deployment*: - - Run the `nginx-meshctl deploy` command with the `--prometheus-address` flag. - - For example: - - ```bash - nginx-meshctl deploy ... --prometheus-address "my-prometheus.example-namespace:9090" - ``` - - - *At runtime*: - - You can use the [NGINX Service Mesh API]({{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}}) - to update the Prometheus address that the control plane uses to get metrics. - - The example below demonstrates how to update the `meshconfig` resource: - - ```yaml - spec: - prometheusAddress: my-prometheus.example-namespace:9090 - ``` - -1. Add the `nginx-mesh-sidecars` scrape config to your Prometheus configuration. - If you are deploying NGINX Plus Ingress Controller with the NGINX Service Mesh, add the `nginx-plus-ingress` scrape config as well. - Consult the [Metrics]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#nginx-plus-ingress-controller-metrics" >}} ) section of the NGINX Ingress Controller Deployment tutorial for more information about the metrics collected. - - - {{< icon "download" >}} {{< link "/examples/nginx-mesh-sidecars-scrape-config.yaml" "`nginx-mesh-sidecars-scrape-config.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/nginx-plus-ingress-scrape-config.yaml" "`nginx-plus-ingress-scrape-config.yaml`" >}} - -{{< call-out "note" >}} -For more information on how to view and understand the metrics that we track, see our [Prometheus Metrics]({{< ref "prometheus-metrics.md" >}}) guide. -{{< /call-out>}} - -### Grafana -The custom NGINX Service Mesh Grafana dashboard `NGINX Mesh Top` can be imported into your Grafana instance. -For instructions and a list of features, see the [Grafana example](https://github.com/nginxinc/nginx-service-mesh/tree/main/examples/grafana) in the `nginx-service-mesh` GitHub repo. - - -### Tracing with OpenTelemetry - -NGINX Service Mesh can export tracing data using `OpenTelemetry`. Tracing data can be exported to an OpenTelemetry Protocol (OTLP) gRPC Exporter, such as the [OpenTelemetry (OTEL) Collector](https://opentelemetry.io/docs/collector/), which can then export data to one or more upstream collectors like [Jaeger](https://www.jaegertracing.io/), [DataDog](https://docs.datadoghq.com/tracing/), LightStep, or many others. Before installing the mesh, deploy an OTEL Collector that is [configured to export data](https://opentelemetry.io/docs/collector/configuration/#exporters) to an upstream collector that you have already deployed or have access to. - -Tracing relies on the trace headers passed through each microservice in an application in order to build a full trace of a request. If you don't configure your app to pass trace headers, you'll get disjointed traces that are more difficult to understand. See the [OpenTelemetry Instrumentation](https://opentelemetry.io/docs/instrumentation/) guide for information on how to instrument your application to pass trace headers. - -- *At deployment*: - - Use the `--telemetry-exporters` flag to point the mesh to your OTLP exporter: - - ```bash - nginx-meshctl deploy ... --telemetry-exporters "type=otlp,host=otel-collector.example-namespace.svc,port=4317" - ``` - - You can set the desired sampler ratio to use for tracing by adding the `--telemetry-sampler-ratio` flag to your deploy command. - The sampler ratio must be a float between `0` and `1`. The sampler ratio sets the probability of sampling a span; this means that a sampler ratio of `0.1` sets a 10% probability the span is sampled. For example: - - ```bash - nginx-meshctl deploy ... --telemetry-sampler-ratio 0.1 - ``` - -- *At runtime*: - - You can use the [NGINX Service Mesh API]({{< ref "api-usage.md#modifying-the-global-mesh-configuration" >}}) to update the telemetry configuration. - - The example below demonstrates how to update the `meshconfig` resource: - - ```yaml - telemetry: - exporters: - otlp: - host: otel-collector.example-namespace.svc - port: 4317 - samplerRatio: 0.1 - ``` - -If configured correctly, tracing data that is generated or propagated by the NGINX Service Mesh sidecar will be exported to the OTEL Collector, and then exported to the upstream collector(s), as shown in the following example diagram: - -{{< img src="img/opentelemetry.png" alt="OpenTelemetry Data Flow" >}} -*Tracing data flow using the OpenTelemetry Collector* diff --git a/content/mesh/guides/private-registry.md b/content/mesh/guides/private-registry.md deleted file mode 100644 index fed94e5bc..000000000 --- a/content/mesh/guides/private-registry.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -title: Private Registry -description: How to set up access to a private registry -weight: 70 -toc: true -nd-docs: DOCS-694 -type: -- how-to ---- - -## Overview - -F5 NGINX Service Mesh supports using a private registry to store its components. In order to deploy NGINX Service Mesh from a private registry, you need to configure the NGINX Service Mesh CLI with credentials that can access the registry. - -## CLI Flags - -You can use the following NGINX Service Mesh CLI flags to configure private registry access. - -{{% table %}} -| Flag | Description | -|-----------------------|-------------| -| `--registry-server` | The host name and port (if needed) of the private registry, for example, "gcr.io". Can also contain the path, though only the domain is used for authentication. Pull requests for images to this registry will authenticate using the provided credentials. | -| `--registry-username` | The username to access the private registry. Must be used with `--registry-password`. Cannot be used with `--registry-key`. | -| `--registry-password` | The password to access the private registry. Must be used with `--registry-username`. Cannot be used with `--registry-key`. | -| `--registry-key` | The path on disk to a JSON key file that allows access to a GKE registry. Cannot be used with `--registry-username` or `--registry-password`. | -{{% /table %}} - -There are two methods of accessing a private registry: - -- Registry username and password can be specified with `--registry-username` and `--registry-password`. -- For a GKE registry, you can specify the path to the JSON key using `--registry-key`. The path can be relative to the working directory or absolute. - -{{< call-out "warning" >}} -Using the `--registry-password` flag can expose your plain text password on the console and in the console history. -{{< /call-out >}} - -## Images - -See this [list]( {{< ref "/mesh/about/mesh-tech-specs.md#images" >}} ) for the images you need to copy to your private registry. The image names and tags must remain the same. - - `gcr.io/spiffe-io/spire-agent:1.5.6` would become `your-registry/spire-agent:1.5.6` - - `nats:2.9-alpine` would become `your-registry/nats:2.9-alpine` - -When running `nginx-meshctl deploy`, use the `--disable-public-images` flag to instruct the mesh to use your `--registry-server` for all images. - -```bash -nginx-meshctl deploy --registry-server your-registry --disable-public-images ... -``` - -## Examples - -Deploying from a private registry using a username and password: - -```bash -nginx-meshctl deploy ... --registry-server --registry-username --registry-password -``` - -Deploy from a GKE registry using a JSON Key: - -```bash -nginx-meshctl deploy ... --registry-server --registry-key -``` - -## How it Works - -When deploying with the private registry flags, `nginx-meshctl` will create a Kubernetes Secret (example below) that encapsulates the secret data: - -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: nginx-mesh-registry-key - namespace: nginx-mesh - labels: - usage: nginx-mesh-registry-key -data: - .dockerconfigjson: -type: kubernetes.io/dockerconfigjson -``` - -The is a base64 encoded JSON that encapsulates the secret data with a header. When using the `--registry-username` and `--regsitry-password` option, that section looks like: - -```json -{ - "auths": { - "": { - "username": "", - "password": "", - "auth": "" - } - } -} -``` - -NGINX Service Mesh creates the Kubernetes Secret in its namespace. Kubernetes Secrets aren't cluster-wide, so when injecting a pod with a sidecar, NGINX Service Mesh duplicates the Kubernetes Secret into the namespace of that pod. - -NGINX Service Mesh will additionally inject the below yaml snippet into Pods injected with a sidecar. This allows the Pod to use the Kubernetes Secret to pull the NGINX Service Mesh sidecar container: - -```yaml -imagePullSecrets: -- name: nginx-mesh-registry-key -``` - -When you remove NGINX Service Mesh, all of the Kubernetes Secrets that it created are deleted. It uses a [label selector](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) to get a list of all the Kubernetes Secrets with the label `usage=nginx-mesh-registry-key`. You can simulate this operation using kubectl: - -```bash -kubectl get secrets -l usage=nginx-mesh-registry-key -A -``` diff --git a/content/mesh/guides/production-tuning.md b/content/mesh/guides/production-tuning.md deleted file mode 100644 index 4dc90023d..000000000 --- a/content/mesh/guides/production-tuning.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Production Tuning -weight: 80 -toc: true -nd-docs: DOCS-695 -type: -- how-to ---- - -#### Overview - -While F5 NGINX Service Mesh provides a number of capabilities for finely controlling and authorizing traffic, it is up to the user to configure these options to best suit the needs of their application. This document will go over the various deployment options and tools compatible with NGINX Service Mesh that you can use to secure and orchestrate traffic throughout your application. For the best production experience possible, consider the options below. - -#### mTLS Strict - -By default, NGINX Service Mesh ships with mTLS `permissive` mode. Because of the security implications of allowing plaintext, unauthenticated traffic to and from your application, we strongly recommend switching to mTLS `strict` mode. This will ensure that all traffic stays within the confines of the mesh. For more information on this setting, see the [Secure Mesh Traffic using mTLS]( {{< ref "/mesh/guides/secure-traffic-mtls.md#overview" >}} ) guide. - -You can also secure ingress traffic to your cluster using the NGINX Plus Ingress Controller for Kubernetes. View our tutorial to [Deploy with NGINX Plus Ingress Controller]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md" >}} ) to secure traffic at the ingress point of your cluster. If you'd like to support non-meshed services, see our [Configure a Secure Egress Route with NGINX Plus Ingress Controller]( {{< ref "/mesh/tutorials/kic/egress-walkthrough.md" >}}) tutorial for sending traffic to non-meshed or external services. Note that we do not secure traffic past the egress point, so when routing traffic to an external service, you will be required to secure that traffic yourself. - -#### Access Control - -Alongside the authentication piece provided by mTLS sits authorization. NGINX Service Mesh uses access control policies from the SMI Spec to support authorization. Together, mTLS and Access Control provide a holistic approach to application security. We highly recommend that you deploy the mesh using `--access-control-mode=deny` to automatically deny all incoming connections and enact a zero trust network from which to build upon. Then, piece by piece, explicitly allow connections solely from clients that you expect to be making requests to your destinations. For more information on how you can properly set up access control for your application, see the [Access Control]( {{< ref "/mesh/guides/smi-traffic-policies.md#access-control" >}}) section of our Traffic Policies guide. - -#### SPIRE Settings - -NGINX Service Mesh uses SPIRE as the backbone for mTLS. It is responsible for minting certificates used in the mTLS handshake between application pods, as well as specifying the certificate authority used in the mesh webhooks and Traffic Metrics extension api-server. SPIRE ships with a variety of defaults which make development and testing easy, but they should be evaluated when considering production. - -##### Using an Upstream Authority - -If left unspecified, NGINX Service Mesh will generate a self signed root certificate with which to sign certificates. While this works for testing and development, it is recommended that you use a proper public key infrastructure (PKI). See [Deploy Using an Upstream Root CA]( {{< ref "/mesh/guides/secure-traffic-mtls.md#deploy-using-an-upstream-root-ca" >}} ) for more information on the options available to you for deploying using your own PKI. It is recommended that you specify an intermediate certificate to NGINX Service Mesh rather than a root certificate to ensure that, even if the intermediate gets compromised, the entirety of your PKI remains secure and intact. - -##### In-memory SPIRE key manager - -The `memory` key manager plugin should be used when deploying NGINX Service Mesh in a production environment. By using the `memory` key manager plugin, SPIRE signing keys are kept in memory - less vulnerable to the prying eyes of bad actors should they gain access to the SPIRE server. For more information on the various key managers that NGINX Service Mesh provides, as well as the differences between the two, see the [SPIRE Key Manager Plugin]( {{< ref "/mesh/guides/secure-traffic-mtls.md#choose-a-spire-key-manager-plugin" >}}) section of our mTLS guide. - -We **highly** recommend pairing the `memory` key manager plugin with an upstream authority. Without one, if the SPIRE server fails, agents must be restarted manually and all workloads must receive newly signed certificates. This can have a significant impact on the cluster's resources. - -##### Persistent Storage - -Persistent storage allows for optimized handling of Spire server restarts in case of a failure as data such as registration entities and selectors do not need to be rebuilt. This saves on resource utilization and removes traffic disruptions for workloads. For most environments, we deploy persistent storage by default and recommend using it. See our [Persistent Storage]( {{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}} ) setup page for more information on configuring persistent storage in your environment. - -##### Certificate TTL - -It is important to specify a relatively low TTL to minimize damage should a certificate be compromised. For this reason, we recommend that you keep the default mTLS TTL values provided by NGINX Service Mesh. - -#### Traffic Policies - -While not purely necessary for production scenarios, NGINX Service Mesh provides rate limiting and circuit breaking utilities for better stability during times of peak traffic, as well as a better customer experience in the event of service failure. - -##### Rate Limit - -Using the rate limiting SMI extension, you are able to place a limit on the number of requests allowed for a particular service. In your preliminary testing, you may have gotten an idea of the upper threshold of requests your service is able to handle. Short of setting up autoscaling for your services, which can add cost and complexity to your system, you can simply add a rate limiting policy which will deny traffic as soon as it hits a specified threshold. This can be useful for services which have high resource requirements, or are running on relatively lightweight equipment. A rate limit, set up properly, can be the difference between intermittent error codes and complete loss of function which, in extreme circumstances, can lead to cascading failures across your application. See our [Rate Limiting]( {{< ref "/mesh/guides/smi-traffic-policies.md#rate-limiting" >}}) section of the Traffic Policies guide for information on setting up a rate limit. - -##### Circuit Breaker - -If the upper limit of the backend service is unknown, or you want an added layer of security for your services, you can specify a circuit breaker. When the level of errors begin to hint at a failure in the backend service itself, a circuit breaker can trip and allow that service to recover. Paired with a fallback service, your users can experience near zero downtime. For more information on setting up a circuit breaker, see our [Circuit Breaker]( {{< ref "/mesh/guides/smi-traffic-policies.md#circuit-breaking" >}}) section of the Traffic Policies guide. diff --git a/content/mesh/guides/prometheus-metrics.md b/content/mesh/guides/prometheus-metrics.md deleted file mode 100644 index f4b95585b..000000000 --- a/content/mesh/guides/prometheus-metrics.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Prometheus Metrics -description: How to set up and view prometheus metrics for valuable workload insights -weight: 40 -toc: true -nd-docs: DOCS-840 -type: -- how-to ---- - -## Overview - -F5 NGINX Service Mesh integrates with Prometheus for metrics and Grafana for visualizations. - -{{< call-out "note" >}} -To configure NGINX Service Mesh to use Prometheus when deploying, refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md#prometheus" >}} ) guide for instructions. -{{< /call-out >}} - -The mesh supports the [SMI spec](https://github.com/servicemeshinterface/smi-spec), including traffic metrics. -The NGINX Service Mesh creates an extension API Server and shim that query Prometheus and return the results in a traffic metrics format. See [SMI Traffic Metrics]( {{< ref "smi-traffic-metrics.md" >}}) for more information. - -{{< call-out "note" >}} -Occasionally metrics are reset when the nginx-mesh-sidecar reloads NGINX Plus. If traffic is flowing and you -fail to see metrics, retry after 30 seconds. -{{< /call-out >}} - -If you are deploying NGINX Plus Ingress Controller with the NGINX Service Mesh, make sure to configure the NGINX Plus Ingress Controller to export metrics. -Refer to the [Metrics]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#nginx-plus-ingress-controller-metrics" >}} ) section of the NGINX Plus Ingress Controller Deployment tutorial for instructions. - -### Prometheus Metrics - -The NGINX Service Mesh sidecar exposes the following metrics in Prometheus format via the `/metrics` path on port 8887: - -- [NGINX Plus metrics](https://docs.nginx.com/nginx/admin-guide/dynamic-modules/prometheus-njs/#exported-metrics). -- `upstream_server_response_latency_ms`: a histogram of upstream server response latencies in milliseconds. -The response time is the time from when NGINX establishes a connection to an upstream server to when the last byte of the response body is received by NGINX. - -All metrics have the namespace `nginxplus`, for example `nginxplus_http_requests_total` and `nginxplus_upstream_server_response_latency_ms_count`. - -#### Examples - -This section includes a set of example metrics that you may plug into your existing Prometheus-based tooling to gain insights into the traffic flowing through your applications. - -##### HTTP - -- View the rate of requests currently flowing: - - ```promQL - irate(nginxplus_http_requests_total[30s]) - ``` - -- View unsuccessful response codes of your applications: - - ```promQL - nginxplus_upstream_server_responses{code=~"3xx|4xx|5xx"} - ``` - - This can be used to form more complex queries such as current success rate: - - ```promQL - sum(irate(nginxplus_upstream_server_responses{code=~"1xx|2xx"}[30s])) by (app, version) / sum(irate(nginxplus_upstream_server_responses[30s])) by (app, version) - ``` - -##### UDP/TCP - -- View the current throughput of clients sending to upstreams: - - ```promQL - irate(nginxplus_stream_upstream_server_sent[30s]) - ``` - -- You can also see the total number of connections made: - - ```promQL - nginxplus_stream_upstream_server_connections - ``` - -- (**TCP Only**): NGINX Service Mesh exposes a whole host of latency information for TCP connections: - - ```promQL - nginxplus_stream_upstream_server_connect_time - ``` - - ```promQL - nginxplus_stream_upstream_server_first_byte_time - ``` - - ```promQL - nginxplus_stream_upstream_server_response_time - ``` - -#### Labels - -All metrics have the following labels: - -{{% table %}} -| Metric Name | Description | -|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| job | Prometheus job name. All metrics scraped from an nginx-mesh-sidecar have a job name of `nginx-mesh-sidecars`, and all metrics scraped from an NGINX Plus Ingress Controller have a job name of `nginx-plus-ingress`. | -| pod | Name of the Pod. | -| namespace | Namespace where the Pod resides. | -| instance | Address of the Pod. | -| pod_template_hash | Value of the pod-template-hash Kubernetes label. | -| deployment, statefulset, or daemonset | Name of the Deployment, StatefulSet, or DaemonSet that the Pod belongs to. | -{{% /table %}} - -Metrics for upstream servers, such as `nginxplus_upstream_server_requests`, have these additional labels: - -{{% table %}} -| Metric Name | Description | -|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| code | Response code of the upstream server. For NGINX Plus metrics, the code will be one of the following: 1xx, 2xx, 3xx, 4xx, or 5xx. For the `upstream_server_response_latency_ms` metrics, the code is the specific response code, such as 201. | -| upstream | Name of the upstream server group. | -| server | Address of the upstream server selected by NGINX. | -{{% /table %}} - -Metrics for outgoing requests have the following destination labels: - -{{% table %}} -| Metric Name | Description | -|---------------------------------------------------------------|---------------------------------------------------------------------------------| -| dst_pod | Name of the Pod that the request was sent to. | -| dst_service | Name of the Service that the request was sent to. | -| dst_deployment, dst_statefulset, or dst_daemonset | Name of the Deployment, StatefulSet, or DaemonSet that the request was sent to. | -| dst_namespace | Namespace that the request was sent to. | -{{% /table %}} - -Metrics exported by NGINX Plus Ingress Controller have these additional labels: - -{{% table %}} -| Metric Name | Description | -|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ingress | Set to true if ingress traffic is enabled. | -| egress | Set to true if egress traffic is enabled. | -| class | Ingress class of the NGINX Plus Ingress Controller. | -| resource_type | Type of resource: VirtualServer, VirtualServerRoute, or Ingress. | -| resource_name | Name of the VirtualServer, VirtualServerRoute, or Ingress resource. | -| resource_namespace | Namespace of the resource. This value is kept for backwards compatibility; for consistency with NGINX Service Mesh metrics you can use `dst_namespace` for queries and filters. | -| service | Service the request was sent to. This value is kept for backwards compatibility; for consistency with NGINX Service Mesh metrics you can use `dst_service` for queries and filters. | -| pod_name | Name of the Pod that the request was sent to. This value is kept for backwards compatibility; for consistency with NGINX Service Mesh metrics you can use `dst_pod` for queries and filters. | -{{% /table %}} - -##### Filter Prometheus Metrics using Labels - -Here are some examples of how you can use the labels above to filter your Prometheus metrics: - -- Find all upstream server responses with server side errors for deployment `productpage-v1` in namespace `prod`: - - ```promQL - nginxplus_upstream_server_responses{deployment="productpage-v1",namespace="prod",code="5xx"} - ``` - -- Find all upstream server responses with successful response codes for deployment `productpage-v1` in namespace `prod`: - - ```promQL - nginxplus_upstream_server_responses{deployment="productpage-v1",namespace="prod",code=~"1xx|2xx"} - ``` - -- Find the p99 latency of all requests sent from deployment `productpage-v1` in namespace `prod` to service `details` in namespace `prod` over the last 30 seconds: - - ```promQL - histogram_quantile(0.99, sum(irate(nginxplus_upstream_server_response_latency_ms_bucket{namespace="prod",deployment="productpage-v1",dst_service="details"}[30s])) by (le)) - ``` - -- Find the p90 latency of all requests sent from deployment `productpage-v1` in namespace `prod` to service `details` in namespace `prod` over the last 30 seconds, excluding 301 response codes: - - ```promQL - histogram_quantile(0.90, sum(irate(nginxplus_upstream_server_response_latency_ms_bucket{namespace="prod",deployment="productpage-v1",dst_service="details",code!="301"}[30s])) by (le)) - ``` - -- Find the p50 latency of all successful(response codes of 200, or 201) requests sent from deployment `productpage-v1` in namespace `prod` to service `details` in namespace `prod` over the last 30 seconds: - - ```promQL - histogram_quantile(0.50, sum(irate(nginxplus_upstream_server_response_latency_ms_bucket{namespace="prod",deployment="productpage-v1",dst_service="details",code=~"200|201"}[30s])) by (le)) - ``` - -- Find all active connections for the NGINX Plus Ingress Controller: - - ```promQL - nginxplus_connections_active{job="nginx-plus-ingress"} - ``` - -### Grafana - -The custom NGINX Service Mesh Grafana dashboard `NGINX Mesh Top` can be imported into your Grafana instance. -For instructions and a list of features, see the [Grafana example](https://github.com/nginxinc/nginx-service-mesh/tree/main/examples/grafana) in the `nginx-service-mesh` GitHub repo. - -To view Grafana, port-forward your Grafana Service: - -```bash -kubectl port-forward -n svc/grafana 3000 -``` diff --git a/content/mesh/guides/secure-traffic-mtls.md b/content/mesh/guides/secure-traffic-mtls.md deleted file mode 100644 index 07493d44a..000000000 --- a/content/mesh/guides/secure-traffic-mtls.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -title: Secure Mesh Traffic using mTLS -toc: true -description: Learn about the mTLS options available in F5 NGINX Service Mesh. -weight: 30 -nd-docs: DOCS-696 -type: -- how-to ---- - -## Overview - -TLS authentication is ubiquitous. Because of the baseline level of security TLS provides the client when connecting to an unknown host, and the low barrier to entry created by the advent of services like Let's Encrypt, TLS has become table stakes for any moderately reputable website. In a microservices, multi-tenant Kubernetes environment, it is no longer sufficient for a client to authenticate a server's signature. Clients may be compromised, and in a tightly controlled environment such as a service mesh, it is paramount that clients get vetted to ensure they should be making requests to any particular server. F5 NGINX Service Mesh does this authentication through mTLS, and provides the ability to define [Access Control policies]( {{< ref "/mesh/guides/smi-traffic-policies.md#access-control" >}}) for the additional authorization piece needed to properly grant access to an incoming request from a given client. This document details the steps required to enable mTLS in your cluster using NGINX Service Mesh. - -{{< call-out "important" >}} -NGINX Service Mesh does not support mTLS communication with UDP at this time. UDP datagrams are currently proxied over plaintext. -{{< /call-out >}} - -Within the mTLS umbrella, NGINX Service Mesh allows for some level of configurability. This allows the flexibility to better support testing, development, and production environments. The options available are: - -- `off`: Disables mTLS between injected pods, and allows communication from any source or destination over plaintext. Suitable only for development environments. - -- `permissive` (default): Enables mTLS communication between injected pods, but also allows plaintext communication where mTLS cannot be established. While this provides flexibility in communicating to services outside of the mesh, it also means pods remain open for potential bad actors from unverified sources to gain access to an internal endpoint. - - Permissive mode can be appealing when first experimenting with NGINX Service Mesh because of the ease of setup in deploying your application, but we strongly suggest that you move to mTLS `strict` mode when evaluating NGINX Service Mesh for production scenarios. - -- `strict`: Production ready. All traffic between pods is encrypted, and only traffic destined for injected pods is supported. All other outgoing and incoming traffic is denied at the sidecar. See [Sidecar Proxy Injection]( {{< ref "/mesh/guides/inject-sidecar-proxy" >}}) for more information on properly injecting your applications for use within the mesh. - - If you need to route traffic to a non-meshed service in a `strict` environment, see our guide on [using the NGINX Ingress Controller for egress traffic]( {{< ref "/mesh/tutorials/kic/egress-walkthrough.md" >}} ). This can be useful when migrating legacy applications. Also, see [Deploy with NGINX Plus Ingress Controller]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md" >}}) for information on how to get external traffic routed securely to resources managed by NGINX Service Mesh. - - {{< call-out "important" >}} -Due to how tracing is set up within NGINX Service Mesh, mTLS `strict` mode does not support tracing originating from the application itself. Each mesh sidecar automatically logs request information and aggregates that information in the configured tracing application. See [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) for more information on how tracing is set up within NGINX Service Mesh. - {{< /call-out >}} - -All Kubernetes Resources that use the NGINX Service Mesh sidecar proxy inherit their mTLS settings from the global configuration. -You can override the global setting for individual Resources if needed. Refer to [Change the mTLS Settings for a Resource](#change-the-mtls-setting-for-a-resource) for instructions. - -## Usage - -### Enable mTLS - -When deploying NGINX Service Mesh with mTLS enabled, you can opt to use `permissive` or `strict` mode. The default setting for mTLS is `permissive`. - -{{< call-out "caution" >}} -Using permissive mode is not recommended for production deployments. -{{< /call-out >}} - -To enable mTLS, specify the `--mtls-mode` flag with the desired setting when deploying NGINX Service Mesh. For example: - -```bash -nginx-meshctl deploy ... --mtls-mode strict -``` - -### Deploy Using an Upstream Root CA - -By default, deployments with mTLS enabled use a self-signed root certificate. For testing and evaluation purposes this is acceptable, but for production deployments you should use a proper Public Key Infrastructure (PKI). - -SPIRE uses a mechanism called "Upstream Authority" to interface with PKI systems. In order to use an upstream authority, a user must provide the proper configuration and credentials so that SPIRE may interface with the upstream and obtain the pertinent certificates. - -In order to use a proper PKI, you must first choose one of the upstream authorities NGINX Service Mesh supports: - -- [disk](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_disk.md): Requires certificates and private key be on disk. - - Template: {{< icon "download" >}} {{< link "/examples/upstream-ca/disk.yaml" "disk.yaml" >}} - - - The minimal configuration to successfully deploy the mesh using the `disk` upstream authority looks like this: - - ```yaml - apiVersion: v1 - upstreamAuthority: disk - config: - cert_file_path: /path/to/rootCA.crt - key_file_path: /path/to/rootCA.key - ``` - -- [aws_pca](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_aws_pca.md): Uses [Amazon Certificate Manager Private Certificate Authority (ACM PCA)](https://docs.aws.amazon.com/acm-pca/latest/userguide/PcaWelcome.html) to manage certificates. - - Template: {{< icon "download" >}} {{< link "/examples/upstream-ca/aws_pca.yaml" "aws_pca.yaml" >}} - - - Here is the minimal configuration to deploy the mesh using the `aws_pca` upstream authority: - - ```yaml - apiVersion: "v1" - upstreamAuthority: "aws_pca" - config: - region: "us-west-2" - certificate_authority_arn: "arn:aws:acm-pca::123456789012:certificate-authority/test" - ``` - - {{< call-out "important" >}} - This configuration assumes that the SPIRE server has access to your certificate authority in ACM PCA. See below for details on how to configure access. - {{< /call-out >}} - - In order to use the `aws_pca` plugin, you need to give the SPIRE server access to your ACM PCA certificate authority. - - If you'd like NGINX Service Mesh to configure authentication on your behalf, you must specify your AWS Access Key ID and AWS Secret Key in the `aws_pca` config file. NGINX Service Mesh will create an AWS shared credentials file with these credentials, encode and store this file in a Kubernetes Secret, and then mount the secret to `~/.aws/credentials` on the SPIRE server Pod(s). - - If you would like the SPIRE server to use an IAM role to access your certificate authority, make sure your role contains the policy described in the [SPIRE `aws_pca` documentation](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_aws_pca.md), and do one of the following: - - Attach the IAM role to your EC2 instances where NGINX Service Mesh is running. - - Tell SPIRE to assume the IAM role by specifying the role ARN in the `assume_role_arn` field in the `aws_pca` config file. - - {{< call-out "note" >}} - The SPIRE server will need permission to assume this IAM role. Either attach an IAM role to the EC2 instance with the capability to assume the ACM PCA IAM role, or provide your AWS credentials in the `aws_pca` config file. - {{< /call-out >}} - -- [awssecret](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_awssecret.md): Loads CA credentials from AWS SecretsManager. - - Template: {{< icon "download" >}} {{< link "/examples/upstream-ca/awssecret.yaml" "awssecret.yaml" >}} - - - Here is the minimal configuration to deploy the mesh using the `awssecret` upstream authority: - - ```yaml - apiVersion: "v1" - upstreamAuthority: "awssecret" - config: - region: "us-west-2" - cert_file_arn: "arn:aws:acm-pca::123456789012:certificate-authority/test" - key_file_arn: "arn:aws:acm-pca::123456789012:certificate-authority/test-key" - ``` - - {{< call-out "important" >}} - AWS credentials may be necessary depending on your situation. View the [SPIRE guide](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_awssecret.md). - {{< /call-out >}} - -- [vault](https://github.com/spiffe/spire/blob/v0.12.3/doc/plugin_server_upstreamauthority_vault.md): Uses Vault PKI Engine to manage certificates. - - Template: {{< icon "download" >}} {{< link "/examples/upstream-ca/vault.yaml" "vault.yaml" >}} - -- [cert-manager](https://github.com/spiffe/spire/blob/v1.0.0/doc/plugin_server_upstreamauthority_cert_manager.md): Uses an instance of `cert-manager` running in Kubernetes to request intermediate signing certificates for SPIRE server. - - Template: {{< icon "download" >}} {{< link "/examples/upstream-ca/cert-manager.yaml" "cert-manager.yaml" >}} - - - Here is the minimal configuration to deploy the mesh using the `cert-manager` upstream authority: - - ```yaml - apiVersion: "v1" - upstreamAuthority: "cert-manager" - config: - namespace: "my-cert-namespace" - issuer_name: "my-spire-issuer-name" - ``` - -For a production deployment, you should provide the following: - -- `rootCA.crt` - A root CA certificate -- `rootCA.key` - A root CA certificate key -- `intermediateCA.crt` - An intermediate CA certificate (optional) -- `intermediateCA.key` - An intermediate CA certificate key (optional) - -For a production deployment, you should use an intermediate CA certificate instead of using the root CA certificate directly. In this case, you would specify the root CA certificate using the appropriate option for the upstream authority: - -- disk: `bundle_file_path` -- aws_pca: `supplemental_bundle_path` - -This keeps the root CA key secure because it adds the certificate, not the key itself, to the chain. The upstream bundle may contain multiple intermediate certificates, all the way up to the root CA. - -For example, a production deployment using the `disk` upstream authority will look something like this: - -```yaml -apiVersion: "v1" -upstreamAuthority: "disk" -config: - cert_file_path: "/path/to/intermediateCA.crt" - key_file_path: "/path/to/intermediateCA.key" - bundle_file_path: "/path/to/rootCA.crt" -``` - -To deploy using one of these upstream authorities, you must specify the `--mtls-upstream-ca-conf` flag: - -```bash -nginx-meshctl deploy ... --mtls-upstream-ca-conf /path/to/upstream_authority.yaml -``` - -To find out more about how `nginx-meshctl` interprets the upstream authority configuration, refer to the [Upstream CA Validation JSON schema](https://github.com/nginxinc/nginx-service-mesh/blob/main/api/upstream-ca-validation.json) - -#### Pathlen - -x509 certificates have a [pathlen field](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.9) that is used to limit the number of intermediate certificates in between the current certificate and the final endpoint certificate, not including the endpoint certificate. - -SPIRE creates a certificate for itself using the intermediate certificate passed in using the arguments defined above, so the `pathlen` must be either set to 1 or unset. For the root certificate, the pathlen must be at least 2, or unset. - -### Choose a SPIRE Key Manager Plugin - -SPIRE maintains a set of keys to sign certificates. NGINX Service Mesh supports two methods of storing those keys: `disk` and `memory`. - -- `disk` (default): Signing keys are kept on disk and recoverable in the case of a SPIRE server restart, but keys are vulnerable due to being kept on disk. - - {{< call-out "note" >}} - The `disk` key manager plugin only maintains the integrity of the SPIRE CA if persistent storage is being used. For most environments, persistent storage will be deployed by default. See [Persistent Storage]( {{< ref "/mesh/get-started/platform-setup/persistent-storage.md" >}} ) setup page for more information on configuring persistent storage in your environment. - {{< /call-out >}} - -- `memory`: Maintains the set of signing keys in memory and out of reach from bad actors should they gain access to your SPIRE server, but keys are lost on SPIRE server restart. - - We recommend that you only use the `memory` key manager plugin when you are using an upstream CA. Otherwise, when the SPIRE Server restarts due to a failure, all agents must be manually restarted and all workload certificates must be re-minted and re-distributed - causing unnecessary load on your resources and a potential disruption to workload communication. - -There are benefits and drawbacks to both key manager plugins, but we recommend using the `memory` key manager alongside an upstream CA for a more secure production experience. When paired with an upstream CA, the drawbacks of the `memory` key manager can be eliminated. For more information on productionizing your deployment, see our [Production Tuning]( {{< ref "/mesh/guides/production-tuning.md" >}}) guide. - -### Change the mTLS Setting for a Resource - -NGINX Service Mesh provides you the ability to modify the global mTLS setting on a per-resource basis. This allows you to patch individual resources with mTLS `strict` mode as you begin to properly secure your application. - -When configuring mTLS for resources, if your global mTLS mode is `strict`, you will not be able to modify the mode on a per resource basis. The reason for this is that we want to push users towards the most secure deployment possible when evaluating mTLS `strict` mode and production. Also if an admin configures `strict` mTLS mode globally, it will prevent the Application Developer persona from modifying NGINX Service Mesh's security settings on an ad hoc basis and potentially introducing security holes. We do provide the ability to communicate with non-meshed services using the [NGINX Ingress Controller for egress traffic]( {{< ref "/mesh/tutorials/kic/egress-walkthrough.md" >}} ). If not all of your application components are ready for `strict` mode, we encourage the use of `permissive` mode and a non-production environment. - -{{< call-out "important" >}} -If the global mTLS value is set to `strict`, then the annotation value will be ignored. -{{< /call-out >}} - -To override the global mTLS setting for a specific resource, add an annotation to the resource's PodTemplateSpec. For example: - -```yaml -config.nsm.nginx.com/mtls-mode: "strict" -``` - -### Disable mTLS - -To disable mTLS globally, specify the `--mtls-mode off` flag when deploying NGINX Service Mesh. For example: - -```bash -nginx-meshctl deploy ... --mtls-mode off -``` - -To disable mTLS for a specific resource, add the following annotation to the resource's PodTemplateSpec: - -```yaml -config.nsm.nginx.com/mtls-mode: "off" -``` - -{{< call-out "note" >}} -Refer to [NGINX Service Mesh Annotations]( {{< ref "/mesh/get-started/install/configuration.md#pod-annotations" >}}) for more information. -{{< /call-out >}} - -{{< call-out "note" >}} -[How to update mTLS settings after deployment.](#update-mtls-settings-after-deployment) -{{< /call-out>}} - -## Verify Deployment - -NGINX Service Mesh deploys additional pods in the configured control plane namespace (default `nginx-mesh`) for the SPIRE Server and Agent. - -To verify deployment, check whether or not the SPIRE Pods are running. You should have a single Server Pod and an Agent Pod for each Kubernetes Node. - -```bash -kubectl get pods -n nginx-mesh -NAME READY STATUS RESTARTS AGE -... -spire-agent-mb9jv 1/1 Running 0 24h -spire-server-0 2/2 Running 0 24h -... -``` - -### Verify Encryption by Using an Example Service - -We'll use the [Istio `bookinfo`](https://istio.io/docs/examples/bookinfo/) example to test that traffic is, in fact, encrypted with mTLS enabled. - -- {{< icon "download" >}} {{< link "/examples/bookinfo.yaml" "`bookinfo.yaml`" >}} - -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Deploy the `bookinfo` application: - - ```bash - kubectl apply -f bookinfo.yaml - ``` - -1. To access `bookinfo`, set up port-forwarding: - - ```bash - kubectl port-forward svc/productpage 9080 - ``` - -1. Finally, navigate to `http://localhost:9080` in a browser. On the front side, it uses clear text. All of the service-to-service calls will be SSL-encrypted. - - -### Debug mTLS Issues - -Not all MTLS misconfiguration errors can be caught when the configuration is loaded. For example, NGINX will not detect if the certificate expires during operation. NGINX responds to requests with invalid certificates with a `400 Bad Request` error. Debugging information is provided in the error log at the `info` level. - -Refer to [logging]({{< ref "/mesh/get-started/install/configuration.md#logging">}} ) for information about changing the log level. - -### Update mTLS settings after deployment - -The following mTLS settings can be changed after the mesh has been deployed: - -- mode -- caTTL -- svidTTL -- caKeyType - -{{< call-out "important" >}} -When updating `caTTL`, `svidTTL`, or `caKeyType`, the SPIRE server will be restarted and brief downtime should be expected. Certificates cannot be given out during this time, so it is advised to not create any applications until the server has fully restarted. Any existing certificates will live until their expirations, but all new certificates will use the updated mTLS settings. Workload certificates can be forced to be updated by re-rolling the workload Pods. - -If using persistent storage--which is the default and recommended setting when deploying the mesh--, updated `caTTL` and `caKeyType` fields will not take effect until the original root CA certificate expires. The expiry time is based on the original `caTTL` value. If you want to change these values immediately, then the fastest--but most disruptive--way to do this is to remove and redeploy NGINX Service Mesh. -{{< /call-out >}} - -See the [API Usage Guide]( {{< ref "api-usage.md#modify-the-mesh-state-by-using-the-rest-api" >}} ) for instructions on how to update the mTLS settings using the REST API. - -### What Next - -With mTLS properly set up within your service mesh, it is important to set up authorization to properly verify incoming connections. - -See [Access Control policies]( {{< ref "/mesh/guides/smi-traffic-policies.md#access-control" >}}) for how to define authorization within your application. diff --git a/content/mesh/guides/smi-traffic-metrics.md b/content/mesh/guides/smi-traffic-metrics.md deleted file mode 100644 index 99c8d89ee..000000000 --- a/content/mesh/guides/smi-traffic-metrics.md +++ /dev/null @@ -1,208 +0,0 @@ ---- -title: SMI Traffic Metrics -description: How to set up and view SMI traffic metrics -weight: 50 -toc: true -nd-docs: DOCS-697 -type: -- how-to ---- - -## Overview - -Traffic Metrics are a mechanism through which you can view important information about your running workloads. Exposed by the [SMI spec](https://github.com/servicemeshinterface/smi-spec), this allows you to migrate functionality from your existing service mesh. The F5 NGINX Service Mesh creates an extension API Server and shim that queries Prometheus and returns the results in a traffic metrics format. You can access these metrics through a variety of methods ranging from the `nginx-meshctl` tool to querying the Kubernetes API manually. - -Traffic Metrics depends on Prometheus to get the critical service information needed to visualize your applications. - -{{< call-out "note" >}} -To configure NGINX Service Mesh to export metrics to your Prometheus deployment, refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md#use-an-existing-prometheus-deployment" >}} ) guide for instructions. -{{< /call-out >}} - -If you are deploying NGINX Plus Ingress Controller with the NGINX Service Mesh, make sure to configure the NGINX Plus Ingress Controller to export metrics. -Refer to the [Metrics]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#nginx-plus-ingress-controller-metrics" >}} ) section of the NGINX Plus Ingress Controller Deployment tutorial for instructions. - -### How to View Traffic Metrics - -There are multiple ways you can view the traffic metrics generated by NGINX Service Mesh. - -{{< call-out "note" >}} -Occasionally metrics are reset when the nginx-mesh-sidecar reloads NGINX Plus. If traffic is flowing and you -fail to see metrics, retry after 30 seconds. -{{< /call-out >}} - -#### nginx-meshctl top - -The `nginx-meshctl top` command uses traffic metrics to give information at a glance of traffic currently flowing through the mesh. An example output looks like this: - -```txt -$ nginx-meshctl top - -Deployment Incoming Success Outgoing Success NumRequests -productpage-v1 100.00% 100.00% 221 -reviews-v2 100.00% 100.00% 112 -reviews-v3 100.00% 100.00% 110 -reviews-v1 100.00% 54 -details-v1 100.00% 165 -ratings-v1 100.00% 54 -``` - -You can also view additional golden metrics and edge information by specifying a specific resource in the top command. An example of this is: - -```txt -$ nginx-meshctl top deployments/productpage-v1 - -Deployment Direction Resource Success Rate P99 P90 P50 NumRequests -productpage-v1 - To reviews-v2 100.00% 91ms 22ms 15ms 54 - To details-v1 100.00% 15ms 4ms 2ms 160 - To reviews-v1 100.00% 91ms 16ms 5ms 54 - To reviews-v3 100.00% 183ms 20ms 14ms 52 - From nginx-plus-ingress 100.00% 183ms 47ms 31ms 161 -``` - -{{< call-out "note" >}} -A success rate of `` means that traffic metrics are still populating. Send additional traffic and wait for success rate to properly appear. -{{< /call-out >}} - -#### Raw - -To view individual traffic metrics, use the `kubectl get` command shown below. - -```bash -kubectl get --raw /apis/metrics.smi-spec.io/v1alpha1/namespaces/default/deployments/reviews-v3/edges -``` - -The output looks similar to that shown below. - -```json -{ - "kind": "TrafficMetricsList", - "apiVersion": "metrics.smi-spec.io/v1alpha1", - "metadata": { - "selfLink": "/apis/metrics.smi-spec.io/v1alpha1/namespaces/default/deployments/reviews-v3/edges" - }, - "resource": { - "kind": "Deployment", - "namespace": "default", - "name": "reviews-v3" - }, - "items": [ - { - "kind": "TrafficMetrics", - "apiVersion": "metrics.smi-spec.io/v1alpha1", - "metadata": { - "name": "reviews-v3", - "namespace": "default", - "selfLink": "/apis/metrics.smi-spec.io/v1alpha1/namespaces/default/deployments/reviews-v3/edges", - "creationTimestamp": "2019-10-16T22:33:02Z" - }, - "timestamp": "2019-10-16T22:33:02Z", - "window": "30s", - "resource": { - "kind": "Deployment", - "namespace": "default", - "name": "reviews-v3" - }, - "edge": { - "direction": "to", - "resource": { - "kind": "Deployment", - "namespace": "default", - "name": "ratings-v1" - } - }, - "metrics": [ - { - "name": "p99_response_latency", - "unit": "ms", - "value": "2969m" - }, - { - "name": "p90_response_latency", - "unit": "ms", - "value": "2700m" - }, - { - "name": "p50_response_latency", - "unit": "ms", - "value": "1499m" - }, - { - "name": "success_count", - "value": "9" - }, - { - "name": "failure_count", - "value": "0" - } - ] - }, - { - "kind": "TrafficMetrics", - "apiVersion": "metrics.smi-spec.io/v1alpha1", - "metadata": { - "name": "reviews-v3", - "namespace": "default", - "selfLink": "/apis/metrics.smi-spec.io/v1alpha1/namespaces/default/deployments/reviews-v3/edges", - "creationTimestamp": "2019-10-16T22:33:02Z" - }, - "timestamp": "2019-10-16T22:33:02Z", - "window": "30s", - "resource": { - "kind": "Deployment", - "namespace": "default", - "name": "reviews-v3" - }, - "edge": { - "direction": "from", - "resource": { - "kind": "Deployment", - "namespace": "default", - "name": "productpage-v1" - } - }, - "metrics": [ - { - "name": "p99_response_latency", - "unit": "ms", - "value": "29600m" - }, - { - "name": "p90_response_latency", - "unit": "ms", - "value": "26" - }, - { - "name": "p50_response_latency", - "unit": "ms", - "value": "13333m" - }, - { - "name": "success_count", - "value": "11" - }, - { - "name": "failure_count", - "value": "0" - } - ] - } - ] -} -``` - -##### Supported Metrics Endpoints - -NGINX Service Mesh supports the SMI-spec endpoints shown in the table below. - -{{% table %}} -| Endpoint | Description | -|---------------------------------------------------------------|-------------------------------------------------------------------------------------| -| /apis/metrics.smi-spec.io/v1alpha1/ | gets supported resource kinds. | -| .../namespaces | retrieves generic information for traffic between namespaces. | -| .../namespaces/{namespace} | retrieves generic information for traffic to and from a particular namespace. | -| .../namespaces/{namespace}/edges | retrieves specific information for traffic to and from a particular namespace. | -| .../namespaces/{namespace}/{resourceKind} | retrieves generic information for traffic between resourceKind types. | -| .../namespace/{namespace}/{resourceKind}/{resourceName} | retrieves generic information for traffic between named resource of specified kind. | -| .../namespace/{namespace}/{resourceKind}/{resourceName}/edges | retrieves specific edge information to and from named resource of specified kind. | -{{% /table %}} diff --git a/content/mesh/guides/smi-traffic-policies.md b/content/mesh/guides/smi-traffic-policies.md deleted file mode 100644 index 471272c4c..000000000 --- a/content/mesh/guides/smi-traffic-policies.md +++ /dev/null @@ -1,378 +0,0 @@ ---- -title: SMI Traffic Policies -toc: true -description: Learn about the traffic policies supported by F5 NGINX Service Mesh and - how to configure them. -weight: 60 -nd-docs: DOCS-698 -type: -- how-to ---- - -## Overview - -This topic discusses the various traffic policies that are supported by F5 NGINX Service Mesh. We support the SMI spec to allow for a variety of functionality within our mesh, from traffic shaping to access control. NGINX Service Mesh provides additional traffic policies to extend on the SMI spec. This topic provides examples of how you can use the SMI spec and NGINX custom resources with NGINX Service Mesh to apply policies and control your traffic. - -Refer to the [SMI GitHub repo](https://github.com/servicemeshinterface/smi-spec) to find out more about the SMI spec and how to configure it. - -{{< call-out "note" >}} -Avoid configuring traffic policies such as TrafficSplits, RateLimits, and CircuitBreakers for headless services. -These policies will not work as expected because NGINX Service Mesh has no way to tie each pod IP address to its headless service. -{{< /call-out >}} - -## SMI Specification - -### Traffic Splitting - - - -You can use the SMI [TrafficSplit spec](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md) to implement Canary, A/B testing, and other traffic routing setups. - - - -NGINX Service Mesh is also compatible with Flagger and other SMI-compatible projects. - -The [Deployments using Traffic Splitting]( {{< ref "/mesh/tutorials/trafficsplit-deployments.md" >}} ) tutorial provides a walkthrough of using traffic splits in a deployment. - -{{< call-out "note" >}} -The NGINX Plus Ingress Controller's custom resource [TransportServer](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/) has the same Kubernetes short name (`ts`) as the custom resource TrafficSplit. -If you install the NGINX Plus Ingress Controller, use the full names `transportserver(s)` and `trafficsplit(s)` when managing these resources with `kubectl`. -{{< /call-out >}} - -#### Traffic Split Matches - -The [TrafficSplit spec](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md) outlines how you can split traffic based on headers in order to implement A/B testing. -NGINX Service Mesh expands on this concept by allowing you to split traffic based on the path, HTTP methods, and/or headers of a request. -This is achieved by specifying `matches` that associate HTTPRouteGroups with your traffic split policy. - -```yaml -apiVersion: split.smi-spec.io/v1alpha3 -kind: TrafficSplit -metadata: - name: target -spec: - service: target-svc - backends: - - service: target-v1 - weight: 0 - - service: target-v2 - weight: 1 - - service: target-v3 - weight: 0 - matches: - - kind: HTTPRouteGroup - name: target-route-group ---- - apiVersion: specs.smi-spec.io/v1alpha3 - kind: HTTPRouteGroup - metadata: - name: target-route-group - namespace: default - spec: - matches: - - name: metrics - pathRegex: "/metrics" - methods: - - GET - - name: test-header - headers: - x-test: "^true$" -``` - -This example associates all matches defined in the `target-route-group` to the `target` TrafficSplit. When a request is sent to the `target-svc`, if it's a GET request to the `/metrics` endpoint or has the `x-test:true` header set, the traffic split is applied and the request is routed to the `target-v2` service. -All other requests will be sent to the root `target-svc`, which will forward the request to one of the target services based on the load-balancing algorithm of the mesh. - -{{< call-out "note" >}} -If there are multiple matches defined in a HTTPRouteGroup, or multiple HTTPRouteGroups listed in the TrafficSplit `spec.matches` field, then all the matches across all HTTPRouteGroups will be attached to the TrafficSplit. -Matches are evaluated with the `OR` operation, meaning that a request only needs to satisfy one of the matches in order for the traffic split to be applied. -{{< /call-out >}} - -Services named in a TrafficSplit definition should not forward to an overlapping set of Pods. In other words, using the example above, `target-v1` and `target-v2` should have unique selectors to ensure they are forwarding to different sets of Pods. This avoids any unintentional or confusing traffic flow to incorrect destinations. - -In another example, the spec above could be changed to set `target-v1` as the root default service, rather than `target-svc`. With this configuration, all traffic that matches the HTTPRouteGroup rules will be sent to `target-v2`, while all unmatched traffic will be sent to `target-v1`. - -For more information about HTTPRouteGroups see the SMI [Traffic Specs guide](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-specs/v1alpha3/traffic-specs.md). - -### Access Control - - -You can use the SMI [Traffic Access](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-access/v1alpha2/traffic-access.md) spec to define access to applications throughout your cluster. Keep in mind that you must use this spec in conjunction with SMI [Specs](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-specs/v1alpha3/traffic-specs.md) to fully define access control in the mesh. - - -#### Access Control Rules - -The [Traffic Access spec](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-access/v1alpha2/traffic-access.md) describes how you can define L7 rules for your access control policies with HTTPRouteGroups. - -HTTPRouteGroup rules are picked on a `first match` basis. A match is the first rule that satisfies all criteria -(`pathRegex`, `methods`, `headers`, and `port`) for a request. Matches should be defined in order -from most specific to least specific to ensure the `first match` policy picks the best option. - -This match policy works on a per TrafficTarget basis. If multiple TrafficTargets reference the same destination -and same sources, rule ordering is not guaranteed. Ensure that a single TrafficTarget contains all appropriate -rules for a destination and source. - -The [Services using Access Control]({{< ref "/mesh/tutorials/accesscontrol-walkthrough.md" >}}) tutorial provides a walkthrough of using access control between services. - -## NGINX SMI Extensions - -### Rate Limiting - -API Version: v1alpha2 - -The [Configure Rate Limiting]({{< ref "/mesh/tutorials/ratelimit-walkthrough.md" >}}) tutorial provides a walkthrough of setting up rate limiting between workloads. - -You can configure rate limiting between your workloads in NGINX Service Mesh by creating a RateLimit resource. - -```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: source-1 - namespace: default - name: 10rm - rate: 10r/m - burst: 10 - delay: "nodelay" - rules: - - kind: HTTPRouteGroup - name: hrg - matches: - - get-only ---- - apiVersion: specs.smi-spec.io/v1alpha3 - kind: HTTPRouteGroup - metadata: - name: hrg - namespace: default - spec: - matches: - - name: get-only - methods: - - GET - - name: v2 - pathRegex: "/configuration-v2" - headers: - X-DEMO: "^true$" - methods: - - GET -``` - -In this example, `GET` requests to the destination service from `source-1` will be rate limited at the rate of 10r/m. -The burst of 10 and a delay of `nodelay` means that 10 excess requests over the rate will be forwarded to the destination service immediately. -Requests from sources other than `source-1`, or requests from `source-1` that are _not_ `GET` requests, will not be rate limited. - -> You can download the schema for the RateLimit CRD here: {{< icon "download" >}} [`rate-limit-schema.yaml`](https://github.com/nginxinc/nginx-service-mesh/blob/main/helm-chart/crds/ratelimit.yaml) - -The rate limit spec contains the following fields: - -- `destination`: The destination resource for the rate limit (required). - - Must provide a `name`, `kind`, and `namespace` in order to bind to the specified resource. Supported kinds: `Pod`, `Deployment`, `DaemonSet`, `StatefulSet`, and `Service`. -- `sources`: The source resources that the rate limit is applied to (optional). - - Rate limits only affect the traffic from services that are in the sources list. Services not included in this - list are able to pass unlimited traffic to their destination(s). - If no sources are provided then the rate limit applies to all resources making requests to the destination. - - {{< call-out "note" >}} The sources do not have to be in the same namespace as the destination; cross-namespaces rate limiting is supported. {{< /call-out >}} - -- `name`: The name of the rate limit (required). -- `rate`: The rate to restrict traffic to (required). Example: "1r/s", "30r/m" - - Each Pod in the destination accepts the total rate defined in a rate limit policy. If a policy has - a rate of 100 r/m, and the destination consists of 3 Pods, each Pod accepts 100 r/m. - - If a single rate limit policy contains multiple sources, the rate divides evenly amongst them. For - example, a policy defined with - - ```yaml - destination: - name: destService - sources: - - name: source1 - - name: source2 - rate: 100 r/m - ``` - - would result in `destService` accepting 50 requests per minute from `source1`, and 50 requests per minute - from `source2`, for a total rate of 100 requests per minute. If two separate policies are defined for the - same destination, then the rate is not divided amongst the sources. - - {{< call-out "important" >}} - If you are creating multiple rate limit policies for the same destination, the source lists for each rate limit must be distinct. - You cannot reference the same source and destination across multiple rate limits. - {{< /call-out >}} - -- `burst`: The number of requests to allow beyond a given rate (optional). - - Refer to the [NGINX Documentation](http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req) for more information on burst. - -- `delay`: The number of requests after which to delay requests (optional). - - Refer to the [NGINX Documentation](http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req) for more information on delay. - -- `rules`: A list of routing rules (optional). - - RateLimit `rules` allow you to configure rate limiting based on the path, HTTP methods, and/or headers of a request. - The `rules` field is a list of [HTTPRouteGroups](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-specs/v1alpha3/traffic-specs.md) with an optional `matches` field. - The `matches` field allows you to specify one or more matches from a particular HTTPRouteGroup. If the `matches` field is omitted, then all matches from the HTTPRouteGroup are attached to the RateLimit. - - {{< call-out "important" >}}HTTPRouteGroups must be in the same namespace as the RateLimit.{{< /call-out >}} - - {{< call-out "note" >}} - If there are multiple matches defined in an HTTPRouteGroup, or multiple HTTPRouteGroups listed in the RateLimit `spec.rules` field, then all the matches across all HTTPRouteGroups will be attached to the RateLimit. - Matches are evaluated with the OR operation, meaning that a request only needs to satisfy one of the matches in order for the rate limit to be applied. - {{< /call-out >}} - -Documentation for the v1alpha1 RateLimit can be found [here]({{< ref "v1alpha1-ratelimit.md" >}}). - -#### Default rate limit policies - -If you would like to enforce one rate limit policy for all requests made to a destination, you can omit the `sources` field from your rate limit spec. - -```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: dest-svc-default - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - name: 10rs - rate: 10r/s -``` - -The above configuration will restrict all traffic sent to `dest-svc` to 10 requests per second. - -You can also create additional rate limit policies for `dest-svc` with `sources` defined, and the default policy will only apply if the source of the traffic is not listed in another policy. -For example, if you have a source that sends bursts of requests to `dest-svc`, you might create the following rate limit: - -```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: dest-svc-bursty - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - kind: Deployment - name: bursty-src - namespace: default - name: 10rs - rate: 10r/s - burst: 10 -``` - -The `dest-svc-bursty` rate limit will allow a burst of 10 requests to get through to the `dest-svc` only if the requests are from `bursty-src`. All other requests will be limited according to the `dest-svc-default` rate limit policy. - -#### Rate limit conflicts - -Multiple rate limit policies are allowed for the same destination, but the source lists for each rate limit must be distinct. You cannot reference the same source and destination across multiple rate limits. -The NGINX Service Mesh control plane's [admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#what-are-admission-webhooks) will reject rate limits that reference the same source and destination as an existing rate limit. -However, the admission webhook can only compare the resource definitions (`Kind`, `Namespace`, and `Name`) of the destination and sources in order to determine if there's a conflict with an existing rate limit. -For example, consider the following rate limits: - -```yaml -apiVersion: specs.smi.nginx.com/v1alpha2 -kind: RateLimit -metadata: - name: limit-1 - namespace: default -spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Service - name: src-1 - namespace: default -``` - -```yaml -apiVersion: specs.smi.nginx.com/v1alpha2 -kind: RateLimit -metadata: - name: limit-2 - namespace: default -spec: - destination: - kind: Deployment - name: dest-svc - namespace: default - sources: - - kind: Service - name: src-1 - namespace: default -``` - -Let's say that we create `limit-1` first and `limit-2` second. The admission webhook will allow both limits to be created, because even though the sources are the same, the destinations are different. -If the control plane later determines that the IP addresses between the two destinations are the same, `limit-2` will not be configured and the control plane will emit an `UpsertRateLimitFailed` event on the rate limit object. - -To check for misconfiguration events, you can describe the rate limit: - -```bash -kubectl describe ratelimit -``` - -### Circuit Breaking - -API Version: v1alpha1 - -You can enable circuit breaking by creating a CircuitBreaker resource. -A circuit breaker requires a destination and an associated spec. The destination takes a `name`, `kind`, and `namespace` in order to bind to a selected resource. - -{{< call-out "note" >}} -Currently, only `kind: Service` is supported. -{{< /call-out >}} - -The circuit breaker spec has three custom fields: - -- `errors`: The number of errors before the circuit trips. -- `timeoutSeconds`: The window for errors to occur within before tripping the circuit. Also the amount of time -to wait before closing the circuit. -- `fallback`: The name and port of a Kubernetes Service to re-route traffic to after the circuit has been tripped. - - Example: - - ```yaml - fallback: - name: "my-namespace/fallback-svc" - port: 8080 - ``` - - If no namespace or port is specified, default values are `default` and `80`, respectively. - -{{< call-out "important" >}} -The destination and fallback services must be in the same namespace. The fallback service must be [injected with the sidecar proxy]( {{< ref "/mesh/guides/inject-sidecar-proxy.md" >}} ). -{{< /call-out >}} - -{{< call-out "important" >}} -If Circuit Breakers are configured, the load balancing algorithm `random` cannot be used. Combining Circuit Breakers with `random` load balancing will cause sidecars to exit with an error. Data flow will be affected. - -To avoid this issue, use a different load balancing algorithm. See the [Configuration]({{< ref "/mesh/get-started/install/configuration.md" >}}) guide. -{{< /call-out >}} - -{{< call-out "important" >}} -If a Traffic Split is applied to the same service that a Circuit Breaker is defined for, the Circuit Breaker may no longer function as intended. This is because the Traffic Split changes the destination service to a backend service, not the original root destination for which the Circuit Breaker is defined. Therefore, Circuit Breakers must be defined for each backend service individually. -{{< /call-out >}} - -> You can download our Circuit Breaker example here: {{< link "/examples/circuit-breaker/circuit-breaker.yaml" "circuit-breaker.yaml" >}} and the Circuit Breaker schema here: [`circuit-breaker-schema.yaml`](https://github.com/nginxinc/nginx-service-mesh/blob/main/helm-chart/crds/circuitbreaker.yaml) - -Refer to the [NGINX Documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server) for more information about the `max_fails`, `fail_timeout`, and `backup` parameters, which are used for circuit breaking. diff --git a/content/mesh/guides/v1alpha1-ratelimit.md b/content/mesh/guides/v1alpha1-ratelimit.md deleted file mode 100644 index 3d1321159..000000000 --- a/content/mesh/guides/v1alpha1-ratelimit.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: v1alpha1 RateLimit Documentation -nd-docs: DOCS-701 -description: v1alpha1 RateLimit documentation. ---- - -## RateLimit - -API Version: v1alpha1 - -```yaml -apiVersion: specs.smi.nginx.com/v1alpha1 -kind: RateLimit -metadata: -name: ratelimit-v1 -namespace: default -spec: -destination: -kind: Service -name: dest-svc -namespace: default -sources: -- kind: Deployment -name: source-1 -namespace: default -name: 10rm -rate: 10r/m -burst: 10 -delay: "nodelay" -``` - -In this example, requests to the destination service from `source-1` will be rate limited at the rate of 10r/m. -The burst of 10 and a delay of `nodelay` means that 10 excess requests over the rate will be forwarded to the destination service immediately. -Requests from sources other than `source-1` will not be rate limited. - - -The rate limit spec contains the following fields: - -- `destination`: The destination resource for the rate limit (required). - - Must provide a `name`, `kind`, and `namespace` in order to bind to the specified resource. Supported kinds: `Pod`, `Deployment`, `DaemonSet`, `StatefulSet`, and `Service`. - -- `sources`: The source resources that the rate limit is applied to (optional). - - Rate limits only affect the traffic from services that are in the sources list. Services not included in this - list are able to pass unlimited traffic to their destination(s). - If no sources are provided then the rate limit applies to all resources making requests to the destination. - - {{< call-out "note" >}} The sources do not have to be in the same namespace as the destination; cross-namespaces rate limiting is supported. {{< /call-out >}} - -- `name`: The name of the rate limit (required). -- `rate`: The rate to restrict traffic to (required). Example: "1r/s", "30r/m" - - Each Pod in the destination accepts the total rate defined in a rate limit policy. If a policy has - a rate of 100 r/m, and the destination consists of 3 Pods, each Pod accepts 100 r/m. - - If a single rate limit policy contains multiple sources, the rate divides evenly amongst them. For - example, a policy defined with - - ```yaml - destination: - name: destService - sources: - - name: source1 - - name: source2 - rate: 100 r/m - ``` - - would result in `destService` accepting 50 requests per minute from `source1`, and 50 requests per minute - from `source2`, for a total rate of 100 requests per minute. If two separate policies are defined for the - same destination, then the rate is not divided amongst the sources. - - {{< call-out "important" >}} - If you are creating multiple rate limit policies for the same destination, the source lists for each rate limit must be distinct. - You cannot reference the same source and destination across multiple rate limits. - {{< /call-out >}} - -- `burst`: The number of requests to allow beyond a given rate (optional). - - Refer to the [NGINX Documentation](http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req) for more information on burst. - -- `delay`: The number of requests after which to delay requests (optional). - - Refer to the [NGINX Documentation](http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req) for more information on delay. diff --git a/content/mesh/reference/_index.md b/content/mesh/reference/_index.md deleted file mode 100644 index 36875c884..000000000 --- a/content/mesh/reference/_index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Reference -weight: 500 -url: /nginx-service-mesh/reference/ ---- - diff --git a/content/mesh/reference/api-usage.md b/content/mesh/reference/api-usage.md deleted file mode 100644 index e5f3845ad..000000000 --- a/content/mesh/reference/api-usage.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: NGINX Service Mesh API -description: Instructions for interacting with the F5 NGINX Service Mesh API. -toc: true -weight: 200 -nd-docs: DOCS-702 -type: -- reference ---- - -## Overview - -The F5 NGINX Service Mesh API exists within a Kubernetes Custom Resource, and can be used to manage the global mesh configuration. This resource is created when the mesh is deployed, and can be updated at runtime using the Kubernetes API. - -## Modifying the global mesh configuration - -To update the global mesh configuration, use `kubectl` to edit the `meshconfig` resource that lives in the NGINX Service Mesh namespace. By default, the name of the resource is `nginx-mesh-config`. - -```bash -kubectl edit meshconfig nginx-mesh-config -n nginx-mesh -``` - -This will open your default text editor to make changes. To see the configurable fields, download the custom resource definition: - -{{< icon "download" >}} [`meshconfig-schema.yaml`](https://github.com/nginxinc/nginx-service-mesh/blob/main/helm-chart/crds/meshconfig.yaml) - -{{< call-out "warning" >}} -If the `meshconfig` resource is deleted, or the `spec.meshConfigClassName` field is removed or changed, then the global mesh configuration cannot be updated, and unexpected behavior may occur. -{{< /call-out >}} - -## Viewing the global mesh configuration - -The `meshconfig` custom resource only contains configuration fields that can be changed at runtime. To view the full state of the mesh configuration, including fields that were set at installation, you can use the `nginx-meshctl` command line tool. - -- View the full configuration of the mesh: - -```bash -nginx-meshctl config -``` - -- View the services participating in the mesh: - -```bash -nginx-meshctl services -``` - -## Programmatic Access - -For programmatic access, we recommend using a [Kubernetes client SDK](https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/#programmatic-access-to-the-api). diff --git a/content/mesh/reference/nginx-meshctl.md b/content/mesh/reference/nginx-meshctl.md deleted file mode 100644 index 46a68e55d..000000000 --- a/content/mesh/reference/nginx-meshctl.md +++ /dev/null @@ -1,384 +0,0 @@ ---- -title: CLI Reference -description: Man page and instructions for using the F5 NGINX Service Mesh CLI -draft: false -weight: 300 -toc: true -nd-docs: DOCS-704 -type: -- reference ---- - -## Usage - -`nginx-meshctl` is the CLI utility for the F5 NGINX Service Mesh control plane. -Requires a connection to a Kubernetes cluster via a `kubeconfig`. - -```txt -Usage: - nginx-meshctl [flags] - nginx-meshctl [command] - -Available Commands: - completion Generate the autocompletion script for the specified shell - config Display the NGINX Service Mesh configuration - deploy Deploys NGINX Service Mesh into your Kubernetes cluster - help Help for nginx-meshctl or any command - inject Inject the NGINX Service Mesh sidecars into Kubernetes resources - remove Remove NGINX Service Mesh from your Kubernetes cluster - services List the Services registered with NGINX Service Mesh - status Check connection to NGINX Service Mesh API - supportpkg Create an NGINX Service Mesh support package - top Display traffic statistics - upgrade Upgrade NGINX Service Mesh - version Display NGINX Service Mesh version - -Flags: - -h, --help help for nginx-meshctl - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Completion - -Generate the autocompletion script for nginx-meshctl for the specified shell. -See each sub-command's help for details on how to use the generated script. - -```txt -Usage: - nginx-meshctl completion [command] - -Available Commands: - bash Generate the autocompletion script for bash - fish Generate the autocompletion script for fish - powershell Generate the autocompletion script for powershell - zsh Generate the autocompletion script for zsh - -Flags: - -h, --help help for completion - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Config - -Display the NGINX Service Mesh configuration. - -```txt -Usage: - nginx-meshctl config [flags] - -Flags: - -h, --help help for config - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Deploy - -Deploy NGINX Service Mesh into your Kubernetes cluster. -This command installs the following resources into your Kubernetes cluster by default: - -- NGINX Mesh API: The Control Plane for the Service Mesh. -- NGINX Metrics API: SMI-formatted metrics. -- SPIRE: mTLS service-to-service communication. -- NATS: Message bus. - -
- -```txt -Usage: - nginx-meshctl deploy [flags] - -Flags: - --access-control-mode string default access control mode for service-to-service communication - Valid values: allow, deny (default "allow") - --client-max-body-size string NGINX client max body size (default "1m") - --disable-public-images don't pull third party images from public repositories - --enable-udp enable UDP traffic proxying (beta); Linux kernel 4.18 or greater is required - --environment string environment to deploy the mesh into - Valid values: kubernetes, openshift (default "kubernetes") - -h, --help help for deploy - --image-tag string tag used for pulling images from registry - Affects: nginx-mesh-controller, nginx-mesh-cert-reloader, nginx-mesh-init, nginx-mesh-metrics, nginx-mesh-sidecar (default "2.0.0") - --mtls-ca-key-type string the key type used for the SPIRE Server CA - Valid values: ec-p256, ec-p384, rsa-2048, rsa-4096 (default "ec-p256") - --mtls-ca-ttl string the CA/signing key TTL in hours(h). Min value 24h. Max value 999999h. (default "720h") - --mtls-mode string mTLS mode for pod-to-pod communication - Valid values: off, permissive, strict (default "permissive") - --mtls-svid-ttl string the TTL of certificates issued to workloads in hours(h) or minutes(m). Max value is 999999. (default "1h") - --mtls-trust-domain string the trust domain of the NGINX Service Mesh (default "example.org") - --mtls-upstream-ca-conf string the upstream certificate authority configuration file - --nginx-error-log-level string NGINX error log level - Valid values: debug, info, notice, warn, error, crit, alert, emerg (default "warn") - --nginx-lb-method string NGINX load balancing method - Valid values: least_conn, least_time, least_time last_byte, least_time last_byte inflight, random, random two, random two least_conn, random two least_time, random two least_time=last_byte, round_robin (default "least_time") - --nginx-log-format string NGINX log format - Valid values: default, json (default "default") - --persistent-storage string use persistent storage. "auto" will enable persistent storage if a default StorageClass exists - Valid values: auto, off, on (default "auto") - --prometheus-address string the address of a Prometheus server deployed in your Kubernetes cluster - Address should be in the format .: - --registry-key string path to JSON Key file for accessing private GKE registry - Cannot be used with --registry-username or --registry-password - --registry-password string password for accessing private registry - Requires --registry-username to be set. Cannot be used with --registry-key - --registry-server string hostname:port (if needed) for registry and path to images - Affects: nginx-mesh-controller, nginx-mesh-cert-reloader, nginx-mesh-init, nginx-mesh-metrics, nginx-mesh-sidecar (default "docker-registry.nginx.com/nsm") - --registry-username string username for accessing private registry - Requires --registry-password to be set. Cannot be used with --registry-key - --spire-server-key-manager string storage logic for SPIRE Server's private keys - Valid values: disk, memory (default "disk") - --telemetry-exporters stringArray list of telemetry exporter key-value configurations - Format: "type=,host=,port=". - Type, host, and port are required. Only type "otlp" exporter is supported. - --telemetry-sampler-ratio float32 the percentage of traces that are processed and exported to the telemetry backend. - Float between 0 and 1 (default 0.01) - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -### Deploy Examples - -Most of the examples below show shortened commands for convenience. The '...' in these examples represents the image references. Be sure to include the image references when running the deploy command. - -- Deploy the latest version of NGINX Service Mesh, using default values, from your container registry: - - `nginx-meshctl deploy --registry-server "registry:5000"` - -- Deploy the Service Mesh in namespace "my-namespace": - - `nginx-meshctl deploy ... --namespace my-namespace` - -- Deploy the Service Mesh with mTLS turned off: - - `nginx-meshctl deploy ... --mtls-mode off` - -- Deploy the Service Mesh and enable telemetry traces to be exported to your OTLP gRPC collector running in your Kubernetes cluster: - - `nginx-meshctl deploy ... --telemetry-exporters "type=otlp,host=otel-collector.my-namespace.svc.cluster.local,port=4317"` - -- Deploy the Service Mesh with upstream certificates and keys for mTLS: - - `nginx-meshctl deploy ... --mtls-upstream-ca-conf="disk.yaml"` - -## Inject - -Inject the NGINX Service Mesh sidecar into Kubernetes resources. - -- Accepts JSON and YAML formats. -- Outputs JSON or YAML resources with injected sidecars to stdout. - -
- -```txt -Usage: - nginx-meshctl inject [flags] - -Flags: - -f, --file string the filename that contains the resources you want to inject - If no filename is provided, input will be taken from stdin - -h, --help help for inject - --ignore-incoming-ports ints ports to ignore for incoming traffic - --ignore-outgoing-ports ints ports to ignore for outgoing traffic - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -### Inject Examples - -- Inject the resources in my-app.yaml and create in Kubernetes: - - `nginx-meshctl inject -f ./my-app.yaml | kubectl apply -f -` - -- Inject the resources passed into stdin and write the changes to the same file: - - `nginx-meshctl inject < ./my-app.json > ./my-injected-app.json` - -- Inject the resources in my-app.yaml and configure proxies to ignore ports 1433 and 1434 for outgoing traffic: - - `nginx-meshctl inject --ignore-outgoing-ports 1433,1434 -f ./my-app.yaml` - -- Inject the resources passed into stdin and configure proxies to ignore port 1433 for incoming traffic: - - `nginx-meshctl inject --ignore-incoming-ports 1433 < ./my-app.json` - -## Remove - -Remove the NGINX Service Mesh from your Kubernetes cluster. - -- Removes the resources created by the `deploy` command from the Service Mesh namespace (default: "nginx-mesh"). -- You will need to clean up all Deployments with injected proxies manually. - -
- -```txt -Usage: - nginx-meshctl remove [flags] - -Flags: - --environment string environment the mesh is deployed in - Valid values: kubernetes, openshift - -h, --help help for remove - -y, --yes answer yes for confirmation of removal - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -### Remove Examples - -- Remove the NGINX Service Mesh from the default namespace ("nginx-mesh"): - - `nginx-meshctl remove` - -- Remove the NGINX Service Mesh from namespace "my-namespace": - - `nginx-meshctl remove --namespace my-namespace` - -- Remove the NGINX Service Mesh without prompting the user to confirm removal: - - `nginx-meshctl remove -y` - -## Services - -List the Services registered with NGINX Service Mesh. - -- Outputs the Services and their upstream addresses and ports. -- The list contains only those Services whose Pods contain the NGINX Service Mesh sidecar. - -
- -```txt -Usage: - nginx-meshctl services [flags] - -Flags: - -h, --help help for services - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Status - -Check connection to NGINX Service Mesh API. - -```txt -Usage: - nginx-meshctl status [flags] - -Flags: - -h, --help help for status - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Supportpkg - -Create an NGINX Service Mesh support package. - -```txt -Usage: - nginx-meshctl supportpkg [flags] - -Flags: - --disable-sidecar-logs disable the collection of sidecar logs - -h, --help help for supportpkg - -o, --output string output directory for supportpkg tarball (default "$PWD") - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` - -## Top - -Display traffic statistics. -Top provides information about the incoming and outgoing requests to and from a resource type or name. -Supported resource types are: Pods, Deployments, StatefulSets, DaemonSets, and Namespaces. - -```txt -Usage: - nginx-meshctl top [resource-type/resource] [flags] - -Flags: - -h, --help help for top - -n, --namespace string namespace where the resource(s) resides (default "default") - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") -``` - -### Top Examples - -- Display traffic statistics for all Deployments: - - `nginx-meshctl top` - -- Display traffic statistics for all Pods: - - `nginx-meshctl top pods` - -- Display traffic statistics for Deployment "my-app": - - `nginx-meshctl top deployments/my-app` - -## Upgrade - -Upgrade NGINX Service Mesh to the latest version. -This command removes the existing NGINX Service Mesh while preserving user configuration data. -The latest version of NGINX Service Mesh is then deployed using that data. - -```txt -Usage: - nginx-meshctl upgrade [flags] - -Flags: - -h, --help help for upgrade - -y, --yes answer yes for confirmation of upgrade - -t, --timeout duration timeout when waiting for an upgrade to finish (default 5m0s) - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") -``` - -## Version - -Display NGINX Service Mesh version. -Will contact Mesh API Server for version and timeout if unable to connect. - -```txt -Usage: - nginx-meshctl version [flags] - -Flags: - -h, --help help for version - -Global Flags: - -k, --kubeconfig string path to kubectl config file (default "/Users//.kube/config") - -n, --namespace string NGINX Service Mesh control plane namespace (default "nginx-mesh") - -t, --timeout duration timeout when communicating with NGINX Service Mesh (default 5s) -``` diff --git a/content/mesh/reference/permissions.md b/content/mesh/reference/permissions.md deleted file mode 100644 index b4e9af739..000000000 --- a/content/mesh/reference/permissions.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: NGINX Service Mesh Permissions -weight: 100 -draft: false -toc: true -nd-docs: DOCS-883 -type: -- reference ---- - -## Init Container -The init container is a privileged container that runs as root. In addition to the container running with root privileges on the host system, it also has weaker sandboxing. The init container needs this level of access in order to manipulate `iptables` and `eBPF` on the host. - -### Capabilities -Kubernetes allows pods to be given capabilities that extend their permissions and allow them to perform restricted tasks. These capabilities are modelled after the standard Linux capabilities (`man capabilities`). The sidecar init container uses the following capabilities: - -- **NET_ADMIN**: (`CAP_NET_ADMIN`) This capability provides the ability to administer the IP firewall and modify the routing tables. - -- **NET_RAW**: (`CAP_NET_RAW`) This capability provides the ability to open and use RAW sockets. - -- **SYS_RESOURCE**: (`CAP_SYS_RESOURCE`) Used by the init container to lock memory for BPF resources. - -- **SYS_ADMIN**: (`CAP_SYS_ADMIN`) This capability provides access to BPF operations, among other things. - -### Tips and tricks -#### Compatibility concerns around init container privilege level -Some services like F5 NGINX Ingress Controller and Certificate Manager will fail to deploy when auto-injected with the NGINX Service Mesh init container. This may be because they specify `runAsNonRoot` in their security policies, which prevents the init container from launching. This issue can be avoided by containing these services in their own namespaces where auto-injection is disabled. - -## Sidecar Proxy -The sidecar container cannot escalate privilege and is not a privileged container. The sidecar container runs as user 2102 once the init container has completed. - -## Additional Containers -All other containers in NGINX Service Mesh use `securityContext: {}`. diff --git a/content/mesh/releases/_index.md b/content/mesh/releases/_index.md deleted file mode 100644 index e92cc3699..000000000 --- a/content/mesh/releases/_index.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Releases -description: Stay up-to-date with the latest F5 NGINX Service Mesh release -url: /nginx-service-mesh/releases/ ---- diff --git a/content/mesh/releases/oss-dependencies/index.md b/content/mesh/releases/oss-dependencies/index.md deleted file mode 100644 index 4cb7184be..000000000 --- a/content/mesh/releases/oss-dependencies/index.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Open Source Licenses Addendum -url: /nginx-service-mesh/releases/oss-dependencies/ ---- - -{{% table %}} -| Package | License(s) | Added In | -|---------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|----------| -| [nginx-opentracing](https://github.com/opentracing-contrib/nginx-opentracing) | [Apache-2.0](https://github.com/opentracing-contrib/nginx-opentracing/blob/master/LICENSE) | 0.5.0 | -| [opentracing-cpp](https://github.com/opentracing/opentracing-cpp) | [Apache-2.0](https://github.com/opentracing/opentracing-cpp/blob/master/LICENSE) | 0.5.0 | -| [expected](https://github.com/opentracing/opentracing-cpp/tree/master/3rd_party/include/opentracing/expected) | [MIT](https://github.com/opentracing/opentracing-cpp/blob/master/3rd_party/include/opentracing/expected/LICENSE) | 0.5.0 | -| [zipkin-cpp-opentracing](https://github.com/rnburn/zipkin-cpp-opentracing) | [Apache-2.0](https://github.com/rnburn/zipkin-cpp-opentracing/blob/master/LICENSE) | 0.5.0 | -| [randutils](https://github.com/rnburn/zipkin-cpp-opentracing/tree/master/3rd_party/include/zipkin/randutils) | [MIT](https://github.com/rnburn/zipkin-cpp-opentracing/blob/master/3rd_party/include/zipkin/randutils/LICENSE) | 0.5.0 | -| [rapidjson](https://github.com/rnburn/zipkin-cpp-opentracing/tree/master/3rd_party/include/zipkin/rapidjson) | [MIT](https://github.com/rnburn/zipkin-cpp-opentracing/blob/master/3rd_party/include/zipkin/rapidjson/license.txt) | 0.5.0 | -| [jaeger-client-cpp](https://github.com/jaegertracing/jaeger-client-cpp) | [Apache-2.0](https://github.com/jaegertracing/jaeger-client-cpp/blob/master/LICENSE) | 0.5.0 | -| [jaeger-idl](https://github.com/jaegertracing/jaeger-idl/tree/95d85500d409d56c8a3be2ede56c42a2ed984a63) | [Apache-2.0](https://github.com/jaegertracing/jaeger-idl/blob/95d85500d409d56c8a3be2ede56c42a2ed984a63/LICENSE) | 0.5.0 | -| [variant](https://github.com/opentracing/opentracing-cpp/tree/master/3rd_party/include/opentracing/variant) | [BSD-3-clause](https://github.com/opentracing/opentracing-cpp/blob/master/3rd_party/include/opentracing/variant/LICENSE) | 0.5.0 | -| [dd-opentracing-cpp](https://github.com/DataDog/dd-opentracing-cpp) | [Apache-2.0](https://github.com/DataDog/dd-opentracing-cpp/blob/master/LICENSE) | 0.9.0 | -| [msgpack](https://github.com/msgpack/msgpack-c) | [Boost 1.0](https://github.com/msgpack/msgpack-c/blob/master/LICENSE_1_0.txt) | 0.9.0 | -| [json](https://github.com/nlohmann/json) | [MIT](https://github.com/nlohmann/json/blob/develop/LICENSE.MIT) | 0.9.0 | -| [libcurl](https://github.com/curl/curl) | [curl](https://github.com/curl/curl/blob/master/COPYING) | 0.9.0 | -| [zlib](https://github.com/madler/zlib) | [zlib](http://zlib.net/zlib_license.html) | 0.9.0 | -| [opentelemetry-cpp-contrib](https://github.com/open-telemetry/opentelemetry-cpp-contrib) | [Apache-2.0](https://github.com/open-telemetry/opentelemetry-cpp-contrib/blob/main/LICENSE) | 1.4.0 | -| [opentelemetry-cpp](https://github.com/open-telemetry/opentelemetry-cpp) | [Apache-2.0](https://github.com/open-telemetry/opentelemetry-cpp/blob/main/LICENSE) | 1.4.0 | -| [grpc](https://github.com/grpc/grpc) | [Apache-2.0](https://github.com/grpc/grpc/blob/master/LICENSE) | 1.4.0 | -| [abseil-cpp](https://github.com/abseil/abseil-cpp) | [Apache-2.0](https://github.com/abseil/abseil-cpp/blob/master/LICENSE) | 1.4.0 | -| [data-plane-api](https://github.com/envoyproxy/data-plane-api) | [Apache-2.0](https://github.com/envoyproxy/data-plane-api/blob/main/LICENSE) | 1.4.0 | -| [protoc-gen-validate](https://github.com/envoyproxy/protoc-gen-validate) | [Apache-2.0](https://github.com/envoyproxy/protoc-gen-validate/blob/main/LICENSE) | 1.4.0 | -| [googleapis](https://github.com/googleapis/googleapis) | [Apache-2.0](https://github.com/googleapis/googleapis/blob/master/LICENSE) | 1.4.0 | -| [xds](https://github.com/cncf/xds) | [Apache-2.0](https://github.com/cncf/xds/blob/main/LICENSE) | 1.4.0 | -| [opencensus-proto](https://github.com/census-instrumentation/opencensus-proto) | [Apache-2.0](https://github.com/census-instrumentation/opencensus-proto/blob/master/LICENSE) | 1.4.0 | -| [opentelemetry-proto](https://github.com/open-telemetry/opentelemetry-proto) | [Apache-2.0](https://github.com/open-telemetry/opentelemetry-proto/blob/main/LICENSE) | 1.4.0 | -| [re2](https://github.com/google/re2) | [BSD-3-clause](https://github.com/google/re2/blob/main/LICENSE) | 1.4.0 | -| [c-ares](https://github.com/c-ares/c-ares) | [c-ares](https://github.com/c-ares/c-ares/blob/main/LICENSE.md) | 1.4.0 | -| [boringssl](https://github.com/google/boringssl) | [boringssl](https://github.com/google/boringssl/blob/master/LICENSE) | 1.4.0 | -| [protobuf](https://github.com/protocolbuffers/protobuf) | [protobuf](https://github.com/protocolbuffers/protobuf/blob/master/LICENSE) | 1.4.0 | -| [libuv](https://github.com/libuv/libuv) | [libuv](https://github.com/libuv/libuv/blob/v1.x/LICENSE) | 1.4.0 | - -{{% /table %}} diff --git a/content/mesh/releases/release-notes-0.5.0.md b/content/mesh/releases/release-notes-0.5.0.md deleted file mode 100644 index b1428bf96..000000000 --- a/content/mesh/releases/release-notes-0.5.0.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Release Notes 0.5.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -500 -nd-docs: DOCS-705 -type: -- reference ---- - -## NGINX Service Mesh Version 0.5.0 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 0.5.0, in the following categories: - -- [NGINX Service Mesh Version 0.5.0](#nginx-service-mesh-version-050) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-0.5.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 0.5.0 includes the following updates: - - -- Support for stand-alone (kubeadm) clusters, including VSphere VM clusters. - -- Auto-detection of Persistent Volumes (PVs) for SPIRE. - -- Image names streamlined (affects deployment scripts). - -- Support for non-production environments without persistent volume (not recommended outside of testing) - - - -### Resolved Issues - -This release includes fixes for the following issues. - - - -- *NGINX Service Mesh may exit during network outages (13295)* - - - -- *Inject does not fully support multiple resources in a single JSON file (13531)* - - - -- *NGINX Service Mesh annotations are validated inconsistently (13927)* - - - -- *On using untested mesh tools or environments (15126)* - - - -- *Running nginx-meshctl returns the error "unable to get mesh config" (15416)* - - - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**NGINX Service Mesh may drop metrics (12282)**: -
- Prometheus and Service Mesh Interface (SMI) metrics may fail to return metrics values in rare cases. - -
- Workaround: -

- - If you notice that metrics aren't returned, you should retry the request as the system will self-recover. diff --git a/content/mesh/releases/release-notes-0.6.0.md b/content/mesh/releases/release-notes-0.6.0.md deleted file mode 100644 index 015ae0ef6..000000000 --- a/content/mesh/releases/release-notes-0.6.0.md +++ /dev/null @@ -1,315 +0,0 @@ ---- -title: Release Notes 0.6.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -600 -nd-docs: DOCS-706 -type: -- reference ---- - -## NGINX Service Mesh Version 0.6.0 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 0.6.0, in the following categories: - -- [NGINX Service Mesh Version 0.6.0](#nginx-service-mesh-version-060) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-0.6.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 0.6.0 includes the following updates: - - -- None - - -### Resolved Issues - -This release includes fixes for the following issues. - - - -- *Maximum number of pods and services (10492)* - - - -- *Mixed resource types metrics limitation (11168)* - - - -- *Terminating `nginx-meshctl` prematurely during `deploy` can prevent proper cleanup (16916)* - - - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**NGINX Service Mesh does not support apps/v1beta1 (10258)**: -
- - When injecting configurations -- `nginx-meshctl inject` -- using `apiVersion: apps/v1beta1`, the sidecar injection fails silently, and no new configuration is written. - -
- Workaround: -

- - The `apps/v1beta1` API version is not supported. Retry the injection using the proper version: `apps/v1`. -

- - -**Command line tool may timeout connecting to control plane (10685)**: -
- - The `nginx-meshctl` tool requires the NGINX Service Mesh control plane to be available and ready for operations to succeed. During startup or network outages, connections may time out and fail. - -
- Workaround: -

- - Wait until all services report that they're ready and you can connect to the cluster, then retry the command. -

- - -**Non-injected pods and services mishandled as fallback services (14731)**: -
- - We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - - Instead, we recommend using injected pods and services for service mesh injected workloads. - -
- Workaround: -

- - If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. -

- - -**Kubernetes Liveness and Readiness HTTP Requests fail when mtls-mode is strict (17038)**: -
- - Kubernetes Liveness and Readiness HTTP Requests fail when `mtls-mode` is `strict`. - -
- Workaround: -

- - 1. Use commands instead of HTTP requests when defining liveness and readiness probes. - 1. Deploy NGINX Service Mesh with a permissive mtls mode. A permissive mode allows the liveness and readiness HTTP requests to be proxied to the application over plaintext. - 1. Create dedicated ports for the liveness and readiness probes in your application and add these ports to the `ignore-incoming-ports` during injection. Dedicated ports allow the HTTP requests to hit the application directly without being proxied. -

- - -**Warning messages emitted when traffic access policies applied (17117)**: -
- - After successfully configuring traffic access polices (TrafficTarget, HTTPRouteGroup, TCPRoute), warning messages may be emitted to the `nginx-mesh-sidecar` logs. - - For example: - - ```console - 2020/09/24 01:03:14 could not parse syslog message: nginx could not connect to upstream - ``` - - This warning message is harmless and can safely be ignored. The message does not indicate an operational problem. -

- - -**HTTPRouteGroups are not validated for proper input (17153)**: -
- - HTTPRouteGroups are not validated for proper input. - - 1. You can have multiple `matches` with the same name, leading to undefined behavior. - 1. You can specify multiple `pathRegex` statements, also leading to undefined behavior. - -
- Workaround: -

- - When creating `HTTPRouteGroups`, ensure there are no duplicate `matches` with the same name or duplicate `pathRegex` statements. -

- - -**Traffic sent to backend service if root service and destination backend services don't match (17156)**: -
- - When configuring Traffic Splitting, the port on the root service and the port on every destination backend service must match. Backend services with a mismatching port should not be sent traffic. With this release, the mismatch case is not caught, and traffic is sent to that backend service. - -
- Workaround: -

- - Ensure ports on the root service and destination backend service match. - - - -**NGINX Service Mesh remove command may fail (17160)**: -
- - In some cases, the NGINX Service Mesh `remove` command may fail for unexpected reasons due to environmental, network, or timeout errors. If the `remove` command fails continually, manual intervention may be necessary. - -
- Workaround: -

- - When troubleshooting, first verify that the command is run correctly with the correct arguments and that the target namespace exists. - - If you are running the command correctly and the target namespace exists and is not empty -- that is to say, the NGINX Service Mesh Deployments, Pods, Services, and so on, have been deployed -- you may need to remove the NGINX Service Mesh namespace and start over: - - To remove the NGINX Service mesh namespace and start over: - - 1. Run the following command to delete the nginx-mesh namespace: - - ```bash - kubectl delete namespace nginx-mesh - ``` - - > **Note**: This command should appear to stall. You can run `kubectl get namespaces` in a separate terminal to view the status, which should display as "Terminating." - - 1. In a separate terminal, list and set a variable for all `spiffeid` resources: - - ```bash - SPIFFEIDS=$(kubectl -n get spiffeids | grep -v NAME | awk '{print $1}') - ``` - - 1. Remove `finalizers` from each `spiffeid` resource: - - ```bash - kubectl patch spiffeid $SPIFFEIDS --type='merge' -p '{"metadata":{"finalizers":null}}' -n - ``` - - After step 3 completes, the command from step 1 should also complete, and the namespace should be removed. - - 1. Run `nginx-meshctl deploy` and allow the operation to finish. -

- - -**Improper destination and source namespace defaults for TrafficTarget (17234)**: -
- - If the TrafficTarget `.spec` does not explicitly set namespaces, access control may be applied to unexpected resources. The TrafficTarget `.spec.destination.namespace` and `.spec.sources[*].namespace` will default to the `default` namespace regardless of the namespace of the TrafficTarget resource. - -
- Workaround: -

- - When defining TrafficTarget resources, always explicitly set the destination and source namespaces. - - For example: - - ```yaml - kind: TrafficTarget - metadata: - name: example-traffictarget - namespace: example-namespace - spec: - destination: - kind: ServiceAccount - name: example-destination-sa - namespace: example-namespace - sources: - - kind: ServiceAccount - name: example-source-sa - namespace: example-namespace - ``` - -

- - -**Removing Mesh could delete clusterrole/binding for custom Prometheus (17302)**: -
- - When removing Mesh, if a custom Prometheus deployment has a clusterrole/binding named "prometheus", the clusterrole/binding is deleted. - -
- Workaround: -

- - Avoid using "prometheus" as a name for the clusterrole/binding for custom Prometheus deployments. -

- - -**TrafficSplits cannot route traffic based on the value of the host header (17304)**: -
- - A TrafficSplit can list an HTTPRouteGroup in `spec.Matches`. If this HTTPRouteGroup contains a host header in the header filters, the TrafficSplit will not work. The root service of the TrafficSplit will handle the traffic. -

- - -**Namespaces stuck deleting after removing NGINX Service Mesh (17313)**: -
- - After attempting to removing the NGINX Service Mesh, namespaces may get stuck deleting. Resource `finalizers` can deadlock a namespace when the owning controller is unavailable. Spire, and in turn NGINX Service Mesh, use `finalizers` in the `spiffe.spire.io` custom resource definitions. If your namespace cannot be deleted or is stuck in the "Terminating" state for a long time, you may need to remove the problematic `finalizers`. - -
- Workaround: -

- - To clear the deadlock by removing `finalizers`, run the following command: - - ```bash - SPIFFEIDS=$(kubectl -n get spiffeids | grep -v NAME | awk '{print $1}') - ``` - - Remove `finalizers` from each `spiffeid` resource: - - ```bash - kubectl patch spiffeid $SPIFFEIDS --type='merge' -p '{"metadata":{"finalizers":null}}' -n - ``` - -

- - -**nginx-meshctl erroneously shows out of namespace resources (17381)**: -
- - When running `nginx-mestctl top namespace/[namespace]`, resources from outside the requested namespace may appear. This may happen whether or not cross-namespace traffic is occurring. - -
- Workaround: -

- - There is no direct workaround for specific namespace filtering; however, running `nginx-meshctl` and filtering on other supported resources--such as Deployments, Pods, StatefulSets, and DaemonSets--will show proper traffic edges. Cross-referencing between Namespace output and another resource type will demonstrate the correct activity. - - - -**Warning messages may print while deploying the NGINX Service Mesh on EKS (17390)**: -
- - The warning message "Unable to cancel request for \*exec.roundTripper" may print when deploying NGINX Service Mesh on EKS. This warning message does not prevent the mesh from deploying successfully. -

- - - - -### Supported Versions - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NGINX Service Mesh SMI Extensions: - -- Traffic Specs: v1alpha1 diff --git a/content/mesh/releases/release-notes-0.7.0.md b/content/mesh/releases/release-notes-0.7.0.md deleted file mode 100644 index c9707a837..000000000 --- a/content/mesh/releases/release-notes-0.7.0.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Release Notes 0.7.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -700 -nd-docs: DOCS-707 -type: -- reference ---- - -## NGINX Service Mesh Version 0.7.0 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 0.7.0, in the following categories: - -- [NGINX Service Mesh Version 0.7.0](#nginx-service-mesh-version-070) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-0.7.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 0.7.0 includes the following updates: - -- Bug fixes and improvements. - - - -- Changes the behavior of `nginx-meshctl deploy` command. The `--registry-server` argument will now be used for image domain and path-components in conjunction with the `--image-tag` command. If not provided, PodSpecs are configured for local images. - - - -- CircuitBreaker and RateLimit CRDs are moved to the `smi.specs.nginx.com` API group. - - - - -### Resolved Issues - -This release includes fixes for the following issues. You can search by the issue ID to locate the details for an issue. - - - -- *NGINX Service Mesh may drop metrics (12282)* - - - -- *Kubernetes Liveness and Readiness HTTP Requests fail when mtls-mode is strict (17038)* - - - -- *HTTPRouteGroups are not validated for proper input (17153)* - - - -- *Traffic sent to backend service if root service and destination backend services don't match (17156)* - - - -- *Improper destination and source namespace defaults for TrafficTarget (17234)* - - - -- *Removing Mesh could delete clusterrole/binding for custom Prometheus (17302)* - - - -- *TrafficSplits cannot route traffic based on the value of the host header (17304)* - - - -- *nginx-meshctl erroneously shows out of namespace resources (17381)* - - - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**NGINX Service Mesh remove command may fail (17160)**: -
- - In some cases, the NGINX Service Mesh `remove` command may fail for unexpected reasons due to environmental, network, or timeout errors. If the `remove` command fails continually, manual intervention may be necessary. - -{{< call-out "note" >}} -If deploying NGINX Service Mesh failed or you pressed ctrl-C during deployment, make sure to first remove the service mesh using the `remove` command before attempting the below steps -{{< /call-out >}} - -
- Workaround: -

- - When troubleshooting, first verify that the command is run correctly with the correct arguments and that the target namespace exists. - - If you are running the command correctly and the target namespace exists and is not empty -- that is to say, the NGINX Service Mesh Deployments, Pods, Services, and so on, have been deployed -- you may need to remove the NGINX Service Mesh namespace and start over: - - To remove the NGINX Service Mesh namespace and start over: - - 1. Run the following command to delete the NGINX Service Mesh namespace: - - ```bash - kubectl delete namespace - ``` - - > **Note**: This command should appear to stall. You can run `kubectl get namespaces` in a separate terminal to view the status, which should display as "Terminating." - - 1. In a separate terminal, list and patch all Spiffeid resources (use following script): - - ```bash - for ns in $(kubectl get ns | awk '{print $1}' | tail -n +2) - do - if [ $(kubectl get spiffeids -n $ns 2>/dev/null | wc -l) -ne 0 ] - then - kubectl patch spiffeid $(kubectl get spiffeids -n $ns | awk '{print $1}' | tail -n +2) --type='merge' -p '{"metadata":{"finalizers":null}}' -n $ns - fi - done - ``` - - After step 2 completes, the command from step 1 should also complete, and the namespace should be removed. - - 1. Run `nginx-meshctl deploy` and allow the operation to finish. -

- - -**Warning messages may print while deploying the NGINX Service Mesh on EKS (17390)**: -
- - The warning message "Unable to cancel request for \*exec.roundTripper" may print when deploying NGINX Service Mesh on EKS. This warning message does not prevent the mesh from deploying successfully. -

- - -**Deployment may fail if NGINX Service Mesh is already installed (19351)**: -
- If NGINX Service Mesh is installed in a namespace other than the default (nginx-mesh) and the deploy command is run without specifying the different namespace, the deployment may fail to clean up all of the NGINX Service Mesh resources. - -
- Workaround: -

- - Always provide the `-n ` or `--namespace ` flag with every CLI command. Additionally, we recommend that you remove NGINX Service Mesh using the `nginx-meshctl remove` command before running `deploy`. -

- - -**Cannot disable Prometheus scraping of the NGINX Ingress Controller (19375)**: -
- - The Prometheus server deployed by NGINX Service Mesh scrapes metrics from all containers with the name `nginx-plus-ingress`. Omitting the `prometheus.io/scrape` annotation or explicitly setting the annotation to `false` does not change this behavior. - -
- Workaround: -

- - If you do not want Prometheus to scrape metrics from your NGINX Ingress Controller pods, you can change the container name to something other than `nginx-plus-ingress`. -

- - - - -### Supported Versions - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NGINX Service Mesh SMI Extensions: - -- Traffic Specs: v1alpha1 diff --git a/content/mesh/releases/release-notes-0.8.0.md b/content/mesh/releases/release-notes-0.8.0.md deleted file mode 100644 index e42c5934c..000000000 --- a/content/mesh/releases/release-notes-0.8.0.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -title: Release Notes 0.8.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -800 -nd-docs: DOCS-708 -type: -- reference ---- - -## NGINX Service Mesh Version 0.8.0 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 0.8.0, in the following categories: - -- [NGINX Service Mesh Version 0.8.0](#nginx-service-mesh-version-080) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-0.8.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 0.8.0 includes the following updates: - -- Bug fixes and improvements -- Updated NGINX Service Mesh sidecars to NGINX Plus R23 -- Tested and documented support for the following platforms/tools: - - minikube - - kind - - kubespray -- Support for new load balancing methods: - - least_time - - random two least_time -- NGINX Plus KIC daemon set support for ingress - - - -### Resolved Issues - -This release includes fixes for the following issues. You can search by the issue ID to locate the details for an issue. - - - -- *Command line tool may timeout connecting to control plane (10685)* - - - -- *Namespaces stuck deleting after removing NGINX Service Mesh (17313)* - - - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**Non-injected pods and services mishandled as fallback services (14731)**: -
- - We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - -Instead, we recommend using injected pods and services for service mesh injected workloads. - -
- Workaround: -

- - If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. -

- - -**Warning messages emitted when traffic access policies applied (17117)**: -
- - After successfully configuring traffic access polices (TrafficTarget, HTTPRouteGroup, TCPRoute), warning messages may be emitted to the `nginx-mesh-sidecar` logs. - -For example: - -```plaintext -2020/09/24 01:03:14 could not parse syslog message: nginx could not connect to upstream -``` - -This warning message is harmless and can safely be ignored. The message does not indicate an operational problem. -

- - -**"Could not start API server" error is logged when Mesh API is shut down normally (17670)**: -
- - When the nginx-mesh-api Pod exits normally, the system may log the error "Could not start API server" and an error string. If the process is signaled, the signal value is lost and isn't printed correctly. - -If the error shows "http: Server closed," the nginx-mesh-api Pod has properly exited, and this message can be disregarded. - -Other legitimate error cases correctly show the error encountered, but this may be well after startup and proper operation. -

- - -**Rejected configurations return generic HTTP status codes (18101)**: -
- - **The NGINX Service Mesh APIs are a beta feature.** Beta features are provided for you to try out before they are released. You shouldn't use beta features for production purposes. - -The NGINX Service Mesh APIs validate input for configured resources. These validations may reject the configuration for various reasons, including non-sanitized input, duplicates, conflicts, and so on When these configurations are rejected, a 500 Internal Server error is generated and returned to the client. - -
- Workaround: -

- - When configuring NGINX Service Mesh resources, do not use the NGINX Service Mesh APIs for production-grade releases if fine-grained error notifications are required. Each feature has Kubernetes API correlates that work according to the Kubernetes API Server semantics and constraints. All features are supported via Kubernetes. -

- - -**Deployment may fail if NGINX Service Mesh is already installed (19351)**: -
- - If NGINX Service Mesh is installed in a namespace other than the default (nginx-mesh) and the deploy command is run without specifying the different namespace, the deployment may fail to clean up all of the NGINX Service Mesh resources. - -
- Workaround: -

- - Always provide the `-n ` or `--namespace ` flag with every CLI command. Additionally, we recommend that you remove NGINX Service Mesh using the `nginx-meshctl remove` command before running `deploy`. -

- -**Cannot disable Prometheus scraping of the NGINX Ingress Controller (19375)**: -
- - The Prometheus server deployed by NGINX Service Mesh scrapes metrics from all containers with the name `nginx-plus-ingress`. Omitting the `prometheus.io/scrape` annotation or explicitly setting the annotation to `false` does not change this behavior. - -
- Workaround: -

- - If you do not want Prometheus to scrape metrics from your NGINX Ingress Controller pods, you can change the container name to something other than `nginx-plus-ingress`. -

- - -**Pods fail to deploy if invalid Jaeger tracing address is set (19469)**: -
- If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. - -
- Workaround: -

- - If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. -

- - -**Duplicate targetPorts in a Service are disregarded (20566)**: -
- - NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
- Workaround: -

- - No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. -

- - -**NGINX Service Mesh deployment may fail with TLS errors (20902)**: -
- - After deploying NGINX Service Mesh, the logs may show repeated TLS errors similar to the following: - -From the smi-metrics Pod logs: - -```bash -echo: http: TLS handshake error from :: remote error: tls: bad certificate -``` - -From the Kubernetes api-server log: - -```bash -E0105 10:03:45.159812 1 controller.go:116] loading OpenAPI spec for "v1alpha1.metrics.smi-spec.io" failed with: failed to retrieve openAPI spec, http error: ResponseCode: 503, Body: error trying to reach service: x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "NGINX") -``` - -A race condition may occur during deployment where the Spire server fails to communicate its certificate authority (CA) to dependent resources. Without the CA, these subsystems cannot operate correctly: metrics aggregation layer, injection, and validation. - -
- Workaround: -

- - You must [re-deploy NGINX Service Mesh](https://docs.nginx.com/nginx-service-mesh/reference/nginx-meshctl/): - -```bash -nginx-meshctl remove -nginx-meshctl deploy ... -``` - -

- - - - -### Supported Versions - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NGINX Service Mesh SMI Extensions: - -- Traffic Specs: v1alpha1 - - - diff --git a/content/mesh/releases/release-notes-0.9.0.md b/content/mesh/releases/release-notes-0.9.0.md deleted file mode 100644 index a9044eecd..000000000 --- a/content/mesh/releases/release-notes-0.9.0.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -title: Release Notes 0.9.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -900 -nd-docs: DOCS-709 -type: -- reference ---- - -## NGINX Service Mesh Version 0.9.0 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 0.9.0, in the following categories: - -- [NGINX Service Mesh Version 0.9.0](#nginx-service-mesh-version-090) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-0.9.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 0.9.0 includes the following updates: - -- Support for Datadog OpenTracing end-point -- Updates to Grafana dashboard metrics -- Spire support for AWS Private CA -- Greater support and control over pod egress traffic policies -- Access control default behavior can now be set at install -- Support for NGINX Plus Ingress Controller v1.10 for ingress and egress traffic management -- New tutorials and updated docs -- Bug fixes and improvements - - - -### Resolved Issues - -This release includes fixes for the following issues. You can search by the issue ID to locate the details for an issue. - - - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- -**Non-injected pods and services mishandled as fallback services (14731)** -
- - We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - -Instead, we recommend using injected pods and services for service mesh injected workloads. - -
- Workaround: -

- If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. -

- - -**Warning messages emitted when traffic access policies applied(17117)** -
- - After successfully configuring traffic access polices (TrafficTarget, HTTPRouteGroup, TCPRoute), warning messages may be emitted to the `nginx-mesh-sidecar` logs. - -For example: - -```plaintext -2020/09/24 01:03:14 could not parse syslog message: nginx could not connect to upstream -``` - -This warning message is harmless and can safely be ignored. The message does not indicate an operational problem. -

- - -**"Could not start API server" error is logged when Mesh API is shut down normally (17670)** -
- - When the nginx-mesh-api Pod exits normally, the system may log the error "Could not start API server" and an error string. If the process is signaled, the signal value is lost and isn't printed correctly. - -If the error shows "http: Server closed," the nginx-mesh-api Pod has properly exited, and this message can be disregarded. - -Other legitimate error cases correctly show the error encountered, but this may be well after startup and proper operation. -

- - -**Rejected configurations return generic HTTP status codes (18101)** -
- - **The NGINX Service Mesh APIs are a beta feature.** Beta features are provided for you to try out before they are released. You shouldn't use beta features for production purposes. - -The NGINX Service Mesh APIs validate input for configured resources. These validations may reject the configuration for various reasons, including non-sanitized input, duplicates, conflicts, and so on When these configurations are rejected, a 500 Internal Server error is generated and returned to the client. - -
- Workaround: -

- - When configuring NGINX Service Mesh resources, do not use the NGINX Service Mesh APIs for production-grade releases if fine-grained error notifications are required. Each feature has Kubernetes API correlates that work according to the Kubernetes API Server semantics and constraints. All features are supported via Kubernetes. -

- - -**Deployment may fail if NGINX Service Mesh is already installed (19351)** -
- - If NGINX Service Mesh is installed in a namespace other than the default (nginx-mesh) and the deploy command is run without specifying the different namespace, the deployment may fail to clean up all of the NGINX Service Mesh resources. - -
- Workaround: -

- - Always provide the `-n ` or `--namespace ` flag with every CLI command. Additionally, we recommend that you remove NGINX Service Mesh using the `nginx-meshctl remove` command before running `deploy`. -

- - -**Cannot disable Prometheus scraping of the NGINX Ingress Controller (19375)** -
- - The Prometheus server deployed by NGINX Service Mesh scrapes metrics from all containers with the name `nginx-plus-ingress`. Omitting the `prometheus.io/scrape` annotation or explicitly setting the annotation to `false` does not change this behavior. - -
- Workaround: -

- - If you do not want Prometheus to scrape metrics from your NGINX Ingress Controller pods, you can change the container name to something other than `nginx-plus-ingress`. -

- -**Pods fail to deploy if invalid Jaeger tracing address is set (19469)** -
- - If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. - -
- Workaround: -

- - If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. -

- - -**Duplicate targetPorts in a Service are disregarded (20566)** -
- - NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
- Workaround: -

- - No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. -

- -**NGINX Service Mesh deployment may fail with TLS errors (20902)** -
- - After deploying NGINX Service Mesh, the logs may show repeated TLS errors similar to the following: - -From the smi-metrics Pod logs: - -```bash -echo: http: TLS handshake error from :: remote error: tls: bad certificate -``` - -From the Kubernetes api-server log: - -```bash -E0105 10:03:45.159812 1 controller.go:116] loading OpenAPI spec for "v1alpha1.metrics.smi-spec.io" failed with: failed to retrieve openAPI spec, http error: ResponseCode: 503, Body: error trying to reach service: x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "NGINX") -``` - -A race condition may occur during deployment where the Spire server fails to communicate its certificate authority (CA) to dependent resources. Without the CA, these subsystems cannot operate correctly: metrics aggregation layer, injection, and validation. - -
- Workaround: -

- - You must [re-deploy NGINX Service Mesh](https://docs.nginx.com/nginx-service-mesh/reference/nginx-meshctl/): - -```bash -nginx-meshctl remove -nginx-meshctl deploy ... -``` - -

- - -**The `nginx-meshctl remove` command may not list all resources that require a restart (22710)** -
- - The `nginx-meshctl remove` command may fail to list all the resources that require a restart if there are different resources in the same namespace with the same name (for example, DaemonSets and Deployments). Every resource will not be listed. Only the namespace/name will be listed once. - -
- Workaround: -

- - During the NGINX Service Mesh removal process, a list of resources of injected resources is compiled. These resources require a restart to remove the sidecar containers; this task is left for the administrator to complete because traffic flow will be affected. - -The compiled list may not be entirely accurate. If resources of different kinds share the same name and namespace, the namespace/name pair will be printed only once. When removing NGINX Service Mesh, be sure to query the Kubernetes API for various supported resource kinds to completely remove the superfluous sidecars. -

- -**Circuit Breaker functionality is incompatible with load balancing algorithm "random" (22718)** -
- - Circuit Breaker functionality is incompatible with the "random" load balancing algorithm. The two configurations interfere with each other and lead to errors. If Circuit Breaker resources exist in your environment, you cannot use the global load balancing algorithm "random" or an annotation for specific Services. The opposite is also true: if using the "random" algorithm, you cannot create Circuit Breaker resources. - -
- Workaround: -

- - If Circuit Breakers (API Version: specs.smi.nginx.com/v1alpha1 Kind: CircuitBreaker) are configured, the load balancing algorithm "random" cannot be used. Combining Circuit Breaker with "random" load balancing will result in errors and cause the sidecar containers to exit in error. Data flow will be detrimentally affected. - -There is no workaround at this time, but the configuration can be changed dynamically. If another load balancing algorithm is set, the sidecars will reset and traffic will return to normal operations. - -To fix the issue, take one or more of the following actions: - -- All load balancing annotations (config.nsm.nginx.com/lb-method) should be removed or updated to another supported algorithm (see [Configuration Options for NGINX Service Mesh]({{< ref "/mesh/get-started/install/configuration.md" >}})). -- The global load balancing algorithm should be set to another supported algorithm (see [Configuration Options for NGINX Service Mesh]({{< ref "/mesh/get-started/install/configuration.md" >}})) . -

- -**Kubernetes reports warnings on versions >=1.19 (22721)** -
- - NGINX Service Mesh dependencies use older API versions that newer Kubernetes versions issue a deprecation warning for. Until these resource versions are updated, an NGINX Service Mesh installation will issue the following warnings: - - -```plaintext -W0303 00:12:03.484737 44320 warnings.go:70] apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition -``` - -```plaintext -W0303 00:12:04.996104 44320 warnings.go:70] admissionregistration.k8s.io/v1beta1 ValidatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration -``` - -
- Workaround: -

- - These are deprecation warnings. The resources are supported but will not be in an upcoming release. There is nothing that you need to do. -

- - -**`nginx-meshctl version` command may display errors (22797)** -
- - The `nginx-meshctl version` command may display errors due to a variety of connectivity issues. The command always reports its own version while also attempting to gather the version of other mesh assets. If the command cannot contact a running cluster or use a valid kubeconfig, it will print out internal conditions. - -
- Workaround: -

- - You can safely ignore these errors. The nginx-meshctl version command should gracefully ignore assets it can't discover. The command displays its version and then conducts a best-effort discovery of other mesh assets. - -If you are receiving this message, be sure to: - -- verify your kubeconfig location, and if non-default, pass that in with an argument -- verify your kubeconfig context is set to a valid and running cluster -- verify that you have installed NGINX Service Mesh and the Pods are running in your chosen namespace -

- - -**Pods with HTTPGet health probes may not start if manually injected (22861)** -
- - Kubernetes liveness, readiness, and startup HTTP/S health probes are rewritten at injection time. If an HTTP probe does not specify the scheme of the request, and the Pod is manually injected, the probe is rewritten without a scheme, and the Pod will fail to start. - -
- Workaround: -

- - Specify a scheme when defining HTTPGet health probes. -

- - - - -### Supported Versions - -Supported Kubernetes Versions - -NGINX Service Mesh has been verified to run on the following Kubernetes versions: - -- Kubernetes 1.16-1.20 - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NGINX Service Mesh SMI Extensions: - -- Traffic Specs: v1alpha1 - - - - diff --git a/content/mesh/releases/release-notes-0.9.1.md b/content/mesh/releases/release-notes-0.9.1.md deleted file mode 100644 index c16f5f20e..000000000 --- a/content/mesh/releases/release-notes-0.9.1.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Release Notes 0.9.1 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -901 -nd-docs: DOCS-710 -type: -- reference ---- - -## NGINX Service Mesh Version 0.9.1 - - - -This hotfix release resolves an issue affecting version 0.9.0 described below. - -**Deploying NGINX Service Mesh v0.9.0 fails when using private registry credentials (23236)**: - - NGINX Service Mesh allows containers to be pulled from private Docker registries. If you're using a Docker registry that requires authentication, NGINX Service Mesh v0.9.0 will fail to start. A deploy message and error similar to the following is displayed: - - ```plaintext - Deploying NGINX Service Mesh Control Plane in namespace ""... - Created namespace "nginx-mesh". - Created SpiffeID CRD. - Waiting for SPIRE to be running...done. - Deployed Spire. - Deployed NATS server. - Created traffic policy CRDs. - Deployed Mesh API. - Deployed Metrics API Server. - Deployed Prometheus Server nginx-mesh/prometheus. - Deployed Grafana nginx-mesh/grafana. - Deployed tracing server nginx-mesh/zipkin. - All resources created. Testing the connection to the Service Mesh API Server... - Connection to NGINX Service Mesh API Server failed. - Check the logs of the nginx-mesh-api container in namespace nginx-mesh for more details. - ``` - - Run `kubectl -n get pods` (note: always provide the namespace chosen when running the `deploy` command) to show the `nginx-smi-metrics` Pod. It will display as not ready and a status other than `Running`. - - **Workaround:** - - If you're upgrading, make sure to preserve the configurations saved in the previous upgrade process. - -- Remove the failed services: - - ```bash - nginx-meshctl -n remove - ``` - -- If this occurred during an upgrade and immediate restoration of service is required before download of the 0.9.1 images, re-deploy with your prior version images. Otherwise move to next step. - - ```bash - nginx-meshctl -n deploy --registry-server --image-tag - ``` - -- Download updated binaries from the [F5 Downloads](https://downloads.f5.com) site. - -- Restart the upgrade/deploy process using `--image-tag 0.9.1`. diff --git a/content/mesh/releases/release-notes-1.0.0.md b/content/mesh/releases/release-notes-1.0.0.md deleted file mode 100644 index c6b7d211c..000000000 --- a/content/mesh/releases/release-notes-1.0.0.md +++ /dev/null @@ -1,327 +0,0 @@ ---- -title: Release Notes 1.0.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1000 -nd-docs: DOCS-711 -type: -- reference ---- - -## NGINX Service Mesh Version 1.0.0 - -04 May 2021 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.0.0, in the following categories: - -- [NGINX Service Mesh Version 1.0.0](#nginx-service-mesh-version-100) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.0.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 1.0.0 includes the following updates: - -**Bug fixes and improvements** -
- -- Platform verification and validation for 1.0 GA production release -- Support for the new NGINX container registry: docker-registry.nginx.com -- Added secure message transport for NATS messaging -- Updated Spire 0.12.1 to address CVE-2021-27098 and CVE-2021-27099 -- Default local OpenTracing tracer is now Jaeger -- Documentation overhaul - - - -### Resolved Issues - -This release includes fixes for the following issues. You can search by the issue ID to locate the details for an issue. - - -**Warning messages emitted when traffic access policies applied (17117)** -
- -**Deployment may fail if NGINX Service Mesh is already installed (19351)** -
- -**Cannot disable Prometheus scraping of the NGINX Ingress Controller (19375)** -
- -**The `nginx-meshctl remove` command may not list all resources that require a restart (22710)** -
- -**`nginx-meshctl version` command may display errors (22797)** -
- - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**Non-injected pods and services mishandled as fallback services (14731)**: -
- -We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - -Instead, we recommend using injected pods and services for service mesh injected workloads. - -
- Workaround: -

- -If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. - -Non-injected fallback servers are incompatible with mTLS mode strict. -

- - -**"Could not start API server" error is logged when Mesh API is shut down normally (17670)**: -
- -When the nginx-mesh-api Pod exits normally, the system may log the error "Could not start API server" and an error string. If the process is signaled, the signal value is lost and isn't printed correctly. - -If the error shows "http: Server closed," the nginx-mesh-api Pod has properly exited, and this message can be disregarded. - -Other legitimate error cases correctly show the error encountered, but this may be well after startup and proper operation. - -
- Workaround: -

- -No workaround exists, the exit messages are innocuous. -

- - -**Rejected configurations return generic HTTP status codes (18101)**: -
- -**The NGINX Service Mesh APIs are a beta feature.** Beta features are provided for you to try out before they are released. You shouldn't use beta features for production purposes. - -The NGINX Service Mesh APIs validate input for configured resources. These validations may reject the configuration for various reasons, including non-sanitized input, duplicates, conflicts, and so on When these configurations are rejected, a 500 Internal Server error is generated and returned to the client. - -
- Workaround: -

- -When configuring NGINX Service Mesh resources, do not use the NGINX Service Mesh APIs for production-grade releases if fine-grained error notifications are required. Each feature has Kubernetes API correlates that work according to the Kubernetes API Server semantics and constraints. All features are supported via Kubernetes. -

- - -**Pods fail to deploy if invalid Jaeger tracing address is set (19469)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. - -
- Workaround: -

- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. -

- - -**Duplicate targetPorts in a Service are disregarded (20566)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
- Workaround: -

- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. -

- - -**NGINX Service Mesh deployment may fail with TLS errors (20902)**: -
- -After deploying NGINX Service Mesh, the logs may show repeated TLS errors similar to the following: - -From the smi-metrics Pod logs: - -```bash -echo: http: TLS handshake error from :: remote error: tls: bad certificate -``` - -From the Kubernetes api-server log: - -```bash -E0105 10:03:45.159812 1 controller.go:116] loading OpenAPI spec for "v1alpha1.metrics.smi-spec.io" failed with: failed to retrieve openAPI spec, http error: ResponseCode: 503, Body: error trying to reach service: x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "NGINX") -``` - -A race condition may occur during deployment where the Spire server fails to communicate its certificate authority (CA) to dependent resources. Without the CA, these subsystems cannot operate correctly: metrics aggregation layer, injection, and validation. - -
- Workaround: -

- -You must [re-deploy NGINX Service Mesh](https://docs.nginx.com/nginx-service-mesh/reference/nginx-meshctl/): - -```bash -nginx-meshctl remove -nginx-meshctl deploy ... -``` - -

- - -**Circuit Breaker functionality is incompatible with load balancing algorithm "random" (22718)**: -
- -Circuit Breaker functionality is incompatible with the "random" load balancing algorithm. The two configurations interfere with each other and lead to errors. If Circuit Breaker resources exist in your environment, you cannot use the global load balancing algorithm "random" or an annotation for specific Services. The opposite is also true: if using the "random" algorithm, you cannot create Circuit Breaker resources. - -
- Workaround: -

- -If Circuit Breakers (API Version: specs.smi.nginx.com/v1alpha1 Kind: CircuitBreaker) are configured, the load balancing algorithm "random" cannot be used. Combining Circuit Breaker with "random" load balancing will result in errors and cause the sidecar containers to exit in error. Data flow will be detrimentally affected. - -There is no workaround at this time, but the configuration can be changed dynamically. If another load balancing algorithm is set, the sidecars will reset and traffic will return to normal operations. - -To fix the issue, take one or more of the following actions: - -- All load balancing annotations (config.nsm.nginx.com/lb-method) should be removed or updated to another supported algorithm. -- The global load balancing algorithm should be set to another supported algorithm. -

- - -**Kubernetes reports warnings on versions >=1.19 (22721)**: -
- -NGINX Service Mesh dependencies use older API versions that newer Kubernetes versions issue a deprecation warning for. Until these resource versions are updated, an NGINX Service Mesh installation will issue the following warnings: - -```plaintext -W0303 00:12:03.484737 44320 warnings.go:70] apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition -``` - -```plaintext -W0303 00:12:04.996104 44320 warnings.go:70] admissionregistration.k8s.io/v1beta1 ValidatingWebhookConfiguration is deprecated in v1.16+, unavailable in v1.22+; use admissionregistration.k8s.io/v1 ValidatingWebhookConfiguration -``` - -
- Workaround: -

- -These are deprecation warnings. The resources are supported but will not be in an upcoming release. There is nothing that you need to do. -

- - -**Optional, default visualization dependencies may cause excessive disk usage (23886)**: -
- -NGINX Service Mesh deploys optional metrics, tracing, and visualization services by default. These services are deployed as a convenience for evaluation and demonstration purposes only; these optional deployments should not be used in production. - -NGINX Service Mesh supports a "Bring Your Own" model where individual organizations can manage and tailor third-party dependencies. The optional dependencies -- Prometheus for metrics, Jaeger or Zipkin for tracing, and Grafana for visualization -- should be managed separately for production environments. The default deployments may cause excessive disk usage as their backing stores may be written to Node local storage. In high traffic environments, this may cause DiskPressure warnings and evictions. - -
- Workaround: -

- -To mitigate disk usage issues related to visualization dependencies in high traffic environments, we recommend the following: - -- Do not run high capacity applications with default visualization software. -- Use the `--disable-tracing` option at deployment or provide your own service with `--tracing-backend` -- Use the `--deploy-grafana=false` option at deployment and provide your service to query Prometheus -- Use the `--prometheus-address` option at deployment and provide your own service - -Refer to the [NGINX Service Mesh: Monitoring and Tracing](https://docs.nginx.com/nginx-service-mesh/guides/monitoring-and-tracing/) guide for additional guidance. -

- - -**`ImagePullError` for `nginx-mesh-api` may not be obvious (24182)**: -
- -When deploying NGINX Service Mesh, if the `nginx-mesh-api` image cannot be pulled, and as a result `nginx-meshctl` cannot connect to the mesh API, the error that's shown simply says to "check the logs" without further instruction on what to check for. - -
- Workaround: -

- -If `nginx-meshctl` fails to connect to the mesh API when deploying, you should check to see if an `ImagePullError` exists for the `nginx-mesh-api` Pod. If you find an `ImagePullError`, you should confirm that your registry server is correct when deploying the mesh. -

- - -**Deploying NGINX Service Mesh to an existing namespace fails and returns an inaccurate error (24599)**: -
- -NGINX Service Mesh requires its own dedicated namespace, which the installer creates during deployment. If you run the `nginx-meshctl deploy` command for a namespace that already exists -- either by using the `--namespace` option or using the default `nginx-mesh` namespace -- the deployment fails with the following error: `NGINX Service Mesh already exists. To remove the existing Mesh, run "nginx-meshctl remove".` - -This error message is inaccurate. Running the `nginx-meshctl remove` command does not fix the deployment error. - -
- Workaround: -

- -You don't need to create a namespace before deploying NGINX Service Mesh. If the namespace you want to use is empty, you should delete it before deploying: - -``` bash -kubectl delete namespace -``` - -If the namespace you want to use isn't empty, you should choose an alternate namespace to deploy to or migrate your resources from the desired namespace. -

- - - - -### Supported Versions - -Supported Kubernetes Versions - -NGINX Service Mesh has been verified to run on the following Kubernetes versions: - -- Kubernetes 1.16-1.20 - -NGINX Plus Ingress Controller: - -- 1.11 - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NSM SMI Extensions: - -- Traffic Specs: v1alpha1 - - - - diff --git a/content/mesh/releases/release-notes-1.0.1.md b/content/mesh/releases/release-notes-1.0.1.md deleted file mode 100644 index 4075f2ffe..000000000 --- a/content/mesh/releases/release-notes-1.0.1.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Release Notes 1.0.1 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1001 -nd-docs: DOCS-712 -type: -- reference ---- - -## NGINX Service Mesh Version 1.0.1 - - - -This hotfix release resolves an issue affecting version 1.0.0 described below. - -**Update to NGINX Plus R23 P1 (25166)**: - - A bug discovered in the NGINX Plus R23 sidecar is fixed in NGINX Plus R23 P1. See the [upgrade guide]({{< ref "/mesh/get-started/upgrade/upgrade.md#nginx-service-mesh-101" >}}) for instructions on upgrading. diff --git a/content/mesh/releases/release-notes-1.1.0.md b/content/mesh/releases/release-notes-1.1.0.md deleted file mode 100644 index 52a5075b9..000000000 --- a/content/mesh/releases/release-notes-1.1.0.md +++ /dev/null @@ -1,353 +0,0 @@ ---- -title: Release Notes 1.1.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1100 -nd-docs: DOCS-713 -type: -- reference ---- - -## NGINX Service Mesh Version 1.1.0 - -29 June 2021 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.1.0, in the following categories: - -- [NGINX Service Mesh Version 1.1.0](#nginx-service-mesh-version-110) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.1.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### Updates - -NGINX Service Mesh 1.1.0 includes the following updates: - -**Improvements** -
- -- Helm Support for install and removal -- Air-gap installation support for private environments -- In-place upgrades for non-disruptive version updates (control plane, data plane) -- Update to NGINX Plus R24 P1 sidecar images -- Update to SPIRE 0.12.3 images - - -**Bug fixes** -
- -- Better error handling on mesh startup -- Fixed issue where re-roll instructions and service details were incorrectly flagging NGINX Plus Ingress Controller as including a sidecar -- Enhanced error notification when installing in existing namespace - - - -### Resolved Issues - -This release includes fixes for the following issues. You can search by the issue ID to locate the details for an issue. - - -**Kubernetes reports warnings on versions >=1.19 (22721)** -
- -**Deploying NGINX Service Mesh to an existing namespace fails and returns an inaccurate error (24599)** -
- - - - -### Known Issues - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -

- - -**Non-injected pods and services mishandled as fallback services (14731)**: -
- -We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - -Instead, we recommend using injected pods and services for service mesh injected workloads. - -
- Workaround: -

- -If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. - -Non-injected fallback servers are incompatible with mTLS mode strict. -

- - -**"Could not start API server" error is logged when Mesh API is shut down normally (17670)**: -
- -When the nginx-mesh-api Pod exits normally, the system may log the error "Could not start API server" and an error string. If the process is signaled, the signal value is lost and isn't printed correctly. - -If the error shows "http: Server closed," the nginx-mesh-api Pod has properly exited, and this message can be disregarded. - -Other legitimate error cases correctly show the error encountered, but this may be well after startup and proper operation. - -
- Workaround: -

- -No workaround exists, the exit messages are innocuous. -

- - -**Rejected configurations return generic HTTP status codes (18101)**: -
- -**The NGINX Service Mesh APIs are a beta feature.** Beta features are provided for you to try out before they are released. You shouldn't use beta features for production purposes. - -The NGINX Service Mesh APIs validate input for configured resources. These validations may reject the configuration for various reasons, including non-sanitized input, duplicates, conflicts, and so on When these configurations are rejected, a 500 Internal Server error is generated and returned to the client. - -
- Workaround: -

- -When configuring NGINX Service Mesh resources, do not use the NGINX Service Mesh APIs for production-grade releases if fine-grained error notifications are required. Each feature has Kubernetes API correlates that work according to the Kubernetes API Server semantics and constraints. All features are supported via Kubernetes. -

- - -**Pods fail to deploy if invalid Jaeger tracing address is set (19469)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. - -
- Workaround: -

- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. -

- - -**Duplicate targetPorts in a Service are disregarded (20566)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
- Workaround: -

- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. -

- - -**NGINX Service Mesh deployment may fail with TLS errors (20902)**: -
- -After deploying NGINX Service Mesh, the logs may show repeated TLS errors similar to the following: - -From the smi-metrics Pod logs: - -```bash -echo: http: TLS handshake error from :: remote error: tls: bad certificate -``` - -From the Kubernetes api-server log: - -```bash -E0105 10:03:45.159812 1 controller.go:116] loading OpenAPI spec for "v1alpha1.metrics.smi-spec.io" failed with: failed to retrieve openAPI spec, http error: ResponseCode: 503, Body: error trying to reach service: x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "NGINX") -``` - -A race condition may occur during deployment where the Spire server fails to communicate its certificate authority (CA) to dependent resources. Without the CA, these subsystems cannot operate correctly: metrics aggregation layer, injection, and validation. - -
- Workaround: -

- -You must [re-deploy NGINX Service Mesh](https://docs.nginx.com/nginx-service-mesh/reference/nginx-meshctl/): - -```bash -nginx-meshctl remove -nginx-meshctl deploy ... -``` - -

- - -**NGINX Service Mesh DNS Suffix support (21951)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. - -
- Workaround: -

- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. -

- - -**Circuit Breaker functionality is incompatible with load balancing algorithm "random" (22718)**: -
- -Circuit Breaker functionality is incompatible with the "random" load balancing algorithm. The two configurations interfere with each other and lead to errors. If Circuit Breaker resources exist in your environment, you cannot use the global load balancing algorithm "random" or an annotation for specific Services. The opposite is also true: if using the "random" algorithm, you cannot create Circuit Breaker resources. - -
- Workaround: -

- -If Circuit Breakers (API Version: specs.smi.nginx.com/v1alpha1 Kind: CircuitBreaker) are configured, the load balancing algorithm "random" cannot be used. Combining Circuit Breaker with "random" load balancing will result in errors and cause the sidecar containers to exit in error. Data flow will be detrimentally affected. - -There is no workaround at this time, but the configuration can be changed dynamically. If another load balancing algorithm is set, the sidecars will reset and traffic will return to normal operations. - -To fix the issue, take one or more of the following actions: - -- All load balancing annotations (config.nsm.nginx.com/lb-method) should be removed or updated to another supported algorithm. -- The global load balancing algorithm should be set to another supported algorithm. -

- - -**Optional, default visualization dependencies may cause excessive disk usage (23886)**: -
- -NGINX Service Mesh deploys optional metrics, tracing, and visualization services by default. These services are deployed as a convenience for evaluation and demonstration purposes only; these optional deployments should not be used in production. - -NGINX Service Mesh supports a "Bring Your Own" model where individual organizations can manage and tailor third-party dependencies. The optional dependencies -- Prometheus for metrics, Jaeger or Zipkin for tracing, and Grafana for visualization -- should be managed separately for production environments. The default deployments may cause excessive disk usage as their backing stores may be written to Node local storage. In high traffic environments, this may cause DiskPressure warnings and evictions. - -
- Workaround: -

- -To mitigate disk usage issues related to visualization dependencies in high traffic environments, we recommend the following: - -- Do not run high capacity applications with default visualization software. -- Use the `--disable-tracing` option at deployment or provide your own service with `--tracing-backend` -- Use the `--deploy-grafana=false` option at deployment and provide your service to query Prometheus -- Use the `--prometheus-address` option at deployment and provide your own service - -Refer to the [NGINX Service Mesh: Monitoring and Tracing](https://docs.nginx.com/nginx-service-mesh/guides/monitoring-and-tracing/) guide for additional guidance. -

- - -**`ImagePullError` for `nginx-mesh-api` may not be obvious (24182)**: -
- -When deploying NGINX Service Mesh, if the `nginx-mesh-api` image cannot be pulled, and as a result `nginx-meshctl` cannot connect to the mesh API, the error that's shown simply says to "check the logs" without further instruction on what to check for. - -
- Workaround: -

- -If `nginx-meshctl` fails to connect to the mesh API when deploying, you should check to see if an `ImagePullError` exists for the `nginx-mesh-api` Pod. If you find an `ImagePullError`, you should confirm that your registry server is correct when deploying the mesh. -

- - -**Use of an invalid container image does not report an immediate error (24899)**: -
- -If you pass an invalid value for `--registry-server` and/or `--image-tag` (for example an unreachable host, an invalid or non-existent path-component or an invalid or non-existent tag), the `nginx-meshctl` command will only notify of an error when it verifies the installation. The verification stage of deployment may take over 2 minutes before running. - -An image name constructed from `--registry-server` and `--image-tag`, when invalid, will only notify of an error once the `nginx-meshctl` command begins verifying the deployment. The following message will be displayed after a few minutes of running: - -```plaintext -All resources created. Testing the connection to the Service Mesh API Server... -Connection to NGINX Service Mesh API Server failed. - Check the logs of the nginx-mesh-api container in namespace nginx-mesh for more details. -Error: failed to connect to Mesh API Server, ensure you are authorized and can access services in your Kubernetes cluster -``` - -Running `kubectl -n nginx-mesh get pods` will show containers in an `ErrImagePull` or `ImagePullBackOff` status. - -For example: - -```plaintext -NAME READY STATUS RESTARTS AGE -grafana-5647fdf464-hx9s4 1/1 Running 0 64s -jaeger-6fcf7cd97b-cgrt9 1/1 Running 0 64s -nats-server-6bc4f9bbc8-jxzct 0/2 Init:ImagePullBackOff 0 2m9s -nginx-mesh-api-84898cbc67-tdwdw 0/1 ImagePullBackOff 0 68s -nginx-mesh-metrics-55fd89954c-mbb25 0/1 ErrImagePull 0 66s -prometheus-8d5fb5879-fgdbh 1/1 Running 0 65s -spire-agent-47t2w 1/1 Running 1 2m49s -spire-agent-8pnch 1/1 Running 1 2m49s -spire-agent-qtntx 1/1 Running 0 2m49s -spire-server-0 2/2 Running 0 2m50s -``` - -
- Workaround: -

- -You must correct your `--registry-server` and/or `--image-tag` arguments to be valid values. - -In a non-air gapped deployment, be sure to use `docker-registry.nginx.com/nsm` and a valid version tag appropriate to your requirements. See for more details. - -In an air gapped deployment, be sure to use the correct private registry domain and path for your environment and the correct tag used when loading images. -

- - -**Deployments enter a `CrashLoopBackoff` status after removing NGINX Service Mesh (25421)**: -
- -If a traffic policy (RateLimit, TrafficSplit, and so on) is still applied when removing NGINX Service Mesh, the sidecar container will crash causing the pod to enter a `CrashLoopBackoff` state. - -
- Workaround: -

- -Remove all NGINX Service Mesh traffic policies before removing the mesh. Alternatively, you can re-roll all deployments after removing the mesh which will resolve the `CrashLoopBackoff` state. -

- - - - -### Supported Versions - -Supported Kubernetes Versions - -NGINX Service Mesh has been verified to run on the following Kubernetes versions: - -- Kubernetes 1.16-1.20 - -NGINX Plus Ingress Controller: - -- 1.11 - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NSM SMI Extensions: - -- Traffic Specs: v1alpha1 - - - - diff --git a/content/mesh/releases/release-notes-1.2.0.md b/content/mesh/releases/release-notes-1.2.0.md deleted file mode 100644 index d94655a4f..000000000 --- a/content/mesh/releases/release-notes-1.2.0.md +++ /dev/null @@ -1,403 +0,0 @@ ---- -title: Release Notes 1.2.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1200 -nd-docs: DOCS-714 -type: -- reference ---- - -## NGINX Service Mesh Version 1.2.0 - -14 September 2021 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.2.0, in the following categories: - -- [NGINX Service Mesh Version 1.2.0](#nginx-service-mesh-version-120) - - [Updates](#updates) - - [Vulnerabilites](#vulnerabilities) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.2.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 1.2.0 includes the following updates: -

- -- Support for Rancher and availability in the Rancher Catalog -- Support for running NSM on Red Hat OpenShift clusters -- Extended HTTP URI and Method-based routing and security policy support -- New supportpkg tool for enterprise customers -- Upgrade to Spire 1.0.2 -- Deprecation - - The following NGINX Service Mesh API endpoints are deprecated and are targeted to be removed in the v1.3.0 release: - - /api/traffic-splits - - /api/rate-limits - - /api/traffic-targets - - /api/http-route-groups - - /api/tcp-routes - - /api/circuit-breakers - - - -### **Vulnerabilities** - - -#### **Fixes** - -This release includes vulnerability fixes for the following issues. -
- -- None - -
- - - -#### **Third Party Updates** - -This release includes third party updates for the following issues. -

- -- apk-tools CVE-2021-36159 CRITICAL (27973) - -- apk-tools CVE-2021-30139 HIGH (27974) - -- busybox CVE-2021-28831 HIGH (28092) - -- libcrypto1.1 CVE-2021-23840 HIGH (28093) - -- libcrypto1.1 CVE-2021-3450 HIGH (28094) - -- libcrypto1.1 CVE-2021-3711 HIGH (28095) - -- musl CVE-2019-14697 CRITICAL (28096) - -- libcrypto1.1 CVE-2019-1543 HIGH (28097) - -- libcrypto1.1 CVE-2020-1967 HIGH (28099) - -- libssl1.1 CVE-2021-3450 HIGH (28100) - -- libssl1.1 CVE-2021-3711 HIGH (28101) - -- libssl1.1 CVE-2019-1543 HIGH (28102) - -- libssl1.1 CVE-2020-1967 HIGH (28103) - -- ssl_client CVE-2021-28831 HIGH (28104) - -- krb5-libs CVE-2021-36222 HIGH (28105) - -- openssl CVE-2021-3711 HIGH (28107) - -- golang.org/x/crypto CVE-2020-29652 HIGH (28110) - -- libcrypto1.1 CVE-2021-23841 MEDIUM (28111) - -- libcrypto1.1 CVE-2021-3449 MEDIUM (28115) - -- libcrypto1.1 CVE-2020-1971 MEDIUM (28118) - -- libcrypto1.1 CVE-2019-1547 MEDIUM (28119) - -- libcrypto1.1 CVE-2019-1549 MEDIUM (28121) - -- libcrypto1.1 CVE-2019-1551 MEDIUM (28148) - -- libssl1.1 CVE-2021-23841 MEDIUM (28149) - -- libssl1.1 CVE-2021-3449 MEDIUM (28150) - -- libssl1.1 CVE-2020-1971 MEDIUM (28152) - -- libssl1.1 CVE-2019-1547 MEDIUM (28153) - -- libssl1.1 CVE-2019-1549 MEDIUM (28154) - -- libssl1.1 CVE-2019-1551 MEDIUM (28157) - -- musl CVE-2020-28928 MEDIUM (28165) - -- musl-utils CVE-2020-28928 MEDIUM (28166) - -- libcrypto1.1 CVE-2021-23839 LOW (28171) - -- libssl1.1 CVE-2021-23839 LOW (28172) - -- libcrypto1.1 CVE-2019-1563 LOW (28173) - -- libssl1.1 CVE-2019-1563 LOW (28174) - -
- - - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- "Could not start API server" error is logged when Mesh API is shut down normally (17670) - -- NGINX Service Mesh deployment may fail with TLS errors (20902) - -- Circuit Breaker functionality is incompatible with load balancing algorithm "random" (22718) - -- Deployments enter a `CrashLoopBackoff` status after removing NGINX Service Mesh (25421) - -- Spire fails to bind persistent volume claim when using deprecated NFS client (28468) - -
- - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Non-injected pods and services mishandled as fallback services (14731)**: -
- -We do not recommend using non-injected pods with a fallback service. Unless the non-injected fallback service is created following the proper order of operations, the service may not be recognized and updated in the circuit breaker flow. - -Instead, we recommend using injected pods and services for service mesh injected workloads. -
-
- Workaround: -
- -If you must use non-injected workloads, you need to configure the fallback service and pods before the Circuit Breaker CRD references them. - -Non-injected fallback servers are incompatible with mTLS mode strict. - - -
**Rejected configurations return generic HTTP status codes (18101)**: -
- -**The NGINX Service Mesh APIs are a beta feature.** Beta features are provided for you to try out before they are released. You shouldn't use beta features for production purposes. - -The NGINX Service Mesh APIs validate input for configured resources. These validations may reject the configuration for various reasons, including non-sanitized input, duplicates, conflicts, and so on When these configurations are rejected, a 500 Internal Server error is generated and returned to the client. -
-
- Workaround: -
- -When configuring NGINX Service Mesh resources, do not use the NGINX Service Mesh APIs for production-grade releases if fine-grained error notifications are required. Each feature has Kubernetes API correlates that work according to the Kubernetes API Server semantics and constraints. All features are supported via Kubernetes. - - -
**Pods fail to deploy if invalid Jaeger tracing address is set (19469)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. -
-
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Duplicate targetPorts in a Service are disregarded (20566)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (21951)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Optional, default visualization dependencies may cause excessive disk usage (23886)**: -
- -NGINX Service Mesh deploys optional metrics, tracing, and visualization services by default. These services are deployed as a convenience for evaluation and demonstration purposes only; these optional deployments should not be used in production. - -NGINX Service Mesh supports a "Bring Your Own" model where individual organizations can manage and tailor third-party dependencies. The optional dependencies -- Prometheus for metrics, Jaeger or Zipkin for tracing, and Grafana for visualization -- should be managed separately for production environments. The default deployments may cause excessive disk usage as their backing stores may be written to Node local storage. In high traffic environments, this may cause DiskPressure warnings and evictions. -
-
- Workaround: -
- -To mitigate disk usage issues related to visualization dependencies in high traffic environments, we recommend the following: - -- Do not run high capacity applications with default visualization software. -- Use the `--disable-tracing` option at deployment or provide your own service with `--tracing-backend` -- Use the `--deploy-grafana=false` option at deployment and provide your service to query Prometheus -- Use the `--prometheus-address` option at deployment and provide your own service - -Refer to the [NGINX Service Mesh: Monitoring and Tracing](https://docs.nginx.com/nginx-service-mesh/guides/monitoring-and-tracing/) guide for additional guidance. - - -
**`ImagePullError` for `nginx-mesh-api` may not be obvious (24182)**: -
- -When deploying NGINX Service Mesh, if the `nginx-mesh-api` image cannot be pulled, and as a result `nginx-meshctl` cannot connect to the mesh API, the error that's shown simply says to "check the logs" without further instruction on what to check for. -
-
- Workaround: -
- -If `nginx-meshctl` fails to connect to the mesh API when deploying, you should check to see if an `ImagePullError` exists for the `nginx-mesh-api` Pod. If you find an `ImagePullError`, you should confirm that your registry server is correct when deploying the mesh. - - -
**Use of an invalid container image does not report an immediate error (24899)**: -
- -If you pass an invalid value for `--registry-server` and/or `--image-tag` (for example, an unreachable host, an invalid or non-existent path-component or an invalid or non-existent tag), the `nginx-meshctl` command will only notify of an error when it verifies the installation. The verification stage of deployment may take over 2 minutes before running. - -An image name constructed from `--registry-server` and `--image-tag`, when invalid, will only notify of an error once the `nginx-meshctl` command begins verifying the deployment. The following message will be displayed after a few minutes of running: - -```plaintext -All resources created. Testing the connection to the Service Mesh API Server... -Connection to NGINX Service Mesh API Server failed. - Check the logs of the nginx-mesh-api container in namespace nginx-mesh for more details. -Error: failed to connect to Mesh API Server, ensure you are authorized and can access services in your Kubernetes cluster -``` - -Running `kubectl -n nginx-mesh get pods` will show containers in an `ErrImagePull` or `ImagePullBackOff` status. - -For example: - -```plaintext -NAME READY STATUS RESTARTS AGE -grafana-5647fdf464-hx9s4 1/1 Running 0 64s -jaeger-6fcf7cd97b-cgrt9 1/1 Running 0 64s -nats-server-6bc4f9bbc8-jxzct 0/2 Init:ImagePullBackOff 0 2m9s -nginx-mesh-api-84898cbc67-tdwdw 0/1 ImagePullBackOff 0 68s -nginx-mesh-metrics-55fd89954c-mbb25 0/1 ErrImagePull 0 66s -prometheus-8d5fb5879-fgdbh 1/1 Running 0 65s -spire-agent-47t2w 1/1 Running 1 2m49s -spire-agent-8pnch 1/1 Running 1 2m49s -spire-agent-qtntx 1/1 Running 0 2m49s -spire-server-0 2/2 Running 0 2m50s -``` - -
-
- Workaround: -
- -You must correct your `--registry-server` and/or `--image-tag` arguments to be valid values. - -In a non-air gapped deployment, be sure to use `docker-registry.nginx.com/nsm` and a valid version tag appropriate to your requirements. See for more details. - -In an air gapped deployment, be sure to use the correct private registry domain and path for your environment and the correct tag used when loading images. - - -
**Invalid rate limit configurations are allowed (28043)**: -
- -Invalid rate limit configurations, for example a rate limit that references the same destination and source(s) as an existing rate limit, can be created in Kubernetes without error. -
-
- Workaround: -
- -Check if your rate limit configuration is valid by describing your rate limit after creation: `kubectl describe ratelimit ` - - -
**Tracer address reported by nginx-meshctl config when no tracer is deployed (28256)**: -
- -If NGINX Service Mesh is deployed without a tracing backend, `nginx-meshctl config` reports the default tracing backend (jaeger) and the default tracing backend address ("jaeger..svc.cluster.local:6831"). This has no impact on the functionality of the mesh as tracing is disabled. -
-
- Workaround: -
- -No workaround necessary. - - -
- - - - -### **Supported Versions** -
- -NGINX Service Mesh supports the following versions: - -Kubernetes: - -- 1.16-1.21 - -OpenShift - -- 4.8 - -Helm: - -- 3.5, 3.6 - -Rancher: - -- 2.6 - -NGINX Plus Ingress Controller: - -- 1.12.0 - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NSM SMI Extensions: - -- Traffic Specs: - - - RateLimit: v1alpha1,v1alpha2 - - CircuitBreaker: v1alpha1 diff --git a/content/mesh/releases/release-notes-1.2.1.md b/content/mesh/releases/release-notes-1.2.1.md deleted file mode 100644 index 8ebe77cb5..000000000 --- a/content/mesh/releases/release-notes-1.2.1.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Release Notes 1.2.1 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1201 -nd-docs: DOCS-715 -type: -- reference ---- - -## NGINX Service Mesh Version 1.2.1 - -11 October 2021 - - - -This hotfix release resolves an issue affecting version 1.2.0 described below. - -**Update packaged version of Grafana to v8.1.7 to address CVE-2021-39226 (29195)**: - -Updates the packaged version of Grafana to v8.1.7. This update incorporates the fix for [CVE-2021-39226](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-39226), which affects Grafana's snapshot feature in versions 2.0.1 to 8.1.5. See the [upgrade guide]({{< ref "/mesh/get-started/upgrade/upgrade.md#nginx-service-mesh-101" >}}) for instructions on upgrading. diff --git a/content/mesh/releases/release-notes-1.3.0.md b/content/mesh/releases/release-notes-1.3.0.md deleted file mode 100644 index e4af48714..000000000 --- a/content/mesh/releases/release-notes-1.3.0.md +++ /dev/null @@ -1,345 +0,0 @@ ---- -title: Release Notes 1.3.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1300 -nd-docs: DOCS-716 -type: -- reference ---- - -## NGINX Service Mesh Version 1.3.0 - -16 November 2021 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.3.0, in the following categories: - -- [NGINX Service Mesh Version 1.3.0](#nginx-service-mesh-version-130) - - [Updates](#updates) - - [Vulnerabilites](#vulnerabilities) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.3.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 1.3.0 includes the following updates: -

- -#### **Product Enhancements** - -- Ability to configure certificate TTL after deploy -- Configurable certificate size and type -- Support for IAM roles when using AWS PCA as the upstream CA -- Upgrade support for NGINX Service Mesh running in Red Hat OpenShift -- Availability of NGINX Service Mesh in the Rancher Catalog -- Support for Kubernetes 1.22 - -#### **Functional Enhancements** - -- `--registry-server` flag is no longer required when deploying the mesh with nginx-meshctl. Defaults to the value `docker-registry.nginx.com/nsm`. -- API field `mtlsMode` has been deprecated in favor of `mtls.mode`. -- The SPIRE server only issues certificates for Pods that are injected with the NGINX Service Mesh sidecar. If you are upgrading to NGINX Service Mesh 1.3 and are running NGINX Plus Ingress Controller, add following label `spiffe.io/spiffeid: "true"` to the NGINX Plus Ingress Controller Pod spec(s) and re-roll the Pod(s). This ensures that the SPIRE server will issue a certificate for the NGINX Plus Ingress Controller. - -### **The following NGINX Service Mesh API endpoints have been removed:** - -- /api/traffic-splits -- /api/rate-limits -- /api/traffic-targets -- /api/http-route-groups -- /api/tcp-routes -- /api/circuit-breakers - - - -### **Vulnerabilities** - - -#### **Fixes** - -This release includes vulnerability fixes for the following issues. -
- -- None - -
- - - -#### **Third Party Updates** - -This release includes third party updates for the following issues. -
- -- Update to Spire 1.1.0 - -- github.com/gogo/protobuf CVE-2021-3121 HIGH (28109) - -- libcrypto1.1 CVE-2021-3712 MEDIUM (28116) - -- libssl1.1 CVE-2021-3712 MEDIUM (28151) - -- workload-registrar k8s.io/client-go CVE-2020-8565 MEDIUM (28170) - - -
- - - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- Non-injected pods and services mishandled as fallback services (14731) - -- Rejected configurations return generic HTTP status codes (18101) - -- `ImagePullError` for `nginx-mesh-api` may not be obvious (24182) - -- Use of an invalid container image does not report an immediate error (24899) - -- NGINX Service Mesh fails to deploy on some OpenShift versions (29905) - -- NATS restarts can lead to sidecar failure (30051) - -
- - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Pods fail to deploy if invalid Jaeger tracing address is set (19469)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. -
-
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Duplicate targetPorts in a Service are disregarded (20566)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (21951)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Optional, default visualization dependencies may cause excessive disk usage (23886)**: -
- -NGINX Service Mesh deploys optional metrics, tracing, and visualization services by default. These services are deployed as a convenience for evaluation and demonstration purposes only; these optional deployments should not be used in production. - -NGINX Service Mesh supports a "Bring Your Own" model where individual organizations can manage and tailor third-party dependencies. The optional dependencies -- Prometheus for metrics, Jaeger or Zipkin for tracing, and Grafana for visualization -- should be managed separately for production environments. The default deployments may cause excessive disk usage as their backing stores may be written to Node local storage. In high traffic environments, this may cause DiskPressure warnings and evictions. -
-
- Workaround: -
- -To mitigate disk usage issues related to visualization dependencies in high traffic environments, we recommend the following: - -- Do not run high capacity applications with default visualization software. -- Use the `--disable-tracing` option at deployment or provide your own service with `--tracing-backend` -- Use the `--deploy-grafana=false` option at deployment and provide your service to query Prometheus -- Use the `--prometheus-address` option at deployment and provide your own service - -Refer to the [NGINX Service Mesh: Monitoring and Tracing](https://docs.nginx.com/nginx-service-mesh/guides/monitoring-and-tracing/) guide for additional guidance. - - -
**Sidecar version not reported after upgrade (25821)**: -
- -After upgrading NGINX Service Mesh to the 1.3 release, the version of the sidecar in the `nginx-meshctl version` command will not be reported until you reroll the sidecar. This is caused by an issue with expired certificates in the sidecar in version 1.2. -
-
- Workaround: -
- -Reroll deployments to restart with the 1.3 version of the sidecar:`kubectl rollout restart /`. - -After rerolling, the 1.3 version of the sidecar will start up and report its version. - - -
**Invalid rate limit configurations are allowed (28043)**: -
- -Invalid rate limit configurations, for example a rate limit that references the same destination and source(s) as an existing rate limit, can be created in Kubernetes without error. -
-
- Workaround: -
- -Check if your rate limit configuration is valid by describing your rate limit after creation: `kubectl describe ratelimit ` - - -
**Tracer address reported by nginx-meshctl config when no tracer is deployed (28256)**: -
- -If NGINX Service Mesh is deployed without a tracing backend, `nginx-meshctl config` reports the default tracing backend (jaeger) and the default tracing backend address ("jaeger..svc.cluster.local:6831"). This has no impact on the functionality of the mesh as tracing is disabled. -
-
- Workaround: -
- -No workaround necessary. - - -
**NGINX Service Mesh cannot be deployed or removed after upgrade or removal is interrupted (28585)**: -
- -If the upgrade or removal process for NGINX Service Mesh is interrupted, then the mesh can be left in an incomplete state where some resources still exist. This results in errors such as "Error: namespace 'nginx-mesh' not found." when attempting to run `nginx-meshctl remove`, and "NGINX Service Mesh already exists." when attempting to run `nginx-meshctl deploy`. -
-
- Workaround: -
- -Ensure all CustomResourceDefinitions (CRDs) are removed. The list of CRDs to be deleted are: - -- `trafficsplits.split.smi-spec.io` -- `traffictargets.access.smi-spec.io` -- `httproutegroups.specs.smi-spec.io` -- `tcproutes.specs.smi-spec.io` -- `ratelimits.specs.smi.nginx.com` -- `circuitbreakers.specs.smi.nginx.com` - -Once these are deleted, another attempt at redeploying NGINX Service Mesh may fail, but should clean up any more lingering resources. At this point the mesh should be fully removed. - - -
**Deploying a TrafficSplit with an invalid weight value fails but does not return any errors (28602)**: -
- -When deploying a TrafficSplit, it is possible to set the weight value as a negative integer. NGINX Service Mesh does not support negative-integer weights, so the TrafficSplit will appear to deploy successfully but will not take effect. No error or log message is returned to indicate that the TrafficSplit deployment has failed. -
-
- Workaround: -
- -Be sure to use positive integers when assigning weight values to TrafficSplits. - - -
**Pods can't be created if nginx-mesh-api is unreachable (29560)**: -
- -If the nginx-mesh-api Pod cannot be reached by the "sidecar-injector-webhook-cfg.internal.builtin.nsm.nginx" MutatingWebhookConfiguration, then all Pod creations will fail. -
-
- Workaround: -
- -If attempting to create Pods that are not going to be injected by NGINX Service Mesh, then the simplest solution is to remove NGINX Service Mesh. - -Otherwise, if the nginx-mesh-api Pod is crashing, then verify that your configuration is valid before deploying NGINX Service Mesh. Reinstalling the mesh may also fix connectivity issues. - - -
**Spire Server crashes after reaching ~1500 certificate rotations (30150)**: -
- -After reaching approximately 1500 certificate rorations, the Spire Server crashes. This condition can be reached by either setting your certificate authority TTL to a low number -- for example, `--mtls-ca-ttl 1m --mtls-svid-ttl 1m` -- or leaving the mesh running for extended periods of time. -
-
- Workaround: -
- -Use the default TTL, which would produce the conditions that cause the crash after about 100 years of continuous operation. If you must use a lower TTL that will result in a significant number of certificate rotations, redeploy NGINX Service Mesh to refresh its state *before* the crash conditions can be reached. - - -
- - - - -### **Supported Versions** -
- -NGINX Service Mesh supports the following versions: - -Kubernetes: - -- 1.18-1.22 - -OpenShift - -- 4.8 - -Helm: - -- 3.2+ - -Rancher: - -- 2.5, 2.6 - -NGINX Plus Ingress Controller: - -- 1.12, 2.0 - -Note: The minimum supported version of Kubernetes is 1.19 if you are running NGINX Plus Ingress Controller 2.0. For older Kubernetes versions, use the NGINX Plus Ingress Controller 1.12 version. - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NSM SMI Extensions: - -- Traffic Specs: - - - RateLimit: v1alpha1,v1alpha2 - - CircuitBreaker: v1alpha1 diff --git a/content/mesh/releases/release-notes-1.3.1.md b/content/mesh/releases/release-notes-1.3.1.md deleted file mode 100644 index 561253f38..000000000 --- a/content/mesh/releases/release-notes-1.3.1.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Release Notes 1.3.1 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1301 -nd-docs: DOCS-717 -type: -- reference ---- - -## NGINX Service Mesh Version 1.3.1 - -22 November 2021 - - - -This hotfix release resolves an issue affecting version 1.3.0 described below. - -**Fix issue when upgrading from 1.2 to 1.3 (NSM-622)**: - -Fixed an issue that would result in a failed upgrade from 1.2 to 1.3 if the original deployment used a custom upstream CA. diff --git a/content/mesh/releases/release-notes-1.4.0.md b/content/mesh/releases/release-notes-1.4.0.md deleted file mode 100644 index bfca72bfe..000000000 --- a/content/mesh/releases/release-notes-1.4.0.md +++ /dev/null @@ -1,303 +0,0 @@ ---- -title: Release Notes 1.4.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1400 -nd-docs: DOCS-1488 -type: -- reference ---- - -## NGINX Service Mesh Version 1.4.0 - -23 February 2022 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.4.0, in the following categories: - -- [NGINX Service Mesh Version 1.4.0](#nginx-service-mesh-version-140) - - [Updates](#updates) - - [Vulnerabilites](#vulnerabilities) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions](#supported-versions) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.4.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 1.4.0 includes the following updates: -
- -- NGINX Service Mesh has changed its API to follow Kubernetes convention enabling granular controls of the NGINX Service Mesh API using Kubernetes native RBAC. - - [Use the NGINX Service Mesh API]({{< ref "mesh/reference/api-usage.md" >}}) -- Support for service-to-service UDP traffic proxying -- The addition of OpenTelemetry tracing along side the existing OpenTracing support to provide rich telemetry options -- Coupling with the cert-manager project to instantly drop into your existing Certificate Authority issuer workflow - -#### **Deprecation** - -- Starting in v1.5.0 release, NGINX Service Mesh will no longer bundle Prometheus, Grafana, or tracing pods. - - The --disable-tracing and --deploy-grafana flags are deprecated and will be removed in v1.5.0 - - The tracing.disable and deployGrafana helm values are deprecated and will be removed in v1.5.0 - - The config.nsm.nginx.com/tracing-enabled pod annotation is deprecated and will be removed in v1.5.0 - -#### **Features** - -- [Support for UDP traffic proxying]({{< ref "mesh/about/architecture.md/#udp-and-ebpf" >}}) -- [Support for OpenTelemetry tracing using the OTLP gRPC Exporter]({{< ref "mesh/guides/monitoring-and-tracing.md/#opentelemetry" >}}) -- [Support for cert-manager as an upstream authority]({{< ref "mesh/guides/secure-traffic-mtls.md/#deploy-using-an-upstream-root-ca" >}}) -- [How to access the NGINX Service Mesh API using Kubernetes native RBAC]({{< ref "mesh/reference/api-usage.md" >}}) - -#### **Improvements** - -- Updated to Spire 1.2.0 -- Tightened API access - -#### **Fixes** - -- Openshift CSI Driver is automatically cleaned up after mesh removal once all workloads have been re-rolled or deleted - - - -### **Vulnerabilities** - - -#### **Fixes** - -This release includes vulnerability fixes for the following issues. -
- -- None - -
- - - -#### **Third Party Updates** - -This release includes third party updates for the following issues. -

- -- github.com/apache/thrift CVE-2020-13949 HIGH (22) - -- busybox CVE-2021-42374, CVE-2021-42375, CVE-2021-42378, CVE-2021-42379, CVE-2021-42380, CVE-2021-42381, CVE-2021-42382, CVE-2021-42383, CVE-2021-42384 HIGH CVE-2021-42385, CVE-2021-42386 MEDIUM (677) - -- github.com/containerd/containerd CVE-2021-41103 HIGH (678) - -- golang.org/x/crypto/ssh CVE-2020-29652 HIGH (734) - -- golang.org/x/text CVE-2021-38561 HIGH (735) - -- github.com/containerd/containerd CVE-2021-43816 CRITICAL (776) - -
- - - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- NGINX Service Mesh cannot be deployed or removed (428) - -- Invalid rate limit configurations are allowed (449) - -- Optional, default visualization dependencies may cause excessive disk usage (498) - -
- - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Spire Server crashes after reaching ~1500 certificate rotations (375)**: -
- -After reaching approximately 1500 certificate rorations, the Spire Server crashes. This condition can be reached by either setting your certificate authority TTL to a low number, for example, {{--mtls-ca-ttl 1m --mtls-svid-ttl 1m}} – or leaving the mesh running for extended periods of time. -
-
- Workaround: -
- -Use the default TTL, which would produce the conditions that cause the crash after about 100 years of continuous operation. If you must use a lower TTL that will result in a significant number of certificate rotations, redeploy NGINX Service Mesh to refresh its state _before_ the crash conditions can be reached. - - -
**Pods can't be created if nginx-mesh-api is unreachable (384)**: -
- -If the nginx-mesh-api Pod cannot be reached by the "sidecar-injector-webhook-cfg.internal.builtin.nsm.nginx" MutatingWebhookConfiguration, then all Pod creations will fail. -
-
- Workaround: -
- -If attempting to create Pods that are not going to be injected by NGINX Service Mesh, then the simplest solution is to remove NGINX Service Mesh. - -Otherwise, if the nginx-mesh-api Pod is crashing, then the user should verify that their configuration when deploying NGINX Service Mesh is valid. Reinstalling the mesh may also fix connectivity issues. - - -
**Deploying a TrafficSplit with an invalid weight value fails but does not return any errors (426)**: -
- -When deploying a TrafficSplit, it is possible to set the weight value as a negative integer. NGINX Service Mesh does not support negative-integer weights, so the TrafficSplit will appear to deploy successfully but will not take effect. No error or log message is returned to indicate that the TrafficSplit deployment has failed. -
-
- Workaround: -
- -Be sure to use positive integers when assigning weight values to TrafficSplits. - - -
**Tracer address reported by nginx-meshctl config when no tracer is deployed (440)**: -
- -If NGINX Service Mesh is deployed without a tracing backend, `nginx-meshctl config` reports the default tracing backend (jaeger) and the default tracing backend address ("jaeger..svc.cluster.local:6831"). This has no impact on the functionality of the mesh as tracing is disabled. -
-
- Workaround: -
- -No workaround necessary. - - -
**NGINX Service Mesh DNS Suffix support (519)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Duplicate targetPorts in a Service are disregarded (532)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - -``` plaintext -apiVersion: v1 -kind: Service -spec: - ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**Pods fail to deploy if invalid Jaeger tracing address is set (540)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. -
-
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Inject command errors are ambiguous (789)**: -
- -Inject command may return ambiguous error messages: - -{noformat}./build/nginx-meshctl inject < example.yaml -Cannot inject NGINX Service Mesh sidecar. -Error: error formatting response string: invalid syntax{noformat} - -This error can be returned due to invalid encoding, if the control plane is down, if NGINX Service Mesh is not installed, or if it is installed in an unexpected namespace. -
-
- Workaround: -
- -When encountering an error from Inject do the following checks: - - 1. Verify you have valid YAML. - 2. Check the status of your install: {{nginx-meshctl status}} - 3. Check the status of you Pods: {{kubectl -n nginx-mesh get pods}} - -If steps 2 or 3 show failures, or Pods that are not in the Running state, NGINX Service Mesh will need further troubleshooting. Pods may be restarted through {{kubectl}} by using {{rollout restart}} on the Deployments, or by deleting the Pod resources. If the issue persists contact your support agent. - - -
- - - - -### **Supported Versions** -
- -NGINX Service Mesh supports the following versions: - -Kubernetes: - -- 1.18-1.22 - -OpenShift - -- 4.8 - -Helm: - -- 3.2+ - -Rancher: - -- 2.5, 2.6 - -NGINX Plus Ingress Controller: - -- 1.12, 2.0, 2.1 - -Note: The minimum supported version of Kubernetes is 1.19 if you are running NGINX Plus Ingress Controller 2.0. For older Kubernetes versions, use the NGINX Plus Ingress Controller 1.12 version. - -SMI Specification: - -- Traffic Access: v1alpha2 -- Traffic Metrics: v1alpha1 (in progress, supported resources: StatefulSets, Namespaces, Deployments, Pods, DaemonSets) -- Traffic Specs: v1alpha3 -- Traffic Split: v1alpha3 - -NSM SMI Extensions: - -- Traffic Specs: - - - RateLimit: v1alpha1,v1alpha2 - - CircuitBreaker: v1alpha1 - - diff --git a/content/mesh/releases/release-notes-1.4.1.md b/content/mesh/releases/release-notes-1.4.1.md deleted file mode 100644 index dc220dea2..000000000 --- a/content/mesh/releases/release-notes-1.4.1.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Release Notes 1.4.1 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1401 -nd-docs: DOCS-1484 -type: -- reference ---- - -## NGINX Service Mesh Version 1.4.1 - -26 May 2022 - -This hotfix release enhances previous versions as described below. - -**Egress upstream should be resilient**: - -Ability to send egress traffic to a pool of NGINX Ingress Controller endpoints. This applies to a single NGINX Ingress Controller deployment with multiple replicas, not multiple NGINX Ingress Controller deployments. diff --git a/content/mesh/releases/release-notes-1.5.0.md b/content/mesh/releases/release-notes-1.5.0.md deleted file mode 100644 index a17a2b63f..000000000 --- a/content/mesh/releases/release-notes-1.5.0.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -title: Release Notes 1.5.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1500 -nd-docs: DOCS-1487 -type: -- reference ---- - -## NGINX Service Mesh Version 1.5.0 - -26 July 2022 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.5.0, in the following categories: - -- [NGINX Service Mesh Version 1.5.0](#nginx-service-mesh-version-150) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions]({{< ref "mesh/about/mesh-tech-specs.md" >}}) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.5.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - - - -### **Updates** - -NGINX Service Mesh 1.5.0 includes the following updates: - -- For a reduced footprint, default sidecar memory usage has been optimized. -- To reduce the size and stay true to having a small and easy to manage Service Mesh, the observability stack has been removed from the default installation. See the [Observability Tutorial]( {{< ref "/mesh/tutorials/observability.md" >}} ) for an example on deploying your own. -- For improved scale support and optimization, sidecar proxy memory usage is dynamically managed. -- For improved performance, the configuration changes that require a reload of NGINX has been reduced in the sidecar proxies. -- client-max-body-size support has been added to support tuning for large request body sizes. -- For latest features and patches, SPIRE has been updated to 1.3. -- To complete deprecation, mtlsMode API field has been removed (please use mtls.mode instead). -- Maintaining consistency with SMI, the HTTPRouteGroup headers field changed from a list to an object. - -#### **Deprecation** - -- OpenTracing has been deprecated in favor of OpenTelemetry - -
- - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- Deploying a TrafficSplit with an invalid weight value fails but does not return any errors (426) - -- Tracer address reported by nginx-meshctl config when no tracer is deployed (440) - -- DNS fails over TCP when mTLS mode is set to "strict" (695) - -- Request bodies large than 1MB are rejected (1971) - -
- - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**TrafficSplits do not work for headless Services (2324)**: -
- -TrafficSplits are not applied properly for headless Services. This results in traffic distribution that is not weighted. - -
- Workaround: -
- -Services need a cluster IP address for Traffic Splitting to work properly. - - -
**Remove can't always clean up. (1566)**: -
- -While deploying NGINX Service Mesh, if the `label-namespace` job fails, lingering resources can be left around. This prevents the mesh from being fully removed or re-deployed. - -
- Workaround: -
- -Remove lingering resources (Clusterroles, clusterrolebindings, etc.) manually. These resources have the `app.kubernetes.io/part-of: nginx-service-mesh` label. - - -
**Deploy hangs forever if helm job can't run. (1565)**: -
- -Deploying NGINX Service Mesh in an air-gapped environment (using `--disable-public-images` flag) with an invalid registry can result in the deploy command hanging for a long time without any error messages. - -
- Workaround: -
- -Ensure your private registry is valid and accessible if you are deploying in an air-gapped environment. - - -
**Inject command errors are ambiguous (789)**: -
- -Inject command may return ambiguous error messages: - -```bash -./build/nginx-meshctl inject < example.yaml -Cannot inject NGINX Service Mesh sidecar. -Error: error formatting response string: invalid syntax -``` - -This error can be returned due to invalid encoding, if the control plane is down, if NGINX Service Mesh is not installed, or if it is installed in an unexpected namespace. - -
- Workaround: -
- -When encountering an error from Inject do the following checks: - - 1. Verify you have valid YAML. - 2. Check the status of your install: nginx-meshctl status - 3. Check the status of you Pods: kubectl -n nginx-mesh get pods - -If steps 2 or 3 show failures, or Pods that are not in the Running state, NGINX Service Mesh will need further troubleshooting. Pods may be restarted through `kubectl` by using `rollout restart` on the Deployments, or by deleting the Pod resources. If the issue persists contact your support agent. - - -
**Injecting through API returns unformatted text (744)**: -
- -Injecting (sending a POST request) a Kubernetes resource through NSM API returns unformatted text that cannot be applied directly with `kubectl apply`. - -
- Workaround: -
- -Use either one of the following two options: - -- Inject your Kubernetes resources with the NSM sidecar through the CLI with the following command: - - ```bash - ./nginx-meshctl inject -f - ``` - -- Enable [automatic injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) in your cluster or namespace. - - -
**Lingering invalid RateLimits can cause restart inconsistencies with the NGINX Service Mesh control plane. (658)**: -
- -The NGINX Service Mesh control plane has a validating webhook that will reject the majority of RateLimits that conflict with an existing RateLimit. However, there are some cases where the validating webhook is unable to determine a conflict. In these cases, the NGINX Service Mesh control plane process will catch the conflict and prevent configuration of the offending RateLimit, but the RateLimit will still be stored in Kubernetes. These RateLimit resources are invalid and can be found by looking for a `Warning` event on the RateLimit object. If invalid RateLimits exist and the NGINX Service Mesh control plane restarts, the invalid RateLimits may be configured over the previous valid RateLimits. - -
- Workaround: -
- -When you create a RateLimit resource in Kubernetes, run `kubectl describe ratelimit ` and check for any `Warning` events. If a `Warning` event exists, either fix the conflict described in the `Warning` event message, or delete the RateLimit by running: `kubectl delete ratelimit `. - - -
**Pods fail to deploy if invalid Jaeger tracing address is set (540)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. - -
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Duplicate targetPorts in a Service are disregarded (532)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports\[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports\[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - - -```yaml -apiVersion: v1 -kind: Service -spec: -ports: - - port: 8080 - protocol: TCP - targetPort: 55555 - - port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (519)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. - -
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Pods can't be created if nginx-mesh-api is unreachable (384)**: -
- -If the nginx-mesh-api Pod cannot be reached by the "sidecar-injector-webhook-cfg.internal.builtin.nsm.nginx" MutatingWebhookConfiguration, then all Pod creations will fail. - -
- Workaround: -
- -If attempting to create Pods that are not going to be injected by NGINX Service Mesh, then the simplest solution is to remove NGINX Service Mesh. - -Otherwise, if the nginx-mesh-api Pod is crashing, then the user should verify that their configuration when deploying NGINX Service Mesh is valid. Reinstalling the mesh may also fix connectivity issues. - - -
**Spire Server crashes after reaching ~1500 certificate rotations (375)**: -
- -After reaching approximately 1500 certificate rorations, the Spire Server crashes. This condition can be reached by either setting your certificate authority TTL to a low number, for example, `--mtls-ca-ttl 1m --mtls-svid-ttl 1m` – or leaving the mesh running for extended periods of time. - -
- Workaround: -
- -Use the default TTL, which would produce the conditions that cause the crash after about 100 years of continuous operation. If you must use a lower TTL that will result in a significant number of certificate rotations, redeploy NGINX Service Mesh to refresh its state _before_ the crash conditions can be reached. diff --git a/content/mesh/releases/release-notes-1.6.0.md b/content/mesh/releases/release-notes-1.6.0.md deleted file mode 100644 index 77e9d4c4d..000000000 --- a/content/mesh/releases/release-notes-1.6.0.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -title: Release Notes 1.6.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1600 -nd-docs: DOCS-1486 -type: -- reference ---- - -## NGINX Service Mesh Version 1.6.0 - -01 November 2022 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.6.0, in the following categories: - -- [NGINX Service Mesh Version 1.6.0](#nginx-service-mesh-version-160) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions]({{< ref "mesh/about/mesh-tech-specs.md" >}}) - - {{< link "nginx-service-mesh/licenses/license-servicemesh-1.6.0.html" "Open Source Licenses" >}} - - [Open Source Licenses Addendum]({{< ref "oss-dependencies/index.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 1.6.0 includes the following updates: - -- For more control over automatic sidecar injection, namespaces can opt in or out using the `injector.nsm.nginx.com/auto-inject` label. -- To avoid crashing due to frequent rotations, SPIRE's `caTTL` field has a minimum value of `24h`. - -#### **Deprecation** - -- `disabledNamespaces` configuration field has been deprecated and will be removed in a future release. -- Other `autoInjection` configuration fields have been deprecated in favor of using top-level fields. -- `injector.nsm.nginx.com/auto-inject` Pod annotation has been deprecated in favor of using a label. - - - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- Spire Server crashes after reaching ~1500 certificate rotations (375) - -- Injecting a resource through API returns unformatted text (744) - -- Inject command errors are ambiguous (789) - -- Deploy command for NGINX Service Mesh leads to unresponsive stage if helm job cannot execute. (1565) - -- Removing NGINX Service Mesh can't always clean up lingering resources. (1566) - -
- - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Lingering invalid RateLimits can cause restart inconsistencies with the NGINX Service Mesh control plane. (658)**: -
- -The NGINX Service Mesh control plane has a validating webhook that will reject the majority of RateLimits that conflict with an existing RateLimit. However, there are some cases where the validating webhook is unable to determine a conflict. In these cases, the NGINX Service Mesh control plane process will catch the conflict and prevent configuration of the offending RateLimit, but the RateLimit will still be stored in Kubernetes. These RateLimit resources are invalid and can be found by looking for a `Warning` event on the RateLimit object. If invalid RateLimits exist and the NGINX Service Mesh control plane restarts, the invalid RateLimits may be configured over the previous valid RateLimits. -
-
- Workaround: -
- -When you create a RateLimit resource in Kubernetes, run `kubectl describe ratelimit ` and check for any `Warning` events. If a `Warning` event exists, either fix the conflict described in the `Warning` event message, or delete the RateLimit by running: `kubectl delete ratelimit `. - - -
**Pods fail to deploy if invalid Jaeger tracing address is set (540)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. -
-
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Duplicate targetPorts in a Service are disregarded (532)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports\[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports\[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - - -```yaml -apiVersion: v1 -kind: Service -spec: -ports: -- port: 8080 - protocol: TCP - targetPort: 55555 -- port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (519)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Pods can't be created if nginx-mesh-api is unreachable (384)**: -
- -If the nginx-mesh-api Pod cannot be reached by the `sidecar-injector-webhook-cfg.internal.builtin.nsm.nginx` MutatingWebhookConfiguration, then all Pod creations will fail. -
-
- Workaround: -
- -If attempting to create Pods that are not going to be injected by NGINX Service Mesh, then the simplest solution is to remove NGINX Service Mesh. - -Otherwise, if the nginx-mesh-api Pod is crashing, then the user should verify that their configuration when deploying NGINX Service Mesh is valid. Reinstalling the mesh may also fix connectivity issues. - diff --git a/content/mesh/releases/release-notes-1.7.0.md b/content/mesh/releases/release-notes-1.7.0.md deleted file mode 100644 index 77a3eba3a..000000000 --- a/content/mesh/releases/release-notes-1.7.0.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: Release Notes 1.7.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1700 -nd-docs: DOCS-1483 -type: -- reference ---- - -## NGINX Service Mesh Version 1.7.0 - -14 February 2023 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 1.7.0, in the following categories: - -- [NGINX Service Mesh Version 1.7.0](#nginx-service-mesh-version-170) - - [Updates](#updates) - - [Known Issues](#known-issues) - - [Supported Versions]({{< ref "mesh/about/mesh-tech-specs.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 1.7.0 includes the following updates: -

- -- `nginx-meshctl` command-line tool can now be downloaded from [Github](https://github.com/nginxinc/nginx-service-mesh/releases/latest). -- NGINX Service Mesh can now integrate with the open source version of [NGINX Ingress Controller](https://github.com/nginxinc/kubernetes-ingress). -- For easier integration with NGINX Ingress Controller, users can now [simply add a label]({{< ref "/mesh/tutorials/kic/deploy-with-kic.md" >}}) to their Ingress Controller PodSpec, and NGINX Service Mesh will automatically update the controller to integrate. -- Sidecar and init containers now support ARM processors. Full ARM support is coming. -- OpenShift CSI Driver volume plugin has been renamed from `wlapi-mounter.spire.nginx.com` to `csi.spiffe.io`. -- For OpenShift deployments, NGINX Service Mesh now uses the open source [SPIFFE CSI Driver](https://github.com/spiffe/spiffe-csi). - -{{< call-out "important" >}} -OpenShift users see the [upgrade guide]({{< ref "/mesh/get-started/upgrade/upgrade.md#upgrade-to-170-in-openshift" >}}) for instructions on how to upgrade to this release version. -{{< /call-out >}} - - -#### **Deprecation** - -- NGINX Ingress Controller annotations `nsm.nginx.com/enable-ingress` and `nsm.nginx.com/enable-egress` have been deprecated in favor of using labels. - - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Lingering invalid RateLimits can cause restart inconsistencies with the NGINX Service Mesh control plane. (658)**: -
- -The NGINX Service Mesh control plane has a validating webhook that will reject the majority of RateLimits that conflict with an existing RateLimit. However, there are some cases where the validating webhook is unable to determine a conflict. In these cases, the NGINX Service Mesh control plane process will catch the conflict and prevent configuration of the offending RateLimit, but the RateLimit will still be stored in Kubernetes. These RateLimit resources are invalid and can be found by looking for a `Warning` event on the RateLimit object. If invalid RateLimits exist and the NGINX Service Mesh control plane restarts, the invalid RateLimits may be configured over the previous valid RateLimits. -
-
- Workaround: -
- -When you create a RateLimit resource in Kubernetes, run `kubectl describe ratelimit ` and check for any `Warning` events. If a `Warning` event exists, either fix the conflict described in the `Warning` event message, or delete the RateLimit by running: `kubectl delete ratelimit `. - - -
**Pods fail to deploy if invalid Jaeger tracing address is set (540)**: -
- -If `--tracing-address` is set to an invalid Jaeger address when deploying NGINX Service Mesh, all pods will fail to start. -
-
- Workaround: -
- -If you use your own Zipkin or Jaeger instance with NGINX Service Mesh, make sure to correctly set `--tracing-address` when deploying the mesh. - - -
**Duplicate targetPorts in a Service are disregarded (532)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports\[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports\[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - - -```yaml -apiVersion: v1 -kind: Service -spec: -ports: -- port: 8080 - protocol: TCP - targetPort: 55555 -- port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (519)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - -
**Pods can't be created if nginx-mesh-api is unreachable (384)**: -
- -If the nginx-mesh-api Pod cannot be reached by the `sidecar-injector-webhook-cfg.internal.builtin.nsm.nginx` MutatingWebhookConfiguration, then all Pod creations will fail. -
-
- Workaround: -
- -If attempting to create Pods that are not going to be injected by NGINX Service Mesh, then the simplest solution is to remove NGINX Service Mesh. - -Otherwise, if the nginx-mesh-api Pod is crashing, then the user should verify that their configuration when deploying NGINX Service Mesh is valid. Reinstalling the mesh may also fix connectivity issues. - - diff --git a/content/mesh/releases/release-notes-2.0.0.md b/content/mesh/releases/release-notes-2.0.0.md deleted file mode 100644 index 64a49211d..000000000 --- a/content/mesh/releases/release-notes-2.0.0.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -title: Release Notes 2.0.0 -draft: false -toc: true -description: Release information for F5 NGINX Service Mesh, a configurable, low‑latency - infrastructure layer designed to handle a high volume of network‑based interprocess - communication among application infrastructure services using application programming - interfaces (APIs). Lists of new features and known issues are provided. -weight: -1800 -nd-docs: DOCS-1485 -type: -- reference ---- - -## NGINX Service Mesh Version 2.0.0 - -25 April 2023 - - - -These release notes provide general information and describe known issues for NGINX Service Mesh version 2.0.0, in the following categories: - -- [NGINX Service Mesh Version 2.0.0](#nginx-service-mesh-version-200) - - [Updates](#updates) - - [Resolved Issues](#resolved-issues) - - [Known Issues](#known-issues) - - [Supported Versions]({{< ref "mesh/about/mesh-tech-specs.md" >}}) - -
-
- - -### **Updates** - -NGINX Service Mesh 2.0.0 includes the following updates: -

- -- NGINX Service Mesh global configuration API has been moved to a Kubernetes Custom Resource Definition. The NGINX Service Mesh API server has been removed. See the [API Usage guide]( {{< ref "/mesh/reference/api-usage.md" >}} ) for details on how to use the new CRD. -- Removed deprecated auto-injection annotations for Pods in favor of labels. -- Removed deprecated NGINX Ingress Controller annotations for integrating with NGINX Service Mesh in favor of labels. -- Automatic injection is now disabled globally by default, and requires users to opt-in via Namespace or Pod labels. See the [Automatic Injection guide]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for more details. -- Removed `disableAutoInjection` and `enabledNamespaces` configuration fields. -- Removed deprecated OpenTracing support in favor of OpenTelemetry. -- Fixed issues that would prevent NGINX Service Mesh from deploying in OpenShift. -- `nginx-mesh-api` component has been renamed to `nginx-mesh-controller`. -- Helm chart version has been bumped to match the product version. - - - - -### **Resolved Issues** - -This release includes fixes for the following issues. -

- - -- Pods can't be created if nginx-mesh-api is unreachable (384) - -- Pods fail to deploy if invalid Jaeger tracing address is set (540) - -
- - - -### **Known Issues** - -The following issues are known to be present in this release. Look for updates to these issues in future NGINX Service Mesh release notes. -
- - -
**Lingering invalid RateLimits can cause restart inconsistencies with the NGINX Service Mesh control plane. (658)**: -
- -The NGINX Service Mesh control plane has a validating webhook that will reject the majority of RateLimits that conflict with an existing RateLimit. However, there are some cases where the validating webhook is unable to determine a conflict. In these cases, the NGINX Service Mesh control plane process will catch the conflict and prevent configuration of the offending RateLimit, but the RateLimit will still be stored in Kubernetes. These RateLimit resources are invalid and can be found by looking for a `Warning` event on the RateLimit object. If invalid RateLimits exist and the NGINX Service Mesh control plane restarts, the invalid RateLimits may be configured over the previous valid RateLimits. -
-
- Workaround: -
- -When you create a RateLimit resource in Kubernetes, run `kubectl describe ratelimit ` and check for any `Warning` events. If a `Warning` event exists, either fix the conflict described in the `Warning` event message, or delete the RateLimit by running: `kubectl delete ratelimit `. - - -
**Duplicate targetPorts in a Service are disregarded (532)**: -
- -NGINX Service Mesh supports a variety of Service `.spec.ports\[]` configurations and honors each port list item with one exception. - -If the Service lists multiple port configurations that duplicate `.spec.ports\[].targetPort`, the duplicates are disregarded. Only one port configuration is honored for traffic forwarding, authentication, and encryption. - -Example invalid configuration: - - -```yaml -apiVersion: v1 -kind: Service -spec: -ports: -- port: 8080 - protocol: TCP - targetPort: 55555 -- port: 9090 - protocol: TCP - targetPort: 55555 -``` - -
-
- Workaround: -
- -No workaround exists outside of reconfiguring the Service and application. The Service must use unique `.spec.ports[].targetPort` values (open up multiple ports on the application workload) or route all traffic to the application workload through the same Service port. - - -
**NGINX Service Mesh DNS Suffix support (519)**: -
- -NGINX Service Mesh only supports the `cluster.local` DNS suffix. Services such as Grafana and Prometheus will not work in clusters with a custom DNS suffix. -
-
- Workaround: -
- -Ensure your cluster is setup with the default `cluster.local` DNS suffix. - - diff --git a/content/mesh/support/_index.md b/content/mesh/support/_index.md deleted file mode 100644 index 8f0922116..000000000 --- a/content/mesh/support/_index.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Support -weight: 600 -description: Contact Support and troubleshoot F5 NGINX Service Mesh. -draft: false -url: /nginx-service-mesh/support/ ---- - diff --git a/content/mesh/support/contact-support.md b/content/mesh/support/contact-support.md deleted file mode 100644 index 04c4cec67..000000000 --- a/content/mesh/support/contact-support.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Where to Go for Support -weight: 100 -description: Learn where to go for F5 NGINX Service Mesh support. -toc: true -draft: false -nd-docs: DOCS-718 -type: -- task ---- - -Thank you for your interest in F5 NGINX Service Mesh. NGINX Service Mesh can be installed via helm charts, or by [downloading nginx-meshctl](https://github.com/nginxinc/nginx-service-mesh/releases/latest). - - -### Resources - -- [Product Details](https://www.nginx.com/products/nginx-service-mesh/) -- [Install NGINX Service Mesh using nginx-meshctl]( {{< ref "/mesh/get-started/install/install.md" >}} ) -- [Install NGINX Service Mesh using Helm]( {{< ref "/mesh/get-started/install/install-with-helm.md" >}} ) -- [NGINX Plus Kubernetes Ingress Controller](https://www.nginx.com/products/nginx-ingress-controller/) -- [NGINX Plus Product Details](https://www.nginx.com/products/nginx/) - -### Commercial Support - -For paid customers of NGINX Service Mesh, follow the procedures described in the following knowledge base article: [K000140156](https://my.f5.com/manage/s/article/K000140156/). - -#### Generate a Support Package - -To generate a support package, run the following command: - -```bash -nginx-meshctl supportpkg -``` - -To exclude information about sidecar containers: - -```bash -nginx-meshctl supportpkg --disable-sidecar-logs -``` - -The `supportpkg` command generates a tar file containing information about the state of your NGINX Service Mesh deployment. The package includes logs, component yaml files, events, Pod descriptions, and more. The README contained within the package describes its contents. - -### GitHub Issues - -{{< call-out "note" >}} -Paid customers of NGINX Service Mesh should use [Commercial Support](#commercial-support) instead of GitHub Issues. -{{< /call-out >}} - -For NGINX Service Mesh support or issues not addressed by documentation, you can reach out via the Issues tab in the [NGINX Service Mesh GitHub repo](https://github.com/nginxinc/nginx-service-mesh/issues). - -We use issues for bug reports and to discuss new features. Creating issues is good, creating good issues is even better. Filing meaningful bug reports with lots of information in them helps us figure out what to fix when and how it impacts our users. We like bugs because it means people are using our code, and we like fixing them even more. - -All issues should follow these guidelines: - -- Describe the problem. Include version of Kubernetes, `nginx-meshctl version`, and what Kubernetes platform. -- Include detailed information about how to recreate the issue. -- Include relevant configurations, error messages, and logs. -- Sanitize the data. For example, be mindful of IPs, ports, application names, and URLs. - - -### NGINX Plus Kubernetes Ingress Controller Support - -If you are using NGINX Service Mesh with NGINX Plus Ingress Controller for Kubernetes, you can get support through your usual channels. - -Existing NGINX and F5 customers can reach out to their account team(s) for help and support with NGINX Service Mesh. diff --git a/content/mesh/tutorials/_index.md b/content/mesh/tutorials/_index.md deleted file mode 100644 index 641c2bb9d..000000000 --- a/content/mesh/tutorials/_index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Tutorials -weight: 200 -description: Walkthrough docs for using F5 NGINX Service Mesh. -url: /nginx-service-mesh/tutorials/ ---- - diff --git a/content/mesh/tutorials/accesscontrol-walkthrough.md b/content/mesh/tutorials/accesscontrol-walkthrough.md deleted file mode 100644 index befb02b62..000000000 --- a/content/mesh/tutorials/accesscontrol-walkthrough.md +++ /dev/null @@ -1,360 +0,0 @@ ---- -title: "Services using Access Control" -description: "This article provides a guide for using access control between services." -weight: 130 -toc: true -nd-docs: "DOCS-719" ---- - -## Overview - -You can use access control to shape traffic within your cluster and mesh. By default all services within the mesh can freely communicate, which might not be appropriate for larger production grade microservices. If traffic shaping is necessary, you can use access control resources to allow traffic to and from specific source and destination endpoints. You can apply basic rules at the L4 layer, and apply more complex, granular rules at the L7 HTTP layer. - -The access control mode can be [set to `deny` at the global level]( {{< ref "/mesh/get-started/install/configuration.md#access-control" >}} ), which prevents any traffic from flowing until access control policies are defined. This tutorial assumes that the access control mode is set to the default value of `allow`. - -## Before You Begin - -1. Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/). -1. [Deploy F5 NGINX Service Mesh]({{< ref "/mesh/get-started/install/install.md" >}}) in your Kubernetes cluster. -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Download all of the example files: - - - {{< icon "download" >}} {{< link "/examples/dest-svc.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/access.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/driver-allowed.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/driver-disallowed.yaml" >}} - -## Objectives - -Follow the steps in this guide to learn how to use access control between services. - -### Deploy the Destination Service - -1. To begin, we'll deploy a destination target server as a Deployment and ConfigMap, a destination Service, and a ServiceAccount to provide a TrafficTarget destination resource. - {{< call-out "tip" >}} -ServiceAccount resources are used to classify sets of workloads for access control. Multiple different types of workloads can participate in the same ServiceAccount to create M:N traffic relationships, or scaled down to a workload type per ServiceAccount for more granular control of communications. For example, a collection of frontend services that all need access to authentication or SSO endpoints can be classified together within a ServiceAccount to simplify configuration. - {{< /call-out >}} - - **Command:** - - ```bash - kubectl apply -f dest-svc.yaml - ``` - - **Expectation:** Deployment, Service, ServiceAccount, and ConfigMap resources are deployed successfully. - - Use `kubectl` to make sure the resources deploy successfully. - - ```bash - kubectl get pods - NAME READY STATUS RESTARTS AGE - dest-svc-69f4b86fb4-r8wzh 2/2 Running 0 2m - ``` - - For other resource types -- for example, Deployments, ConfigMaps, Services, or ServiceAccounts -- use `kubectl get` for each type as appropriate. - -1. Once the destination workload is ready, we can generate unfiltered traffic. Use a separate terminal window in order to watch traffic flow as a request driver begins sending requests. - - **Commands:** - - Stream the destination workload's logs in your previous terminal: - - ```bash - kubectl logs -l app=dest-svc -f -c dest-svc - ``` - - - Start an unfiltered driver to send request traffic: - - ```bash - kubectl apply -f driver-allowed.yaml - ``` - - **Expectation:** Requests will start 10 seconds after the driver-allowed pod becomes ready. The log stream should begin showing activity by responding to requests. - - For additional verification use the `nginx-meshctl top` command to view traffic statistics. - - ```bash - nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - driver-allowed 100.00% 15 - dest-svc 100.00% 15 - ``` - -1. Once traffic is flowing unfiltered between the driver and workload, open a third terminal to establish a second driver workload. This traffic will start unfiltered and will be restricted as we proceed. - - **Commands:** - - Start an unfiltered driver to send request traffic: - - ```bash - kubectl apply -f driver-disallowed.yaml - ``` - - - Stream the new driver's logs: - - ```bash - kubectl logs -l app=driver-disallowed -f -c driver - ``` - - **Expectation:** Requests will start 10 seconds after the driver-disallowed pod becomes ready. The log stream should begin showing successful activity with response output. - - Example: - - ```bash - * Trying 10.100.5.18:8080... - * Connected to dest-svc (10.100.5.18) port 8080 (#0) - > GET /echo HTTP/1.1 - > Host: dest-svc:8080 - > User-Agent: curl/7.72.0-DEV - > Accept: */* - > x-demo-1:demo-1 - > x-demo-2:demo-2 - > x-demo-3:demo-3 - > - * Mark bundle as not supporting multiuse - < HTTP/1.1 200 OK - < Server: nginx/1.19.0 - < Date: Wed, 23 Sep 2020 23:51:31 GMT - < Content-Type: text/plain - < Content-Length: 20 - < Connection: keep-alive - < X-Mesh-Request-ID: 45d9b1ffc53bde6aa5478a0d688894d5 - < - { [20 bytes data] - * Connection #0 to host dest-svc left intact - destination service - ``` - - Once again, verify traffic statistics using `nginx-meshclt top`. - - ```bash - nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - driver-allowed 100.00% 15 - driver-disallowed 100.00% 15 - dest-svc 100.00% 30 - ``` - -1. At this point, traffic should be freely flowing between each workload. We can now apply an HTTPRouteGroup and TrafficTarget to restrict traffic. The TrafficTarget resource establishes the source and destination relationship. It also applies the selected rules to further refine what traffic should flow between the various services. The rules are expressed via HTTPRouteGroup and TCPRoute resources; these examples will use the HTTPRouteGroup rule specifications. - - **Command:** - - Apply the access controls: - - ```bash - kubectl apply -f access.yaml - ``` - - **Expectation:** Once applied there should be no change in traffic between the driver-allowed and dest-svc workloads. The driver-disallowed should begin receiving HTTP 403 Forbidden errors. - - Example: - - ```bash - * Trying 10.100.5.18:8080... - * Connected to dest-svc (10.100.5.18) port 8080 (#0) - > GET /echo HTTP/1.1 - > Host: dest-svc:8080 - > User-Agent: curl/7.72.0-DEV - > Accept: */* - > x-demo-1:demo-1 - > x-demo-2:demo-2 - > x-demo-3:demo-3 - > - * Mark bundle as not supporting multiuse - < HTTP/1.1 403 Forbidden - < Server: nginx/1.19.0 - < Date: Wed, 23 Sep 2020 23:53:56 GMT - < Content-Type: text/html - < Content-Length: 153 - < Connection: keep-alive - < - { [153 bytes data] - * Connection #0 to host dest-svc left intact - - 403 Forbidden - -

403 Forbidden

-
nginx/1.19.0
- - - ``` - - Verify with `nginx-meshctl top`; driver-disallowed will display a 0 success rate. - - ```bash - nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - driver-allowed 100.00% 15 - driver-disallowed 0.00% 15 - dest-svc 100.00% 30 - ``` - - Let's take a closer look at what we've configured. We now have this configuration topology: - - ```txt - -------------- - | driver | ----------- - | allowed | -> | sidecar | -- - -------------- ----------- \ - \ ----------- ------------ - --> | sidecar | -> | dest-svc | - / ----------- ------------ - -------------- ----------- / - | driver | -> | sidecar | -- - | disallowed | ----------- - -------------- - ``` - - Each driver is sending this request: - - ```txt - GET HTTP/1.1 /echo - Host: dest-svc:8080 - x-demo-1:demo-1 - x-demo-2:demo-2 - x-demo-3:demo-3 - ``` - - And we've configured these access control constraints: - - ```yaml - apiVersion: access.smi-spec.io/v1alpha2 - kind: TrafficTarget - metadata: - name: traffic-target - spec: - destination: - kind: ServiceAccount - name: destination-sa - rules: - - kind: HTTPRouteGroup - name: route-group - matches: - - destination-traffic - sources: - - kind: ServiceAccount - name: source-allowed-sa - ``` - - ```yaml - apiVersion: specs.smi-spec.io/v1alpha3 - kind: HTTPRouteGroup - metadata: - name: route-group - spec: - matches: - - name: destination-traffic - methods: - - GET - pathRegex: "/echo" - headers: - X-Demo-1: "^demo-1$" - x-demo-2: "demo" - ``` - - Let's take a look at the subsequent configuration. - The TrafficTarget `.spec.sources` and `.spec.destination` reference the allowed source and destination identities; this TrafficTarget configuration allows traffic from the ServiceAccount `source-allowed-sa` to the ServiceAccount `destination-sa`. - Additionally, the `.spec.rules` configuration maps the HTTPRouteGroup's `.spec.matches` directives to the TrafficTarget. The match directive allows `GET` methods to the `/echo` path regex, with the headers `X-Demo-1: ^demo-1$` and `x-demo-2: demo` regex values. - {{< call-out "note" >}} -The header capitalization mismatches intentionally, header names are not case-sensitive and they match regardless of case. - {{< /call-out >}} - We've configured our driver-allowed workload in the `source-allowed-sa` ServiceAccount (that is to say, we've given it the `source-allowed-sa` identity). But our driver-disallowed workload is configured in the `source-disallowed-sa` ServiceAccount. This source identity is not allowed, so even traffic which passes our filtering rules remains forbidden. - -1. Activate previously disallowed traffic. - - **Command:** - - ```bash - kubectl edit traffictarget traffic-target - ``` - - Add the previously denied source: - - ```yaml - apiVersion: access.smi-spec.io/v1alpha2 - kind: TrafficTarget - metadata: - name: traffic-target - spec: - destination: - kind: ServiceAccount - name: destination-sa - rules: - - kind: HTTPRouteGroup - name: route-group - matches: - - destination-traffic - sources: - - kind: ServiceAccount - name: source-allowed-sa - - kind: ServiceAccount - name: source-disallowed-sa - ``` - - **Expectation:** Without restarting, HUP'ing, or re-rolling Pods or Deployments the traffic should begin to succeed for the driver-disallowed workload. - -### Summary - -You should now have a functioning access control configuration that shapes the topology of your mesh. The configuration provided here is very flexible and we encourage you to continue to experiment with different configurations. The provided drivers can be configured to send different methods, different paths, different headers, and to the Service name of your choice. - -Each driver's ConfigMap supports the following options: - -{{% table %}} -Parameter | Type | Description ----|---|--- -`host` | string | base URL of target Service -`request_path` | string | request path -`method` | string | HTTP method to use -`headers` | string | comma-delimited list of additional request headers to include -{{% /table %}} - -The destination workload can be set to serve different ports, or multiple ports. To configure the destination workload, edit the `dest-svc.yaml` file. An example configuration is shown below: - -NGINX `dest-svc` Configuration: - -- Update the Pod container port: `.spec.template.spec.containers[0].ports[0].containerPort`. -- Update the ConfigMap NGINX listen port: `.data.nginx.conf: http.server.listen`. -- Update the Service port: `.spec.ports[0].port`. - -The following examples show snippets of the relevant sections: - - ```yaml - --- - kind: Deployment - spec: - template: - spec: - containers: - - name: example - - containerPort: 55555 - --- - apiVersion: v1 - kind: ConfigMap - metadata: - name: dest-svc - data: - nginx.conf: |- - events {} - http { - server { - listen 55555; - location / { - return 200 "destination service\n"; - } - } - } - --- - kind: Service - spec: - ports: - - port: 55555 - ``` - -Traffic can be filtered via sets that are classified via ServiceAccounts. But [TrafficSpecs](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-specs/v1alpha3/traffic-specs.md) provide additional powerful configurations; lists of HTTP methods, path regular expression matching, header regular expression matching, and specific ports. - {{< call-out "tip" >}} -For exact matches, be sure to use regular expression anchors. To exactly match the header value `hello`, be sure to use `^hello$`; otherwise, additional headers that contain the sequence `hello` will be allowed. - {{< /call-out >}} -{{< call-out "tip" >}} -For an expanded example showing configuration for an application using a headless service, checkout our example for clustered application traffic policies {{< icon "download" >}} {{< link "/examples/clustered-application.yaml" >}} -{{< /call-out >}} - -## Resources - -- [SMI Traffic Access Example on GitHub](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-access/v1alpha2/traffic-access.md#example-implementation) (external) diff --git a/content/mesh/tutorials/deploy-example-app.md b/content/mesh/tutorials/deploy-example-app.md deleted file mode 100644 index 3439e022b..000000000 --- a/content/mesh/tutorials/deploy-example-app.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Deploy an Example App with NGINX Service Mesh -draft: false -toc: true -description: This topic provides a walkthrough of deploying an App with F5 NGINX Service - Mesh. -weight: 100 -nd-docs: DOCS-720 -type: -- tutorial ---- - -## Overview - -In this tutorial, we will use the `bookinfo` example app Deployment. - -- {{< icon "download" >}} {{< link "examples/bookinfo.yaml" "examples/bookinfo.yaml" >}} - -{{< call-out "note" >}} -Notice in the above yaml: - -- All of the service spec port names are populated with the name of the protocol -- All deployment `containerPort` fields are specified. - -This is used in the mesh to identify the kind of traffic being sent and where it is allowed to be received. For more information on deployment and service identification rules, see [identification-rules]( {{< ref "/mesh/get-started/install/configuration.md#identification-rules" >}} ) in the Getting Started section. -{{< /call-out >}} - -{{< call-out "note" >}} -Review the [ports reserved by F5 NGINX Service Mesh sidecar]( {{< ref "/mesh/about/mesh-tech-specs.md#ports" >}} ) and make sure there are no overlaps with ports used by your applications. -{{< /call-out >}} - -## Prerequisite -Ensure that you have deployed Prometheus, Grafana, and a tracing backend and configured NGINX Service Mesh to export data to them. Refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) guide for instructions. - -## Inject the Sidecar Proxy - -You can use either [automatic]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) or [manual injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#manual-proxy-injection" >}} ) to add the NGINX Service Mesh sidecar containers to your application pods. - -### Inject the Sidecar Proxy Automatically - -To enable automatic sidecar injection for all Pods in a namespace, add the `injector.nsm.nginx.com/auto-inject=enabled` label to the namespace. - -```bash -kubectl label namespaces default injector.nsm.nginx.com/auto-inject=enabled -``` - -With auto-injection enabled, you can use `kubectl` to apply your Deployment as normal. -The sidecar proxy will be added and runs automatically. - -```bash -kubectl apply -f examples/bookinfo.yaml -``` - -### Inject the Sidecar Proxy Manually - -You can inject the sidecar proxy into your Deployment manually by using the [`nginx-meshctl inject`]( {{< ref "nginx-meshctl.md#inject" >}} ) CLI command. -You can then `kubectl apply` the Deployment as usual, or pipe the command directly to `kubectl`: - -```bash -nginx-meshctl inject < examples/bookinfo.yaml | kubectl apply -f - -``` - -## Verify that the Sample App Works Correctly - -1. Port-forward to the `productpage` Service: - - ```bash - kubectl port-forward svc/productpage 9080 - ``` - -2. Open the Service URL in a browser: `http://localhost:9080`. -3. Click one of the links to view the app as a general user, then as a test user, and verify that all portions of the page load. - -## Verify Observability - -### Check the Grafana dashboard - -[Grafana](https://grafana.com/grafana/) is the observability dashboard used to visualize Prometheus metrics for applications in NGINX Service Mesh. - -1. Port-forward your Grafana Service: - - ```bash - kubectl -n port-forward svc/grafana 3000 - ``` - -2. Open the Grafana URL in a browser: `http://localhost:3000`. - -### Check Prometheus metrics - -[Prometheus](https://prometheus.io/docs/concepts/data_model/) is the systems monitoring tool used to collect metrics, such as request time and success rate, from applications in NGINX Service Mesh. - - -1. Port-forward your Prometheus Service: - - ```bash - kubectl -n port-forward svc/prometheus 9090 - ``` - -2. Open the Prometheus server URL in a browser window: `http://localhost:9090/graph` - -### Check Tracing - -[Tracing](https://opentelemetry.io/docs/concepts/data-sources/#traces) is used to track and profile requests as they pass through applications, and is collected using services such as [Jaeger](https://www.jaegertracing.io/), [DataDog](https://docs.datadoghq.com/tracing/), or [LightStep](https://lightstep.com/). - -1. Port-forward your tracing Service: - - ```bash - kubectl -n port-forward svc/ - ``` - -2. Open the tracing server URL in a browser. For example, you might access the Jaeger server at `http://localhost:16686`. diff --git a/content/mesh/tutorials/kic/_index.md b/content/mesh/tutorials/kic/_index.md deleted file mode 100644 index fe4013577..000000000 --- a/content/mesh/tutorials/kic/_index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: NGINX Ingress Controller -weight: 5 -description: "Learn how to deploy NGINX Ingress Controller with NGINX Service Mesh." -url: /nginx-service-mesh/tutorials/kic/ ---- \ No newline at end of file diff --git a/content/mesh/tutorials/kic/deploy-with-kic.md b/content/mesh/tutorials/kic/deploy-with-kic.md deleted file mode 100644 index f3d339638..000000000 --- a/content/mesh/tutorials/kic/deploy-with-kic.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -title: Deploy with NGINX Ingress Controller -description: This topic describes how to install and use F5 NGINX Ingress Controller - with NGINX Service Mesh -weight: 200 -draft: false -toc: true -nd-docs: DOCS-721 -type: -- tutorial ---- - -## Overview - -You can deploy NGINX Ingress Controller for Kubernetes with NGINX Service Mesh to control both ingress and [egress](#enable-egress) traffic. - -{{< call-out "important" >}} -NGINX Ingress Controller can be used for free with NGINX Open Source. Paying customers have access to NGINX Ingress Controller with NGINX Plus. -To deploy NGINX Ingress Controller with NGINX Service Mesh, you must use either: - -- Open Source NGINX Ingress Controller version 3.0+ -- NGINX Plus version of NGINX Ingress Controller - -Visit the [NGINX Ingress Controller](https://www.nginx.com/products/nginx-ingress-controller/) product page for more information. -{{< /call-out >}} - -## Supported Versions - -The supported NGINX Plus Ingress Controller versions for each release are listed in the [technical specifications]({{< ref "/mesh/about/mesh-tech-specs.md#supported-versions" >}}) doc. - -The documentation for the latest stable release of NGINX Ingress Controller is available at [docs.nginx.com/nginx-ingress-controller](https://docs.nginx.com/nginx-ingress-controller/). -For version specific documentation, deployment configs, and configuration examples, select the tag corresponding to your desired version in [GitHub](https://github.com/nginxinc/kubernetes-ingress/tags). - -## Secure Communication Between NGINX Ingress Controller and NGINX Service Mesh - -The NGINX Ingress Controller can participate in the mTLS cert exchange with services in the mesh without being injected with the sidecar proxy. The SPIRE server - the certificate authority of the mesh - issues certs and keys for NGINX Ingress Controller and pushes them to the SPIRE agents running on each node in the cluster. NGINX Ingress Controller fetches these certs and keys from the SPIRE agent via a unix socket and uses them to communicate with services in the mesh. - -## Cert Rotation with NGINX Ingress Controller - -The `ttl` of the SVID certificates issued by SPIRE is set to `1hr` by default. You can change this when deploying the mesh; refer to the [nginx-meshctl]({{< ref "nginx-meshctl.md" >}}) documentation for more information. - -When using NGINX Ingress Controller with mTLS enabled, it is best practice to keep the `ttl` at 1 hour or greater. - -## Install NGINX Ingress Controller with mTLS enabled - -To configure NGINX Ingress Controller to communicate with mesh workloads over mTLS you need to make a modification to the Ingress Controller's Pod spec. This section describes each modification that is required, but if you'd like to jump to installation, go to the [Install with Manifests](#install-with-manifests) or [Install with Helm](#install-with-helm) sections. - -1. Add NGINX Service Mesh label - - One or both of the following labels must be added to the Ingress Controller's Pod spec, depending on your use case: - - ```yaml - labels: - nsm.nginx.com/enable-ingress: "true" - ... - ``` - - ```yaml - labels: - nsm.nginx.com/enable-egress: "true" - ... - ``` - - These labels tell NGINX Service Mesh to mutate the Ingress Controller Pod with the proper configuration in order to properly integrate with the mesh. - {{< call-out "note" >}} - For more information, see [How NGINX Ingress Controller Integrates with NGINX Service Mesh](#how-nginx-ingress-controller-integrates-with-nginx-service-mesh). - {{< /call-out>}} - -{{< call-out "note" >}} -All communication between NGINX Ingress Controller and the upstream Services occurs over mTLS, using the certificates and keys generated by the SPIRE server. -Therefore, NGINX Ingress Controller can only route traffic to Services in the mesh that have an `mtls-mode` of `permissive` or `strict`. -In cases where you need to route traffic to both mTLS and non-mTLS Services, you may need another Ingress Controller that does not participate in the mTLS fabric. - -Refer to the NGINX Ingress Controller's [Running Multiple Ingress Controllers](https://docs.nginx.com/nginx-ingress-controller/installation/run-multiple-ingress-controllers/) guide for instructions on how to configure multiple Ingress Controllers. -{{< /call-out >}} - - -If you would like to enable egress traffic, refer to the [Enable Egress](#enable-egress) section of this guide. - -### Install with Manifests - -Before installing NGINX Ingress Controller, you must install NGINX Service Mesh with an [mTLS mode]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) of `permissive`, or `strict`. -NGINX Ingress Controller will try to fetch certs from the SPIRE agent on startup. If it cannot reach the SPIRE agent, startup will fail, and NGINX Ingress Controller will go into CrashLoopBackoff state. The state will resolve once NGINX Ingress Controller connects to the SPIRE agent. -For instructions on how to install NGINX Service Mesh, see the [Installation]({{< ref "/mesh/get-started/install/install.md" >}}) guide. - -{{< call-out "note" >}} -Before continuing, check the NGINX Ingress Controller [supported versions](#supported-versions) section and make sure you are working off the correct release tag for all NGINX Ingress Controller instructions. -{{< /call-out >}} - -#### NGINX OSS Ingress Controller - -1. Build or Pull the NGINX OSS Ingress Controller image: - - [Create and push your NGINX Docker image](https://docs.nginx.com/nginx-ingress-controller/installation/build-nginx-ingress-controller/). - - For NGINX OSS Ingress you can also [pull the NGINX Docker image](https://docs.nginx.com/nginx-ingress-controller/installation/nic-images/). -1. Set up Kubernetes Resources for [NGINX Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) using Kubernetes manifests: - - [Configure role-based access control (RBAC)](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#1-configure-rbac) - - [Create Common Resources](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#2-create-common-resources) -1. Create the NGINX Ingress Controller as a **Deployment** or **DaemonSet** in Kubernetes using one of the following example manifests: - - Kubernetes Deployment: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/oss/nginx-ingress.yaml" "`nginx-ingress-controller/oss/nginx-ingress.yaml`" >}} - - Kubernetes DaemonSet: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/oss/nginx-ingress-daemonset.yaml" "`nginx-ingress-controller/oss/nginx-ingress-daemonset.yaml`" >}} - - OpenShift Deployment: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/oss/openshift/nginx-ingress.yaml" "`nginx-ingress-controller/oss/openshift/nginx-ingress.yaml`" >}} - - Openshift DaemonSet: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/oss/openshift/nginx-ingress-daemonset.yaml" "`nginx-ingress-controller/oss/openshift/nginx-ingress-daemonset.yaml`" >}} - {{< call-out "note" >}} The provided manifests configure NGINX Ingress Controller for ingress traffic only. If you would like to enable egress traffic, refer to the [Enable Egress](#enable-with-manifests) section of this guide. {{< /call-out >}} - {{< call-out "important" >}} Be sure to replace the `nginx-ingress:version` image used in the manifest with the chosen image from a supported Container registry; or the container image that you have built. {{< /call-out >}} - - - *OpenShift only*: - - Download the SecurityContextConstraint necessary to run NGINX Ingress Controller in an OpenShift environment. - - - {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/oss/openshift/nic-scc.yaml" "`nginx-ingress-controller/oss/openshift/nic-scc.yaml`" >}} - - - Apply the `nginx-ingress-permissions` SecurityContextConstraint: - - ```bash - kubectl apply -f nic-scc.yaml - ``` - - - Install the OpenShift CLI by following the steps in their [documentation](https://docs.openshift.com/container-platform/4.7/cli_reference/openshift_cli/getting-started-cli.html#installing-openshift-cli). - - - Add the `nginx-ingress-permissions` to the ServiceAccount of the NGINX Ingress Controller. - - ```bash - oc adm policy add-scc-to-user nginx-ingress-permissions -z nginx-ingress -n nginx-ingress - ``` - -#### NGINX Plus Ingress Controller - -1. Build or Pull the NGINX Plus Ingress Controller image: - - [Create and push your NGINX Plus Docker image](https://docs.nginx.com/nginx-ingress-controller/installation/build-nginx-ingress-controller/). - - For NGINX Plus Ingress releases >= `1.12.0` you can also [pull the NGINX Plus Docker image](https://docs.nginx.com/nginx-ingress-controller/installation/nic-images/). -1. Set up Kubernetes Resources for [NGINX Plus Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/) using Kubernetes manifests: - - [Configure role-based access control (RBAC)](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#1-configure-rbac) - - [Create Common Resources](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#2-create-common-resources) -1. Create the NGINX Plus Ingress Controller as a **Deployment** or **DaemonSet** in Kubernetes using one of the following example manifests: - - Kubernetes Deployment: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/plus/nginx-plus-ingress.yaml" "`nginx-ingress-controller/plus/nginx-plus-ingress.yaml`" >}} - - Kubernetes DaemonSet: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/plus/nginx-plus-ingress-daemonset.yaml" "`nginx-ingress-controller/plus/nginx-plus-ingress-daemonset.yaml`" >}} - - OpenShift Deployment: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/openshift/nginx-plus-ingress.yaml" "`nginx-ingress-controller/openshift/nginx-plus-ingress.yaml`" >}} - - Openshift DaemonSet: {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/openshift/nginx-plus-ingress-daemonset.yaml" "`nginx-ingress-controller/openshift/nginx-plus-ingress-daemonset.yaml`" >}} - {{< call-out "note" >}} The provided manifests configure NGINX Plus Ingress Controller for ingress traffic only. If you would like to enable egress traffic, refer to the [Enable Egress](#enable-with-manifests) section of this guide. {{< /call-out >}} - {{< call-out "important" >}} Be sure to replace the `nginx-plus-ingress:version` image used in the manifest with the chosen image from the F5 Container registry; or the container image that you have built. {{< /call-out >}} - - - *OpenShift only*: - - Download the SecurityContextConstraint necessary to run NGINX Ingress Controller in an OpenShift environment. - - - {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/openshift/nic-scc.yaml" "`nginx-ingress-controller/openshift/nic-scc.yaml`" >}} - - - Apply the `nginx-ingress-permissions` SecurityContextConstraint: - - ```bash - kubectl apply -f nic-scc.yaml - ``` - - - Install the OpenShift CLI by following the steps in their [documentation](https://docs.openshift.com/container-platform/4.7/cli_reference/openshift_cli/getting-started-cli.html#installing-openshift-cli). - - - Add the `nginx-ingress-permissions` to the ServiceAccount of the NGINX Plus Ingress Controller. - - ```bash - oc adm policy add-scc-to-user nginx-ingress-permissions -z nginx-ingress -n nginx-ingress - ``` - -### Install with Helm - -Before installing NGINX Ingress Controller, you must install NGINX Service Mesh with an [mTLS mode]({{< ref "/mesh/guides/secure-traffic-mtls.md" >}}) of `permissive`, or `strict`. -NGINX Ingress Controller will try to fetch certs from the SPIRE agent on startup. If it cannot reach the SPIRE agent, startup will fail, and NGINX Ingress Controller will go into CrashLoopBackoff state. The state will resolve once NGINX Ingress Controller connects to the SPIRE agent. -For instructions on how to install NGINX Service Mesh, see the [Installation]({{< ref "/mesh/get-started/install/install.md" >}}) guide. - -{{< call-out "note" >}} NGINX Plus Ingress Controller v2.2+ or NGINX OSS Ingress Controller v3.0+ is required to deploy via Helm and integrate with NGINX Service Mesh. {{< /call-out >}} - -Follow the [instructions](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/) to install the NGINX Ingress Controller with Helm. -Set the `nginxServiceMesh.enable` parameter to `true`. -{{< call-out "note" >}} This will configure NGINX Ingress Controller to route ingress traffic to NGINX Service Mesh workloads. If you would like to enable egress traffic, refer to the [Enable Egress](#enable-with-helm) section of this guide. {{< /call-out >}} - -The [`values-nsm.yaml`](https://github.com/nginxinc/kubernetes-ingress/blob/main/charts/nginx-ingress/values-nsm.yaml) file contains all the configuration parameters that are relevant for integration with NGINX Service Mesh. You can use this file if you are installing NGINX Ingress Controller via chart sources. - -## Expose your applications - -With mTLS enabled, you can use Kubernetes [Ingress](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/), [VirtualServer, and VirtualServerRoutes](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) resources to configure load balancing for HTTP and gRPC applications. -TCP load balancing via TransportServer resources is not supported. - -{{< call-out "note" >}} -The NGINX Ingress Controller's custom resource [TransportServer](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/) and the SMI Spec's custom resource [TrafficSplit](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-split/v1alpha3/traffic-split.md) share the same Kubernetes short name `ts`. -To avoid conflicts, use the full names `transportserver(s)` and `trafficsplit(s)` when managing these resources with `kubectl`. -{{< /call-out >}} - -To learn how to expose your applications using NGINX Ingress Controller, refer to the [Expose an Application with NGINX Ingress Controller]( {{< ref "/mesh/tutorials/kic/ingress-walkthrough.md" >}} ) tutorial. - -## Enable Egress - -You can configure NGINX Ingress Controller to act as the egress endpoint of the mesh, enabling your meshed services to communicate securely with external, non-meshed services. - -{{< call-out "note" >}} Multiple endpoints for a single egress deployment are supported, but multiple egress -deployments are not supported. {{< /call-out >}} - -### Enable with Manifests -If you are installing NGINX Ingress Controller with manifests follow the [Install with Manifests](#install-with-manifests) instructions and make the following change to the NGINX Ingress Controller Pod spec: - -- Add the following label to the NGINX Ingress Controller Pod spec: - - ```yaml - labels: - nsm.nginx.com/enable-egress: "true" - ... - ``` - - This label prevents automatic injection of the sidecar proxy and configures the NGINX Ingress Controller as the egress endpoint of the mesh. - - This will create a virtual server block in NGINX Ingress Controller that terminates TLS connections using the SPIFFE certs fetched from the SPIRE agent. - -### Enable with Helm - -{{< call-out "note" >}} NGINX Plus Ingress Controller v2.2+ or NGINX OSS Ingress Controller v3.0+ is required to deploy via Helm and integrate with NGINX Service Mesh. {{< /call-out >}} - -If you are installing NGINX Ingress Controller with Helm, follow the [Install with Helm](#install-with-helm) instructions and set `nginxServiceMesh.enableEgress` to `true`. - -### Allow Pods to route egress traffic through NGINX Ingress Controller - -If egress is enabled you can configure Pods to route **all** egress traffic - requests to non-meshed services - through NGINX Ingress Controller. -This feature can be enabled by adding the following annotation to the Pod spec of an application Pod: - -```bash -config.nsm.nginx.com/default-egress-allowed: "true" -``` - -This annotation can be removed or changed after deployment and the egress behavior of the Pod will be updated accordingly. - -### Create internal routes for non-meshed services - -Internal routes represent a route from NGINX Ingress Controller to a non-meshed service. -This route is called "internal" because it is only accessible from a Pod in the mesh and is not accessible from the public internet. - -{{< call-out "caution" >}} -If you deploy NGINX Ingress Controller without mTLS enabled, the internal routes could be accessible from the public internet. -We do not recommend using the egress feature with a plaintext deployment of NGINX Ingress Controller. -{{< /call-out >}} - -To generate an internal route, create an [Ingress resource](https://docs.nginx.com/nginx-ingress-controller/configuration/ingress-resources/) or [VirtualServer resource](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/) using the information for your non-meshed service. - -Add the following configuration, depending on the type of resource you created: - -- For an Ingress resource, add the following annotation to the resource definition: - - ```bash -nsm.nginx.com/internal-route: "true" -``` - -- For a VirtualServer resource, add the following field to the custom resource definition: - - ```yaml -spec: - internalRoute: true -``` - -If your non-meshed service is external to Kubernetes, follow the [ExternalName services example](https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/custom-resources/externalname-services). - -{{< call-out "note" >}} -The `nsm.nginx.com/internal-route: "true"` Ingress annotation or `internalRoute: true` VirtualServer field is still required for routing to external endpoints. -{{< /call-out >}} - -The [NGINX Ingress Controller egress tutorial]({{< ref "/mesh/tutorials/kic/egress-walkthrough.md" >}}) provides instructions for creating internal routes for non-meshed services. - - -## Enable Ingress and Egress Traffic - -There are a couple ways to enable both ingress and egress traffic using the NGINX Ingress Controller. -You can either allow both ingress and egress traffic through the same NGINX Ingress Controller, -or deploy two NGINX Ingress Controllers: one for handling ingress traffic only and the other for handling egress traffic. - -For the single deployment option, follow the [installation instructions](#install-nginx-ingress-controller-with-mtls-enabled) and the instructions to [Enable Egress](#enable-egress). -If you would like to configure two Ingress Controllers to keep ingress and egress traffic separate you can leverage [Ingress Classes](https://docs.nginx.com/nginx-ingress-controller/installation/run-multiple-ingress-controllers/#ingress-class). - -## Enable UDP Traffic - -By default, NGINX Ingress Controller only routes TCP traffic. You can configure it to route UDP traffic by making the following changes to the NGINX Ingress Controller before deploying: - -- Enable GlobalConfiguration resources for NGINX Ingress Controller by following the setup defined in the [GlobalConfiguration Resource](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/globalconfiguration-resource) documentation. - - This allows you to define global configuration parameters for the NGINX Ingress Controller, and create a UDP listener to route ingress UDP traffic to your backend applications. - -{{< call-out "important" >}} -mTLS does not affect UDP communication, as mTLS in NGINX Service Mesh applies only to TCP traffic at this time. -{{< /call-out >}} - -### Create a GlobalConfiguration Resource - -To allow UDP traffic to be routed to your Kubernetes applications, create a UDP listener in NGINX Ingress Controller. This can be done via a GlobalConfiguration Resource. - -To create a GlobalConfiguration resource, see the NGINX Ingress Controller [documentation](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/globalconfiguration-resource#globalconfiguration-specification) to create a listener with protocol UDP. - -### Ingress UDP Traffic - -You can pass and load balance UDP traffic by using a TransportServer resource. This will link the UDP listener defined in the [Create a GlobalConfiguration Resource](#create-a-globalconfiguration-resource) step with an upstream associated with your designated backend UDP application. - -To create a TransportServer resource, follow the steps outlined in the [TransportServer](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/) NGINX Ingress Controller guide and link the UDP listener with the name and port of your backend service. - -To learn how to expose a UDP application using NGINX Ingress Controller, see the [Expose a UDP Application with NGINX Ingress Controller]({{< ref "/mesh/tutorials/kic/ingress-udp-walkthrough.md" >}}) tutorial. - -## Plaintext configuration - -Deploy NGINX Service Mesh with `mtls-mode` set to `off` and follow the [instructions](https://docs.nginx.com/nginx-ingress-controller/installation) to deploy NGINX Ingress Controller. - -Add the enable-ingress and/or the enable-egress label shown below to the NGINX Ingress Controller Pod spec: - -```bash -nsm.nginx.com/enable-ingress: "true" -nsm.nginx.com/enable-egress: "true" -``` - -{{< call-out "caution" >}} -All communication between NGINX Ingress Controller and the services in the mesh will be over plaintext! -We do not recommend using the egress feature with a plaintext deployment of NGINX Ingress Controller, -it is possible that internal routes could be accessible from the public internet. -We highly recommend [installing NGINX Ingress Controller with mTLS enabled](#install-nginx-ingress-controller-with-mtls-enabled). -{{< /call-out >}} - -## NGINX Ingress Controller Metrics - -To enable metrics collection for the NGINX Ingress Controller, take the following steps: - -1. Run the NGINX Ingress Controller with the `-enable-prometheus-metrics` command line argument. - The NGINX Ingress Controller exposes [NGINX metrics](https://github.com/nginxinc/nginx-prometheus-exporter#exported-metrics) - in Prometheus format via the `/metrics` path on port 9113. This port is customizable via the `-prometheus-metrics-listen-port` command-line argument; consult the - [Command Line Arguments](https://docs.nginx.com/nginx-ingress-controller/configuration/global-configuration/command-line-arguments/) section of the NGINX Ingress Controller docs for more information on available command line arguments. - -{{< call-out "note" >}} -If using the NGINX Plus Ingress Controller, add this additional flag to enable latency metrics: `-enable-latency-metrics` -{{< /call-out >}} - -1. Add the following Prometheus annotations NGINX Ingress Controller Pod spec: - - ```yaml - prometheus.io/scrape: "true" - prometheus.io/port: "" - ``` - -1. Add the resource name as a label to the NGINX Ingress Controller Pod spec: - - - For *Deployment*: - - ```yaml - nsm.nginx.com/deployment: - ``` - - - For *DaemonSet*: - - ```yaml - nsm.nginx.com/daemonset: - ``` - - This allows metrics scraped from NGINX Ingress Controller Pods to be associated with the resource that created the Pods. - -### View the metrics in Prometheus - -The NGINX Service Mesh uses the Pod's container name setting to identify the NGINX Ingress Controller metrics that should be consumed by the Prometheus server. - -Add the applicable `nginx-ingress` scrape config to your Prometheus configuration and consult -[Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md#prometheus" >}} ) for installation instructions. - -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-scrape-config.yaml" "`nginx-ingress-scrape-config.yaml`" >}} -- {{< icon "download" >}} {{< link "/examples/nginx-plus-ingress-scrape-config.yaml" "`nginx-plus-ingress-scrape-config.yaml`" >}} - -## Available metrics -For a list of the NGINX Ingress Controller metrics, consult the [Available Metrics](https://docs.nginx.com/nginx-ingress-controller/logging-and-monitoring/prometheus/#available-metrics) section of the NGINX Ingress Controller docs. - -{{< call-out "note" >}} -The NGINX Plus metrics exported by the NGINX Plus Ingress Controller are renamed from `nginx_ingress_controller_` to `nginxplus_` to be consistent with the metrics exported by NGINX Service Mesh sidecars. -For example, `nginx_ingress_controller_upstream_server_response_latency_ms_count` is renamed to `nginxplus_upstream_server_response_latency_ms_count`. -The Ingress Controller specific metrics, such as `nginx_ingress_controller_nginx_reloads_total`, are not renamed. -{{< /call-out >}} - -For more information on metrics, a list of Prometheus labels, and examples of querying and filtering, see the [Prometheus Metrics]({{< ref "/mesh/guides/prometheus-metrics.md" >}}) doc. - -To view the metrics, use port-forwarding: - -```bash -kubectl port-forward -n nginx-mesh svc/prometheus 9090 -``` - -### Monitor your application in Grafana - -NGINX Service Mesh provides a [custom dashboard](https://github.com/nginxinc/nginx-service-mesh/tree/main/examples/grafana) that you can import into your Grafana deployment to monitor your application. To import and view the dashboard, port-forward your Grafana service: - -```bash -kubectl port-forward -n svc/grafana 3000 -``` - -Then you can navigate your browser to `localhost:3000` to view Grafana. - -Here is a view of the provided "NGINX Mesh Top" dashboard: - -{{< img src="img/grafana.png" >}} - -## How NGINX Ingress Controller Integrates with NGINX Service Mesh - -### Mutating Webhook -NGINX Service Mesh version v1.7+ provides a mutating webhook that detects and configures instances of NGINX Ingress Controller. - -#### Pod Spec Changes - -1. NGINX Service Mesh mounts and configures the SPIRE agent socket based on environment - - The SPIRE agent socket needs to be mounted to the Ingress Controller Pod so the Ingress Controller can fetch its certificates and keys from the SPIRE agent. This allows the Ingress Controller to authenticate with workloads in the mesh. For more information on how SPIRE distributes certificates see the [SPIRE]({{< ref "/mesh/about/architecture#spire" >}}) section in the architecture doc. - -- *Kubernetes* - - The following `hostPath` is added as a volume to the Ingress Controller's Pod spec: - - ```yaml - volumes: - - hostPath: - path: /run/spire/sockets - type: DirectoryOrCreate - name: spire-agent-socket - ``` - - NGINX Service Mesh also mounts the socket to the Ingress Controller's container spec: - - ```yaml - volumeMounts: - - mountPath: /run/spire/sockets - name: spire-agent-socket - ``` - -- *OpenShift* - - To mount the SPIRE agent socket in OpenShift, NGINX Service Mesh adds the following `csi` driver to the Ingress Controller's Pod spec: - - ```yaml - volumes: - - csi: - driver: csi.spiffe.io - readOnly: true - name: spire-agent-socket - ``` - - and mount the socket to the Ingress Controller's container spec: - - ```yaml - volumeMounts: - - mountPath: /run/spire/sockets - name: spire-agent-socket - ``` - - For more information as to why a CSI Driver is needed for loading the agent socket in OpenShift, see [Introduction]({{< ref "/mesh/get-started/platform-setup/openshift.md#introduction" >}}) in the OpenShift Considerations doc. - -1. NGINX Service Mesh adds a command line argument - - The following argument is added to the Ingress Controller's container args: - - ```yaml - args: - - -spire-agent-address=/run/spire/sockets/agent.sock - ... - - - ``` - - - The `spire-agent-address` passes the address of the SPIRE agent `/run/spire/sockets/agent.sock` to the Ingress Controller. - - If egress is enabled NGINX Service Mesh also adds the following argument to the Ingress Controller's container args: - - ```yaml - args: - - -enable-internal-routes - ... - - - ``` - -1. NGINX Service Mesh adds a SPIFFE label - - ```yaml - labels: - spiffe.io/spiffeid: "true" - ... - ``` - - These labels tell NGINX Service Mesh to mutate the Ingress Controller Pod with the proper configuration in order to properly integrate with the mesh. diff --git a/content/mesh/tutorials/kic/egress-walkthrough.md b/content/mesh/tutorials/kic/egress-walkthrough.md deleted file mode 100644 index f903b213f..000000000 --- a/content/mesh/tutorials/kic/egress-walkthrough.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -title: Configure a Secure Egress Route with NGINX Ingress Controller -description: This topic provides a walkthrough of how to securely route egress traffic - through F5 NGINX Ingress Controller for Kubernetes with NGINX Service Mesh. -weight: 210 -toc: true -nd-docs: DOCS-722 -type: -- tutorial ---- - -## Overview - -Learn how to create internal routes in F5 NGINX Ingress Controller to securely route egress traffic to non-meshed services. -{{< call-out "note" >}} -NGINX Ingress Controller can be used for free with NGINX Open Source. Paying customers have access to NGINX Ingress Controller with NGINX Plus. -To complete this tutorial, you must use either: - -- Open Source NGINX Ingress Controller version 3.0+ -- NGINX Plus version of NGINX Ingress Controller - -{{< /call-out >}} - -## Objectives - -Follow this tutorial to deploy the NGINX Ingress Controller with egress enabled, and securely route egress traffic from a meshed service -to a non-meshed service. - -## Before You Begin - -1. Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/). -1. Download the example files: - - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-v1.0.yaml" "target-v1.0.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/egress-driver.yaml" "egress-driver.yaml" >}} - -## Install NGINX Service Mesh - -{{< call-out "note" >}} -If you want to view metrics for NGINX Ingress Controller, ensure that you have deployed Prometheus and Grafana and then configure NGINX Service Mesh to integrate with them when installing. Refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) guide for instructions. -{{< /call-out >}} - -1. Follow the installation [instructions]( {{< ref "/mesh/get-started/install/install.md" >}} ) to install NGINX Service Mesh on your Kubernetes cluster. - - - When deploying the mesh set the [mTLS mode]( {{< ref "/mesh/guides/secure-traffic-mtls.md" >}} ) to `strict`. - - Your deploy command should contain the following flags: - - ```bash - nginx-meshctl deploy ... --mtls-mode=strict - ``` - -1. Get the config of the mesh and verify that `mtls.mode` is `strict`: - - ```bash - nginx-meshctl config - ``` - -## Create an Application Outside of the Mesh - -The `target` application is a basic NGINX server listening on port 80. It returns a "target version" value, which is `v1.0`. - -1. Create a namespace, `legacy`, that will not be managed by the mesh: - - ```bash - kubectl create namespace legacy - ``` - -1. Create the `target` application in the `legacy` namespace: - - ```bash - kubectl -n legacy apply -f target-v1.0.yaml - ``` - -1. Verify that the target application is running and the target pod is not injected with the sidecar proxy: - - ```bash - kubectl -n legacy get pods,svc - - NAME READY STATUS RESTARTS AGE - pod/target-v1-0-5985d8544d-sgkxg 1/1 Running 0 12s - - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - service/target-v1-0 ClusterIP 10.0.0.0 80/TCP 11s - ``` - -### Send traffic to the target application - -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Create the `sender` application in the `default` namespace: - - ```bash - kubectl apply -f egress-driver.yaml - ``` - -1. Verify that the `egress-driver` pod is injected with the sidecar proxy. - - ```bash - kubectl get pods -o wide - - NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES - egress-driver-5587fbdf78-hm4w6 2/2 Running 0 5s 10.1.1.1 node-name - ``` - - The `egress-driver` Pod will automatically send requests to the `target-v1-0.legacy` Service. Once started, the script will delay for 10 seconds and then begin to send requests. - -1. Check the Pod logs to verify that the requests are being sent: - - ```bash - kubectl logs -f -c egress-driver - ``` - - **Expectation:** - - You should see the `egress-driver` is not able to reach `target`. The script employs a verbose curl command that also displays connection and HTTP information. For example: - - ```bash - * Trying 10.16.14.126:80... - * Connected to target-v1-0.legacy (10.16.14.126) port 80 (#0) - > GET / HTTP/1.1 - > Host: target-v1-0.legacy - > User-Agent: curl/7.72.0-DEV - > Accept: */* - > - * Received HTTP/0.9 when not allowed - - * Closing connection 0 - ``` - -1. Use the top command to check traffic metrics: - - ```bash - nginx-meshctl top deploy/egress-driver - ``` - - **Expectation:** No traffic metrics are populated! - - ```bash - Cannot build traffic statistics. - Error: no metrics populated - make sure traffic is flowing - exit status 1 - ``` - -The `egress-driver` application is unable to reach the `target` Service because it is not injected with the sidecar proxy. We are running with `--mtls-mode=strict` which restricts the `egress-driver` to communicating using mTLS with other injected pods. As a result we cannot build traffic statistics for these requests. - -Now, let's use NGINX Ingress Controller to create a secure internal route from the `egress-driver` application to the `target` Service. - -### Install NGINX Ingress Controller - -1. [Install the NGINX Ingress Controller]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#install-nginx-ingress-controller-with-mtls-enabled">}} ). This tutorial will demonstrate installation as a Deployment. - - Follow the instructions to [enable egress]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#enable-egress" >}} ) - -1. Verify the NGINX Ingress Controller is running: - - ```bash - kubectl -n nginx-ingress get pods,svc -o wide - - NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES - pod/nginx-ingress-c6f9fb95f-fqklz 1/1 Running 0 5s 10.2.2.2 node-name - ``` - -Notice that we do not have a Service fronting NGINX Ingress Controller. This is because we are using NGINX Ingress Controller for egress only, which means we don't need an external IP address. -The sidecar proxy will route egress traffic to the NGINX Ingress Controller's Pod IP. - -### Create an internal route to the legacy target service - -To create an internal route from the NGINX Ingress Controller to the legacy `target` Service, we need to create either: - -- an Ingress resource with the annotation `nsm.nginx.com/internal-route: "true"`. -- a VirtualServer resource with the following field added to the custom resource definition: - - ```yaml - spec: - internalRoute: true - ``` - -{{< call-out "tip" >}} -For this tutorial, the legacy Service is deployed in Kubernetes so the host name of the Ingress/VirtualServer resource is the Kubernetes -DNS name. - -To create internal routes to services outside of the cluster, refer to [creating internal routes]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#create-internal-routes-for-non-meshed-services" >}} ). -{{< /call-out >}} - -Either copy and apply the Ingress or VirtualServer resource shown below, or download and apply the linked file. - -Ingress: - -{{< call-out "important" >}} -If using Kubernetes v1.18.0 or greater you must use `ingressClassName` in your Ingress resources. Uncomment line 9 in the resource below or the downloaded file, `target-internal-route.yaml`. -{{< /call-out >}} - -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/target-internal-route.yaml" "nginx-ingress-controller/target-internal-route.yaml" >}} - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: target-internal-route - namespace: legacy - annotations: - nsm.nginx.com/internal-route: "true" -spec: - # ingressClassName: nginx # use only with k8s version >= 1.18.0 - tls: - rules: - - host: target-v1-0.legacy - http: - paths: - - path: / - pathType: Exact - backend: - service: - name: target-v1-0 - port: - number: 80 -``` - -VirtualServer: - -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/target-vs-internal-route.yaml" "nginx-ingress-controller/target-vs-internal-route.yaml" >}} - -```yaml -apiVersion: k8s.nginx.org/v1 -kind: VirtualServer -metadata: - name: target-vs-internal-route - namespace: legacy -spec: - internalRoute: true - ingressClassName: nginx - host: target-v1-0.legacy - upstreams: - - name: legacy - tls: - enable: false - service: target-v1-0 - port: 80 - routes: - - path: / - action: - pass: legacy -``` - -Verify the Ingress or VirtualServer resource has been created: - -```bash -kubectl -n legacy describe ingress target-internal-route -``` - -```bash -kubectl -n legacy describe virtualserver target-vs-internal-route -``` - -### Allow the egress-driver application to route egress traffic to NGINX Ingress Controller - -To enable the `egress-driver` application to send egress requests to NGINX Ingress Controller, edit the `egress-driver` Pod and add the following annotation: - `config.nsm.nginx.com/default-egress-allowed: "true"` - -To verify that the default egress route is configured look at the logs of the proxy container: - -```bash - kubectl logs -f -c nginx-mesh-sidecar | grep "Enabling default egress route" -``` - -### Test the internal route - -The `egress-driver` should have been continually sending traffic, which will now be routed through NGINX Ingress Controller. - -```bash -kubectl logs -f -c egress-driver -``` - -**Expectation:** You should see the target service respond with the text `target v1.0` and a successful response code. The script employs a verbose curl command that also displays connection and HTTP information. For example: - -```bash -* Trying 10.100.9.60:80... -* Connected to target-v1-0.legacy (10.100.9.60) port 80 (#0) -> GET / HTTP/1.1 -> Host: target-v1-0.legacy -> User-Agent: curl/7.72.0-DEV -> Accept: */* -> -* Mark bundle as not supporting multiuse -< HTTP/1.1 200 OK -< Server: nginx/1.19.2 -< Date: Wed, 23 Sep 2020 22:24:29 GMT -< Content-Type: text/plain -< Content-Length: 12 -< Connection: keep-alive -< -{ [12 bytes data] -target v1.0 -* Connection #0 to host target-v1-0.legacy left intact -``` - -Use the top command to check traffic metrics: - -```bash -nginx-meshctl top deploy/egress-driver -``` - -**Expectation:** The `nginx-ingress` deployment will show 100% incoming success rate and the `egress-driver` deployment will show 100% outgoing success rate. Keep in mind that the `top` command only shows traffic from the last 30s. - -```terminal -Deployment Direction Resource Success Rate P99 P90 P50 NumRequests -egress-driver - To nginx-ingress 100.00% 3ms 3ms 2ms 15 -``` - -This request from the `egress-driver` application to `target-v1-0.legacy` was securely routed through the NGINX Ingress Controller, and we now have visibility into the outgoing traffic from the `egress-driver` application! - -### Cleaning up - -1. Delete the `legacy` namespace and `egress-driver` application - - ```bash - kubectl delete ns legacy - kubectl delete deploy egress-driver - ``` - -1. Follow instructions to [uninstall NGINX Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#uninstall-the-ingress-controller). - -1. Follow instructions to [uninstall NGINX Service Mesh]( {{< ref "/mesh/get-started/install/install.md#uninstall" >}} ). diff --git a/content/mesh/tutorials/kic/ingress-udp-walkthrough.md b/content/mesh/tutorials/kic/ingress-udp-walkthrough.md deleted file mode 100644 index 7e7f88a81..000000000 --- a/content/mesh/tutorials/kic/ingress-udp-walkthrough.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -title: Expose a UDP Application with NGINX Ingress Controller -description: This topic describes the steps to deploy F5 NGINX Ingress Controller - for Kubernetes, to expose a UDP application within NGINX Service Mesh. -weight: 230 -toc: true -nd-docs: DOCS-841 -type: -- tutorial ---- - -## Overview - -Follow this tutorial to deploy the NGINX Ingress Controller with NGINX Service Mesh and an example UDP application. - -Objectives: - -- Deploy NGINX Service Mesh. -- Install NGINX Ingress Controller. -- Deploy the example `udp-listener` app. -- Create a Kubernetes GlobalConfiguration resource to establish a NGINX Ingress Controller UDP listener. -- Create a Kubernetes TransportServer resource for the udp-listener application. - -{{< call-out "note" >}} -NGINX Ingress Controller can be used for free with NGINX Open Source. Paying customers have access to NGINX Ingress Controller with NGINX Plus. -To complete this tutorial, you must use either: - -- Open Source NGINX Ingress Controller version 3.0+ -- NGINX Plus version of NGINX Ingress Controller - -{{< /call-out >}} - -### Install NGINX Service Mesh - -Follow the installation [instructions]( {{< ref "/mesh/get-started/install/install.md" >}} ) to install NGINX Service Mesh on your Kubernetes cluster. UDP traffic proxying is disabled by default, so you will need to enable it using the `--enable-udp` flag when deploying. Linux kernel 4.18 or greater is required. - -{{< call-out "caution" >}} -Before proceeding, verify that the mesh is running (Step 2 of the installation [instructions]( {{< ref "/mesh/get-started/install/install.md" >}} )). -NGINX Ingress Controller will try to fetch certs from the Spire agent that gets deployed by NGINX Service Mesh on startup. If the mesh is not running, NGINX Ingress controller will fail to start. -{{< /call-out >}} - -### Install NGINX Ingress Controller - -1. [Install NGINX Ingress Controller]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#install-nginx-ingress-controller-with-mtls-enabled">}} ) with the option to allow UDP ingress traffic. This tutorial will demonstrate installation as a Deployment. - - Follow the instructions to [enable UDP]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#enable-udp-traffic" >}} ) - - {{< call-out "important" >}} - mTLS does not affect UDP communication, as mTLS in NGINX Service Mesh applies only to TCP traffic at this time. - {{< /call-out >}} -2. Get access to the NGINX Ingress Controller by applying the `udp-nodeport.yaml` NodePort resource. - - {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/udp/udp-nodeport.yaml" "udp-nodeport.yaml" >}} -3. Check the exposed port from the NodePort service just defined: - - ```bash - $ kubectl get svc -n nginx-ingress - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - nginx-ingress NodePort 10.120.10.134 80:32705/TCP,443:30181/TCP 57m - udp-listener-nodeport NodePort 10.120.4.106 8900:31839/UDP 6m35s - ``` - - As you can see, our exposed port is `31839`. We'll use this for the remaining steps. -4. Get the IP of one of your worker nodes: - - ```bash - $ kubectl get node -o wide - NAME ... INTERNAL-IP EXTERNAL-IP ... - gke-aidan-dev-default-pool-f507f772-qiun ... 10.128.15.210 12.115.30.1 ... - gke-aidan-dev-default-pool-f507f772-tjpo ... 10.128.15.211 12.200.3.8 ... - ``` - - We'll use `12.115.30.1`. - - - At this point, you should have the NGINX Ingress Controller running in your cluster; you can deploy the udp-listener example app to test out the mesh integration, or use NGINX Ingress controller to expose one of your own apps. - -### Deploy the udp-listener App - -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Download the manifest for the `udp-listener` app. - - {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/udp/udp-listener.yaml" "udp-listener.yaml" >}} -1. Use `kubectl` to deploy the example `udp-listener` app. - - ```bash - kubectl apply -f udp-listener.yaml - ``` - -1. Verify that all of the Pods are ready and in "Running" status: - - ```bash - kubectl get pod - NAME READY STATUS RESTARTS AGE - udp-listener-59665d7ffc-drzh2 2/2 Running 0 4s - ``` - -### Expose the udp-listener App - -To route UDP requests to an application in the mesh through the NGINX Ingress Controller, you will need both a GlobalConfiguration and TransportServer Resource. - -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/udp/nic-global-configuration.yaml" "nic-global-configuration.yaml" >}} -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/udp/udp-transportserver.yaml" "udp-transportserver.yaml" >}} - -1. Deploy a GlobalConfiguration to configure what port to listen for UDP requests on: - - ```bash - kubectl apply -f nic-global-configuration.yaml - ``` - - The GlobalConfiguration configures a listener to listen for UDP datagrams on a specified port. - - ```yaml - apiVersion: k8s.nginx.org/v1alpha1 - kind: GlobalConfiguration - metadata: - name: nginx-configuration - namespace: nginx-ingress - spec: - listeners: - - name: accept-udp - port: 8900 - protocol: UDP - ``` - -2. Apply the TransportServer to configure UDP traffic to route from the GlobalConfiguration listener your udp-listener app. - - ```bash - kubectl apply -f udp-transportserver.yaml - ``` - - This TransportServer will route requests from the listener supplied in the GlobalConfiguration to a named upstream -- in this case `udp-listener-upstream`. Our upstream is configured to pass traffic to our `udp-listener` service at port 5005, where our udp-listener application lives. - - ```yaml - apiVersion: k8s.nginx.org/v1alpha1 - kind: TransportServer - metadata: - name: udp-listener - spec: - listener: - name: accept-udp - protocol: UDP - upstreams: - - name: udp-listener-upstream - service: udp-listener - port: 5005 - upstreamParameters: - udpRequests: 1 - action: - pass: udp-listener-upstream - ``` - -### Send Datagrams to the udp-listener App - -Now that everything for the NGINX Ingress Controller is deployed, we can now send datagrams to the udp-listener application. - -1. Use the IP and port defined in the [Install NGINX Ingress Controller](#install-nginx-ingress-controller) section to send a netcat UDP message: - - ```bash - echo "UDP Datagram Message" | nc -u 12.115.30.1 31839 - ``` - -2. Check that that the "UDP Datagram Message" text was correctly sent to the udp-listener server: - - ```bash - $ kubectl logs udp-listener-59665d7ffc-drzh2 -c udp-listener - Listening on UDP port 5005 - UDP Datagram Message - ``` - -3. Check that the UDP message is present in the udp-listener sidecar logs: - - ```bash - kubectl logs udp-listener-59665d7ffc-drzh2 -c nginx-mesh-sidecar - ... - 2022/01/22 00:09:31 SVID updated for spiffeID: "spiffe://example.org/ns/default/sa/default" - 2022/01/22 00:09:31 Enqueueing event: SPIRE, key: 0xc00007ac00 - 2022/01/22 00:09:31 Dequeueing event: SPIRE, key: 0xc00007ac00 - 2022/01/22 00:09:31 Reloading nginx with configVersion: 2 - 2022/01/22 00:09:31 executing nginx -s reload - 2022/01/22 00:09:32 success, version 2 ensured. iterations: 4. took: 100ms - [08/Feb/2022:19:49:02 +0000] 10.116.0.26:41802 UDP 200 0 49 0.000 "127.0.0.1:5005" "21" "0" "0.000" - ``` - - We're looking for the `[08/Feb/2022:19:49:02 +0000] 10.116.0.26:41802 UDP 200 0 49 0.000 "127.0.0.1:5005" "21" "0" "0.000"` line, which includes the `UDP` protocol and the correct size of the UDP packet we sent. - - Notice the `49` bytes representing the incoming packet size. This correlates to the `28` bytes of headroom added to the packet to maintain original destination information. See the [UDP and eBPF architecture]( {{< ref "/mesh/about/architecture.md#udp-and-ebpf" >}} ) section for more information on why this is necessary. diff --git a/content/mesh/tutorials/kic/ingress-walkthrough.md b/content/mesh/tutorials/kic/ingress-walkthrough.md deleted file mode 100644 index 4768445ed..000000000 --- a/content/mesh/tutorials/kic/ingress-walkthrough.md +++ /dev/null @@ -1,179 +0,0 @@ ---- -title: Expose an Application with NGINX Ingress Controller -description: This topic provides a walkthrough of deploying F5 NGINX Ingress Controller - for Kubernetes to expose an application within NGINX Service Mesh. -weight: 220 -toc: true -nd-docs: DOCS-723 -type: -- tutorial ---- - -## Overview - -Follow this tutorial to deploy the F5 NGINX Ingress Controller with NGINX Service Mesh and an example application. -All communication between the NGINX Ingress Controller and the example application will occur over mTLS. - -Objectives: - -- Deploy the NGINX Service Mesh. -- Install NGINX Ingress Controller. -- Deploy the example `bookinfo` app. -- Create a Kubernetes Ingress resource for the Bookinfo application. - -{{< call-out "note" >}} -NGINX Ingress Controller can be used for free with NGINX Open Source. Paying customers have access to NGINX Ingress Controller with NGINX Plus. -To complete this tutorial, you must use either: - -- Open Source NGINX Ingress Controller version 3.0+ -- NGINX Plus version of NGINX Ingress Controller - -{{< /call-out >}} - -### Install NGINX Service Mesh - -If you want to view metrics for NGINX Ingress Controller, ensure that you have deployed Prometheus and Grafana and then configure NGINX Service Mesh to integrate with them when installing. Refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) guide for instructions. - -Follow the installation [instructions]( {{< ref "/mesh/get-started/install/install.md" >}} ) to install NGINX Service Mesh on your Kubernetes cluster. -You can either deploy the Mesh with the default value for [mTLS mode]( {{< ref "/mesh/guides/secure-traffic-mtls.md" >}} ), which is `permissive`, or set it to `strict`. - -Before proceeding, verify that the mesh is running (Step 2 of the installation [instructions]( {{< ref "/mesh/get-started/install/install.md" >}} )). -NGINX Ingress Controller will try to fetch certs from the Spire agent that gets deployed by NGINX Service Mesh on startup. If the mesh is not running, NGINX Ingress controller will fail to start. - -### Install NGINX Ingress Controller - -1. [Install NGINX Ingress Controller with mTLS enabled]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#install-nginx-ingress-controller-with-mtls-enabled">}} ). This tutorial will demonstrate installation as a Deployment. -2. [Get Access to the Ingress Controller](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/#4-get-access-to-the-ingress-controller). This tutorial creates a LoadBalancer Service for the NGINX Ingress Controller. -3. Find the public IP address of your NGINX Ingress Controller Service. - - ```bash - kubectl get svc -n nginx-ingress - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - nginx-ingress LoadBalancer 10.76.7.165 34.94.247.235 80:31287/TCP,443:31923/TCP 66s - ``` - - At this point, you should have the NGINX Ingress Controller running in your cluster; you can deploy the Bookinfo example app to test out the mesh integration, or use NGINX Ingress controller to expose one of your own apps. - -### Deploy the Bookinfo App - -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Download the manifest for the `bookinfo` app. - - {{< icon "download" >}} {{< link "/examples/bookinfo.yaml" "bookinfo.yaml" >}} -1. Use `kubectl` to deploy the example `bookinfo` app. - - ```bash - kubectl apply -f bookinfo.yaml - ``` - -1. Verify that all of the Pods are ready and in "Running" status: - - ```bash - kubectl get pods - - NAME READY STATUS RESTARTS AGE - details-v1-74f858558f-khg8t 2/2 Running 0 25s - productpage-v1-8554d58bff-n4r85 2/2 Running 0 24s - ratings-v1-7855f5bcb9-zswkm 2/2 Running 0 25s - reviews-v1-59fd8b965b-kthtq 2/2 Running 0 24s - reviews-v2-d6cfdb7d6-h62cb 2/2 Running 0 24s - reviews-v3-75699b5cfb-9jtvq 2/2 Running 0 24s - - ``` - -(Optional) Verify that the application works: - -{{< call-out "note" >}} -The steps in this section only work with `permissive` [mTLS mode]( {{< ref "/mesh/guides/secure-traffic-mtls.md" >}} ). With `strict` mTLS mode, the sidecar will drop all traffic that is not encrypted with a certificate issued by NGINX Service Mesh, so the below steps won't work. For `strict` mTLS mode skip forward to the next section which covers how to [Expose the Bookinfo App](#expose-the-bookinfo-app). -{{< /call-out >}} - -1. Port-forward to the `productpage` Service: - - ```bash - kubectl port-forward svc/productpage 9080 - ``` - -2. Open the Service URL in a browser: `http://localhost:9080`. -3. Click one of the links to view the app as a general user, then as a test user, and verify that all portions of the page load. - -### Expose the Bookinfo App - -Create an Ingress Resource to expose the Bookinfo application, using the example `bookinfo-ingress.yaml` file. - -{{< call-out "important" >}} -If using Kubernetes v1.18.0 or greater you must use `ingressClassName` in your Ingress resources. Uncomment line 6 in the resource below or the downloaded file, `bookinfo-ingress.yaml`. -{{< /call-out >}} - -- {{< icon "download" >}} {{< link "/examples/nginx-ingress-controller/bookinfo-ingress.yaml" "bookinfo-ingress.yaml" >}} - -```bash -kubectl apply -f bookinfo-ingress.yaml -``` - -The Bookinfo Ingress defines a host with domain name `bookinfo.example.com`. It routes all requests for that domain name to the `productpage` Service on port 9080. - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: bookinfo-ingress -spec: - ingressClassName: nginx # use only with k8s version >= 1.18.0 - tls: - rules: - - host: bookinfo.example.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: productpage - port: - number: 9080 -``` - -### Access the Bookinfo App - -To access the Bookinfo application: - -1. Modify `/etc/hosts` so that requests to `bookinfo.example.com` resolve to NGINX Ingress Controller's public IP address. - Add the following line to your `/etc/hosts` file: - - ```bash - bookinfo.example.com - ``` - -2. Open `http://bookinfo.example.com` in your browser. -3. Click one of the links to view the app as a general user, then as a test user, and verify that all portions of the page load. - -### View Traffic Flow - -After sending a few requests as a general or test user, you can view the flow of traffic throughout your application. If you have [configured NGINX Service Mesh]({{< ref "/mesh/guides/monitoring-and-tracing.md#prometheus" >}}) to export metrics to your Prometheus deployment, run the `nginx-meshctl top` command to see traffic in the namespace your bookinfo application is deployed in: - -```txt -$ nginx-meshctl top - -Deployment Incoming Success Outgoing Success NumRequests -productpage-v1 100.00% 6 -reviews-v2 100.00% 100.00% 4 -reviews-v3 100.00% 2 -details-v1 100.00% 6 -ratings-v1 100.00% 2 -reviews-v1 100.00% 3 -``` - -Or, for a more in-depth look at the bookinfo components, run the `top` command on a deployment: - -```txt -$ nginx-meshctl top deployment/productpage-v1 - -Deployment Direction Resource Success Rate P99 P90 P50 NumRequests -productpage-v1 - To reviews-v3 100.00% 50ms 48ms 40ms 2 - To details-v1 100.00% 20ms 15ms 3ms 6 - To reviews-v1 100.00% 99ms 90ms 20ms 3 - To reviews-v2 100.00% 100ms 95ms 75ms 4 - From nginx-plus-ingress 100.00% 196ms 160ms 75ms 6 -``` - -You can also view the Grafana dashboard, which provides additional statistics on your application, by following the [Monitor your application in Grafana]( {{< ref "/mesh/tutorials/kic/deploy-with-kic.md#monitor-your-application-in-grafana" >}} ) section of our Expose an Application with NGINX Ingress Controller guide. diff --git a/content/mesh/tutorials/observability.md b/content/mesh/tutorials/observability.md deleted file mode 100644 index ef46107c0..000000000 --- a/content/mesh/tutorials/observability.md +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Install NGINX Service Mesh with Basic Observability -draft: false -toc: true -description: This topic provides a walkthrough of deploying F5 NGINX Service Mesh - with basic observability components. -weight: 90 -nd-docs: DOCS-886 -type: -- tutorial ---- - -## Overview - -In this tutorial, we will install F5 NGINX Service Mesh with some basic observability components. These components include Prometheus for collecting metrics, Grafana for visualizing metrics, and the OpenTelemetry Collector and Jaeger for collecting traces. These deployments are meant for demo purposes only, and are not recommended for production environments. - -## Deploy the Observability Components - -Download the following files containing the configurations for the observability components: - -- {{< icon "download" >}} {{< link "/examples/prometheus.yaml" "prometheus.yaml" >}} -- {{< icon "download" >}} {{< link "/examples/grafana.yaml" "grafana.yaml" >}} -- {{< icon "download" >}} {{< link "/examples/otel-collector.yaml" "otel-collector.yaml" >}} -- {{< icon "download" >}} {{< link "/examples/jaeger.yaml" "jaeger.yaml" >}} - -Deploy the components: - -```bash -kubectl apply -f prometheus.yaml -f grafana.yaml -f otel-collector.yaml -f jaeger.yaml -``` - -This command creates the `nsm-monitoring` namespace and deploys all of the components in that namespace. This namespace does not have the auto-inject label because we do not want to inject the sidecar into the observability deployments. - -## Install NGINX Service Mesh - -Install NGINX Service Mesh and configure it to integrate with the observability deployments: - -Using the CLI: - -```bash -nginx-meshctl deploy --prometheus-address "prometheus.nsm-monitoring.svc:9090" --telemetry-exporters "type=otlp,host=otel-collector.nsm-monitoring.svc,port=4317" --telemetry-sampler-ratio 1 -``` - -Using Helm: - -```bash -helm repo add nginx-stable https://helm.nginx.com/stable -helm repo update - -helm install nsm nginx-stable/nginx-service-mesh --namespace nginx-mesh --create-namespace --wait --set prometheusAddress=prometheus.nsm-monitoring.svc:9090 --set telemetry.exporters.otlp.host=otel-collector.nsm-monitoring.svc --set telemetry.exporters.otlp.port=4317 --set telemetry.samplerRatio=1 -``` - -{{< call-out "note" >}} -A sampler ratio of 1 results in 100% of traces being sampled. Adjust this value (float from 0 to 1) to your needs. -{{< /call-out >}} - -## View the Dashboards - -To view the Prometheus dashboard: - -```bash -kubectl -n nsm-monitoring port-forward svc/prometheus 9090 -``` - -Visit [http://localhost:9090](http://localhost:9090) - -To view the Grafana dashboard: - -```bash -kubectl -n nsm-monitoring port-forward svc/grafana 3000 -``` - -Visit [http://localhost:3000](http://localhost:3000). Both the default username and password are "admin". - -To view the Jaeger dashboard: - -```bash -kubectl -n nsm-monitoring port-forward svc/jaeger 16686 -``` - -Visit [http://localhost:16686](http://localhost:16686) - -## What's Next - -[Deploy an Example App]( {{< ref "/mesh/tutorials/deploy-example-app.md" >}}) diff --git a/content/mesh/tutorials/ratelimit-walkthrough.md b/content/mesh/tutorials/ratelimit-walkthrough.md deleted file mode 100644 index 7194cfdc0..000000000 --- a/content/mesh/tutorials/ratelimit-walkthrough.md +++ /dev/null @@ -1,760 +0,0 @@ ---- -title: Configure Rate Limiting -description: Learn how to configure rate limiting between workloads. -draft: false -toc: true -weight: 140 -nd-docs: DOCS-724 -type: -- tutorial ---- - -## Overview - -Rate limiting allows you to limit the number of HTTP requests a user can make in a given period to protect your application from being overwhelmed with traffic. - -In a Kubernetes environment, rate limiting is traditionally applied at the ingress layer, restricting the number of requests that an external user can make into the cluster. - -However, applications with a microservices architecture might also want to apply rate limits between their workloads running inside the cluster. For example, a rate limit applied to a particular microservice can prevent mission-critical components from being overwhelmed at times of peak traffic and attack, leading to extended periods of downtime for your users. - -This tutorial shows you how to set up rate limiting policies between your workloads in F5 NGINX Service Mesh and how to attach L7 rules to a rate limit policy to give you fine-grained control over the type of traffic that is limited. - -## Before You Begin - -1. Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/). -1. [Deploy NGINX Service Mesh]({{< ref "/mesh/get-started/install/install.md" >}}) in your Kubernetes cluster. -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Download all of the example files: - - - {{< icon "download" >}} {{< link "/examples/rate-limit/destination.yaml" "`destination.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/client-v1.yaml" "`client-v1.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/client-v2.yaml" "`client-v2.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/bursty-client.yaml" "`bursty-client.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/ratelimit.yaml" "`ratelimit.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/ratelimit-burst.yaml" "`ratelimit-burst.yaml`" >}} - - {{< icon "download" >}} {{< link "/examples/rate-limit/ratelimit-rules.yaml" "`ratelimit-rules.yaml`" >}} - -{{< call-out "note" >}} -Avoid configuring traffic policies such as TrafficSplits, RateLimits, and CircuitBreakers for headless services. -These policies will not work as expected because NGINX Service Mesh has no way to tie each pod IP address to its headless service. -{{< /call-out >}} - -## Objectives - -Follow the steps in this guide to configure rate limiting between workloads. - -### Deploy the Destination Server - -1. To begin, deploy a destination server as a Deployment, ConfigMap, and a Service. - - **Command:** - - ```bash - kubectl apply -f destination.yaml - ``` - - **Expectation:** Deployment, ConfigMap, and Service are deployed successfully. - - Use `kubectl` to make sure the resources deploy successfully. - - ```bash - kubectl get pods - NAME READY STATUS RESTARTS AGE - dest-69f4b86fb4-r8wzh 2/2 Running 0 76s - ``` - - {{< call-out "note" >}} - For other resource types -- for example, Deployments or Services -- use `kubectl get` for each type as appropriate. - {{< /call-out >}} - -### Deploy the Clients - -Now that the destination workload is ready, you can create clients and generate unlimited traffic to the destination service. - -1. Create the `client-v1` and `client-v2` Deployments. The clients are configured to send one request to the destination service every second. - - **Command:** - - ```bash - kubectl apply -f client-v1.yaml -f client-v2.yaml - ``` - - **Expectation:** The client Deployments and Configmaps are deployed successfully. - - There should be three Pods running in the default namespace: - - ```bash - kubectl get pods - NAME READY STATUS RESTARTS AGE - client-v1-5776794486-m42bb 2/2 Running 0 26s - client-v2-795bc558c9-x7dgx 2/2 Running 0 26s - dest-69f4b86fb4-r8wzh 2/2 Running 0 1m46s - ``` - -1. Open a new terminal window and stream the logs from the `client-v1` container. - - **Command:** - - ```bash - kubectl logs -l app=client-v1 -f -c client - ``` - - **Expectation:** Requests will start 10 seconds after the `client-v1` Pod is ready. Since we have not applied a rate limit policy, this traffic will be unlimited; therefore, all the requests should be successful. - - In the logs from the `client-v1` container, you should see the following responses from the destination server: - - ```bash - Hello from destination service! - Method: POST - Path: /configuration-v1 - "x-demo": true - Time: Tuesday, 17-Aug-2021 21:55:19 UTC - - Hello from destination service! - Method: POST - PATH: /configuration-v1 - "x-demo": true - Time: Tuesday, 17-Aug-2021 21:55:20 UTC - ``` - - Note that the request time, path, method, and value of the `x-demo` header are logged for each request. The timestamp should show that the requests are spaced out by 1 second. - -1. Open another terminal window and stream the logs from the `client-v2` container. - - **Command:** - - ```bash - kubectl logs -l app=client-v2 -f -c client - ``` - - **Expectation:** Requests will start 10 seconds after the `client-v2` Pod is ready. Since we have not applied a rate limit policy to the clients and destination server, this traffic will be unlimited; therefore, all the requests should be successful. - - In the logs from the `client-v2` container, you should see the following responses from the destination server: - - ```bash - Hello from destination service! - Method: GET - Path: /configuration-v2 - "x-demo": true - Time: Tuesday, 17-Aug-2021 22:03:35 UTC - - Hello from destination service! - Method: GET - Path: /configuration-v2 - "x-demo": true - Time: Tuesday, 17-Aug-2021 22:03:36 UTC - ``` - -### Create a Rate Limit Policy - -At this point, traffic should be flowing unabated between the clients and the destination service. - -1. To create a rate limit policy to limit the amount of requests that `client-v1` can send, take the following steps: - - **Command:** Create the rate limit policy. - - ```bash - kubectl create -f ratelimit.yaml - ``` - - **Expectation:** Once created, the requests from `client-v1` should be limited to 10 requests per minute, or one request every six seconds. In the logs of the `client-v1` container, you should see that five of every six requests are denied. If you look at the timestamps of the successful requests, you should see that they are six seconds apart. The requests from `client-v2` should not be limited. - - **Example**: - - ```bash - kubectl logs -l app=client-v1 -f -c client - - Hello from destination service! - Method: GET - Path: /configuration-v1 - "x-demo": true - Time: Friday, 13-Aug-2021 21:17:41 UTC - - - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.19.10
- - - - - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.19.10
- - - - - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.19.10
- - - ``` - - **Consideration**: - - Let's take a closer look at the rate limit policy we've configured: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: client-v1 - namespace: default - name: 10rm - rate: 10r/m - ``` - - The `.spec.destination` is the service receiving the requests, and the `.spec.sources` is a list of clients sending requests to the destination. The destination and sources do not need to be in the same namespace; cross-namespace rate limiting is supported. - - The `.spec.rate` is the rate to restrict traffic, expressed in requests per second or per minute. - - This rate limit policy allows 10 requests per minute, or one request every six seconds, to be sent from `client-v1` to `dest-svc`. - - {{< call-out "note" >}} - The `.spec.destination.kind` and `spec.source.kind` can be a `Service`, `Deployment`, `Pod`, `Daemonset`, or `StatefulSet`. - {{< /call-out >}} - -1. The rate limit configured above only limits requests sent from `client-v1`. To limit the requests sent from `client-v2`, take the following steps to add `client-v2` to the list of sources: - - **Command:** - - ```bash - kubectl edit ratelimit ratelimit-v1 - ``` - - Add the `client-v2` Deployment to `spec.sources`: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: client-v1 - namespace: default - - kind: Deployment - name: client-v2 - namespace: default - name: 10rm - rate: 10r/m - ``` - - Save your edits and exit the editor. - - **Expectation:** The requests sent from `client-v2` should be limited now. When multiple sources are listed in the rate limit spec, the rate is divided evenly across all the sources. In this spec, `client-v1` and `client-v2` can send five requests per minute or one request every 12 seconds. To verify, watch the logs of each container and check that 11 out of every 12 requests are denied. - - {{< call-out "tip" >}} - If you want to enforce a single rate limit across all clients, you can omit the source list from the rate limit spec. If there no sources are listed, the rate limit applies to all clients making requests to the destination. - - If you want to enforce a different rate limit per source, you can create a separate rate limit for each source. - {{< /call-out >}} - -### Rate Limits with L7 Rules - -So far, we've configured basic rate-limiting policies based on the source and destination workloads. - -What if you have a workload that exposes several endpoints, where each endpoint can handle a different amount of traffic? Or you're performing A/B testing and want to rate limit requests based on the value or presence of a header? - -This section shows you how to configure rate limit rules to create more advanced L7 policies that apply to specific parts of an application rather than the entire Pod. - -Let's revisit the logs of our `client-v1` and `client-v2` containers, which at this point are both rate limiting at a rate of 5r/m each. Each client is sending a different type of request. - -`client-v1` and `client-v2` make requests to the destination service with the following attributes: - -{{% table %}} -| attribute | client-v1 | client-v2 | -|---------------|---------------------|---------------------| -| **path** | `/configuration-v1` | `/configuration-v2` | -| **headers** | `x-demo:true` | `x-demo:true` | -| **method** | `POST` | `GET` | -{{% /table %}} - -If you want to limit all GET requests, you can create an `HTTPRouteGroup` resource and add a rules section to the rate limit. Consider the following configuration: - - ```yaml - apiVersion: specs.smi-spec.io/v1alpha3 - kind: HTTPRouteGroup - metadata: - name: hrg - namespace: default - spec: - matches: - - name: get-only - methods: - - GET - - name: demo-header - headers: - X-Demo: "^true$" - - name: config-v1-path - pathRegex: "/configuration-v1" - - name: v2-only - pathRegex: "/configuration-v2" - headers: - X-DEMO: "^true$" - methods: - - GET - ``` - - {{< call-out "note" >}} - The header capitalization `X-Demo` and `X-DEMO` in the `HTTPRouteGroup` mismatches intentionally; header names are not case-sensitive. - {{< /call-out >}} - - The [HTTPRouteGroup](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-specs/v1alpha3/traffic-specs.md#httproutegroup) is used to describe HTTP traffic. - The `spec.matches` field defines a list of routes that an application can serve. Routes are made up of the following match conditions: pathRegex, headers, and HTTP methods. - - In the `hrg` above, four matches are defined: `get-only`, `demo-header`, `config-v1-path`, and `v2-only`. - - You can limit all `GET` requests by referencing the `get-only` match from `hrg` in our rate limit spec: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: client-v1 - namespace: default - - kind: Deployment - name: client-v2 - namespace: default - name: 10rm - rate: 10r/m - rules: - - kind: HTTPRouteGroup - name: hrg - matches: - - get-only - ``` - - The `.spec.rules` list maps HTTPRouteGroup's `.spec.matches` directives to the rate limit. This means that the rate limit only applies if the request's attributes satisfy the match conditions outlined in the match directive. - - If there are multiple rules and/or multiple matches per rule, the rate limit will be applied if the request satisfies any of the specified matches. - - In this case, we're mapping just the `get-only` match directive from the `HTTPRouteGroup` : `hrg` to our rate limit . The match `get-only` matches all `GET` requests. - - {{< call-out "tip" >}} - You can reference multiple `HTTPRouteGroups` in the `spec.rules` list, but they all must be in the same namespace of the rate limit. - {{< /call-out >}} - -1. To rate limit only `GET` requests, take the following steps: - - **Command:** - - ```bash - kubectl apply -f ratelimit-rules.yaml - ``` - - **Expectation:** Requests from `client-v`2 should still be rate limited. Since `client-v1` is making `POST` requests, all of its requests should now be successful. - -1. Edit the rate limit and add the `config-v1-path` match to the rules: - - **Command:** - - ```bash - kubectl edit ratelimit ratelimit-v1 - ``` - - Add the match `config-v1-path` to the `spec.rules[0].matches` list: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: client-v1 - namespace: default - - kind: Deployment - name: client-v2 - namespace: default - name: 10rm - rate: 10r/m - rules: - - kind: HTTPRouteGroup - name: hrg - matches: - - get-only - - config-v1-path - ``` - - Save your edits and close the editor. - - **Expectation:** Requests from both `client-v1` and `client-v2` are rate limited. If multiple matches or rules are listed in the rate limit spec, then the request has to satisfy only one of the matches. Therefore, the rules in this rate limit apply to any request that is either a `GET` request or has a path of `/configuration-v1`. - -1. Edit the rate limit and add a more complex match directive. - - If you want to rate limit requests that have a combination of method, path, and headers, you can create a more complex match. For example, consider the `v2-only` match in our `HTTPRouteGroup`: - - ```yaml - - name: v2-only - pathRegex: "/configuration-v2" - headers: - X-DEMO: "^true$" - methods: - - GET - ``` - - This configuration matches `GET` requests with the `x-demo:true` header and a path of `/configuration-v2`. - - Try it out by editing the RateLimit and replacing the matches in rules with the `v2-only` match. - - **Command:** - - ```bash - kubectl edit ratelimit ratelimit-v1 - ``` - - Remove all of the matches from `spec.rules[0].matches` and add the `v2-only` match: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-v1 - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: client-v1 - namespace: default - - kind: Deployment - name: client-v2 - namespace: default - name: 10rm - rate: 10r/m - rules: - - kind: HTTPRouteGroup - name: hrg - matches: - - v2-only - ``` - - Save your edits and close the editor. - - **Expectation:** Only the requests from `client-v2` are rate limited. Even though `client-v1` has the `x-demo:true` header, the rest of the request's attributes do not match the criteria in the `v2-only` match. - - {{< call-out "tip" >}} - If you want to add all of the matches from a single `HTTPRouteGroup`, you can omit the `matches` field from the rule. - {{< /call-out >}} - -1. Clean up. - - Before moving on the next section, delete the clients and the rate limit. - - **Command:** - - ```bash - kubectl delete -f client-v1.yaml -f client-v2.yaml -f ratelimit-rules.yaml - ``` - -### Handle Bursts - -Some applications are "bursty" by nature; for example, they might send multiple requests within 100ms of each other. To handle applications like this, you can leverage the burst and delay fields in the rate limit spec. - -`burst` is the number of excess requests to allow beyond the rate, and `delay` controls how the burst of requests is forwarded to the destination. - -Let's create a bursty application and a rate limit to demonstrate this behavior. - -1. Create a bursty client. - - **Command:** - - ```bash - kubectl apply -f bursty-client.yaml - ``` - - **Expectation:** The `bursty-client` Deployment and Configmap deployed successfully. - - There should be two Pods running in the default namespace: - - ```bash - kubectl get pods - NAME READY STATUS RESTARTS AGE - bursty-client-7b75d74d44-zjqlh 2/2 Running 0 6s - dest-69f4b86fb4-r8wzh 2/2 Running 0 5m16s - ``` - -1. Stream the logs of the `bursty-client` container in a separate terminal window. - - **Command:** - - ```bash - kubectl logs -l app=bursty-client -f -c client - ``` - - **Expectation:** The `bursty-client` is configured to send a burst of three requests to the destination service every 10 seconds. At this point, there is no rate limit applied to the `bursty-client`, so all the requests should be successful. - - ```bash - ----Sending burst of 3 requests---- - - Hello from destination service! - Method: GET - Path: /echo - "x-demo": - Time: Friday, 13-Aug-2021 21:43:50 UTC - - - Hello from destination service! - Method: GET - Path: /echo - "x-demo": - Time: Friday, 13-Aug-2021 21:43:50 UTC - - - Hello from destination service! - Method: GET - Path: /echo - "x-demo": - Time: Friday, 13-Aug-2021 21:43:50 UTC - - -------Sleeping 10 seconds------- - ``` - -1. Apply a rate limit with a rate of 1r/s. - - **Command:** - - ```bash - kubectl apply -f ratelimit-burst.yaml - ``` - - **Expectation:** Since only one request is allowed per second, only one of the requests in the burst is successful. - - ```bash - ----Sending burst of 3 requests---- - - Hello from destination service! - Method: GET - Path: /echo - "x-demo": - Time: Friday, 13-Aug-2021 21:44:10 UTC - - - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.19.10
- - - - - - 503 Service Temporarily Unavailable - -

503 Service Temporarily Unavailable

-
nginx/1.19.10
- - - - -------Sleeping 10 seconds------- - ``` - -1. Since we know that our `bursty-client` is configured to send requests in bursts of three, we can edit the rate limit and add a `burst` of `2` to make sure all requests get through to the destination service. - - **Command:** - - ```bash - kubectl edit ratelimit ratelimit-burst - ``` - - Add a `burst` of `2`: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-burst - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: bursty-client - namespace: default - name: ratelimit-burst - rate: 1r/s - burst: 2 - ``` - - Save your changes and exit the editor. - - A `burst` of `2` means that of the three requests that the `bursty-client` sends within one second, one request is allowed and is forwarded immediately to the destination service, and the following two requests are placed in a queue of length `2`. - - The requests in the queue are forwarded to the destination service according to the `delay` field. The `delay` field specifies the number of requests, within the burst size, at which excessive requests are delayed. If any additional requests are made to the destination service once the queue is filled, they are denied. - - **Expectation:** In the `bursty-client` logs, you should see that all the requests from the `bursty-client` are successful. - - However, if you look at the timestamps of the response, you should see that each response is logged one second apart. This is because the second and third requests of the burst were added to a queue and forwarded to the destination service at a rate of one request per second. - - Delaying the excess requests in the queue can make your application appear slow. If you want to have the excess requests forwarded immediately, you can set the `delay` field to `nodelay`. - - {{< call-out "tip" >}} - The default value for `delay` is `0`. A delay of `0` means that every request in the queue is delayed according to the rate specified in the rate limit spec. - {{< /call-out >}} - -1. To forward the excess requests to the destination service immediately, edit the rate limit and set delay to `nodelay`. - - **Command:** - - ```bash - kubectl edit ratelimit ratelimit-burst - ``` - - Set delay to `nodelay`: - - ```yaml - apiVersion: specs.smi.nginx.com/v1alpha2 - kind: RateLimit - metadata: - name: ratelimit-burst - namespace: default - spec: - destination: - kind: Service - name: dest-svc - namespace: default - sources: - - kind: Deployment - name: bursty-client - namespace: default - name: ratelimit-burst - rate: 1r/s - burst: 2 - delay: nodelay - ``` - - **Expectation:** A delay of `nodelay` means that the requests in the queue are immediately sent to the destination service. You can verify this by looking at the timestamps of the responses in the `bursty-client` logs; they should all be within the same second. - - {{< call-out "tip" >}} - You can also set the `delay` field to an integer. For example, a delay of `1` means that one request is forwarded immediately, and all other requests in the queue are delayed. - {{< /call-out >}} - -1. Clean up all the resources. - - **Command:** - - ```bash - kubectl delete -f bursty-client.yaml -f ratelimit-burst.yaml -f destination.yaml - ``` - -### Summary - -You should now have a good idea of how to configure rate limiting between your workloads. - -If you'd like to continue experimenting with different rate-limiting configurations, you can modify the configurations of the clients and destination service. - -The clients can be configured to send requests to the Service name of your choice with different methods, paths, and headers. - -Each client's ConfigMap supports the following options: - -{{% table %}} -Parameter | Type | Description ----|---|--- -`host` | string | base URL of target Service -`request_path` | string | request path -`method` | string | HTTP method to use -`headers` | string | comma-delimited list of additional request headers to include -{{% /table %}} - -The bursty client Configmap also supports these additional options: - -{{% table %}} -Parameter | Type | Description ----|---|--- -`burst` | string | number of requests per burst -`delay` | string | number of seconds to sleep between bursts -{{% /table %}} - -The destination workload can be set to serve different ports or multiple ports. To configure the destination workload, edit the `destination.yaml` file. An example configuration is shown below: - -NGINX `dest-svc` configuration: - -- Update the Pod container port: `.spec.template.spec.containers[0].ports[0].containerPort`. -- Update the ConfigMap NGINX listen port: `.data.nginx.conf: http.server.listen`. -- Update the Service port: `.spec.ports[0].port`. - -The following examples show snippets of the relevant sections: - - ```yaml - --- - kind: Deployment - spec: - template: - spec: - containers: - - name: example - - containerPort: 55555 - --- - apiVersion: v1 - kind: ConfigMap - metadata: - name: dest-svc - data: - nginx.conf: |- - events {} - http { - server { - listen 55555; - location / { - return 200 "destination service\n"; - } - } - } - --- - kind: Service - spec: - ports: - - port: 55555 - ``` - -## Resources and Further Reading - -- [Rate Limiting with NGINX and NGINX Plus](https://www.nginx.com/blog/rate-limiting-nginx/) -- [How to Use NGINX Service Mesh for Rate Limiting](https://www.nginx.com/blog/how-to-use-nginx-service-mesh-for-rate-limiting/) -- [NGINX HTTP Rate Limit Req Module](http://nginx.org/en/docs/http/ngx_http_limit_req_module.html) diff --git a/content/mesh/tutorials/trafficsplit-deployments.md b/content/mesh/tutorials/trafficsplit-deployments.md deleted file mode 100644 index a87121633..000000000 --- a/content/mesh/tutorials/trafficsplit-deployments.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -title: Deployments using Traffic Splitting -description: This topic provides a guide for using traffic splits with different deployment - strategies. -weight: 110 -toc: true -nd-docs: DOCS-725 -type: -- tutorial ---- - -## Overview - -You can use traffic splitting for most deployment scenarios, including canary, blue-green, A/B testing, and so on. The ability to control traffic flow to different versions of an application makes it easy to roll out a new application version with minimal effort and interruption to production traffic. - -## Before You Begin - -1. Install [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/). -1. (Optional) If you want to view metrics, ensure that you have deployed Prometheus and Grafana. - Refer to the [Monitoring and Tracing]( {{< ref "/mesh/guides/monitoring-and-tracing.md" >}} ) guide for instructions. -1. Set up a Kubernetes cluster with [F5 NGINX Service Mesh]( {{< ref "/mesh/get-started/install/install.md" >}} ) deployed with the following configuration: - - `--mtls-mode` is set to `permissive` or `off` states. - - (Optional) `--prometheus-address` is pointed to the Prometheus instance you created above. -1. Enable [automatic sidecar injection]( {{< ref "/mesh/guides/inject-sidecar-proxy.md#automatic-proxy-injection" >}} ) for the `default` namespace. -1. Download all the example files: - - - {{< icon "download" >}} {{< link "/examples/traffic-split/gateway.yaml" "gateway.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-svc.yaml" "target-svc.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-v1.0.yaml" "target-v1.0.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-v2.0-failing.yaml" "target-v2.0-failing.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-v2.1-successful.yaml" "target-v2.1-successful.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/target-v3.0.yaml" "target-v3.0.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/trafficsplit.yaml" "trafficsplit.yaml" >}} - - {{< icon "download" >}} {{< link "/examples/traffic-split/trafficsplit-matches.yaml" "trafficsplit-matches.yaml" >}} - - -{{< call-out "note" >}} -The NGINX Plus Ingress Controller's custom resource [TransportServer](https://docs.nginx.com/nginx-ingress-controller/configuration/transportserver-resource/) has the same Kubernetes short name(`ts`) as the custom resource TrafficSplit. -If you have the NGINX Plus Ingress Controller installed, use the full name `trafficsplit(s)` instead of `ts` in the following instructions. -{{< /call-out >}} - -{{< call-out "note" >}} -Avoid configuring traffic policies such as TrafficSplits, RateLimits, and CircuitBreakers for headless services. -These policies will not work as expected because NGINX Service Mesh has no way to tie each pod IP address to its headless service. -{{< /call-out >}} - -## Objectives - -Follow the steps in this guide to learn how to use traffic splitting for various deployment strategies. - -### Deploy the Production Version of the Target App - -1. First, let's begin by deploying the "production" v1.0 target app, the load balancer Service, and the ingress gateway. -{{< call-out "tip" >}} -For simplicity, this guide uses a simple NGINX reverse proxy for the ingress gateway. For production usage and for more advanced ingress control, we recommend using the [NGINX Ingress Controller for Kubernetes](https://www.nginx.com/products/nginx-ingress-controller/). Refer to [Deploy NGINX Ingress Controller with NGINX Service Mesh]( {{< ref "ingress-walkthrough.md" >}} ) to learn more. -{{< /call-out >}} - - **Command:** - - ```bash - kubectl apply -f target-svc.yaml -f target-v1.0.yaml -f gateway.yaml - ``` - - **Expectation:** All Pods and Services deploy successfully. - - Use `kubectl` to make sure the Pods and Services deploy successfully. - - Example: - - ```bash - $ kubectl get pods - NAME READY STATUS RESTARTS AGE - gateway-58c6c76dd-4mmht 2/2 Running 0 2m - target-v1-0-6f69fc48f6-mzcf2 2/2 Running 0 2m - ``` - - ```bash - $ kubectl get svc - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - gateway-svc LoadBalancer 10.0.0.2 1.2.3.4 80:30975/TCP 2m - target-svc ClusterIP 10.0.0.3 80/TCP 2m - target-v1-0 ClusterIP 10.0.0.4 80/TCP 2m - ``` - - To better understand what is going on here, let's take a quick look at what we deployed here: - - **gateway**: simple NGINX reverse proxy that forwards traffic to the target app. Besides providing a single point of ingress to the cluster, using the gateway lets us use the `nginx-meshctl top` command to check traffic metrics between it and the backend Services it is sending traffic to. - - **target-svc**: the root Service that connects to all the different versions of the target app. - - **target**: for our example we will be deploying 3 different versions of the target app. The target app is a basic NGINX server that returns the target version. Each one has its own Service tagged with its version number. These are the Services that the root target-svc sends requests to. - -1. Once the Pods and Services are ready, generate traffic to `target-svc`. Use a different bash window for this step so you can watch the traffic change as you are doing the deployments. - - **Commands:** - - Get the external IP for gateway-svc: - - ```bash - kubectl get svc gateway-svc - ``` - - - Save the IP address as an environment variable: - - ```bash - export GATEWAY_IP= - ``` - - - Start a loop that sends a request to that IP once per second for 5 minutes. Rerun as needed: - - ```bash - for i in $(seq 1 300); do curl $GATEWAY_IP; sleep 1; done - ``` - - **Expectation:** Requests will start to come in to `target-svc`. At this point you should only see `target v1.0` responses. - -1. Back in your original bash window, use the mesh CLI to check traffic metrics. - - **Command:** `nginx-meshctl top` \ - **Expectation:** The `target-v1-0` deployment will show 100% incoming success rate and the `gateway` deployment will show 100% outgoing success rate. The `top` command only shows traffic from the last 30s. `top` provides a quick look at your Services for immediate debugging and to see if there’s any anomalies that need further investigation. For more detailed and accurate traffic monitoring, we recommend using Grafana. Refer to [traffic metrics]( {{< ref "/mesh/guides/smi-traffic-metrics.md" >}} ) for details. - - Example: - - ```bash - $ nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - gateway 100.00% 10 - target-v1-0 100.00% 10 - ``` - -### Deploy a New Version of the Target App using a Canary Deployment - -Using traffic splits we can use a variety of deployment strategies. Whether using a blue-green deployment, canary deployment, or a hybrid of different deployment strategies, traffic splits make the process extremely easy. - -For this version of the target app, let's try using a canary deployment strategy. - -1. Apply the traffic split so that once a new version is deployed, it will not receive any traffic until we are ready. Ideally we would apply this at the same time as the first `target` version, `target-svc`, and `gateway`. To make it easier to see what is happening though, we are applying it in this separate step. - - **Command:** - - ```bash - kubectl apply -f trafficsplit.yaml - ``` - - **Expectation:** The traffic split is applied successfully. Use `kubectl get ts` to see the current traffic splits. - - Use `kubectl describe ts target-ts` to see details about the traffic split we just applied. Currently the traffic split is configured to send 100% of traffic to target v1.0. - - ```yaml - apiVersion: split.smi-spec.io/v1alpha3 - kind: TrafficSplit - metadata: - name: target-ts - spec: - service: target-svc - backends: - - service: target-v1-0 - weight: 100 - ``` - -1. Now let's deploy target v2.0. To show a scenario where an upgrade is failing, this version of target is configured to return a `500` error status code instead of a successful `200`. - - **Command:** - - ```bash - kubectl apply -f target-v2.0-failing.yaml - ``` - - **Expectation:** Target v2.0 will deploy to the cluster successfully. You should see the new `target-v2-0` Pod and Service in the `kubectl get pods`/`kubectl get svc` output. Since we deployed the traffic split, if you look at your other bash window where the traffic is being generated you should still only see responses from target v1.0. If you check `nginx-meshctl top` you should see the same deployments as before. This is because no traffic has been sent to or received from target v2.0. - -1. For this deployment we'll send 10% of traffic to target v2.0 while 90% is still going to target v1.0. Open `trafficsplit.yaml` in the editor of your choice and add a new backend for `target-v2-0` with a weight of `10`. Change the weight of `target-v1-0` to `90`. - - ```yaml - apiVersion: split.smi-spec.io/v1alpha3 - kind: TrafficSplit - metadata: - name: target-ts - spec: - service: target-svc - backends: - - service: target-v1-0 - weight: 90 - - service: target-v2-0 - weight: 10 - ``` - -1. After updating `trafficsplit.yaml`, save and apply it. - - **Command:** - - ```bash - kubectl apply -f trafficsplit.yaml - ``` - - **Expectation:** After applying the updated traffic split, you should start seeing responses from target v2.0 in the other bash where traffic is being generated. Because of the weight we set in the previous step, about 1 out of 10 requests will be sent to v2.0. Something to keep in mind is that these are weighted, so it will not be exactly 1 in 10, but it will be close. - -1. Check the traffic metrics now that v2.0 is available. - - **Command:** - - ```bash - nginx-meshctl top - ``` - - **Expectation:** - - - `target-v1-0` deployment will still show 100% incoming success rate - - `target-v2-0` deployment will show 0% incoming success rate - - `gateway` deployment will show the appropriate percentage of successful outgoing requests - - Example: - - ```bash - $ nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - gateway 90.00% 10 - target-v1-0 100.00% 9 - target-v2-0 0.00% 1 - ``` - -1. It looks like v2.0 doesn't work! We can see that because the incoming success rate to target-v2 is 0%. Thankfully, using traffic splitting, it is easy to redirect all traffic back to v1.0 without doing a complicated rollback. To update the traffic split, simply update `trafficsplit.yaml` to send 100% of traffic to v1.0 and 0% of traffic to v2.0 and re-apply it. - - You can either explicitly set the weight of `target-v2-0` to `0` or remove the `target-v2-0` backend completely. The result will be the same. - - At this point you can delete v2.0 from the cluster. - - **Command:** - - ```bash - kubectl delete -f target-v2.0-failing.yaml - ``` - -### Deploy a New Version of the Target App using a Blue-Green Deployment - -For this version of the target app, let's use a blue-green deployment. - -1. Deploy v2.1 of target, which fixes the issue causing the failing requests that we saw in v2.0. - - **Command:** - - ```bash - kubectl apply -f target-v2.1-successful.yaml - ``` - - **Expectation:** Target v2.1 will deploy successfully. You should see the new `target-v2-1` Pod and Service in the `kubectl get pods`/`kubectl get svc` output. Just as with `target-v2-0` though, we have the traffic split configured to send all traffic to `target-v1-0` until we are ready to do the actual deployment and make `target-v2-1` available for traffic. - -1. Since we are doing a blue-green deployment, we will configure the traffic split to send all traffic to target v2.1. Open `trafficsplit.yaml` in the editor of your choice and add a new backend for `target-v2-1` with a weight of `100`. Change the weight of `target-v1-0` to `0`. You could also delete the `target-v1-0` backend completely, but with this type of deployment it's easier to set the weight to `0` in case you need to roll back quickly. - - ```yaml - apiVersion: split.smi-spec.io/v1alpha3 - kind: TrafficSplit - metadata: - name: target-ts - spec: - service: target-svc - backends: - - service: target-v1-0 - weight: 0 - - service: target-v2-1 - weight: 100 - ``` - -1. After updating `trafficsplit.yaml`, save and apply it. - - **Command:** - - ```bash - kubectl apply -f trafficsplit.yaml - ``` - - **Expectation:** After applying the updated traffic split, you should start seeing responses from target v2.1 in the other bash where traffic is being generated. Because of the weight we set in the previous step, all traffic should be going to v2.1. - -1. Check the traffic metrics now that v2.1 is available. - - **Command:** - - ```bash - nginx-meshctl top - ``` - - **Expectation:** - - - `target-v1-0` deployment will not show up, although keep in mind that it will take a bit for the previous requests to move out of the 30s metric window. If you see `target-v1-0`, try again in 30s or so. - - `target-v2-1` deployment will show 100% incoming success rate - - `gateway` deployment will show 100% outgoing success rate - - Example: - - ```bash - $ nginx-meshctl top - Deployment Incoming Success Outgoing Success NumRequests - gateway 100.00% 10 - target-v2-1 100.00% 10 - ``` - -1. Since target v2.1 is working as expected, we can delete v1.0 from the cluster. If v2.1 had started failing, we could have quickly rolled back to v1.0 just as we did earlier. - - **Command:** - - ```bash - kubectl delete -f target-v1.0.yaml - ``` - -### A/B Testing with Traffic Splits - -If you want to implement A/B testing, you can create an HTTPRouteGroup resource and associate the HTTPRouteGroup with the traffic split. - -Consider the following configuration: - -```yaml -apiVersion: specs.smi-spec.io/v1alpha3 -kind: HTTPRouteGroup -metadata: - name: target-hrg - namespace: default -spec: - matches: - - name: firefox-users - headers: - user-agent: ".*Firefox.*" -``` - -The [HTTPRouteGroup](https://github.com/servicemeshinterface/smi-spec/blob/main/apis/traffic-specs/v1alpha3/traffic-specs.md#httproutegroup) is used to describe HTTP traffic. The `spec.matches` field defines a list of routes that an application can serve. Routes are made up of the following match conditions: pathRegex, headers, and HTTP methods. -In the `target-hrg` example above, we have defined one route, `firefox-users`, using the header filter `user-agent: ".*Firefox.*"`. Incoming HTTP traffic that has the `user-agent` header set to a value that matches the regex `".*Firefox.*"` satisfies the `firefox-users` match condition. - -{{< call-out "tip" >}} -A route with multiple match conditions (pathRegex, headers, and/or HTTP methods) within a single match represent an `AND` condition. This means that all match conditions must be satisfied for the traffic to match the route. -{{< /call-out >}} - -To associate the `target-hrg` HTTPRouteGroup with the traffic split we need to add the `matches` field to our traffic split spec: - -```yaml -apiVersion: split.smi-spec.io/v1alpha3 -kind: TrafficSplit -metadata: - name: target-ts -spec: - service: target-svc - backends: - - service: target-v2-1 - weight: 0 - - service: target-v3-0 - weight: 100 - matches: - - kind: HTTPRouteGroup - name: target-hrg -``` - -Traffic split `matches` allow you to associate one or more `HTTPRouteGroups` with a traffic split. -The `matches` field in the traffic split spec maps the HTTPRouteGroup's `matches` directives to the traffic split. This means that the traffic split only applies if the request's attributes satisfy the match conditions outlined in the match directives. - -If there are multiple HTTPRouteGroups listed in the traffic split `matches` field and/or multiple matches defined in the HTTPRouteGroup, the traffic split will be applied if the request satisfies any of the specified matches. - -In this example, all traffic sent to the root Service `target-svc` that contains the string `Firefox` in the `user-agent` header will be routed to the `target-v2-1` backend. All other traffic will be sent to the root Service `target-svc`. - -1. To demonstrate how to A/B test with traffic splits, let's create a new version of the target application: - - **Command:** - - ```bash - kubectl apply -f target-v3.0.yaml - ``` - - **Expectation:** The target-v3-0 Pod and Service deploy successfully. At this point there should be three Pods and four Services running. - - Example: - - ```bash - $ kubectl get pods - NAME READY STATUS RESTARTS AGE - gateway-58c6c76dd-4mmht 2/2 Running 0 2m - target-v2-1-6f69fc48f6-mzcf2 2/2 Running 0 2m - target-v3-0-5f6fc9cf99-tps6k 2/2 Running 0 2m - ``` - - ```bash - $ kubectl get svc - NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE - gateway-svc LoadBalancer 10.0.0.2 1.2.3.4 80:30975/TCP 2m - target-svc ClusterIP 10.0.0.3 80/TCP 2m - target-v2-1 ClusterIP 10.0.0.4 80/TCP 2m - target-v3-0 ClusterIP 10.0.0.5 80/TCP 2m - ``` - - In the terminal window where you are generating traffic to the gateway Service you should still see all responses coming from the `target-v2-1` backend. You may need to restart the loop that generates traffic: - - ```bash - for i in $(seq 1 300); do curl $GATEWAY_IP; sleep 1; done - ``` - -1. Create the `target-hrg` HTTPRouteGroup and update the traffic split: - - **Command:** - - ```bash - kubectl apply -f trafficsplit-matches.yaml - ``` - - **Expectation:** The `target-hrg` HTTPRouteGroup is created and the `target-ts` traffic split is updated. - - {{< call-out "tip" >}} Use `kubectl get` and `kubectl describe` for `httproutegroups` and `trafficsplits` to make sure the resources were created or updated. {{< /call-out >}} - - In the terminal window where you are generating traffic to the gateway Service you should now see responses from both the `target-v2-1` and `target-v3` backends. To test the A/B traffic shaping, open another terminal window and generate traffic to the gateway Service with the header `user-agent: Firefox`: - - ```bash - for i in $(seq 1 100); do curl $GATEWAY_IP -H "user-agent:Firefox"; sleep 1; done - ``` - - Since the `user-agent` header is set to "Firefox", you should see responses from the `target-v3-0` backend only. - -### Traffic Splitting based on path and HTTP methods - -In addition to supporting traffic splitting based on header filters, NGINX Service Mesh also supports traffic splitting based on path and HTTP methods. To demonstrate this let's update the `target-hrg` and add a new match. - -1. Edit the HTTPRouteGroup `target-hrg`: - - **Command:** - - ```bash - kubectl edit httproutegroup target-hrg - ``` - - Add the `get-api-requests` route to the list of matches: - - ```yaml - apiVersion: specs.smi-spec.io/v1alpha3 - kind: HTTPRouteGroup - metadata: - name: target-hrg - namespace: default - spec: - matches: - - name: firefox-users - headers: - user-agent: ".*Firefox.*" - - name: get-api-requests - pathRegex: "/api" - methods: - - GET - ``` - - Save and close the editor. - - The `get-api-requests` route will match all GET requests to the `/api` endpoint. By adding this route to the `target-hrg` matches, the `target-ts` traffic split will now have both the `firefox-users` and `get-api-requests` matches applied to it. Since multiple matches are applied with an `OR` operator, if an incoming HTTP request to `target-svc` matches either `firefox-users` or `get-api-requests`, the traffic split will be applied, and the request will be routed to the `target-v3-0` backend Service. - All other incoming HTTP requests will be routed to the root `target-svc`, which will forward the request to one of the target services based on the load-balancing algorithm of the mesh. - - **Expectations:** - - - In the terminal window where requests to the gateway Service have the `user-agent:Firefox` header set, you should still see responses from the `target-v3-0` backend only. - - To test the `get-api-requests` route, start a new for loop that sends GET requests to the `/api` endpoint: - - ```bash - for i in $(seq 1 100); do curl $GATEWAY_IP/api; sleep 1; done - ``` - - You should only see responses from the `target-v3-0` backend. If you remove the `/api` path from the request you should see responses from both the `target-v2-1` and `target-v3-0` backends. - -## Cleanup - -Delete all the resources from your cluster: - -**Command:** - -```bash -kubectl delete -f gateway.yaml -f target-svc.yaml -f target-v2.1-successful.yaml -f target-v3.0.yaml -f trafficsplit-matches.yaml -``` - -## Use Case - -An example use case for traffic splitting can be seen in [this blog](https://www.nginx.com/blog/how-do-i-choose-api-gateway-vs-ingress-controller-vs-service-mesh/#East-West-API-Gateway-Use-Cases:-Use-a-Service-Mesh). The blog uses a self-referential TrafficSplit configuration in which the root service is also listed as a backend service. Even though the Service Mesh Interface specification mentions that self-referential configurations are invalid, NGINX Service Mesh supports this type of configuration due to its value in use cases like the example defined in the blog. - -## Summary - -These are just a couple examples of how you can use traffic splits for a deployment. Whether you want to do a gradual roll out of 5% increments or send 5% to two staging backends while 90% goes to production or any other combination of splits, traffic splits offer a convenient way to handle almost any deployment strategy you need. - -## Resources - - -- [SMI Traffic Split Example on GitHub](https://github.com/servicemeshinterface/smi-spec/blob/master/apis/traffic-split/v1alpha3/traffic-split.md#example-implementation) - diff --git a/static/examples/grafana/README.md b/static/examples/grafana/README.md deleted file mode 100644 index 9d9b97ca3..000000000 --- a/static/examples/grafana/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Grafana Dashboard - -Two Grafana dashboards have been included as examples for use in your environment: -- NGINX Mesh Top -- NGINX Service Mesh Summary - -Both Grafana dashboards provide a high-level picture of the request volume and success rate of all your applications in the mesh, and each have been included to provide two different references for consuming and rendering NGINX Service Mesh metrics data. - -You can import either or both example dashboards included in this directory into your Grafana deployment. - -## Prerequisites -- Grafana version >= 6.6.0 -- Prometheus datasource must be [configured](https://prometheus.io/docs/visualization/grafana/#creating-a-prometheus-data-source). - - **Note:** Make sure to add the NGINX Service Mesh [scrape config](../prometheus/README.md) to your Prometheus configuration. - -## Installing Example Dashboards - -To install an example dashboard using the Grafana UI follow these steps: -- Click on Import under the Dashboards icon in the side menu. -- Upload the dashboard JSON, ex: [nginx-mesh-top.json](nginx-mesh-top.json), file or copy and paste the contents of the file in the textbox and click Load. -- Select the Prometheus data source you configured previously from the dropdown menu and click Import. - -![nginx-mesh-top](dashboard.png) - - -### Features -The default NGINX Mesh Top dashboard includes the following stats and graphs: - -- Stats: - - Global Success Rate: the percentage of requests that are successful over the last 30 seconds (`nginxplus_upstream_server_responses` with response codes of `1xx` or `2xx`). - - Global Request Volume: the volume of requests over the last 30 seconds, measured in requests per second (`nginxplus_http_total_requests`) . - - Pods Monitored: The number of pods being scraped by Prometheus. -- Graphs: - - Request Volume: this graph shows the volume of requests per Pod, in requests per second (`nginxplus_http_total_requests`). - - Pod Success: this graph shows the percentage of requests that are successful per Pod (`nginxplus_upstream_server_responses` with response codes of `1xx` or `2xx`). - - Sidecar Memory Usage (RSS): this graph shows the Resident Set Size (RSS) of the NSM sidecars. - - Sidecar Memory Usage (Private): this graph shows the Private memory used by the NSM sidecars. - - -## Customizing your own Dashboard - -NGINX Service Mesh exposes NGINX Plus metrics in Prometheus format that can be used to create your own panels and custom dashboards. For a list of available metrics, labels, and example Prometheus queries, see the [Traffic Metrics guide](https://docs.nginx.com/nginx-service-mesh/guides/prometheus-metrics/). diff --git a/static/examples/grafana/dashboard.png b/static/examples/grafana/dashboard.png deleted file mode 100644 index 3f3db0568aa2ec7ca479c74b016c8da75543bf1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251004 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∨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

hEaCAvLCVR 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�H$%eJ_@T=I(GR!mB~8B#iIop%O5{2{nNo*)ASU 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 Kmr4=6fd3DQW9;() diff --git a/static/examples/grafana/nginx-mesh-top.json b/static/examples/grafana/nginx-mesh-top.json deleted file mode 100644 index 7ecdf09c1..000000000 --- a/static/examples/grafana/nginx-mesh-top.json +++ /dev/null @@ -1,731 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.2.0" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "singlestat", - "name": "Singlestat", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "links": [], - "panels": [ - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "format": "percentunit", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 6, - "w": 8, - "x": 0, - "y": 0 - }, - "id": 4, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(irate(nginxplus_upstream_server_responses{code=~\"1xx|2xx\"}[30s])) / sum(irate(nginxplus_upstream_server_responses[30s]))", - "format": "time_series", - "interval": "5s", - "intervalFactor": 1, - "refId": "A" - } - ], - "thresholds": "", - "title": "GLOBAL SUCCESS RATE", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "format": "reqps", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 6, - "w": 13, - "x": 8, - "y": 0 - }, - "id": 6, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": true - }, - "tableColumn": "", - "targets": [ - { - "expr": "sum(irate(nginxplus_http_requests_total[30s]))", - "format": "time_series", - "interval": "5s", - "intervalFactor": 1, - "refId": "A" - } - ], - "thresholds": "", - "title": "GLOBAL REQUEST VOLUME", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 6, - "w": 3, - "x": 21, - "y": 0 - }, - "id": 5, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": true, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "", - "targets": [ - { - "expr": "count(nginxplus_http_requests_total)", - "format": "time_series", - "interval": "5s", - "intervalFactor": 1, - "refId": "A" - } - ], - "thresholds": "", - "title": "PODS MONITORED", - "type": "singlestat", - "valueFontSize": "200%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 0, - "y": 6 - }, - "hiddenSeries": false, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "irate(nginxplus_http_requests_total[30s])", - "format": "time_series", - "interval": "", - "intervalFactor": 1, - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Request Volume", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "reqps", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 9, - "w": 12, - "x": 12, - "y": 6 - }, - "hiddenSeries": false, - "id": 123124, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(irate(nginxplus_upstream_server_responses{code=~\"1xx|2xx\"}[30s])) by (app, version) / sum(irate(nginxplus_upstream_server_responses[30s])) by (app, version)", - "format": "time_series", - "instant": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Pod Success", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "percentunit", - "label": null, - "logBase": 1, - "max": "1", - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": null, - "description": "RSS used by NGINX Service Mesh sidecars", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 15 - }, - "hiddenSeries": false, - "id": 123126, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "nginxplus_workers_mem_rss", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Sidecar Memory Usage (RSS)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "decbytes", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": null, - "description": "Private memory used by NGINX Service Mesh sidecars", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 0, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 15 - }, - "hiddenSeries": false, - "id": 123128, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pointradius": 2, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "nginxplus_workers_mem_private", - "interval": "", - "legendFormat": "", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Sidecar Memory Usage (Private)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "5s", - "schemaVersion": 27, - "style": "dark", - "tags": [ - "nginx-service-mesh" - ], - "templating": { - "list": [] - }, - "time": { - "from": "now-5m", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "NGINX Mesh Top", - "uid": "N3zQ72OWk", - "version": 2 -} diff --git a/static/examples/grafana/nginx-service-mesh-summary.json b/static/examples/grafana/nginx-service-mesh-summary.json deleted file mode 100644 index eec7bae6f..000000000 --- a/static/examples/grafana/nginx-service-mesh-summary.json +++ /dev/null @@ -1,601 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "6.6.0" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "stat", - "name": "Stat", - "version": "" - }, - { - "type": "panel", - "id": "text", - "name": "Text", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "iteration": 1618339715410, - "links": [], - "panels": [ - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 14, - "panels": [], - "title": "Global", - "type": "row" - }, - { - "datasource": null, - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 24, - "x": 0, - "y": 1 - }, - "id": 12, - "options": { - "content": "

\nGlobal View\n
", - "mode": "html" - }, - "pluginVersion": "8.3.4", - "timeFrom": null, - "timeShift": null, - "transparent": true, - "type": "text" - }, - { - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": { - "mappings": [ - { - "from": "", - "id": 0, - "text": "N/A", - "to": "", - "type": 1, - "value": "null" - } - ], - "thresholds": { - "mode": "percentage", - "steps": [ - { - "color": "rgb(255, 255, 255)", - "value": null - }, - { - "color": "dark-red", - "value": 89 - }, - { - "color": "rgb(242, 154, 54)", - "value": 90 - }, - { - "color": "rgb(255, 242, 0)", - "value": 95 - }, - { - "color": "rgb(0, 150, 57)", - "value": 99.99999 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 9, - "x": 0, - "y": 3 - }, - "id": 2, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.3.4", - "targets": [ - { - "expr": "sum(irate(nginxplus_upstream_server_responses{code=~\"1xx|2xx\"}[30s])) / sum(irate(nginxplus_upstream_server_responses[30s]))", - "interval": "5s", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Global Sucess Rate", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": { - "mappings": [ - { - "from": "", - "id": 0, - "text": "N/A", - "to": "", - "type": 1, - "value": "null" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgb(255, 255, 255)", - "value": null - }, - { - "color": "rgb(0, 150, 57)", - "value": 1 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 6, - "x": 9, - "y": 3 - }, - "id": 6, - "options": { - "colorMode": "value", - "graphMode": "none", - "justifyMode": "auto", - "orientation": "auto", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.3.4", - "targets": [ - { - "expr": "count(nginxplus_http_requests_total)", - "interval": "5s", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Pods Monitored", - "type": "stat" - }, - { - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": { - "mappings": [ - { - "from": "", - "id": 0, - "text": "N/A", - "to": "", - "type": 1, - "value": "null" - } - ], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "rgb(255, 255, 255)", - "value": null - }, - { - "color": "rgb(228, 0, 43)", - "value": 0 - }, - { - "color": "rgb(0, 150, 57)", - "value": 0.01 - } - ] - }, - "unit": "reqps" - }, - "overrides": [] - }, - "gridPos": { - "h": 6, - "w": 9, - "x": 15, - "y": 3 - }, - "id": 4, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "horizontal", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "text": {}, - "textMode": "auto" - }, - "pluginVersion": "8.3.4", - "targets": [ - { - "expr": "sum(irate(nginxplus_http_requests_total[30s]))", - "interval": "5s", - "legendFormat": "", - "refId": "A" - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Global Request Volume", - "type": "stat" - }, - { - "collapsed": false, - "datasource": null, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 9 - }, - "id": 17, - "panels": [], - "title": "Workloads", - "type": "row" - }, - { - "datasource": null, - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "gridPos": { - "h": 2, - "w": 24, - "x": 0, - "y": 10 - }, - "id": 15, - "options": { - "content": "
\nWorkload View\n
", - "mode": "html" - }, - "pluginVersion": "8.3.4", - "timeFrom": null, - "timeShift": null, - "transparent": true, - "type": "text" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 3, - "gridPos": { - "h": 7, - "w": 12, - "x": 0, - "y": 12 - }, - "hiddenSeries": false, - "id": 8, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.4", - "pointradius": 2, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "irate(nginxplus_http_requests_total[30s])", - "interval": "", - "legendFormat": "App: {{app}} Ver: {{version}} NS: {{namespace}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Request Volume", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "reqps", - "label": null, - "logBase": 1, - "max": null, - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fieldConfig": { - "defaults": {}, - "overrides": [] - }, - "fill": 1, - "fillGradient": 2, - "gridPos": { - "h": 7, - "w": 12, - "x": 12, - "y": 12 - }, - "hiddenSeries": false, - "id": 10, - "legend": { - "alignAsTable": true, - "avg": false, - "current": false, - "max": false, - "min": false, - "rightSide": true, - "show": true, - "sideWidth": null, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 2, - "nullPointMode": "null", - "options": { - "alertThreshold": true - }, - "percentage": false, - "pluginVersion": "8.3.4", - "pointradius": 2, - "points": true, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "sum(irate(nginxplus_upstream_server_responses{code=~\"1xx|2xx\"}[30s])) by (app, version) / sum(irate(nginxplus_upstream_server_responses[30s])) by (app, version)", - "interval": "", - "legendFormat": "App: {{app}} Ver: {{version}}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeRegions": [], - "timeShift": null, - "title": "Pod Success", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "percentunit", - "label": null, - "logBase": 1, - "max": "1", - "min": "0", - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - } - ], - "refresh": "5s", - "schemaVersion": 27, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "current": { - "selected": true, - "text": [ - "prometheus" - ], - "value": [ - "prometheus" - ] - }, - "description": null, - "error": null, - "hide": 0, - "includeAll": true, - "label": "Datasource", - "multi": false, - "name": "datasource", - "options": [], - "query": "prometheus", - "queryValue": "", - "refresh": 1, - "regex": "", - "skipUrlSync": false, - "type": "datasource" - } - ] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] - }, - "timezone": "", - "title": "NGINX Service Mesh Dashboard", - "uid": "jeEbxdLMk", - "version": 1 -} From b5f98d9eeb92d0148069b22b170060127b9fb33f Mon Sep 17 00:00:00 2001 From: Alan Dooley Date: Wed, 5 Nov 2025 12:13:10 +0000 Subject: [PATCH 2/2] feat: Remove NGINX Service Mesh from linkchecker --- .github/workflows/linkchecker.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index 704a9b581..5e279976a 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -56,7 +56,6 @@ jobs: - waf - nginx-ingress-controller - nginxaas/azure - - nginx-service-mesh - nginx-amplify - nginx-controller - nginx-waf