Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support N nginx ingresses #2467

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9cdf72b
add additionalStableIngresses datatype
tperdue321 Jun 22, 2022
62e9534
add validation for additionalStableIngresses
tperdue321 Jun 22, 2022
44cf430
add rollout controller logic for additionalStableIngresses
tperdue321 Jun 22, 2022
56144c3
add nginx ingress logic for setting weight & fetching names for addit…
tperdue321 Jun 22, 2022
188e3df
add nginx testing for additionalStableIngresses
tperdue321 Jun 22, 2022
48989e0
add ingress & controller tests for additionalStableIngresses
Jun 23, 2022
90b899a
remove validation_references_test.go snafu
Jun 23, 2022
202304c
add ingress util testing for additionalStableIngresses
Jun 23, 2022
f973001
update test pattern
Dec 8, 2022
28e2a3b
go fmt
Dec 8, 2022
9ab2106
make codegen
Dec 8, 2022
3936581
improve testing, and fix validation error reporting for when there is…
Dec 13, 2022
e5a4753
fix bug
Dec 14, 2022
7952e6e
update logging
Dec 14, 2022
7a334dc
add validation for additionalStableIngresses
tperdue321 Jun 22, 2022
98e18bb
add nginx testing for additionalStableIngresses
tperdue321 Jun 22, 2022
f83381f
add ingress & controller tests for additionalStableIngresses
Jun 23, 2022
ed68ee8
make codegen
Jan 12, 2023
4584951
fix rebase issues
Jan 12, 2023
bc415c2
deduplicate tests
Jan 17, 2023
bc017bd
DRY up nginx_text.go
Jan 18, 2023
ea4cfcf
DRY more code
Jan 18, 2023
070360e
dry up code
Jan 18, 2023
adc7cf0
address sonarcloud code smells & duplicate code
Jan 25, 2023
d3cb726
change additionalStableIngresses to stableIngresses
Mar 9, 2023
5b50c2d
update validation
Mar 9, 2023
2f25533
reason only about stableIngress or StableIngresses, not both
Mar 11, 2023
3e6ba69
update rollout controller tests
Mar 12, 2023
ccc4a47
update validation_references tests
Mar 13, 2023
5c75ddf
fix nginx bug & tests
Mar 13, 2023
c30bcf9
fix ingress_test
Mar 13, 2023
82e2a1c
update specs
Mar 13, 2023
29297c5
Merge remote-tracking branch 'upstream/master' into feature-support-n…
Mar 13, 2023
4e91541
update nginx md
Mar 14, 2023
2bf5042
Merge remote-tracking branch 'upstream/master' into feature-support-n…
Mar 17, 2023
bdcd8f0
make file/test specific variables private
Mar 23, 2023
a57447a
Merge remote-tracking branch 'upstream/master' into feature-support-n…
Mar 24, 2023
7699f3a
make codegen
Mar 24, 2023
afd6d13
Merge remote-tracking branch 'upstream/master' into feature-support-n…
Mar 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/features/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,12 @@ spec:

# NGINX Ingress Controller routing configuration
nginx:
stableIngress: primary-ingress # required
# Either stableIngress or stableIngresses must be configured, but not both.
stableIngress: primary-ingress
stableIngresses:
- primary-ingress
- secondary-ingress
- tertiary-ingress
annotationPrefix: customingress.nginx.ingress.kubernetes.io # optional
additionalIngressAnnotations: # optional
canary-by-header: X-Canary
Expand Down
13 changes: 11 additions & 2 deletions docs/features/traffic-management/nginx.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ spec:
stableService: stable-service # required
trafficRouting:
nginx:
stableIngress: primary-ingress # required
# Either stableIngress or stableIngresses must be configured, but not both.
stableIngress: primary-ingress
stableIngresses:
- primary-ingress
- secondary-ingress
- tertiary-ingress
annotationPrefix: customingress.nginx.ingress.kubernetes.io # optional
additionalIngressAnnotations: # optional
canary-by-header: X-Canary
Expand All @@ -38,7 +43,11 @@ The controller routes traffic to the canary Service by creating a second Ingress
Since the Nginx Ingress controller allows users to configure the annotation prefix used by the Ingress controller, Rollouts can specify the optional `annotationPrefix` field. The canary Ingress uses that prefix instead of the default `nginx.ingress.kubernetes.io` if the field set.


## Using Argo Rollouts with multiple NGINX ingress controllers
## Using Argo Rollouts with multiple NGINX ingress controllers per service
Starting with v1.5, argo rollouts supports multiple Nginx ingress controllers pointing at one service with canary deployments. If only one ingress controller is needed, utilize the existing key `stableIngress`. If multiple ingress controllers are needed (e.g., separating internal vs external traffic), use the key `stableIngresses` instead. It takes an array of string values that are the names of the ingress controllers. Canary steps are applied identically across all ingress controllers.


