Repository: kubernetes/ingress-nginx
Pull request #6864 (2021-02-10 18:56:48)

Add scope configuration check.
<!--- Provide a general summary of your changes in the Title above --->
<!--- Please don't @-mention people in PR or commit messages (do so in an additional comment). --->

## What this PR does / why we need it:
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

A specific configuration of scope variables result in errors in runtime.

| config | Helm render | Kubernetes runtime |
|-|-|-|
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=true \`<br>&nbsp;&nbsp;`--set=rbac.scope=true` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=false \`<br>&nbsp;&nbsp;`--set=rbac.scope=false ` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=true \`<br>&nbsp;&nbsp;`--set=rbac.scope=false ` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=false \`<br>&nbsp;&nbsp;`--set=rbac.scope=true` | ✔️  | :x: |

### Reproduction

```console
$ helm install nginx-ingress charts/ingress-nginx \
    --set=controller.scope.enabled=false \
    --set=rbac.scope=true
```
results in runtime in

```console
$ kubectl logs -f nginx-ingress-controller-5566dbf6f-cr4ms

 ❌ Failed to watch *v1beta1.Ingress: failed to list *v1beta1.Ingress: ingresses.networking.k8s.io is forbidden: User "system:serviceaccount:default:nginx-ingress" cannot list resource "ingresses" in API group "networking.k8s.io" at the cluster scope
```

By checking these config incompatibility during rendering the chart we can catch this early on.

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

```console
$ helm template charts/ingress-nginx \
    --output-dir=_manifests \
    --set=controller.scope.enabled=false \
    --set=rbac.scope=true

  Invalid configuration: 'rbac.scope' should be equal to 'controller.scope.enabled' (true/false).
```

| config | Helm render | Kubernetes runtime |
|-|-|-|
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=true \`<br>&nbsp;&nbsp;`--set=rbac.scope=true` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=false \`<br>&nbsp;&nbsp;`--set=rbac.scope=false ` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=true \`<br>&nbsp;&nbsp;`--set=rbac.scope=false ` | :white_check_mark: | :white_check_mark: |
| `helm template charts/ingress-nginx \`<br>&nbsp;&nbsp;`--output-dir=_manifests \`<br>&nbsp;&nbsp;`--set=controller.scope.enabled=false \`<br>&nbsp;&nbsp;`--set=rbac.scope=true` | ✖️  | |


## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] ~~My change requires a change to the documentation.~~
- [ ] ~~I have updated the documentation accordingly.~~
- [x] I've read the [CONTRIBUTION](https://github.com/kubernetes/ingress-nginx/blob/master/CONTRIBUTING.md) guide
- [ ] ~~I have added tests to cover my changes.~~
- [x] All ~~new and~~ existing tests passed.



k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please log a ticket with the Linux Foundation Helpdesk: <https://support.linuxfoundation.org/>
- Should you encounter any issues with the Linux Foundation Helpdesk, send a message to the backup e-mail support address at: login-issues@jira.linuxfoundation.org

<!-- need_sender_cla -->

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: Welcome @maikelvl! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes/ingress-nginx'>kubernetes/ingress-nginx</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes/ingress-nginx has [its own contribution guidelines](https://github.com/kubernetes/ingress-nginx/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @maikelvl. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/6864#" title="Author self-approved">maikelvl</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **chiefalexander** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @chiefalexander` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details open>
Needs approval from an approver in each of these files:

