Vitess – How to upgrade Vitess Cluster?

Quite recently we have seen the release of a new Vitess version (v12.0.0) and this may lead to some questions – how to upgrade your Vitess installation when you use the Kubernetes and Vitess operator? In this blog post we would like to show the upgrade process of a simple Vitess cluster.

There are several elements that we have to keep in mind. First, Vitess operator itself has to be upgraded. This can be done quite easily if you use the GitHub repository with the operator. Single git pull should update the repository:

git branch remotes/origin/release-12.0

root@k8smaster:~/vitess# git pull
remote: Enumerating objects: 3256, done.
remote: Counting objects: 100% (3210/3210), done.
remote: Compressing objects: 100% (1013/1013), done.
remote: Total 3256 (delta 2206), reused 2995 (delta 2122), pack-reused 46
Receiving objects: 100% (3256/3256), 8.84 MiB | 1.94 MiB/s, done.
Resolving deltas: 100% (2207/2207), completed with 243 local objects.
From https://github.com/vitessio/vitess
   f085f12b1c..90852014be  main                       -> origin/main
 * [new branch]            frances/plotly-replace     -> origin/frances/plotly-replace
 * [new branch]            partitions                 -> origin/partitions
   62b8e97d68..7bc70f1c6a  release-12.0               -> origin/release-12.0
 * [new branch]            sarabee-tailwind-font-size -> origin/sarabee-tailwind-font-size
Updating f085f12b1c..90852014be

From this point we have to upgrade the operator within Kubernetes. We will just reapply the operator.yaml file:

root@k8smaster:~/vitess# kubectl apply -f examples/operator/operator.yaml
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/etcdlockservers.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitessbackups.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitessbackupstorages.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitesscells.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitessclusters.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitesskeyspaces.planetscale.com configured
customresourcedefinition.apiextensions.k8s.io/vitessshards.planetscale.com configured
serviceaccount/vitess-operator unchanged
role.rbac.authorization.k8s.io/vitess-operator unchanged
rolebinding.rbac.authorization.k8s.io/vitess-operator unchanged
Warning: scheduling.k8s.io/v1beta1 PriorityClass is deprecated in v1.14+, unavailable in v1.22+; use scheduling.k8s.io/v1 PriorityClass
priorityclass.scheduling.k8s.io/vitess configured
priorityclass.scheduling.k8s.io/vitess-operator-control-plane configured
deployment.apps/vitess-operator configured

From then we need to start working on the upgrade of the pods. Let’s double-check which version do we have running.

VTTablet:

root@k8snode1:~# docker exec -it 663fdeb6d7af /bin/bash
vitess@vitesstpcc-vttablet-zone1-3896337564-86d89914:/$ /vt/
bin/        config/     dist/       secrets/    socket/     vtdataroot/ web/
vitess@vitesstpcc-vttablet-zone1-3896337564-86d89914:/$ /vt/bin/
mysqlctl      mysqlctld     vtbackup      vtctlclient   vtctld        vtctldclient  vtgate        vtorc         vttablet      vtworker
vitess@vitesstpcc-vttablet-zone1-3896337564-86d89914:/$ /vt/bin/vttablet --version
ERROR: logging before flag.Parse: E1104 22:11:45.304104      36 syslogger.go:149] can't connect to syslog
Version: 12.0.0-SNAPSHOT (Git revision 912fb2b85a branch 'main') built on Tue Oct 12 04:17:46 UTC 2021 by vitess@buildkitsandbox using go1.17 linux/amd64

VTCtl:

root@k8snode2:~# docker exec -it 447ce05d13db /bin/bash
vitess@vitesstpcc-zone1-vtctld-f38ee748-7b75487874-flfql:/$ /vt/bin/vtctl
vtctlclient   vtctld        vtctldclient
vitess@vitesstpcc-zone1-vtctld-f38ee748-7b75487874-flfql:/$ /vt/bin/vtctld --version
ERROR: logging before flag.Parse: E1104 22:15:38.690147      27 syslogger.go:149] can't connect to syslog
Version: 12.0.0-SNAPSHOT (Git revision 912fb2b85a branch 'main') built on Tue Oct 12 04:17:46 UTC 2021 by vitess@buildkitsandbox using go1.17 linux/amd64

