|
2 | 2 | template: main.html |
3 | 3 | --- |
4 | 4 |
|
5 | | -# A/B Experiments |
| 5 | +# A/B Testing with the Iter8 SDK |
6 | 6 |
|
7 | | -This tutorial describes an [A/B testing](../../user-guide/topics/ab_testing.md) experiment for a backend component. |
| 7 | +This tutorial describes how to do A/B testing of a backend component using the [Iter8 SDK](../../user-guide/topics/ab_testing.md). |
8 | 8 |
|
9 | 9 |  |
10 | 10 |
|
11 | 11 | *** |
12 | 12 |
|
13 | 13 | ???+ warning "Before you begin" |
14 | 14 | 1. Try [your first experiment](../../getting-started/your-first-experiment.md). Understand the main [concepts](../../getting-started/concepts.md) behind Iter8 experiments. |
| 15 | + 2. Have Grafana available. For example, Grafana can be installed on your cluster as follows: |
| 16 | + ```shell |
| 17 | + kubectl create deploy grafana --image=grafana/grafana |
| 18 | + kubectl expose deploy grafana --port=3000 |
| 19 | + ``` |
15 | 20 |
|
16 | | -## Launch Iter8 A/B/n service |
17 | | - |
18 | | -Deploy the Iter8 A/B/n service. When deploying the service, specify which Kubernetes resource types to watch for each application. To watch for versions of the *backend* application in the *default* namespace, configure the service to watch for Kubernetes service and deployment resources: |
19 | | - |
20 | | -```shell |
21 | | -helm install --repo https://iter8-tools.github.io/iter8 iter8-abn abn \ |
22 | | ---set "apps.default.backend.resources={service,deployment}" |
23 | | -``` |
| 21 | +## Launch the Iter8 A/B/n service |
24 | 22 |
|
25 | | -??? warn "Assumptions" |
26 | | - To simplify specification, Iter8 assumes certain conventions: |
27 | | - |
28 | | - - The baseline track identifier is the application name |
29 | | - - Track identifiers associated with candidate versions are of the form `<application_name>-candidate-<index>` |
30 | | - - All resource objects for all versions are deployed in the same namespace |
31 | | - - There is only 1 resource object of a given type in each version |
32 | | - - The name of each object in the version associated with the baseline track is the application name |
33 | | - - The name of each object in the version associated with a candidate track is of the form `<application_name>-candidate-<index>` where index is 1, 2, etc. |
| 23 | +Deploy the Iter8 A/B/n service using either `helm` or `kustomize`: |
34 | 24 |
|
| 25 | +--8<-- "docs/tutorials/installiter8controller.md" |
35 | 26 |
|
36 | 27 | ## Deploy the sample application |
37 | 28 |
|
38 | | -Deploy both the frontend and backend components of the application as described in each tab: |
| 29 | +A sample application using the Iter8 SDK is provided. Deploy both the frontend and backend components of this application as described in each tab: |
39 | 30 |
|
40 | 31 | === "frontend" |
41 | 32 | Install the frontend component using an implementation in the language of your choice: |
42 | 33 |
|
43 | 34 | === "node" |
44 | 35 | ```shell |
45 | | - kubectl create deployment frontend --image=iter8/abn-sample-frontend-node:0.13 |
| 36 | + kubectl create deployment frontend --image=iter8/abn-sample-frontend-node:0.15.0 |
46 | 37 | kubectl expose deployment frontend --name=frontend --port=8090 |
47 | 38 | ``` |
| 39 | + <!-- kubectl create deployment frontend --image=kalantar/frontend-node:20230717-1552 --> |
48 | 40 |
|
49 | 41 | === "Go" |
50 | 42 | ```shell |
51 | | - kubectl create deployment frontend --image=iter8/abn-sample-frontend-go:0.13 |
| 43 | + kubectl create deployment frontend --image=iter8/abn-sample-frontend-go:0.15.0 |
52 | 44 | kubectl expose deployment frontend --name=frontend --port=8090 |
53 | 45 | ``` |
| 46 | + <!-- kubectl create deployment frontend --image=kalantar/frontend-go:20230717-1339 --> |
54 | 47 |
|
55 | | - The frontend component is implemented to call *Lookup()* before each call to the backend component. The frontend componet uses the returned track identifier to route the request to a version of the backend component. |
| 48 | + The frontend component is implemented to call `Lookup()` before each call to the backend component. The frontend component uses the returned version number to route the request to the recommended version of the backend component. |
56 | 49 |
|
57 | 50 | === "backend" |
58 | | - Deploy version *v1* of the *backend* component, associating it with the track identifier *backend*. |
| 51 | + Deploy an initial version of the *backend* component: |
59 | 52 |
|
60 | 53 | ```shell |
61 | 54 | kubectl create deployment backend --image=iter8/abn-sample-backend:0.13-v1 |
62 | | - kubectl label deployment backend app.kubernetes.io/version=v1 |
| 55 | + kubectl label deployment backend iter8.tools/watch="true" |
63 | 56 |
|
64 | 57 | kubectl expose deployment backend --name=backend --port=8091 |
65 | 58 | ``` |
66 | 59 |
|
| 60 | +## Describe the Application |
| 61 | + |
| 62 | +In order to support `Lookup()`, Iter8 needs to know what the application component versions look like. A `ConfigMap` is used to describe the make up of possible versions: |
| 63 | + |
| 64 | +```shell |
| 65 | +cat <<EOF | kubectl apply -f - |
| 66 | +apiVersion: v1 |
| 67 | +kind: ConfigMap |
| 68 | +metadata: |
| 69 | + name: backend |
| 70 | + labels: |
| 71 | + app.kubernetes.io/managed-by: iter8 |
| 72 | + iter8.tools/kind: routemap |
| 73 | + iter8.tools/version: "v0.15" |
| 74 | +immutable: true |
| 75 | +data: |
| 76 | + strSpec: | |
| 77 | + versions: |
| 78 | + - resources: |
| 79 | + - gvrShort: svc |
| 80 | + name: backend |
| 81 | + namespace: default |
| 82 | + - gvrShort: deploy |
| 83 | + name: backend |
| 84 | + namespace: default |
| 85 | + - resources: |
| 86 | + - gvrShort: svc |
| 87 | + name: backend-candidate-1 |
| 88 | + namespace: default |
| 89 | + - gvrShort: deploy |
| 90 | + name: backend-candidate-1 |
| 91 | + namespace: default |
| 92 | +EOF |
| 93 | +``` |
| 94 | + |
| 95 | +In this definition, each version of the application is composed of a `Service` and a `Deployment`. In the primary version, both are named `backend`. In any candidate version they are named `backend-candidate-1`. Iter8 uses this definition to identify when any of the versions of the application are available. It can then respond appropriate to `Lookup()` requests. |
| 96 | + |
67 | 97 | ## Generate load |
68 | 98 |
|
69 | | -Generate load. In separate shells, port-forward requests to the frontend component and generate load for multiple users. A [script](https://raw.githubusercontent.com/iter8-tools/docs/main/samples/abn-sample/generate_load.sh) is provided to do this. To use it: |
| 99 | +In separate shells, port-forward requests to the frontend component and generate load for multiple users. A [script](https://raw.githubusercontent.com/iter8-tools/docs/main/samples/abn-sample/generate_load.sh) is provided to do this. To use it: |
70 | 100 | ```shell |
71 | 101 | kubectl port-forward service/frontend 8090:8090 |
72 | 102 | ``` |
73 | 103 | ```shell |
74 | | - curl -s https://raw.githubusercontent.com/iter8-tools/docs/main/samples/abn-sample/generate_load.sh | sh -s -- |
| 104 | + curl -s https://raw.githubusercontent.com/iter8-tools/docs/v0.15.0/samples/abn-sample/generate_load.sh | sh -s -- |
75 | 105 | ``` |
| 106 | + <!-- # source /Users/kalantar/projects/go.workspace/src/github.com/iter8-tools/docs/samples/abn-sample/generate_load.sh --> |
76 | 107 |
|
77 | 108 | ## Deploy a candidate version |
78 | 109 |
|
79 | | -Deploy version *v2* of the *backend* component, associating it with the track identifier *backend-candidate-1*. |
| 110 | +Deploy the candidate version of the *backend* component, naming it `backend-candidate-1`. |
80 | 111 |
|
81 | 112 | ```shell |
82 | 113 | kubectl create deployment backend-candidate-1 --image=iter8/abn-sample-backend:0.13-v2 |
83 | | -kubectl label deployment backend-candidate-1 app.kubernetes.io/version=v2 |
| 114 | +kubectl label deployment backend-candidate-1 iter8.tools/watch="true" |
84 | 115 |
|
85 | 116 | kubectl expose deployment backend-candidate-1 --name=backend-candidate-1 --port=8091 |
86 | 117 | ``` |
87 | 118 |
|
88 | | -Until the candidate version is ready; that is, until all expected resources are deployed and available, calls to *Lookup()* will return only the *backend* track identifier. |
89 | | -Once the candidate version is ready, *Lookup()* will return both track identifiers so that requests will be distributed between versions. |
| 119 | +Until the candidate version is ready; that is, until all expected resources are deployed and available, calls to `Lookup()` will return only the index 0; the existing version. |
| 120 | +Once the candidate version is ready, `Lookup()` will return both indices (0 and 1) so that requests can be distributed across versions. |
| 121 | + |
| 122 | +## Compare versions using Grafana |
90 | 123 |
|
91 | | -## Launch experiment |
| 124 | +Inspect the metrics using Grafana. If Grafana is deployed to your cluster, port-forward requests as follows: |
92 | 125 |
|
93 | 126 | ```shell |
94 | | -iter8 k launch \ |
95 | | ---set abnmetrics.application=default/backend \ |
96 | | ---set "tasks={abnmetrics}" \ |
97 | | ---set runner=cronjob \ |
98 | | ---set cronjobSchedule="*/1 * * * *" |
| 127 | +kubectl port-forward service/grafana 3000:3000 |
99 | 128 | ``` |
100 | 129 |
|
101 | | -??? note "About this experiment" |
102 | | - This experiment periodically (in this case, once a minute) reads the `abn` metrics associated with the *backend* application component in the *default* namespace. These metrics are written by the frontend component using the *WriteMetric()* interface as a part of processing user requests. |
103 | | - |
104 | | -## Inspect experiment report |
105 | | - |
106 | | -Inspect the metrics: |
| 130 | +Open Grafana in a browser: |
107 | 131 |
|
108 | 132 | ```shell |
109 | | -iter8 k report |
| 133 | +http://localhost:3000/ |
110 | 134 | ``` |
111 | 135 |
|
112 | | -??? note "Sample output from report" |
113 | | - ``` |
114 | | - Experiment summary: |
115 | | - ******************* |
116 | | - |
117 | | - Experiment completed: true |
118 | | - No task failures: true |
119 | | - Total number of tasks: 1 |
120 | | - Number of completed tasks: 1 |
121 | | - Number of completed loops: 3 |
122 | | - |
123 | | - Latest observed values for metrics: |
124 | | - *********************************** |
125 | | - |
126 | | - Metric | backend (v1) | backend-candidate-1 (v2) |
127 | | - ------- | ----- | ----- |
128 | | - abn/sample_metric/count | 35.00 | 28.00 |
129 | | - abn/sample_metric/max | 99.00 | 100.00 |
130 | | - abn/sample_metric/mean | 56.31 | 52.79 |
131 | | - abn/sample_metric/min | 0.00 | 1.00 |
132 | | - abn/sample_metric/stddev | 28.52 | 31.91 |
133 | | - ``` |
134 | | -The output allows you to compare the versions against each other and select a winner. Since the experiment runs periodically, the values in the report will change over time. |
| 136 | +[Add a JSON API data source](http://localhost:3000/connections/datasources/marcusolsson-json-datasource) `Iter8` with: |
135 | 137 |
|
136 | | -Once a winner is identified, the experiment can be terminated, the winner can be promoted, and the candidate version(s) can be deleted. |
| 138 | +- URL `http://iter8.default:8080/metrics` and |
| 139 | +- query string `application=default%2Fbackend` |
137 | 140 |
|
138 | | -To delete the experiment: |
| 141 | +[Create a new dashboard](http://localhost:3000/dashboards) by *import*. Do so by pasting the contents of this [JSON definition](https://gist.githubusercontent.com/Alan-Cha/aa4ba259cc4631aafe9b43500502c60f/raw/034249f24e2c524ee4e326e860c06149ae7b2677/gistfile1.txt) into the box and *load* it. Associate it with the JSON API data source defined above. |
139 | 142 |
|
140 | | -```shell |
141 | | -iter8 k delete |
142 | | -``` |
| 143 | +The Iter8 dashboard allows you to compare the behavior of the two versions of the backend component against each other and select a winner. Since user requests are being sent by the load generation script, the values in the report may change over time. The Iter8 dashboard may look like the following: |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | +Once a winner is identified, the winner can be promoted, and the candidate version deleted. |
143 | 148 |
|
144 | 149 | ## Promote candidate version |
145 | 150 |
|
146 | | -Delete the candidate version: |
| 151 | +To promote the candidate version (`backend-candidate-1`), first update the primary version, `backend`, using the new image. You can also overwrite any metadata describing the version. |
147 | 152 |
|
148 | 153 | ```shell |
149 | | -kubectl delete deployment backend-candidate-1 |
150 | | -kubectl delete service backend-candidate-1 |
| 154 | +kubectl set image deployment/backend abn-sample-backend=iter8/abn-sample-backend:0.13-v2 |
151 | 155 | ``` |
152 | 156 |
|
153 | | -Update the version associated with the baseline track identifier *backend*: |
| 157 | +Finally, delete the candidate version: |
154 | 158 |
|
155 | 159 | ```shell |
156 | | -kubectl set image deployment/backend abn-sample-backend=iter8/abn-sample-backend:0.13-v2 |
157 | | -kubectl label --overwrite deployment/backend app.kubernetes.io/version=v2 |
| 160 | +kubectl delete svc/backend-candidate-1 deploy/backend-candidate-1 |
158 | 161 | ``` |
159 | 162 |
|
| 163 | +Calls to `Lookup()` will now recommend that all traffic be sent to the primary version `backend` (currently serving the promoted version of the code). |
| 164 | + |
160 | 165 | ## Cleanup |
161 | 166 |
|
162 | 167 | ### Delete sample application |
163 | 168 |
|
164 | 169 | ```shell |
165 | 170 | kubectl delete \ |
166 | | -deploy/frontend deploy/backend deploy/backend-candidate-1 \ |
167 | | -service/frontend service/backend service/backend-candidate-1 |
| 171 | +svc/frontend deploy/frontend \ |
| 172 | +svc/backend deploy/backend \ |
| 173 | +svc/backend-candidate-1 deploy/backend-candidate-1 |
168 | 174 | ``` |
169 | 175 |
|
170 | | -### Uninstall the A/B/n service |
| 176 | +### Delete the application description |
171 | 177 |
|
172 | 178 | ```shell |
173 | | -helm delete iter8-abn |
| 179 | +kubectl delete cm/backend |
174 | 180 | ``` |
| 181 | + |
| 182 | +### Uninstall the A/B/n service |
| 183 | + |
| 184 | +--8<-- "docs/tutorials/deleteiter8controller.md" |
0 commit comments