Repository: kumahq/kuma
Pull request #1412 (2021-01-12 18:10:56)

feat(kuma-dp) override bootstrap version
### Summary

This PR introduces an option to override Bootstrap Version on Kuma DP side.

The regular case is that Kuma CP decides which Bootstrap Version (and API Version) it will serve to the DP.

Envoy 1.17 technically drops V2 support, but you can still use it when you specify explicitly `--bootstrap-verison 2` as CLI args to Envoy.
Kuma DP did not know about Bootstrap version so far, so I added extra header that we return from the Kuma CP so Kuma DP can pass the version to Envoy.

Additionally, there are some cases when you would like to override Bootstrap Version for one Kuma DP (for example to V3, while keeping the default Bootstrap to V2 for all other DPs). Those use cases are
1) Testing. When testing, it's usefull to just switch back and forth between versions without restarting CP.
2) Migrations. When running on prod, you may want to check only one DP on V3 that everything is fine while keeping every other on V2.

### Documentation

- This can be included in upgrade guide as a tip when we will change V2 -> V3 by default.



Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6df4f06b1..917375243 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -222,8 +222,6 @@ jobs:
         keys:
         # prefer the exact match
         - go.mod-{{ .Branch }}-{{ checksum "go.sum" }}
-        # otherwise, fallback to the most recently generated cache on any branch
-        - go.mod-
     - run:
         name: "Install pre-requirements"
         # `unzip` is necessary to install `protoc`
@@ -258,10 +256,6 @@ jobs:
         keys:
         # prefer the exact match
         - api-go.mod-{{ .Branch }}-{{ checksum "api/go.sum" }}
-        # otherwise, fallback to the most recently generated cache on that branch
-        - api-go.mod-{{ .Branch }}-
-        # otherwise, fallback to the most recently generated cache on any branch
-        - api-go.mod-
     - run:
         name: "Download Go modules"
         command: |
@@ -291,8 +285,6 @@ jobs:
         keys:
         # prefer the exact match
         - go.mod-{{ .Branch }}-{{ checksum "go.sum" }}
-        # otherwise, fallback to the most recently generated cache on any branch
-        - go.mod-
     - run:
         name: "Install pre-requirements"
         # `unzip` is necessary to install `protoc`
@@ -328,10 +320,6 @@ jobs:
         keys:
         # prefer the exact match
         - vm-executor-go.mod-{{ .Branch }}-{{ checksum "go.sum" }}
-        # otherwise, fallback to the most recently generated cache on that branch
-        - vm-executor-go.mod-{{ .Branch }}-
-        # otherwise, fallback to the most recently generated cache on any branch
-        - vm-executor-go.mod-
     - run:
         name: "Download Go modules"
         command: |
@@ -364,8 +352,6 @@ jobs:
         keys:
         # prefer the exact match
         - go.mod-{{ .Branch }}-{{ checksum "go.sum" }}
-        # otherwise, fallback to the most recently generated cache on any branch
-        - go.mod-
     - run:
         name: Build all Kuma binaries (such as, kumactl, kuma-cp, kuma-dp, kuma-prometheus-sd)
         command: make build
@@ -524,8 +510,6 @@ jobs:
         keys:
         # prefer the exact match
         - go.mod-{{ .Branch }}-{{ checksum "go.sum" }}
-        # otherwise, fallback to the most recently generated cache on any branch
-        - go.mod-
     - run:
         name: "Install Docker client"
         command: |
diff --git a/README.md b/README.md
index ab38b49a4..f0438ed61 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@ Kuma has been built with universality and scalability in mind. Kubernetes and VM
 * **GUI**: Out of the box browser GUI to explore all the Service Meshes configured in the system.
 * **Tagging Selectors**: To apply sophisticated regional, cloud-specific and team-oriented policies.
 * **Platform-Agnostic**: Support for Kubernetes, VMs, and bare metal. Including hybrid deployments.
+* **Transparent Proxying**: Out of the box transparent proxying on Kubernetes, VMs and any other platform.
 * **Network Overlay**: Create a configurable Mesh overlay across different Kubernetes clusters and namespaces.
 
 Below you can find a snapshot of the GUI that ships with Kuma:
diff --git a/api/mesh/v1alpha1/traffic_route.pb.go b/api/mesh/v1alpha1/traffic_route.pb.go
index fc2703384..90d6756d6 100644
--- a/api/mesh/v1alpha1/traffic_route.pb.go
+++ b/api/mesh/v1alpha1/traffic_route.pb.go
@@ -160,6 +160,129 @@ func (x *TrafficRoute_Split) GetDestination() map[string]string {
 	return nil
 }
 
