Scenario: Hybrid (A/B + SLOs) testing and progressive traffic shift
Hybrid (A/B + SLOs) testing enables you to combine A/B or A/B/n testing with a reward metric on the one hand with SLO validation using objectives on the other. Among the versions that satisfy objectives, the version which performs best in terms of the reward metric is the winner. In this tutorial, you will:
Perform hybrid (A/B + SLOs) testing.
Specify user-engagement as the reward metric. This metric will be mocked by Iter8 in this tutorial.
Specify latency and error-rate based objectives; data for these metrics will be provided by Prometheus.
Combine hybrid (A/B + SLOs) testing with progressive traffic shift. Iter8 will progressively shift traffic towards the winner and promote it at the end as depicted below.
Platform setup
Follow these steps to install Iter8 and Istio in your K8s cluster.
Generate requests to your app using Fortio as follows.
# URL_VALUE is the URL of the `bookinfo` applicationURL_VALUE="http://$(kubectl -n istio-system get svc istio-ingressgateway -o jsonpath='{.spec.clusterIP}'):80/productpage"
sed "s+URL_VALUE+${URL_VALUE}+g"$ITER8/samples/istio/quickstart/fortio.yaml | kubectl apply -f -
Iter8 introduces a Kubernetes CRD called Metric that makes it easy to use metrics from RESTful metric providers like Prometheus, New Relic, Sysdig and Elastic during experiments.
Define the Iter8 metrics used in this experiment as follows. For the purpose of this tutorial, you will mock the user-engagement metric. The latency and error metrics will be provided by Prometheus.
apiVersion:v1kind:Namespacemetadata:labels:creator:iter8stack:istioname:iter8-istio---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:name:user-engagementnamespace:iter8-istiospec:mock:-name:productpage-v1level:15.0-name:productpage-v2level:20.0---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:labels:creator:iter8name:error-countnamespace:iter8-istiospec:description:Number of error responsesjqExpression:.data.result[0].value[1] | tonumberparams:-name:queryvalue:|sum(increase(istio_requests_total{response_code=~'5..',reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0)provider:prometheustype:CounterurlTemplate:http://prometheus-operated.iter8-system:9090/api/v1/query---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:labels:creator:iter8name:error-ratenamespace:iter8-istiospec:description:Fraction of requests with error responsesjqExpression:.data.result[0].value[1] | tonumberparams:-name:queryvalue:|(sum(increase(istio_requests_total{response_code=~'5..',reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0)) / (sum(increase(istio_requests_total{reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0))provider:prometheussampleSize:request-counttype:GaugeurlTemplate:http://prometheus-operated.iter8-system:9090/api/v1/query---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:labels:creator:iter8name:le500ms-latency-percentilenamespace:iter8-istiospec:description:Less than 500 ms latencyjqExpression:.data.result[0].value[1] | tonumberparams:-name:queryvalue:|(sum(increase(istio_request_duration_milliseconds_bucket{le='500',reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0)) / (sum(increase(istio_request_duration_milliseconds_bucket{le='+Inf',reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0))provider:prometheussampleSize:iter8-istio/request-counttype:GaugeurlTemplate:http://prometheus-operated.iter8-system:9090/api/v1/query---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:labels:creator:iter8name:mean-latencynamespace:iter8-istiospec:description:Mean latencyjqExpression:.data.result[0].value[1] | tonumberparams:-name:queryvalue:|(sum(increase(istio_request_duration_milliseconds_sum{reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0)) / (sum(increase(istio_requests_total{reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s])) or on() vector(0))provider:prometheussampleSize:request-counttype:Gaugeunits:millisecondsurlTemplate:http://prometheus-operated.iter8-system:9090/api/v1/query---apiVersion:iter8.tools/v2alpha2kind:Metricmetadata:labels:creator:iter8name:request-countnamespace:iter8-istiospec:description:Number of requestsjqExpression:.data.result[0].value[1] | tonumberparams:-name:queryvalue:|sum(increase(istio_requests_total{reporter='source',destination_workload='$name',destination_workload_namespace='$namespace'}[${elapsedTime}s]))provider:prometheustype:CounterurlTemplate:http://prometheus-operated.iter8-system:9090/api/v1/query
Metrics in your environment
You can define and use custom metrics from any database in Iter8 experiments.
For your application, replace the mocked metric used in this tutorial with any custom metric you wish to optimize. Documentation on defining custom metrics is here.
Iter8 defines a custom K8s resource called Experiment that automates a variety of release engineering and experimentation strategies for K8s applications and ML models. Launch the Hybrid (A/B + SLOs) testing & progressive traffic shift experiment as follows.
apiVersion:iter8.tools/v2alpha2kind:Experimentmetadata:name:quickstart-expspec:# target identifies the service under experimentation using its fully qualified nametarget:bookinfo-iter8/productpagestrategy:# this experiment will perform an A/B testtestingPattern:A/B# this experiment will progressively shift traffic to the winning versiondeploymentPattern:Progressiveactions:# when the experiment completes, promote the winning version using kubectl applyfinish:-if:CandidateWon()run:kubectl -n bookinfo-iter8 apply -f https://raw.githubusercontent.com/iter8-tools/iter8/master/samples/istio/quickstart/vs-for-v2.yaml-if:not CandidateWon()run:kubectl -n bookinfo-iter8 apply -f https://raw.githubusercontent.com/iter8-tools/iter8/master/samples/istio/quickstart/vs-for-v1.yamlcriteria:rewards:# (business) reward metric to optimize in this experiment-metric:iter8-istio/user-engagementpreferredDirection:Highobjectives:# used for validating versions-metric:iter8-istio/mean-latencyupperLimit:300-metric:iter8-istio/error-rateupperLimit:"0.01"requestCount:iter8-istio/request-countduration:# product of fields determines length of the experimentintervalSeconds:10iterationsPerLoop:5versionInfo:# information about the app versions used in this experimentbaseline:name:productpage-v1variables:-name:namespace# used by final action if this version is the winnervalue:bookinfo-iter8weightObjRef:apiVersion:networking.istio.io/v1beta1kind:VirtualServicenamespace:bookinfo-iter8name:bookinfofieldPath:.spec.http[0].route[0].weightcandidates:-name:productpage-v2variables:-name:namespace# used by final action if this version is the winnervalue:bookinfo-iter8weightObjRef:apiVersion:networking.istio.io/v1beta1kind:VirtualServicenamespace:bookinfo-iter8name:bookinfofieldPath:.spec.http[0].route[1].weight