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

Add support for Istio VirtualService delegation #715

Merged
merged 12 commits into from
Oct 28, 2020
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ jobs:
- run: test/e2e-istio-dependencies.sh
- run: test/e2e-istio-tests.sh
- run: test/e2e-istio-tests-skip-analysis.sh
- run: test/e2e-kubernetes-cleanup.sh
- run: test/e2e-istio-dependencies.sh
- run: test/e2e-istio-tests-delegate.sh

e2e-gloo-testing:
machine: true
Expand Down
3 changes: 3 additions & 0 deletions artifacts/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ spec:
type: array
items:
type: string
isDelegation:
description: enable behaving as a delegate VirtualService
type: boolean
match:
description: URI match conditions
type: array
Expand Down
3 changes: 3 additions & 0 deletions charts/flagger/crds/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ spec:
type: array
items:
type: string
isDelegation:
description: enable behaving as a delegate VirtualService
type: boolean
match:
description: URI match conditions
type: array
Expand Down
3 changes: 3 additions & 0 deletions kustomize/base/flagger/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ spec:
type: array
items:
type: string
isDelegation:
description: enable behaving as a delegate VirtualService
type: boolean
match:
description: URI match conditions
type: array
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/flagger/v1beta1/canary.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ type CanaryService struct {
// +optional
Hosts []string `json:"hosts,omitempty"`

// IsDelegation behaves as a delegate virtual service
// if enabled, the pilot env `PILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE` must also be set to enabled.
// +optional
IsDelegation bool `json:"isDelegation,omitempty"`
kazukousen marked this conversation as resolved.
Show resolved Hide resolved

// TrafficPolicy attached to the generated Istio destination rules
// +optional
TrafficPolicy *istiov1alpha3.TrafficPolicy `json:"trafficPolicy,omitempty"`
Expand Down
13 changes: 13 additions & 0 deletions pkg/router/istio.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ func (ir *IstioRouter) reconcileDestinationRule(canary *flaggerv1.Canary, name s
func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
apexName, primaryName, canaryName := canary.GetServiceNames()

if canary.Spec.Service.IsDelegation {
if len(canary.Spec.Service.Hosts) > 0 || len(canary.Spec.Service.Gateways) > 0 {
// delegate VirtualService cannot have hosts and gateways.
return fmt.Errorf("VirtualService %s.%s cannot have hosts and gateways", apexName, canary.Namespace)
kazukousen marked this conversation as resolved.
Show resolved Hide resolved
}
}

// set hosts and add the ClusterIP service host if it doesn't exists
hosts := canary.Spec.Service.Hosts
var hasServiceHost bool
Expand Down Expand Up @@ -132,6 +139,12 @@ func (ir *IstioRouter) reconcileVirtualService(canary *flaggerv1.Canary) error {
makeDestination(canary, canaryName, 0),
}

if canary.Spec.Service.IsDelegation {
// delegate VirtualService requires the hosts and gateway empty.
hosts = []string{}
gateways = []string{}
}

newSpec := istiov1alpha3.VirtualServiceSpec{
Hosts: hosts,
Gateways: gateways,
Expand Down
47 changes: 47 additions & 0 deletions pkg/router/istio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,53 @@ func TestIstioRouter_GatewayPort(t *testing.T) {
assert.Equal(t, uint32(mocks.canary.Spec.Service.Port), port)
}

func TestIstioRouter_Delegate(t *testing.T) {
t.Run("ok", func(t *testing.T) {
mocks := newFixture(nil)
mocks.canary.Spec.Service.Hosts = []string{}
mocks.canary.Spec.Service.Gateways = []string{}
mocks.canary.Spec.Service.IsDelegation = true

router := &IstioRouter{
logger: mocks.logger,
flaggerClient: mocks.flaggerClient,
istioClient: mocks.meshClient,
kubeClient: mocks.kubeClient,
}

err := router.Reconcile(mocks.canary)
require.NoError(t, err)

vs, err := mocks.meshClient.NetworkingV1alpha3().VirtualServices("default").Get(context.TODO(), "podinfo", metav1.GetOptions{})
require.NoError(t, err)

assert.Equal(t, 0, len(vs.Spec.Hosts))
assert.Equal(t, 0, len(vs.Spec.Gateways))
})

t.Run("invalid", func(t *testing.T) {
mocks := newFixture(nil)
if len(mocks.canary.Spec.Service.Gateways) == 0 {
// in this case, the gateways or hosts should not be not empty because it requires to cause an error.
mocks.canary.Spec.Service.Gateways = []string{
"public-gateway.istio",
"mesh",
}
}
mocks.canary.Spec.Service.IsDelegation = true

router := &IstioRouter{
logger: mocks.logger,
flaggerClient: mocks.flaggerClient,
istioClient: mocks.meshClient,
kubeClient: mocks.kubeClient,
}

err := router.Reconcile(mocks.canary)
require.Error(t, err)
})
}

func TestIstioRouter_Finalize(t *testing.T) {
mocks := newFixture(nil)
router := &IstioRouter{
Expand Down
150 changes: 150 additions & 0 deletions test/e2e-istio-tests-delegate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/usr/bin/env bash

# This script runs e2e tests for when the canary delegation is enabled
# Prerequisites: Kubernetes Kind and Istio

set -o errexit

echo '>>> Set pilot env to enable virtual service delegate'
kubectl -n istio-system set env deploy istiod PILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE=true
kubectl -n istio-system rollout status deploy istiod

echo '>>> Initialising Gateway'
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: istio-ingressgateway
namespace: istio-system
spec:
selector:
app: istio-ingressgateway
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
EOF

echo '>>> Initialising root virtual service'
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: root-vs
namespace: test
spec:
gateways:
- istio-system/istio-ingressgateway
hosts:
- "*"
http:
- match:
- uri:
prefix: "/podinfo"
rewrite:
uri: "/"
delegate:
name: podinfo
namespace: test
EOF

echo '>>> Initialising canary for delegate'
cat <<EOF | kubectl apply -f -
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
progressDeadlineSeconds: 60
service:
port: 80
targetPort: 9898
portDiscovery: true
isDelegation: true
skipAnalysis: true
analysis:
interval: 15s
threshold: 15
maxWeight: 30
stepWeight: 10
webhooks:
- name: load-test
url: http://flagger-loadtester.test/
timeout: 5s
metadata:
type: cmd
cmd: "hey -z 10m -q 10 -c 2 http://istio-ingressgateway.istio-system/podinfo"
logCmdOutput: "true"
EOF

echo '>>> Waiting for primary to be ready'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test get canary/podinfo | grep 'Initialized' && ok=true || ok=false
sleep 5
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n istio-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done

echo '✔ Canary initialization test passed'

echo '>>> Triggering canary deployment'
kubectl -n test set image deployment/podinfo podinfod=stefanprodan/podinfo:3.1.1

echo '>>> Waiting for canary promotion'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test describe deployment/podinfo-primary | grep '3.1.1' && ok=true || ok=false
sleep 10
kubectl -n istio-system logs deployment/flagger --tail 1
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n test describe deployment/podinfo
kubectl -n test describe deployment/podinfo-primary
kubectl -n istio-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done

echo '>>> Waiting for canary finalization'
retries=50
count=0
ok=false
until ${ok}; do
kubectl -n test get canary/podinfo | grep 'Succeeded' && ok=true || ok=false
sleep 5
count=$(($count + 1))
if [[ ${count} -eq ${retries} ]]; then
kubectl -n istio-system logs deployment/flagger
echo "No more retries left"
exit 1
fi
done

echo '>>> Set pilot env to disable virtual service delegate'
kubectl -n istio-system set env deploy istiod PILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE=false
kubectl -n istio-system rollout status deploy istiod

echo '✔ Canary promotion test passed'

if [[ "$1" = "canary" ]]; then
exit 0
fi
4 changes: 3 additions & 1 deletion test/e2e-istio.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ cd ${REPO_ROOT}/bin && \
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=${ISTIO_VER} sh -

echo ">>> Installing Istio ${ISTIO_VER}"
${REPO_ROOT}/bin/istio-${ISTIO_VER}/bin/istioctl manifest apply --set profile=default
${REPO_ROOT}/bin/istio-${ISTIO_VER}/bin/istioctl manifest apply --set profile=default \
--set values.pilot.resources.requests.cpu=100m \
--set values.pilot.resources.requests.memory=100Mi

kubectl -n istio-system rollout status deployment/prometheus

Expand Down