+// LoadBalancer defines the load balancing policy and configuration
+type TrafficRoute_LoadBalancer struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Types that are assignable to LbType:
+	//	*TrafficRoute_LoadBalancer_RoundRobin_
+	//	*TrafficRoute_LoadBalancer_LeastRequest_
+	//	*TrafficRoute_LoadBalancer_RingHash_
+	//	*TrafficRoute_LoadBalancer_Random_
+	//	*TrafficRoute_LoadBalancer_Maglev_
+	LbType isTrafficRoute_LoadBalancer_LbType `protobuf_oneof:"lb_type"`
+}
+
+func (x *TrafficRoute_LoadBalancer) Reset() {
+	*x = TrafficRoute_LoadBalancer{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1}
+}
+
+func (m *TrafficRoute_LoadBalancer) GetLbType() isTrafficRoute_LoadBalancer_LbType {
+	if m != nil {
+		return m.LbType
+	}
+	return nil
+}
+
+func (x *TrafficRoute_LoadBalancer) GetRoundRobin() *TrafficRoute_LoadBalancer_RoundRobin {
+	if x, ok := x.GetLbType().(*TrafficRoute_LoadBalancer_RoundRobin_); ok {
+		return x.RoundRobin
+	}
+	return nil
+}
+
+func (x *TrafficRoute_LoadBalancer) GetLeastRequest() *TrafficRoute_LoadBalancer_LeastRequest {
+	if x, ok := x.GetLbType().(*TrafficRoute_LoadBalancer_LeastRequest_); ok {
+		return x.LeastRequest
+	}
+	return nil
+}
+
+func (x *TrafficRoute_LoadBalancer) GetRingHash() *TrafficRoute_LoadBalancer_RingHash {
+	if x, ok := x.GetLbType().(*TrafficRoute_LoadBalancer_RingHash_); ok {
+		return x.RingHash
+	}
+	return nil
+}
+
+func (x *TrafficRoute_LoadBalancer) GetRandom() *TrafficRoute_LoadBalancer_Random {
+	if x, ok := x.GetLbType().(*TrafficRoute_LoadBalancer_Random_); ok {
+		return x.Random
+	}
+	return nil
+}
+
+func (x *TrafficRoute_LoadBalancer) GetMaglev() *TrafficRoute_LoadBalancer_Maglev {
+	if x, ok := x.GetLbType().(*TrafficRoute_LoadBalancer_Maglev_); ok {
+		return x.Maglev
+	}
+	return nil
+}
+
+type isTrafficRoute_LoadBalancer_LbType interface {
+	isTrafficRoute_LoadBalancer_LbType()
+}
+
+type TrafficRoute_LoadBalancer_RoundRobin_ struct {
+	RoundRobin *TrafficRoute_LoadBalancer_RoundRobin `protobuf:"bytes,1,opt,name=round_robin,json=roundRobin,proto3,oneof"`
+}
+
+type TrafficRoute_LoadBalancer_LeastRequest_ struct {
+	LeastRequest *TrafficRoute_LoadBalancer_LeastRequest `protobuf:"bytes,2,opt,name=least_request,json=leastRequest,proto3,oneof"`
+}
+
+type TrafficRoute_LoadBalancer_RingHash_ struct {
+	RingHash *TrafficRoute_LoadBalancer_RingHash `protobuf:"bytes,3,opt,name=ring_hash,json=ringHash,proto3,oneof"`
+}
+
+type TrafficRoute_LoadBalancer_Random_ struct {
+	Random *TrafficRoute_LoadBalancer_Random `protobuf:"bytes,4,opt,name=random,proto3,oneof"`
+}
+
+type TrafficRoute_LoadBalancer_Maglev_ struct {
+	Maglev *TrafficRoute_LoadBalancer_Maglev `protobuf:"bytes,5,opt,name=maglev,proto3,oneof"`
+}
+
+func (*TrafficRoute_LoadBalancer_RoundRobin_) isTrafficRoute_LoadBalancer_LbType() {}
+
+func (*TrafficRoute_LoadBalancer_LeastRequest_) isTrafficRoute_LoadBalancer_LbType() {}
+
+func (*TrafficRoute_LoadBalancer_RingHash_) isTrafficRoute_LoadBalancer_LbType() {}
+
+func (*TrafficRoute_LoadBalancer_Random_) isTrafficRoute_LoadBalancer_LbType() {}
+
+func (*TrafficRoute_LoadBalancer_Maglev_) isTrafficRoute_LoadBalancer_LbType() {}
+
 // Conf defines the destination configuration
 type TrafficRoute_Conf struct {
 	state         protoimpl.MessageState
@@ -167,13 +290,14 @@ type TrafficRoute_Conf struct {
 	unknownFields protoimpl.UnknownFields
 
 	// List of destinations with weights assigned to them.
-	Split []*TrafficRoute_Split `protobuf:"bytes,1,rep,name=split,proto3" json:"split,omitempty"`
+	Split        []*TrafficRoute_Split      `protobuf:"bytes,1,rep,name=split,proto3" json:"split,omitempty"`
+	LoadBalancer *TrafficRoute_LoadBalancer `protobuf:"bytes,2,opt,name=load_balancer,json=loadBalancer,proto3" json:"load_balancer,omitempty"`
 }
 
 func (x *TrafficRoute_Conf) Reset() {
 	*x = TrafficRoute_Conf{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[2]
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -186,7 +310,7 @@ func (x *TrafficRoute_Conf) String() string {
 func (*TrafficRoute_Conf) ProtoMessage() {}
 
 func (x *TrafficRoute_Conf) ProtoReflect() protoreflect.Message {
-	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[2]
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -199,7 +323,7 @@ func (x *TrafficRoute_Conf) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use TrafficRoute_Conf.ProtoReflect.Descriptor instead.
 func (*TrafficRoute_Conf) Descriptor() ([]byte, []int) {
-	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1}
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 2}
 }
 
 func (x *TrafficRoute_Conf) GetSplit() []*TrafficRoute_Split {
@@ -209,6 +333,251 @@ func (x *TrafficRoute_Conf) GetSplit() []*TrafficRoute_Split {
 	return nil
 }
 
+func (x *TrafficRoute_Conf) GetLoadBalancer() *TrafficRoute_LoadBalancer {
+	if x != nil {
+		return x.LoadBalancer
+	}
+	return nil
+}
+
+// RoundRobin is a simple policy in which each available upstream host is
+// selected in round robin order
+type TrafficRoute_LoadBalancer_RoundRobin struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *TrafficRoute_LoadBalancer_RoundRobin) Reset() {
+	*x = TrafficRoute_LoadBalancer_RoundRobin{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer_RoundRobin) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer_RoundRobin) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer_RoundRobin) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer_RoundRobin.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer_RoundRobin) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1, 0}
+}
+
+// LeastRequest uses different algorithms depending on whether hosts have the
+// same or different weights
+type TrafficRoute_LoadBalancer_LeastRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The number of random healthy hosts from which the host with the fewest
+	// active requests will be chosen. Defaults to 2 so that we perform
+	// two-choice selection if the field is not set.
+	ChoiceCount uint32 `protobuf:"varint,1,opt,name=choice_count,json=choiceCount,proto3" json:"choice_count,omitempty"`
+}
+
+func (x *TrafficRoute_LoadBalancer_LeastRequest) Reset() {
+	*x = TrafficRoute_LoadBalancer_LeastRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer_LeastRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer_LeastRequest) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer_LeastRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer_LeastRequest.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer_LeastRequest) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1, 1}
+}
+
+func (x *TrafficRoute_LoadBalancer_LeastRequest) GetChoiceCount() uint32 {
+	if x != nil {
+		return x.ChoiceCount
+	}
+	return 0
+}
+
+// RingHash implements consistent hashing to upstream hosts
+type TrafficRoute_LoadBalancer_RingHash struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The hash function used to hash hosts onto the ketama ring. The value
+	// defaults to 'XX_HASH'
+	HashFunction string `protobuf:"bytes,1,opt,name=hash_function,json=hashFunction,proto3" json:"hash_function,omitempty"`
+	// Minimum hash ring size
+	MinRingSize uint64 `protobuf:"varint,2,opt,name=min_ring_size,json=minRingSize,proto3" json:"min_ring_size,omitempty"`
+	// Maximum hash ring size.
+	MaxRingSize uint64 `protobuf:"varint,3,opt,name=max_ring_size,json=maxRingSize,proto3" json:"max_ring_size,omitempty"`
+}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) Reset() {
+	*x = TrafficRoute_LoadBalancer_RingHash{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer_RingHash) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer_RingHash.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer_RingHash) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1, 2}
+}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) GetHashFunction() string {
+	if x != nil {
+		return x.HashFunction
+	}
+	return ""
+}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) GetMinRingSize() uint64 {
+	if x != nil {
+		return x.MinRingSize
+	}
+	return 0
+}
+
+func (x *TrafficRoute_LoadBalancer_RingHash) GetMaxRingSize() uint64 {
+	if x != nil {
+		return x.MaxRingSize
+	}
+	return 0
+}
+
+// Random selects a random available host
+type TrafficRoute_LoadBalancer_Random struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *TrafficRoute_LoadBalancer_Random) Reset() {
+	*x = TrafficRoute_LoadBalancer_Random{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer_Random) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer_Random) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer_Random) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer_Random.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer_Random) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1, 3}
+}
+
+// Maglev implements consistent hashing to upstream hosts
+type TrafficRoute_LoadBalancer_Maglev struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *TrafficRoute_LoadBalancer_Maglev) Reset() {
+	*x = TrafficRoute_LoadBalancer_Maglev{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *TrafficRoute_LoadBalancer_Maglev) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TrafficRoute_LoadBalancer_Maglev) ProtoMessage() {}
+
+func (x *TrafficRoute_LoadBalancer_Maglev) ProtoReflect() protoreflect.Message {
+	mi := &file_mesh_v1alpha1_traffic_route_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use TrafficRoute_LoadBalancer_Maglev.ProtoReflect.Descriptor instead.
+func (*TrafficRoute_LoadBalancer_Maglev) Descriptor() ([]byte, []int) {
+	return file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP(), []int{0, 1, 4}
+}
+
 var File_mesh_v1alpha1_traffic_route_proto protoreflect.FileDescriptor
 
 var file_mesh_v1alpha1_traffic_route_proto_rawDesc = []byte{
@@ -218,8 +587,8 @@ var file_mesh_v1alpha1_traffic_route_proto_rawDesc = []byte{
 	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1c, 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x76, 0x31,
 	0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x2e,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f,
-	0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8d,
-	0x04, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12,
+	0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x83,
+	0x0a, 0x0a, 0x0c, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x12,
 	0x40, 0x0a, 0x07, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
 	0x32, 0x1c, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61,
 	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x42, 0x08,
@@ -246,16 +615,63 @@ var file_mesh_v1alpha1_traffic_route_proto_rawDesc = []byte{
 	0x0a, 0x10, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x74,
 	0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
 	0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x4e,
-	0x0a, 0x04, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x46, 0x0a, 0x05, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x18,
-	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73,
-	0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66,
-	0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x42, 0x08, 0xfa,
-	0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x05, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x42, 0x2a,
-	0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x6d,
-	0x61, 0x68, 0x71, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x65, 0x73,
-	0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x9e,
+	0x05, 0x0a, 0x0c, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x12,
+	0x5b, 0x0a, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x72, 0x6f, 0x62, 0x69, 0x6e, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68,
+	0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69,
+	0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e,
+	0x63, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x62, 0x69, 0x6e, 0x48, 0x00,
+	0x52, 0x0a, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x6f, 0x62, 0x69, 0x6e, 0x12, 0x61, 0x0a, 0x0d,
+	0x6c, 0x65, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e,
+	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63,
+	0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
+	0x65, 0x72, 0x2e, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48,
+	0x00, 0x52, 0x0c, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x55, 0x0a, 0x09, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x36, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65, 0x73, 0x68, 0x2e, 0x76,
+	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x52,
+	0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
+	0x72, 0x2e, 0x52, 0x69, 0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x48, 0x00, 0x52, 0x08, 0x72, 0x69,
+	0x6e, 0x67, 0x48, 0x61, 0x73, 0x68, 0x12, 0x4e, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65,
+	0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66,
+	0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c,
+	0x61, 0x6e, 0x63, 0x65, 0x72, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x48, 0x00, 0x52, 0x06,
+	0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x12, 0x4e, 0x0a, 0x06, 0x6d, 0x61, 0x67, 0x6c, 0x65, 0x76,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d, 0x65,
+	0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x66,
+	0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c,
+	0x61, 0x6e, 0x63, 0x65, 0x72, 0x2e, 0x4d, 0x61, 0x67, 0x6c, 0x65, 0x76, 0x48, 0x00, 0x52, 0x06,
+	0x6d, 0x61, 0x67, 0x6c, 0x65, 0x76, 0x1a, 0x0c, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52,
+	0x6f, 0x62, 0x69, 0x6e, 0x1a, 0x31, 0x0a, 0x0c, 0x4c, 0x65, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x63,
+	0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x68, 0x6f, 0x69,
+	0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x69, 0x6e, 0x67, 0x48,
+	0x61, 0x73, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x68, 0x61, 0x73, 0x68, 0x5f, 0x66, 0x75, 0x6e, 0x63,
+	0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x61, 0x73, 0x68,
+	0x46, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f,
+	0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0b, 0x6d, 0x69, 0x6e, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0d,
+	0x6d, 0x61, 0x78, 0x5f, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x52, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65,
+	0x1a, 0x08, 0x0a, 0x06, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x1a, 0x08, 0x0a, 0x06, 0x4d, 0x61,
+	0x67, 0x6c, 0x65, 0x76, 0x42, 0x09, 0x0a, 0x07, 0x6c, 0x62, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x1a,
+	0xa2, 0x01, 0x0a, 0x04, 0x43, 0x6f, 0x6e, 0x66, 0x12, 0x46, 0x0a, 0x05, 0x73, 0x70, 0x6c, 0x69,
+	0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d,
+	0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61,
+	0x66, 0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x42,
+	0x08, 0xfa, 0x42, 0x05, 0x92, 0x01, 0x02, 0x08, 0x01, 0x52, 0x05, 0x73, 0x70, 0x6c, 0x69, 0x74,
+	0x12, 0x52, 0x0a, 0x0d, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
+	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x6d,
+	0x65, 0x73, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x54, 0x72, 0x61,
+	0x66, 0x66, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x42, 0x61,
+	0x6c, 0x61, 0x6e, 0x63, 0x65, 0x72, 0x52, 0x0c, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x61, 0x6c, 0x61,
+	0x6e, 0x63, 0x65, 0x72, 0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x68, 0x71, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x2f, 0x61,
+	0x70, 0x69, 0x2f, 0x6d, 0x65, 0x73, 0x68, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -270,25 +686,37 @@ func file_mesh_v1alpha1_traffic_route_proto_rawDescGZIP() []byte {
 	return file_mesh_v1alpha1_traffic_route_proto_rawDescData
 }
 
-var file_mesh_v1alpha1_traffic_route_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_mesh_v1alpha1_traffic_route_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
 var file_mesh_v1alpha1_traffic_route_proto_goTypes = []interface{}{
-	(*TrafficRoute)(nil),       // 0: kuma.mesh.v1alpha1.TrafficRoute
-	(*TrafficRoute_Split)(nil), // 1: kuma.mesh.v1alpha1.TrafficRoute.Split
-	(*TrafficRoute_Conf)(nil),  // 2: kuma.mesh.v1alpha1.TrafficRoute.Conf
-	nil,                        // 3: kuma.mesh.v1alpha1.TrafficRoute.Split.DestinationEntry
-	(*Selector)(nil),           // 4: kuma.mesh.v1alpha1.Selector
+	(*TrafficRoute)(nil),              // 0: kuma.mesh.v1alpha1.TrafficRoute
+	(*TrafficRoute_Split)(nil),        // 1: kuma.mesh.v1alpha1.TrafficRoute.Split
+	(*TrafficRoute_LoadBalancer)(nil), // 2: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer
+	(*TrafficRoute_Conf)(nil),         // 3: kuma.mesh.v1alpha1.TrafficRoute.Conf
+	nil,                               // 4: kuma.mesh.v1alpha1.TrafficRoute.Split.DestinationEntry
+	(*TrafficRoute_LoadBalancer_RoundRobin)(nil),   // 5: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.RoundRobin
+	(*TrafficRoute_LoadBalancer_LeastRequest)(nil), // 6: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.LeastRequest
+	(*TrafficRoute_LoadBalancer_RingHash)(nil),     // 7: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.RingHash
+	(*TrafficRoute_LoadBalancer_Random)(nil),       // 8: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.Random
+	(*TrafficRoute_LoadBalancer_Maglev)(nil),       // 9: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.Maglev
+	(*Selector)(nil),                               // 10: kuma.mesh.v1alpha1.Selector
 }
 var file_mesh_v1alpha1_traffic_route_proto_depIdxs = []int32{
-	4, // 0: kuma.mesh.v1alpha1.TrafficRoute.sources:type_name -> kuma.mesh.v1alpha1.Selector
-	4, // 1: kuma.mesh.v1alpha1.TrafficRoute.destinations:type_name -> kuma.mesh.v1alpha1.Selector
-	2, // 2: kuma.mesh.v1alpha1.TrafficRoute.conf:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Conf
-	3, // 3: kuma.mesh.v1alpha1.TrafficRoute.Split.destination:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Split.DestinationEntry
-	1, // 4: kuma.mesh.v1alpha1.TrafficRoute.Conf.split:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Split
-	5, // [5:5] is the sub-list for method output_type
-	5, // [5:5] is the sub-list for method input_type
-	5, // [5:5] is the sub-list for extension type_name
-	5, // [5:5] is the sub-list for extension extendee
-	0, // [0:5] is the sub-list for field type_name
+	10, // 0: kuma.mesh.v1alpha1.TrafficRoute.sources:type_name -> kuma.mesh.v1alpha1.Selector
+	10, // 1: kuma.mesh.v1alpha1.TrafficRoute.destinations:type_name -> kuma.mesh.v1alpha1.Selector
+	3,  // 2: kuma.mesh.v1alpha1.TrafficRoute.conf:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Conf
+	4,  // 3: kuma.mesh.v1alpha1.TrafficRoute.Split.destination:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Split.DestinationEntry
+	5,  // 4: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.round_robin:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.RoundRobin
+	6,  // 5: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.least_request:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.LeastRequest
+	7,  // 6: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.ring_hash:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.RingHash
+	8,  // 7: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.random:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.Random
+	9,  // 8: kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.maglev:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer.Maglev
+	1,  // 9: kuma.mesh.v1alpha1.TrafficRoute.Conf.split:type_name -> kuma.mesh.v1alpha1.TrafficRoute.Split
+	2,  // 10: kuma.mesh.v1alpha1.TrafficRoute.Conf.load_balancer:type_name -> kuma.mesh.v1alpha1.TrafficRoute.LoadBalancer
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_mesh_v1alpha1_traffic_route_proto_init() }
@@ -323,6 +751,18 @@ func file_mesh_v1alpha1_traffic_route_proto_init() {
 			}
 		}
 		file_mesh_v1alpha1_traffic_route_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*TrafficRoute_Conf); i {
 			case 0:
 				return &v.state
@@ -334,6 +774,73 @@ func file_mesh_v1alpha1_traffic_route_proto_init() {
 				return nil
 			}
 		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer_RoundRobin); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer_LeastRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer_RingHash); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer_Random); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_mesh_v1alpha1_traffic_route_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*TrafficRoute_LoadBalancer_Maglev); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_mesh_v1alpha1_traffic_route_proto_msgTypes[2].OneofWrappers = []interface{}{
+		(*TrafficRoute_LoadBalancer_RoundRobin_)(nil),
+		(*TrafficRoute_LoadBalancer_LeastRequest_)(nil),
+		(*TrafficRoute_LoadBalancer_RingHash_)(nil),
+		(*TrafficRoute_LoadBalancer_Random_)(nil),
+		(*TrafficRoute_LoadBalancer_Maglev_)(nil),
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -341,7 +848,7 @@ func file_mesh_v1alpha1_traffic_route_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_mesh_v1alpha1_traffic_route_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   4,
+			NumMessages:   10,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/mesh/v1alpha1/traffic_route.proto b/api/mesh/v1alpha1/traffic_route.proto
index 3f43db9fc..d1efcb3a2 100644
--- a/api/mesh/v1alpha1/traffic_route.proto
+++ b/api/mesh/v1alpha1/traffic_route.proto
@@ -40,10 +40,55 @@ message TrafficRoute {
     } ];
   };
 
+  // LoadBalancer defines the load balancing policy and configuration
+  message LoadBalancer {
+
+    // RoundRobin is a simple policy in which each available upstream host is
+    // selected in round robin order
+    message RoundRobin {}
+
+    // LeastRequest uses different algorithms depending on whether hosts have
+    // the same or different weights
+    message LeastRequest {
+      // The number of random healthy hosts from which the host with the fewest
+      // active requests will be chosen. Defaults to 2 so that we perform
+      // two-choice selection if the field is not set.
+      uint32 choice_count = 1;
+    }
+
+    // RingHash implements consistent hashing to upstream hosts
+    message RingHash {
+      // The hash function used to hash hosts onto the ketama ring. The value
+      // defaults to 'XX_HASH'
+      string hash_function = 1;
+
+      // Minimum hash ring size
+      uint64 min_ring_size = 2;
+
+      // Maximum hash ring size.
+      uint64 max_ring_size = 3;
+    }
+
+    // Random selects a random available host
+    message Random {}
+
+    // Maglev implements consistent hashing to upstream hosts
+    message Maglev {}
+
+    oneof lb_type {
+      RoundRobin round_robin = 1;
+      LeastRequest least_request = 2;
+      RingHash ring_hash = 3;
+      Random random = 4;
+      Maglev maglev = 5;
+    }
+  };
+
   // Conf defines the destination configuration
   message Conf {
     // List of destinations with weights assigned to them.
     repeated Split split = 1 [ (validate.rules).repeated .min_items = 1 ];
+    LoadBalancer load_balancer = 2;
   };
 
   // Configuration for the route