- **[charts/ingress-nginx/OWNERS](https://github.com/kubernetes/ingress-nginx/blob/master/charts/ingress-nginx/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["chiefalexander"]} -->
maikelvl: I've signed the CLA
fejta-bot: Issues go stale after 90d of inactivity.
Mark the issue as fresh with `/remove-lifecycle stale`.
Stale issues rot after an additional 30d of inactivity and eventually close.

If this issue is safe to close now please do so with `/close`.

Send feedback to sig-contributor-experience at [kubernetes/community](https://github.com/kubernetes/community).
/lifecycle stale
rikatz: /area helm
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/6864#" title="Author self-approved">maikelvl</a>*, *<a href="https://github.com/kubernetes/ingress-nginx/pull/6864#pullrequestreview-697038158" title="LGTM">tao12345666333</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **chiefalexander** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @chiefalexander` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details open>
Needs approval from an approver in each of these files:

- **[charts/ingress-nginx/OWNERS](https://github.com/kubernetes/ingress-nginx/blob/master/charts/ingress-nginx/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["chiefalexander"]} -->

Patch:
diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml
index a8a4c0227..7c54282de 100644
--- a/.github/workflows/helm.yaml
+++ b/.github/workflows/helm.yaml
@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - main
+      - dev-v1
 
 jobs:
 
diff --git a/OWNERS b/OWNERS
index 250bce4e8..1d90c4cd5 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,15 +1,12 @@
 # See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
 
 approvers:
-  - ingress-nginx-admins
-  - ingress-nginx-maintainers
-  - ElvinEfendi
-  - rikatz
+- ingress-nginx-admins
+- ingress-nginx-maintainers
+- sig-network-leads
 
 reviewers:
-  - ElvinEfendi
-  - cmluciano
-  - rikatz
+- ingress-nginx-reviewers
 
 emeritus_approvers:
-  - aledbf  # 2020-04-02
+- aledbf  # 2020-04-02
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 30f781cfc..b313f36b8 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -2,9 +2,31 @@
 
 aliases:
   sig-network-leads:
-    - caseydavenport
-    - dcbw
-    - thockin
+  - caseydavenport
+  - dcbw
+  - thockin
+
   ingress-nginx-admins:
-    - bowei
+  - bowei
+  - rikatz
+
   ingress-nginx-maintainers:
+  - ElvinEfendi
+  - justinsb
+  - rikatz
+  - strongjz
+
+  ingress-nginx-reviewers:
+  - ElvinEfendi
+  - cmluciano
+  - rikatz
+  - strongjz
+  - tao12345666333
+
+  ingress-nginx-helm-maintainers:
+  - ChiefAlexander
+  - cpanato
+
+  ingress-nginx-helm-reviewers:
+  - ChiefAlexander
+  - cpanato
diff --git a/README.md b/README.md
index cdbd96a4a..3675e690c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
 # NGINX Ingress Controller
 
 [![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/ingress-nginx)](https://goreportcard.com/report/github.com/kubernetes/ingress-nginx)
-[![GitHub license](https://img.shields.io/github/license/kubernetes/ingress-nginx.svg)](https://github.com/kubernetes/ingress-nginx/blob/master/LICENSE)
+[![GitHub license](https://img.shields.io/github/license/kubernetes/ingress-nginx.svg)](https://github.com/kubernetes/ingress-nginx/blob/main/LICENSE)
 [![GitHub stars](https://img.shields.io/github/stars/kubernetes/ingress-nginx.svg)](https://github.com/kubernetes/ingress-nginx/stargazers)
-[![GitHub stars](https://img.shields.io/badge/contributions-welcome-orange.svg)](https://github.com/kubernetes/ingress-nginx/blob/master/CONTRIBUTING.md)
+[![GitHub stars](https://img.shields.io/badge/contributions-welcome-orange.svg)](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md)
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fkubernetes%2Fingress-nginx.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fkubernetes%2Fingress-nginx?ref=badge_shield)
 
 ## Overview
@@ -20,15 +20,6 @@ See the [Getting Started](https://kubernetes.github.io/ingress-nginx/deploy/) do
 
 If you encounter issues, review the [troubleshooting docs](docs/troubleshooting.md), [file an issue](https://github.com/kubernetes/ingress-nginx/issues), or talk to us on the [#ingress-nginx channel](https://kubernetes.slack.com/messages/ingress-nginx) on the Kubernetes Slack server.
 
-## Contributing
-
-Thanks for taking the time to join our community and start contributing!
-
-- This project adheres to the [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md). By participating in this project, you agree to abide by its terms.
-- See [CONTRIBUTING.md](CONTRIBUTING.md) for information about setting up your environment, the workflow that we expect, and instructions on the developer certificate of origin that we require.
-- Check out the [open issues](https://github.com/kubernetes/ingress-nginx).
-- Join our Kubernetes Slack channel for developer discussion : [#ingress-nginx-dev](https://kubernetes.slack.com/archives/C021E147ZA4).
-
 ## Changelog
 
 See [the list of releases](https://github.com/kubernetes/ingress-nginx/releases) to find out about feature changes.
@@ -45,19 +36,28 @@ For detailed changes on the `ingress-nginx` helm chart, please check the followi
 | v0.47.0               | 1.21, 1.20, 1.19       | 3.13.5         |  1.20.1       |
 | v0.46.0               | 1.21, 1.20, 1.19       | 3.13.2         |  1.19.6       |
 
-# Get Involved
+## Get Involved
 
-- **Contributing**: Pull requests are welcome!
-  - Read [`CONTRIBUTING.md`](CONTRIBUTING.md) and check out [help-wanted](https://github.com/kubernetes/ingress-nginx/labels/help%20wanted) issues.
-  - Submit github issues for any feature enhancements, bugs or documentation problems.
-- **Support**: Join to [Kubernetes Slack](http://slack.kubernetes.io/) in the [#ingress-nginx-users](https://kubernetes.slack.com/messages/CANQGM8BA/) channel to ask questions to get support from the maintainers and other users.
-  - The [github issues](https://github.com/kubernetes/ingress-nginx/issues) in the repository are **exclusively** for bug reports and feature requests.
-- **Discuss**: Tweet using the `#IngressNginx` hashtag.
+Thanks for taking the time to join our community and start contributing!
 
-## Issues
+- This project adheres to the [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md). By participating in this project, you agree to abide by its terms.
+
+- **Contributing**: Contributions of all kind are welcome!
+  
+  - Read [`CONTRIBUTING.md`](CONTRIBUTING.md) for information about setting up your environment, the workflow that we expect, and instructions on the developer certificate of origin that we require.
+
+  - Check out the [open issues](https://github.com/kubernetes/ingress-nginx), especially [help-wanted](https://github.com/kubernetes/ingress-nginx/labels/help%20wanted) ones.
+
+  - Join our Kubernetes Slack channel for developer discussion : [#ingress-nginx-dev](https://kubernetes.slack.com/archives/C021E147ZA4).
+  
+  - Submit github issues for any feature enhancements, bugs or documentation problems. Please make sure to read the [Issue Reporting Checklist](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines **may be closed immediately**.
 
-Please make sure to read the [Issue Reporting Checklist](https://github.com/kubernetes/ingress-nginx/blob/master/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines **may be closed immediately**.
+- **Support**: Join the the [#ingress-nginx-users](https://kubernetes.slack.com/messages/CANQGM8BA/) channel inside the [Kubernetes Slack](http://slack.kubernetes.io/) to ask questions or get support from the maintainers and other users.
+  
+  - The [github issues](https://github.com/kubernetes/ingress-nginx/issues) in the repository are **exclusively** for bug reports and feature requests.
+
+- **Discuss**: Tweet using the `#IngressNginx` hashtag.
 
 ## License
 
-[Apache License 2.0](https://github.com/kubernetes/ingress-nginx/blob/master/LICENSE)
+[Apache License 2.0](https://github.com/kubernetes/ingress-nginx/blob/main/LICENSE)
diff --git a/build/run-in-docker.sh b/build/run-in-docker.sh
index d6723c7a1..e31b98524 100755
--- a/build/run-in-docker.sh
+++ b/build/run-in-docker.sh
@@ -25,6 +25,9 @@ set -o pipefail
 # temporal directory for the /etc/ingress-controller directory
 INGRESS_VOLUME=$(mktemp -d)
 
+# make sure directory for SSL cert storage exists under ingress volume
+mkdir "${INGRESS_VOLUME}/ssl"
+
 if [[ "$OSTYPE" == darwin* ]]; then
   INGRESS_VOLUME=/private$INGRESS_VOLUME
 fi
diff --git a/charts/ingress-nginx/Chart.yaml b/charts/ingress-nginx/Chart.yaml
index d4eedf977..de46643ba 100644
--- a/charts/ingress-nginx/Chart.yaml
+++ b/charts/ingress-nginx/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
 name: ingress-nginx
 # When the version is modified, make sure the artifacthub.io/changes list is updated
 # Also update CHANGELOG.md
-version: 3.34.0
+version: 3.35.0
 appVersion: 0.48.1
 home: https://github.com/kubernetes/ingress-nginx
 description: Ingress controller for Kubernetes using NGINX as a reverse proxy and load balancer
diff --git a/charts/ingress-nginx/OWNERS b/charts/ingress-nginx/OWNERS
index 0cece5f7c..392bc92f5 100644
--- a/charts/ingress-nginx/OWNERS
+++ b/charts/ingress-nginx/OWNERS
@@ -1,7 +1,10 @@
+# See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
+
 approvers:
-  - ChiefAlexander
-  - cpanato
+- ingress-nginx-helm-maintainers
 
 reviewers:
-  - ChiefAlexander
-  - cpanato
+- ingress-nginx-helm-reviewers
+
+labels:
+- area/helm
\ No newline at end of file
diff --git a/charts/ingress-nginx/README.md b/charts/ingress-nginx/README.md
index 53657e56f..d5a2c3ca8 100644
--- a/charts/ingress-nginx/README.md
+++ b/charts/ingress-nginx/README.md
@@ -159,7 +159,7 @@ controller:
       enabled: true
       annotations:
         # Create internal ELB
-        service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
+        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
         # Any other annotation can be declared here.
 ```
 
diff --git a/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml b/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
index 443e39d8b..d8948d634 100644
--- a/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
+++ b/charts/ingress-nginx/ci/daemonset-internal-lb-values.yaml
@@ -7,4 +7,4 @@ controller:
     internal:
       enabled: true
       annotations:
-        service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
+        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
diff --git a/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml b/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml
index 892f6de3f..c7f22d636 100644
--- a/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml
+++ b/charts/ingress-nginx/ci/deployment-internal-lb-values.yaml
@@ -6,4 +6,4 @@ controller:
     internal:
       enabled: true
       annotations:
-        service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
+        service.beta.kubernetes.io/aws-load-balancer-internal: "true"
diff --git a/charts/ingress-nginx/templates/clusterrole.yaml b/charts/ingress-nginx/templates/clusterrole.yaml
index 8ec5f49fa..b546aaea2 100644
--- a/charts/ingress-nginx/templates/clusterrole.yaml
+++ b/charts/ingress-nginx/templates/clusterrole.yaml
@@ -1,4 +1,10 @@
-{{- if and .Values.rbac.create (not .Values.rbac.scope) -}}
+{{- if .Values.rbac.create }}
+
+{{- if and .Values.rbac.scope (not .Values.controller.scope.enabled) -}}
+  {{ required "Invalid configuration: 'rbac.scope' should be equal to 'controller.scope.enabled' (true/false)." (index (dict) ".") }}
+{{- end }}
+
+{{- if not .Values.rbac.scope -}}
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
@@ -73,3 +79,5 @@ rules:
       - list
       - watch
 {{- end }}
+
+{{- end }}
diff --git a/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml b/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml
index a5a425f74..9556f5863 100644
--- a/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml
+++ b/charts/ingress-nginx/templates/controller-poddisruptionbudget.yaml
@@ -1,5 +1,5 @@
 {{- if or (and .Values.controller.autoscaling.enabled (gt (.Values.controller.autoscaling.minReplicas | int) 1)) (and (not .Values.controller.autoscaling.enabled) (gt (.Values.controller.replicaCount | int) 1)) }}
-apiVersion: policy/v1beta1
+apiVersion: {{ ternary "policy/v1" "policy/v1beta1" (semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version) }}
 kind: PodDisruptionBudget
 metadata:
   labels:
diff --git a/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml b/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
index 153f005e2..9e586aa21 100644
--- a/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
+++ b/charts/ingress-nginx/templates/default-backend-poddisruptionbudget.yaml
@@ -1,5 +1,5 @@
 {{- if or (gt (.Values.defaultBackend.replicaCount | int) 1) (gt (.Values.defaultBackend.autoscaling.minReplicas | int) 1) }}
-apiVersion: policy/v1beta1
+apiVersion: {{ ternary "policy/v1" "policy/v1beta1" (semverCompare ">=1.21.0-0" .Capabilities.KubeVersion.Version) }}
 kind: PodDisruptionBudget
 metadata:
   labels:
diff --git a/deploy/grafana/kustomization.yaml b/deploy/grafana/kustomization.yaml
index 2069c1a7a..e2744818e 100644
--- a/deploy/grafana/kustomization.yaml
+++ b/deploy/grafana/kustomization.yaml
@@ -9,4 +9,3 @@ resources:
 - service.yaml
 images:
 - name: grafana/grafana
-  newTag: 6.1.6
diff --git a/deploy/prometheus/kustomization.yaml b/deploy/prometheus/kustomization.yaml
index 2d6e0a7e2..a55bd8d2c 100644
--- a/deploy/prometheus/kustomization.yaml
+++ b/deploy/prometheus/kustomization.yaml
@@ -12,7 +12,6 @@ resources:
 - service.yaml
 images:
 - name: prom/prometheus
-  newTag: v2.3.2
 configMapGenerator:
 - name: prometheus-configuration
   files:
diff --git a/deploy/static/provider/aws/deploy-tls-termination.yaml b/deploy/static/provider/aws/deploy-tls-termination.yaml
index 36ad19519..8f890975e 100644
--- a/deploy/static/provider/aws/deploy-tls-termination.yaml
+++ b/deploy/static/provider/aws/deploy-tls-termination.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -38,7 +38,7 @@ metadata:
   namespace: ingress-nginx
 data:
   http-snippet: |
-    server {
+    server{
       listen 2443;
       return 308 https://$host$request_uri;
     }
@@ -50,7 +50,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -119,7 +119,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -139,7 +139,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -225,7 +225,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -247,7 +247,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -278,7 +278,7 @@ metadata:
     service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https
     service.beta.kubernetes.io/aws-load-balancer-type: elb
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -308,7 +308,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -426,7 +426,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -467,7 +467,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -483,7 +483,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -507,7 +507,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -532,7 +532,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -557,7 +557,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -582,7 +582,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -593,7 +593,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -630,7 +630,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -641,7 +641,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/aws/deploy.yaml b/deploy/static/provider/aws/deploy.yaml
index 6233febd9..ca406635c 100644
--- a/deploy/static/provider/aws/deploy.yaml
+++ b/deploy/static/provider/aws/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -43,7 +43,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -112,7 +112,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -132,7 +132,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -218,7 +218,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -240,7 +240,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -268,7 +268,7 @@ metadata:
     service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true'
     service.beta.kubernetes.io/aws-load-balancer-type: nlb
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -298,7 +298,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -413,7 +413,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -454,7 +454,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -470,7 +470,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -494,7 +494,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -519,7 +519,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -544,7 +544,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -569,7 +569,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -580,7 +580,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -617,7 +617,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -628,7 +628,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/baremetal/deploy.yaml b/deploy/static/provider/baremetal/deploy.yaml
index 01dbd0376..2ee36c827 100644
--- a/deploy/static/provider/baremetal/deploy.yaml
+++ b/deploy/static/provider/baremetal/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -43,7 +43,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -112,7 +112,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -132,7 +132,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -218,7 +218,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -240,7 +240,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -265,7 +265,7 @@ kind: Service
 metadata:
   annotations:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -294,7 +294,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -408,7 +408,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -449,7 +449,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -465,7 +465,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -489,7 +489,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -514,7 +514,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -539,7 +539,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -564,7 +564,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -575,7 +575,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -612,7 +612,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -623,7 +623,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/cloud/deploy.yaml b/deploy/static/provider/cloud/deploy.yaml
index ef156a1ad..0bd420d92 100644
--- a/deploy/static/provider/cloud/deploy.yaml
+++ b/deploy/static/provider/cloud/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -43,7 +43,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -112,7 +112,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -132,7 +132,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -218,7 +218,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -240,7 +240,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -265,7 +265,7 @@ kind: Service
 metadata:
   annotations:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -295,7 +295,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -410,7 +410,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -451,7 +451,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -467,7 +467,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -491,7 +491,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -516,7 +516,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -541,7 +541,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -566,7 +566,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -577,7 +577,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -614,7 +614,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -625,7 +625,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/do/deploy.yaml b/deploy/static/provider/do/deploy.yaml
index e11912f95..7f044bc0f 100644
--- a/deploy/static/provider/do/deploy.yaml
+++ b/deploy/static/provider/do/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -44,7 +44,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -113,7 +113,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -133,7 +133,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -219,7 +219,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -241,7 +241,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -267,7 +267,7 @@ metadata:
   annotations:
     service.beta.kubernetes.io/do-loadbalancer-enable-proxy-protocol: 'true'
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -297,7 +297,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -412,7 +412,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -453,7 +453,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -469,7 +469,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -493,7 +493,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -518,7 +518,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -543,7 +543,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -568,7 +568,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -579,7 +579,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -616,7 +616,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -627,7 +627,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/exoscale/deploy.yaml b/deploy/static/provider/exoscale/deploy.yaml
index 4ca8022e0..923051ca6 100644
--- a/deploy/static/provider/exoscale/deploy.yaml
+++ b/deploy/static/provider/exoscale/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -43,7 +43,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -112,7 +112,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -132,7 +132,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -218,7 +218,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -240,7 +240,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -273,7 +273,7 @@ metadata:
     service.beta.kubernetes.io/exoscale-loadbalancer-service-healthcheck-timeout: 3s
     service.beta.kubernetes.io/exoscale-loadbalancer-service-strategy: source-hash
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -303,7 +303,7 @@ apiVersion: apps/v1
 kind: DaemonSet
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -341,7 +341,7 @@ spec:
             - --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
             - --election-id=ingress-controller-leader
             - --ingress-class=nginx
-            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
+            - --configmap=ingress-nginx/ingress-nginx-controller
             - --validating-webhook=:8443
             - --validating-webhook-certificate=/usr/local/certificates/cert
             - --validating-webhook-key=/usr/local/certificates/key
@@ -418,7 +418,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -459,7 +459,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -475,7 +475,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -499,7 +499,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -524,7 +524,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -549,7 +549,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -574,7 +574,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -585,7 +585,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -622,7 +622,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -633,7 +633,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/kind/deploy.yaml b/deploy/static/provider/kind/deploy.yaml
index e949e8597..4f2c6ae99 100644
--- a/deploy/static/provider/kind/deploy.yaml
+++ b/deploy/static/provider/kind/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -43,7 +43,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -112,7 +112,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -132,7 +132,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -218,7 +218,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -240,7 +240,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -265,7 +265,7 @@ kind: Service
 metadata:
   annotations:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -294,7 +294,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -420,7 +420,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -461,7 +461,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -477,7 +477,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -501,7 +501,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -526,7 +526,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -551,7 +551,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -576,7 +576,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -587,7 +587,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -624,7 +624,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -635,7 +635,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/deploy/static/provider/scw/deploy.yaml b/deploy/static/provider/scw/deploy.yaml
index 464742262..1d148a7b9 100644
--- a/deploy/static/provider/scw/deploy.yaml
+++ b/deploy/static/provider/scw/deploy.yaml
@@ -13,7 +13,7 @@ apiVersion: v1
 kind: ServiceAccount
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -28,7 +28,7 @@ apiVersion: v1
 kind: ConfigMap
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -44,7 +44,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -113,7 +113,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -133,7 +133,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -219,7 +219,7 @@ apiVersion: rbac.authorization.k8s.io/v1
 kind: RoleBinding
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -241,7 +241,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -267,7 +267,7 @@ metadata:
   annotations:
     service.beta.kubernetes.io/scw-loadbalancer-proxy-protocol-v2: 'true'
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -297,7 +297,7 @@ apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -412,7 +412,7 @@ apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -453,7 +453,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -469,7 +469,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -493,7 +493,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -518,7 +518,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -543,7 +543,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade,post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -568,7 +568,7 @@ metadata:
     helm.sh/hook: pre-install,pre-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -579,7 +579,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-create
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
@@ -616,7 +616,7 @@ metadata:
     helm.sh/hook: post-install,post-upgrade
     helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
   labels:
-    helm.sh/chart: ingress-nginx-3.34.0
+    helm.sh/chart: ingress-nginx-3.35.0
     app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/instance: ingress-nginx
     app.kubernetes.io/version: 0.48.1
@@ -627,7 +627,7 @@ spec:
     metadata:
       name: ingress-nginx-admission-patch
       labels:
-        helm.sh/chart: ingress-nginx-3.34.0
+        helm.sh/chart: ingress-nginx-3.35.0
         app.kubernetes.io/name: ingress-nginx
         app.kubernetes.io/instance: ingress-nginx
         app.kubernetes.io/version: 0.48.1
diff --git a/docs/OWNERS b/docs/OWNERS
new file mode 100644
index 000000000..1d3805a73
--- /dev/null
+++ b/docs/OWNERS
@@ -0,0 +1,4 @@
+# See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
+
+labels:
+- area/docs
\ No newline at end of file
diff --git a/docs/examples/affinity/cookie/README.md b/docs/examples/affinity/cookie/README.md
index 670d345e3..4f93fc0f8 100644
--- a/docs/examples/affinity/cookie/README.md
+++ b/docs/examples/affinity/cookie/README.md
@@ -10,6 +10,7 @@ Session affinity can be configured using the following annotations:
 | --- | --- | --- |
 |nginx.ingress.kubernetes.io/affinity|Type of the affinity, set this to `cookie` to enable session affinity|string (NGINX only supports `cookie`)|
 |nginx.ingress.kubernetes.io/affinity-mode|The affinity mode defines how sticky a session is. Use `balanced` to redistribute some sessions when scaling pods or `persistent` for maximum stickiness.|`balanced` (default) or `persistent`|
+|nginx.ingress.kubernetes.io/affinity-canary-behavior|Defines session affinity behavior of canaries. By default the behavior is `sticky`, and canaries respect session affinity configuration. Set this to `legacy` to restore original canary behavior, when session affinity parameters were not respected.|`sticky` (default) or `legacy`|
 |nginx.ingress.kubernetes.io/session-cookie-name|Name of the cookie that will be created|string (defaults to `INGRESSCOOKIE`)|
 |nginx.ingress.kubernetes.io/session-cookie-path|Path that will be set on the cookie (required if your [Ingress paths][ingress-paths] use regular expressions)|string (defaults to the currently [matched path][ingress-paths])|
 |nginx.ingress.kubernetes.io/session-cookie-samesite|SameSite attribute to apply to the cookie|Browser accepted values are `None`, `Lax`, and `Strict`|
diff --git a/docs/examples/grpc/README.md b/docs/examples/grpc/README.md
index 60e696bfd..8835b5110 100644
--- a/docs/examples/grpc/README.md
+++ b/docs/examples/grpc/README.md
@@ -1,93 +1,152 @@
 # gRPC
 
-This example demonstrates how to route traffic to a gRPC service through the
-nginx controller.
+This example demonstrates how to route traffic to a gRPC service through the nginx controller.
 
 ## Prerequisites
 
 1. You have a kubernetes cluster running.
-2. You have a domain name such as `example.com` that is configured to route
-   traffic to the ingress controller.  Replace references to
-   `fortune-teller.stack.build` (the domain name used in this example) to your
-   own domain name (you're also responsible for provisioning an SSL certificate
-   for the ingress).
-3. You have the nginx-ingress controller installed in typical fashion (must be
-   at least
-   [quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0](https://quay.io/kubernetes-ingress-controller/nginx-ingress-controller)
-   for grpc support.
-4. You have a backend application running a gRPC server and listening for TCP
-   traffic.  If you prefer, you can use the
-   [fortune-teller](https://github.com/kubernetes/ingress-nginx/tree/master/images/grpc-fortune-teller)
-   application provided here as an example.
-
-### Step 1: kubernetes `Deployment`
-
-```sh
-$ kubectl create -f app.yaml
-```
-
-This is a standard kubernetes deployment object.  It is running a grpc service
-listening on port `50051`.
-
-The sample application
-[fortune-teller-app](https://github.com/kubernetes/ingress-nginx/tree/master/images/grpc-fortune-teller)
-is a grpc server implemented in go. Here's the stripped-down implementation:
-
-```go
-func main() {
-	grpcServer := grpc.NewServer()
-	fortune.RegisterFortuneTellerServer(grpcServer, &FortuneTeller{})
-	lis, _ := net.Listen("tcp", ":50051")
-	grpcServer.Serve(lis)
-}
-```
-
-The takeaway is that we are not doing any TLS configuration on the server (as we
-are terminating TLS at the ingress level, grpc traffic will travel unencrypted
-inside the cluster and arrive "insecure").
-
-For your own application you may or may not want to do this.  If you prefer to
-forward encrypted traffic to your POD and terminate TLS at the gRPC server
-itself, add the ingress annotation `nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"`.
-
-### Step 2: the kubernetes `Service`
-
-```sh
-$ kubectl create -f svc.yaml
-```
-
-Here we have a typical service. Nothing special, just routing traffic to the
-backend application on port `50051`.
-
-### Step 3: the kubernetes `Ingress`
-
-```sh
-$ kubectl create -f ingress.yaml
-```
-
-A few things to note:
-
-1. We've tagged the ingress with the annotation
-   `nginx.ingress.kubernetes.io/backend-protocol: "GRPC"`.  This is the magic
-   ingredient that sets up the appropriate nginx configuration to route http/2
-   traffic to our service.
-1. We're terminating TLS at the ingress and have configured an SSL certificate
-   `fortune-teller.stack.build`.  The ingress matches traffic arriving as
-   `https://fortune-teller.stack.build:443` and routes unencrypted messages to
-   our kubernetes service.
+2. You have a domain name such as `example.com` that is configured to route traffic to the ingress controller.
+3. You have the nginx-ingress controller installed as per docs, with gRPC support.
+4. You have a backend application running a gRPC server and listening for TCP traffic.  If you want, you can use <https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go> as an example.
+5. You're also responsible for provisioning an SSL certificate for the ingress. So you need to have a valid SSL certificate, deployed as a Kubernetes secret of type tls, in the same namespace as the gRPC application.
+
+### Step 1: Create a Kubernetes `Deployment` for gRPC app
+
+- Make sure your gRPC application pod is running and listening for connections. For example you can try a kubectl command like this below:
+  ```
+  $ kubectl get po -A -o wide | grep go-grpc-greeter-server
+  ```
+- If you have a gRPC app deployed in your cluster, then skip further notes in this Step 1, and continue from Step 2 below. 
+
+- As an example gRPC application, we can use this app <https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go> .
+
+- To create a container image for this app, you can use [this Dockerfile](../../../images/go-grpc-greeter-server/rootfs/Dockerfile). 
+
+- If you use the Dockerfile mentioned above, to create a image, then given below is an example of a Kubernetes manifest, to create a deployment resource, that uses that image. If needed, then edit this manifest to suit your needs. Assuming the name of this yaml file is  `deployment.go-grpc-greeter-server.yaml` ;
+
+  ```
+  cat <<EOF | kubectl apply -f -
+  apiVersion: apps/v1
+  kind: Deployment
+  metadata:
+    labels:
+      app: go-grpc-greeter-server
+    name: go-grpc-greeter-server
+  spec:
+    replicas: 1
+    selector:
+      matchLabels:
+        app: go-grpc-greeter-server
+    template:
+      metadata:
+        labels:
+          app: go-grpc-greeter-server
+      spec:
+        containers:
+        - image: <reponame>/go-grpc-greeter-server   # Edit this for your reponame
+          resources:
+            limits:
+              cpu: 100m
+              memory: 100Mi
+            requests:
+              cpu: 50m
+              memory: 50Mi
+          name: go-grpc-greeter-server
+          ports:
+          - containerPort: 50051
+  EOF
+  ```
+
+### Step 2: Create the Kubernetes `Service` for the gRPC app
+
+- You can use the following example manifest to create a service of type ClusterIP. Edit the name/namespace/label/port to match your deployment/pod ;
+  ```
+  cat <<EOF | kubectl apply -f -
+  apiVersion: v1
+  kind: Service
+  metadata:
+    labels:
+      app: go-grpc-greeter-server
+    name: go-grpc-greeter-server
+  spec:
+    ports:
+    - port: 80
+      protocol: TCP
+      targetPort: 50051
+    selector:
+      app: go-grpc-greeter-server
+    type: ClusterIP
+  EOF
+  ```
+- You can save the above example manifest to a file with name `service.go-grpc-greeter-server.yaml` and edit it to match your deployment/pod, if required. You can create the service resource with a kubectl command like this ;
+
+  ```
+  $ kubectl create -f service.go-grpc-greeter-server.yaml
+  ```
+
+### Step 3: Create the Kubernetes `Ingress` resource for the gRPC app
+
+- Use the following example manifest of a ingress resource to create a ingress for your grpc app. If required, edit it to match your app's details like name, namespace, service, secret etc. Make sure you have the required SSL-Certificate, existing in your Kubernetes cluster, in the same namespace where the gRPC app is. The certificate must be available as a kubernetes secret resource, of type "kubernete.io/tls" https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets. This is because we are terminating TLS on the ingress;
+
+  ```
+  cat <<EOF | kubectl apply -f -
+  apiVersion: networking.k8s.io/v1
+  kind: Ingress
+  metadata:
+    annotations:
+      kubernetes.io/ingress.class: "nginx"
+      nginx.ingress.kubernetes.io/ssl-redirect: "true"
+      nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
+    name: fortune-ingress
+    namespace: default
+  spec:
+    rules:
+    - host: grpctest.dev.mydomain.com
+      http:
+        paths:
+        - path: /
+          pathType: Prefix
+          backend:
+            service:
+              name: go-grpc-greeter-server
+              port:
+                number: 80
+    tls:
+    # This secret must exist beforehand
+    # The cert must also contain the subj-name grpctest.dev.mydomain.com
+    # https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/PREREQUISITES.md#tls-certificates
+    - secretName: wildcard.dev.mydomain.com
+      hosts:
+        - grpctest.dev.mydomain.com
+  EOF
+  ```
+
+- If you save the above example manifest as a file named `ingress.go-grpc-greeter-server.yaml` and edit it to match your deployment and service, you can create the ingress like this ;
+
+  ```
+  $ kubectl create -f ingress.go-grpc-greeter-server.yaml
+  ```
+
+- The takeaway is that we are not doing any TLS configuration on the server (as we are terminating TLS at the ingress level, gRPC traffic will travel unencrypted inside the cluster and arrive "insecure").
+
+- For your own application you may or may not want to do this.  If you prefer to forward encrypted traffic to your POD and terminate TLS at the gRPC server itself, add the ingress annotation `nginx.ingress.kubernetes.io/backend-protocol: "GRPCS"`.
+
+- A few more things to note:
+
+  - We've tagged the ingress with the annotation `nginx.ingress.kubernetes.io/backend-protocol: "GRPC"`.  This is the magic ingredient that sets up the appropriate nginx configuration to route http/2 traffic to our service.
+
+  - We're terminating TLS at the ingress and have configured an SSL certificate `wildcard.dev.mydomain.com`.  The ingress matches traffic arriving as `https://grpctest.dev.mydomain.com:443` and routes unencrypted messages to the backend Kubernetes service.
 
 ### Step 4: test the connection
 
-Once we've applied our configuration to kubernetes, it's time to test that we
-can actually talk to the backend.  To do this, we'll use the
-[grpcurl](https://github.com/fullstorydev/grpcurl) utility:
+- Once we've applied our configuration to Kubernetes, it's time to test that we can actually talk to the backend.  To do this, we'll use the [grpcurl](https://github.com/fullstorydev/grpcurl) utility:
 
-```sh
-$ grpcurl fortune-teller.stack.build:443 build.stack.fortune.FortuneTeller/Predict
-{
-  "message": "Let us endeavor so to live that when we come to die even the undertaker will be sorry.\n\t\t-- Mark Twain, \"Pudd'nhead Wilson's Calendar\""
-}
-```
+  ```
+  $ grpcurl grpctest.dev.mydomain.com:443 helloworld.Greeter/SayHello
+  {
+    "message": "
+  }
+  ```
 
 ### Debugging Hints
 
diff --git a/docs/examples/grpc/app.yaml b/docs/examples/grpc/app.yaml
deleted file mode 100644
index acc4060d0..000000000
--- a/docs/examples/grpc/app.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: fortune-teller-app
-  labels:
-    k8s-app: fortune-teller-app
-  namespace: default
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      k8s-app: fortune-teller-app
-  template:
-    metadata:
-      labels:
-        k8s-app: fortune-teller-app
-    spec:
-      containers:
-      - name: fortune-teller-app
-        image: quay.io/kubernetes-ingress-controller/grpc-fortune-teller:0.1
-        ports:
-        - containerPort: 50051
-          name: grpc
diff --git a/docs/examples/grpc/cert.yaml b/docs/examples/grpc/cert.yaml
deleted file mode 100644
index 562c30313..000000000
--- a/docs/examples/grpc/cert.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-apiVersion: "stable.k8s.psg.io/v1"
-kind: "Certificate"
-metadata:
-  name: fortune-teller.stack.build
-  namespace: default
-spec:
-  domain: "fortune-teller.stack.build"
diff --git a/docs/examples/grpc/ingress.yaml b/docs/examples/grpc/ingress.yaml
deleted file mode 100644
index 1d76476d1..000000000
--- a/docs/examples/grpc/ingress.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: networking.k8s.io/v1beta1
-kind: Ingress
-metadata:
-  annotations:
-    kubernetes.io/ingress.class: "nginx"
-    nginx.ingress.kubernetes.io/ssl-redirect: "true"
-    nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
-  name: fortune-ingress
-  namespace: default
-spec:
-  rules:
-  - host: fortune-teller.stack.build
-    http:
-      paths:
-      - backend:
-          serviceName: fortune-teller-service
-          servicePort: grpc
-  tls:
-  # This secret must exist beforehand
-  # The cert must also contain the subj-name fortune-teller.stack.build
-  # https://github.com/kubernetes/ingress-nginx/blob/master/docs/examples/PREREQUISITES.md#tls-certificates
-  - secretName: fortune-teller.stack.build
-    hosts:
-      - fortune-teller.stack.build
diff --git a/docs/examples/grpc/svc.yaml b/docs/examples/grpc/svc.yaml
deleted file mode 100644
index 7bfa789fd..000000000
--- a/docs/examples/grpc/svc.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
-  name: fortune-teller-service
-  namespace: default
-spec:
-  selector:
-    k8s-app: fortune-teller-app
-  ports:
-  - port: 50051
-    targetPort: 50051
-    name: grpc
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index 1707aa0e2..a8cf212b1 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -173,68 +173,47 @@ Verify with the following commands:
 
 ```console
 # start a container that contains curl
-$ kubectl run test --image=tutum/curl -- sleep 10000
-
-# check that container is running
-$ kubectl get pods
-NAME                   READY     STATUS    RESTARTS   AGE
-test-701078429-s5kca   1/1       Running   0          16s
+$ kubectl run -it --rm test --image=curlimages/curl --restart=Never -- /bin/sh
 
 # check if secret exists
-$ kubectl exec test-701078429-s5kca -- ls /var/run/secrets/kubernetes.io/serviceaccount/
-ca.crt
-namespace
-token
-
-# get service IP of master
-$ kubectl get services
-NAME         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
-kubernetes   10.0.0.1     <none>        443/TCP   1d
+/ $ ls /var/run/secrets/kubernetes.io/serviceaccount/
+ca.crt     namespace  token
+/ $
 
 # check base connectivity from cluster inside
-$ kubectl exec test-701078429-s5kca -- curl -k https://10.0.0.1
-Unauthorized
+/ $ curl -k https://kubernetes.default.svc.cluster.local
+{
+  "kind": "Status",
+  "apiVersion": "v1",
+  "metadata": {
+
+  },
+  "status": "Failure",
+  "message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",
+  "reason": "Forbidden",
+  "details": {
+
+  },
+  "code": 403
+}/ $
 
 # connect using tokens
-$ TOKEN_VALUE=$(kubectl exec test-701078429-s5kca -- cat /var/run/secrets/kubernetes.io/serviceaccount/token)
-$ echo $TOKEN_VALUE
-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3Mi....9A
-$ kubectl exec test-701078429-s5kca -- curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H  "Authorization: Bearer $TOKEN_VALUE" https://10.0.0.1
+}/ $ curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt -H  "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kubernetes.default.svc.cluster.local
+&& echo
 {
   "paths": [
     "/api",
     "/api/v1",
     "/apis",
-    "/apis/apps",
-    "/apis/apps/v1alpha1",
-    "/apis/authentication.k8s.io",
-    "/apis/authentication.k8s.io/v1beta1",
-    "/apis/authorization.k8s.io",
-    "/apis/authorization.k8s.io/v1beta1",
-    "/apis/autoscaling",
-    "/apis/autoscaling/v1",
-    "/apis/batch",
-    "/apis/batch/v1",
-    "/apis/batch/v2alpha1",
-    "/apis/certificates.k8s.io",
-    "/apis/certificates.k8s.io/v1alpha1",
-    "/apis/networking",
-    "/apis/networking/v1beta1",
-    "/apis/policy",
-    "/apis/policy/v1alpha1",
-    "/apis/rbac.authorization.k8s.io",
-    "/apis/rbac.authorization.k8s.io/v1alpha1",
-    "/apis/storage.k8s.io",
-    "/apis/storage.k8s.io/v1beta1",
-    "/healthz",
-    "/healthz/ping",
-    "/logs",
-    "/metrics",
-    "/swaggerapi/",
-    "/ui/",
+    "/apis/",
+    ... TRUNCATED
+    "/readyz/shutdown",
     "/version"
   ]
 }
+/ $
+
+# when you type `exit` or `^D` the test pod will be deleted.
 ```
 
 If it is not working, there are two possible reasons:
diff --git a/docs/user-guide/monitoring.md b/docs/user-guide/monitoring.md
index 4f6376a3b..6471f997e 100644
--- a/docs/user-guide/monitoring.md
+++ b/docs/user-guide/monitoring.md
@@ -7,79 +7,137 @@ This tutorial will show you how to install [Prometheus](https://prometheus.io/)
 
 ## Before You Begin
 
-The NGINX Ingress controller should already be deployed according to the deployment instructions [here](../deploy/index.md).
+- The NGINX Ingress controller should already be deployed according to the deployment instructions [here](../deploy/index.md).
+
+- The controller should be configured for exporting metrics. This requires 3 configurations to the controller. These configurations are :
+  1. controller.metrics.enabled=true
+  2. controller.podAnnotations."prometheus.io/scrape"="true"
+  3. controller.podAnnotations."prometheus.io/port"="10254"
+
+  - The easiest way to configure the controller for metrics is via helm upgrade. Assuming you have installed the ingress-nginx controller as a helm release named ingresscontroller0, then you can simply type the command show below :
+  ```
+  helm upgrade ingresscontroller0 ingress-nginx/ingress-nginx \
+  --namespace ingress-nginx \
+  --set controller.metrics.enabled=true \
+  --set-string controller.podAnnotations."prometheus\.io/scrape"="true" \
+  --set-string controller.podAnnotations."prometheus\.io/port"="10254"
+  ```
+  - You can validate that the controller is configured for metrics by looking at the values of the installed release, like this ;
+  ```
+  helm get values ingress-controller --namespace ingress-nginx
+  ```
+  - You should be able to see the values shown below ;
+  ```
+  ..
+  controller:
+    metrics:
+      enabled: true
+      service:
+        annotations:
+          prometheus.io/port: "10254"
+          prometheus.io/scrape: "true" 
+  ..
+  ```
 
-Note that the kustomize bases used in this tutorial are stored in the [deploy](https://github.com/kubernetes/ingress-nginx/tree/master/deploy) folder of the GitHub repository [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx).
 
 ## Deploy and configure Prometheus Server
 
-The Prometheus server must be configured so that it can discover endpoints of services. If a Prometheus server is already running in the cluster and if it is configured in a way that it can find the ingress controller pods, no extra configuration is needed.
+Note that the kustomize bases used in this tutorial are stored in the [deploy](https://github.com/kubernetes/ingress-nginx/tree/master/deploy) folder of the GitHub repository [kubernetes/ingress-nginx](https://github.com/kubernetes/ingress-nginx).
+
+- The Prometheus server must be configured so that it can discover endpoints of services. If a Prometheus server is already running in the cluster and if it is configured in a way that it can find the ingress controller pods, no extra configuration is needed.
 
-If there is no existing Prometheus server running, the rest of this tutorial will guide you through the steps needed to deploy a properly configured Prometheus server.
+- If there is no existing Prometheus server running, the rest of this tutorial will guide you through the steps needed to deploy a properly configured Prometheus server.
 
-Running the following command deploys prometheus in Kubernetes:
+- Running the following command deploys prometheus in Kubernetes:
 
-```console
-kubectl apply --kustomize github.com/kubernetes/ingress-nginx/deploy/prometheus/
-```
+  ```
+  kubectl apply --kustomize github.com/kubernetes/ingress-nginx/deploy/prometheus/
+  ```
 
 ### Prometheus Dashboard
 
-Open Prometheus dashboard in a web browser:
+- Open Prometheus dashboard in a web browser:
 
-```console
-kubectl get svc -n ingress-nginx
-NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE
-default-http-backend   ClusterIP   10.103.59.201   <none>        80/TCP                                       3d
-ingress-nginx          NodePort    10.97.44.72     <none>        80:30100/TCP,443:30154/TCP,10254:32049/TCP   5h
-prometheus-server      NodePort    10.98.233.86    <none>        9090:32630/TCP                               1m
-```
+  ```console
+  kubectl get svc -n ingress-nginx
+  NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE
+  default-http-backend   ClusterIP   10.103.59.201   <none>        80/TCP                                       3d
+  ingress-nginx          NodePort    10.97.44.72     <none>        80:30100/TCP,443:30154/TCP,10254:32049/TCP   5h
+  prometheus-server      NodePort    10.98.233.86    <none>        9090:32630/TCP                               1m
+  ```
 
-Obtain the IP address of the nodes in the running cluster:
+  - Obtain the IP address of the nodes in the running cluster:
 
-```console
-kubectl get nodes -o wide
-```
+  ```console
+  kubectl get nodes -o wide
+  ```
 
-In some cases where the node only have internal IP addresses we need to execute:
+  - In some cases where the node only have internal IP addresses we need to execute:
 
-```console
-kubectl get nodes --selector=kubernetes.io/role!=master -o jsonpath={.items[*].status.addresses[?\(@.type==\"InternalIP\"\)].address}
-10.192.0.2 10.192.0.3 10.192.0.4
-```
+  ```
+  kubectl get nodes --selector=kubernetes.io/role!=master -o jsonpath={.items[*].status.addresses[?\(@.type==\"InternalIP\"\)].address}
+  10.192.0.2 10.192.0.3 10.192.0.4
+  ```
 
-Open your browser and visit the following URL: _http://{node IP address}:{prometheus-svc-nodeport}_ to load the Prometheus Dashboard.
+  - Open your browser and visit the following URL: _http://{node IP address}:{prometheus-svc-nodeport}_ to load the Prometheus Dashboard.
 
-According to the above example, this URL will be http://10.192.0.3:32630
+  - According to the above example, this URL will be http://10.192.0.3:32630
 
-![Dashboard](../images/prometheus-dashboard.png)
+  ![Prometheus Dashboard](../images/prometheus-dashboard.png)
 
 ### Grafana
 
-```console
-kubectl apply --kustomize github.com/kubernetes/ingress-nginx/deploy/grafana/
-```
+  - Install grafana using the below command
+  ```
+  kubectl apply --kustomize github.com/kubernetes/ingress-nginx/deploy/grafana/
+  ```
+  - Look at the services
+  ```
+  kubectl get svc -n ingress-nginx
+  NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE
+  default-http-backend   ClusterIP   10.103.59.201   <none>        80/TCP                                       3d
+  ingress-nginx          NodePort    10.97.44.72     <none>        80:30100/TCP,443:30154/TCP,10254:32049/TCP   5h
+  prometheus-server      NodePort    10.98.233.86    <none>        9090:32630/TCP                               10m
+  grafana                NodePort    10.98.233.87    <none>        3000:31086/TCP                               10m
+  ```
+
+  - Open your browser and visit the following URL: _http://{node IP address}:{grafana-svc-nodeport}_ to load the Grafana Dashboard.
+According to the above example, this URL will be http://10.192.0.3:31086
 
-```console
-kubectl get svc -n ingress-nginx
-NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE
-default-http-backend   ClusterIP   10.103.59.201   <none>        80/TCP                                       3d
-ingress-nginx          NodePort    10.97.44.72     <none>        80:30100/TCP,443:30154/TCP,10254:32049/TCP   5h
-prometheus-server      NodePort    10.98.233.86    <none>        9090:32630/TCP                               10m
-grafana                NodePort    10.98.233.87    <none>        3000:31086/TCP                               10m
-```
+  The username and password is `admin`
 
-Open your browser and visit the following URL: _http://{node IP address}:{grafana-svc-nodeport}_ to load the Grafana Dashboard.
-According to the above example, this URL will be http://10.192.0.3:31086
+  - After the login you can import the Grafana dashboard from [official dashboards](https://github.com/kubernetes/ingress-nginx/tree/master/deploy/grafana/dashboards), by following steps given below :
+
+    - Navigate to lefthand panel of grafana
+    - Hover on the gearwheel icon for Configuration and click "Data Sources"
+    - Click "Add data source"
+    - Select "Prometheus"
+    - Enter the details (note: I used http://CLUSTER_IP_PROMETHEUS_SVC:9090)
+    - Left menu (hover over +) -> Dashboard
+    - Click "Import"
+    - Enter the copy pasted json from https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/grafana/dashboards/nginx.json
+    - Click Import JSON
+    - Select the Prometheus data source
+    - Click "Import"
 
-The username and password is `admin`
 
-After the login you can import the Grafana dashboard from [official dashboards](https://github.com/kubernetes/ingress-nginx/tree/master/deploy/grafana/dashboards)
 
-![Dashboard](../images/grafana.png)
+  ![Grafana Dashboard](../images/grafana.png)
 
 ## Caveats
 
 ### Wildcard ingresses
 
-By default request metrics are labeled with the hostname. When you have a wildcard domain ingress, then there will be no metrics for that ingress (to prevent the metrics from exploding in cardinality). To get metrics in this case you need to run the ingress controller with `--metrics-per-host=false` (you will lose labeling by hostname, but still have labeling by ingress).
+  - By default request metrics are labeled with the hostname. When you have a wildcard domain ingress, then there will be no metrics for that ingress (to prevent the metrics from exploding in cardinality). To get metrics in this case you need to run the ingress controller with `--metrics-per-host=false` (you will lose labeling by hostname, but still have labeling by ingress).
+
+## Grafana dashboard using ingress resource
+  - If you want to expose the dashboard for grafana using a ingress resource, then you can : 
+    - change the service type of the prometheus-server service and the grafana service to "ClusterIP" like this :
+    ```
+    kubectl -n ingress-nginx edit svc grafana
+    ```
+    - This will open the currently deployed service grafana in the default editor configured in your shell (vi/nvim/nano/other)
+    - scroll down to line 34 that looks like "type: NodePort"
+    - change it to look like "type: ClusterIP". Save and exit.
+    - create a ingress reource with backend as "grafana" and port as "3000"
+  - Similarly, you can edit the service "prometheus-server" and add a ingress resource.
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
index add5c5595..ef9a4e5ef 100755
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -18,6 +18,7 @@ You can add these Kubernetes annotations to specific Ingress objects to customiz
 |[nginx.ingress.kubernetes.io/app-root](#rewrite)|string|
 |[nginx.ingress.kubernetes.io/affinity](#session-affinity)|cookie|
 |[nginx.ingress.kubernetes.io/affinity-mode](#session-affinity)|"balanced" or "persistent"|
+|[nginx.ingress.kubernetes.io/affinity-canary-behavior](#session-affinity)|"sticky" or "legacy"|
 |[nginx.ingress.kubernetes.io/auth-realm](#authentication)|string|
 |[nginx.ingress.kubernetes.io/auth-secret](#authentication)|string|
 |[nginx.ingress.kubernetes.io/auth-secret-type](#authentication)|string|
@@ -140,7 +141,7 @@ In some cases, you may want to "canary" a new set of changes by sending a small
 Canary rules are evaluated in order of precedence. Precedence is as follows:
 `canary-by-header -> canary-by-cookie -> canary-weight`
 
-**Note** that when you mark an ingress as canary, then all the other non-canary annotations will be ignored (inherited from the corresponding main ingress) except `nginx.ingress.kubernetes.io/load-balance` and `nginx.ingress.kubernetes.io/upstream-hash-by`.
+**Note** that when you mark an ingress as canary, then all the other non-canary annotations will be ignored (inherited from the corresponding main ingress) except `nginx.ingress.kubernetes.io/load-balance`, `nginx.ingress.kubernetes.io/upstream-hash-by`, and [annotations related to session affinity](#session-affinity). If you want to restore the original behavior of canaries when session affinity was ignored, set `nginx.ingress.kubernetes.io/affinity-canary-behavior` annotation with value `legacy` on the non-canary ingress definition.
 
 **Known Limitations**
 
@@ -163,6 +164,8 @@ The only affinity type available for NGINX is `cookie`.
 
 The annotation `nginx.ingress.kubernetes.io/affinity-mode` defines the stickiness of a session. Setting this to `balanced` (default) will redistribute some sessions if a deployment gets scaled up, therefore rebalancing the load on the servers. Setting this to `persistent` will not rebalance sessions to new servers, therefore providing maximum stickiness.
 
+The annotation `nginx.ingress.kubernetes.io/affinity-canary-behavior` defines the behavior of canaries when session affinity is enabled. Setting this to `sticky` (default) will ensure that users that were served by canaries, will continue to be served by canaries. Setting this to `legacy` will restore original canary behavior, when session affinity was ignored.
+
 !!! attention
     If more than one Ingress is defined for a host and at least one Ingress uses `nginx.ingress.kubernetes.io/affinity: cookie`, then only paths on the Ingress using `nginx.ingress.kubernetes.io/affinity` will use session cookie affinity. All paths defined on other Ingresses for the host will be load balanced through the random selection of a backend server.
 
@@ -342,7 +345,7 @@ CORS can be controlled with the following annotations:
   - Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"`
 
 * `nginx.ingress.kubernetes.io/cors-expose-headers`
-  controls which headers are exposed to response. This is a multi-valued field, separated by ',' and accepts 
+  controls which headers are exposed to response. This is a multi-valued field, separated by ',' and accepts
   letters, numbers, _, - and *.
   - Default: *empty*
   - Example: `nginx.ingress.kubernetes.io/cors-expose-headers: "*, X-CustomResponseHeader"`
diff --git a/images/OWNERS b/images/OWNERS
index b135a937c..00f162ebd 100644
--- a/images/OWNERS
+++ b/images/OWNERS
@@ -1,7 +1,9 @@
+# See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
+
 approvers:
-- aledbf
-- ElvinEfendi
+- ingress-nginx-admins
+- ingress-nginx-maintainers
 
 reviewers:
-- ElvinEfendi
-- aledbf
+- ingress-nginx-admins
+- ingress-nginx-maintainers
diff --git a/images/go-grpc-greeter-server/Makefile b/images/go-grpc-greeter-server/Makefile
new file mode 100644
index 000000000..523b83bfb
--- /dev/null
+++ b/images/go-grpc-greeter-server/Makefile
@@ -0,0 +1,55 @@
+# Copyright 2021 The Kubernetes Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# set default shell
+SHELL=/bin/bash -o pipefail -o errexit
+
+DIR:=$(strip $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))))
+INIT_BUILDX=$(DIR)/../../hack/init-buildx.sh
+
+TAG ?=v$(shell date +%m%d%Y)-$(shell git rev-parse --short HEAD)
+REGISTRY ?= local
+
+IMAGE = $(REGISTRY)/go-grpc-greeter-server
+
+# required to enable buildx
+export DOCKER_CLI_EXPERIMENTAL=enabled
+
+# build with buildx
+PLATFORMS?=linux/amd64,linux/arm,linux/arm64,linux/s390x
+OUTPUT=
+PROGRESS=plain
+
+build: ensure-buildx
+	docker buildx build \
+		--platform=${PLATFORMS} $(OUTPUT) \
+		--progress=$(PROGRESS) \
+		--pull \
+		-t $(IMAGE):$(TAG) rootfs
+
+# push the cross built image
+push: OUTPUT=--push
+push: build
+
+# enable buildx
+ensure-buildx:
+# this is required for cloudbuild
+ifeq ("$(wildcard $(INIT_BUILDX))","")
+	@curl -sSL https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/hack/init-buildx.sh | bash
+else
+	@exec $(INIT_BUILDX)
+endif
+	@echo "done"
+
+.PHONY: build push ensure-buildx
diff --git a/images/go-grpc-greeter-server/cloudbuild.yaml b/images/go-grpc-greeter-server/cloudbuild.yaml
new file mode 100644
index 000000000..14c64db31
--- /dev/null
+++ b/images/go-grpc-greeter-server/cloudbuild.yaml
@@ -0,0 +1,24 @@
+timeout: 1200s
+options:
+  substitution_option: ALLOW_LOOSE
+  # job builds a multi-arch docker image for amd64,arm,arm64 and s390x.
+  machineType: N1_HIGHCPU_8
+steps:
+  - name: gcr.io/k8s-testimages/gcb-docker-gcloud:v20210622-762366a
+    entrypoint: bash
+    env:
+      - DOCKER_CLI_EXPERIMENTAL=enabled
+      - TAG=$_GIT_TAG
+      - BASE_REF=$_PULL_BASE_REF
+      - REGISTRY=gcr.io/k8s-staging-ingress-nginx
+      # default cloudbuild has HOME=/builder/home and docker buildx is in /root/.docker/cli-plugins/docker-buildx
+      # set the home to /root explicitly to if using docker buildx
+      - HOME=/root
+    args:
+    - -c
+    - |
+      gcloud auth configure-docker \
+      && make push
+substitutions:
+  _GIT_TAG: "12345"
+  _PULL_BASE_REF: "master"
diff --git a/images/go-grpc-greeter-server/rootfs/Dockerfile b/images/go-grpc-greeter-server/rootfs/Dockerfile
new file mode 100644
index 000000000..a528c6cb6
--- /dev/null
+++ b/images/go-grpc-greeter-server/rootfs/Dockerfile
@@ -0,0 +1,16 @@
+FROM golang:buster as build
+
+WORKDIR /go/src/greeter-server
+
+RUN curl -o main.go https://github.com/grpc/grpc-go/blob/91e0aeb192456225adf27966d04ada4cf8599915/examples/features/reflection/server/main.go && \
+  go mod init greeter-server && \
+  go mod tidy && \
+  go build -o /greeter-server main.go
+
+FROM gcr.io/distroless/base-debian10
+
+COPY --from=build /greeter-server /
+
+EXPOSE 50051
+
+CMD ["/greeter-server"]
diff --git a/images/grpc-fortune-teller/.gitignore b/images/grpc-fortune-teller/.gitignore
deleted file mode 100644
index ac51a054d..000000000
--- a/images/grpc-fortune-teller/.gitignore
+++ /dev/null
@@ -1,1 +0,0 @@
-bazel-*
diff --git a/images/grpc-fortune-teller/BUILD.bazel b/images/grpc-fortune-teller/BUILD.bazel
deleted file mode 100644
index b000f8347..000000000
--- a/images/grpc-fortune-teller/BUILD.bazel
+++ /dev/null
@@ -1,15 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "gazelle", "go_prefix")
-
-gazelle(
-    name = "gazelle",
-    args = [
-        "-build_file_name",
-        "BUILD,BUILD.bazel",
-    ],
-    command = "fix",
-    prefix = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller",
-)
-
-go_prefix(
-    prefix = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller",
-)
diff --git a/images/grpc-fortune-teller/Gopkg.lock b/images/grpc-fortune-teller/Gopkg.lock
deleted file mode 100644
index e25ff02dd..000000000
--- a/images/grpc-fortune-teller/Gopkg.lock
+++ /dev/null
@@ -1,104 +0,0 @@
-# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
-
-
-[[projects]]
-  name = "github.com/golang/protobuf"
-  packages = [
-    "proto",
-    "protoc-gen-go/descriptor",
-    "ptypes",
-    "ptypes/any",
-    "ptypes/duration",
-    "ptypes/timestamp"
-  ]
-  revision = "925541529c1fa6821df4e44ce2723319eb2be768"
-  version = "v1.0.0"
-
-[[projects]]
-  name = "github.com/vromero/gofortune"
-  packages = [
-    "lib",
-    "lib/fortune"
-  ]
-  revision = "8a3c485287c0d3d3e4f8f0d9e0058f6cdd29740d"
-  version = "v0.0.1"
-
-[[projects]]
-  branch = "master"
-  name = "golang.org/x/net"
-  packages = [
-    "context",
-    "http2",
-    "http2/hpack",
-    "idna",
-    "internal/timeseries",
-    "lex/httplex",
-    "trace"
-  ]
-  revision = "b3c676e531a6dc479fa1b35ac961c13f5e2b4d2e"
-
-[[projects]]
-  name = "golang.org/x/text"
-  packages = [
-    "collate",
-    "collate/build",
-    "internal/colltab",
-    "internal/gen",
-    "internal/tag",
-    "internal/triegen",
-    "internal/ucd",
-    "language",
-    "secure/bidirule",
-    "transform",
-    "unicode/bidi",
-    "unicode/cldr",
-    "unicode/norm",
-    "unicode/rangetable"
-  ]
-  revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
-  version = "v0.3.0"
-
-[[projects]]
-  branch = "master"
-  name = "google.golang.org/genproto"
-  packages = ["googleapis/rpc/status"]
-  revision = "35de2414665fc36f56b72d982c5af480d86de5ab"
-
-[[projects]]
-  name = "google.golang.org/grpc"
-  packages = [
-    ".",
-    "balancer",
-    "balancer/base",
-    "balancer/roundrobin",
-    "codes",
-    "connectivity",
-    "credentials",
-    "encoding",
-    "encoding/proto",
-    "grpclb/grpc_lb_v1/messages",
-    "grpclog",
-    "internal",
-    "keepalive",
-    "metadata",
-    "naming",
-    "peer",
-    "reflection",
-    "reflection/grpc_reflection_v1alpha",
-    "resolver",
-    "resolver/dns",
-    "resolver/passthrough",
-    "stats",
-    "status",
-    "tap",
-    "transport"
-  ]
-  revision = "d89cded64628466c4ab532d1f0ba5c220459ebe8"
-  version = "v1.11.2"
-
-[solve-meta]
-  analyzer-name = "dep"
-  analyzer-version = 1
-  inputs-digest = "fcd36504ba120cfbbe161eac2ddd439c884fcef61ce318b5c0a544d27bc47043"
-  solver-name = "gps-cdcl"
-  solver-version = 1
diff --git a/images/grpc-fortune-teller/README.md b/images/grpc-fortune-teller/README.md
deleted file mode 100644
index 5cc755ef6..000000000
--- a/images/grpc-fortune-teller/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-
-# grpc-fortune-teller
-
-This is the grpc server application for the nginx-ingress grpc example.  Bazel
-0.12 is used for the building and container management.
-
-## Build
-
-Builds a statically compiled go binary cross-compiled for linux:
-
-```
-$ bazel build //app:fortune 
-Target //app:fortune up-to-date:
-  bazel-bin/app/linux_amd64_static_pure_stripped/fortune
-```
-
-> To build for your host system, comment out the `goos` and `goarch` attributes
-> in the `go_binary` rule.
-
-## Run
-
-Builds a minimal docker image that wraps the go_binary, loads it into your local
-docker image repository, and runs it:
-
-```sh
-$ bazel run //app:image
-Loaded image ID: sha256:aa597c897c873116fcbfccafecf5ab0f6f4178a05e4a00c8e79de91ac0d2e9e7
-Tagging aa597c897c873116fcbfccafecf5ab0f6f4178a05e4a00c8e79de91ac0d2e9e7 as bazel/app:image
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/fortunes.dat
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/literature
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/literature.dat
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/riddles
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/riddles.dat
-2018/05/01 02:13:43 Restored /tmp/fortune-teller/usr/share/games/fortunes/fortunes
-2018/05/01 02:13:43 Assets restored to /tmp/fortune-teller
-2018/05/01 02:13:43 Listening for gRPC requests at 50051
-```
-
-Or run it via docker:
-
-```sh
-$ docker run bazel/app:image
-```
-
-Build image and push to the container registry specified in the `container_push`
-rule:
-
-```sh
-$ bazel run //app:push
-```
-
-## Invoke
-
-```sh
-$ grpcurl -plaintext localhost:50051 build.stack.fortune.FortuneTeller/Predict
-{
-  "message": "Whenever the literary German dives into a sentence, that is the last\nyou are going to see of him until he emerges on the other side of his\nAtlantic with his verb in his mouth.\n\t\t-- Mark Twain \"A Connecticut Yankee in King Arthur's Court\""
-}
-```
diff --git a/images/grpc-fortune-teller/WORKSPACE b/images/grpc-fortune-teller/WORKSPACE
deleted file mode 100644
index a6960d054..000000000
--- a/images/grpc-fortune-teller/WORKSPACE
+++ /dev/null
@@ -1,62 +0,0 @@
-workspace(name = "com_github_kubernetes_ingress_nginx_images_grpc_fortune_teller")
-
-#####################################################################
-# RULES_GO
-#####################################################################
-
-git_repository(
-    name = "io_bazel_rules_go",
-    remote = "https://github.com/bazelbuild/rules_go.git",
-    commit = "161c91485b007c6bf51c0e81808cf4ee2ded299d",
-)
-
-http_archive(
-    name = "com_github_scele_rules_go_dep",
-    urls = ["https://github.com/scele/rules_go_dep/archive/49a5e4ca9f6a16c9b4c930a51ce3a537498bb4e1.tar.gz"],
-    strip_prefix = "rules_go_dep-49a5e4ca9f6a16c9b4c930a51ce3a537498bb4e1",
-    sha256 = "f170d3d6f55e216f1493f975cde6c489d7070da2a8a41fd4de9812d96f4fb38b",
-)
-
-load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
-load("@com_github_scele_rules_go_dep//dep:dep.bzl", "dep_import")
-
-go_register_toolchains(go_version = "1.10.1")
-
-go_rules_dependencies()
-
-dep_import(
-    name = "godeps",
-    prefix = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller",
-    gopkg_lock = "//:Gopkg.lock",
-)
-
-load("@godeps//:Gopkg.bzl", "go_deps")
-
-go_deps()
-
-#############################################################
-# RULES_DOCKER
-#############################################################
-
-RULES_DOCKER_VERSION = "553d5506bb7325185950f91533b967da8f5bc536"
-
-http_archive(
-    name = "io_bazel_rules_docker",
-    url = "https://github.com/bazelbuild/rules_docker/archive/%s.zip" % RULES_DOCKER_VERSION,
-    strip_prefix = "rules_docker-" + RULES_DOCKER_VERSION,
-    sha256 = "e0b3d966f2a5c0fe921b6294df7c823afa63b4c439f0a7f3b9da3ed6534bab83",
-)
-
-load(
-    "@io_bazel_rules_docker//container:container.bzl",
-    container_repositories = "repositories",
-)
-
-container_repositories()
-
-load(
-    "@io_bazel_rules_docker//go:image.bzl",
-    go_image_repositories = "repositories",
-)
-
-go_image_repositories()
diff --git a/images/grpc-fortune-teller/app/BUILD.bazel b/images/grpc-fortune-teller/app/BUILD.bazel
deleted file mode 100644
index 4fffd481e..000000000
--- a/images/grpc-fortune-teller/app/BUILD.bazel
+++ /dev/null
@@ -1,69 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_embed_data")
-load("@io_bazel_rules_docker//go:image.bzl", "go_image")
-load("@io_bazel_rules_docker//container:push.bzl", "container_push")
-
-# Concatenates the fortune databases to a single bundle.
-# May need to adjust paths for your system (built on ubuntu 16.04).
-# $ apt-get install fortune
-genrule(
-    name = "tar",
-    outs = ["fortune.tar"],
-    cmd = " && ".join([
-        "OUT=$$(pwd)/$@",
-        "tar -cvf $$OUT /usr/share/games/fortunes",
-    ]),
-)
-
-# Generates a .go source file with the tarball content in 
-# the fortuneFiles variable.
-go_embed_data(
-    name = "fortune_assets",
-    srcs = [
-        ":tar",
-    ],
-    package = "main",
-    unpack = True,
-    var = "fortuneFiles",
-)
-
-go_library(
-    name = "go_default_library",
-    srcs = [
-        "main.go",
-        ":fortune_assets",  # keep
-    ],
-    importpath = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller/app",
-    visibility = ["//visibility:private"],
-    deps = [
-        "//proto/fortune:go_default_library",
-        "@com_github_vromero_gofortune//lib/fortune:go_default_library",
-        "@org_golang_google_grpc//:go_default_library",
-        "@org_golang_google_grpc//codes:go_default_library",
-        "@org_golang_google_grpc//reflection:go_default_library",
-    ],
-)
-
-go_binary(
-    name = "fortune",
-    embed = [":go_default_library"],
-    goarch = "amd64",
-    goos = "linux",
-    pure = "on",
-    static = "on",
-    visibility = ["//visibility:public"],
-)
-
-go_image(
-    name = "image",
-    binary = ":fortune",
-    visibility = ["//visibility:public"],
-)
-
-container_push(
-    name = "push",
-    format = "Docker",
-    image = ":image",
-    registry = "quay.io",
-    repository = "kubernetes-ingress-controller/grpc-fortune-teller",
-    tag = "0.1",
-)
diff --git a/images/grpc-fortune-teller/app/main.go b/images/grpc-fortune-teller/app/main.go
deleted file mode 100644
index d09799cbe..000000000
--- a/images/grpc-fortune-teller/app/main.go
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
-Copyright 2018 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
-	"context"
-	"fmt"
-	"io/ioutil"
-	"log"
-	"net"
-	"os"
-	"path"
-
-	proto "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller/proto/fortune"
-	"github.com/vromero/gofortune/lib/fortune"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/reflection"
-)
-
-const (
-	grpcPort = 50051
-)
-
-func main() {
-
-	baseDir := "/tmp/fortune-teller"
-	mustMkdirAll(baseDir)
-
-	opts := []grpc.ServerOption{
-		grpc.MaxConcurrentStreams(200),
-	}
-
-	grpcServer := grpc.NewServer(opts...)
-
-	fortuneTeller := &FortuneTeller{
-		fs: createFortuneFilesystemNodeDescriptor(baseDir),
-	}
-	proto.RegisterFortuneTellerServer(grpcServer, fortuneTeller)
-
-	reflection.Register(grpcServer)
-
-	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", grpcPort))
-	if err != nil {
-		log.Fatalf("Error while starting grpc server: %v\n", err)
-	}
-
-	log.Printf("Listening for gRPC requests at %d\n", grpcPort)
-	grpcServer.Serve(lis)
-}
-
-// FortuneTeller - struct that will implement the grpc service interface.
-type FortuneTeller struct {
-	fs *fortune.FileSystemNodeDescriptor
-}
-
-// Predict - implementation for the grpc unary request method.
-func (f *FortuneTeller) Predict(ctx context.Context, r *proto.PredictionRequest) (*proto.PredictionResponse, error) {
-	_, data, err := fortune.GetRandomFortune(*f.fs)
-	if err != nil {
-		return nil, grpc.Errorf(codes.Internal, "Unable to render fortune: %v", err)
-	}
-	return &proto.PredictionResponse{
-		Message: data,
-	}, nil
-}
-
-func createFortuneFilesystemNodeDescriptor(baseDir string) *fortune.FileSystemNodeDescriptor {
-
-	// Restore the packed fortune data
-	fortuneDir := path.Join(baseDir, "usr/share/games/fortunes")
-
-	mustRestore(baseDir, fortuneFiles, nil)
-
-	// init gofortune fs
-	fs, err := fortune.LoadPaths([]fortune.ProbabilityPath{
-		{Path: fortuneDir},
-	})
-	if err != nil {
-		log.Fatalf("Unable to load fortune paths: %v", err)
-	}
-
-	fortune.SetProbabilities(&fs, true) // consider all equal probabilities
-	return &fs
-}
-
-// mustRestore - Restore assets.
-func mustRestore(baseDir string, assets map[string][]byte, mappings map[string]string) {
-	// unpack variable is provided by the go_embed data and is a
-	// map[string][]byte such as {"/usr/share/games/fortune/literature.dat":
-	// bytes... }
-	for basename, bytes := range assets {
-		if mappings != nil {
-			replacement := mappings[basename]
-			if replacement != "" {
-				basename = replacement
-			}
-		}
-		filename := path.Join(baseDir, basename)
-		dirname := path.Dir(filename)
-		//log.Printf("file %s, dir %s, rel %d, abs %s, absdir: %s", file, dir, rel, abs, absdir)
-		if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
-			log.Fatalf("Failed to create asset dir %s: %v", dirname, err)
-		}
-
-		if err := ioutil.WriteFile(filename, bytes, os.ModePerm); err != nil {
-			log.Fatalf("Failed to write asset %s: %v", filename, err)
-		}
-		log.Printf("Restored %s", filename)
-	}
-
-	log.Printf("Assets restored to %s", baseDir)
-}
-
-// mustMkdirAll - make all dirs and panic if fail
-func mustMkdirAll(dirs ...string) {
-	for _, dir := range dirs {
-		err := os.MkdirAll(dir, os.ModePerm)
-		if err != nil {
-			panic(fmt.Sprintf("Failed mkdir %s: %v", dir, err))
-		}
-	}
-}
diff --git a/images/grpc-fortune-teller/go.mod b/images/grpc-fortune-teller/go.mod
deleted file mode 100644
index e69de29bb..000000000
diff --git a/images/grpc-fortune-teller/proto/fortune/BUILD.bazel b/images/grpc-fortune-teller/proto/fortune/BUILD.bazel
deleted file mode 100644
index b3008b826..000000000
--- a/images/grpc-fortune-teller/proto/fortune/BUILD.bazel
+++ /dev/null
@@ -1,24 +0,0 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library")
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-go_library(
-    name = "go_default_library",
-    srcs = ["doc.go"],
-    embed = [":build_stack_fortune_go_proto"],  # keep
-    importpath = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller/proto/fortune",
-    visibility = ["//visibility:public"],
-)
-
-proto_library(
-    name = "build_stack_fortune_proto",
-    srcs = ["fortune.proto"],
-    visibility = ["//visibility:public"],
-)
-
-go_proto_library(
-    name = "build_stack_fortune_go_proto",
-    compilers = ["@io_bazel_rules_go//proto:go_grpc"],
-    importpath = "github.com/kubernetes/ingress-nginx/images/grpc-fortune-teller/proto/fortune",
-    proto = ":build_stack_fortune_proto",
-    visibility = ["//visibility:public"],
-)
diff --git a/images/grpc-fortune-teller/proto/fortune/fortune.proto b/images/grpc-fortune-teller/proto/fortune/fortune.proto
deleted file mode 100644
index d71b4acb1..000000000
--- a/images/grpc-fortune-teller/proto/fortune/fortune.proto
+++ /dev/null
@@ -1,14 +0,0 @@
-syntax = "proto3";
-
-package build.stack.fortune;
-
-message PredictionRequest {
-}
-
-message PredictionResponse {
-    string message = 1;
-}
-
-service FortuneTeller {
-    rpc Predict(PredictionRequest) returns (PredictionResponse);
-}
\ No newline at end of file
diff --git a/internal/ingress/annotations/backendprotocol/main.go b/internal/ingress/annotations/backendprotocol/main.go
index d6e7beb4c..65566d6e9 100644
--- a/internal/ingress/annotations/backendprotocol/main.go
+++ b/internal/ingress/annotations/backendprotocol/main.go
@@ -31,7 +31,7 @@ import (
 const HTTP = "HTTP"
 
 var (
-	validProtocols = regexp.MustCompile(`^(HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
+	validProtocols = regexp.MustCompile(`^(AUTO_HTTP|HTTP|HTTPS|AJP|GRPC|GRPCS|FCGI)$`)
 )
 
 type backendProtocol struct {
diff --git a/internal/ingress/annotations/sessionaffinity/main.go b/internal/ingress/annotations/sessionaffinity/main.go
index c5b340ec7..80b24f13b 100644
--- a/internal/ingress/annotations/sessionaffinity/main.go
+++ b/internal/ingress/annotations/sessionaffinity/main.go
@@ -27,8 +27,10 @@ import (
 )
 
 const (
-	annotationAffinityType = "affinity"
-	annotationAffinityMode = "affinity-mode"
+	annotationAffinityType           = "affinity"
+	annotationAffinityMode           = "affinity-mode"
+	annotationAffinityCanaryBehavior = "affinity-canary-behavior"
+
 	// If a cookie with this name exists,
 	// its value is used as an index into the list of available backends.
 	annotationAffinityCookieName = "session-cookie-name"
@@ -66,6 +68,8 @@ type Config struct {
 	Type string `json:"type"`
 	// The affinity mode, i.e. how sticky a session is
 	Mode string `json:"mode"`
+	// Affinity behavior for canaries (sticky or legacy)
+	CanaryBehavior string `json:"canaryBehavior"`
 	Cookie
 }
 
@@ -160,6 +164,11 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
 		am = ""
 	}
 
+	cb, err := parser.GetStringAnnotation(annotationAffinityCanaryBehavior, ing)
+	if err != nil {
+		cb = ""
+	}
+
 	switch at {
 	case "cookie":
 		cookie = a.cookieAffinityParse(ing)
@@ -169,8 +178,9 @@ func (a affinity) Parse(ing *networking.Ingress) (interface{}, error) {
 	}
 
 	return &Config{
-		Type:   at,
-		Mode:   am,
-		Cookie: *cookie,
+		Type:           at,
+		Mode:           am,
+		CanaryBehavior: cb,
+		Cookie:         *cookie,
 	}, nil
 }
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index 5d4cff721..08ef2d240 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -1315,7 +1315,7 @@ func canMergeBackend(primary *ingress.Backend, alternative *ingress.Backend) boo
 }
 
 // Performs the merge action and checks to ensure that one two alternative backends do not merge into each other
-func mergeAlternativeBackend(priUps *ingress.Backend, altUps *ingress.Backend) bool {
+func mergeAlternativeBackend(ing *ingress.Ingress, priUps *ingress.Backend, altUps *ingress.Backend) bool {
 	if priUps.NoServer {
 		klog.Warningf("unable to merge alternative backend %v into primary backend %v because %v is a primary backend",
 			altUps.Name, priUps.Name, priUps.Name)
@@ -1329,6 +1329,10 @@ func mergeAlternativeBackend(priUps *ingress.Backend, altUps *ingress.Backend) b
 		}
 	}
 
+	if ing.ParsedAnnotations != nil && ing.ParsedAnnotations.SessionAffinity.CanaryBehavior != "legacy" {
+		priUps.SessionAffinity.DeepCopyInto(&altUps.SessionAffinity)
+	}
+
 	priUps.AlternativeBackends =
 		append(priUps.AlternativeBackends, altUps.Name)
 
@@ -1368,7 +1372,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
 					klog.V(2).Infof("matching backend %v found for alternative backend %v",
 						priUps.Name, altUps.Name)
 
-					merged = mergeAlternativeBackend(priUps, altUps)
+					merged = mergeAlternativeBackend(ing, priUps, altUps)
 				}
 			}
 
@@ -1421,7 +1425,7 @@ func mergeAlternativeBackends(ing *ingress.Ingress, upstreams map[string]*ingres
 					klog.V(2).Infof("matching backend %v found for alternative backend %v",
 						priUps.Name, altUps.Name)
 
-					merged = mergeAlternativeBackend(priUps, altUps)
+					merged = mergeAlternativeBackend(ing, priUps, altUps)
 				}
 			}
 
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
index 3aa521c87..a14e2d50b 100644
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -617,6 +617,8 @@ func buildProxyPass(host string, b interface{}, loc interface{}) string {
 	proxyPass := "proxy_pass"
 
 	switch location.BackendProtocol {
+	case "AUTO_HTTP":
+		proto = "$scheme://"
 	case "HTTPS":
 		proto = "https://"
 	case "GRPC":
diff --git a/rootfs/etc/nginx/lua/OWNERS b/rootfs/etc/nginx/lua/OWNERS
new file mode 100644
index 000000000..79814fdba
--- /dev/null
+++ b/rootfs/etc/nginx/lua/OWNERS
@@ -0,0 +1,4 @@
+# See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
+
+labels:
+- area/lua
\ No newline at end of file
diff --git a/rootfs/etc/nginx/lua/balancer.lua b/rootfs/etc/nginx/lua/balancer.lua
index afcfebb67..e83257a6f 100644
--- a/rootfs/etc/nginx/lua/balancer.lua
+++ b/rootfs/etc/nginx/lua/balancer.lua
@@ -48,7 +48,7 @@ local function get_implementation(backend)
 
   if backend["sessionAffinityConfig"] and
      backend["sessionAffinityConfig"]["name"] == "cookie" then
-    if backend["sessionAffinityConfig"]["mode"] == 'persistent' then
+    if backend["sessionAffinityConfig"]["mode"] == "persistent" then
       name = "sticky_persistent"
     else
       name = "sticky_balanced"
@@ -186,6 +186,11 @@ local function sync_backends()
 end
 
 local function route_to_alternative_balancer(balancer)
+  if balancer.is_affinitized(balancer) then
+    -- If request is already affinitized to a primary balancer, keep the primary balancer.
+    return false
+  end
+
   if not balancer.alternative_backends then
     return false
   end
@@ -204,6 +209,13 @@ local function route_to_alternative_balancer(balancer)
     return false
   end
 
+  if alternative_balancer.is_affinitized(alternative_balancer) then
+    -- If request is affinitized to an alternative balancer, instruct caller to
+    -- switch to alternative.
+    return true
+  end
+
+  -- Use traffic shaping policy, if request didn't have affinity set.
   local traffic_shaping_policy =  alternative_balancer.traffic_shaping_policy
   if not traffic_shaping_policy then
     ngx.log(ngx.ERR, "traffic shaping policy is not set for balancer ",
@@ -254,6 +266,10 @@ local function route_to_alternative_balancer(balancer)
   return false
 end
 
+local function get_balancer_by_upstream_name(upstream_name)
+  return balancers[upstream_name]
+end
+
 local function get_balancer()
   if ngx.ctx.balancer then
     return ngx.ctx.balancer
@@ -263,7 +279,7 @@ local function get_balancer()
 
   local balancer = balancers[backend_name]
   if not balancer then
-    return
+    return nil
   end
 
   if route_to_alternative_balancer(balancer) then
@@ -352,6 +368,7 @@ setmetatable(_M, {__index = {
   sync_backend = sync_backend,
   route_to_alternative_balancer = route_to_alternative_balancer,
   get_balancer = get_balancer,
+  get_balancer_by_upstream_name = get_balancer_by_upstream_name,
 }})
 
 return _M
diff --git a/rootfs/etc/nginx/lua/balancer/chashsubset.lua b/rootfs/etc/nginx/lua/balancer/chashsubset.lua
index 28c2354a1..d9ceb471e 100644
--- a/rootfs/etc/nginx/lua/balancer/chashsubset.lua
+++ b/rootfs/etc/nginx/lua/balancer/chashsubset.lua
@@ -68,6 +68,10 @@ function _M.new(self, backend)
   return o
 end
 
+function _M.is_affinitized()
+  return false
+end
+
 function _M.balance(self)
   local key = util.generate_var_value(self.hash_by)
   local subset_id = self.instance:find(key)
diff --git a/rootfs/etc/nginx/lua/balancer/ewma.lua b/rootfs/etc/nginx/lua/balancer/ewma.lua
index ae65ccc73..681866dc1 100644
--- a/rootfs/etc/nginx/lua/balancer/ewma.lua
+++ b/rootfs/etc/nginx/lua/balancer/ewma.lua
@@ -170,6 +170,10 @@ local function calculate_slow_start_ewma(self)
   return total_ewma / endpoints_count
 end
 
+function _M.is_affinitized()
+  return false
+end
+
 function _M.balance(self)
   local peers = self.peers
   local endpoint, ewma_score = peers[1], -1
diff --git a/rootfs/etc/nginx/lua/balancer/resty.lua b/rootfs/etc/nginx/lua/balancer/resty.lua
index c1065ff19..12b24be14 100644
--- a/rootfs/etc/nginx/lua/balancer/resty.lua
+++ b/rootfs/etc/nginx/lua/balancer/resty.lua
@@ -14,6 +14,10 @@ function _M.new(self, o)
   return o
 end
 
+function _M.is_affinitized()
+  return false
+end
+
 function _M.sync(self, backend)
   self.traffic_shaping_policy = backend.trafficShapingPolicy
   self.alternative_backends = backend.alternativeBackends
diff --git a/rootfs/etc/nginx/lua/balancer/sticky.lua b/rootfs/etc/nginx/lua/balancer/sticky.lua
index 45ea9beaf..63f3c6785 100644
--- a/rootfs/etc/nginx/lua/balancer/sticky.lua
+++ b/rootfs/etc/nginx/lua/balancer/sticky.lua
@@ -13,6 +13,7 @@ local setmetatable = setmetatable
 
 local _M = balancer_resty:new()
 local DEFAULT_COOKIE_NAME = "route"
+local COOKIE_VALUE_DELIMITER = "|"
 
 function _M.cookie_name(self)
   return self.cookie_session_affinity.name or DEFAULT_COOKIE_NAME
@@ -22,7 +23,8 @@ function _M.new(self)
   local o = {
     alternative_backends = nil,
     cookie_session_affinity = nil,
-    traffic_shaping_policy = nil
+    traffic_shaping_policy = nil,
+    backend_key = nil
   }
 
   setmetatable(o, self)
@@ -31,13 +33,37 @@ function _M.new(self)
   return o
 end
 
-function _M.get_cookie(self)
+function _M.get_cookie_parsed(self)
   local cookie, err = ck:new()
   if not cookie then
     ngx.log(ngx.ERR, err)
   end
 
-  return cookie:get(self:cookie_name())
+  local result = {
+    upstream_key = nil,
+    backend_key = nil
+  }
+
+  local raw_value = cookie:get(self:cookie_name())
+  if not raw_value then
+    return result
+  end
+
+  local parsed_value, len = split.split_string(raw_value, COOKIE_VALUE_DELIMITER)
+  if len == 0 then
+    return result
+  end
+
+  result.upstream_key = parsed_value[1]
+  if len > 1 then
+    result.backend_key = parsed_value[2]
+  end
+
+  return result
+end
+
+function _M.get_cookie(self)
+  return self:get_cookie_parsed().upstream_key
 end
 
 function _M.set_cookie(self, value)
@@ -63,7 +89,7 @@ function _M.set_cookie(self, value)
 
   local cookie_data = {
     key = self:cookie_name(),
-    value = value,
+    value = value .. COOKIE_VALUE_DELIMITER .. self.backend_key,
     path = cookie_path,
     httponly = true,
     samesite = cookie_samesite,
@@ -86,6 +112,10 @@ function _M.set_cookie(self, value)
   end
 end
 
+function _M.is_affinitized(self)
+  return self:get_cookie_parsed().backend_key == self.backend_key
+end
+
 function _M.get_last_failure()
   return ngx_balancer.get_last_failure()
 end
@@ -166,6 +196,7 @@ function _M.sync(self, backend)
   self.traffic_shaping_policy = backend.trafficShapingPolicy
   self.alternative_backends = backend.alternativeBackends
   self.cookie_session_affinity = backend.sessionAffinityConfig.cookieSessionAffinity
+  self.backend_key = ngx.md5(ngx.md5(backend.name) .. backend.name)
 end
 
 return _M
diff --git a/rootfs/etc/nginx/lua/util/split.lua b/rootfs/etc/nginx/lua/util/split.lua
index d5400ab57..63edf0900 100644
--- a/rootfs/etc/nginx/lua/util/split.lua
+++ b/rootfs/etc/nginx/lua/util/split.lua
@@ -65,4 +65,19 @@ function _M.split_upstream_addr(addrs_str)
   return host_and_ports
 end
 
+-- Splits string by delimiter. Returns array of parsed values and the length of the array.
+function _M.split_string(what, delim)
+  local result = {}
+  local idx = 0
+
+  if what and delim and delim ~= "" then
+    for chunk in what:gmatch("([^" .. delim .. "]+)") do
+      idx = idx + 1
+      result[idx] = chunk
+    end
+  end
+
+  return result, idx
+end
+
 return _M


Test patch:
diff --git a/internal/ingress/annotations/annotations_test.go b/internal/ingress/annotations/annotations_test.go
index 1a428e729..de02a8a16 100644
--- a/internal/ingress/annotations/annotations_test.go
+++ b/internal/ingress/annotations/annotations_test.go
@@ -30,19 +30,20 @@ import (
 )
 
 var (
-	annotationPassthrough          = parser.GetAnnotationWithPrefix("ssl-passthrough")
-	annotationAffinityType         = parser.GetAnnotationWithPrefix("affinity")
-	annotationAffinityMode         = parser.GetAnnotationWithPrefix("affinity-mode")
-	annotationCorsEnabled          = parser.GetAnnotationWithPrefix("enable-cors")
-	annotationCorsAllowMethods     = parser.GetAnnotationWithPrefix("cors-allow-methods")
-	annotationCorsAllowHeaders     = parser.GetAnnotationWithPrefix("cors-allow-headers")
-	annotationCorsExposeHeaders    = parser.GetAnnotationWithPrefix("cors-expose-headers")
-	annotationCorsAllowCredentials = parser.GetAnnotationWithPrefix("cors-allow-credentials")
-	defaultCorsMethods             = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
-	defaultCorsHeaders             = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
-	annotationAffinityCookieName   = parser.GetAnnotationWithPrefix("session-cookie-name")
-	annotationUpstreamHashBy       = parser.GetAnnotationWithPrefix("upstream-hash-by")
-	annotationCustomHTTPErrors     = parser.GetAnnotationWithPrefix("custom-http-errors")
+	annotationPassthrough            = parser.GetAnnotationWithPrefix("ssl-passthrough")
+	annotationAffinityType           = parser.GetAnnotationWithPrefix("affinity")
+	annotationAffinityMode           = parser.GetAnnotationWithPrefix("affinity-mode")
+	annotationAffinityCanaryBehavior = parser.GetAnnotationWithPrefix("affinity-canary-behavior")
+	annotationCorsEnabled            = parser.GetAnnotationWithPrefix("enable-cors")
+	annotationCorsAllowMethods       = parser.GetAnnotationWithPrefix("cors-allow-methods")
+	annotationCorsAllowHeaders       = parser.GetAnnotationWithPrefix("cors-allow-headers")
+	annotationCorsExposeHeaders      = parser.GetAnnotationWithPrefix("cors-expose-headers")
+	annotationCorsAllowCredentials   = parser.GetAnnotationWithPrefix("cors-allow-credentials")
+	defaultCorsMethods               = "GET, PUT, POST, DELETE, PATCH, OPTIONS"
+	defaultCorsHeaders               = "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
+	annotationAffinityCookieName     = parser.GetAnnotationWithPrefix("session-cookie-name")
+	annotationUpstreamHashBy         = parser.GetAnnotationWithPrefix("upstream-hash-by")
+	annotationCustomHTTPErrors       = parser.GetAnnotationWithPrefix("custom-http-errors")
 )
 
 type mockCfg struct {
@@ -162,29 +163,38 @@ func TestAffinitySession(t *testing.T) {
 	ing := buildIngress()
 
 	fooAnns := []struct {
-		annotations  map[string]string
-		affinitytype string
-		affinitymode string
-		name         string
+		annotations    map[string]string
+		affinitytype   string
+		affinitymode   string
+		cookiename     string
+		canarybehavior string
 	}{
-		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "route"}, "cookie", "balanced", "route"},
-		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "persistent", annotationAffinityCookieName: "route1"}, "cookie", "persistent", "route1"},
-		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: ""}, "cookie", "balanced", "INGRESSCOOKIE"},
-		{map[string]string{}, "", "", ""},
-		{nil, "", "", ""},
+		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "route", annotationAffinityCanaryBehavior: ""}, "cookie", "balanced", "route", ""},
+		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "persistent", annotationAffinityCookieName: "route1", annotationAffinityCanaryBehavior: "sticky"}, "cookie", "persistent", "route1", "sticky"},
+		{map[string]string{annotationAffinityType: "cookie", annotationAffinityMode: "balanced", annotationAffinityCookieName: "", annotationAffinityCanaryBehavior: "legacy"}, "cookie", "balanced", "INGRESSCOOKIE", "legacy"},
+		{map[string]string{}, "", "", "", ""},
+		{nil, "", "", "", ""},
 	}
 
 	for _, foo := range fooAnns {
 		ing.SetAnnotations(foo.annotations)
 		r := ec.Extract(ing).SessionAffinity
-		t.Logf("Testing pass %v %v", foo.affinitytype, foo.name)
+		t.Logf("Testing pass %v %v", foo.affinitytype, foo.cookiename)
+
+		if r.Type != foo.affinitytype {
+			t.Errorf("Returned %v but expected %v for Type", r.Type, foo.affinitytype)
+		}
 
 		if r.Mode != foo.affinitymode {
-			t.Errorf("Returned %v but expected %v for Name", r.Mode, foo.affinitymode)
+			t.Errorf("Returned %v but expected %v for Mode", r.Mode, foo.affinitymode)
+		}
+
+		if r.CanaryBehavior != foo.canarybehavior {
+			t.Errorf("Returned %v but expected %v for CanaryBehavior", r.CanaryBehavior, foo.canarybehavior)
 		}
 
-		if r.Cookie.Name != foo.name {
-			t.Errorf("Returned %v but expected %v for Name", r.Cookie.Name, foo.name)
+		if r.Cookie.Name != foo.cookiename {
+			t.Errorf("Returned %v but expected %v for Cookie.Name", r.Cookie.Name, foo.cookiename)
 		}
 	}
 }
diff --git a/internal/ingress/controller/controller_test.go b/internal/ingress/controller/controller_test.go
index 22d108ca4..112aac0b1 100644
--- a/internal/ingress/controller/controller_test.go
+++ b/internal/ingress/controller/controller_test.go
@@ -44,6 +44,7 @@ import (
 	"k8s.io/ingress-nginx/internal/ingress/annotations/canary"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
+	"k8s.io/ingress-nginx/internal/ingress/annotations/sessionaffinity"
 	"k8s.io/ingress-nginx/internal/ingress/controller/config"
 	ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
 	"k8s.io/ingress-nginx/internal/ingress/controller/store"
@@ -786,6 +787,326 @@ func TestMergeAlternativeBackends(t *testing.T) {
 				},
 			},
 		},
+		"alternative backend gets SessionAffinitySettings configured when CanaryBehavior is 'sticky'": {
+			&ingress.Ingress{
+				Ingress: networking.Ingress{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "example",
+					},
+					Spec: networking.IngressSpec{
+						Rules: []networking.IngressRule{
+							{
+								Host: "example.com",
+								IngressRuleValue: networking.IngressRuleValue{
+									HTTP: &networking.HTTPIngressRuleValue{
+										Paths: []networking.HTTPIngressPath{
+											{
+												Path:     "/",
+												PathType: &pathTypePrefix,
+												Backend: networking.IngressBackend{
+													ServiceName: "http-svc-canary",
+													ServicePort: intstr.IntOrString{
+														IntVal: 80,
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				ParsedAnnotations: &annotations.Ingress{
+					SessionAffinity: sessionaffinity.Config{
+						CanaryBehavior: "sticky",
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:     "example-http-svc-80",
+					NoServer: false,
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:                "example-http-svc-80",
+					NoServer:            false,
+					AlternativeBackends: []string{"example-http-svc-canary-80"},
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+		},
+		"alternative backend gets SessionAffinitySettings configured when CanaryBehavior is not 'legacy'": {
+			&ingress.Ingress{
+				Ingress: networking.Ingress{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "example",
+					},
+					Spec: networking.IngressSpec{
+						Rules: []networking.IngressRule{
+							{
+								Host: "example.com",
+								IngressRuleValue: networking.IngressRuleValue{
+									HTTP: &networking.HTTPIngressRuleValue{
+										Paths: []networking.HTTPIngressPath{
+											{
+												Path:     "/",
+												PathType: &pathTypePrefix,
+												Backend: networking.IngressBackend{
+													ServiceName: "http-svc-canary",
+													ServicePort: intstr.IntOrString{
+														IntVal: 80,
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				ParsedAnnotations: &annotations.Ingress{
+					SessionAffinity: sessionaffinity.Config{
+						CanaryBehavior: "", // In fact any value but 'legacy' would do the trick.
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:     "example-http-svc-80",
+					NoServer: false,
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:                "example-http-svc-80",
+					NoServer:            false,
+					AlternativeBackends: []string{"example-http-svc-canary-80"},
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+		},
+		"alternative backend doesn't get SessionAffinitySettings configured when CanaryBehavior is 'legacy'": {
+			&ingress.Ingress{
+				Ingress: networking.Ingress{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "example",
+					},
+					Spec: networking.IngressSpec{
+						Rules: []networking.IngressRule{
+							{
+								Host: "example.com",
+								IngressRuleValue: networking.IngressRuleValue{
+									HTTP: &networking.HTTPIngressRuleValue{
+										Paths: []networking.HTTPIngressPath{
+											{
+												Path:     "/",
+												PathType: &pathTypePrefix,
+												Backend: networking.IngressBackend{
+													ServiceName: "http-svc-canary",
+													ServicePort: intstr.IntOrString{
+														IntVal: 80,
+													},
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+				ParsedAnnotations: &annotations.Ingress{
+					SessionAffinity: sessionaffinity.Config{
+						CanaryBehavior: "legacy",
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:     "example-http-svc-80",
+					NoServer: false,
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+			map[string]*ingress.Backend{
+				"example-http-svc-80": {
+					Name:                "example-http-svc-80",
+					NoServer:            false,
+					AlternativeBackends: []string{"example-http-svc-canary-80"},
+					SessionAffinity: ingress.SessionAffinityConfig{
+						AffinityType: "cookie",
+						AffinityMode: "balanced",
+						CookieSessionAffinity: ingress.CookieSessionAffinity{
+							Name: "test",
+						},
+					},
+				},
+				"example-http-svc-canary-80": {
+					Name:     "example-http-svc-canary-80",
+					NoServer: true,
+					TrafficShapingPolicy: ingress.TrafficShapingPolicy{
+						Weight: 20,
+					},
+				},
+			},
+			map[string]*ingress.Server{
+				"example.com": {
+					Hostname: "example.com",
+					Locations: []*ingress.Location{
+						{
+							Path:     "/",
+							PathType: &pathTypePrefix,
+							Backend:  "example-http-svc-80",
+						},
+					},
+				},
+			},
+		},
 	}
 
 	for title, tc := range testCases {
@@ -801,7 +1122,7 @@ func TestMergeAlternativeBackends(t *testing.T) {
 				if !actualUpstream.Equal(expUpstream) {
 					t.Logf("actual upstream %s alternative backends: %s", actualUpstream.Name, actualUpstream.AlternativeBackends)
 					t.Logf("expected upstream %s alternative backends: %s", expUpstream.Name, expUpstream.AlternativeBackends)
-					t.Errorf("upstream %s was not equal to what was expected: ", upsName)
+					t.Errorf("upstream %s was not equal to what was expected", actualUpstream.Name)
 				}
 			}
 
diff --git a/internal/ingress/controller/store/endpoint_test.go b/internal/ingress/controller/store/endpoint_test.go
new file mode 100644
index 000000000..6c8ae40e2
--- /dev/null
+++ b/internal/ingress/controller/store/endpoint_test.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2017 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package store
+
+import (
+	apiv1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/cache"
+	"testing"
+)
+
+func newEndpointLister(t *testing.T) *EndpointLister {
+	t.Helper()
+
+	return &EndpointLister{Store: cache.NewStore(cache.MetaNamespaceKeyFunc)}
+}
+
+func TestEndpointLister(t *testing.T) {
+	t.Run("the key does not exist", func(t *testing.T) {
+		el := newEndpointLister(t)
+
+		key := "namespace/endpoint"
+		_, err := el.ByKey(key)
+
+		if err == nil {
+			t.Error("expected an error but nothing has been returned")
+		}
+
+		if _, ok := err.(NotExistsError); !ok {
+			t.Errorf("expected NotExistsError, got %v", err)
+		}
+	})
+
+	t.Run("the key exists", func(t *testing.T) {
+		el := newEndpointLister(t)
+
+		key := "namespace/endpoint"
+		endpoint := &apiv1.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "namespace", Name: "endpoint"}}
+
+		el.Add(endpoint)
+
+		e, err := el.ByKey(key)
+
+		if err != nil {
+			t.Errorf("unexpeted error %v", err)
+		}
+
+		if e != endpoint {
+			t.Errorf("expected %v, error, got %v", e, endpoint)
+		}
+	})
+}
diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go
index 411b24349..c5f40c246 100644
--- a/internal/ingress/controller/template/template_test.go
+++ b/internal/ingress/controller/template/template_test.go
@@ -59,20 +59,22 @@ func init() {
 var (
 	// TODO: add tests for SSLPassthrough
 	tmplFuncTestcases = map[string]struct {
-		Path             string
-		Target           string
-		Location         string
-		ProxyPass        string
-		Sticky           bool
-		XForwardedPrefix string
-		SecureBackend    bool
-		enforceRegex     bool
+		Path              string
+		Target            string
+		Location          string
+		ProxyPass         string
+		AutoHttpProxyPass string
+		Sticky            bool
+		XForwardedPrefix  string
+		SecureBackend     bool
+		enforceRegex      bool
 	}{
 		"when secure backend enabled": {
 			"/",
 			"/",
 			"/",
 			"proxy_pass https://upstream_balancer;",
+			"proxy_pass https://upstream_balancer;",
 			false,
 			"",
 			true,
@@ -83,6 +85,7 @@ var (
 			"/",
 			"/",
 			"proxy_pass https://upstream_balancer;",
+			"proxy_pass https://upstream_balancer;",
 			false,
 			"",
 			true,
@@ -93,6 +96,7 @@ var (
 			"/",
 			"/",
 			"proxy_pass https://upstream_balancer;",
+			"proxy_pass https://upstream_balancer;",
 			true,
 			"",
 			true,
@@ -103,6 +107,7 @@ var (
 			"/",
 			"/",
 			"proxy_pass http://upstream_balancer;",
+			"proxy_pass $scheme://upstream_balancer;",
 			false,
 			"",
 			false,
@@ -113,6 +118,7 @@ var (
 			"/",
 			"/",
 			"proxy_pass http://upstream_balancer;",
+			"proxy_pass $scheme://upstream_balancer;",
 			false,
 			"",
 			false,
@@ -125,6 +131,9 @@ var (
 			`
 rewrite "(?i)/" /jenkins break;
 proxy_pass http://upstream_balancer;`,
+			`
+rewrite "(?i)/" /jenkins break;
+proxy_pass $scheme://upstream_balancer;`,
 			false,
 			"",
 			false,
@@ -137,6 +146,9 @@ proxy_pass http://upstream_balancer;`,
 			`
 rewrite "(?i)/" /something break;
 proxy_pass http://upstream_balancer;`,
+			`
+rewrite "(?i)/" /something break;
+proxy_pass $scheme://upstream_balancer;`,
 			true,
 			"",
 			false,
@@ -149,6 +161,9 @@ proxy_pass http://upstream_balancer;`,
 			`
 rewrite "(?i)/" /something break;
 proxy_pass http://upstream_balancer;`,
+			`
+rewrite "(?i)/" /something break;
+proxy_pass $scheme://upstream_balancer;`,
 			true,
 			"",
 			false,
@@ -162,6 +177,10 @@ proxy_pass http://upstream_balancer;`,
 rewrite "(?i)/there" /something break;
 proxy_set_header X-Forwarded-Prefix "/there";
 proxy_pass http://upstream_balancer;`,
+			`
+rewrite "(?i)/there" /something break;
+proxy_set_header X-Forwarded-Prefix "/there";
+proxy_pass $scheme://upstream_balancer;`,
 			true,
 			"/there",
 			false,
@@ -172,6 +191,7 @@ proxy_pass http://upstream_balancer;`,
 			"/something",
 			`~* "^/something"`,
 			"proxy_pass http://upstream_balancer;",
+			"proxy_pass $scheme://upstream_balancer;",
 			false,
 			"",
 			false,
@@ -344,6 +364,48 @@ func TestBuildProxyPass(t *testing.T) {
 	}
 }
 
+func TestBuildProxyPassAutoHttp(t *testing.T) {
+	defaultBackend := "upstream-name"
+	defaultHost := "example.com"
+
+	for k, tc := range tmplFuncTestcases {
+		loc := &ingress.Location{
+			Path:             tc.Path,
+			Rewrite:          rewrite.Config{Target: tc.Target},
+			Backend:          defaultBackend,
+			XForwardedPrefix: tc.XForwardedPrefix,
+		}
+
+		if tc.SecureBackend {
+			loc.BackendProtocol = "HTTPS"
+		} else {
+			loc.BackendProtocol = "AUTO_HTTP"
+		}
+
+		backend := &ingress.Backend{
+			Name: defaultBackend,
+		}
+
+		if tc.Sticky {
+			backend.SessionAffinity = ingress.SessionAffinityConfig{
+				AffinityType: "cookie",
+				CookieSessionAffinity: ingress.CookieSessionAffinity{
+					Locations: map[string][]string{
+						defaultHost: {tc.Path},
+					},
+				},
+			}
+		}
+
+		backends := []*ingress.Backend{backend}
+
+		pp := buildProxyPass(defaultHost, backends, loc)
+		if !strings.EqualFold(tc.AutoHttpProxyPass, pp) {
+			t.Errorf("%s: expected \n'%v'\nbut returned \n'%v'", k, tc.ProxyPass, pp)
+		}
+	}
+}
+
 func TestBuildAuthLocation(t *testing.T) {
 	invalidType := &ingress.Ingress{}
 	expected := ""
@@ -899,13 +961,14 @@ func TestEscapeLiteralDollar(t *testing.T) {
 
 func TestOpentracingPropagateContext(t *testing.T) {
 	tests := map[*ingress.Location]string{
-		{BackendProtocol: "HTTP"}:  "opentracing_propagate_context;",
-		{BackendProtocol: "HTTPS"}: "opentracing_propagate_context;",
-		{BackendProtocol: "GRPC"}:  "opentracing_grpc_propagate_context;",
-		{BackendProtocol: "GRPCS"}: "opentracing_grpc_propagate_context;",
-		{BackendProtocol: "AJP"}:   "opentracing_propagate_context;",
-		{BackendProtocol: "FCGI"}:  "opentracing_propagate_context;",
-		nil:                        "",
+		{BackendProtocol: "HTTP"}:      "opentracing_propagate_context;",
+		{BackendProtocol: "HTTPS"}:     "opentracing_propagate_context;",
+		{BackendProtocol: "AUTO_HTTP"}: "opentracing_propagate_context;",
+		{BackendProtocol: "GRPC"}:      "opentracing_grpc_propagate_context;",
+		{BackendProtocol: "GRPCS"}:     "opentracing_grpc_propagate_context;",
+		{BackendProtocol: "AJP"}:       "opentracing_propagate_context;",
+		{BackendProtocol: "FCGI"}:      "opentracing_propagate_context;",
+		nil:                            "",
 	}
 
 	for loc, expectedDirective := range tests {
diff --git a/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua b/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua
index 44e103c1c..a0c0ae54f 100644
--- a/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua
+++ b/rootfs/etc/nginx/lua/test/balancer/sticky_test.lua
@@ -5,22 +5,33 @@ local util = require("util")
 
 local original_ngx = ngx
 
-function mock_ngx(mock)
+local function reset_sticky_balancer()
+  package.loaded["balancer.sticky"] = nil
+  package.loaded["balancer.sticky_balanced"] = nil
+  package.loaded["balancer.sticky_persistent"] = nil
+
+  sticky_balanced = require("balancer.sticky_balanced")
+  sticky_persistent = require("balancer.sticky_persistent")
+end
+
+local function mock_ngx(mock, after_mock_set)
   local _ngx = mock
-  setmetatable(_ngx, {__index = _G.ngx})
+  setmetatable(_ngx, { __index = ngx })
   _G.ngx = _ngx
+
+  if after_mock_set then
+    after_mock_set()
+  end
+
+  -- Balancer module caches ngx module, must be reset after mocks were configured.
+  reset_sticky_balancer()
 end
 
 local function reset_ngx()
   _G.ngx = original_ngx
-end
 
-local function reset_sticky_balancer()
-  package.loaded["balancer.sticky"] = nil
-  package.loaded["balancer.sticky_balanced"] = nil
-  package.loaded["balancer.sticky_persistent"] = nil
-  sticky_balanced = require("balancer.sticky_balanced")
-  sticky_persistent = require("balancer.sticky_persistent")
+  -- Ensure balancer cache is reset.
+  _G.ngx.ctx.balancer = nil
 end
 
 function get_mocked_cookie_new()
@@ -55,7 +66,6 @@ end
 describe("Sticky", function()
   before_each(function()
     mock_ngx({ var = { location_path = "/", host = "test.com" } })
-    reset_sticky_balancer()
   end)
 
   after_each(function()
@@ -65,29 +75,44 @@ describe("Sticky", function()
   local test_backend = get_test_backend()
   local test_backend_endpoint= test_backend.endpoints[1].address .. ":" .. test_backend.endpoints[1].port
 
+  local legacy_cookie_value = test_backend_endpoint
+  local function create_current_cookie_value(backend_key)
+    return test_backend_endpoint .. "|" .. backend_key
+  end
+
   describe("new(backend)", function()
-    context("when backend specifies cookie name", function()
-      local function test(sticky)
-        local sticky_balancer_instance = sticky:new(test_backend)
+    describe("when backend specifies cookie name", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
         local test_backend_cookie_name = test_backend.sessionAffinityConfig.cookieSessionAffinity.name
         assert.equal(sticky_balancer_instance:cookie_name(), test_backend_cookie_name)
       end
 
-      it("returns an instance containing the corresponding cookie name", function() test(sticky_balanced) end)
-      it("returns an instance containing the corresponding cookie name", function() test(sticky_persistent) end)
+      it("returns an instance containing the corresponding cookie name", function() test_with(sticky_balanced) end)
+      it("returns an instance containing the corresponding cookie name", function() test_with(sticky_persistent) end)
     end)
 
-    context("when backend does not specify cookie name", function()
-      local function test(sticky)
+    describe("when backend does not specify cookie name", function()
+      local function test_with(sticky_balancer_type)
         local temp_backend = util.deepcopy(test_backend)
         temp_backend.sessionAffinityConfig.cookieSessionAffinity.name = nil
-        local sticky_balancer_instance = sticky:new(temp_backend)
+        local sticky_balancer_instance = sticky_balancer_type:new(temp_backend)
         local default_cookie_name = "route"
         assert.equal(sticky_balancer_instance:cookie_name(), default_cookie_name)
       end
 
-      it("returns an instance with 'route' as cookie name", function() test(sticky_balanced) end)
-      it("returns an instance with 'route' as cookie name", function() test(sticky_persistent) end)
+      it("returns an instance with 'route' as cookie name", function() test_with(sticky_balanced) end)
+      it("returns an instance with 'route' as cookie name", function() test_with(sticky_persistent) end)
+    end)
+
+    describe("backend_key", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+        assert.is_truthy(sticky_balancer_instance.backend_key)
+      end
+
+      it("calculates at construction time", function() test_with(sticky_balanced) end)
+      it("calculates at construction time", function() test_with(sticky_persistent) end)
     end)
   end)
 
@@ -95,28 +120,25 @@ describe("Sticky", function()
     local mocked_cookie_new = cookie.new
 
     before_each(function()
-      package.loaded["balancer.sticky_balanced"] = nil
-      package.loaded["balancer.sticky_persistent"] = nil
-      sticky_balanced = require("balancer.sticky_balanced")
-      sticky_persistent = require("balancer.sticky_persistent")
+      reset_sticky_balancer()
     end)
 
     after_each(function()
       cookie.new = mocked_cookie_new
     end)
 
-    context("when client doesn't have a cookie set and location is in cookie_locations", function()
+    describe("when client doesn't have a cookie set and location is in cookie_locations", function()
 
-      local function test_pick_endpoint(sticky)
-        local sticky_balancer_instance = sticky:new(test_backend)
+      local function test_pick_endpoint_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
         local peer = sticky_balancer_instance:balance()
         assert.equal(test_backend_endpoint, peer)
       end
 
-      it("picks an endpoint for the client", function() test_pick_endpoint(sticky_balanced) end)
-      it("picks an endpoint for the client", function() test_pick_endpoint(sticky_persistent) end)
+      it("picks an endpoint for the client", function() test_pick_endpoint_with(sticky_balanced) end)
+      it("picks an endpoint for the client", function() test_pick_endpoint_with(sticky_persistent) end)
 
-      local function test_set_cookie(sticky)
+      local function test_set_cookie_with(sticky_balancer_type)
         local s = {}
         cookie.new = function(self)
           local cookie_instance = {
@@ -137,15 +159,15 @@ describe("Sticky", function()
         local b = get_test_backend()
         b.sessionAffinityConfig.cookieSessionAffinity.locations = {}
         b.sessionAffinityConfig.cookieSessionAffinity.locations["test.com"] = {"/"}
-        local sticky_balancer_instance = sticky:new(b)
+        local sticky_balancer_instance = sticky_balancer_type:new(b)
         assert.has_no.errors(function() sticky_balancer_instance:balance() end)
         assert.spy(s).was_called()
       end
 
-      it("sets a cookie on the client", function() test_set_cookie(sticky_balanced) end)
-      it("sets a cookie on the client", function() test_set_cookie(sticky_persistent) end)
+      it("sets a cookie on the client", function() test_set_cookie_with(sticky_balanced) end)
+      it("sets a cookie on the client", function() test_set_cookie_with(sticky_persistent) end)
 
-      local function test_set_ssl_cookie(sticky)
+      local function test_set_ssl_cookie_with(sticky_balancer_type)
         ngx.var.https = "on"
         local s = {}
         cookie.new = function(self)
@@ -167,21 +189,17 @@ describe("Sticky", function()
         local b = get_test_backend()
         b.sessionAffinityConfig.cookieSessionAffinity.locations = {}
         b.sessionAffinityConfig.cookieSessionAffinity.locations["test.com"] = {"/"}
-        local sticky_balancer_instance = sticky:new(b)
+        local sticky_balancer_instance = sticky_balancer_type:new(b)
         assert.has_no.errors(function() sticky_balancer_instance:balance() end)
         assert.spy(s).was_called()
       end
 
-      it("sets a secure cookie on the client when being in ssl mode", function()
-          test_set_ssl_cookie(sticky_balanced)
-      end)
-      it("sets a secure cookie on the client when being in ssl mode", function()
-        test_set_ssl_cookie(sticky_persistent)
-      end)
+      it("sets a secure cookie on the client when being in ssl mode", function() test_set_ssl_cookie_with(sticky_balanced) end)
+      it("sets a secure cookie on the client when being in ssl mode", function() test_set_ssl_cookie_with(sticky_persistent) end)
     end)
 
-    context("when client doesn't have a cookie set and cookie_locations contains a matching wildcard location",
-    function()
+    describe("when client doesn't have a cookie set and cookie_locations contains a matching wildcard location", function()
+
       before_each(function ()
         ngx.var.host = "dev.test.com"
       end)
@@ -189,7 +207,7 @@ describe("Sticky", function()
         ngx.var.host = "test.com"
       end)
 
-      local function test(sticky)
+      local function test_with(sticky_balancer_type)
         local s = {}
         cookie.new = function(self)
           local cookie_instance = {
@@ -211,27 +229,27 @@ describe("Sticky", function()
         local b = get_test_backend()
         b.sessionAffinityConfig.cookieSessionAffinity.locations = {}
         b.sessionAffinityConfig.cookieSessionAffinity.locations["*.test.com"] = {"/"}
-        local sticky_balancer_instance = sticky:new(b)
+        local sticky_balancer_instance = sticky_balancer_type:new(b)
         assert.has_no.errors(function() sticky_balancer_instance:balance() end)
         assert.spy(s).was_called()
       end
 
-      it("sets a cookie on the client", function() test(sticky_balanced) end)
-      it("sets a cookie on the client", function() test(sticky_persistent) end)
+      it("sets a cookie on the client", function() test_with(sticky_balanced) end)
+      it("sets a cookie on the client", function() test_with(sticky_persistent) end)
     end)
 
-    context("when client doesn't have a cookie set and location not in cookie_locations", function()
+    describe("when client doesn't have a cookie set and location not in cookie_locations", function()
 
-      local function test_pick_endpoint(sticky)
-        local sticky_balancer_instance = sticky:new(test_backend)
+      local function test_pick_endpoint_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
         local peer = sticky_balancer_instance:balance()
         assert.equal(peer, test_backend_endpoint)
       end
 
-      it("picks an endpoint for the client", function() test_pick_endpoint(sticky_balanced) end)
-      it("picks an endpoint for the client", function() test_pick_endpoint(sticky_persistent) end)
+      it("picks an endpoint for the client", function() test_pick_endpoint_with(sticky_balanced) end)
+      it("picks an endpoint for the client", function() test_pick_endpoint_with(sticky_persistent) end)
 
-      local function test_no_cookie(sticky)
+      local function test_no_cookie_with(sticky_balancer_type)
         local s = {}
         cookie.new = function(self)
           local cookie_instance = {
@@ -248,34 +266,34 @@ describe("Sticky", function()
           s = spy.on(cookie_instance, "set")
           return cookie_instance, false
         end
-        local sticky_balancer_instance = sticky:new(get_test_backend())
+        local sticky_balancer_instance = sticky_balancer_type:new(get_test_backend())
         assert.has_no.errors(function() sticky_balancer_instance:balance() end)
         assert.spy(s).was_not_called()
       end
 
-      it("does not set a cookie on the client", function() test_no_cookie(sticky_balanced) end)
-      it("does not set a cookie on the client", function() test_no_cookie(sticky_persistent) end)
+      it("does not set a cookie on the client", function() test_no_cookie_with(sticky_balanced) end)
+      it("does not set a cookie on the client", function() test_no_cookie_with(sticky_persistent) end)
     end)
 
-    context("when client has a cookie set", function()
+    describe("when client has a cookie set", function()
 
-      local function test_no_cookie(sticky)
+      local function test_no_cookie_with(sticky_balancer_type)
         local s = {}
         cookie.new = function(self)
           local return_obj = {
             set = function(v) return false, nil end,
-            get = function(k) return test_backend_endpoint end,
+            get = function(k) return legacy_cookie_value end,
           }
           s = spy.on(return_obj, "set")
           return return_obj, false
         end
-        local sticky_balancer_instance = sticky:new(test_backend)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
         assert.has_no.errors(function() sticky_balancer_instance:balance() end)
         assert.spy(s).was_not_called()
       end
 
-      it("does not set a cookie", function() test_no_cookie(sticky_balanced) end)
-      it("does not set a cookie", function() test_no_cookie(sticky_persistent) end)
+      it("does not set a cookie", function() test_no_cookie_with(sticky_balanced) end)
+      it("does not set a cookie", function() test_no_cookie_with(sticky_persistent) end)
 
       local function test_correct_endpoint(sticky)
         local sticky_balancer_instance = sticky:new(test_backend)
@@ -312,17 +330,16 @@ describe("Sticky", function()
 
     before_each(function()
       mock_ngx({ var = { location_path = "/", host = "test.com" } })
-      reset_sticky_balancer()
     end)
 
     after_each(function()
       reset_ngx()
     end)
 
-    context("when request to upstream fails", function()
+    describe("when request to upstream fails", function()
 
-      local function test(sticky, change_on_failure)
-        local sticky_balancer_instance = sticky:new(get_several_test_backends(change_on_failure))
+      local function test_with(sticky_balancer_type, change_on_failure)
+        local sticky_balancer_instance = sticky_balancer_type:new(get_several_test_backends(change_on_failure))
 
         local old_upstream = sticky_balancer_instance:balance()
         assert.is.Not.Nil(old_upstream)
@@ -349,29 +366,21 @@ describe("Sticky", function()
         end
       end
 
-      it("changes upstream when change_on_failure option is true", function()
-        test(sticky_balanced, true)
-      end)
-      it("changes upstream when change_on_failure option is true", function()
-        test(sticky_balanced, false)
-      end)
-      it("changes upstream when change_on_failure option is true", function()
-        test(sticky_persistent, true)
-      end)
-      it("changes upstream when change_on_failure option is true", function()
-        test(sticky_persistent, false)
-      end)
+      it("changes upstream when change_on_failure option is true", function() test_with(sticky_balanced, true) end)
+      it("changes upstream when change_on_failure option is true", function() test_with(sticky_persistent, true) end)
+
+      it("changes upstream when change_on_failure option is false", function() test_with(sticky_balanced, false) end)
+      it("changes upstream when change_on_failure option is false", function() test_with(sticky_persistent, false) end)
     end)
   end)
 
-  context("when client doesn't have a cookie set and no host header, matching default server '_'",
-  function()
+  describe("when client doesn't have a cookie set and no host header, matching default server '_'", function()
     before_each(function ()
       ngx.var.host = "not-default-server"
       ngx.var.server_name = "_"
     end)
 
-    local function test(sticky)
+    local function test_with(sticky_balancer_type)
       local s = {}
       cookie.new = function(self)
         local cookie_instance = {
@@ -393,30 +402,27 @@ describe("Sticky", function()
       local b = get_test_backend()
       b.sessionAffinityConfig.cookieSessionAffinity.locations = {}
       b.sessionAffinityConfig.cookieSessionAffinity.locations["_"] = {"/"}
-      local sticky_balancer_instance = sticky:new(b)
+      local sticky_balancer_instance = sticky_balancer_type:new(b)
       assert.has_no.errors(function() sticky_balancer_instance:balance() end)
       assert.spy(s).was_called()
     end
 
-    it("sets a cookie on the client", function() test(sticky_balanced) end)
-    it("sets a cookie on the client", function() test(sticky_persistent) end)
+    it("sets a cookie on the client", function() test_with(sticky_balanced) end)
+    it("sets a cookie on the client", function() test_with(sticky_persistent) end)
   end)
 
   describe("SameSite settings", function()
     local mocked_cookie_new = cookie.new
 
     before_each(function()
-      package.loaded["balancer.sticky_balanced"] = nil
-      package.loaded["balancer.sticky_persistent"] = nil
-      sticky_balanced = require("balancer.sticky_balanced")
-      sticky_persistent = require("balancer.sticky_persistent")
+      reset_sticky_balancer()
     end)
 
     after_each(function()
       cookie.new = mocked_cookie_new
     end)
 
-    local function test_set_cookie(sticky, samesite, conditional_samesite_none, expected_path, expected_samesite)
+    local function test_set_cookie_with(sticky_balancer_type, samesite, conditional_samesite_none, expected_path, expected_samesite)
       local s = {}
       cookie.new = function(self)
         local cookie_instance = {
@@ -439,34 +445,179 @@ describe("Sticky", function()
       b.sessionAffinityConfig.cookieSessionAffinity.locations["test.com"] = {"/"}
       b.sessionAffinityConfig.cookieSessionAffinity.samesite = samesite
       b.sessionAffinityConfig.cookieSessionAffinity.conditional_samesite_none = conditional_samesite_none
-      local sticky_balancer_instance = sticky:new(b)
+      local sticky_balancer_instance = sticky_balancer_type:new(b)
       assert.has_no.errors(function() sticky_balancer_instance:balance() end)
       assert.spy(s).was_called()
     end
 
     it("returns a cookie with SameSite=Strict when user specifies samesite strict", function()
-      test_set_cookie(sticky_balanced, "Strict", false, "/", "Strict")
+      test_set_cookie_with(sticky_balanced, "Strict", false, "/", "Strict")
     end)
     it("returns a cookie with SameSite=Strict when user specifies samesite strict and conditional samesite none", function()
-      test_set_cookie(sticky_balanced, "Strict", true, "/", "Strict")
+      test_set_cookie_with(sticky_balanced, "Strict", true, "/", "Strict")
     end)
     it("returns a cookie with SameSite=Lax when user specifies samesite lax", function()
-      test_set_cookie(sticky_balanced, "Lax", false, "/", "Lax")
+      test_set_cookie_with(sticky_balanced, "Lax", false, "/", "Lax")
     end)
     it("returns a cookie with SameSite=Lax when user specifies samesite lax and conditional samesite none", function()
-      test_set_cookie(sticky_balanced, "Lax", true, "/", "Lax")
+      test_set_cookie_with(sticky_balanced, "Lax", true, "/", "Lax")
     end)
     it("returns a cookie with SameSite=None when user specifies samesite None", function()
-      test_set_cookie(sticky_balanced, "None", false, "/", "None")
+      test_set_cookie_with(sticky_balanced, "None", false, "/", "None")
     end)
     it("returns a cookie with SameSite=None when user specifies samesite None and conditional samesite none with supported user agent", function()
       mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.2704.103 Safari/537.36"} })
-      test_set_cookie(sticky_balanced, "None", true, "/", "None")
+      test_set_cookie_with(sticky_balanced, "None", true, "/", "None")
     end)
     it("returns a cookie without SameSite=None when user specifies samesite None and conditional samesite none with unsupported user agent", function()
       mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"} })
-      reset_sticky_balancer()
-      test_set_cookie(sticky_balanced, "None", true, "/", nil)
+      test_set_cookie_with(sticky_balanced, "None", true, "/", nil)
+    end)
+
+    it("returns a cookie with SameSite=Strict when user specifies samesite strict", function()
+      test_set_cookie_with(sticky_persistent, "Strict", false, "/", "Strict")
+    end)
+    it("returns a cookie with SameSite=Strict when user specifies samesite strict and conditional samesite none", function()
+      test_set_cookie_with(sticky_persistent, "Strict", true, "/", "Strict")
+    end)
+    it("returns a cookie with SameSite=Lax when user specifies samesite lax", function()
+      test_set_cookie_with(sticky_persistent, "Lax", false, "/", "Lax")
+    end)
+    it("returns a cookie with SameSite=Lax when user specifies samesite lax and conditional samesite none", function()
+      test_set_cookie_with(sticky_persistent, "Lax", true, "/", "Lax")
     end)
+    it("returns a cookie with SameSite=None when user specifies samesite None", function()
+      test_set_cookie_with(sticky_persistent, "None", false, "/", "None")
+    end)
+    it("returns a cookie with SameSite=None when user specifies samesite None and conditional samesite none with supported user agent", function()
+      mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.2704.103 Safari/537.36"} })
+      test_set_cookie_with(sticky_persistent, "None", true, "/", "None")
+    end)
+    it("returns a cookie without SameSite=None when user specifies samesite None and conditional samesite none with unsupported user agent", function()
+      mock_ngx({ var = { location_path = "/", host = "test.com" , http_user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"} })
+      test_set_cookie_with(sticky_persistent, "None", true, "/", nil)
+    end)
+  end)
+
+  describe("get_cookie()", function()
+
+    describe("legacy cookie value", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+
+        cookie.new = function(self)
+          local return_obj = {
+            set = function(v) return false, nil end,
+            get = function(k) return legacy_cookie_value end,
+          }
+          return return_obj, false
+        end
+
+        assert.equal(test_backend_endpoint, sticky_balancer_instance.get_cookie(sticky_balancer_instance))
+      end
+
+      it("retrieves upstream key value", function() test_with(sticky_balanced) end)
+      it("retrieves upstream key value", function() test_with(sticky_persistent) end)
+    end)
+
+    describe("current cookie value", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+
+        cookie.new = function(self)
+          local return_obj = {
+            set = function(v) return false, nil end,
+            get = function(k) return create_current_cookie_value(sticky_balancer_instance.backend_key) end,
+          }
+          return return_obj, false
+        end
+
+        assert.equal(test_backend_endpoint, sticky_balancer_instance.get_cookie(sticky_balancer_instance))
+      end
+
+      it("retrieves upstream key value", function() test_with(sticky_balanced) end)
+      it("retrieves upstream key value", function() test_with(sticky_persistent) end)
+    end)
+
+  end)
+
+  describe("get_cookie_parsed()", function()
+
+    describe("legacy cookie value", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+
+        cookie.new = function(self)
+          local return_obj = {
+            set = function(v) return false, nil end,
+            get = function(k) return legacy_cookie_value end,
+          }
+          return return_obj, false
+        end
+
+        local parsed_cookie = sticky_balancer_instance.get_cookie_parsed(sticky_balancer_instance)
+
+        assert.is_truthy(parsed_cookie)
+        assert.equal(test_backend_endpoint, parsed_cookie.upstream_key)
+        assert.is_falsy(parsed_cookie.backend_key)
+      end
+
+      it("retrieves upstream key value", function() test_with(sticky_balanced) end)
+      it("retrieves upstream key value", function() test_with(sticky_persistent) end)
+    end)
+
+    describe("current cookie value", function()
+      local function test_with(sticky_balancer_type)
+        local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+
+        cookie.new = function(self)
+          local return_obj = {
+            set = function(v) return false, nil end,
+            get = function(k) return create_current_cookie_value(sticky_balancer_instance.backend_key) end,
+          }
+          return return_obj, false
+        end
+
+        local parsed_cookie = sticky_balancer_instance.get_cookie_parsed(sticky_balancer_instance)
+
+        assert.is_truthy(parsed_cookie)
+        assert.equal(test_backend_endpoint, parsed_cookie.upstream_key)
+        assert.equal(sticky_balancer_instance.backend_key, parsed_cookie.backend_key)
+      end
+
+      it("retrieves all supported values", function() test_with(sticky_balanced) end)
+      it("retrieves all supported values", function() test_with(sticky_persistent) end)
+    end)
+
+  end)
+
+  describe("set_cookie()", function()
+
+    local function test_with(sticky_balancer_type)
+      local sticky_balancer_instance = sticky_balancer_type:new(test_backend)
+
+      local cookieSetSpy = {}
+      cookie.new = function(self)
+        local return_obj = {
+          set = function(self, payload)
+            assert.equal(create_current_cookie_value(sticky_balancer_instance.backend_key), payload.value)
+
+            return true, nil
+          end,
+          get = function(k) return nil end,
+        }
+        cookieSetSpy = spy.on(return_obj, "set")
+
+        return return_obj, false
+      end
+
+      sticky_balancer_instance.set_cookie(sticky_balancer_instance, test_backend_endpoint)
+
+      assert.spy(cookieSetSpy).was_called()
+    end
+
+    it("constructs correct cookie value", function() test_with(sticky_balanced) end)
+    it("constructs correct cookie value", function() test_with(sticky_persistent) end)
+
   end)
 end)
diff --git a/rootfs/etc/nginx/lua/test/balancer_test.lua b/rootfs/etc/nginx/lua/test/balancer_test.lua
index 1834b6f60..4f40bc6ae 100644
--- a/rootfs/etc/nginx/lua/test/balancer_test.lua
+++ b/rootfs/etc/nginx/lua/test/balancer_test.lua
@@ -6,12 +6,9 @@ local original_ngx = ngx
 
 local function reset_ngx()
   _G.ngx = original_ngx
-end
 
-local function mock_ngx(mock)
-  local _ngx = mock
-  setmetatable(_ngx, { __index = ngx })
-  _G.ngx = _ngx
+  -- Ensure balancer cache is reset.
+  _G.ngx.ctx.balancer = nil
 end
 
 local function reset_balancer()
@@ -19,6 +16,19 @@ local function reset_balancer()
   balancer = require("balancer")
 end
 
+local function mock_ngx(mock, after_mock_set)
+  local _ngx = mock
+  setmetatable(_ngx, { __index = ngx })
+  _G.ngx = _ngx
+
+  if after_mock_set then
+    after_mock_set()
+  end
+
+  -- Balancer module caches ngx module, must be reset after mocks were configured.
+  reset_balancer()
+end
+
 local function reset_expected_implementations()
   expected_implementations = {
     ["access-router-production-web-80"] = package.loaded["balancer.round_robin"],
@@ -26,7 +36,8 @@ local function reset_expected_implementations()
     ["my-dummy-app-2"] = package.loaded["balancer.chash"],
     ["my-dummy-app-3"] = package.loaded["balancer.sticky_persistent"],
     ["my-dummy-app-4"] = package.loaded["balancer.ewma"],
-    ["my-dummy-app-5"] = package.loaded["balancer.sticky_balanced"]
+    ["my-dummy-app-5"] = package.loaded["balancer.sticky_balanced"],
+    ["my-dummy-app-6"] = package.loaded["balancer.chashsubset"]
   }
 end
 
@@ -48,20 +59,35 @@ local function reset_backends()
         cookie = ""
       },
     },
-    { name = "my-dummy-app-1", ["load-balance"] = "round_robin", },
     {
-      name = "my-dummy-app-2", ["load-balance"] = "chash",
+      name = "my-dummy-app-1",
+      ["load-balance"] = "round_robin",
+    },
+    {
+      name = "my-dummy-app-2",
+      ["load-balance"] = "round_robin",           -- upstreamHashByConfig will take priority.
       upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", },
     },
     {
-      name = "my-dummy-app-3", ["load-balance"] = "ewma",
-      sessionAffinityConfig = { name = "cookie", mode = 'persistent', cookieSessionAffinity = { name = "route" } }
+      name = "my-dummy-app-3",
+      ["load-balance"] = "ewma",                  -- sessionAffinityConfig will take priority.
+      sessionAffinityConfig = { name = "cookie", mode = "persistent", cookieSessionAffinity = { name = "route" } }
     },
-    { name = "my-dummy-app-4", ["load-balance"] = "ewma", },
     {
-      name = "my-dummy-app-5", ["load-balance"] = "ewma", ["upstream-hash-by"] = "$request_uri",
+      name = "my-dummy-app-4",
+      ["load-balance"] = "ewma",
+    },
+    {
+      name = "my-dummy-app-5",
+      ["load-balance"] = "ewma",                  -- sessionAffinityConfig will take priority.
+      upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", },
       sessionAffinityConfig = { name = "cookie", cookieSessionAffinity = { name = "route" } }
     },
+    {
+      name = "my-dummy-app-6",
+      ["load-balance"] = "ewma",                  -- upstreamHashByConfig will take priority.
+      upstreamHashByConfig = { ["upstream-hash-by"] = "$request_uri", ["upstream-hash-by-subset"] = "true", }
+    },
   }
 end
 
@@ -77,7 +103,7 @@ describe("Balancer", function()
   end)
 
   describe("get_implementation()", function()
-    it("returns correct implementation for given backend", function()
+    it("uses heuristics to select correct load balancer implementation for a given backend", function()
       for _, backend in pairs(backends) do
         local expected_implementation = expected_implementations[backend.name]
         local implementation = balancer.get_implementation(backend)
@@ -89,8 +115,8 @@ describe("Balancer", function()
   describe("get_balancer()", function()
     it("always returns the same balancer for given request context", function()
       local backend = {
-        name = "my-dummy-app-6", ["load-balance"] = "ewma",
-        alternativeBackends = { "my-dummy-canary-app-6" },
+        name = "my-dummy-app-100", ["load-balance"] = "ewma",
+        alternativeBackends = { "my-dummy-canary-app-100" },
         endpoints = { { address = "10.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
         trafficShapingPolicy = {
           weight = 0,
@@ -100,8 +126,8 @@ describe("Balancer", function()
         },
       }
       local canary_backend = {
-        name = "my-dummy-canary-app-6", ["load-balance"] = "ewma",
-        alternativeBackends = { "my-dummy-canary-app-6" },
+        name = "my-dummy-canary-app-100", ["load-balance"] = "ewma",
+        alternativeBackends = { "my-dummy-canary-app-100" },
         endpoints = { { address = "11.184.7.40", port = "8080", maxFails = 0, failTimeout = 0 } },
         trafficShapingPolicy = {
           weight = 5,
@@ -112,7 +138,6 @@ describe("Balancer", function()
       }
 
       mock_ngx({ var = { proxy_upstream_name = backend.name } })
-      reset_balancer()
 
       balancer.sync_backend(backend)
       balancer.sync_backend(canary_backend)
@@ -126,172 +151,223 @@ describe("Balancer", function()
   end)
 
   describe("route_to_alternative_balancer()", function()
-    local backend, _balancer
+    local backend, _primaryBalancer
 
     before_each(function()
       backend = backends[1]
-      _balancer = {
+      _primaryBalancer = {
         alternative_backends = {
           backend.name,
         }
       }
       mock_ngx({ var = { request_uri = "/" } })
-      reset_balancer()
     end)
 
-    it("returns false when no trafficShapingPolicy is set", function()
-      balancer.sync_backend(backend)
-      assert.equal(false, balancer.route_to_alternative_balancer(_balancer))
-    end)
+    -- Not affinitized request must follow traffic shaping policies.
+    describe("not affinitized", function()
 
-    it("returns false when no alternative backends is set", function()
-      backend.trafficShapingPolicy.weight = 100
-      balancer.sync_backend(backend)
-      _balancer.alternative_backends = nil
-      assert.equal(false, balancer.route_to_alternative_balancer(_balancer))
-    end)
+      before_each(function()
+        _primaryBalancer.is_affinitized = function (_)
+          return false
+        end
+      end)
 
-    it("returns false when alternative backends name does not match", function()
-      backend.trafficShapingPolicy.weight = 100
-      balancer.sync_backend(backend)
-      _balancer.alternative_backends[1] = "nonExistingBackend"
-      assert.equal(false, balancer.route_to_alternative_balancer(_balancer))
-    end)
+      it("returns false when no trafficShapingPolicy is set", function()
+        balancer.sync_backend(backend)
+        assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
+      end)
 
-    context("canary by weight", function()
-      it("returns true when weight is 100", function()
+      it("returns false when no alternative backends is set", function()
         backend.trafficShapingPolicy.weight = 100
         balancer.sync_backend(backend)
-        assert.equal(true, balancer.route_to_alternative_balancer(_balancer))
+        _primaryBalancer.alternative_backends = nil
+        assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
       end)
 
-      it("returns false when weight is 0", function()
-        backend.trafficShapingPolicy.weight = 0
+      it("returns false when alternative backends name does not match", function()
+        backend.trafficShapingPolicy.weight = 100
         balancer.sync_backend(backend)
-        assert.equal(false, balancer.route_to_alternative_balancer(_balancer))
+        _primaryBalancer.alternative_backends[1] = "nonExistingBackend"
+        assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
       end)
-    end)
 
-    context("canary by cookie", function()
-      it("returns correct result for given cookies", function()
-        local test_patterns = {
-          {
-            case_title = "cookie_value is 'always'",
-            request_cookie_name = "canaryCookie",
-            request_cookie_value = "always",
-            expected_result = true,
-          },
-          {
-            case_title = "cookie_value is 'never'",
-            request_cookie_name = "canaryCookie",
-            request_cookie_value = "never",
-            expected_result = false,
-          },
-          {
-            case_title = "cookie_value is undefined",
-            request_cookie_name = "canaryCookie",
-            request_cookie_value = "foo",
-            expected_result = false,
-          },
-          {
-            case_title = "cookie_name is undefined",
-            request_cookie_name = "foo",
-            request_cookie_value = "always",
-            expected_result = false
-          },
-        }
-        for _, test_pattern in pairs(test_patterns) do
-          mock_ngx({ var = {
-            ["cookie_" .. test_pattern.request_cookie_name] = test_pattern.request_cookie_value,
-            request_uri = "/"
-          }})
-          reset_balancer()
-          backend.trafficShapingPolicy.cookie = "canaryCookie"
+      describe("canary by weight", function()
+        it("returns true when weight is 100", function()
+          backend.trafficShapingPolicy.weight = 100
           balancer.sync_backend(backend)
-          assert.message("\nTest data pattern: " .. test_pattern.case_title)
-            .equal(test_pattern.expected_result, balancer.route_to_alternative_balancer(_balancer))
-          reset_ngx()
-        end
+          assert.equal(true, balancer.route_to_alternative_balancer(_primaryBalancer))
+        end)
+
+        it("returns false when weight is 0", function()
+          backend.trafficShapingPolicy.weight = 0
+          balancer.sync_backend(backend)
+          assert.equal(false, balancer.route_to_alternative_balancer(_primaryBalancer))
+        end)
+      end)
+
+      describe("canary by cookie", function()
+        it("returns correct result for given cookies", function()
+          local test_patterns = {
+            {
+              case_title = "cookie_value is 'always'",
+              request_cookie_name = "canaryCookie",
+              request_cookie_value = "always",
+              expected_result = true,
+            },
+            {
+              case_title = "cookie_value is 'never'",
+              request_cookie_name = "canaryCookie",
+              request_cookie_value = "never",
+              expected_result = false,
+            },
+            {
+              case_title = "cookie_value is undefined",
+              request_cookie_name = "canaryCookie",
+              request_cookie_value = "foo",
+              expected_result = false,
+            },
+            {
+              case_title = "cookie_name is undefined",
+              request_cookie_name = "foo",
+              request_cookie_value = "always",
+              expected_result = false
+            },
+          }
+          for _, test_pattern in pairs(test_patterns) do
+            mock_ngx({ var = {
+              ["cookie_" .. test_pattern.request_cookie_name] = test_pattern.request_cookie_value,
+              request_uri = "/"
+            }})
+            backend.trafficShapingPolicy.cookie = "canaryCookie"
+            balancer.sync_backend(backend)
+            assert.message("\nTest data pattern: " .. test_pattern.case_title)
+              .equal(test_pattern.expected_result, balancer.route_to_alternative_balancer(_primaryBalancer))
+            reset_ngx()
+          end
+        end)
       end)
+
+      describe("canary by header", function()
+        it("returns correct result for given headers", function()
+          local test_patterns = {
+            -- with no header value setting
+            {
+              case_title = "no custom header value and header value is 'always'",
+              header_name = "canaryHeader",
+              header_value = "",
+              request_header_name = "canaryHeader",
+              request_header_value = "always",
+              expected_result = true,
+            },
+            {
+              case_title = "no custom header value and header value is 'never'",
+              header_name = "canaryHeader",
+              header_value = "",
+              request_header_name = "canaryHeader",
+              request_header_value = "never",
+              expected_result = false,
+            },
+            {
+              case_title = "no custom header value and header value is undefined",
+              header_name = "canaryHeader",
+              header_value = "",
+              request_header_name = "canaryHeader",
+              request_header_value = "foo",
+              expected_result = false,
+            },
+            {
+              case_title = "no custom header value and header name is undefined",
+              header_name = "canaryHeader",
+              header_value = "",
+              request_header_name = "foo",
+              request_header_value = "always",
+              expected_result = false,
+            },
+            -- with header value setting
+            {
+              case_title = "custom header value is set and header value is 'always'",
+              header_name = "canaryHeader",
+              header_value = "foo",
+              request_header_name = "canaryHeader",
+              request_header_value = "always",
+              expected_result = false,
+            },
+            {
+              case_title = "custom header value is set and header value match custom header value",
+              header_name = "canaryHeader",
+              header_value = "foo",
+              request_header_name = "canaryHeader",
+              request_header_value = "foo",
+              expected_result = true,
+            },
+            {
+              case_title = "custom header value is set and header name is undefined",
+              header_name = "canaryHeader",
+              header_value = "foo",
+              request_header_name = "bar",
+              request_header_value = "foo",
+              expected_result = false
+            },
+          }
+
+          for _, test_pattern in pairs(test_patterns) do
+            mock_ngx({ var = {
+              ["http_" .. test_pattern.request_header_name] = test_pattern.request_header_value,
+              request_uri = "/"
+            }})
+            backend.trafficShapingPolicy.header = test_pattern.header_name
+            backend.trafficShapingPolicy.headerValue = test_pattern.header_value
+            balancer.sync_backend(backend)
+            assert.message("\nTest data pattern: " .. test_pattern.case_title)
+              .equal(test_pattern.expected_result, balancer.route_to_alternative_balancer(_primaryBalancer))
+            reset_ngx()
+          end
+        end)
+      end)
+
     end)
 
-    context("canary by header", function()
-      it("returns correct result for given headers", function()
-        local test_patterns = {
-          -- with no header value setting
-          {
-            case_title = "no custom header value and header value is 'always'",
-            header_name = "canaryHeader",
-            header_value = "",
-            request_header_name = "canaryHeader",
-            request_header_value = "always",
-            expected_result = true,
-          },
-          {
-            case_title = "no custom header value and header value is 'never'",
-            header_name = "canaryHeader",
-            header_value = "",
-            request_header_name = "canaryHeader",
-            request_header_value = "never",
-            expected_result = false,
-          },
-          {
-            case_title = "no custom header value and header value is undefined",
-            header_name = "canaryHeader",
-            header_value = "",
-            request_header_name = "canaryHeader",
-            request_header_value = "foo",
-            expected_result = false,
-          },
-          {
-            case_title = "no custom header value and header name is undefined",
-            header_name = "canaryHeader",
-            header_value = "",
-            request_header_name = "foo",
-            request_header_value = "always",
-            expected_result = false,
-          },
-          -- with header value setting
-          {
-            case_title = "custom header value is set and header value is 'always'",
-            header_name = "canaryHeader",
-            header_value = "foo",
-            request_header_name = "canaryHeader",
-            request_header_value = "always",
-            expected_result = false,
-          },
-          {
-            case_title = "custom header value is set and header value match custom header value",
-            header_name = "canaryHeader",
-            header_value = "foo",
-            request_header_name = "canaryHeader",
-            request_header_value = "foo",
-            expected_result = true,
-          },
-          {
-            case_title = "custom header value is set and header name is undefined",
-            header_name = "canaryHeader",
-            header_value = "foo",
-            request_header_name = "bar",
-            request_header_value = "foo",
-            expected_result = false
-          },
-        }
+    -- Affinitized request prefers backend it is affinitized to.
+    describe("affinitized", function()
 
-        for _, test_pattern in pairs(test_patterns) do
-          mock_ngx({ var = {
-            ["http_" .. test_pattern.request_header_name] = test_pattern.request_header_value,
-            request_uri = "/"
-          }})
-          reset_balancer()
-          backend.trafficShapingPolicy.header = test_pattern.header_name
-          backend.trafficShapingPolicy.headerValue = test_pattern.header_value
-          balancer.sync_backend(backend)
-          assert.message("\nTest data pattern: " .. test_pattern.case_title)
-            .equal(test_pattern.expected_result, balancer.route_to_alternative_balancer(_balancer))
-          reset_ngx()
+      before_each(function()
+        mock_ngx({ var = { request_uri = "/", proxy_upstream_name = backend.name } })
+        balancer.sync_backend(backend)
+      end)
+
+      it("returns false if request is affinitized to primary backend", function()
+        _primaryBalancer.is_affinitized = function (_)
+          return true
         end
+
+        local alternativeBalancer = balancer.get_balancer_by_upstream_name(backend.name)
+
+        local primarySpy = spy.on(_primaryBalancer, "is_affinitized")
+        local alternativeSpy = spy.on(alternativeBalancer, "is_affinitized")
+
+        assert.is_false(balancer.route_to_alternative_balancer(_primaryBalancer))
+        assert.spy(_primaryBalancer.is_affinitized).was_called()
+        assert.spy(alternativeBalancer.is_affinitized).was_not_called()
       end)
+
+      it("returns true if request is affinitized to alternative backend", function()
+        _primaryBalancer.is_affinitized = function (_)
+          return false
+        end
+
+        local alternativeBalancer = balancer.get_balancer_by_upstream_name(backend.name)
+        alternativeBalancer.is_affinitized = function (_)
+          return true
+        end
+
+        local primarySpy = spy.on(_primaryBalancer, "is_affinitized")
+        local alternativeSpy = spy.on(alternativeBalancer, "is_affinitized")
+
+        assert.is_true(balancer.route_to_alternative_balancer(_primaryBalancer))
+        assert.spy(_primaryBalancer.is_affinitized).was_called()
+        assert.spy(alternativeBalancer.is_affinitized).was_called()
+      end)
+
     end)
   end)
 
@@ -432,10 +508,13 @@ describe("Balancer", function()
           },
         }
       }
-      mock_ngx({ var = { proxy_upstream_name = "access-router-production-web-80" }, ctx = { } })
-      ngx.shared.configuration_data:set("backends", cjson.encode(backends))
-      reset_balancer()
+
+      mock_ngx({ var = { proxy_upstream_name = "access-router-production-web-80" }, ctx = { } }, function()
+        ngx.shared.configuration_data:set("backends", cjson.encode(backends))
+      end)
+
       balancer.init_worker()
+
       assert.not_equal(balancer.get_balancer(), nil)
     end)
 
diff --git a/rootfs/etc/nginx/lua/test/util/split.lua b/rootfs/etc/nginx/lua/test/util/split.lua
deleted file mode 100644
index 3d3a6d7e9..000000000
--- a/rootfs/etc/nginx/lua/test/util/split.lua
+++ /dev/null
@@ -1,15 +0,0 @@
-local split = require("util.split")
-
-
-describe("split", function()
-  it("get_last_value", function()
-    for _, case in ipairs({
-        {"127.0.0.1:26157 : 127.0.0.1:26158", "127.0.0.1:26158"},
-        {"127.0.0.1:26157, 127.0.0.1:26158", "127.0.0.1:26158"},
-        {"127.0.0.1:26158", "127.0.0.1:26158"},
-    }) do
-      local last = split.get_last_value(case[1])
-      assert.equal(case[2], last)
-    end
-  end)
-end)
diff --git a/rootfs/etc/nginx/lua/test/util/split_test.lua b/rootfs/etc/nginx/lua/test/util/split_test.lua
new file mode 100644
index 000000000..d81a92c2d
--- /dev/null
+++ b/rootfs/etc/nginx/lua/test/util/split_test.lua
@@ -0,0 +1,57 @@
+local split = require("util.split")
+
+describe("split", function()
+
+  describe("get_last_value", function()
+    it("splits value of an upstream variable and returns last value", function()
+      for _, case in ipairs({{"127.0.0.1:26157 : 127.0.0.1:26158", "127.0.0.1:26158"},
+                             {"127.0.0.1:26157, 127.0.0.1:26158", "127.0.0.1:26158"},
+                             {"127.0.0.1:26158", "127.0.0.1:26158"}}) do
+        local last = split.get_last_value(case[1])
+        assert.equal(case[2], last)
+      end
+    end)
+  end)
+
+  describe("split_string", function()
+
+    it("returns empty array if input string is empty", function()
+      local splits, len = split.split_string("", ",")
+      assert.equal(0, len)
+      assert.is.truthy(splits)
+    end)
+
+    it("returns empty array if input string is nil", function()
+      local splits, len = split.split_string(nil, ",")
+      assert.equal(0, len)
+      assert.is.truthy(splits)
+    end)
+
+    it("returns empty array if delimiter is empty", function()
+      local splits, len = split.split_string("1,2", "")
+      assert.equal(0, len)
+      assert.is.truthy(splits)
+    end)
+
+    it("returns empty array delimiter is nil", function()
+      local splits, len = split.split_string("1,2", nil)
+      assert.equal(0, len)
+      assert.is.truthy(splits)
+    end)
+
+    it("returns array of 1 value if input string is not a list", function()
+      local splits, len = split.split_string("123", ",")
+      assert.equal(1, len)
+      assert.equal("123", splits[1])
+    end)
+
+    it("returns array of values extracted from the input string", function()
+      local splits, len = split.split_string("1,2,3", ",")
+      assert.equal(3, len)
+      assert.equal("1", splits[1])
+      assert.equal("2", splits[2])
+      assert.equal("3", splits[3])
+    end)
+
+  end)
+end)
diff --git a/test/e2e/annotations/backendprotocol.go b/test/e2e/annotations/backendprotocol.go
index db7e50908..a215cbe83 100644
--- a/test/e2e/annotations/backendprotocol.go
+++ b/test/e2e/annotations/backendprotocol.go
@@ -46,6 +46,21 @@ var _ = framework.DescribeAnnotation("backend-protocol", func() {
 			})
 	})
 
+	ginkgo.It("should set backend protocol to $scheme:// and use proxy_pass", func() {
+		host := "backendprotocol.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/backend-protocol": "AUTO_HTTP",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.WaitForNginxServer(host,
+			func(server string) bool {
+				return strings.Contains(server, "proxy_pass $scheme://upstream_balancer;")
+			})
+	})
+
 	ginkgo.It("should set backend protocol to grpc:// and use grpc_pass", func() {
 		host := "backendprotocol.foo.com"
 		annotations := map[string]string{
diff --git a/test/e2e/annotations/clientbodybuffersize.go b/test/e2e/annotations/clientbodybuffersize.go
index 4091bdad1..7a4ce977f 100644
--- a/test/e2e/annotations/clientbodybuffersize.go
+++ b/test/e2e/annotations/clientbodybuffersize.go
@@ -18,10 +18,10 @@ package annotations
 
 import (
 	"fmt"
+	"net/http"
 	"strings"
 
 	"github.com/onsi/ginkgo"
-
 	"k8s.io/ingress-nginx/test/e2e/framework"
 )
 
@@ -46,6 +46,12 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 
 	ginkgo.It("should set client_body_buffer_size to 1K", func() {
@@ -62,6 +68,12 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 
 	ginkgo.It("should set client_body_buffer_size to 1k", func() {
@@ -78,6 +90,12 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 
 	ginkgo.It("should set client_body_buffer_size to 1m", func() {
@@ -94,6 +112,12 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 
 	ginkgo.It("should set client_body_buffer_size to 1M", func() {
@@ -110,6 +134,12 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 
 	ginkgo.It("should not set client_body_buffer_size to invalid 1b", func() {
@@ -126,5 +156,11 @@ var _ = framework.DescribeAnnotation("client-body-buffer-size", func() {
 			func(server string) bool {
 				return !strings.Contains(server, fmt.Sprintf("client_body_buffer_size %s;", clientBodyBufferSize))
 			})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 })
diff --git a/test/e2e/annotations/sslciphers.go b/test/e2e/annotations/sslciphers.go
index 0e2753b52..a619bf3bc 100644
--- a/test/e2e/annotations/sslciphers.go
+++ b/test/e2e/annotations/sslciphers.go
@@ -17,6 +17,7 @@ limitations under the License.
 package annotations
 
 import (
+	"net/http"
 	"strings"
 
 	"github.com/onsi/ginkgo"
@@ -46,5 +47,11 @@ var _ = framework.DescribeAnnotation("ssl-ciphers", func() {
 				return strings.Contains(server, "ssl_ciphers ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;") &&
 					strings.Contains(server, "ssl_prefer_server_ciphers off;")
 			})
+		f.HTTPTestClient().
+			GET("/something").
+			WithURL(f.GetURL(framework.HTTPS)).
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK)
 	})
 })