diff --git a/docs/InstallationGuide.md b/docs/InstallationGuide.md
index 504f099..dca643d 100644
--- a/docs/InstallationGuide.md
+++ b/docs/InstallationGuide.md
@@ -6,10 +6,15 @@
+ |  | 
+--- | --- | ---
+
+
+
## Pre-Requisites
- Working kubernetes cluster, with admin privleges
-- Running nginx-ingress controller, either OSS or Plus. This install guide follows the instructions for deploying an Nginx Ingress Controller here: https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/
+- Running nginx-ingress controller, either OSS or Plus. This install guide followed the instructions for deploying an Nginx Ingress Controller here: https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/
- Demo application, this install guide uses the Nginx Cafe example, found here: https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/ingress-resources/complete-example
- A bare metal Linux server or VM for the external LB Server, connected to a network external to the cluster. Two of these will be required if High Availability is needed, as shown here.
- Nginx Plus software loaded on the LB Server(s). This install guide follows the instructions for installing Nginx Plus on Centos 7, located here: https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/
@@ -19,15 +24,29 @@
## Kubernetes Cluster
+
+
+
+
+
+
A standard K8s cluster is all that is required. There must be enough resources available to run the Nginx Ingress Controller, and the Nginx Kubernetes Loadbalancer Controller. You must have administrative access to be able to create the namespace, services, and deployments for this Solution. This Solution was tested on Kubernetes version 1.23. Most recent versions => v1.21 should work just fine.
## Nginx Ingress Controller
+
+
+
+
+
+
The Nginx Ingress Controller in this Solution is the destination target for traffic (north-south) that is being sent to the cluster. The installation of the actual Ingress Controller is outside the scope of this installation guide, but we include the links to the docs for your reference. `The NIC installation must follow the documents exactly as written,` as this Solution refers to the `nginx-ingress` namespace and service objects. **Only the very last step is changed.**
-NOTE: This Solution only works with nginx-ingress from Nginx. It will `not` work with the Community version of Ingress, called ingress-nginx. If you are unsure which Ingress Controller you are running, check out the blog on Nginx.com:
+NOTE: This Solution only works with nginx-ingress from Nginx. It will `not` work with the Community version of Ingress, called ingress-nginx.
+
+If you are unsure which Ingress Controller you are running, check out the blog on Nginx.com:
https://www.nginx.com/blog/guide-to-choosing-ingress-controller-part-4-nginx-ingress-controller-options
@@ -62,23 +81,35 @@ spec:
```
+Apply the updated nodeport-nkl.yaml Manifest:
```bash
kubectl apply -f nodeport-nkl.yaml
```
+
+## Demo Application
-## Demo Application
+
-This is not part of the actual Solution, but it is useful to have a well-known application running in the cluster, as a useful target for test commands. The example provided here is used by the Solution to demonstrate proper traffic flows, and application health check monitoring, to determine if the application is running in the cluster. If you choose a different Application to test with, the health checks provided here will NOT work, and will need to be modified to work correctly.
+
+
+This is not part of the actual Solution, but it is useful to have a well-known application running in the cluster, as a known-good target for test commands. The example provided here is used by the Solution to demonstrate proper traffic flows, as well as application health check monitoring, to determine if the application is running in the cluster.
+
+Note: If you choose a different Application to test with, `the Nginx health checks provided here will NOT work,` and will need to be modified to work correctly.
- Deploy the Nginx Cafe Demo application, found here:
https://github.com/nginxinc/kubernetes-ingress/tree/main/examples/ingress-resources/complete-example
+- The Cafe Demo Docker image used is an upgraded one, with graphics and additional Request and Response variables added.
+
+https://hub.docker.com/r/nginxinc/ingress-demo
+You can use the `cafe.yaml` manifest included.
+
- Do not use the `cafe-ingress.yaml` file. Rather, use the `cafe-virtualserver.yaml` file that is provided here. It uses the Nginx CRDs to define a VirtualServer, and the related Routes and Redirects needed. The `redirects are required` for the LB Server's health checks to work correctly!
```yaml
@@ -143,9 +174,12 @@ spec:
## Linux VM or bare-metal LB Server
-This is a standard Linux OS system, based on the Linux Distro and Technical Specs required for Nginx Plus, which can be found here: https://docs.nginx.com/nginx/technical-specs/
+
+
-This installation guide followed the "Installation of Nginx Plus on Centos/Redhat/Oracle" steps for installing Nginx Plus.
+This is any standard Linux OS system, based on the Linux Distro and Technical Specs required for Nginx Plus, which can be found here: https://docs.nginx.com/nginx/technical-specs/
+
+This Solution followed the "Installation of Nginx Plus on Centos/Redhat/Oracle" steps for installing Nginx Plus.
>NOTE: This solution will not work with Nginx OpenSource, as OpenSource does not have the API that is used in this Solution. Installation on unsupported Distros is not recommended or supported.
@@ -153,11 +187,17 @@ This installation guide followed the "Installation of Nginx Plus on Centos/Redha
## Nginx Plus LB Server
+
+
+
+
+
+
This is the configuration required for the LB Server, external to the cluster. It must be configured for the following.
- Move the Nginx default Welcome page from port 80 to port 8080. Port 80 will be used by the stream context, instead of the http context.
- API write access enabled on port 9000.
-- Plus Dashboard enabled, used for testing, monitoring, and visualization of the solution working.
+- Plus Dashboard enabled, used for testing, monitoring, and visualization of the Solution working.
- The `Stream` context is enabled, for TCP loadbalancing.
- Stream context is configured.
@@ -202,13 +242,13 @@ server {

-- Create a new folder for the stream config .conf files. /etc/nginx/stream was used in this Solution.
+- Create a new folder for the stream config .conf files. /etc/nginx/stream is used in this Solution.
```bash
mkdir /etc/nginx/stream
```
-- Create 2 new `STATE` files for Nginx. These are used to backup the configuration, in case Nginx restarts/reloads.
+- Create 2 new `STATE` files for Nginx. These are used to backup the Upstream configuration, in case Nginx is restarted/reloaded.
Nginx State Files Required for Upstreams
- state file /var/lib/nginx/state/nginx-lb-http.state
@@ -289,7 +329,7 @@ stream {
`Notice that is uses Ports 80 and 443.`
- Place this file in the /etc/nginx/stream folder.
+ Place this file in the /etc/nginx/stream folder, and reload Nginx. Notice the match block and health check directives are for the cafe.example.com Demo application from Nginx.
```bash
# NginxK8sLB Stream configuration, for L4 load balancing
@@ -338,6 +378,11 @@ stream {
+
+
+
+
+
This is the new Controller, which is configured to watch the k8s environment, the nginx-ingress Service object, and send API updates to the Nginx LB Server when there are changes. It only requires three things.
- New kubernetes namespace and RBAC
diff --git a/docs/cafe.yaml b/docs/cafe.yaml
new file mode 100644
index 0000000..6736ec8
--- /dev/null
+++ b/docs/cafe.yaml
@@ -0,0 +1,70 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: coffee
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: coffee
+ template:
+ metadata:
+ labels:
+ app: coffee
+ spec:
+ containers:
+ - name: coffee
+ image: nginxinc/ingress-demo # upgraded Cafe Docker image
+ ports:
+ - containerPort: 80
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: coffee-svc
+spec:
+ type: ClusterIP
+ clusterIP: None
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ name: http
+ selector:
+ app: coffee
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: tea
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: tea
+ template:
+ metadata:
+ labels:
+ app: tea
+ spec:
+ containers:
+ - name: tea
+ image: nginxinc/ingress-demo
+ ports:
+ - containerPort: 80
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: tea-svc
+ labels:
+spec:
+ type: ClusterIP
+ clusterIP: None
+ ports:
+ - port: 80
+ targetPort: 80
+ protocol: TCP
+ name: http
+ selector:
+ app: tea
diff --git a/docs/http/clusters.conf b/docs/http/clusters.conf
new file mode 100644
index 0000000..58ea593
--- /dev/null
+++ b/docs/http/clusters.conf
@@ -0,0 +1,129 @@
+# NginxK8sLB HTTP configuration, for L7 load balancing
+# Chris Akker, Apr 2023
+# HTTP Proxy and load balancing
+# 2 k8s Clusters LB with http split clients
+# Nginx Kubernetes Loadbalancer
+# Upstream servers managed by NKL Controller
+# Nginx Key Value store for Split ratios
+#
+#### clusters.conf
+
+# Define Key Value store, backup state file, timeout, and enable sync
+
+keyval_zone zone=split:1m state=/var/lib/nginx/state/split.keyval timeout=30d sync;
+ keyval $host $split_level zone=split;
+
+# Main Nginx Server Block for cafe.example.com, with TLS
+
+server {
+ listen 443 ssl;
+ status_zone https://cafe.example.com;
+ server_name cafe.example.com;
+
+ ssl_certificate /etc/ssl/nginx/default.crt;
+ ssl_certificate_key /etc/ssl/nginx/default.key;
+
+ location / {
+ status_zone /;
+
+ proxy_set_header Host $host;
+ proxy_http_version 1.1;
+ proxy_set_header "Connection" "";
+ proxy_pass https://$upstream;
+
+ }
+
+}
+
+# Cluster1 upstreams
+
+upstream cluster1-https {
+ zone cluster1-https 256k;
+ least_time last_byte;
+ server 10.1.1.10:31317;
+ server 10.1.1.8:31317;
+ keepalive 16;
+ #servers managed by NKL
+ #state /var/lib/nginx/state/cluster1-https.state;
+}
+
+# Cluster2 upstreams
+
+upstream cluster2-https {
+ zone cluster2-https 256k;
+ least_time last_byte;
+ server 10.1.1.11:31390;
+ server 10.1.1.12:31390;
+ #servers managed by NKL
+ #state /var/lib/nginx/state/cluster2-https.state;
+}
+
+# HTTP Split Clients Configuration for Cluster1/Cluster2 ratios
+
+split_clients $request_id $split0 {
+ * cluster2-https;
+ }
+
+split_clients $request_id $split1 {
+ 1.0% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split5 {
+ 5.0% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split10 {
+ 10% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split25 {
+ 25% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split50 {
+ 50% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split75 {
+ 75% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split90 {
+ 90% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split95 {
+ 95% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split99 {
+ 99% cluster1-https;
+ * cluster2-https;
+ }
+
+split_clients $request_id $split100 {
+ * cluster1-https;
+ }
+
+map $split_level $upstream {
+ 0 $split0;
+ 1.0 $split1;
+ 5.0 $split5;
+ 10 $split10;
+ 25 $split25;
+ 50 $split50;
+ 75 $split75;
+ 90 $split90;
+ 95 $split95;
+ 99 $split99;
+ 100 $split100;
+ default $split50;
+ }
diff --git a/docs/http/grafana-dashboard.json b/docs/http/grafana-dashboard.json
new file mode 100644
index 0000000..b032697
--- /dev/null
+++ b/docs/http/grafana-dashboard.json
@@ -0,0 +1,1346 @@
+{
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "datasource",
+ "uid": "grafana"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "target": {
+ "limit": 100,
+ "matchAny": false,
+ "tags": [],
+ "type": "dashboard"
+ },
+ "type": "dashboard"
+ }
+ ]
+ },
+ "description": "NGINX Plus Prometheus Exporter",
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "gnetId": 12930,
+ "graphTooltip": 0,
+ "id": 2,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 6,
+ "panels": [],
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "refId": "A"
+ }
+ ],
+ "title": "Metrics",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 0,
+ "y": 1
+ },
+ "id": 27,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(increase(nginxplus_http_requests_total[1h]))",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Total requests in last hour",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "dark-blue",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 6,
+ "y": 1
+ },
+ "id": 29,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(increase(nginxplus_upstream_server_responses{code=\"2xx\"}[1h]))",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Total (2XX from upstream) requests in last hour",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "orange",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 12,
+ "y": 1
+ },
+ "id": 30,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(increase(nginxplus_upstream_server_responses{code=\"4xx\"}[1h]))",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Total (4XX from upstream) requests in last hour",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "red",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 6,
+ "x": 18,
+ "y": 1
+ },
+ "id": 28,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(increase(nginxplus_upstream_server_responses{code=\"5xx\"}[1h]))",
+ "interval": "",
+ "legendFormat": "",
+ "refId": "A"
+ }
+ ],
+ "title": "Total (5XX from upstream) requests in last hour",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "requests (rate)",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 13,
+ "w": 12,
+ "x": 0,
+ "y": 4
+ },
+ "id": 15,
+ "links": [],
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "expr": "irate(nginxplus_http_requests_total{instance=~\"$instance\"}[5m])",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}}",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum (irate(nginxplus_http_requests_total{instance=~\"$instance\"}[5m]))",
+ "hide": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Total",
+ "refId": "B"
+ }
+ ],
+ "title": "Total Requests",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 13,
+ "w": 12,
+ "x": 12,
+ "y": 4
+ },
+ "id": 20,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "asc"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "exemplar": false,
+ "expr": "irate(nginxplus_upstream_server_response_time{instance=~\"$instance\"}[$__interval])",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "legendFormat": "{{ instance }}",
+ "range": true,
+ "refId": "A"
+ }
+ ],
+ "title": "LB Server Resp Time",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "requests (rate)",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 14,
+ "w": 12,
+ "x": 0,
+ "y": 17
+ },
+ "id": 31,
+ "links": [],
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "expr": "irate(nginxplus_upstream_server_requests{instance=~\"$instance\"}[$__interval])",
+ "format": "time_series",
+ "hide": false,
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{ upstream }} - {{ server }}",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "expr": "sum (irate(nginxplus_upstream_server_requests{instance=~\"$instance\"}[$__interval]))",
+ "hide": true,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "Total",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "LB Upstream Requests",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "ms"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 14,
+ "w": 12,
+ "x": 12,
+ "y": 17
+ },
+ "id": 25,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "expr": "avg by (server) (nginxplus_upstream_server_response_time)",
+ "hide": true,
+ "interval": "",
+ "legendFormat": "{{ server }}",
+ "range": true,
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "editorMode": "code",
+ "expr": "irate(nginxplus_upstream_server_response_time{instance=~\"$instance\"}[$__interval])",
+ "hide": false,
+ "interval": "",
+ "legendFormat": "{{ server }}",
+ "range": true,
+ "refId": "B"
+ }
+ ],
+ "title": "LB Upstream Resp Time",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "Connections (rate)",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 1,
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 0,
+ "y": 31
+ },
+ "id": 10,
+ "links": [],
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "irate(nginxplus_connections_accepted{instance=~\"$instance\"}[5m])",
+ "format": "time_series",
+ "instant": false,
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}} accepted",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(irate(nginxplus_connections_accepted[5m]))",
+ "interval": "",
+ "legendFormat": "Total",
+ "refId": "B"
+ }
+ ],
+ "title": "Processed connections",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "Connections",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "decimals": 0,
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 12,
+ "x": 12,
+ "y": 31
+ },
+ "id": 12,
+ "links": [],
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "nginxplus_connections_active{instance=~\"$instance\"}",
+ "format": "time_series",
+ "interval": "",
+ "intervalFactor": 1,
+ "legendFormat": "{{instance}}",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum(nginxplus_connections_active)",
+ "interval": "",
+ "legendFormat": "Total",
+ "refId": "B"
+ }
+ ],
+ "title": "Active Connections",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 24,
+ "x": 0,
+ "y": 40
+ },
+ "id": 22,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum by (upstream ) (irate(nginxplus_upstream_server_responses{code=\"2xx\"}[5m]))",
+ "interval": "",
+ "legendFormat": "{{ upstream }}",
+ "refId": "A"
+ }
+ ],
+ "title": "2XX status code (rate over 5m)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 48
+ },
+ "id": 24,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum by (upstream ) (irate(nginxplus_upstream_server_responses{code=\"4xx\"}[5m]))",
+ "interval": "",
+ "legendFormat": "{{ upstream }}",
+ "refId": "A"
+ }
+ ],
+ "title": "4XX status code (rate over 5m)",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 10,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "never",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "links": [],
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ },
+ "unit": "short"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 48
+ },
+ "id": 23,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "multi",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "9.4.7",
+ "targets": [
+ {
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "expr": "sum by (upstream ) (irate(nginxplus_upstream_server_responses{code=\"5xx\"}[5m]))",
+ "interval": "",
+ "legendFormat": "{{ upstream }}",
+ "refId": "A"
+ }
+ ],
+ "title": "5XX status code (rate over 5m)",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "5s",
+ "revision": 1,
+ "schemaVersion": 38,
+ "style": "dark",
+ "tags": [
+ "nginx"
+ ],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": true,
+ "text": [
+ "10.1.1.4:80"
+ ],
+ "value": [
+ "10.1.1.4:80"
+ ]
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "definition": "label_values(up, instance)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "",
+ "multi": true,
+ "name": "instance",
+ "options": [],
+ "query": {
+ "query": "label_values(up, instance)",
+ "refId": "StandardVariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 0,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ },
+ {
+ "current": {
+ "selected": true,
+ "text": [
+ "All"
+ ],
+ "value": [
+ "$__all"
+ ]
+ },
+ "datasource": {
+ "type": "prometheus",
+ "uid": "EszmC2Y4z"
+ },
+ "definition": "label_values(upstream)",
+ "hide": 0,
+ "includeAll": true,
+ "label": "",
+ "multi": true,
+ "name": "upstream",
+ "options": [],
+ "query": {
+ "query": "label_values(upstream)",
+ "refId": "StandardVariableQuery"
+ },
+ "refresh": 1,
+ "regex": "",
+ "skipUrlSync": false,
+ "sort": 1,
+ "tagValuesQuery": "",
+ "tagsQuery": "",
+ "type": "query",
+ "useTags": false
+ }
+ ]
+ },
+ "time": {
+ "from": "now-5m",
+ "to": "now"
+ },
+ "timepicker": {
+ "hidden": false,
+ "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 K8s LB",
+ "uid": "L4nOehY4k",
+ "version": 7,
+ "weekStart": ""
+ }
+
\ No newline at end of file
diff --git a/docs/http/nginx.conf b/docs/http/nginx.conf
new file mode 100644
index 0000000..6a97691
--- /dev/null
+++ b/docs/http/nginx.conf
@@ -0,0 +1,47 @@
+user nginx;
+worker_processes auto;
+
+error_log /var/log/nginx/error.log notice;
+pid /var/run/nginx.pid;
+
+load_module modules/ngx_http_js_module.so;
+
+worker_rlimit_nofile 2048;
+
+events {
+ worker_connections 2048;
+}
+
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ log_format main '$remote_addr - $upstream_addr - $upstream_status - $remote_user [$time_local] $host - "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+
+ access_log /var/log/nginx/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ include /etc/nginx/conf.d/*.conf;
+
+ #added for Prometheus
+ subrequest_output_buffer_size 32k;
+
+}
+
+
+# TCP load balancing block
+#
+stream {
+ include /etc/nginx/stream/*.conf;
+ log_format stream '$remote_addr - $server_addr [$time_local] $status $upstream_addr $upstream_bytes_sent';
+ access_log /var/log/nginx/stream.log stream;
+}
diff --git a/docs/http/prometheus.conf b/docs/http/prometheus.conf
new file mode 100644
index 0000000..97fc124
--- /dev/null
+++ b/docs/http/prometheus.conf
@@ -0,0 +1,18 @@
+# NginxK8sLB Prometheus configuration, for HTTP scraper page
+# Chris Akker, Apr 2023
+# https://www.nginx.com/blog/how-to-visualize-nginx-plus-with-prometheus-and-grafana/
+#
+#### prometheus.conf
+
+js_import /usr/share/nginx-plus-module-prometheus/prometheus.js;
+
+server {
+ location = /metrics {
+ js_content prometheus.metrics;
+ }
+
+ location /api {
+ api;
+ }
+
+}
diff --git a/docs/http/prometheus.yml b/docs/http/prometheus.yml
new file mode 100644
index 0000000..c8b19cb
--- /dev/null
+++ b/docs/http/prometheus.yml
@@ -0,0 +1,14 @@
+global:
+ scrape_interval: 15s
+
+ external_labels:
+ monitor: 'codelab-monitor'
+
+scrape_configs:
+ - job_name: 'prometheus'
+
+ scrape_interval: 5s
+
+ static_configs:
+ - targets: ['10.1.1.4:80', '10.1.1.5:80']
+
\ No newline at end of file
diff --git a/docs/http/zonesync.conf b/docs/http/zonesync.conf
new file mode 100644
index 0000000..b11ed73
--- /dev/null
+++ b/docs/http/zonesync.conf
@@ -0,0 +1,19 @@
+# NginxK8sLB Zone Sync configuration, for KVstore split
+# Chris Akker, Apr 2023
+# Stream Zone Sync block
+# 2 Nginx Plus nodes KVstore zone
+# Nginx Kubernetes Loadbalancer
+# https://docs.nginx.com/nginx/admin-guide/high-availability/zone_sync/
+#
+#### zonesync.conf
+
+server {
+ zone_sync;
+
+ listen 9001;
+
+ # cluster of 2 nodes
+ zone_sync_server 10.1.1.4:9001;
+ zone_sync_server 10.1.1.5:9001;
+
+ }
diff --git a/docs/media/cafe-dashboard.png b/docs/media/cafe-dashboard.png
new file mode 100644
index 0000000..c5cf902
Binary files /dev/null and b/docs/media/cafe-dashboard.png differ
diff --git a/docs/media/kubernetes-icon.png b/docs/media/kubernetes-icon.png
new file mode 100644
index 0000000..63c679e
Binary files /dev/null and b/docs/media/kubernetes-icon.png differ
diff --git a/docs/media/linux-icon.png b/docs/media/linux-icon.png
new file mode 100644
index 0000000..0ce8869
Binary files /dev/null and b/docs/media/linux-icon.png differ
diff --git a/docs/media/nginx-2020.png b/docs/media/nginx-2020.png
new file mode 100644
index 0000000..dddde7f
Binary files /dev/null and b/docs/media/nginx-2020.png differ
diff --git a/docs/media/nginx-icon.png b/docs/media/nginx-icon.png
new file mode 100644
index 0000000..43a81f5
Binary files /dev/null and b/docs/media/nginx-icon.png differ
diff --git a/docs/media/nginx-ingress-icon.png b/docs/media/nginx-ingress-icon.png
new file mode 100644
index 0000000..0196a5a
Binary files /dev/null and b/docs/media/nginx-ingress-icon.png differ
diff --git a/docs/media/nginx-logo.png b/docs/media/nginx-logo.png
new file mode 100644
index 0000000..c333dc9
Binary files /dev/null and b/docs/media/nginx-logo.png differ
diff --git a/docs/media/nginx-plus-icon.png b/docs/media/nginx-plus-icon.png
new file mode 100644
index 0000000..21f0e25
Binary files /dev/null and b/docs/media/nginx-plus-icon.png differ
diff --git a/docs/media/nginxlb-multicluster-http.png b/docs/media/nginxlb-multicluster-http.png
new file mode 100644
index 0000000..76dfd64
Binary files /dev/null and b/docs/media/nginxlb-multicluster-http.png differ
diff --git a/docs/media/nginxlb-multicluster.png b/docs/media/nginxlb-multicluster.png
new file mode 100644
index 0000000..d0441bf
Binary files /dev/null and b/docs/media/nginxlb-multicluster.png differ
diff --git a/docs/media/nginxredplus.png b/docs/media/nginxredplus.png
new file mode 100644
index 0000000..c5509df
Binary files /dev/null and b/docs/media/nginxredplus.png differ
diff --git a/docs/media/nkl-background.png b/docs/media/nkl-background.png
index 741415b..57b112d 100644
Binary files a/docs/media/nkl-background.png and b/docs/media/nkl-background.png differ
diff --git a/docs/media/nkl-banner.png b/docs/media/nkl-banner.png
new file mode 100644
index 0000000..2aa37bb
Binary files /dev/null and b/docs/media/nkl-banner.png differ
diff --git a/docs/media/robot.svg b/docs/media/robot.svg
new file mode 100644
index 0000000..8a61287
--- /dev/null
+++ b/docs/media/robot.svg
@@ -0,0 +1,163 @@
+
+
+