diff --git a/api/system/v1alpha1/zone_insight.pb.go b/api/system/v1alpha1/zone_insight.pb.go
index 65144effe..65f2543db 100644
--- a/api/system/v1alpha1/zone_insight.pb.go
+++ b/api/system/v1alpha1/zone_insight.pb.go
@@ -101,6 +101,8 @@ type KDSSubscription struct {
 	DisconnectTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=disconnect_time,json=disconnectTime,proto3" json:"disconnect_time,omitempty"`
 	// Status of the KDS subscription.
 	Status *KDSSubscriptionStatus `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"`
+	// Version of Remote Kuma CP.
+	Version *Version `protobuf:"bytes,6,opt,name=version,proto3" json:"version,omitempty"`
 }
 
 func (x *KDSSubscription) Reset() {
@@ -170,6 +172,13 @@ func (x *KDSSubscription) GetStatus() *KDSSubscriptionStatus {
 	return nil
 }
 
+func (x *KDSSubscription) GetVersion() *Version {
+	if x != nil {
+		return x.Version
+	}
+	return nil
+}
+
 // KDSSubscriptionStatus defines status of an KDS subscription.
 type KDSSubscriptionStatus struct {
 	state         protoimpl.MessageState
@@ -303,6 +312,131 @@ func (x *KDSServiceStats) GetResponsesRejected() uint64 {
 	return 0
 }
 
+// Version defines version of Kuma ControlPlane
+type Version struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Version of Remote Kuma CP
+	KumaCp *KumaCpVersion `protobuf:"bytes,1,opt,name=kumaCp,proto3" json:"kumaCp,omitempty"`
+}
+
+func (x *Version) Reset() {
+	*x = Version{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_system_v1alpha1_zone_insight_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Version) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Version) ProtoMessage() {}
+
+func (x *Version) ProtoReflect() protoreflect.Message {
+	mi := &file_system_v1alpha1_zone_insight_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Version.ProtoReflect.Descriptor instead.
+func (*Version) Descriptor() ([]byte, []int) {
+	return file_system_v1alpha1_zone_insight_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Version) GetKumaCp() *KumaCpVersion {
+	if x != nil {
+		return x.KumaCp
+	}
+	return nil
+}
+
+// KumaCpVersion describes details of Kuma ControlPlane version
+type KumaCpVersion struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Version number of Kuma ControlPlane
+	Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
+	// Git tag of Kuma ControlPlane version
+	GitTag string `protobuf:"bytes,2,opt,name=gitTag,proto3" json:"gitTag,omitempty"`
+	// Git commit of Kuma ControlPlane version
+	GitCommit string `protobuf:"bytes,3,opt,name=gitCommit,proto3" json:"gitCommit,omitempty"`
+	// Build date of Kuma ControlPlane version
+	BuildDate string `protobuf:"bytes,4,opt,name=buildDate,proto3" json:"buildDate,omitempty"`
+}
+
+func (x *KumaCpVersion) Reset() {
+	*x = KumaCpVersion{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_system_v1alpha1_zone_insight_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *KumaCpVersion) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*KumaCpVersion) ProtoMessage() {}
+
+func (x *KumaCpVersion) ProtoReflect() protoreflect.Message {
+	mi := &file_system_v1alpha1_zone_insight_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use KumaCpVersion.ProtoReflect.Descriptor instead.
+func (*KumaCpVersion) Descriptor() ([]byte, []int) {
+	return file_system_v1alpha1_zone_insight_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *KumaCpVersion) GetVersion() string {
+	if x != nil {
+		return x.Version
+	}
+	return ""
+}
+
+func (x *KumaCpVersion) GetGitTag() string {
+	if x != nil {
+		return x.GitTag
+	}
+	return ""
+}
+
+func (x *KumaCpVersion) GetGitCommit() string {
+	if x != nil {
+		return x.GitCommit
+	}
+	return ""
+}
+
+func (x *KumaCpVersion) GetBuildDate() string {
+	if x != nil {
+		return x.BuildDate
+	}
+	return ""
+}
+
 var File_system_v1alpha1_zone_insight_proto protoreflect.FileDescriptor
 
 var file_system_v1alpha1_zone_insight_proto_rawDesc = []byte{
@@ -319,7 +453,7 @@ var file_system_v1alpha1_zone_insight_proto_rawDesc = []byte{
 	0x61, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61,
 	0x31, 0x2e, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
 	0x6e, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x22, 0xbe, 0x02, 0x0a, 0x0f, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
+	0x22, 0xf7, 0x02, 0x0a, 0x0f, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70,
 	0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
 	0x42, 0x07, 0xfa, 0x42, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x02, 0x69, 0x64, 0x12, 0x35, 0x0a,
 	0x12, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
@@ -339,37 +473,53 @@ var file_system_v1alpha1_zone_insight_proto_rawDesc = []byte{
 	0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62,
 	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42,
 	0x08, 0xfa, 0x42, 0x05, 0x8a, 0x01, 0x02, 0x10, 0x01, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x22, 0xc5, 0x02, 0x0a, 0x15, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
-	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x44, 0x0a, 0x10, 0x6c,
-	0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-	0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
-	0x65, 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x73, 0x12, 0x37, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xc5, 0x02, 0x0a, 0x15, 0x4b,
+	0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x12, 0x44, 0x0a, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x70, 0x64,
+	0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x6c, 0x61, 0x73, 0x74,
+	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x6f,
+	0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x75, 0x6d, 0x61,
+	0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31,
+	0x2e, 0x4b, 0x44, 0x53, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73,
+	0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x49, 0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18,
+	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x73, 0x79, 0x73,
+	0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4b, 0x44, 0x53,
+	0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x73, 0x74,
+	0x61, 0x74, 0x1a, 0x5e, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
+	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
+	0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
 	0x32, 0x25, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76,
 	0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4b, 0x44, 0x53, 0x53, 0x65, 0x72, 0x76, 0x69,
-	0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x49,
-	0x0a, 0x04, 0x73, 0x74, 0x61, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b,
-	0x75, 0x6d, 0x61, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70,
-	0x68, 0x61, 0x31, 0x2e, 0x4b, 0x44, 0x53, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-	0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x45, 0x6e,
-	0x74, 0x72, 0x79, 0x52, 0x04, 0x73, 0x74, 0x61, 0x74, 0x1a, 0x5e, 0x0a, 0x09, 0x53, 0x74, 0x61,
-	0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x3b, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x73,
-	0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4b,
-	0x44, 0x53, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9e, 0x01, 0x0a, 0x0f, 0x4b, 0x44,
-	0x53, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a,
-	0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73,
-	0x53, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x73, 0x5f, 0x61, 0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x64, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x41,
-	0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x72,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65,
-	0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x73, 0x52, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
+	0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
+	0x38, 0x01, 0x22, 0x9e, 0x01, 0x0a, 0x0f, 0x4b, 0x44, 0x53, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d,
+	0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x35, 0x0a,
+	0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x5f, 0x61, 0x63, 0x6b, 0x6e, 0x6f,
+	0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x72,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x41, 0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65,
+	0x64, 0x67, 0x65, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x73, 0x5f, 0x72, 0x65, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x11, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x65, 0x6a, 0x65, 0x63,
+	0x74, 0x65, 0x64, 0x22, 0x46, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3b,
+	0x0a, 0x06, 0x6b, 0x75, 0x6d, 0x61, 0x43, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
+	0x2e, 0x6b, 0x75, 0x6d, 0x61, 0x2e, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x76, 0x31, 0x61,
+	0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4b, 0x75, 0x6d, 0x61, 0x43, 0x70, 0x56, 0x65, 0x72, 0x73,
+	0x69, 0x6f, 0x6e, 0x52, 0x06, 0x6b, 0x75, 0x6d, 0x61, 0x43, 0x70, 0x22, 0x7d, 0x0a, 0x0d, 0x4b,
+	0x75, 0x6d, 0x61, 0x43, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
+	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76,
+	0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x69, 0x74, 0x54, 0x61, 0x67,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x74, 0x54, 0x61, 0x67, 0x12, 0x1c,
+	0x0a, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x09, 0x67, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69,
 	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x75, 0x6d, 0x61, 0x68, 0x71, 0x2f,
 	0x6b, 0x75, 0x6d, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f,
 	0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
@@ -387,29 +537,33 @@ func file_system_v1alpha1_zone_insight_proto_rawDescGZIP() []byte {
 	return file_system_v1alpha1_zone_insight_proto_rawDescData
 }
 
-var file_system_v1alpha1_zone_insight_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_system_v1alpha1_zone_insight_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
 var file_system_v1alpha1_zone_insight_proto_goTypes = []interface{}{
 	(*ZoneInsight)(nil),           // 0: kuma.system.v1alpha1.ZoneInsight
 	(*KDSSubscription)(nil),       // 1: kuma.system.v1alpha1.KDSSubscription
 	(*KDSSubscriptionStatus)(nil), // 2: kuma.system.v1alpha1.KDSSubscriptionStatus
 	(*KDSServiceStats)(nil),       // 3: kuma.system.v1alpha1.KDSServiceStats
-	nil,                           // 4: kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry
-	(*timestamp.Timestamp)(nil),   // 5: google.protobuf.Timestamp
+	(*Version)(nil),               // 4: kuma.system.v1alpha1.Version
+	(*KumaCpVersion)(nil),         // 5: kuma.system.v1alpha1.KumaCpVersion
+	nil,                           // 6: kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry
+	(*timestamp.Timestamp)(nil),   // 7: google.protobuf.Timestamp
 }
 var file_system_v1alpha1_zone_insight_proto_depIdxs = []int32{
-	1, // 0: kuma.system.v1alpha1.ZoneInsight.subscriptions:type_name -> kuma.system.v1alpha1.KDSSubscription
-	5, // 1: kuma.system.v1alpha1.KDSSubscription.connect_time:type_name -> google.protobuf.Timestamp
-	5, // 2: kuma.system.v1alpha1.KDSSubscription.disconnect_time:type_name -> google.protobuf.Timestamp
-	2, // 3: kuma.system.v1alpha1.KDSSubscription.status:type_name -> kuma.system.v1alpha1.KDSSubscriptionStatus
-	5, // 4: kuma.system.v1alpha1.KDSSubscriptionStatus.last_update_time:type_name -> google.protobuf.Timestamp
-	3, // 5: kuma.system.v1alpha1.KDSSubscriptionStatus.total:type_name -> kuma.system.v1alpha1.KDSServiceStats
-	4, // 6: kuma.system.v1alpha1.KDSSubscriptionStatus.stat:type_name -> kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry
-	3, // 7: kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry.value:type_name -> kuma.system.v1alpha1.KDSServiceStats
-	8, // [8:8] is the sub-list for method output_type
-	8, // [8:8] is the sub-list for method input_type
-	8, // [8:8] is the sub-list for extension type_name
-	8, // [8:8] is the sub-list for extension extendee
-	0, // [0:8] is the sub-list for field type_name
+	1,  // 0: kuma.system.v1alpha1.ZoneInsight.subscriptions:type_name -> kuma.system.v1alpha1.KDSSubscription
+	7,  // 1: kuma.system.v1alpha1.KDSSubscription.connect_time:type_name -> google.protobuf.Timestamp
+	7,  // 2: kuma.system.v1alpha1.KDSSubscription.disconnect_time:type_name -> google.protobuf.Timestamp
+	2,  // 3: kuma.system.v1alpha1.KDSSubscription.status:type_name -> kuma.system.v1alpha1.KDSSubscriptionStatus
+	4,  // 4: kuma.system.v1alpha1.KDSSubscription.version:type_name -> kuma.system.v1alpha1.Version
+	7,  // 5: kuma.system.v1alpha1.KDSSubscriptionStatus.last_update_time:type_name -> google.protobuf.Timestamp
+	3,  // 6: kuma.system.v1alpha1.KDSSubscriptionStatus.total:type_name -> kuma.system.v1alpha1.KDSServiceStats
+	6,  // 7: kuma.system.v1alpha1.KDSSubscriptionStatus.stat:type_name -> kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry
+	5,  // 8: kuma.system.v1alpha1.Version.kumaCp:type_name -> kuma.system.v1alpha1.KumaCpVersion
+	3,  // 9: kuma.system.v1alpha1.KDSSubscriptionStatus.StatEntry.value:type_name -> kuma.system.v1alpha1.KDSServiceStats
+	10, // [10:10] is the sub-list for method output_type
+	10, // [10:10] is the sub-list for method input_type
+	10, // [10:10] is the sub-list for extension type_name
+	10, // [10:10] is the sub-list for extension extendee
+	0,  // [0:10] is the sub-list for field type_name
 }
 
 func init() { file_system_v1alpha1_zone_insight_proto_init() }
@@ -466,6 +620,30 @@ func file_system_v1alpha1_zone_insight_proto_init() {
 				return nil
 			}
 		}
+		file_system_v1alpha1_zone_insight_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Version); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_system_v1alpha1_zone_insight_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*KumaCpVersion); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -473,7 +651,7 @@ func file_system_v1alpha1_zone_insight_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_system_v1alpha1_zone_insight_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   5,
+			NumMessages:   7,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/system/v1alpha1/zone_insight.proto b/api/system/v1alpha1/zone_insight.proto
index 0071bb53f..2bd7babf5 100644
--- a/api/system/v1alpha1/zone_insight.proto
+++ b/api/system/v1alpha1/zone_insight.proto
@@ -41,6 +41,9 @@ message KDSSubscription {
 
   // Status of the KDS subscription.
   KDSSubscriptionStatus status = 5 [ (validate.rules).message.required = true ];
+
+  // Version of Remote Kuma CP.
+  Version version = 6;
 }
 
 // KDSSubscriptionStatus defines status of an KDS subscription.
@@ -67,3 +70,26 @@ message KDSServiceStats {
   // Number of xDS responses NACKed by the Dataplane.
   uint64 responses_rejected = 3;
 }
+
+// Version defines version of Kuma ControlPlane
+message Version {
+
+  // Version of Remote Kuma CP
+  KumaCpVersion kumaCp = 1;
+}
+
+// KumaCpVersion describes details of Kuma ControlPlane version
+message KumaCpVersion {
+
+  // Version number of Kuma ControlPlane
+  string version = 1;
+
+  // Git tag of Kuma ControlPlane version
+  string gitTag = 2;
+
+  // Git commit of Kuma ControlPlane version
+  string gitCommit = 3;
+
+  // Build date of Kuma ControlPlane version
+  string buildDate = 4;
+}
diff --git a/api/system/v1alpha1/zone_insight_helpers.go b/api/system/v1alpha1/zone_insight_helpers.go
index 8e3d35e79..dade4d3ad 100644
--- a/api/system/v1alpha1/zone_insight_helpers.go
+++ b/api/system/v1alpha1/zone_insight_helpers.go
@@ -69,3 +69,14 @@ func (m *ZoneInsight) UpdateSubscription(s *KDSSubscription) {
 		m.Subscriptions = append(m.Subscriptions, s)
 	}
 }
+
+func NewVersion() *Version {
+	return &Version{
+		KumaCp: &KumaCpVersion{
+			Version:   "",
+			GitTag:    "",
+			GitCommit: "",
+			BuildDate: "",
+		},
+	}
+}
diff --git a/app/kuma-dp/cmd/run.go b/app/kuma-dp/cmd/run.go
index be4223cda..3a0ab8bae 100644
--- a/app/kuma-dp/cmd/run.go
+++ b/app/kuma-dp/cmd/run.go
@@ -145,6 +145,7 @@ func newRunCmd() *cobra.Command {
 	cmd.PersistentFlags().StringVar(&cfg.ControlPlane.URL, "cp-address", cfg.ControlPlane.URL, "URL of the Control Plane Dataplane Server. Example: https://localhost:5678")
 	cmd.PersistentFlags().StringVar(&cfg.ControlPlane.CaCertFile, "ca-cert-file", cfg.ControlPlane.CaCert, "Path to CA cert by which connection to the Control Plane will be verified if HTTPS is used")
 	cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.BinaryPath, "binary-path", cfg.DataplaneRuntime.BinaryPath, "Binary path of Envoy executable")
+	cmd.PersistentFlags().StringVar(&cfg.Dataplane.BootstrapVersion, "bootstrap-version", cfg.Dataplane.BootstrapVersion, "Bootstrap version (and API version) of xDS config. If empty, default version defined in Kuma CP will be used. (ex. '2', '3')")
 	cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.ConfigDir, "config-dir", cfg.DataplaneRuntime.ConfigDir, "Directory in which Envoy config will be generated")
 	cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.TokenPath, "dataplane-token-file", cfg.DataplaneRuntime.TokenPath, "Path to a file with dataplane token (use 'kumactl generate dataplane-token' to get one)")
 	cmd.PersistentFlags().StringVar(&cfg.DataplaneRuntime.Token, "dataplane-token", cfg.DataplaneRuntime.Token, "Dataplane Token")
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy.go b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
index 1bdc83a0e..5b3c731c9 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
+	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 
 	"github.com/pkg/errors"
 
@@ -24,7 +25,7 @@ var (
 	runLog = core.Log.WithName("kuma-dp").WithName("run").WithName("envoy")
 )
 