## Using Argo Rollouts with custom NGINX ingress controller names
As a default, the Argo Rollouts controller only operates on ingresses with the `kubernetes.io/ingress.class` annotation or `spec.ingressClassName` set to `nginx`. A user can configure the controller to operate on Ingresses with different class name by specifying the `--nginx-ingress-classes` flag. A user can list the `--nginx-ingress-classes` flag multiple times if the Argo Rollouts controller should operate on multiple values. This solves the case where a cluster has multiple Ingress controllers operating on different class values.

If the user would like the controller to operate on any Ingress without the `kubernetes.io/ingress.class` annotation or `spec.ingressClassName`, a user should add the following `--nginx-ingress-classes ''`.
243 changes: 189 additions & 54 deletions ingress/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ingress

import (
"context"
"fmt"
"sync"
"testing"
"time"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
kubeinformers "k8s.io/client-go/informers"
k8sfake "k8s.io/client-go/kubernetes/fake"
Expand All @@ -24,6 +26,15 @@ import (
"k8s.io/client-go/tools/cache"
)

const stableService string = "test-stable-service"
const additionalIngress string = "test-stable-ingress-additional"
const stableIngress string = "test-stable-ingress"

func testString(val string) string {
return fmt.Sprintf("default/%s", val)

}

func newNginxIngress(name string, port int, serviceName string) *extensionsv1beta1.Ingress {
class := "nginx"
return &extensionsv1beta1.Ingress{
Expand Down Expand Up @@ -87,15 +98,29 @@ func newNginxIngressWithAnnotation(name string, port int, serviceName string) *e
}
}

func newFakeIngressControllerMultiIngress(t *testing.T, ing []*extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) {
return underlyingControllerBuilder(t, ing, rollout)
}

func newFakeIngressController(t *testing.T, ing *extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) {
return underlyingControllerBuilder(t, []*extensionsv1beta1.Ingress{ing}, rollout)
}

func underlyingControllerBuilder(t *testing.T, ing []*extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) {
t.Helper()
client := fake.NewSimpleClientset()
if rollout != nil {
client = fake.NewSimpleClientset(rollout)
}
kubeclient := k8sfake.NewSimpleClientset()
if ing != nil {
kubeclient = k8sfake.NewSimpleClientset(ing)
var x []runtime.Object
for _, i := range ing {
if i != nil {
x = append(x, i)
}
}
kubeclient = k8sfake.NewSimpleClientset(x...)
}
i := informers.NewSharedInformerFactory(client, 0)
k8sI := kubeinformers.NewSharedInformerFactory(kubeclient, 0)
Expand Down Expand Up @@ -140,7 +165,11 @@ func newFakeIngressController(t *testing.T, ing *extensionsv1beta1.Ingress, roll
}

if ing != nil {
k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(ing)
for _, i := range ing {
if i != nil {
k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i)
}
}
}
if rollout != nil {
i.Argoproj().V1alpha1().Rollouts().Informer().GetIndexer().Add(rollout)
Expand All @@ -156,78 +185,184 @@ func TestSyncMissingIngress(t *testing.T) {
}

func TestSyncIngressNotReferencedByRollout(t *testing.T) {
ing := newNginxIngress("test-stable-ingress", 80, "test-stable-service")

ctrl, kubeclient, _ := newFakeIngressController(t, ing, nil)
tests := []struct {
ings []*extensionsv1beta1.Ingress
name string
keys []string
}{
{
[]*extensionsv1beta1.Ingress{
newNginxIngress(stableIngress, 80, stableService),
},
"Single Ingress",
[]string{
testString(stableIngress),
},
},
{
[]*extensionsv1beta1.Ingress{
newNginxIngress(stableIngress, 80, stableService),
newNginxIngress(additionalIngress, 80, stableService),
},
"Multi Ingress",
[]string{
testString(stableIngress),
testString(additionalIngress),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

err := ctrl.syncIngress(context.Background(), "default/test-stable-ingress")
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
ctrl, kubeclient, _ := newFakeIngressControllerMultiIngress(t, test.ings, nil)
for _, key := range test.keys {
err := ctrl.syncIngress(context.Background(), key)
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
}
})
}
}

func TestSyncIngressReferencedByRollout(t *testing.T) {
ing := newNginxIngress("test-stable-ingress", 80, "stable-service")

rollout := &v1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
Name: "rollout",
Namespace: metav1.NamespaceDefault,
tests := []struct {
ings []*extensionsv1beta1.Ingress
name string
keys []string
additionalIngressNames []string
}{
{
[]*extensionsv1beta1.Ingress{
newNginxIngress(stableIngress, 80, stableService),
},
"Single Ingress",
[]string{
testString(stableIngress),
},
[]string{},
},
Spec: v1alpha1.RolloutSpec{
Strategy: v1alpha1.RolloutStrategy{
Canary: &v1alpha1.CanaryStrategy{
StableService: "stable-service",
CanaryService: "canary-service",
TrafficRouting: &v1alpha1.RolloutTrafficRouting{
Nginx: &v1alpha1.NginxTrafficRouting{
StableIngress: "test-stable-ingress",
},
},
},
{
[]*extensionsv1beta1.Ingress{
newNginxIngress(stableIngress, 80, stableService),
newNginxIngress(additionalIngress, 80, stableService),
},
"Multi Ingress",
[]string{
testString(stableIngress),
testString(additionalIngress),
testString(additionalIngress),
},
[]string{additionalIngress},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(t, ing, rollout)
rollout := &v1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
Name: "rollout",
Namespace: metav1.NamespaceDefault,
},
Spec: v1alpha1.RolloutSpec{
Strategy: v1alpha1.RolloutStrategy{
Canary: &v1alpha1.CanaryStrategy{
StableService: stableService,
CanaryService: "canary-service",
TrafficRouting: &v1alpha1.RolloutTrafficRouting{
Nginx: &v1alpha1.NginxTrafficRouting{
StableIngress: stableIngress,
StableIngresses: test.additionalIngressNames,
},
},
},
},
},
}

err := ctrl.syncIngress(context.Background(), "default/test-stable-ingress")
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
assert.Equal(t, 1, enqueuedObjects["default/rollout"])
ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, test.ings, rollout)

for i, key := range test.keys {
err := ctrl.syncIngress(context.Background(), key)
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
assert.Equal(t, i+1, enqueuedObjects["default/rollout"])
}
})
}
}