VTGate:

root@k8snode3:~# docker exec -it 856c1bf5f846 /bin/bash
vitess@vitesstpcc-zone1-vtgate-67d1e711-7b8556d549-b8jsw:/$ /vt/bin/vtgate --version
ERROR: logging before flag.Parse: E1104 22:14:41.725924      52 syslogger.go:149] can't connect to syslog
Version: 12.0.0-SNAPSHOT (Git revision 912fb2b85a branch 'main') built on Tue Oct 12 04:17:46 UTC 2021 by vitess@buildkitsandbox using go1.17 linux/amd64

The Vitess documentation says that the upgrade order should be as follows: vttablets then vtctld and finally vtgate.

The upgrade is quite simple. We can edit the VitessCluster definition and change the image for every element of the cluster:

apiVersion: planetscale.com/v2
kind: VitessCluster
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"planetscale.com/v2","kind":"VitessCluster","metadata":{"annotations":{},"name":"vitesstpcc","namespace":"default"},"spec":{"cells":[{"gateway":{"authentication":{"static":{"secret":{"key":"users.json","name":"example-cluster-config"}}},"replicas":1,"resources":{"limits":{"memory":"1Gi"},"requests":{"cpu":1,"memory":"1Gi"}}},"name":"zone1"}],"images":{"mysqld":{"mysql80Compatible":"vitess/lite:v12.0.0"},"mysqldExporter":"prom/mysqld-exporter:v0.11.0","vtbackup":"vitess/lite:mysql80","vtctld":"vitess/lite:mysql80","vtgate":"vitess/lite:mysql80","vttablet":"vitess/lite:mysql80"},"keyspaces":[{"name":"newsbtest","partitionings":[{"equal":{"parts":1,"shardTemplate":{"databaseInitScriptSecret":{"key":"init_db.sql","name":"example-cluster-config"},"replication":{"enforceSemiSync":false},"tabletPools":[{"cell":"zone1","dataVolumeClaimTemplate":{"accessModes":["ReadWriteOnce"],"resources":{"requests":{"storage":"100Gi"}}},"extraVolumeMounts":[{"mountPath":"/mnt","name":"backupvol"}],"extraVolumes":[{"name":"backupvol","persistentVolumeClaim":{"accessModes":["ReadWriteMany"],"claimName":"backupvol","resources":{"requests":{"storage":"100Gi"}},"volumeName":"backup"}}],"mysqld":{"configOverrides":"innodb_flush_log_at_trx_commit=2\ninnodb_buffer_pool_size=512M\n","resources":{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":1,"memory":"2Gi"}}},"replicas":3,"type":"replica","vttablet":{"extraFlags":{"backup_engine_implementation":"xtrabackup","backup_storage_implementation":"file","db_charset":"utf8mb4","file_backup_storage_root":"/mnt/backup","restore_from_backup":"true","xtrabackup_root_path":"/usr/bin","xtrabackup_stream_mode":"xbstream","xtrabackup_stripes":"8","xtrabackup_user":"root"},"resources":{"limits":{"cpu":1,"memory":"2Gi"},"requests":{"cpu":1,"memory":"2Gi"}}}}]}}}],"turndownPolicy":"Immediate"}],"updateStrategy":{"type":"Immediate"},"vitessDashboard":{"cells":["zone1"],"extraFlags":{"security_policy":"read-only"},"replicas":1,"resources":{"limits":{"memory":"128Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}}}}
  creationTimestamp: "2021-11-16T21:39:24Z"
  generation: 3
  name: vitesstpcc
  namespace: default
  resourceVersion: "8348046"
  uid: deb87a7f-b3d4-4638-a110-739f615a2b6a
spec:
  cells:
  - gateway:
      authentication:
        static:
          secret:
            key: users.json
            name: example-cluster-config
      replicas: 1
      resources:
        limits:
          memory: 1Gi
        requests:
          cpu: 1
          memory: 1Gi
    name: zone1
  images:
    mysqld:
      mysql80Compatible: vitess/lite:mysql80
    mysqldExporter: prom/mysqld-exporter:v0.11.0
    vtbackup: vitess/lite:mysql80
    vtctld: vitess/lite:mysql80
    vtgate: vitess/lite:mysql80
    vttablet: vitess/lite:mysql80
.
.
.

What we need is to change the image to v12.0.0 and watch. As mentioned in the documentation, we will start with vttablet and use vitess/lite:v12.0.0-mysql80 image:

root@k8smaster:~/vitesstests# kubectl edit VitessCluster
vitesscluster.planetscale.com/vitesstpcc edited

In the meantime, our application is issuing queries to vtgate:

.
.
.
[ 538s ] thds: 2 tps: 1.06 qps: 24.47 (r/w/o: 11.70/10.64/2.13) lat (ms,95%): 3706.08 err/s 0.00 reconn/s: 0.00
[ 539s ] thds: 2 tps: 1.00 qps: 3.99 (r/w/o: 0.00/2.00/2.00) lat (ms,95%): 2880.27 err/s 0.00 reconn/s: 0.00
[ 540s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 541s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 542s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 543s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 544s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 545s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 546s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
[ 547s ] thds: 2 tps: 0.00 qps: 0.00 (r/w/o: 0.00/0.00/0.00) lat (ms,95%): 0.00 err/s 0.00 reconn/s: 0.00
FATAL: mysql_drv_query() returned error 1105 (target: newsbtest.-.primary: no healthy tablet available for 'keyspace:"newsbtest" shard:"-" tablet_type:PRIMARY') for query 'UPDATE warehouse4
                  SET w_ytd = w_ytd + 3353
                WHERE w_id = 9'
FATAL: `thread_run' function failed: ./tpcc_run.lua:258: SQL error, errno = 1105, state = 'HY000': target: newsbtest.-.primary: no healthy tablet available for 'keyspace:"newsbtest" shard:"-" tablet_type:PRIMARY'
FATAL: mysql_drv_query() returned error 1105 (target: newsbtest.-.primary: no healthy tablet available for 'keyspace:"newsbtest" shard:"-" tablet_type:PRIMARY') for query 'UPDATE warehouse3
                  SET w_ytd = w_ytd + 992
                WHERE w_id = 8'
FATAL: `thread_run' function failed: ./tpcc_run.lua:258: SQL error, errno = 1105, state = 'HY000': target: newsbtest.-.primary: no healthy tablet available for 'keyspace:"newsbtest" shard:"-" tablet_type:PRIMARY'
root@vagrant:~/sysbench-tpcc# ./tpcc.lua --mysql-host=10.20.0.10 --mysql-port=15336 --mysql-user=sbtest --mysql-password=sbtest --mysql-db=newsbtest --time=30000 --threads=2 --report-interval=1 --tables=10 --scale=10 --db-driver=mysql run

This is because Vitess Operator performs a rolling restart and, to make sure downtime is minimized, it moves PRIMARY tablet from one vttablet to another:

root@k8smaster:~/vitesstests# vtctlclient listalltablets
zone1-1817996704 newsbtest - primary 10.244.3.235:15000 10.244.3.235:3306 [] 2021-11-18T12:27:19Z
zone1-3154448388 newsbtest - replica 10.244.1.6:15000 10.244.1.6:3306 [] <null>
zone1-3896337564 newsbtest - replica 10.244.2.195:15000 10.244.2.195:3306 [] <null>
root@k8smaster:~/vitesstests# vtctlclient listalltablets
zone1-1817996704 newsbtest - replica 10.244.3.236:15000 10.244.3.236:3306 [] <null>
zone1-3154448388 newsbtest - replica 10.244.1.7:15000 10.244.1.7:3306 [] <null>
zone1-3896337564 newsbtest - primary 10.244.2.195:15000 10.244.2.195:3306 [] 2021-11-18T13:03:00Z
root@k8smaster:~/vitesstests# vtctlclient listalltablets
zone1-1817996704 newsbtest - replica 10.244.3.236:15000 10.244.3.236:3306 [] <null>
zone1-3154448388 newsbtest - primary 10.244.1.7:15000 10.244.1.7:3306 [] 2021-11-18T13:06:18Z
zone1-3896337564 newsbtest - replica 10.244.2.195:15000 10.244.2.195:3306 [] <null>

In our case the downtime caused by failovers was 40 seconds in total – two breaks, 20 seconds each. This is not ideal but as for an automated, fire-and-forget approach, it is quite good. Rolling upgrade is not the only method you can use. You can use, for example, OnDelete update strategy where you have more control on when and in what order pods will be upgraded. Alternatively, if you have to minimize the downtime, you can go for two cluster approach with two Vitess clusters replicating off each other. In that case the switch will have to be done on the application or loadbalancer level, giving you even more control on how it should be done. What you have to keep in mind is that Vitess runs MySQL replication under the hood therefore whatever strategy you can apply to a typical MySQL replication setup (one source + several replicas) most likely can be used (albeit with some small changes) for Vitess.

While upgrading, Vitess is doing the rolling restart of the pods:

root@k8smaster:~/vitesstests# kubectl get pods
NAME                                                READY   STATUS      RESTARTS   AGE
recycler-for-pv14                                   0/1     Completed   0          39h
vitess-operator-7ccd86b994-ctj7c                    1/1     Running     1          13d
vitesstpcc-etcd-8f68363b-1                          1/1     Running     0          39h
vitesstpcc-etcd-8f68363b-2                          1/1     Running     0          39h
vitesstpcc-etcd-8f68363b-3                          1/1     Running     0          39h
vitesstpcc-vttablet-zone1-1817996704-58f13a5a       3/3     Running     0          3m43s
vitesstpcc-vttablet-zone1-3154448388-e058f0f6       3/3     Running     0          2m23s
vitesstpcc-vttablet-zone1-3896337564-86d89914       0/3     Init:0/2    0          3s
vitesstpcc-zone1-vtctld-f38ee748-7b75487874-nktdf   1/1     Running     2          39h
vitesstpcc-zone1-vtgate-67d1e711-7b8556d549-xgpcp   1/1     Running     2          39h

Once all is done, we can check the vttablet pods:

root@k8snode1:~# docker ps | grep zone1-1817996704
01fe04d4a6e7   e80442e91b90            "/bin/mysqld_exporte…"   15 minutes ago   Up 15 minutes             k8s_mysqld-exporter_vitesstpcc-vttablet-zone1-1817996704-58f13a5a_default_af92ae68-a2d3-4fcc-87b8-d3bc25a48440_0
7af9af334e66   37c0bee9615c            "/vt/bin/mysqlctld -…"   15 minutes ago   Up 15 minutes             k8s_mysqld_vitesstpcc-vttablet-zone1-1817996704-58f13a5a_default_af92ae68-a2d3-4fcc-87b8-d3bc25a48440_0
a4a630142a45   6099999075af            "/vt/bin/vttablet --…"   15 minutes ago   Up 15 minutes             k8s_vttablet_vitesstpcc-vttablet-zone1-1817996704-58f13a5a_default_af92ae68-a2d3-4fcc-87b8-d3bc25a48440_0
050fde47f88a   k8s.gcr.io/pause:3.2    "/pause"                 15 minutes ago   Up 15 minutes             k8s_POD_vitesstpcc-vttablet-zone1-1817996704-58f13a5a_default_af92ae68-a2d3-4fcc-87b8-d3bc25a48440_0
root@k8snode1:~# docker exec -it a4a630142a45 /bin/bash
vitess@vitesstpcc-vttablet-zone1-1817996704-58f13a5a:/$ /vt/bin/vttablet --version
ERROR: logging before flag.Parse: E1118 13:19:13.483469      21 syslogger.go:149] can't connect to syslog
Version: 12.0.0 (Git revision de06fcaa92 branch 'heads/v12.0.0') built on Tue Oct 26 10:59:18 UTC 2021 by vitess@buildkitsandbox using go1.17 linux/amd64

As you can see, we have upgraded to the Version: 12.0.0.

The rest of the process involves performing exactly the same steps for every other element of the cluster: vtctld, vtgate and vtbackup pods. In the end we will end up with upgraded pods across the whole cluster. You may want to edit the cluster yaml file to reflect the changes made by hand on the live cluster.

As you can see, the upgrade process is not very complex but we would always recommend testing it before performing it on the production setup. There are always potential gotchas that may show up due to particular configuration of an environment.