-type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, dp *rest.Resource, ev EnvoyVersion) ([]byte, error)
+type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, dp *rest.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error)
 
 type Opts struct {
 	Config    kuma_dp.Config
@@ -107,7 +108,7 @@ func (e *Envoy) Start(stop <-chan struct{}) error {
 	}
 	runLog.Info("fetched Envoy version", "version", envoyVersion)
 	runLog.Info("generating bootstrap configuration")
-	bootstrapConfig, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, e.opts.Dataplane, *envoyVersion)
+	bootstrapConfig, version, err := e.opts.Generator(e.opts.Config.ControlPlane.URL, e.opts.Config, e.opts.Dataplane, types.BootstrapVersion(e.opts.Config.Dataplane.BootstrapVersion), *envoyVersion)
 	if err != nil {
 		return errors.Errorf("Failed to generate Envoy bootstrap config. %v", err)
 	}
@@ -140,6 +141,9 @@ func (e *Envoy) Start(stop <-chan struct{}) error {
 		// so, let's turn it off to simplify getting started experience.
 		"--disable-hot-restart",
 	}
+	if version != "" { // version is always send by Kuma CP, but we check empty for backwards compatibility reasons (new Kuma DP connects to old Kuma CP)
+		args = append(args, "--bootstrap-version", string(version))
+	}
 	command := exec.CommandContext(ctx, resolvedPath, args...)
 	command.Stdout = e.opts.Stdout
 	command.Stderr = e.opts.Stderr
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
index 45a7aba44..460ee5c30 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
@@ -43,17 +43,17 @@ func IsInvalidRequestErr(err error) bool {
 	return strings.HasPrefix(err.Error(), "Invalid request: ")
 }
 
-func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_types.Resource, ev EnvoyVersion) ([]byte, error) {
+func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
 	bootstrapUrl, err := net_url.Parse(url)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	if bootstrapUrl.Scheme == "https" {
 		if cfg.ControlPlane.CaCert != "" {
 			certPool := x509.NewCertPool()
 			if ok := certPool.AppendCertsFromPEM([]byte(cfg.ControlPlane.CaCert)); !ok {
-				return nil, errors.New("could not add certificate")
+				return nil, "", errors.New("could not add certificate")
 			}
 			b.client.Transport = &http.Transport{
 				TLSClientConfig: &tls.Config{
@@ -67,19 +67,21 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 
 	backoff, err := retry.NewConstant(cfg.ControlPlane.Retry.Backoff)
 	if err != nil {
-		return nil, errors.Wrap(err, "could not create retry backoff")
+		return nil, "", errors.Wrap(err, "could not create retry backoff")
 	}
 	backoff = retry.WithMaxDuration(cfg.ControlPlane.Retry.MaxDuration, backoff)
 	var respBytes []byte
+	var version types.BootstrapVersion
 	err = retry.Do(context.Background(), backoff, func(ctx context.Context) error {
 		log.Info("trying to fetch bootstrap configuration from the Control Plane")
-		respBytes, err = b.requestForBootstrap(bootstrapUrl, cfg, dp, ev)
+		respBytes, version, err = b.requestForBootstrap(bootstrapUrl, cfg, dp, bootstrapVersion, ev)
 		if err == nil {
 			return nil
 		}
 		if IsInvalidRequestErr(err) { // there is no point in retrying invalid request
 			return err
 		}
+
 		switch err {
 		case DpNotFoundErr:
 			log.Info("Dataplane entity is not yet found in the Control Plane. If you are running on Kubernetes, CP is most likely still in the process of converting Pod to Dataplane. Retrying.", "backoff", cfg.ControlPlane.Retry.Backoff)
@@ -89,18 +91,18 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 		return retry.RetryableError(err)
 	})
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
-	return respBytes, nil
+	return respBytes, version, nil
 }
 
-func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, dp *rest_types.Resource, ev EnvoyVersion) ([]byte, error) {
+func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, dp *rest_types.Resource, bootstrapVersion types.BootstrapVersion, ev EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
 	url.Path = "/bootstrap"
 	var dataplaneResource string
 	if dp != nil {
 		dpJSON, err := json.Marshal(dp)
 		if err != nil {
-			return nil, err
+			return nil, "", err
 		}
 		dataplaneResource = string(dpJSON)
 	}
@@ -112,6 +114,7 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 		AdminPort:          cfg.Dataplane.AdminPort.Lowest(),
 		DataplaneTokenPath: cfg.DataplaneRuntime.TokenPath,
 		DataplaneResource:  dataplaneResource,
+		BootstrapVersion:   bootstrapVersion,
 		Version: types.Version{
 			KumaDp: types.KumaDpVersion{
 				Version:   kuma_version.Build.Version,
@@ -127,32 +130,32 @@ func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Conf
 	}
 	jsonBytes, err := json.Marshal(request)
 	if err != nil {
-		return nil, errors.Wrap(err, "could not marshal request to json")
+		return nil, "", errors.Wrap(err, "could not marshal request to json")
 	}
 	resp, err := b.client.Post(url.String(), "application/json", bytes.NewReader(jsonBytes))
 	if err != nil {
-		return nil, errors.Wrap(err, "request to bootstrap server failed")
+		return nil, "", errors.Wrap(err, "request to bootstrap server failed")
 	}
 	defer resp.Body.Close()
 	if resp.StatusCode != http.StatusOK {
 		bodyBytes, err := ioutil.ReadAll(resp.Body)
 		if err != nil {
-			return nil, errors.Wrapf(err, "Unable to read the response with status code: %d. Make sure you are using https URL", resp.StatusCode)
+			return nil, "", errors.Wrapf(err, "Unable to read the response with status code: %d. Make sure you are using https URL", resp.StatusCode)
 		}
 		if resp.StatusCode == http.StatusNotFound && len(bodyBytes) == 0 {
-			return nil, DpNotFoundErr
+			return nil, "", DpNotFoundErr
 		}
 		if resp.StatusCode == http.StatusNotFound && string(bodyBytes) == "404: Page Not Found" { // response body of Go HTTP Server when hit for invalid endpoint
-			return nil, errors.New("There is no /bootstrap endpoint for provided CP address. Double check if the address passed to the CP has a DP Server port (5678 by default), not HTTP API (5681 by default)")
+			return nil, "", errors.New("There is no /bootstrap endpoint for provided CP address. Double check if the address passed to the CP has a DP Server port (5678 by default), not HTTP API (5681 by default)")
 		}
 		if resp.StatusCode/100 == 4 {
-			return nil, InvalidRequestErr(string(bodyBytes))
+			return nil, "", InvalidRequestErr(string(bodyBytes))
 		}
-		return nil, errors.Errorf("unexpected status code: %d", resp.StatusCode)
+		return nil, "", errors.Errorf("unexpected status code: %d", resp.StatusCode)
 	}
 	respBytes, err := ioutil.ReadAll(resp.Body)
 	if err != nil {
-		return nil, errors.Wrap(err, "could not read the body of the response")
+		return nil, "", errors.Wrap(err, "could not read the body of the response")
 	}
-	return respBytes, nil
+	return respBytes, types.BootstrapVersion(resp.Header.Get(types.BootstrapVersionHeader)), nil
 }
diff --git a/app/kumactl/cmd/inspect/inspect_zones.go b/app/kumactl/cmd/inspect/inspect_zones.go
index bbb0cc870..b59f5b01e 100644
--- a/app/kumactl/cmd/inspect/inspect_zones.go
+++ b/app/kumactl/cmd/inspect/inspect_zones.go
@@ -50,7 +50,7 @@ func newInspectZonesCmd(ctx *inspectContext) *cobra.Command {
 
 func printZoneOverviews(now time.Time, zoneOverviews *system.ZoneOverviewResourceList, out io.Writer) error {
 	data := printers.Table{
-		Headers: []string{"MESH", "NAME", "STATUS", "LAST CONNECTED AGO", "LAST UPDATED AGO", "TOTAL UPDATES", "TOTAL ERRORS"},
+		Headers: []string{"NAME", "STATUS", "LAST CONNECTED AGO", "LAST UPDATED AGO", "TOTAL UPDATES", "TOTAL ERRORS", "REMOTE-CP VERSION"},
 		NextRow: func() func() []string {
 			i := 0
 			return func() []string {
@@ -75,14 +75,21 @@ func printZoneOverviews(now time.Time, zoneOverviews *system.ZoneOverviewResourc
 				}
 				lastUpdated := util_proto.MustTimestampFromProto(lastSubscription.GetStatus().GetLastUpdateTime())
 
+				var remoteCpVersion string
+				if lastSubscription.GetVersion() != nil {
+					if lastSubscription.Version.KumaCp != nil {
+						remoteCpVersion = lastSubscription.Version.KumaCp.Version
+					}
+				}
+
 				return []string{
-					meta.GetMesh(),                       // MESH
 					meta.GetName(),                       // NAME,
 					onlineStatus,                         // STATUS
 					table.Ago(lastConnected, now),        // LAST CONNECTED AGO
 					table.Ago(lastUpdated, now),          // LAST UPDATED AGO
 					table.Number(totalResponsesSent),     // TOTAL UPDATES
 					table.Number(totalResponsesRejected), // TOTAL ERRORS
+					remoteCpVersion,                      // REMOTE-CP VERSION
 				}
 			}
 		}(),
diff --git a/pkg/config/app/kuma-dp/config.go b/pkg/config/app/kuma-dp/config.go
index c4fa720bf..3737b23a9 100644
--- a/pkg/config/app/kuma-dp/config.go
+++ b/pkg/config/app/kuma-dp/config.go
@@ -102,6 +102,9 @@ type Dataplane struct {
 	AdminPort config_types.PortRange `yaml:"adminPort,omitempty" envconfig:"kuma_dataplane_admin_port"`
 	// Drain time for listeners.
 	DrainTime time.Duration `yaml:"drainTime,omitempty" envconfig:"kuma_dataplane_drain_time"`
+	// BootstrapVersion defines bootstrap version (and API version) of xDS config.
+	// If empty, default version defined in Kuma CP will be used.
+	BootstrapVersion string `yaml:"bootstrapVersion" envconfig:"kuma_dataplane_bootstrap_version"`
 }
 
 // DataplaneRuntime defines the context in which dataplane (Envoy) runs.
diff --git a/pkg/core/resources/apis/mesh/traffic_route_validator.go b/pkg/core/resources/apis/mesh/traffic_route_validator.go
index 76a88ed44..be7eda98b 100644
--- a/pkg/core/resources/apis/mesh/traffic_route_validator.go
+++ b/pkg/core/resources/apis/mesh/traffic_route_validator.go
@@ -1,6 +1,7 @@
 package mesh
 
 import (
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core/validators"
 )
 
@@ -43,5 +44,28 @@ func (d *TrafficRouteResource) validateConf() (err validators.ValidationError) {
 			RequireService:       true,
 		}))
 	}
+	err.Add(d.validateLb())
+
+	return
+}
+
+func (d *TrafficRouteResource) validateLb() (err validators.ValidationError) {
+	lb := d.Spec.GetConf().GetLoadBalancer()
+	if lb == nil {
+		return
+	}
+
+	switch lb.LbType.(type) {
+	case *mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_:
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_RingHash_:
+		lbConfig := lb.GetRingHash()
+		switch lbConfig.HashFunction {
+		case "XX_HASH", "MURMUR_HASH_2":
+		default:
+			root := validators.RootedAt("conf.loadBalancer.ringHash.hashFunction")
+			err.AddViolationAt(root, "must have a valid hash function")
+		}
+	}
 	return
 }
diff --git a/pkg/defaults/mesh/traffic_route.go b/pkg/defaults/mesh/traffic_route.go
index d8efdc154..31aec984e 100644
--- a/pkg/defaults/mesh/traffic_route.go
+++ b/pkg/defaults/mesh/traffic_route.go
@@ -27,6 +27,9 @@ var (
 				Weight:      100,
 				Destination: mesh_proto.MatchAnyService(),
 			}},
+			LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_{},
+			},
 		},
 	}
 )
diff --git a/pkg/kds/client/stream.go b/pkg/kds/client/stream.go
index 676f0f02b..9e71d72ff 100644
--- a/pkg/kds/client/stream.go
+++ b/pkg/kds/client/stream.go
@@ -7,10 +7,14 @@ import (
 
 	envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
+	pstruct "github.com/golang/protobuf/ptypes/struct"
 	"google.golang.org/genproto/googleapis/rpc/status"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
+	util_proto "github.com/kumahq/kuma/pkg/util/proto"
+	kuma_version "github.com/kumahq/kuma/pkg/version"
 )
 
 type KDSStream interface {
@@ -40,11 +44,27 @@ func NewKDSStream(s mesh_proto.KumaDiscoveryService_StreamKumaResourcesClient, c
 }
 
 func (s *stream) DiscoveryRequest(resourceType model.ResourceType) error {
+	cpVersion, err := util_proto.ToStruct(&system_proto.Version{
+		KumaCp: &system_proto.KumaCpVersion{
+			Version:   kuma_version.Build.Version,
+			GitTag:    kuma_version.Build.GitTag,
+			GitCommit: kuma_version.Build.GitCommit,
+			BuildDate: kuma_version.Build.BuildDate,
+		},
+	})
+	if err != nil {
+		return err
+	}
 	return s.streamClient.Send(&envoy.DiscoveryRequest{
 		VersionInfo:   "",
 		ResponseNonce: "",
 		Node: &envoy_core.Node{
 			Id: s.clientId,
+			Metadata: &pstruct.Struct{
+				Fields: map[string]*pstruct.Value{
+					"version": {Kind: &pstruct.Value_StructValue{StructValue: cpVersion}},
+				},
+			},
 		},
 		ResourceNames: []string{},
 		TypeUrl:       string(resourceType),
diff --git a/pkg/kds/server/components.go b/pkg/kds/server/components.go
index 2f6bab0f1..c05a5c178 100644
--- a/pkg/kds/server/components.go
+++ b/pkg/kds/server/components.go
@@ -55,7 +55,7 @@ func DefaultStatusTracker(rt core_runtime.Runtime, log logr.Logger) StatusTracke
 				return time.NewTicker(rt.Config().Multizone.Global.KDS.ZoneInsightFlushInterval)
 			},
 			rt.Config().Multizone.Global.KDS.ZoneInsightFlushInterval/10,
-			NewDataplaneInsightStore(rt.ResourceManager()),
+			NewZonesInsightStore(rt.ResourceManager()),
 			l)
 	}, log)
 }
diff --git a/pkg/kds/server/status_sink.go b/pkg/kds/server/status_sink.go
index ff49ebf7a..e2db492c8 100644
--- a/pkg/kds/server/status_sink.go
+++ b/pkg/kds/server/status_sink.go
@@ -85,7 +85,7 @@ func (s *zoneInsightSink) Start(stop <-chan struct{}) {
 	}
 }
 