func TestSkipIngressWithNoClass(t *testing.T) {
ing := newNginxIngressWithAnnotation("test-stable-ingress", 80, "stable-service")
ing.Annotations = nil
rollout := &v1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
Name: "rollout",
Namespace: metav1.NamespaceDefault,
tests := []struct {
ings []*extensionsv1beta1.Ingress
name string
keys []string
additionalIngressNames []string
}{
{
[]*extensionsv1beta1.Ingress{
newNginxIngressWithAnnotation(stableIngress, 80, stableService),
},
"Single Ingress",
[]string{
testString(stableIngress),
},
[]string{},
},
Spec: v1alpha1.RolloutSpec{
Strategy: v1alpha1.RolloutStrategy{
Canary: &v1alpha1.CanaryStrategy{
StableService: "stable-service",
CanaryService: "canary-service",
TrafficRouting: &v1alpha1.RolloutTrafficRouting{
Nginx: &v1alpha1.NginxTrafficRouting{
StableIngress: "test-stable-ingress",
},
},
},
{
[]*extensionsv1beta1.Ingress{
newNginxIngressWithAnnotation(stableIngress, 80, stableService),
newNginxIngressWithAnnotation(additionalIngress, 80, stableService),
},
"Multi Ingress",
[]string{
testString(stableIngress),
testString(additionalIngress),
},
[]string{stableIngress},
},
}

ctrl, kubeclient, enqueuedObjects := newFakeIngressController(t, ing, rollout)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, i := range test.ings {
i.Annotations = nil
}

err := ctrl.syncIngress(context.Background(), "default/test-stable-ingress")
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
assert.Len(t, enqueuedObjects, 0)
rollout := &v1alpha1.Rollout{
ObjectMeta: metav1.ObjectMeta{
Name: "rollout",
Namespace: metav1.NamespaceDefault,
},
Spec: v1alpha1.RolloutSpec{
Strategy: v1alpha1.RolloutStrategy{
Canary: &v1alpha1.CanaryStrategy{
StableService: stableService,
CanaryService: "canary-service",
TrafficRouting: &v1alpha1.RolloutTrafficRouting{
Nginx: &v1alpha1.NginxTrafficRouting{
StableIngress: stableIngress,
StableIngresses: test.additionalIngressNames,
},
},
},
},
},
}

ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, test.ings, rollout)

for _, key := range test.keys {
err := ctrl.syncIngress(context.Background(), key)
assert.NoError(t, err)
actions := kubeclient.Actions()
assert.Len(t, actions, 0)
assert.Len(t, enqueuedObjects, 0)
}
})
}
}

func TestRun(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions manifests/crds/rollout-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,10 @@ spec:
type: string
stableIngress:
type: string
required:
- stableIngress
stableIngresses:
items:
type: string
type: array
type: object
plugins:
type: object
Expand Down
6 changes: 4 additions & 2 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11919,8 +11919,10 @@ spec:
type: string
stableIngress:
type: string
required:
- stableIngress
stableIngresses:
items:
type: string
type: array
type: object
plugins:
type: object
Expand Down
Empty file modified manifests/namespace-install.yaml
100755 → 100644
Empty file.
7 changes: 7 additions & 0 deletions pkg/apiclient/rollout/rollout.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,13 @@
"type": "string"
},
"title": "+optional"
},
"stableIngresses": {
"type": "array",
"items": {
"type": "string"
},
"title": "StableIngresses refers to the names of `Ingress` resources in the same namespace as the `Rollout` in a multi ingress scenario\n+optional"
}
},
"title": "NginxTrafficRouting configuration for Nginx ingress controller to control traffic routing"
Expand Down
Loading