Cluster Discovery

Overview

VTAdmin manages to be stateless because it mostly proxies queries to VTGates and Vtctlds within Vitess clusters. It is able to do this through cluster discovery, the mechanism by which addresses for VTGates and Vtctlds are discovered.

Discovery is specified as a part of cluster configuration. Cluster configuration is passed as the command line argument --cluster to VTAdmin API like so:

  1. vtadmin \
  2. --addr ":14200" \
  3. --http-origin "http://localhost:14201" \
  4. --http-tablet-url-tmpl "http://{{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}" \
  5. --tracer "opentracing-jaeger" \
  6. --grpc-tracing \
  7. --http-tracing \
  8. --logtostderr \
  9. --alsologtostderr \
  10. --rbac \
  11. --rbac-config="./vtadmin/rbac.yaml" \
  12. --cluster "id=local,name=local,discovery=staticfile,discovery-staticfile-path=./vtadmin/discovery.json,tablet-fqdn-tmpl={{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}"

where, in this example, discovery=staticfile is specifying static file discovery.

VTAdmin API currently supports a few methods for discovery:

Static file discovery

With static file discovery, VTGate and Vtctld addresses are specified in a static file, whose path is provided as a parameter to the --cluster command line argument:

  1. --cluster "id=local,name=local,discovery=staticfile,discovery-staticfile-path=./vtadmin/discovery.json,tablet-fqdn-tmpl={{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}"

In this example, the file lives at `./vtadmin/discovery.json`, and might look like:

  1. {
  2. "vtctlds": [
  3. {
  4. "host": {
  5. "fqdn": "localhost:15000",
  6. "hostname": "localhost:15999"
  7. }
  8. }
  9. ],
  10. "vtgates": [
  11. {
  12. "host": {
  13. "fqdn": "localhost:15001",
  14. "hostname": "localhost:15991"
  15. }
  16. }
  17. ]
  18. }

where fqdn specifies the address of the component’s web UI, and hostname specifies the address of the component’s gRPC server.

Multiple clusters

To specify multiple clusters, repeat the --cluster flag:

  1. vtadmin \
  2. --addr ":14200" \
  3. --http-origin "http://localhost:14201" \
  4. --http-tablet-url-tmpl "http://{{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}" \
  5. --tracer "opentracing-jaeger" \
  6. --grpc-tracing \
  7. --http-tracing \
  8. --logtostderr \
  9. --alsologtostderr \
  10. --rbac \
  11. --rbac-config="./vtadmin/rbac.yaml" \
  12. --cluster "id=local,name=local,discovery=staticfile,discovery-staticfile-path=./vtadmin/discovery-local.json,tablet-fqdn-tmpl={{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}"
  13. --cluster "id=prod,name=prod,discovery=staticfile,discovery-staticfile-path=./vtadmin/discovery-prod.json,tablet-fqdn-tmpl={{ .Tablet.Hostname }}:15{{ .Tablet.Alias.Uid }}"

The above multi-cluster configuration would show up in VTAdmin Web as: Multiple clusters on the /clusters page in VTAdmin

Dynamic discovery

It is possible to discover clusters after VTAdmin API has been initialized through dynamic discovery. When using dynamic discovery, a cluster configuration is passed as either gRPC metadata or an HTTP header cookie.

A very basic cluster configuration may look like:

  1. {
  2. "id": "dynamic",
  3. "name": "my-dynamic-cluster",
  4. "discovery": "dynamic",
  5. "discovery-dynamic-discovery": "{\"vtctlds\": [ { \"host\": { \"fqdn\": \"localhost:15000\", \"hostname\": \"localhost:15999\" } } ], \"vtgates\": [ { \"host\": {\"hostname\": \"localhost:15991\" } } ] }"
  6. }

In order to use dynamic discovery, set command line argument --enable-dynamic-clusters=true. At this time, it is only possible to discover a single cluster with each request. We’re working on adding multicluster support to dynamic discovery.

A cluster configuration can be passed as an HTTP cookie named cluster along with HTTP requests.

When passing a cluster configuration as an HTTP header cookie, the cluster configuration must first be base64 encoded and then URL encoded. A cURL request with the above cluster configuration would look like:

  1. $ curl -X GET \
  2. http://localhost:14200/api/clusters \
  3. -H 'cookie: cluster=ewogICAgImlkIjogImR5bmFtaWMiLAogICAgIm5hbWUiOiAibXktZHluYW1pYy1jbHVzdGVyIiwKICAgICJkaXNjb3ZlcnkiOiAiZHluYW1pYyIsCiAgICAiZGlzY292ZXJ5LWR5bmFtaWMtZGlzY292ZXJ5IjogIntcInZ0Y3RsZHNcIjogWyB7IFwiaG9zdFwiOiB7IFwiZnFkblwiOiBcImxvY2FsaG9zdDoxNTAwMFwiLCBcImhvc3RuYW1lXCI6IFwibG9jYWxob3N0OjE1OTk5XCIgfSB9IF0sIFwidnRnYXRlc1wiOiBbIHsgXCJob3N0XCI6IHtcImhvc3RuYW1lXCI6IFwibG9jYWxob3N0OjE1OTkxXCIgfSB9IF0gfSIKfQ%3D%3D'
  4. {"result":{"clusters":[{"id":"dynamic","name":"my-dynamic-cluster"}]},"ok":true}

gRPC metadata

A cluster configuration can also be passed as gRPC metadata with the key cluster. The code snippet below does the following:

  1. Creates a gRPC connection to the VTAdmin client at address localhost:14200
  2. Creates an outgoing context and adds the cluster configuration as gRPC metadata
  3. Calls GetClusters with the created context and gRPC metadata
  1. package main
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "flag"
  6. "fmt"
  7. "google.golang.org/grpc"
  8. "google.golang.org/grpc/metadata"
  9. "vitess.io/vitess/go/cmd/vtctldclient/cli"
  10. "vitess.io/vitess/go/vt/log"
  11. vtadminpb "vitess.io/vitess/go/vt/proto/vtadmin"
  12. )
  13. func main() {
  14. addr := flag.String("addr", ":14200", "")
  15. flag.Parse()
  16. ctx, cancel := context.WithCancel(context.Background())
  17. defer cancel()
  18. cc, err := grpc.DialContext(ctx, *addr, grpc.WithInsecure())
  19. fatal(err)
  20. defer cc.Close()
  21. client := vtadminpb.NewVTAdminClient(cc)
  22. clusterJSON := `{
  23. "id": "dynamic",
  24. "name": "my-dynamic-cluster",
  25. "vtctlds": [
  26. {
  27. "host": {
  28. "fqdn": "localhost:15000",
  29. "hostname": "localhost:15999"
  30. }
  31. }
  32. ],
  33. "vtgates": [
  34. {
  35. "host": {
  36. "hostname": "localhost:15991"
  37. }
  38. }
  39. ]
  40. }
  41. `
  42. ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{
  43. "cluster": base64.StdEncoding.EncodeToString([]byte(clusterJSON)),
  44. }))
  45. resp, err := client.GetClusters(ctx, &vtadminpb.GetClustersRequest{})
  46. if err != nil {
  47. log.Fatal(err)
  48. }
  49. data, err := cli.MarshalJSON(resp)
  50. if err != nil {
  51. log.Fatal(err)
  52. }
  53. fmt.Printf("%s\n", data)
  54. }

The result of the above program is:

  1. $ ./bin/vtadminclient
  2. {
  3. "clusters": [
  4. {
  5. "id": "dynamic",
  6. "name": "my-dynamic-cluster"
  7. }
  8. ]
  9. }

A full gist example can be found here.