-func NewDataplaneInsightStore(resManager manager.ResourceManager) ZoneInsightStore {
+func NewZonesInsightStore(resManager manager.ResourceManager) ZoneInsightStore {
 	return &zoneInsightStore{resManager}
 }
 
diff --git a/pkg/kds/server/status_tracker.go b/pkg/kds/server/status_tracker.go
index a2e727d10..40ef5c252 100644
--- a/pkg/kds/server/status_tracker.go
+++ b/pkg/kds/server/status_tracker.go
@@ -4,6 +4,8 @@ import (
 	"context"
 	"sync"
 
+	pstruct "github.com/golang/protobuf/ptypes/struct"
+
 	"github.com/kumahq/kuma/pkg/core"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/kds/util"
@@ -72,6 +74,7 @@ func (c *statusTracker) OnStreamOpen(ctx context.Context, streamID int64, typ st
 		GlobalInstanceId: c.runtimeInfo.GetInstanceId(),
 		ConnectTime:      util_proto.MustTimestampProto(core.Now()),
 		Status:           system_proto.NewSubscriptionStatus(),
+		Version:          system_proto.NewVersion(),
 	}
 	// initialize state per ADS stream
 	state := &streamState{
@@ -117,9 +120,12 @@ func (c *statusTracker) OnStreamRequest(streamID int64, req *envoy.DiscoveryRequ
 	state.mu.Lock() // write access to the per Dataplane info
 	defer state.mu.Unlock()
 
-	// infer Dataplane id
+	// infer zone
 	if state.zone == "" {
 		state.zone = req.Node.Id
+		if err := readVersion(req.Node.GetMetadata(), state.subscription.Version); err != nil {
+			c.log.Error(err, "failed to extract version out of the Envoy metadata", "streamid", streamID, "metadata", req.Node.GetMetadata())
+		}
 		go c.createStatusSink(state, c.log).Start(state.stop)
 	}
 
@@ -175,3 +181,17 @@ func (s *streamState) GetStatus() (string, *system_proto.KDSSubscription) {
 func (s *streamState) Close() {
 	close(s.stop)
 }
+
+func readVersion(metadata *pstruct.Struct, version *system_proto.Version) error {
+	if metadata == nil {
+		return nil
+	}
+	rawVersion := metadata.Fields["version"].GetStructValue()
+	if rawVersion != nil {
+		err := util_proto.ToTyped(rawVersion, version)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/pkg/xds/bootstrap/generator.go b/pkg/xds/bootstrap/generator.go
index 3e09a7a4f..5dc5ab006 100644
--- a/pkg/xds/bootstrap/generator.go
+++ b/pkg/xds/bootstrap/generator.go
@@ -33,7 +33,7 @@ import (
 )
 
 type BootstrapGenerator interface {
-	Generate(ctx context.Context, request types.BootstrapRequest) (proto.Message, error)
+	Generate(ctx context.Context, request types.BootstrapRequest) (proto.Message, types.BootstrapVersion, error)
 }
 
 func NewDefaultBootstrapGenerator(
@@ -66,19 +66,19 @@ type bootstrapGenerator struct {
 	hostsAndIps   SANSet
 }
 
-func (b *bootstrapGenerator) Generate(ctx context.Context, request types.BootstrapRequest) (proto.Message, error) {
+func (b *bootstrapGenerator) Generate(ctx context.Context, request types.BootstrapRequest) (proto.Message, types.BootstrapVersion, error) {
 	if err := b.validateRequest(request); err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	proxyId, err := core_xds.BuildProxyId(request.Mesh, request.Name)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	dataplane, err := b.dataplaneFor(ctx, request, proxyId)
 	if err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	return b.generateFor(*proxyId, dataplane, request)
@@ -86,6 +86,8 @@ func (b *bootstrapGenerator) Generate(ctx context.Context, request types.Bootstr
 
 var DpTokenRequired = errors.New("Dataplane Token is required. Generate token using 'kumactl generate dataplane-token > /path/file' and provide it via --dataplane-token-file=/path/file argument to Kuma DP")
 
+var InvalidBootstrapVersion = errors.New(`Invalid BootstrapVersion. Available values are: "2", "3"`)
+
 func SANMismatchErr(host string, sans []string) error {
 	return errors.Errorf("A data plane proxy is trying to connect to the control plane using %q address, but the certificate in the control plane has the following SANs %q. "+
 		"Either change the --cp-address in kuma-dp to one of those or execute the following steps:\n"+
@@ -160,7 +162,7 @@ func (b *bootstrapGenerator) validateMeshExist(ctx context.Context, mesh string)
 	return nil
 }
 
-func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *core_mesh.DataplaneResource, request types.BootstrapRequest) (proto.Message, error) {
+func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *core_mesh.DataplaneResource, request types.BootstrapRequest) (proto.Message, types.BootstrapVersion, error) {
 	// if dataplane has no service - fill this with placeholder. Otherwise take the first service
 	service := dataplane.Spec.GetIdentifyingService()
 
@@ -170,14 +172,14 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 	}
 
 	if err := b.verifyAdminPort(adminPort, dataplane); err != nil {
-		return nil, err
+		return nil, "", err
 	}
 
 	var certBytes = ""
 	if b.xdsCertFile != "" {
 		cert, err := ioutil.ReadFile(b.xdsCertFile)
 		if err != nil {
-			return nil, err
+			return nil, "", err
 		}
 		certBytes = base64.StdEncoding.EncodeToString(cert)
 	}
@@ -203,7 +205,7 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 		EnvoyBuild:         request.Version.Envoy.Build,
 	}
 	log.WithValues("params", params).Info("Generating bootstrap config")
-	return b.configForParameters(params)
+	return b.configForParameters(params, request.BootstrapVersion)
 }
 
 func (b *bootstrapGenerator) xdsHost(request types.BootstrapRequest) string {
@@ -226,14 +228,28 @@ func (b *bootstrapGenerator) verifyAdminPort(adminPort uint32, dataplane *core_m
 	return nil
 }
 
-func (b *bootstrapGenerator) configForParameters(params configParameters) (proto.Message, error) {
-	switch b.config.APIVersion {
-	case envoy_common.APIV2:
-		return b.configForParametersV2(params)
-	case envoy_common.APIV3:
-		return b.configForParametersV3(params)
+func (b *bootstrapGenerator) configForParameters(params configParameters, version types.BootstrapVersion) (proto.Message, types.BootstrapVersion, error) {
+	switch {
+	// V2
+	case version == types.BootstrapV2:
+		fallthrough
+	case version == "" && b.config.APIVersion == envoy_common.APIV2: // if client did not overridden bootstrap version, provide bootstrap based on Kuma CP config
+		cfg, err := b.configForParametersV2(params)
+		if err != nil {
+			return nil, "", err
+		}
+		return cfg, types.BootstrapV2, nil
+	// V3
+	case version == types.BootstrapV3:
+		fallthrough
+	case version == "" && b.config.APIVersion == envoy_common.APIV3: // if client did not overridden bootstrap version, provide bootstrap based on Kuma CP config
+		cfg, err := b.configForParametersV3(params)
+		if err != nil {
+			return nil, "", err
+		}
+		return cfg, types.BootstrapV3, nil
 	default:
-		return nil, errors.Errorf("invalid API Version %s", b.config.APIVersion)
+		return nil, "", InvalidBootstrapVersion
 	}
 }
 
diff --git a/pkg/xds/bootstrap/handler.go b/pkg/xds/bootstrap/handler.go
index 02300de70..b7c4a1887 100644
--- a/pkg/xds/bootstrap/handler.go
+++ b/pkg/xds/bootstrap/handler.go
@@ -49,7 +49,7 @@ func (b *BootstrapHandler) Handle(resp http.ResponseWriter, req *http.Request) {
 	reqParams.Host = hostname
 	logger := log.WithValues("params", reqParams)
 
-	config, err := b.Generator.Generate(req.Context(), reqParams)
+	config, version, err := b.Generator.Generate(req.Context(), reqParams)
 	if err != nil {
 		handleError(resp, err, logger)
 		return
@@ -64,6 +64,7 @@ func (b *BootstrapHandler) Handle(resp http.ResponseWriter, req *http.Request) {
 
 	resp.WriteHeader(http.StatusOK)
 	resp.Header().Set("content-type", "text/x-yaml")
+	resp.Header().Set(types.BootstrapVersionHeader, string(version))
 	_, err = resp.Write(bytes)
 	if err != nil {
 		logger.Error(err, "Error while writing the response")
@@ -80,6 +81,13 @@ func handleError(resp http.ResponseWriter, err error, logger logr.Logger) {
 		}
 		return
 	}
+	if err == InvalidBootstrapVersion {
+		resp.WriteHeader(http.StatusBadRequest)
+		if _, err := resp.Write([]byte(err.Error())); err != nil {
+			logger.Error(err, "Error while writing the response")
+		}
+		return
+	}
 	if ISSANMismatchErr(err) {
 		resp.WriteHeader(http.StatusBadRequest)
 		_, err = resp.Write([]byte(err.Error()))
diff --git a/pkg/xds/bootstrap/types/bootstrap_request.go b/pkg/xds/bootstrap/types/bootstrap_request.go
index c3d326a18..66fa2239d 100644
--- a/pkg/xds/bootstrap/types/bootstrap_request.go
+++ b/pkg/xds/bootstrap/types/bootstrap_request.go
@@ -1,5 +1,17 @@
 package types
 
+type BootstrapVersion string
+
+const (
+	BootstrapV2 BootstrapVersion = "2"
+	BootstrapV3 BootstrapVersion = "3"
+)
+
+// Bootstrap is sent to a client (Kuma DP) by putting YAML into a response body.
+// This YAML has no information about Bootstrap version therefore we put extra header with a version
+// Value of this header is then used in CLI arg --bootstrap-version when Envoy is run
+const BootstrapVersionHeader = "kuma-bootstrap-version"
+
 type BootstrapRequest struct {
 	Mesh               string  `json:"mesh"`
 	Name               string  `json:"name"`
@@ -8,6 +20,8 @@ type BootstrapRequest struct {
 	DataplaneResource  string  `json:"dataplaneResource,omitempty"`
 	Host               string  `json:"-"`
 	Version            Version `json:"version"`
+	// BootstrapVersion is an optional version to override the control plane's default setting
+	BootstrapVersion BootstrapVersion `json:"bootstrapVersion"`
 }
 
 type Version struct {
diff --git a/pkg/xds/envoy/clusters/configurers.go b/pkg/xds/envoy/clusters/configurers.go
index da29a0543..faf4b7f0b 100644
--- a/pkg/xds/envoy/clusters/configurers.go
+++ b/pkg/xds/envoy/clusters/configurers.go
@@ -1,6 +1,7 @@
 package clusters
 
 import (
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	mesh_core "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
@@ -134,6 +135,17 @@ func LbSubset(keySets [][]string) ClusterBuilderOptFunc {
 	}
 }
 
+func LB(lb *mesh_proto.TrafficRoute_LoadBalancer) ClusterBuilderOpt {
+	return ClusterBuilderOptFunc(func(config *ClusterBuilderConfig) {
+		config.AddV2(&v2.LbConfigurer{
+			Lb: lb,
+		})
+		config.AddV3(&v3.LbConfigurer{
+			Lb: lb,
+		})
+	})
+}
+
 func PassThroughCluster(name string) ClusterBuilderOpt {
 	return ClusterBuilderOptFunc(func(config *ClusterBuilderConfig) {
 		config.AddV2(&v2.PassThroughClusterConfigurer{
diff --git a/pkg/xds/envoy/clusters/v2/lb_configurer.go b/pkg/xds/envoy/clusters/v2/lb_configurer.go
new file mode 100644
index 000000000..f2ecdc7d1
--- /dev/null
+++ b/pkg/xds/envoy/clusters/v2/lb_configurer.go
@@ -0,0 +1,71 @@
+package clusters
+
+import (
+	"fmt"
+
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+
+	envoy_api "github.com/envoyproxy/go-control-plane/envoy/api/v2"
+	"github.com/golang/protobuf/ptypes/wrappers"
+	"github.com/pkg/errors"
+)
+
+type LbConfigurer struct {
+	Lb *mesh_proto.TrafficRoute_LoadBalancer
+}
+
+var _ ClusterConfigurer = &LbConfigurer{}
+
+func (e *LbConfigurer) Configure(c *envoy_api.Cluster) error {
+	// default to Round Robin
+	if e.Lb.GetLbType() == nil {
+		c.LbPolicy = envoy_api.Cluster_ROUND_ROBIN
+		return nil
+	}
+
+	switch e.Lb.GetLbType().(type) {
+	case *mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_:
+		c.LbPolicy = envoy_api.Cluster_ROUND_ROBIN
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_:
+		c.LbPolicy = envoy_api.Cluster_LEAST_REQUEST
+
+		lbConfig := e.Lb.GetLeastRequest()
+		c.LbConfig = &envoy_api.Cluster_LeastRequestLbConfig_{
+			LeastRequestLbConfig: &envoy_api.Cluster_LeastRequestLbConfig{
+				ChoiceCount: &wrappers.UInt32Value{
+					Value: lbConfig.ChoiceCount,
+				},
+			},
+		}
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_RingHash_:
+		c.LbPolicy = envoy_api.Cluster_RING_HASH
+
+		lbConfig := e.Lb.GetRingHash()
+		hashfn, ok := envoy_api.Cluster_RingHashLbConfig_HashFunction_value[lbConfig.HashFunction]
+		if !ok {
+			return errors.New(fmt.Sprintf("Invalid ring hash function %s", lbConfig.HashFunction))
+		}
+
+		c.LbConfig = &envoy_api.Cluster_RingHashLbConfig_{
+			RingHashLbConfig: &envoy_api.Cluster_RingHashLbConfig{
+				HashFunction: envoy_api.Cluster_RingHashLbConfig_HashFunction(hashfn),
+				MinimumRingSize: &wrappers.UInt64Value{
+					Value: lbConfig.MinRingSize,
+				},
+				MaximumRingSize: &wrappers.UInt64Value{
+					Value: lbConfig.MaxRingSize,
+				},
+			},
+		}
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_Random_:
+		c.LbPolicy = envoy_api.Cluster_RANDOM
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_Maglev_:
+		c.LbPolicy = envoy_api.Cluster_MAGLEV
+	}
+
+	return nil
+}
diff --git a/pkg/xds/envoy/clusters/v3/lb_configurer.go b/pkg/xds/envoy/clusters/v3/lb_configurer.go
new file mode 100644
index 000000000..a9e8fbb4c
--- /dev/null
+++ b/pkg/xds/envoy/clusters/v3/lb_configurer.go
@@ -0,0 +1,71 @@
+package clusters
+
+import (
+	"fmt"
+
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+
+	envoy_cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
+	"github.com/golang/protobuf/ptypes/wrappers"
+	"github.com/pkg/errors"
+)
+
+type LbConfigurer struct {
+	Lb *mesh_proto.TrafficRoute_LoadBalancer
+}
+
+var _ ClusterConfigurer = &LbConfigurer{}
+
+func (e *LbConfigurer) Configure(c *envoy_cluster.Cluster) error {
+	// default to Round Robin
+	if e.Lb.GetLbType() == nil {
+		c.LbPolicy = envoy_cluster.Cluster_ROUND_ROBIN
+		return nil
+	}
+
+	switch e.Lb.GetLbType().(type) {
+	case *mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_:
+		c.LbPolicy = envoy_cluster.Cluster_ROUND_ROBIN
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_:
+		c.LbPolicy = envoy_cluster.Cluster_LEAST_REQUEST
+
+		lbConfig := e.Lb.GetLeastRequest()
+		c.LbConfig = &envoy_cluster.Cluster_LeastRequestLbConfig_{
+			LeastRequestLbConfig: &envoy_cluster.Cluster_LeastRequestLbConfig{
+				ChoiceCount: &wrappers.UInt32Value{
+					Value: lbConfig.ChoiceCount,
+				},
+			},
+		}
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_RingHash_:
+		c.LbPolicy = envoy_cluster.Cluster_RING_HASH
+
+		lbConfig := e.Lb.GetRingHash()
+		hashfn, ok := envoy_cluster.Cluster_RingHashLbConfig_HashFunction_value[lbConfig.HashFunction]
+		if !ok {
+			return errors.New(fmt.Sprintf("Invalid ring hash function %s", lbConfig.HashFunction))
+		}
+
+		c.LbConfig = &envoy_cluster.Cluster_RingHashLbConfig_{
+			RingHashLbConfig: &envoy_cluster.Cluster_RingHashLbConfig{
+				HashFunction: envoy_cluster.Cluster_RingHashLbConfig_HashFunction(hashfn),
+				MinimumRingSize: &wrappers.UInt64Value{
+					Value: lbConfig.MinRingSize,
+				},
+				MaximumRingSize: &wrappers.UInt64Value{
+					Value: lbConfig.MaxRingSize,
+				},
+			},
+		}
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_Random_:
+		c.LbPolicy = envoy_cluster.Cluster_RANDOM
+
+	case *mesh_proto.TrafficRoute_LoadBalancer_Maglev_:
+		c.LbPolicy = envoy_cluster.Cluster_MAGLEV
+	}
+
+	return nil
+}
diff --git a/pkg/xds/envoy/types.go b/pkg/xds/envoy/types.go
index 26c84dc30..c2ae8635b 100644
--- a/pkg/xds/envoy/types.go
+++ b/pkg/xds/envoy/types.go
@@ -5,6 +5,8 @@ import (
 	"sort"
 	"strings"
 
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+
 	envoy_types "github.com/envoyproxy/go-control-plane/pkg/cache/types"
 	"github.com/pkg/errors"
 )
@@ -14,6 +16,7 @@ type ClusterSubset struct {
 	Weight            uint32
 	Tags              Tags
 	IsExternalService bool
+	Lb                *mesh_proto.TrafficRoute_LoadBalancer
 }
 
 type Tags map[string]string
@@ -87,6 +90,7 @@ func DistinctTags(tags []Tags) []Tags {
 type Cluster struct {
 	subsets            []ClusterSubset
 	hasExternalService bool
+	lb                 *mesh_proto.TrafficRoute_LoadBalancer
 }
 
 func (c *Cluster) Add(subset ClusterSubset) {
@@ -94,6 +98,7 @@ func (c *Cluster) Add(subset ClusterSubset) {
 	if subset.IsExternalService {
 		c.hasExternalService = true
 	}
+	c.lb = subset.Lb
 }
 
 func (c *Cluster) Tags() []Tags {
@@ -140,6 +145,10 @@ func (c Clusters) Tags(name string) []Tags {
 	return c[name].Tags()
 }
 
+func (c Clusters) Lb(name string) *mesh_proto.TrafficRoute_LoadBalancer {
+	return c[name].lb
+}
+
 type NamedResource interface {
 	envoy_types.Resource
 	GetName() string
diff --git a/pkg/xds/generator/outbound_proxy_generator.go b/pkg/xds/generator/outbound_proxy_generator.go
index ec81b805c..c5e239e19 100644
--- a/pkg/xds/generator/outbound_proxy_generator.go
+++ b/pkg/xds/generator/outbound_proxy_generator.go
@@ -155,6 +155,7 @@ func (o OutboundProxyGenerator) generateCDS(ctx xds_context.Context, proxy *mode
 	for _, clusterName := range clusters.ClusterNames() {
 		serviceName := clusters.Tags(clusterName)[0][kuma_mesh.ServiceTag]
 		tags := clusters.Tags(clusterName)
+		lb := clusters.Lb(clusterName)
 		healthCheck := proxy.Policies.HealthChecks[serviceName]
 		circuitBreaker := proxy.Policies.CircuitBreakers[serviceName]
 		edsClusterBuilder := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
@@ -175,6 +176,7 @@ func (o OutboundProxyGenerator) generateCDS(ctx xds_context.Context, proxy *mode
 		} else {
 			edsClusterBuilder = edsClusterBuilder.
 				Configure(envoy_clusters.EdsCluster(clusterName)).
+				Configure(envoy_clusters.LB(lb)).
 				Configure(envoy_clusters.ClientSideMTLS(ctx, proxy.Metadata, serviceName, tags)).
 				Configure(envoy_clusters.Http2())
 		}
@@ -271,6 +273,8 @@ func (_ OutboundProxyGenerator) determineSubsets(proxy *model.Proxy, outbound *k
 			}
 		}
 
+		subset.Lb = route.Spec.GetConf().GetLoadBalancer()
+
 		subsets = append(subsets, subset)
 	}
 	return
diff --git a/pkg/xds/server/callbacks/dataplane_status_tracker.go b/pkg/xds/server/callbacks/dataplane_status_tracker.go
index eea8f9cbd..b1084ff51 100644
--- a/pkg/xds/server/callbacks/dataplane_status_tracker.go
+++ b/pkg/xds/server/callbacks/dataplane_status_tracker.go
@@ -182,6 +182,9 @@ func (s *streamState) Close() {
 }
 
 func readVersion(metadata *pstruct.Struct, version *mesh_proto.Version) error {
+	if metadata == nil {
+		return nil
+	}
 	rawVersion := metadata.Fields["version"].GetStructValue()
 	if rawVersion != nil {
 		err := util_proto.ToTyped(rawVersion, version)


Test patch:
diff --git a/app/kuma-dp/cmd/run_test.go b/app/kuma-dp/cmd/run_test.go
index b868df163..c5dcbd687 100644
--- a/app/kuma-dp/cmd/run_test.go
+++ b/app/kuma-dp/cmd/run_test.go
@@ -14,6 +14,7 @@ import (
 	"syscall"
 
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
+	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
@@ -33,10 +34,10 @@ var _ = Describe("run", func() {
 	BeforeEach(func() {
 		backupSetupSignalHandler = core.SetupSignalHandler
 		backupBootstrapGenerator = bootstrapGenerator
-		bootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, version envoy.EnvoyVersion) ([]byte, error) {
+		bootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, _ types.BootstrapVersion, version envoy.EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
 			respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "bootstrap-config.golden.yaml"))
 			Expect(err).ToNot(HaveOccurred())
-			return respBytes, nil
+			return respBytes, "", nil
 		}
 	})
 	AfterEach(func() {
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
index 76cd3f8df..93d792e57 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
@@ -17,6 +17,7 @@ import (
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
+	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
 
 var _ = Describe("Envoy", func() {
@@ -68,9 +69,9 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
 				return []byte(`node:
-  id: example`), nil
+  id: example`), types.BootstrapV2, nil
 			}
 			expectedConfigFile := filepath.Join(configDir, "bootstrap.yaml")
 
@@ -112,7 +113,7 @@ var _ = Describe("Envoy", func() {
 			// then
 			Expect(err).ToNot(HaveOccurred())
 			// and
-			Expect(strings.TrimSpace(buf.String())).To(Equal(fmt.Sprintf("-c %s --drain-time-s 15 --disable-hot-restart", expectedConfigFile)))
+			Expect(strings.TrimSpace(buf.String())).To(Equal(fmt.Sprintf("-c %s --drain-time-s 15 --disable-hot-restart --bootstrap-version 2", expectedConfigFile)))
 
 			By("verifying the contents Envoy config file")
 			// when
@@ -136,8 +137,8 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
-				return nil, nil
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+				return nil, "", nil
 			}
 
 			By("starting a mock dataplane")
@@ -178,8 +179,8 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
-				return nil, nil
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, types.BootstrapVersion, EnvoyVersion) ([]byte, types.BootstrapVersion, error) {
+				return nil, "", nil
 			}
 
 			By("starting a mock dataplane")
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
index 065150c98..cf8f3cd35 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap_test.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	kuma_version "github.com/kumahq/kuma/pkg/version"
+	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
@@ -26,6 +27,7 @@ var _ = Describe("Remote Bootstrap", func() {
 
 	type testCase struct {
 		config                   kuma_dp.Config
+		bootstrapVersion         types.BootstrapVersion
 		dataplane                *rest.Resource
 		expectedBootstrapRequest string
 	}
@@ -50,6 +52,7 @@ var _ = Describe("Remote Bootstrap", func() {
 			Expect(err).ToNot(HaveOccurred())
 			Expect(body).To(MatchJSON(given.expectedBootstrapRequest))
 
+			writer.Header().Set(types.BootstrapVersionHeader, string(given.bootstrapVersion))
 			response, err := ioutil.ReadFile(filepath.Join("testdata", "remote-bootstrap-config.golden.yaml"))
 			Expect(err).ToNot(HaveOccurred())
 			_, err = writer.Write(response)
@@ -62,12 +65,13 @@ var _ = Describe("Remote Bootstrap", func() {
 		generator := NewRemoteBootstrapGenerator(http.DefaultClient)
 
 		// when
-		config, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, given.dataplane, EnvoyVersion{
+		config, version, err := generator(fmt.Sprintf("http://localhost:%d", port), given.config, given.dataplane, given.bootstrapVersion, EnvoyVersion{
 			Build:   "hash/1.15.0/RELEASE",
 			Version: "1.15.0",
 		})
 
 		// then
+		Expect(version).To(Equal(given.bootstrapVersion))
 		Expect(err).ToNot(HaveOccurred())
 		Expect(config).ToNot(BeNil())
 	},
@@ -80,7 +84,8 @@ var _ = Describe("Remote Bootstrap", func() {
 				cfg.DataplaneRuntime.TokenPath = "/tmp/token"
 
 				return testCase{
-					config: cfg,
+					config:           cfg,
+					bootstrapVersion: "2",
 					dataplane: &rest.Resource{
 						Meta: rest.ResourceMeta{
 							Type: "Dataplane",
@@ -106,7 +111,8 @@ var _ = Describe("Remote Bootstrap", func() {
 						  "version": "1.15.0",
 						  "build": "hash/1.15.0/RELEASE"
 						}
-					  }
+					  },
+					  "bootstrapVersion": "2"
 					}`,
 				}
 			}()),
@@ -128,6 +134,7 @@ var _ = Describe("Remote Bootstrap", func() {
 							Name: "sample",
 						},
 					},
+					bootstrapVersion: "3",
 					expectedBootstrapRequest: `
                     {
                       "mesh": "demo",
@@ -146,7 +153,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "version": "1.15.0",
                           "build": "hash/1.15.0/RELEASE"
                         }
-                      }
+                      },
+                      "bootstrapVersion": "3"
                     }`,
 				}
 			}()),
@@ -184,7 +192,8 @@ var _ = Describe("Remote Bootstrap", func() {
                           "version": "1.15.0",
                           "build": "hash/1.15.0/RELEASE"
                         }
-                      }
+                      },
+                      "bootstrapVersion": ""
                     }`,
 				}
 			}()),
@@ -217,13 +226,13 @@ var _ = Describe("Remote Bootstrap", func() {
 		// when
 		cfg := kuma_dp.DefaultConfig()
 		cfg.ControlPlane.Retry.Backoff = 10 * time.Millisecond
-		_, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, &rest.Resource{
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), cfg, &rest.Resource{
 			Meta: rest.ResourceMeta{
 				Type: "Dataplane",
 				Mesh: "default",
 				Name: "dp-1",
 			},
-		}, EnvoyVersion{})
+		}, "", EnvoyVersion{})
 
 		// then
 		Expect(err).ToNot(HaveOccurred())
@@ -249,9 +258,9 @@ var _ = Describe("Remote Bootstrap", func() {
 		config := kuma_dp.DefaultConfig()
 		config.ControlPlane.Retry.Backoff = 10 * time.Millisecond
 		config.ControlPlane.Retry.MaxDuration = 100 * time.Millisecond
-		_, err = generator(fmt.Sprintf("http://localhost:%d", port), config, &rest.Resource{
+		_, _, err = generator(fmt.Sprintf("http://localhost:%d", port), config, &rest.Resource{
 			Meta: rest.ResourceMeta{Mesh: "default", Name: "dp-1"},
-		}, EnvoyVersion{})
+		}, "", EnvoyVersion{})
 
 		// then
 		Expect(err).To(MatchError("retryable: Dataplane entity not found. If you are running on Universal please create a Dataplane entity on kuma-cp before starting kuma-dp. If you are running on Kubernetes, please check the kuma-cp logs to determine why the Dataplane entity could not be created by the automatic sidecar injection."))
diff --git a/app/kumactl/cmd/inspect/inspect_zones_test.go b/app/kumactl/cmd/inspect/inspect_zones_test.go
index e2ca26210..0c26c1dda 100644
--- a/app/kumactl/cmd/inspect/inspect_zones_test.go
+++ b/app/kumactl/cmd/inspect/inspect_zones_test.go
@@ -61,7 +61,6 @@ var _ = Describe("kumactl inspect zones", func() {
 		sampleZoneOverview = []*system_core.ZoneOverviewResource{
 			{
 				Meta: &test_model.ResourceMeta{
-					Mesh:             "default",
 					Name:             "zone-1",
 					CreationTime:     t1,
 					ModificationTime: now,
@@ -126,6 +125,14 @@ var _ = Describe("kumactl inspect zones", func() {
 										},
 									},
 								},
+								Version: &system_proto.Version{
+									KumaCp: &system_proto.KumaCpVersion{
+										Version:   "1.0.0",
+										GitTag:    "v1.0.0",
+										GitCommit: "91ce236824a9d875601679aa80c63783fb0e8725",
+										BuildDate: "2019-08-07T11:26:06Z",
+									},
+								},
 							},
 							{
 								Id:               "2",
@@ -137,6 +144,14 @@ var _ = Describe("kumactl inspect zones", func() {
 										ResponsesRejected: 2,
 									},
 								},
+								Version: &system_proto.Version{
+									KumaCp: &system_proto.KumaCpVersion{
+										Version:   "1.0.0",
+										GitTag:    "v1.0.0",
+										GitCommit: "91ce236824a9d875601679aa80c63783fb0e8725",
+										BuildDate: "2019-08-07T11:26:06Z",
+									},
+								},
 							},
 						},
 					},
@@ -144,7 +159,6 @@ var _ = Describe("kumactl inspect zones", func() {
 			},
 			{
 				Meta: &test_model.ResourceMeta{
-					Mesh:             "default",
 					Name:             "zone-2",
 					CreationTime:     t1,
 					ModificationTime: now,
@@ -171,7 +185,6 @@ var _ = Describe("kumactl inspect zones", func() {
 			},
 			{
 				Meta: &test_model.ResourceMeta{
-					Mesh:             "default",
 					Name:             "zone-3",
 					CreationTime:     t1,
 					ModificationTime: now,
@@ -184,6 +197,14 @@ var _ = Describe("kumactl inspect zones", func() {
 								Id:               "1",
 								GlobalInstanceId: "node-001",
 								ConnectTime:      util_proto.MustTimestampProto(t2),
+								Version: &system_proto.Version{
+									KumaCp: &system_proto.KumaCpVersion{
+										Version:   "1.0.0",
+										GitTag:    "v1.0.0",
+										GitCommit: "91ce236824a9d875601679aa80c63783fb0e8725",
+										BuildDate: "2019-08-07T11:26:06Z",
+									},
+								},
 							},
 						},
 					},
diff --git a/app/kumactl/cmd/inspect/testdata/inspect-zone.golden.yaml b/app/kumactl/cmd/inspect/testdata/inspect-zone.golden.yaml
index 531d97006..b13394f9c 100644
--- a/app/kumactl/cmd/inspect/testdata/inspect-zone.golden.yaml
+++ b/app/kumactl/cmd/inspect/testdata/inspect-zone.golden.yaml
@@ -48,6 +48,12 @@ items:
             total:
               responsesRejected: "11"
               responsesSent: "22"
+          version:
+            kumaCp:
+              buildDate: "2019-08-07T11:26:06Z"
+              gitCommit: 91ce236824a9d875601679aa80c63783fb0e8725
+              gitTag: v1.0.0
+              version: 1.0.0
         - connectTime: "2019-07-17T16:05:36.995Z"
           globalInstanceId: node-002
           id: "2"
@@ -55,6 +61,12 @@ items:
             total:
               responsesRejected: "2"
               responsesSent: "20"
+          version:
+            kumaCp:
+              buildDate: "2019-08-07T11:26:06Z"
+              gitCommit: 91ce236824a9d875601679aa80c63783fb0e8725
+              gitTag: v1.0.0
+              version: 1.0.0
   - creationTime: "2018-07-17T16:05:36.995Z"
     modificationTime: "2019-07-17T18:08:41Z"
     name: zone-2
@@ -80,5 +92,11 @@ items:
         - connectTime: "2019-07-17T16:05:36.995Z"
           globalInstanceId: node-001
           id: "1"
+          version:
+            kumaCp:
+              buildDate: "2019-08-07T11:26:06Z"
+              gitCommit: 91ce236824a9d875601679aa80c63783fb0e8725
+              gitTag: v1.0.0
+              version: 1.0.0
 next: null
 total: 3
diff --git a/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.json b/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.json
index 6a22496d2..121766cd4 100644
--- a/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.json
+++ b/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.json
@@ -66,6 +66,14 @@
                   "responsesRejected": "1"
                 }
               }
+            },
+            "version": {
+              "kumaCp": {
+                "version": "1.0.0",
+                "gitTag": "v1.0.0",
+                "gitCommit": "91ce236824a9d875601679aa80c63783fb0e8725",
+                "buildDate": "2019-08-07T11:26:06Z"
+              }
             }
           },
           {
@@ -77,6 +85,14 @@
                 "responsesSent": "20",
                 "responsesRejected": "2"
               }
+            },
+            "version": {
+              "kumaCp": {
+                "version": "1.0.0",
+                "gitTag": "v1.0.0",
+                "gitCommit": "91ce236824a9d875601679aa80c63783fb0e8725",
+                "buildDate": "2019-08-07T11:26:06Z"
+              }
             }
           }
         ]
@@ -120,11 +136,19 @@
           {
             "id": "1",
             "globalInstanceId": "node-001",
-            "connectTime": "2019-07-17T16:05:36.995Z"
+            "connectTime": "2019-07-17T16:05:36.995Z",
+            "version": {
+              "kumaCp": {
+                "version": "1.0.0",
+                "gitTag": "v1.0.0",
+                "gitCommit": "91ce236824a9d875601679aa80c63783fb0e8725",
+                "buildDate": "2019-08-07T11:26:06Z"
+              }
+            }
           }
         ]
       }
     }
   ],
   "next": null
-}
+}
\ No newline at end of file
diff --git a/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.txt b/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.txt
index 3ad1cb3e6..29b244563 100644
--- a/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.txt
+++ b/app/kumactl/cmd/inspect/testdata/inspect-zones.golden.txt
@@ -1,4 +1,4 @@
-MESH      NAME     STATUS    LAST CONNECTED AGO   LAST UPDATED AGO   TOTAL UPDATES   TOTAL ERRORS
-default   zone-1   Online    2h                   never              42              13
-default   zone-2   Offline   never                never              0               0
-default   zone-3   Offline   2h                   never              0               0
\ No newline at end of file
+NAME     STATUS    LAST CONNECTED AGO   LAST UPDATED AGO   TOTAL UPDATES   TOTAL ERRORS   REMOTE-CP VERSION
+zone-1   Online    2h                   never              42              13             1.0.0
+zone-2   Offline   never                never              0               0              
+zone-3   Offline   2h                   never              0               0              1.0.0
\ No newline at end of file
diff --git a/pkg/config/app/kuma-dp/testdata/default-config.golden.yaml b/pkg/config/app/kuma-dp/testdata/default-config.golden.yaml
index e3538a6b0..02933b400 100644
--- a/pkg/config/app/kuma-dp/testdata/default-config.golden.yaml
+++ b/pkg/config/app/kuma-dp/testdata/default-config.golden.yaml
@@ -8,5 +8,6 @@ controlPlane:
 dataplane:
   mesh: default
   drainTime: 30s
+  bootstrapVersion: ""
 dataplaneRuntime:
   binaryPath: envoy
diff --git a/pkg/core/resources/apis/mesh/traffic_route_validator_test.go b/pkg/core/resources/apis/mesh/traffic_route_validator_test.go
index aefb4b9f2..ba5995836 100644
--- a/pkg/core/resources/apis/mesh/traffic_route_validator_test.go
+++ b/pkg/core/resources/apis/mesh/traffic_route_validator_test.go
@@ -161,6 +161,28 @@ var _ = Describe("TrafficRoute", func() {
                   message: must have at least one tag
                 - field: conf.split[1].destination
                   message: mandatory tag "kuma.io/service" is missing
+`,
+			}),
+			Entry("wrong ring hash function in the load balancer", testCase{
+				route: `
+                sources:
+                - match:
+                    kuma.io/service: '*'
+                destinations:
+                - match:
+                    kuma.io/service: '*'
+                conf:
+                  split:
+                  - destination:
+                      kuma.io/service: 'backend'
+                  loadBalancer:
+                    ringHash:
+                      hashFunction: 'INVALID_HASH_FUNCTION'
+`,
+				expected: `
+                violations:
+                - field: conf.loadBalancer.ringHash.hashFunction
+                  message: must have a valid hash function
 `,
 			}),
 		)
diff --git a/pkg/sds/server/v2/server_test.go b/pkg/sds/server/v2/server_test.go
index c6a0a2b56..95072a5b7 100644
--- a/pkg/sds/server/v2/server_test.go
+++ b/pkg/sds/server/v2/server_test.go
@@ -7,6 +7,8 @@ import (
 	"sync/atomic"
 	"time"
 
+	prometheus_client "github.com/prometheus/client_model/go"
+
 	"github.com/kumahq/kuma/pkg/xds/envoy/tls"
 
 	envoy_api "github.com/envoyproxy/go-control-plane/envoy/api/v2"
@@ -203,9 +205,13 @@ var _ = Describe("SDS Server", func() {
 		expirationSeconds := now.Load().(time.Time).Add(60 * time.Second).Unix()
 		Expect(dpInsight.Spec.MTLS.CertificateExpirationTime.Seconds).To(Equal(expirationSeconds))
 
-		// and metrics are published
-		Expect(test_metrics.FindMetric(metrics, "sds_cert_generation").GetCounter().GetValue()).To(Equal(1.0))
-		Expect(test_metrics.FindMetric(metrics, "sds_generation")).ToNot(BeNil())
+		// and metrics are published (metrics are published async, it does not have to be done before response is sent)
+		Eventually(func() float64 {
+			return test_metrics.FindMetric(metrics, "sds_cert_generation").GetCounter().GetValue()
+		}, "5s").Should(Equal(1.0))
+		Eventually(func() *prometheus_client.Metric {
+			return test_metrics.FindMetric(metrics, "sds_generation")
+		}, "5s").ShouldNot(BeNil())
 
 		close(done)
 	}, 10)
diff --git a/pkg/sds/server/v3/server_test.go b/pkg/sds/server/v3/server_test.go
index 4c0b80fd9..bfba74ab4 100644
--- a/pkg/sds/server/v3/server_test.go
+++ b/pkg/sds/server/v3/server_test.go
@@ -7,6 +7,8 @@ import (
 	"sync/atomic"
 	"time"
 
+	prometheus_client "github.com/prometheus/client_model/go"
+
 	"github.com/kumahq/kuma/pkg/xds/envoy/tls"
 
 	envoy_api_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
@@ -203,9 +205,13 @@ var _ = Describe("SDS Server", func() {
 		expirationSeconds := now.Load().(time.Time).Add(60 * time.Second).Unix()
 		Expect(dpInsight.Spec.MTLS.CertificateExpirationTime.Seconds).To(Equal(expirationSeconds))
 
-		// and metrics are published
-		Expect(test_metrics.FindMetric(metrics, "sds_cert_generation").GetCounter().GetValue()).To(Equal(1.0))
-		Expect(test_metrics.FindMetric(metrics, "sds_generation")).ToNot(BeNil())
+		// and metrics are published (metrics are published async, it does not have to be done before response is sent)
+		Eventually(func() float64 {
+			return test_metrics.FindMetric(metrics, "sds_cert_generation").GetCounter().GetValue()
+		}, "5s").Should(Equal(1.0))
+		Eventually(func() *prometheus_client.Metric {
+			return test_metrics.FindMetric(metrics, "sds_generation")
+		}, "5s").ShouldNot(BeNil())
 
 		close(done)
 	}, 10)
diff --git a/pkg/xds/bootstrap/generator_test.go b/pkg/xds/bootstrap/generator_test.go
index 7a9a1eaff..5e8a07a99 100644
--- a/pkg/xds/bootstrap/generator_test.go
+++ b/pkg/xds/bootstrap/generator_test.go
@@ -82,10 +82,11 @@ var _ = Describe("bootstrapGenerator", func() {
 	})
 
 	type testCase struct {
-		config             func() *bootstrap_config.BootstrapServerConfig
-		dpAuthEnabled      bool
-		request            types.BootstrapRequest
-		expectedConfigFile string
+		config                   func() *bootstrap_config.BootstrapServerConfig
+		dpAuthEnabled            bool
+		request                  types.BootstrapRequest
+		expectedConfigFile       string
+		expectedBootstrapVersion types.BootstrapVersion
 	}
 	DescribeTable("should generate bootstrap configuration",
 		func(given testCase) {
@@ -94,22 +95,20 @@ var _ = Describe("bootstrapGenerator", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			// when
-			bootstrapConfig, err := generator.Generate(context.Background(), given.request)
+			bootstrapConfig, version, err := generator.Generate(context.Background(), given.request)
+
 			// then
 			Expect(err).ToNot(HaveOccurred())
 
-			// when
+			// and config is as expected
 			actual, err := util_proto.ToYAML(bootstrapConfig)
-			// then
 			Expect(err).ToNot(HaveOccurred())
-
-			// when
 			expected, err := ioutil.ReadFile(filepath.Join("testdata", given.expectedConfigFile))
-			// then
 			Expect(err).ToNot(HaveOccurred())
-
-			// expect
 			Expect(actual).To(MatchYAML(expected))
+
+			// and
+			Expect(version).To(Equal(given.expectedBootstrapVersion))
 		},
 		Entry("default config with minimal request", testCase{
 			dpAuthEnabled: false,
@@ -125,7 +124,8 @@ var _ = Describe("bootstrapGenerator", func() {
 				Name:    "name.namespace",
 				Version: defaultVersion,
 			},
-			expectedConfigFile: "generator.default-config-minimal-request.golden.yaml",
+			expectedConfigFile:       "generator.default-config-minimal-request.golden.yaml",
+			expectedBootstrapVersion: types.BootstrapV2,
 		}),
 		Entry("default config", testCase{
 			dpAuthEnabled: true,
@@ -133,7 +133,7 @@ var _ = Describe("bootstrapGenerator", func() {
 				cfg := bootstrap_config.DefaultBootstrapServerConfig()
 				cfg.Params.XdsHost = "localhost"
 				cfg.Params.XdsPort = 5678
-				cfg.APIVersion = envoy_common.APIV2
+				cfg.APIVersion = envoy_common.APIV3
 				return cfg
 			},
 			request: types.BootstrapRequest{
@@ -143,7 +143,8 @@ var _ = Describe("bootstrapGenerator", func() {
 				DataplaneTokenPath: "/tmp/token",
 				Version:            defaultVersion,
 			},
-			expectedConfigFile: "generator.default-config.golden.yaml",
+			expectedConfigFile:       "generator.default-config.golden.yaml",
+			expectedBootstrapVersion: types.BootstrapV3,
 		}),
 		Entry("custom config with minimal request", testCase{
 			dpAuthEnabled: false,
@@ -157,15 +158,17 @@ var _ = Describe("bootstrapGenerator", func() {
 						XdsPort:            15678,
 						XdsConnectTimeout:  2 * time.Second,
 					},
-					APIVersion: envoy_common.APIV2,
+					APIVersion: envoy_common.APIV3,
 				}
 			},
 			request: types.BootstrapRequest{
-				Mesh:    "mesh",
-				Name:    "name.namespace",
-				Version: defaultVersion,
+				Mesh:             "mesh",
+				Name:             "name.namespace",
+				Version:          defaultVersion,
+				BootstrapVersion: types.BootstrapV2,
 			},
-			expectedConfigFile: "generator.custom-config-minimal-request.golden.yaml",
+			expectedConfigFile:       "generator.custom-config-minimal-request.golden.yaml",
+			expectedBootstrapVersion: types.BootstrapV2,
 		}),
 		Entry("custom config", testCase{
 			dpAuthEnabled: true,
@@ -210,7 +213,8 @@ var _ = Describe("bootstrapGenerator", func() {
 }`,
 				Version: defaultVersion,
 			},
-			expectedConfigFile: "generator.custom-config.golden.yaml",
+			expectedConfigFile:       "generator.custom-config.golden.yaml",
+			expectedBootstrapVersion: types.BootstrapV2,
 		}),
 	)
 
@@ -266,7 +270,7 @@ var _ = Describe("bootstrapGenerator", func() {
 		}
 
 		// when
-		_, err = generator.Generate(context.Background(), request)
+		_, _, err = generator.Generate(context.Background(), request)
 		// then
 		Expect(err).To(HaveOccurred())
 		// and
@@ -279,7 +283,7 @@ var _ = Describe("bootstrapGenerator", func() {
 		}
 
 		// when
-		_, err = generator.Generate(context.Background(), request)
+		_, _, err = generator.Generate(context.Background(), request)
 		// then
 		Expect(err).To(HaveOccurred())
 		// and
@@ -331,7 +335,7 @@ var _ = Describe("bootstrapGenerator", func() {
 		}
 
 		// when
-		_, err = generator.Generate(context.Background(), request)
+		_, _, err = generator.Generate(context.Background(), request)
 		// then
 		Expect(err).To(HaveOccurred())
 		// and
@@ -352,7 +356,7 @@ var _ = Describe("bootstrapGenerator", func() {
 		}
 
 		// when
-		_, err = generator.Generate(context.Background(), request)
+		_, _, err = generator.Generate(context.Background(), request)
 		// then
 		Expect(err).To(HaveOccurred())
 		// and
@@ -361,4 +365,27 @@ var _ = Describe("bootstrapGenerator", func() {
 2) Set KUMA_GENERAL_TLS_CERT_FILE and KUMA_GENERAL_TLS_KEY_FILE or the equivalent in Kuma CP config file to the new certificate.
 3) Restart the control plane to read the new certificate and start kuma-dp.`))
 	})
+
+	It("should fail bootstrap due to invalid bootstrap version", func() {
+		// given
+		cfg := bootstrap_config.DefaultBootstrapServerConfig()
+		cfg.Params.XdsHost = "localhost"
+		cfg.Params.XdsPort = 5678
+
+		generator, err := NewDefaultBootstrapGenerator(resManager, cfg, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
+		Expect(err).ToNot(HaveOccurred())
+		request := types.BootstrapRequest{
+			Mesh:             "mesh",
+			Name:             "name.namespace",
+			AdminPort:        9901,
+			BootstrapVersion: "5",
+		}
+
+		// when
+		_, _, err = generator.Generate(context.Background(), request)
+		// then
+		Expect(err).To(HaveOccurred())
+		// and
+		Expect(err.Error()).To(Equal(`Invalid BootstrapVersion. Available values are: "2", "3"`))
+	})
 })
diff --git a/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml b/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
index c476372a3..ad34f6e31 100644
--- a/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
@@ -13,7 +13,7 @@ dynamicResources:
             - fromPlugin:
                 name: envoy.grpc_credentials.file_based_metadata
                 typedConfig:
-                  '@type': type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig
+                  '@type': type.googleapis.com/envoy.config.grpc_credential.v3.FileBasedMetadataConfig
                   secretData:
                     filename: /tmp/token
           channelCredentials:
@@ -23,11 +23,13 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:5678
-    transportApiVersion: V2
+    transportApiVersion: V3
   cdsConfig:
     ads: {}
+    resourceApiVersion: V3
   ldsConfig:
     ads: {}
+    resourceApiVersion: V3
 node:
   cluster: backend
   id: mesh.name.namespace
@@ -35,14 +37,14 @@ node:
     dataplane.admin.port: "1234"
     dataplaneTokenPath: /tmp/token
     version:
-      kumaDp:
-        version: 0.0.1
-        gitTag: v0.0.1
-        gitCommit: 91ce236824a9d875601679aa80c63783fb0e8725
-        buildDate: 2019-08-07T11:26:06Z
       envoy:
-        version: 1.15.0
         build: hash/1.15.0/RELEASE
+        version: 1.15.0
+      kumaDp:
+        buildDate: "2019-08-07T11:26:06Z"
+        gitCommit: 91ce236824a9d875601679aa80c63783fb0e8725
+        gitTag: v0.0.1
+        version: 0.0.1
 staticResources:
   clusters:
     - connectTimeout: 1s
diff --git a/pkg/xds/envoy/clusters/v2/lb_configurer_test.go b/pkg/xds/envoy/clusters/v2/lb_configurer_test.go
new file mode 100644
index 000000000..c5abb7bef
--- /dev/null
+++ b/pkg/xds/envoy/clusters/v2/lb_configurer_test.go
@@ -0,0 +1,123 @@
+package clusters_test
+
+import (
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	util_proto "github.com/kumahq/kuma/pkg/util/proto"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
+	"github.com/kumahq/kuma/pkg/xds/envoy/clusters"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Lb", func() {
+
+	type testCase struct {
+		clusterName string
+		lb          *mesh_proto.TrafficRoute_LoadBalancer
+		expected    string
+	}
+
+	DescribeTable("should generate proper Envoy config",
+		func(given testCase) {
+			// when
+			cluster, err := clusters.NewClusterBuilder(envoy.APIV2).
+				Configure(clusters.EdsCluster(given.clusterName)).
+				Configure(clusters.LB(given.lb)).
+				Build()
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			actual, err := util_proto.ToYAML(cluster)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(actual).To(MatchYAML(given.expected))
+		},
+		Entry("round robin", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+            name: backend
+            type: EDS`,
+		}),
+		Entry("least request", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_{
+					LeastRequest: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest{
+						ChoiceCount: 4,
+					},
+				},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+            lbPolicy: LEAST_REQUEST
+            leastRequestLbConfig:
+              choiceCount: 4
+            name: backend
+            type: EDS`,
+		}),
+		Entry("ring hash", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_RingHash_{
+					RingHash: &mesh_proto.TrafficRoute_LoadBalancer_RingHash{
+						HashFunction: "MURMUR_HASH_2",
+						MinRingSize:  64,
+						MaxRingSize:  1024,
+					},
+				},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+            lbPolicy: RING_HASH
+            name: backend
+            ringHashLbConfig:
+              hashFunction: MURMUR_HASH_2
+              maximumRingSize: "1024"
+              minimumRingSize: "64"
+            type: EDS`,
+		}),
+		Entry("random", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_Random_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+            lbPolicy: RANDOM
+            name: backend
+            type: EDS`,
+		}),
+		Entry("random", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_Maglev_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+            lbPolicy: MAGLEV
+            name: backend
+            type: EDS`,
+		}),
+	)
+})
diff --git a/pkg/xds/envoy/clusters/v3/lb_configurer_test.go b/pkg/xds/envoy/clusters/v3/lb_configurer_test.go
new file mode 100644
index 000000000..16df6436c
--- /dev/null
+++ b/pkg/xds/envoy/clusters/v3/lb_configurer_test.go
@@ -0,0 +1,128 @@
+package clusters_test
+
+import (
+	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
+	util_proto "github.com/kumahq/kuma/pkg/util/proto"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
+	"github.com/kumahq/kuma/pkg/xds/envoy/clusters"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Lb", func() {
+
+	type testCase struct {
+		clusterName string
+		lb          *mesh_proto.TrafficRoute_LoadBalancer
+		expected    string
+	}
+
+	DescribeTable("should generate proper Envoy config",
+		func(given testCase) {
+			// when
+			cluster, err := clusters.NewClusterBuilder(envoy.APIV3).
+				Configure(clusters.EdsCluster(given.clusterName)).
+				Configure(clusters.LB(given.lb)).
+				Build()
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			actual, err := util_proto.ToYAML(cluster)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(actual).To(MatchYAML(given.expected))
+		},
+		Entry("round robin", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            name: backend
+            type: EDS`,
+		}),
+		Entry("least request", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_{
+					LeastRequest: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest{
+						ChoiceCount: 4,
+					},
+				},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            lbPolicy: LEAST_REQUEST
+            leastRequestLbConfig:
+              choiceCount: 4
+            name: backend
+            type: EDS`,
+		}),
+		Entry("ring hash", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_RingHash_{
+					RingHash: &mesh_proto.TrafficRoute_LoadBalancer_RingHash{
+						HashFunction: "MURMUR_HASH_2",
+						MinRingSize:  64,
+						MaxRingSize:  1024,
+					},
+				},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            lbPolicy: RING_HASH
+            name: backend
+            ringHashLbConfig:
+              hashFunction: MURMUR_HASH_2
+              maximumRingSize: "1024"
+              minimumRingSize: "64"
+            type: EDS`,
+		}),
+		Entry("random", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_Random_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            lbPolicy: RANDOM
+            name: backend
+            type: EDS`,
+		}),
+		Entry("random", testCase{
+			clusterName: "backend",
+			lb: &mesh_proto.TrafficRoute_LoadBalancer{
+				LbType: &mesh_proto.TrafficRoute_LoadBalancer_Maglev_{},
+			},
+			expected: `
+            connectTimeout: 5s
+            edsClusterConfig:
+              edsConfig:
+                ads: {}
+                resourceApiVersion: V3
+            lbPolicy: MAGLEV
+            name: backend
+            type: EDS`,
+		}),
+	)
+})
diff --git a/pkg/xds/generator/outbound_proxy_generator_test.go b/pkg/xds/generator/outbound_proxy_generator_test.go
index 0e629579a..66912dd35 100644
--- a/pkg/xds/generator/outbound_proxy_generator_test.go
+++ b/pkg/xds/generator/outbound_proxy_generator_test.go
@@ -181,6 +181,9 @@ var _ = Describe("OutboundProxyGenerator", func() {
 										Weight:      100,
 										Destination: mesh_proto.MatchService("api-http"),
 									}},
+									LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+										LbType: &mesh_proto.TrafficRoute_LoadBalancer_RoundRobin_{},
+									},
 								},
 							},
 						},
@@ -194,6 +197,13 @@ var _ = Describe("OutboundProxyGenerator", func() {
 										Weight:      100,
 										Destination: mesh_proto.MatchService("api-tcp"),
 									}},
+									LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+										LbType: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest_{
+											LeastRequest: &mesh_proto.TrafficRoute_LoadBalancer_LeastRequest{
+												ChoiceCount: 4,
+											},
+										},
+									},
 								},
 							},
 						},
@@ -207,6 +217,15 @@ var _ = Describe("OutboundProxyGenerator", func() {
 										Weight:      100,
 										Destination: mesh_proto.MatchService("api-http2"),
 									}},
+									LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+										LbType: &mesh_proto.TrafficRoute_LoadBalancer_RingHash_{
+											RingHash: &mesh_proto.TrafficRoute_LoadBalancer_RingHash{
+												HashFunction: "MURMUR_HASH_2",
+												MinRingSize:  64,
+												MaxRingSize:  1024,
+											},
+										},
+									},
 								},
 							},
 						},
@@ -220,6 +239,9 @@ var _ = Describe("OutboundProxyGenerator", func() {
 										Weight:      100,
 										Destination: mesh_proto.MatchService("api-grpc"),
 									}},
+									LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+										LbType: &mesh_proto.TrafficRoute_LoadBalancer_Random_{},
+									},
 								},
 							},
 						},
@@ -233,6 +255,9 @@ var _ = Describe("OutboundProxyGenerator", func() {
 										Weight:      100,
 										Destination: mesh_proto.MatchService("backend"),
 									}},
+									LoadBalancer: &mesh_proto.TrafficRoute_LoadBalancer{
+										LbType: &mesh_proto.TrafficRoute_LoadBalancer_Maglev_{},
+									},
 								},
 							},
 						},
diff --git a/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
index 6c76b3b44..ef779ace7 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/03.envoy.golden.yaml
@@ -151,6 +151,7 @@ resources:
         edsConfig:
           ads: { }
       http2ProtocolOptions: { }
+      lbPolicy: RANDOM
       name: api-grpc
       type: EDS
   - name: api-http
@@ -177,7 +178,12 @@ resources:
         edsConfig:
           ads: {}
       http2ProtocolOptions: {}
+      lbPolicy: RING_HASH
       name: api-http2
+      ringHashLbConfig:
+        hashFunction: MURMUR_HASH_2
+        maximumRingSize: "1024"
+        minimumRingSize: "64"
       type: EDS
   - name: api-tcp
     resource:
@@ -187,6 +193,9 @@ resources:
         edsConfig:
           ads: {}
       http2ProtocolOptions: {}
+      lbPolicy: LEAST_REQUEST
+      leastRequestLbConfig:
+        choiceCount: 4
       name: api-tcp
       type: EDS
   - name: backend
@@ -197,6 +206,7 @@ resources:
         edsConfig:
           ads: {}
       http2ProtocolOptions: {}
+      lbPolicy: MAGLEV
       name: backend
       type: EDS
   - name: db
diff --git a/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml b/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
index e017342a2..ff94805c2 100644
--- a/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
+++ b/pkg/xds/generator/testdata/outbound-proxy/04.envoy.golden.yaml
@@ -167,6 +167,9 @@ resources:
         edsConfig:
           ads: {}
       http2ProtocolOptions: {}
+      lbPolicy: LEAST_REQUEST
+      leastRequestLbConfig:
+        choiceCount: 4
       name: api-tcp
       transportSocket:
         name: envoy.transport_sockets.tls
@@ -213,6 +216,7 @@ resources:
         edsConfig:
           ads: {}
       http2ProtocolOptions: {}
+      lbPolicy: MAGLEV
       name: backend
       transportSocket:
         name: envoy.transport_sockets.tls
@@ -390,11 +394,11 @@ resources:
                   - name: envoy.filters.http.router
                 routeConfig:
                   name: outbound:api-http
-                  validateClusters: false
                   requestHeadersToAdd:
                     - header:
                         key: x-kuma-tags
                         value: '&kuma.io/service=web&'
+                  validateClusters: false
                   virtualHosts:
                     - domains:
                         - '*'