Repository: kumahq/kuma
Pull request #1401 (2021-01-11 09:07:48)

chore(kuma-cp) refactor to get rid of explicit V2 and parametrize api version (bp #1379)
This is an automated backport of pull request #1379 done by Mergify
---


<details>
<summary>Mergify commands and options</summary>

<br />

More conditions and actions can be found in the [documentation](https://docs.mergify.io/).

You can also trigger Mergify actions by commenting on this pull request:

- `@Mergifyio refresh` will re-evaluate the rules
- `@Mergifyio rebase` will rebase this PR on its base branch
- `@Mergifyio update` will merge the base branch into this PR
- `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch

Additionally, on Mergify [dashboard](https://dashboard.mergify.io/) you can:

- look at your merge queues
- generate the Mergify configuration with the config editor.

Finally, you can contact us on https://mergify.io/
</details>



Patch:
diff --git a/api/mesh/v1alpha1/dataplane_insight_helpers.go b/api/mesh/v1alpha1/dataplane_insight_helpers.go
index 522514776..3126454ea 100644
--- a/api/mesh/v1alpha1/dataplane_insight_helpers.go
+++ b/api/mesh/v1alpha1/dataplane_insight_helpers.go
@@ -1,9 +1,9 @@
 package v1alpha1
 
 import (
+	"strings"
 	"time"
 
-	envoy_resource "github.com/envoyproxy/go-control-plane/pkg/resource/v2"
 	"github.com/golang/protobuf/ptypes"
 )
 
@@ -111,23 +111,24 @@ func (s *DiscoverySubscriptionStatus) StatsOf(typeUrl string) *DiscoveryServiceS
 	if s == nil {
 		return &DiscoveryServiceStats{}
 	}
-	switch typeUrl {
-	case envoy_resource.ClusterType:
+	// we rely on type URL suffix to get rid of the dependency on concrete V2 / V3 implementation
+	switch {
+	case strings.HasSuffix(typeUrl, "Cluster"):
 		if s.Cds == nil {
 			s.Cds = &DiscoveryServiceStats{}
 		}
 		return s.Cds
-	case envoy_resource.EndpointType:
+	case strings.HasSuffix(typeUrl, "ClusterLoadAssignment"):
 		if s.Eds == nil {
 			s.Eds = &DiscoveryServiceStats{}
 		}
 		return s.Eds
-	case envoy_resource.ListenerType:
+	case strings.HasSuffix(typeUrl, "Listener"):
 		if s.Lds == nil {
 			s.Lds = &DiscoveryServiceStats{}
 		}
 		return s.Lds
-	case envoy_resource.RouteType:
+	case strings.HasSuffix(typeUrl, "RouteConfiguration"):
 		if s.Rds == nil {
 			s.Rds = &DiscoveryServiceStats{}
 		}
diff --git a/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file.go b/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file.go
index 15fdd7df7..d4807dc2d 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file.go
@@ -5,26 +5,14 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
-	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 )
 
-func GenerateBootstrapFile(cfg kuma_dp.DataplaneRuntime, config proto.Message) (string, error) {
-	switch v := config.(type) {
-	case (interface{ Validate() error }):
-		if err := v.Validate(); err != nil {
-			return "", errors.Wrap(err, "Envoy bootstrap config is not valid")
-		}
-	}
-	data, err := util_proto.ToYAML(config)
-	if err != nil {
-		return "", errors.Wrap(err, "failed to marshal Envoy config")
-	}
+func GenerateBootstrapFile(cfg kuma_dp.DataplaneRuntime, config []byte) (string, error) {
 	configFile := filepath.Join(cfg.ConfigDir, "bootstrap.yaml")
-	if err := writeFile(configFile, data, 0600); err != nil {
+	if err := writeFile(configFile, config, 0600); err != nil {
 		return "", errors.Wrap(err, "failed to persist Envoy bootstrap config on disk")
 	}
 	return configFile, nil
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy.go b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
index af2bf4d5b..1bdc83a0e 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy.go
@@ -13,7 +13,6 @@ import (
 
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 
-	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
@@ -25,7 +24,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) (proto.Message, error)
+type BootstrapConfigFactoryFunc func(url string, cfg kuma_dp.Config, dp *rest.Resource, ev EnvoyVersion) ([]byte, error)
 
 type Opts struct {
 	Config    kuma_dp.Config
diff --git a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
index 087857efd..45a7aba44 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/remote_bootstrap.go
@@ -11,18 +11,12 @@ import (
 	net_url "net/url"
 	"strings"
 
-	envoy_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
-	// import only limited proto definitions from Go Control Plane to save kuma-dp size
-	_ "github.com/envoyproxy/go-control-plane/envoy/config/grpc_credential/v2alpha"
-	_ "github.com/envoyproxy/go-control-plane/envoy/config/grpc_credential/v3"
-	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 	"github.com/sethvargo/go-retry"
 
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 	"github.com/kumahq/kuma/pkg/core"
 	rest_types "github.com/kumahq/kuma/pkg/core/resources/model/rest"
-	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	kuma_version "github.com/kumahq/kuma/pkg/version"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap/types"
 )
@@ -49,7 +43,7 @@ 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) (proto.Message, error) {
+func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_types.Resource, ev EnvoyVersion) ([]byte, error) {
 	bootstrapUrl, err := net_url.Parse(url)
 	if err != nil {
 		return nil, err
@@ -97,13 +91,7 @@ func (b *remoteBootstrap) Generate(url string, cfg kuma_dp.Config, dp *rest_type
 	if err != nil {
 		return nil, err
 	}
-
-	bootstrap := envoy_bootstrap.Bootstrap{}
-	if err := util_proto.FromYAML(respBytes, &bootstrap); err != nil {
-		return nil, errors.Wrap(err, "could not parse the bootstrap configuration")
-	}
-
-	return &bootstrap, nil
+	return respBytes, nil
 }
 
 func (b *remoteBootstrap) requestForBootstrap(url *net_url.URL, cfg kuma_dp.Config, dp *rest_types.Resource, ev EnvoyVersion) ([]byte, error) {
diff --git a/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml b/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml
index 40c246c59..b2c1a7675 100644
--- a/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml
+++ b/pkg/config/app/kuma-cp/kuma-cp.defaults.yaml
@@ -65,6 +65,8 @@ store:
 
 # Configuration of Bootstrap Server, which provides bootstrap config to Dataplanes
 bootstrapServer:
+  # The version of Envoy API (available: "v2", "v3")
+  apiVersion: v2
   # Parameters of bootstrap configuration
   params:
     # Address of Envoy Admin
diff --git a/pkg/config/xds/bootstrap/config.go b/pkg/config/xds/bootstrap/config.go
index 85e81d60e..1bc6221dd 100644
--- a/pkg/config/xds/bootstrap/config.go
+++ b/pkg/config/xds/bootstrap/config.go
@@ -7,11 +7,14 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/kumahq/kuma/pkg/config"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 )
 
 var _ config.Config = &BootstrapServerConfig{}
 
 type BootstrapServerConfig struct {
+	// The version of Envoy API (available: "v2", "v3")
+	APIVersion envoy_common.APIVersion `yaml:"apiVersion" envconfig:"kuma_bootstrap_server_api_version"`
 	// Parameters of bootstrap configuration
 	Params *BootstrapParamsConfig `yaml:"params"`
 }
@@ -24,12 +27,18 @@ func (b *BootstrapServerConfig) Validate() error {
 	if err := b.Params.Validate(); err != nil {
 		return errors.Wrap(err, "Params validation failed")
 	}
+	switch b.APIVersion {
+	case envoy_common.APIV2, envoy_common.APIV3:
+	default:
+		return errors.Errorf("APIVersion has invalid value. Available values: %q, %q", envoy_common.APIV2, envoy_common.APIV3)
+	}
 	return nil
 }
 
 func DefaultBootstrapServerConfig() *BootstrapServerConfig {
 	return &BootstrapServerConfig{
-		Params: DefaultBootstrapParamsConfig(),
+		APIVersion: envoy_common.APIV2,
+		Params:     DefaultBootstrapParamsConfig(),
 	}
 }
 
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index f71adf3b5..9b8f2dc0d 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -37,7 +37,6 @@ import (
 	runtime_reports "github.com/kumahq/kuma/pkg/core/runtime/reports"
 	secret_cipher "github.com/kumahq/kuma/pkg/core/secrets/cipher"
 	secret_manager "github.com/kumahq/kuma/pkg/core/secrets/manager"
-	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	"github.com/kumahq/kuma/pkg/metrics"
 )
 
@@ -86,8 +85,6 @@ func buildRuntime(cfg kuma_cp.Config, closeCh <-chan struct{}) (core_runtime.Run
 		return nil, err
 	}
 
-	initializeXds(builder)
-
 	leaderInfoComponent := &component.LeaderInfoComponent{}
 	builder.WithLeaderInfo(leaderInfoComponent)
 
@@ -267,10 +264,6 @@ func initializeConfigStore(cfg kuma_cp.Config, builder *core_runtime.Builder) er
 	}
 }
 
-func initializeXds(builder *core_runtime.Builder) {
-	builder.WithXdsContext(core_xds.NewXdsContext())
-}
-
 func initializeCaManagers(builder *core_runtime.Builder) error {
 	for pluginName, caPlugin := range core_plugins.Plugins().CaPlugins() {
 		caManager, err := caPlugin.NewCaManager(builder, nil)
diff --git a/pkg/core/runtime/builder.go b/pkg/core/runtime/builder.go
index 3991c7f3f..bf1de0d22 100644
--- a/pkg/core/runtime/builder.go
+++ b/pkg/core/runtime/builder.go
@@ -23,7 +23,6 @@ import (
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	"github.com/kumahq/kuma/pkg/core/runtime/component"
 	"github.com/kumahq/kuma/pkg/core/secrets/store"
-	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	"github.com/kumahq/kuma/pkg/metrics"
 )
 
@@ -34,7 +33,6 @@ type BuilderContext interface {
 	SecretStore() store.SecretStore
 	ConfigStore() core_store.ResourceStore
 	ResourceManager() core_manager.ResourceManager
-	XdsContext() core_xds.XdsContext
 	Config() kuma_cp.Config
 	DataSourceLoader() datasource.Loader
 	Extensions() context.Context
@@ -58,7 +56,6 @@ type Builder struct {
 	rm       core_manager.ResourceManager
 	rom      core_manager.ReadOnlyResourceManager
 	cam      core_ca.Managers
-	xds      core_xds.XdsContext
 	dsl      datasource.Loader
 	ext      context.Context
 	dns      resolver.DNSResolver
@@ -134,11 +131,6 @@ func (b *Builder) WithDataSourceLoader(loader datasource.Loader) *Builder {
 	return b
 }
 
-func (b *Builder) WithXdsContext(xds core_xds.XdsContext) *Builder {
-	b.xds = xds
-	return b
-}
-
 func (b *Builder) WithExtensions(ext context.Context) *Builder {
 	b.ext = ext
 	return b
@@ -197,9 +189,6 @@ func (b *Builder) Build() (Runtime, error) {
 	if b.rom == nil {
 		return nil, errors.Errorf("ReadOnlyResourceManager has not been configured")
 	}
-	if b.xds == nil {
-		return nil, errors.Errorf("xDS Context has not been configured")
-	}
 	if b.dsl == nil {
 		return nil, errors.Errorf("DataSourceLoader has not been configured")
 	}
@@ -233,7 +222,6 @@ func (b *Builder) Build() (Runtime, error) {
 			rs:       b.rs,
 			ss:       b.ss,
 			cam:      b.cam,
-			xds:      b.xds,
 			dsl:      b.dsl,
 			ext:      b.ext,
 			dns:      b.dns,
@@ -269,9 +257,6 @@ func (b *Builder) ReadOnlyResourceManager() core_manager.ReadOnlyResourceManager
 func (b *Builder) CaManagers() core_ca.Managers {
 	return b.cam
 }
-func (b *Builder) XdsContext() core_xds.XdsContext {
-	return b.xds
-}
 func (b *Builder) Config() kuma_cp.Config {
 	return b.cfg
 }
diff --git a/pkg/core/runtime/runtime.go b/pkg/core/runtime/runtime.go
index 727399584..1a1557132 100644
--- a/pkg/core/runtime/runtime.go
+++ b/pkg/core/runtime/runtime.go
@@ -22,7 +22,6 @@ import (
 	core_manager "github.com/kumahq/kuma/pkg/core/resources/manager"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	"github.com/kumahq/kuma/pkg/core/runtime/component"
-	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 )
 
 // Runtime represents initialized application state.
@@ -40,7 +39,6 @@ type RuntimeInfo interface {
 
 type RuntimeContext interface {
 	Config() kuma_cp.Config
-	XDS() core_xds.XdsContext
 	DataSourceLoader() datasource.Loader
 	ResourceManager() core_manager.ResourceManager
 	ResourceStore() core_store.ResourceStore
@@ -101,7 +99,6 @@ type runtimeContext struct {
 	cs       core_store.ResourceStore
 	rom      core_manager.ReadOnlyResourceManager
 	cam      ca.Managers
-	xds      core_xds.XdsContext
 	dsl      datasource.Loader
 	ext      context.Context
 	dns      resolver.DNSResolver
@@ -129,10 +126,6 @@ func (rc *runtimeContext) Config() kuma_cp.Config {
 	return rc.cfg
 }
 
-func (rc *runtimeContext) XDS() core_xds.XdsContext {
-	return rc.xds
-}
-
 func (rc *runtimeContext) DataSourceLoader() datasource.Loader {
 	return rc.dsl
 }
diff --git a/pkg/core/xds/types.go b/pkg/core/xds/types.go
index b70e69f6b..0f9f2c079 100644
--- a/pkg/core/xds/types.go
+++ b/pkg/core/xds/types.go
@@ -5,7 +5,7 @@ import (
 	"fmt"
 	"strings"
 
-	envoy_api_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
+	"github.com/golang/protobuf/proto"
 
 	"github.com/pkg/errors"
 
@@ -13,7 +13,7 @@ import (
 	mesh_core "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 
-	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 )
 
 // StreamID represents a stream opened by XDS
@@ -91,15 +91,16 @@ type FaultInjectionMap map[mesh_proto.InboundInterface]*mesh_proto.FaultInjectio
 type TrafficPermissionMap map[mesh_proto.InboundInterface]*mesh_core.TrafficPermissionResource
 
 type CLACache interface {
-	GetCLA(ctx context.Context, meshName, meshHash, service string) (*envoy_api_v2.ClusterLoadAssignment, error)
+	GetCLA(ctx context.Context, meshName, meshHash, service string, apiVersion envoy_common.APIVersion) (proto.Message, error)
 }
 
 type Proxy struct {
-	Id        ProxyId
-	Dataplane *mesh_core.DataplaneResource
-	Metadata  *DataplaneMetadata
-	Routing   Routing
-	Policies  MatchedPolicies
+	Id         ProxyId
+	APIVersion envoy_common.APIVersion // todo(jakubdyszkiewicz) consider moving APIVersion here. pkg/core should not depend on pkg/xds. It should be other way around.
+	Dataplane  *mesh_core.DataplaneResource
+	Metadata   *DataplaneMetadata
+	Routing    Routing
+	Policies   MatchedPolicies
 }
 
 type Routing struct {
@@ -122,6 +123,15 @@ type MatchedPolicies struct {
 	FaultInjections    FaultInjectionMap
 }
 
+type CaSecret struct {
+	PemCerts [][]byte
+}
+
+type IdentitySecret struct {
+	PemCerts [][]byte
+	PemKey   []byte
+}
+
 func (s TagSelectorSet) Add(new mesh_proto.TagSelector) TagSelectorSet {
 	for _, old := range s {
 		if new.Equal(old) {
@@ -170,13 +180,6 @@ func BuildProxyId(mesh, name string, more ...string) (*ProxyId, error) {
 	return ParseProxyIdFromString(id)
 }
 
-func ParseProxyId(node *envoy_core.Node) (*ProxyId, error) {
-	if node == nil {
-		return nil, errors.Errorf("Envoy node must not be nil")
-	}
-	return ParseProxyIdFromString(node.Id)
-}
-
 func ParseProxyIdFromString(id string) (*ProxyId, error) {
 	if id == "" {
 		return nil, errors.Errorf("Envoy ID must not be nil")
diff --git a/pkg/core/xds/context.go b/pkg/core/xds/v2/context.go
similarity index 92%
rename from pkg/core/xds/context.go
rename to pkg/core/xds/v2/context.go
index bf94b98e3..1bdbdbafd 100644
--- a/pkg/core/xds/context.go
+++ b/pkg/core/xds/v2/context.go
@@ -1,4 +1,4 @@
-package xds
+package v2
 
 import (
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
@@ -7,6 +7,7 @@ import (
 	"github.com/go-logr/logr"
 
 	"github.com/kumahq/kuma/pkg/core"
+	"github.com/kumahq/kuma/pkg/core/xds"
 	util_xds "github.com/kumahq/kuma/pkg/util/xds"
 )
 
@@ -57,7 +58,7 @@ func (h hasher) ID(node *envoy_core.Node) string {
 	if node == nil {
 		return "unknown"
 	}
-	proxyId, err := ParseProxyId(node)
+	proxyId, err := xds.ParseProxyIdFromString(node.GetId())
 	if err != nil {
 		h.log.Error(err, "failed to parse Proxy ID", "node", node)
 		return "unknown"
diff --git a/pkg/dp-server/components.go b/pkg/dp-server/components.go
index e646a6be8..8b6365f9f 100644
--- a/pkg/dp-server/components.go
+++ b/pkg/dp-server/components.go
@@ -1,19 +1,21 @@
 package dp_server
 
 import (
+	"github.com/pkg/errors"
+
 	"github.com/kumahq/kuma/pkg/core/runtime"
 	sds_server "github.com/kumahq/kuma/pkg/sds/server"
 	"github.com/kumahq/kuma/pkg/xds/bootstrap"
-	xds_server_v2 "github.com/kumahq/kuma/pkg/xds/server/v2"
+	xds_server "github.com/kumahq/kuma/pkg/xds/server"
 )
 
 func SetupServer(rt runtime.Runtime) error {
 	dpServer := NewDpServer(*rt.Config().DpServer, rt.Metrics())
 	if err := sds_server.RegisterSDS(rt, dpServer.grpcServer); err != nil {
-		return err
+		return errors.Wrap(err, "could not register SDS")
 	}
-	if err := xds_server_v2.RegisterXDS(rt, dpServer.grpcServer); err != nil {
-		return err
+	if err := xds_server.RegisterXDS(rt, dpServer.grpcServer); err != nil {
+		return errors.Wrap(err, "could not register XDS")
 	}
 	if err := bootstrap.RegisterBootstrap(rt, dpServer.httpMux); err != nil {
 		return err
diff --git a/pkg/kds/server/components.go b/pkg/kds/server/components.go
index 93190d817..2f6bab0f1 100644
--- a/pkg/kds/server/components.go
+++ b/pkg/kds/server/components.go
@@ -37,7 +37,7 @@ func New(log logr.Logger, rt core_runtime.Runtime, providedTypes []model.Resourc
 	callbacks := util_xds_v2.CallbacksChain{
 		&typeAdjustCallbacks{},
 		util_xds_v2.NewControlPlaneIdCallbacks(serverID),
-		util_xds_v2.LoggingCallbacks{Log: log},
+		util_xds_v2.AdaptCallbacks(util_xds.LoggingCallbacks{Log: log}),
 		util_xds_v2.AdaptCallbacks(statsCallbacks),
 		syncTracker,
 	}
diff --git a/pkg/mads/server/server.go b/pkg/mads/server/server.go
index 62ff7e750..73d617902 100644
--- a/pkg/mads/server/server.go
+++ b/pkg/mads/server/server.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"github.com/kumahq/kuma/pkg/core"
+	util_xds "github.com/kumahq/kuma/pkg/util/xds"
 	util_xds_v2 "github.com/kumahq/kuma/pkg/util/xds/v2"
 
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
@@ -18,7 +19,7 @@ func SetupServer(rt core_runtime.Runtime) error {
 	reconciler := NewReconciler(hasher, cache, generator, versioner)
 	syncTracker := NewSyncTracker(reconciler, rt.Config().MonitoringAssignmentServer.AssignmentRefreshInterval)
 	callbacks := util_xds_v2.CallbacksChain{
-		util_xds_v2.LoggingCallbacks{Log: madsServerLog},
+		util_xds_v2.AdaptCallbacks(util_xds.LoggingCallbacks{Log: madsServerLog}),
 		syncTracker,
 	}
 	srv := NewServer(cache, callbacks)
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index 8a9d8c61a..75eab88c6 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -13,6 +13,7 @@ type Metrics interface {
 	RegisterGRPC(*grpc.Server)
 	GRPCServerInterceptors() []grpc.ServerOption
 	GRPCClientInterceptors() []grpc.DialOption
+	BulkRegister(...prometheus.Collector) error
 }
 
 type metrics struct {
@@ -68,3 +69,12 @@ func NewMetrics(zone string) (Metrics, error) {
 	}
 	return m, nil
 }
+
+func (m *metrics) BulkRegister(cs ...prometheus.Collector) error {
+	for _, c := range cs {
+		if err := m.Register(c); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/pkg/sds/provider/ca/provider.go b/pkg/sds/ca/provider.go
similarity index 71%
rename from pkg/sds/provider/ca/provider.go
rename to pkg/sds/ca/provider.go
index 6cb6ca4bd..3427b8a79 100644
--- a/pkg/sds/provider/ca/provider.go
+++ b/pkg/sds/ca/provider.go
@@ -10,10 +10,14 @@ import (
 	core_manager "github.com/kumahq/kuma/pkg/core/resources/manager"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 )
 
-func New(resourceManager core_manager.ResourceManager, caManagers core_ca.Managers) sds_provider.SecretProvider {
+type Provider interface {
+	Get(ctx context.Context, mesh string) (*core_xds.CaSecret, error)
+}
+
+func NewProvider(resourceManager core_manager.ResourceManager, caManagers core_ca.Managers) Provider {
 	return &meshCaProvider{
 		resourceManager: resourceManager,
 		caManagers:      caManagers,
@@ -29,12 +33,10 @@ func (s *meshCaProvider) RequiresIdentity() bool {
 	return false
 }
 
-func (s *meshCaProvider) Get(ctx context.Context, resource string, requestor sds_provider.Identity) (sds_provider.Secret, error) {
-	meshName := requestor.Mesh
-
+func (s *meshCaProvider) Get(ctx context.Context, mesh string) (*core_xds.CaSecret, error) {
 	meshRes := core_mesh.NewMeshResource()
-	if err := s.resourceManager.Get(ctx, meshRes, core_store.GetByKey(meshName, model.NoMesh)); err != nil {
-		return nil, errors.Wrapf(err, "failed to find a Mesh %q", meshName)
+	if err := s.resourceManager.Get(ctx, meshRes, core_store.GetByKey(mesh, model.NoMesh)); err != nil {
+		return nil, errors.Wrapf(err, "failed to find a Mesh %q", mesh)
 	}
 
 	backend := meshRes.GetEnabledCertificateAuthorityBackend()
@@ -47,12 +49,12 @@ func (s *meshCaProvider) Get(ctx context.Context, resource string, requestor sds
 		return nil, errors.Errorf("CA manager of type %s not exist", backend.Type)
 	}
 
-	certs, err := caManager.GetRootCert(ctx, meshName, backend)
+	certs, err := caManager.GetRootCert(ctx, mesh, backend)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not get root certs")
 	}
 
-	return &MeshCaSecret{
+	return &core_xds.CaSecret{
 		PemCerts: certs,
 	}, nil
 }
diff --git a/pkg/sds/provider/identity/provider.go b/pkg/sds/identity/provider.go
similarity index 75%
rename from pkg/sds/provider/identity/provider.go
rename to pkg/sds/identity/provider.go
index f4c8acdd1..5c9c01082 100644
--- a/pkg/sds/provider/identity/provider.go
+++ b/pkg/sds/identity/provider.go
@@ -10,10 +10,20 @@ import (
 	core_manager "github.com/kumahq/kuma/pkg/core/resources/manager"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 )
 
-func New(resourceManager core_manager.ResourceManager, caManagers core_ca.Managers) sds_provider.SecretProvider {
+type Identity struct {
+	Mesh     string
+	Name     string
+	Services []string
+}
+
+type Provider interface {
+	Get(ctx context.Context, requestor Identity) (*core_xds.IdentitySecret, error)
+}
+
+func NewProvider(resourceManager core_manager.ResourceManager, caManagers core_ca.Managers) Provider {
 	return &identityCertProvider{
 		resourceManager: resourceManager,
 		caManagers:      caManagers,
@@ -25,11 +35,7 @@ type identityCertProvider struct {
 	caManagers      core_ca.Managers
 }
 
-func (s *identityCertProvider) RequiresIdentity() bool {
-	return true
-}
-
-func (s *identityCertProvider) Get(ctx context.Context, name string, requestor sds_provider.Identity) (sds_provider.Secret, error) {
+func (s *identityCertProvider) Get(ctx context.Context, requestor Identity) (*core_xds.IdentitySecret, error) {
 	meshName := requestor.Mesh
 
 	meshRes := core_mesh.NewMeshResource()
@@ -52,7 +58,7 @@ func (s *identityCertProvider) Get(ctx context.Context, name string, requestor s
 		return nil, errors.Wrapf(err, "could not generate dataplane cert for mesh: %q backend: %q services: %q", meshName, backend.Name, requestor.Services)
 	}
 
-	return &IdentityCertSecret{
+	return &core_xds.IdentitySecret{
 		PemCerts: [][]byte{pair.CertPEM},
 		PemKey:   pair.KeyPEM,
 	}, nil
diff --git a/pkg/sds/metrics/metrics.go b/pkg/sds/metrics/metrics.go
new file mode 100644
index 000000000..c0cef487b
--- /dev/null
+++ b/pkg/sds/metrics/metrics.go
@@ -0,0 +1,62 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+
+	core_metrics "github.com/kumahq/kuma/pkg/metrics"
+	util_xds "github.com/kumahq/kuma/pkg/util/xds"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+)
+
+type Metrics struct {
+	sdsGeneration        *prometheus.SummaryVec
+	sdsGenerationsErrors *prometheus.CounterVec
+	certGenerations      *prometheus.CounterVec
+	Callbacks            util_xds.Callbacks
+}
+
+func NewMetrics(metrics core_metrics.Metrics) (*Metrics, error) {
+	sdsGenerations := prometheus.NewSummaryVec(prometheus.SummaryOpts{
+		Name:       "sds_generation",
+		Help:       "Summary of SDS Snapshot generation",
+		Objectives: core_metrics.DefaultObjectives,
+	}, []string{"apiVersion"})
+
+	sdsGenerationsErrors := prometheus.NewCounterVec(prometheus.CounterOpts{
+		Help: "Counter of errors during SDS generation",
+		Name: "sds_generation_errors",
+	}, []string{"apiVersion"})
+
+	certGenerationsMetric := prometheus.NewCounterVec(prometheus.CounterOpts{
+		Help: "Number of generated certificates",
+		Name: "sds_cert_generation",
+	}, []string{"apiVersion"})
+
+	if err := metrics.BulkRegister(sdsGenerations, sdsGenerationsErrors, certGenerationsMetric); err != nil {
+		return nil, err
+	}
+
+	statsCallbacks, err := util_xds.NewStatsCallbacks(metrics, "sds")
+	if err != nil {
+		return nil, err
+	}
+
+	return &Metrics{
+		sdsGeneration:        sdsGenerations,
+		sdsGenerationsErrors: sdsGenerationsErrors,
+		certGenerations:      certGenerationsMetric,
+		Callbacks:            statsCallbacks,
+	}, nil
+}
+
+func (s *Metrics) SdsGeneration(apiVersion envoy_common.APIVersion) prometheus.Observer {
+	return s.sdsGeneration.WithLabelValues(string(apiVersion))
+}
+
+func (s *Metrics) SdsGenerationsErrors(apiVersion envoy_common.APIVersion) prometheus.Counter {
+	return s.sdsGenerationsErrors.WithLabelValues(string(apiVersion))
+}
+
+func (s *Metrics) CertGenerations(apiVersion envoy_common.APIVersion) prometheus.Counter {
+	return s.certGenerations.WithLabelValues(string(apiVersion))
+}
diff --git a/pkg/sds/provider/interfaces.go b/pkg/sds/provider/interfaces.go
deleted file mode 100644
index 6326fd5d8..000000000
--- a/pkg/sds/provider/interfaces.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package provider
-
-import (
-	"context"
-
-	envoy_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
-)
-
-type Secret interface {
-	ToResource(name string) *envoy_auth.Secret
-}
-
-type Identity struct {
-	Mesh     string
-	Name     string
-	Services []string
-}
-
-type SecretProvider interface {
-	RequiresIdentity() bool
-	Get(ctx context.Context, name string, requestor Identity) (Secret, error)
-}
diff --git a/pkg/sds/server/components.go b/pkg/sds/server/components.go
index 3e1b8356d..03420375d 100644
--- a/pkg/sds/server/components.go
+++ b/pkg/sds/server/components.go
@@ -1,16 +1,21 @@
 package server
 
 import (
+	"google.golang.org/grpc"
+
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
-	ca_sds_provider "github.com/kumahq/kuma/pkg/sds/provider/ca"
-	identity_sds_provider "github.com/kumahq/kuma/pkg/sds/provider/identity"
+	sds_metrics "github.com/kumahq/kuma/pkg/sds/metrics"
+	v2 "github.com/kumahq/kuma/pkg/sds/server/v2"
 )
 
-func DefaultMeshCaProvider(rt core_runtime.Runtime) sds_provider.SecretProvider {
-	return ca_sds_provider.New(rt.ResourceManager(), rt.CaManagers())
-}
+func RegisterSDS(rt core_runtime.Runtime, server *grpc.Server) error {
+	sdsMetrics, err := sds_metrics.NewMetrics(rt.Metrics())
+	if err != nil {
+		return err
+	}
 
-func DefaultIdentityCertProvider(rt core_runtime.Runtime) sds_provider.SecretProvider {
-	return identity_sds_provider.New(rt.ResourceManager(), rt.CaManagers())
+	if err := v2.RegisterSDS(rt, sdsMetrics, server); err != nil {
+		return err
+	}
+	return nil
 }
diff --git a/pkg/sds/server/reconciler.go b/pkg/sds/server/v2/reconciler.go
similarity index 90%
rename from pkg/sds/server/reconciler.go
rename to pkg/sds/server/v2/reconciler.go
index ac3e7bcd0..b2f89a431 100644
--- a/pkg/sds/server/reconciler.go
+++ b/pkg/sds/server/v2/reconciler.go
@@ -1,4 +1,4 @@
-package server
+package v2
 
 import (
 	"context"
@@ -7,10 +7,12 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
-	"sync"
 	"time"
 
 	"github.com/kumahq/kuma/pkg/config/core/resources/store"
+	sds_metrics "github.com/kumahq/kuma/pkg/sds/metrics"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	envoy_secrets "github.com/kumahq/kuma/pkg/xds/envoy/secrets/v2"
 	"github.com/kumahq/kuma/pkg/xds/envoy/tls"
 
 	"github.com/pkg/errors"
@@ -29,7 +31,8 @@ import (
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
+	sds_ca "github.com/kumahq/kuma/pkg/sds/ca"
+	sds_identity "github.com/kumahq/kuma/pkg/sds/identity"
 )
 
 // DataplaneReconciler keeps the state of the Cache for SDS consistent
@@ -41,19 +44,11 @@ import (
 type DataplaneReconciler struct {
 	resManager         core_manager.ResourceManager
 	readOnlyResManager core_manager.ReadOnlyResourceManager
-	meshCaProvider     sds_provider.SecretProvider
-	identityProvider   sds_provider.SecretProvider
+	meshCaProvider     sds_ca.Provider
+	identityProvider   sds_identity.Provider
 	cache              envoy_cache.SnapshotCache
 	upsertConfig       store.UpsertConfig
-
-	sync.RWMutex
-	certGenerated int
-}
-
-func (d *DataplaneReconciler) GeneratedCerts() int {
-	d.RLock()
-	defer d.RUnlock()
-	return d.certGenerated
+	sdsMetrics         *sds_metrics.Metrics
 }
 
 func (d *DataplaneReconciler) Reconcile(dataplaneId core_model.ResourceKey) error {
@@ -91,9 +86,7 @@ func (d *DataplaneReconciler) Reconcile(dataplaneId core_model.ResourceKey) erro
 		if err != nil {
 			return err
 		}
-		d.Lock()
-		d.certGenerated++
-		d.Unlock()
+		d.sdsMetrics.CertGenerations(envoy_common.APIV2).Inc()
 		if err := d.updateInsights(dataplaneId, snapshot); err != nil {
 			// do not stop updating Envoy even if insights update fails
 			sdsServerLog.Error(err, "Could not update Dataplane Insights", "dataplaneId", dataplaneId)
@@ -142,19 +135,16 @@ func (d *DataplaneReconciler) shouldGenerateSnapshot(proxyID string, mesh *mesh_
 }
 
 func (d *DataplaneReconciler) generateSnapshot(dataplane *mesh_core.DataplaneResource, mesh *mesh_core.MeshResource) (envoy_cache.Snapshot, error) {
-	requestor := sds_provider.Identity{
+	requestor := sds_identity.Identity{
 		Services: dataplane.Spec.TagSet().Values(mesh_proto.ServiceTag),
 		Mesh:     dataplane.GetMeta().GetMesh(),
 	}
-	identitySecret, err := d.identityProvider.Get(context.Background(), tls.IdentityCertResource, requestor)
+	identitySecret, err := d.identityProvider.Get(context.Background(), requestor)
 	if err != nil {
 		return envoy_cache.Snapshot{}, errors.Wrap(err, "could not get Dataplane cert pair")
 	}
 
-	requestor = sds_provider.Identity{
-		Mesh: dataplane.GetMeta().GetMesh(),
-	}
-	caSecret, err := d.meshCaProvider.Get(context.Background(), tls.MeshCaResource, requestor)
+	caSecret, err := d.meshCaProvider.Get(context.Background(), dataplane.GetMeta().GetMesh())
 	if err != nil {
 		return envoy_cache.Snapshot{}, errors.Wrap(err, "could not get mesh CA cert")
 	}
@@ -164,8 +154,8 @@ func (d *DataplaneReconciler) generateSnapshot(dataplane *mesh_core.DataplaneRes
 		Resources: [envoy_types.UnknownType]envoy_cache.Resources{},
 	}
 	snap.Resources[envoy_types.Secret] = envoy_cache.NewResources(version, []envoy_types.Resource{
-		identitySecret.ToResource(tls.IdentityCertResource),
-		caSecret.ToResource(tls.MeshCaResource),
+		envoy_secrets.CreateIdentitySecret(identitySecret),
+		envoy_secrets.CreateCaSecret(caSecret),
 	})
 	return snap, nil
 }
diff --git a/pkg/sds/server/server.go b/pkg/sds/server/v2/server.go
similarity index 62%
rename from pkg/sds/server/server.go
rename to pkg/sds/server/v2/server.go
index 6036bfe81..65a2ca9d5 100644
--- a/pkg/sds/server/server.go
+++ b/pkg/sds/server/v2/server.go
@@ -1,4 +1,4 @@
-package server
+package v2
 
 import (
 	"context"
@@ -9,19 +9,21 @@ import (
 	envoy_cache "github.com/envoyproxy/go-control-plane/pkg/cache/v2"
 	envoy_server "github.com/envoyproxy/go-control-plane/pkg/server/v2"
 	"github.com/go-logr/logr"
-	"github.com/prometheus/client_golang/prometheus"
 	"google.golang.org/grpc"
 
 	"github.com/kumahq/kuma/pkg/core"
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
 	core_xds "github.com/kumahq/kuma/pkg/core/xds"
-	core_metrics "github.com/kumahq/kuma/pkg/metrics"
+	sds_ca "github.com/kumahq/kuma/pkg/sds/ca"
+	sds_identity "github.com/kumahq/kuma/pkg/sds/identity"
+	sds_metrics "github.com/kumahq/kuma/pkg/sds/metrics"
 	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
 	util_xds "github.com/kumahq/kuma/pkg/util/xds"
 	util_xds_v2 "github.com/kumahq/kuma/pkg/util/xds/v2"
 	xds_auth "github.com/kumahq/kuma/pkg/xds/auth"
 	auth_components "github.com/kumahq/kuma/pkg/xds/auth/components"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	xds_callbacks "github.com/kumahq/kuma/pkg/xds/server/callbacks"
 )
 
@@ -29,13 +31,13 @@ var (
 	sdsServerLog = core.Log.WithName("sds-server")
 )
 
-func RegisterSDS(rt core_runtime.Runtime, server *grpc.Server) error {
+func RegisterSDS(rt core_runtime.Runtime, sdsMetrics *sds_metrics.Metrics, server *grpc.Server) error {
 	hasher := hasher{sdsServerLog}
 	logger := util_xds.NewLogger(sdsServerLog)
 	cache := envoy_cache.NewSnapshotCache(false, hasher, logger)
 
-	caProvider := DefaultMeshCaProvider(rt)
-	identityProvider := DefaultIdentityCertProvider(rt)
+	caProvider := sds_ca.NewProvider(rt.ResourceManager(), rt.CaManagers())
+	identityProvider := sds_identity.NewProvider(rt.ResourceManager(), rt.CaManagers())
 	authenticator, err := auth_components.DefaultAuthenticator(rt)
 	if err != nil {
 		return err
@@ -49,55 +51,28 @@ func RegisterSDS(rt core_runtime.Runtime, server *grpc.Server) error {
 		identityProvider:   identityProvider,
 		cache:              cache,
 		upsertConfig:       rt.Config().Store.Upsert,
-	}
-	certGenerationsMetric := prometheus.NewGaugeFunc(prometheus.GaugeOpts{
-		Help: "Number of generated certificates",
-		Name: "sds_cert_generation",
-	}, func() float64 {
-		return float64(reconciler.GeneratedCerts())
-	})
-	if err := rt.Metrics().Register(certGenerationsMetric); err != nil {
-		return err
+		sdsMetrics:         sdsMetrics,
 	}
 
-	syncTracker, err := syncTracker(&reconciler, rt.Config().SdsServer.DataplaneConfigurationRefreshInterval, rt.Metrics())
-	if err != nil {
-		return err
-	}
-	statsCallbacks, err := util_xds.NewStatsCallbacks(rt.Metrics(), "sds")
+	syncTracker, err := syncTracker(&reconciler, rt.Config().SdsServer.DataplaneConfigurationRefreshInterval, sdsMetrics)
 	if err != nil {
 		return err
 	}
 	callbacks := util_xds_v2.CallbacksChain{
-		util_xds_v2.AdaptCallbacks(statsCallbacks),
-		util_xds_v2.LoggingCallbacks{Log: sdsServerLog},
+		util_xds_v2.AdaptCallbacks(sdsMetrics.Callbacks),
+		util_xds_v2.AdaptCallbacks(util_xds.LoggingCallbacks{Log: sdsServerLog}),
 		util_xds_v2.AdaptCallbacks(authCallbacks),
 		util_xds_v2.AdaptCallbacks(syncTracker),
 	}
 
 	srv := envoy_server.NewServer(context.Background(), cache, callbacks)
 
-	sdsServerLog.Info("registering Secret Discovery Service in Dataplane Server")
+	sdsServerLog.Info("registering Secret Discovery Service V2 in Dataplane Server")
 	envoy_discovery.RegisterSecretDiscoveryServiceServer(server, srv)
 	return nil
 }
 
-func syncTracker(reconciler *DataplaneReconciler, refresh time.Duration, metrics core_metrics.Metrics) (util_xds.Callbacks, error) {
-	sdsGenerations := prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:       "sds_generation",
-		Help:       "Summary of SDS Snapshot generation",
-		Objectives: core_metrics.DefaultObjectives,
-	})
-	if err := metrics.Register(sdsGenerations); err != nil {
-		return nil, err
-	}
-	sdsGenerationsErrors := prometheus.NewCounter(prometheus.CounterOpts{
-		Help: "Counter of errors during SDS generation",
-		Name: "sds_generation_errors",
-	})
-	if err := metrics.Register(sdsGenerationsErrors); err != nil {
-		return nil, err
-	}
+func syncTracker(reconciler *DataplaneReconciler, refresh time.Duration, sdsMetrics *sds_metrics.Metrics) (util_xds.Callbacks, error) {
 	return xds_callbacks.NewDataplaneSyncTracker(func(dataplaneId core_model.ResourceKey, streamId int64) util_watchdog.Watchdog {
 		return &util_watchdog.SimpleWatchdog{
 			NewTicker: func() *time.Ticker {
@@ -106,12 +81,12 @@ func syncTracker(reconciler *DataplaneReconciler, refresh time.Duration, metrics
 			OnTick: func() error {
 				start := core.Now()
 				defer func() {
-					sdsGenerations.Observe(float64(core.Now().Sub(start).Milliseconds()))
+					sdsMetrics.SdsGeneration(envoy_common.APIV2).Observe(float64(core.Now().Sub(start).Milliseconds()))
 				}()
 				return reconciler.Reconcile(dataplaneId)
 			},
 			OnError: func(err error) {
-				sdsGenerationsErrors.Inc()
+				sdsMetrics.SdsGenerationsErrors(envoy_common.APIV2).Inc()
 				sdsServerLog.Error(err, "OnTick() failed")
 			},
 		}
@@ -126,7 +101,7 @@ func (h hasher) ID(node *envoy_core.Node) string {
 	if node == nil {
 		return "unknown"
 	}
-	proxyId, err := core_xds.ParseProxyId(node)
+	proxyId, err := core_xds.ParseProxyIdFromString(node.GetId())
 	if err != nil {
 		h.log.Error(err, "failed to parse Proxy ID", "node", node)
 		return "unknown"
diff --git a/pkg/util/cache/cache.go b/pkg/util/cache/v2/cache.go
similarity index 98%
rename from pkg/util/cache/cache.go
rename to pkg/util/cache/v2/cache.go
index 4026b38a9..438ba4982 100644
--- a/pkg/util/cache/cache.go
+++ b/pkg/util/cache/v2/cache.go
@@ -1,4 +1,4 @@
-package cache
+package v2
 
 import (
 	"sort"
diff --git a/pkg/util/xds/callbacks.go b/pkg/util/xds/callbacks.go
index a6fe8f103..e44b7d051 100644
--- a/pkg/util/xds/callbacks.go
+++ b/pkg/util/xds/callbacks.go
@@ -3,7 +3,6 @@ package xds
 import (
 	"context"
 
-	"github.com/golang/protobuf/proto"
 	_struct "github.com/golang/protobuf/ptypes/struct"
 )
 
@@ -14,7 +13,6 @@ type DiscoveryRequest interface {
 	GetTypeUrl() string
 	GetResponseNonce() string
 	HasErrors() bool
-	Proto() proto.Message
 }
 
 // DiscoveryResponse defines interface over real Envoy's DiscoveryResponse.
diff --git a/pkg/util/xds/v2/logging_callbacks.go b/pkg/util/xds/logging_callbacks.go
similarity index 78%
rename from pkg/util/xds/v2/logging_callbacks.go
rename to pkg/util/xds/logging_callbacks.go
index 98e93fa4c..f79e152c4 100644
--- a/pkg/util/xds/v2/logging_callbacks.go
+++ b/pkg/util/xds/logging_callbacks.go
@@ -1,19 +1,16 @@
-package v2
+package xds
 
 import (
 	"context"
 
 	"github.com/go-logr/logr"
-
-	envoy "github.com/envoyproxy/go-control-plane/envoy/api/v2"
-	envoy_xds "github.com/envoyproxy/go-control-plane/pkg/server/v2"
 )
 
 type LoggingCallbacks struct {
 	Log logr.Logger
 }
 
-var _ envoy_xds.Callbacks = LoggingCallbacks{}
+var _ Callbacks = LoggingCallbacks{}
 
 // OnStreamOpen is called once an xDS stream is open with a stream ID and the type URL (or "" for ADS).
 // Returning an error will end processing and close the stream. OnStreamClosed will still be called.
@@ -29,19 +26,19 @@ func (cb LoggingCallbacks) OnStreamClosed(streamID int64) {
 
 // OnStreamRequest is called once a request is received on a stream.
 // Returning an error will end processing and close the stream. OnStreamClosed will still be called.
-func (cb LoggingCallbacks) OnStreamRequest(streamID int64, req *envoy.DiscoveryRequest) error {
+func (cb LoggingCallbacks) OnStreamRequest(streamID int64, req DiscoveryRequest) error {
 	cb.Log.V(1).Info("OnStreamRequest", "streamid", streamID, "req", req)
 	return nil
 }
 
 // OnStreamResponse is called immediately prior to sending a response on a stream.
-func (cb LoggingCallbacks) OnStreamResponse(streamID int64, req *envoy.DiscoveryRequest, resp *envoy.DiscoveryResponse) {
+func (cb LoggingCallbacks) OnStreamResponse(streamID int64, req DiscoveryRequest, resp DiscoveryResponse) {
 	cb.Log.V(1).Info("OnStreamResponse", "streamid", streamID, "req", req, "resp", resp)
 }
 
 // OnFetchRequest is called for each Fetch request. Returning an error will end processing of the
 // request and respond with an error.
-func (cb LoggingCallbacks) OnFetchRequest(ctx context.Context, req *envoy.DiscoveryRequest) error {
+func (cb LoggingCallbacks) OnFetchRequest(ctx context.Context, req DiscoveryRequest) error {
 	cb.Log.V(1).Info("OnFetchRequest", "context", ctx, "req", req)
 	return nil
 }
@@ -49,6 +46,6 @@ func (cb LoggingCallbacks) OnFetchRequest(ctx context.Context, req *envoy.Discov
 // OnFetchRequest is called for each Fetch request. Returning an error will end processing of the
 // request and respond with an error.
 // OnFetchResponse is called immediately prior to sending a response.
-func (cb LoggingCallbacks) OnFetchResponse(req *envoy.DiscoveryRequest, resp *envoy.DiscoveryResponse) {
+func (cb LoggingCallbacks) OnFetchResponse(req DiscoveryRequest, resp DiscoveryResponse) {
 	cb.Log.V(1).Info("OnFetchResponse", "req", req, "resp", resp)
 }
diff --git a/pkg/util/xds/v2/callbacks.go b/pkg/util/xds/v2/callbacks.go
index 81d4e970c..b5df09349 100644
--- a/pkg/util/xds/v2/callbacks.go
+++ b/pkg/util/xds/v2/callbacks.go
@@ -73,3 +73,9 @@ var _ xds.DiscoveryRequest = &discoveryRequest{}
 type discoveryResponse struct {
 	*envoy_api_v2.DiscoveryResponse
 }
+
+var _ xds.DiscoveryResponse = &discoveryResponse{}
+
+func (d *discoveryResponse) Proto() proto.Message {
+	return d
+}
diff --git a/pkg/xds/bootstrap/components.go b/pkg/xds/bootstrap/components.go
index 6213bb760..19590ab08 100644
--- a/pkg/xds/bootstrap/components.go
+++ b/pkg/xds/bootstrap/components.go
@@ -10,7 +10,7 @@ import (
 func RegisterBootstrap(rt core_runtime.Runtime, mux *http.ServeMux) error {
 	generator, err := NewDefaultBootstrapGenerator(
 		rt.ResourceManager(),
-		rt.Config().BootstrapServer.Params,
+		rt.Config().BootstrapServer,
 		rt.Config().DpServer.TlsCertFile,
 		rt.Config().DpServer.Auth.Type != dp_server.DpServerAuthNone,
 	)
diff --git a/pkg/xds/bootstrap/generator.go b/pkg/xds/bootstrap/generator.go
index c2aa74546..8db9cfdc0 100644
--- a/pkg/xds/bootstrap/generator.go
+++ b/pkg/xds/bootstrap/generator.go
@@ -17,8 +17,9 @@ import (
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 	"github.com/kumahq/kuma/pkg/core/validators"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 
-	envoy_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
+	envoy_bootstrap_v2 "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
 	"github.com/golang/protobuf/proto"
 	"github.com/pkg/errors"
 
@@ -38,7 +39,7 @@ type BootstrapGenerator interface {
 
 func NewDefaultBootstrapGenerator(
 	resManager core_manager.ResourceManager,
-	config *bootstrap_config.BootstrapParamsConfig,
+	config *bootstrap_config.BootstrapServerConfig,
 	dpServerCertFile string,
 	dpAuthEnabled bool,
 ) (BootstrapGenerator, error) {
@@ -46,8 +47,8 @@ func NewDefaultBootstrapGenerator(
 	if err != nil {
 		return nil, err
 	}
-	if config.XdsHost != "" && !hostsAndIps[config.XdsHost] {
-		return nil, errors.Errorf("hostname: %s set by KUMA_BOOTSTRAP_SERVER_PARAMS_XDS_HOST is not available in the DP Server certificate. Available hostnames: %q. Change the hostname or generate certificate with proper hostname.", config.XdsHost, hostsAndIps.slice())
+	if config.Params.XdsHost != "" && !hostsAndIps[config.Params.XdsHost] {
+		return nil, errors.Errorf("hostname: %s set by KUMA_BOOTSTRAP_SERVER_PARAMS_XDS_HOST is not available in the DP Server certificate. Available hostnames: %q. Change the hostname or generate certificate with proper hostname.", config.Params.XdsHost, hostsAndIps.slice())
 	}
 	return &bootstrapGenerator{
 		resManager:    resManager,
@@ -60,7 +61,7 @@ func NewDefaultBootstrapGenerator(
 
 type bootstrapGenerator struct {
 	resManager    core_manager.ResourceManager
-	config        *bootstrap_config.BootstrapParamsConfig
+	config        *bootstrap_config.BootstrapServerConfig
 	dpAuthEnabled bool
 	xdsCertFile   string
 	hostsAndIps   SANSet
@@ -81,11 +82,7 @@ func (b *bootstrapGenerator) Generate(ctx context.Context, request types.Bootstr
 		return nil, err
 	}
 
-	bootstrapCfg, err := b.generateFor(*proxyId, dataplane, request)
-	if err != nil {
-		return nil, err
-	}
-	return bootstrapCfg, nil
+	return b.generateFor(*proxyId, dataplane, request)
 }
 
 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")
@@ -109,7 +106,7 @@ func (b *bootstrapGenerator) validateRequest(request types.BootstrapRequest) err
 	if b.dpAuthEnabled && request.DataplaneTokenPath == "" {
 		return DpTokenRequired
 	}
-	if b.config.XdsHost == "" { // XdsHost takes precedence over Host in the request, so validate only when it is not set
+	if b.config.Params.XdsHost == "" { // XdsHost takes precedence over Host in the request, so validate only when it is not set
 		if !b.hostsAndIps[request.Host] {
 			return SANMismatchErr(request.Host, b.hostsAndIps.slice())
 		}
@@ -164,11 +161,11 @@ 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) (*envoy_bootstrap.Bootstrap, error) {
+func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *core_mesh.DataplaneResource, request types.BootstrapRequest) (proto.Message, error) {
 	// if dataplane has no service - fill this with placeholder. Otherwise take the first service
 	service := dataplane.Spec.GetIdentifyingService()
 
-	adminPort := b.config.AdminPort
+	adminPort := b.config.Params.AdminPort
 	if request.AdminPort != 0 {
 		adminPort = request.AdminPort
 	}
@@ -189,12 +186,12 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 	params := configParameters{
 		Id:                 proxyId.String(),
 		Service:            service,
-		AdminAddress:       b.config.AdminAddress,
+		AdminAddress:       b.config.Params.AdminAddress,
 		AdminPort:          adminPort,
-		AdminAccessLogPath: b.config.AdminAccessLogPath,
+		AdminAccessLogPath: b.config.Params.AdminAccessLogPath,
 		XdsHost:            b.xdsHost(request),
-		XdsPort:            b.config.XdsPort,
-		XdsConnectTimeout:  b.config.XdsConnectTimeout,
+		XdsPort:            b.config.Params.XdsPort,
+		XdsConnectTimeout:  b.config.Params.XdsConnectTimeout,
 		AccessLogPipe:      accessLogSocket,
 		DataplaneTokenPath: request.DataplaneTokenPath,
 		DataplaneResource:  request.DataplaneResource,
@@ -211,8 +208,8 @@ func (b *bootstrapGenerator) generateFor(proxyId core_xds.ProxyId, dataplane *co
 }
 
 func (b *bootstrapGenerator) xdsHost(request types.BootstrapRequest) string {
-	if b.config.XdsHost != "" { // XdsHost from config takes precedence over Host from request
-		return b.config.XdsHost
+	if b.config.Params.XdsHost != "" { // XdsHost from config takes precedence over Host from request
+		return b.config.Params.XdsHost
 	} else {
 		return request.Host
 	}
@@ -230,8 +227,17 @@ func (b *bootstrapGenerator) verifyAdminPort(adminPort uint32, dataplane *core_m
 	return nil
 }
 
-func (b *bootstrapGenerator) configForParameters(params configParameters) (*envoy_bootstrap.Bootstrap, error) {
-	tmpl, err := template.New("bootstrap").Parse(configTemplate)
+func (b *bootstrapGenerator) configForParameters(params configParameters) (proto.Message, error) {
+	switch b.config.APIVersion {
+	case envoy_common.APIV2:
+		return b.configForParametersV2(params)
+	default:
+		return nil, errors.Errorf("invalid API Version %s", b.config.APIVersion)
+	}
+}
+
+func (b *bootstrapGenerator) configForParametersV2(params configParameters) (proto.Message, error) {
+	tmpl, err := template.New("bootstrap").Parse(configTemplateV2)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to parse config template")
 	}
@@ -239,7 +245,7 @@ func (b *bootstrapGenerator) configForParameters(params configParameters) (*envo
 	if err := tmpl.Execute(&buf, params); err != nil {
 		return nil, errors.Wrap(err, "failed to render config template")
 	}
-	config := &envoy_bootstrap.Bootstrap{}
+	config := &envoy_bootstrap_v2.Bootstrap{}
 	if err := util_proto.FromYAML(buf.Bytes(), config); err != nil {
 		return nil, errors.Wrap(err, "failed to parse bootstrap config")
 	}
diff --git a/pkg/xds/bootstrap/parameters.go b/pkg/xds/bootstrap/parameters.go
new file mode 100644
index 000000000..0e87e7c5c
--- /dev/null
+++ b/pkg/xds/bootstrap/parameters.go
@@ -0,0 +1,24 @@
+package bootstrap
+
+import "time"
+
+type configParameters struct {
+	Id                 string
+	Service            string
+	AdminAddress       string
+	AdminPort          uint32
+	AdminAccessLogPath string
+	XdsHost            string
+	XdsPort            uint32
+	XdsConnectTimeout  time.Duration
+	AccessLogPipe      string
+	DataplaneTokenPath string
+	DataplaneResource  string
+	CertBytes          string
+	KumaDpVersion      string
+	KumaDpGitTag       string
+	KumaDpGitCommit    string
+	KumaDpBuildDate    string
+	EnvoyVersion       string
+	EnvoyBuild         string
+}
diff --git a/pkg/xds/bootstrap/template.go b/pkg/xds/bootstrap/template_v2.go
similarity index 81%
rename from pkg/xds/bootstrap/template.go
rename to pkg/xds/bootstrap/template_v2.go
index a4da6ff7f..faaf6252d 100644
--- a/pkg/xds/bootstrap/template.go
+++ b/pkg/xds/bootstrap/template_v2.go
@@ -1,29 +1,6 @@
 package bootstrap
 
-import "time"
-
-type configParameters struct {
-	Id                 string
-	Service            string
-	AdminAddress       string
-	AdminPort          uint32
-	AdminAccessLogPath string
-	XdsHost            string
-	XdsPort            uint32
-	XdsConnectTimeout  time.Duration
-	AccessLogPipe      string
-	DataplaneTokenPath string
-	DataplaneResource  string
-	CertBytes          string
-	KumaDpVersion      string
-	KumaDpGitTag       string
-	KumaDpGitCommit    string
-	KumaDpBuildDate    string
-	EnvoyVersion       string
-	EnvoyBuild         string
-}
-
-const configTemplate string = `
+const configTemplateV2 string = `
 node:
   id: {{.Id}}
   cluster: {{.Service}}
@@ -77,6 +54,7 @@ dynamic_resources:
   cds_config: {ads: {}}
   ads_config:
     api_type: GRPC
+    transport_api_version: V2
     timeout: {{ .XdsConnectTimeout }}
     grpc_services:
     - googleGrpc:
diff --git a/pkg/xds/cache/cla/cache.go b/pkg/xds/cache/cla/cache.go
index 5bd2dc09a..d3976ac39 100644
--- a/pkg/xds/cache/cla/cache.go
+++ b/pkg/xds/cache/cla/cache.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/golang/protobuf/proto"
+
 	"github.com/kumahq/kuma/pkg/core/datasource"
 
 	"github.com/prometheus/client_golang/prometheus"
@@ -12,7 +14,6 @@ import (
 	"github.com/kumahq/kuma/pkg/core/resources/model"
 	"github.com/kumahq/kuma/pkg/metrics"
 
-	envoy_api_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
 	"github.com/patrickmn/go-cache"
 
 	"github.com/kumahq/kuma/pkg/core"
@@ -21,7 +22,8 @@ import (
 	"github.com/kumahq/kuma/pkg/core/resources/manager"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	"github.com/kumahq/kuma/pkg/xds/cache/once"
-	"github.com/kumahq/kuma/pkg/xds/envoy/endpoints/v2"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	envoy_endpoints "github.com/kumahq/kuma/pkg/xds/envoy/endpoints"
 	"github.com/kumahq/kuma/pkg/xds/topology"
 )
 
@@ -67,12 +69,12 @@ func NewCache(
 	}, nil
 }
 
-func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string) (*envoy_api_v2.ClusterLoadAssignment, error) {
-	key := fmt.Sprintf("%s:%s:%s", meshName, service, meshHash)
+func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string, apiVersion envoy_common.APIVersion) (proto.Message, error) {
+	key := fmt.Sprintf("%s:%s:%s:%s", apiVersion, meshName, service, meshHash)
 	value, found := c.cache.Get(key)
 	if found {
 		c.metrics.WithLabelValues("get", "hit").Inc()
-		return value.(*envoy_api_v2.ClusterLoadAssignment), nil
+		return value.(proto.Message), nil
 	}
 	o := c.onceMap.Get(key)
 	c.metrics.WithLabelValues("get", "hit-wait").Inc()
@@ -92,7 +94,10 @@ func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string)
 			return nil, err
 		}
 		endpointMap := topology.BuildEndpointMap(mesh, c.zone, dataplanes.Items, externalServices.Items, c.dsl)
-		cla := endpoints.CreateClusterLoadAssignment(service, endpointMap[service])
+		cla, err := envoy_endpoints.CreateClusterLoadAssignment(service, endpointMap[service], apiVersion)
+		if err != nil {
+			return nil, err
+		}
 		c.cache.SetDefault(key, cla)
 		c.onceMap.Delete(key)
 		return cla, nil
@@ -100,5 +105,5 @@ func (c *Cache) GetCLA(ctx context.Context, meshName, meshHash, service string)
 	if o.Err != nil {
 		return nil, o.Err
 	}
-	return o.Value.(*envoy_api_v2.ClusterLoadAssignment), nil
+	return o.Value.(proto.Message), nil
 }
diff --git a/pkg/xds/envoy/endpoints/endpoints.go b/pkg/xds/envoy/endpoints/endpoints.go
new file mode 100644
index 000000000..25da1f867
--- /dev/null
+++ b/pkg/xds/envoy/endpoints/endpoints.go
@@ -0,0 +1,34 @@
+package endpoints
+
+import (
+	"errors"
+
+	"github.com/golang/protobuf/proto"
+
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	endpoints_v2 "github.com/kumahq/kuma/pkg/xds/envoy/endpoints/v2"
+	endpoints_v3 "github.com/kumahq/kuma/pkg/xds/envoy/endpoints/v3"
+)
+
+func CreateStaticEndpoint(clusterName string, address string, port uint32, apiVersion envoy_common.APIVersion) (proto.Message, error) {
+	switch apiVersion {
+	case envoy_common.APIV2:
+		return endpoints_v2.CreateStaticEndpoint(clusterName, address, port), nil
+	case envoy_common.APIV3:
+		return endpoints_v3.CreateStaticEndpoint(clusterName, address, port), nil
+	default:
+		return nil, errors.New("unknown API")
+	}
+}
+
+func CreateClusterLoadAssignment(clusterName string, endpoints []core_xds.Endpoint, apiVersion envoy_common.APIVersion) (proto.Message, error) {
+	switch apiVersion {
+	case envoy_common.APIV2:
+		return endpoints_v2.CreateClusterLoadAssignment(clusterName, endpoints), nil
+	case envoy_common.APIV3:
+		return endpoints_v3.CreateClusterLoadAssignment(clusterName, endpoints), nil
+	default:
+		return nil, errors.New("unknown API")
+	}
+}
diff --git a/pkg/sds/provider/ca/types.go b/pkg/xds/envoy/secrets/v2/ca_secret.go
similarity index 59%
rename from pkg/sds/provider/ca/types.go
rename to pkg/xds/envoy/secrets/v2/ca_secret.go
index 583991548..f9b12c2f8 100644
--- a/pkg/sds/provider/ca/types.go
+++ b/pkg/xds/envoy/secrets/v2/ca_secret.go
@@ -1,4 +1,4 @@
-package ca
+package v2
 
 import (
 	"bytes"
@@ -6,23 +6,18 @@ import (
 	envoy_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
+	"github.com/kumahq/kuma/pkg/xds/envoy/tls"
 )
 
-type MeshCaSecret struct {
-	PemCerts [][]byte
-}
-
-var _ sds_provider.Secret = &MeshCaSecret{}
-
-func (s *MeshCaSecret) ToResource(name string) *envoy_auth.Secret {
+func CreateCaSecret(secret *core_xds.CaSecret) *envoy_auth.Secret {
 	return &envoy_auth.Secret{
-		Name: name,
+		Name: tls.MeshCaResource,
 		Type: &envoy_auth.Secret_ValidationContext{
 			ValidationContext: &envoy_auth.CertificateValidationContext{
 				TrustedCa: &envoy_core.DataSource{
 					Specifier: &envoy_core.DataSource_InlineBytes{
-						InlineBytes: bytes.Join(s.PemCerts, []byte("\n")),
+						InlineBytes: bytes.Join(secret.PemCerts, []byte("\n")),
 					},
 				},
 			},
diff --git a/pkg/sds/provider/identity/types.go b/pkg/xds/envoy/secrets/v2/identity_secret.go
similarity index 58%
rename from pkg/sds/provider/identity/types.go
rename to pkg/xds/envoy/secrets/v2/identity_secret.go
index 8cbc2741a..6a522f532 100644
--- a/pkg/sds/provider/identity/types.go
+++ b/pkg/xds/envoy/secrets/v2/identity_secret.go
@@ -1,4 +1,4 @@
-package identity
+package v2
 
 import (
 	"bytes"
@@ -6,29 +6,23 @@ import (
 	envoy_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
 	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 
-	sds_provider "github.com/kumahq/kuma/pkg/sds/provider"
+	core_xds "github.com/kumahq/kuma/pkg/core/xds"
+	"github.com/kumahq/kuma/pkg/xds/envoy/tls"
 )
 
-type IdentityCertSecret struct {
-	PemCerts [][]byte
-	PemKey   []byte
-}
-
-var _ sds_provider.Secret = &IdentityCertSecret{}
-
-func (s *IdentityCertSecret) ToResource(name string) *envoy_auth.Secret {
+func CreateIdentitySecret(secret *core_xds.IdentitySecret) *envoy_auth.Secret {
 	return &envoy_auth.Secret{
-		Name: name,
+		Name: tls.IdentityCertResource,
 		Type: &envoy_auth.Secret_TlsCertificate{
 			TlsCertificate: &envoy_auth.TlsCertificate{
 				CertificateChain: &envoy_core.DataSource{
 					Specifier: &envoy_core.DataSource_InlineBytes{
-						InlineBytes: bytes.Join(s.PemCerts, []byte("\n")),
+						InlineBytes: bytes.Join(secret.PemCerts, []byte("\n")),
 					},
 				},
 				PrivateKey: &envoy_core.DataSource{
 					Specifier: &envoy_core.DataSource_InlineBytes{
-						InlineBytes: s.PemKey,
+						InlineBytes: secret.PemKey,
 					},
 				},
 			},
diff --git a/pkg/xds/generator/direct_access_proxy_generator.go b/pkg/xds/generator/direct_access_proxy_generator.go
index 6bc734f25..a61131ea3 100644
--- a/pkg/xds/generator/direct_access_proxy_generator.go
+++ b/pkg/xds/generator/direct_access_proxy_generator.go
@@ -46,9 +46,9 @@ func (_ DirectAccessProxyGenerator) Generate(ctx xds_context.Context, proxy *cor
 
 	for _, endpoint := range endpoints {
 		name := fmt.Sprintf("direct_access_%s:%d", endpoint.Address, endpoint.Port)
-		listener, err := envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+		listener, err := envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.OutboundListener(name, endpoint.Address, endpoint.Port)).
-			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2).
+			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 				Configure(envoy_listeners.TcpProxy(name, envoy_common.ClusterSubset{ClusterName: "direct_access"})).
 				Configure(envoy_listeners.NetworkAccessLog(meshName, envoy_common.TrafficDirectionOutbound, sourceService, name, proxy.Policies.Logs[mesh_core.PassThroughService], proxy)))).
 			Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
@@ -63,7 +63,7 @@ func (_ DirectAccessProxyGenerator) Generate(ctx xds_context.Context, proxy *cor
 		})
 	}
 
-	directAccessCluster, err := envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+	directAccessCluster, err := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 		Configure(envoy_clusters.PassThroughCluster("direct_access")).
 		Configure(envoy_clusters.UnknownDestinationClientSideMTLS(ctx, proxy.Metadata)).
 		Build()
diff --git a/pkg/xds/generator/inbound_proxy_generator.go b/pkg/xds/generator/inbound_proxy_generator.go
index 592e93eb9..173a81bae 100644
--- a/pkg/xds/generator/inbound_proxy_generator.go
+++ b/pkg/xds/generator/inbound_proxy_generator.go
@@ -32,7 +32,7 @@ func (g InboundProxyGenerator) Generate(ctx xds_context.Context, proxy *model.Pr
 
 		// generate CDS resource
 		localClusterName := envoy_names.GetLocalClusterName(endpoint.WorkloadPort)
-		clusterBuilder := envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+		clusterBuilder := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 			Configure(envoy_clusters.StaticCluster(localClusterName, endpoint.WorkloadIP, endpoint.WorkloadPort))
 		switch protocol {
 		case mesh_core.ProtocolHTTP2, mesh_core.ProtocolGRPC:
@@ -52,7 +52,7 @@ func (g InboundProxyGenerator) Generate(ctx xds_context.Context, proxy *model.Pr
 		service := iface.GetService()
 		inboundListenerName := envoy_names.GetInboundListenerName(endpoint.DataplaneIP, endpoint.DataplanePort)
 		filterChainBuilder := func() *envoy_listeners.FilterChainBuilder {
-			filterChainBuilder := envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2)
+			filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion)
 			switch protocol {
 			// configuration for HTTP case
 			case mesh_core.ProtocolHTTP, mesh_core.ProtocolHTTP2:
@@ -82,7 +82,7 @@ func (g InboundProxyGenerator) Generate(ctx xds_context.Context, proxy *model.Pr
 				Configure(envoy_listeners.ServerSideMTLS(ctx, proxy.Metadata)).
 				Configure(envoy_listeners.NetworkRBAC(inboundListenerName, ctx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[endpoint]))
 		}()
-		inboundListener, err := envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+		inboundListener, err := envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.InboundListener(inboundListenerName, endpoint.DataplaneIP, endpoint.DataplanePort)).
 			Configure(envoy_listeners.FilterChain(filterChainBuilder)).
 			Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
diff --git a/pkg/xds/generator/ingress_generator.go b/pkg/xds/generator/ingress_generator.go
index 13ff70752..fbbfb3241 100644
--- a/pkg/xds/generator/ingress_generator.go
+++ b/pkg/xds/generator/ingress_generator.go
@@ -11,7 +11,7 @@ import (
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
 	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	envoy_clusters "github.com/kumahq/kuma/pkg/xds/envoy/clusters"
-	envoy_endpoints "github.com/kumahq/kuma/pkg/xds/envoy/endpoints/v2"
+	envoy_endpoints "github.com/kumahq/kuma/pkg/xds/envoy/endpoints"
 	envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners"
 	envoy_names "github.com/kumahq/kuma/pkg/xds/envoy/names"
 )
@@ -31,7 +31,7 @@ func (i IngressGenerator) Generate(ctx xds_context.Context, proxy *model.Proxy)
 
 	destinationsPerService := i.destinations(proxy.Routing.TrafficRouteList)
 
-	listener, err := i.generateLDS(proxy.Dataplane, destinationsPerService)
+	listener, err := i.generateLDS(proxy.Dataplane, destinationsPerService, proxy.APIVersion)
 	if err != nil {
 		return nil, err
 	}
@@ -43,13 +43,16 @@ func (i IngressGenerator) Generate(ctx xds_context.Context, proxy *model.Proxy)
 
 	services := i.services(proxy)
 
-	cdsResources, err := i.generateCDS(services, destinationsPerService)
+	cdsResources, err := i.generateCDS(services, destinationsPerService, proxy.APIVersion)
 	if err != nil {
 		return nil, err
 	}
 	resources.Add(cdsResources...)
 
-	edsResources := i.generateEDS(proxy, services)
+	edsResources, err := i.generateEDS(proxy, services, proxy.APIVersion)
+	if err != nil {
+		return nil, err
+	}
 	resources.Add(edsResources...)
 
 	return resources, nil
@@ -61,16 +64,20 @@ func (i IngressGenerator) Generate(ctx xds_context.Context, proxy *model.Proxy)
 // We take all possible destinations from TrafficRoutes and generate FilterChainsMatcher for each unique destination.
 // This approach has a limitation: additional tags on outbound in Universal mode won't work across different zones.
 // Traffic is NOT decrypted here, therefore we don't need certificates and mTLS settings
-func (i IngressGenerator) generateLDS(ingress *core_mesh.DataplaneResource, destinationsPerService map[string][]envoy_common.Tags) (envoy_common.NamedResource, error) {
+func (i IngressGenerator) generateLDS(
+	ingress *core_mesh.DataplaneResource,
+	destinationsPerService map[string][]envoy_common.Tags,
+	apiVersion envoy_common.APIVersion,
+) (envoy_common.NamedResource, error) {
 	inbound := ingress.Spec.Networking.Inbound[0]
 	inboundListenerName := envoy_names.GetInboundListenerName(ingress.Spec.GetNetworking().GetAddress(), inbound.Port)
-	inboundListenerBuilder := envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+	inboundListenerBuilder := envoy_listeners.NewListenerBuilder(apiVersion).
 		Configure(envoy_listeners.InboundListener(inboundListenerName, ingress.Spec.GetNetworking().GetAddress(), inbound.Port)).
 		Configure(envoy_listeners.TLSInspector())
 
 	if !ingress.Spec.HasAvailableServices() {
 		inboundListenerBuilder = inboundListenerBuilder.
-			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2)))
+			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(apiVersion)))
 	}
 
 	sniUsed := map[string]bool{}
@@ -90,7 +97,7 @@ func (i IngressGenerator) generateLDS(ingress *core_mesh.DataplaneResource, dest
 			}
 			sniUsed[sni] = true
 			inboundListenerBuilder = inboundListenerBuilder.
-				Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2).
+				Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(apiVersion).
 					Configure(envoy_listeners.FilterChainMatch(sni)).
 					Configure(envoy_listeners.TcpProxy(service, envoy_common.ClusterSubset{
 						ClusterName: service,
@@ -122,9 +129,13 @@ func (_ IngressGenerator) services(proxy *model.Proxy) []string {
 	return services
 }
 
-func (i IngressGenerator) generateCDS(services []string, destinationsPerService map[string][]envoy_common.Tags) (resources []*model.Resource, _ error) {
+func (i IngressGenerator) generateCDS(
+	services []string,
+	destinationsPerService map[string][]envoy_common.Tags,
+	apiVersion envoy_common.APIVersion,
+) (resources []*model.Resource, _ error) {
 	for _, service := range services {
-		edsCluster, err := envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+		edsCluster, err := envoy_clusters.NewClusterBuilder(apiVersion).
 			Configure(envoy_clusters.EdsCluster(service)).
 			Configure(envoy_clusters.LbSubset(i.lbSubsets(service, destinationsPerService))).
 			Build()
@@ -151,13 +162,21 @@ func (_ IngressGenerator) lbSubsets(service string, destinationsPerService map[s
 	return selectors
 }
 
-func (_ IngressGenerator) generateEDS(proxy *model.Proxy, services []string) (resources []*model.Resource) {
+func (_ IngressGenerator) generateEDS(
+	proxy *model.Proxy,
+	services []string,
+	apiVersion envoy_common.APIVersion,
+) (resources []*model.Resource, err error) {
 	for _, service := range services {
 		endpoints := proxy.Routing.OutboundTargets[service]
+		cla, err := envoy_endpoints.CreateClusterLoadAssignment(service, endpoints, apiVersion)
+		if err != nil {
+			return nil, err
+		}
 		resources = append(resources, &model.Resource{
 			Name:     service,
 			Origin:   OriginIngress,
-			Resource: envoy_endpoints.CreateClusterLoadAssignment(service, endpoints),
+			Resource: cla,
 		})
 	}
 	return
diff --git a/pkg/xds/generator/outbound_proxy_generator.go b/pkg/xds/generator/outbound_proxy_generator.go
index dc34a61a0..ec81b805c 100644
--- a/pkg/xds/generator/outbound_proxy_generator.go
+++ b/pkg/xds/generator/outbound_proxy_generator.go
@@ -66,7 +66,7 @@ func (g OutboundProxyGenerator) Generate(ctx xds_context.Context, proxy *model.P
 	}
 	resources.AddSet(cdsResources)
 
-	edsResources, err := g.generateEDS(ctx, clusters)
+	edsResources, err := g.generateEDS(ctx, clusters, proxy.APIVersion)
 	if err != nil {
 		return nil, err
 	}
@@ -83,7 +83,7 @@ func (_ OutboundProxyGenerator) generateLDS(proxy *model.Proxy, subsets []envoy_
 	outboundListenerName := envoy_names.GetOutboundListenerName(oface.DataplaneIP, oface.DataplanePort)
 	retryPolicy := proxy.Policies.Retries[serviceName]
 	filterChainBuilder := func() *envoy_listeners.FilterChainBuilder {
-		filterChainBuilder := envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2)
+		filterChainBuilder := envoy_listeners.NewFilterChainBuilder(proxy.APIVersion)
 		switch protocol {
 		case mesh_core.ProtocolGRPC:
 			filterChainBuilder.
@@ -139,7 +139,7 @@ func (_ OutboundProxyGenerator) generateLDS(proxy *model.Proxy, subsets []envoy_
 		}
 		return filterChainBuilder
 	}()
-	listener, err := envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+	listener, err := envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 		Configure(envoy_listeners.OutboundListener(outboundListenerName, oface.DataplaneIP, oface.DataplanePort)).
 		Configure(envoy_listeners.FilterChain(filterChainBuilder)).
 		Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
@@ -157,7 +157,7 @@ func (o OutboundProxyGenerator) generateCDS(ctx xds_context.Context, proxy *mode
 		tags := clusters.Tags(clusterName)
 		healthCheck := proxy.Policies.HealthChecks[serviceName]
 		circuitBreaker := proxy.Policies.CircuitBreakers[serviceName]
-		edsClusterBuilder := envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+		edsClusterBuilder := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 			Configure(envoy_clusters.LbSubset(o.lbSubsets(tags))).
 			Configure(envoy_clusters.OutlierDetection(circuitBreaker)).
 			Configure(envoy_clusters.HealthCheck(healthCheck))
@@ -205,14 +205,14 @@ func (_ OutboundProxyGenerator) lbSubsets(tagSets []envoy_common.Tags) [][]strin
 	return result
 }
 
-func (_ OutboundProxyGenerator) generateEDS(ctx xds_context.Context, clusters envoy_common.Clusters) (*model.ResourceSet, error) {
+func (_ OutboundProxyGenerator) generateEDS(ctx xds_context.Context, clusters envoy_common.Clusters, apiVersion envoy_common.APIVersion) (*model.ResourceSet, error) {
 	resources := model.NewResourceSet()
 	for _, clusterName := range clusters.ClusterNames() {
 		// Endpoints for ExternalServices are specified in load assignment in DNS Cluster.
 		// We are not allowed to add endpoints with DNS names through EDS.
 		if !clusters.Get(clusterName).HasExternalService() {
 			serviceName := clusters.Tags(clusterName)[0][kuma_mesh.ServiceTag]
-			loadAssignment, err := ctx.ControlPlane.CLACache.GetCLA(context.Background(), ctx.Mesh.Resource.Meta.GetName(), ctx.Mesh.Hash, serviceName)
+			loadAssignment, err := ctx.ControlPlane.CLACache.GetCLA(context.Background(), ctx.Mesh.Resource.Meta.GetName(), ctx.Mesh.Hash, serviceName, apiVersion)
 			if err != nil {
 				return nil, errors.Wrapf(err, "could not get ClusterLoadAssingment for %s", serviceName)
 			}
diff --git a/pkg/xds/generator/probe_generator.go b/pkg/xds/generator/probe_generator.go
index 25677479b..edf78fccd 100644
--- a/pkg/xds/generator/probe_generator.go
+++ b/pkg/xds/generator/probe_generator.go
@@ -5,7 +5,6 @@ import (
 
 	model "github.com/kumahq/kuma/pkg/core/xds"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
-	"github.com/kumahq/kuma/pkg/xds/envoy"
 	envoy_listeners "github.com/kumahq/kuma/pkg/xds/envoy/listeners"
 	"github.com/kumahq/kuma/pkg/xds/envoy/names"
 	envoy_routes "github.com/kumahq/kuma/pkg/xds/envoy/routes"
@@ -26,7 +25,7 @@ func (g ProbeProxyGenerator) Generate(ctx xds_context.Context, proxy *model.Prox
 		return nil, nil
 	}
 
-	virtualHostBuilder := envoy_routes.NewVirtualHostBuilder(envoy.APIV2).
+	virtualHostBuilder := envoy_routes.NewVirtualHostBuilder(proxy.APIVersion).
 		Configure(envoy_routes.CommonVirtualHost("probe"))
 
 	portSet := map[uint32]bool{}
@@ -47,11 +46,11 @@ func (g ProbeProxyGenerator) Generate(ctx xds_context.Context, proxy *model.Prox
 		}
 	}
 
-	probeListener, err := envoy_listeners.NewListenerBuilder(envoy.APIV2).
+	probeListener, err := envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 		Configure(envoy_listeners.InboundListener(listenerName, proxy.Dataplane.Spec.GetNetworking().GetAddress(), probes.Port)).
-		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy.APIV2).
+		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.HttpConnectionManager(listenerName)).
-			Configure(envoy_listeners.HttpStaticRoute(envoy_routes.NewRouteConfigurationBuilder(envoy.APIV2).
+			Configure(envoy_listeners.HttpStaticRoute(envoy_routes.NewRouteConfigurationBuilder(proxy.APIVersion).
 				Configure(envoy_routes.VirtualHost(virtualHostBuilder)))))).
 		Configure(envoy_listeners.TransparentProxying(proxy.Dataplane.Spec.Networking.GetTransparentProxying())).
 		Build()
diff --git a/pkg/xds/generator/prometheus_endpoint_generator.go b/pkg/xds/generator/prometheus_endpoint_generator.go
index f0e886e08..d83654272 100644
--- a/pkg/xds/generator/prometheus_endpoint_generator.go
+++ b/pkg/xds/generator/prometheus_endpoint_generator.go
@@ -76,23 +76,23 @@ func (g PrometheusEndpointGenerator) Generate(ctx xds_context.Context, proxy *co
 	iface := proxy.Dataplane.Spec.GetNetworking().ToInboundInterface(inbound)
 	var listener envoy.NamedResource
 	if secureMetrics(prometheusEndpoint, ctx.Mesh.Resource) {
-		listener, err = envoy_listeners.NewListenerBuilder(envoy.APIV2).
+		listener, err = envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.InboundListener(prometheusListenerName, prometheusEndpointAddress, prometheusEndpoint.Port)).
 			// generate filter chain that does not require mTLS when DP scrapes itself (for example DP next to Prometheus Server)
-			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy.APIV2).
+			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 				Configure(envoy_listeners.SourceMatcher(proxy.Dataplane.Spec.GetNetworking().Address)).
 				Configure(envoy_listeners.PrometheusEndpoint(prometheusListenerName, prometheusEndpoint.Path, envoyAdminClusterName)),
 			)).
-			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy.APIV2).
+			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 				Configure(envoy_listeners.PrometheusEndpoint(prometheusListenerName, prometheusEndpoint.Path, envoyAdminClusterName)).
 				Configure(envoy_listeners.ServerSideMTLS(ctx, proxy.Metadata)).
 				Configure(envoy_listeners.NetworkRBAC(prometheusListenerName, ctx.Mesh.Resource.MTLSEnabled(), proxy.Policies.TrafficPermissions[iface])),
 			)).
 			Build()
 	} else {
-		listener, err = envoy_listeners.NewListenerBuilder(envoy.APIV2).
+		listener, err = envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.InboundListener(prometheusListenerName, prometheusEndpointAddress, prometheusEndpoint.Port)).
-			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy.APIV2).
+			Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 				Configure(envoy_listeners.PrometheusEndpoint(prometheusListenerName, prometheusEndpoint.Path, envoyAdminClusterName)),
 			)).
 			Build()
@@ -100,7 +100,7 @@ func (g PrometheusEndpointGenerator) Generate(ctx xds_context.Context, proxy *co
 	if err != nil {
 		return nil, err
 	}
-	cluster, err := envoy_clusters.NewClusterBuilder(envoy.APIV2).
+	cluster, err := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 		Configure(envoy_clusters.StaticCluster(envoyAdminClusterName, adminAddress, adminPort)).
 		Build()
 	if err != nil {
diff --git a/pkg/xds/generator/tracing_proxy_generator.go b/pkg/xds/generator/tracing_proxy_generator.go
index c50d6232b..6225d13c2 100644
--- a/pkg/xds/generator/tracing_proxy_generator.go
+++ b/pkg/xds/generator/tracing_proxy_generator.go
@@ -30,7 +30,7 @@ func (t TracingProxyGenerator) Generate(_ xds_context.Context, proxy *core_xds.P
 	resources := core_xds.NewResourceSet()
 	switch proxy.Policies.TracingBackend.Type {
 	case mesh_proto.TracingZipkinType:
-		res, err := t.zipkinCluster(proxy.Policies.TracingBackend)
+		res, err := t.zipkinCluster(proxy.Policies.TracingBackend, proxy.APIVersion)
 		if err != nil {
 			return nil, errors.Wrap(err, "could not generate zipkin cluster")
 		}
@@ -39,7 +39,7 @@ func (t TracingProxyGenerator) Generate(_ xds_context.Context, proxy *core_xds.P
 	return resources, nil
 }
 
-func (t TracingProxyGenerator) zipkinCluster(backend *mesh_proto.TracingBackend) (*core_xds.Resource, error) {
+func (t TracingProxyGenerator) zipkinCluster(backend *mesh_proto.TracingBackend, apiVersion envoy.APIVersion) (*core_xds.Resource, error) {
 	cfg := mesh_proto.ZipkinTracingBackendConfig{}
 	if err := proto.ToTyped(backend.Conf, &cfg); err != nil {
 		return nil, errors.Wrap(err, "could not convert backend")
@@ -54,7 +54,7 @@ func (t TracingProxyGenerator) zipkinCluster(backend *mesh_proto.TracingBackend)
 	}
 
 	clusterName := names.GetTracingClusterName(backend.Name)
-	cluster, err := clusters.NewClusterBuilder(envoy.APIV2).
+	cluster, err := clusters.NewClusterBuilder(apiVersion).
 		Configure(clusters.DNSCluster(clusterName, url.Hostname(), uint32(port))).
 		Build()
 	if err != nil {
diff --git a/pkg/xds/generator/transparent_proxy_generator.go b/pkg/xds/generator/transparent_proxy_generator.go
index b728d9094..ffae1c394 100644
--- a/pkg/xds/generator/transparent_proxy_generator.go
+++ b/pkg/xds/generator/transparent_proxy_generator.go
@@ -35,7 +35,7 @@ func (_ TransparentProxyGenerator) Generate(ctx xds_context.Context, proxy *mode
 	var err error
 
 	if ctx.Mesh.Resource.Spec.IsPassthrough() {
-		outboundPassThroughCluster, err = envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+		outboundPassThroughCluster, err = envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 			Configure(envoy_clusters.PassThroughCluster(outboundName)).
 			Build()
 		if err != nil {
@@ -43,9 +43,9 @@ func (_ TransparentProxyGenerator) Generate(ctx xds_context.Context, proxy *mode
 		}
 	}
 
-	outboundListener, err = envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+	outboundListener, err = envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 		Configure(envoy_listeners.OutboundListener(outboundName, "0.0.0.0", redirectPortOutbound)).
-		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2).
+		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.TcpProxy(outboundName, envoy_common.ClusterSubset{ClusterName: outboundName})).
 			Configure(envoy_listeners.NetworkAccessLog(meshName, envoy_common.TrafficDirectionUnspecified, sourceService, "external", proxy.Policies.Logs[mesh_core.PassThroughService], proxy)))).
 		Configure(envoy_listeners.OriginalDstForwarder()).
@@ -56,7 +56,7 @@ func (_ TransparentProxyGenerator) Generate(ctx xds_context.Context, proxy *mode
 
 	redirectPortInbound := proxy.Dataplane.Spec.Networking.GetTransparentProxying().GetRedirectPortInbound()
 
-	inboundPassThroughCluster, err := envoy_clusters.NewClusterBuilder(envoy_common.APIV2).
+	inboundPassThroughCluster, err := envoy_clusters.NewClusterBuilder(proxy.APIVersion).
 		Configure(envoy_clusters.PassThroughCluster(inboundName)).
 		Configure(envoy_clusters.UpstreamBindConfig("127.0.0.6", 0)).
 		Build()
@@ -64,9 +64,9 @@ func (_ TransparentProxyGenerator) Generate(ctx xds_context.Context, proxy *mode
 		return nil, errors.Wrapf(err, "could not generate cluster: %s", inboundName)
 	}
 
-	inboundListener, err := envoy_listeners.NewListenerBuilder(envoy_common.APIV2).
+	inboundListener, err := envoy_listeners.NewListenerBuilder(proxy.APIVersion).
 		Configure(envoy_listeners.InboundListener(inboundName, "0.0.0.0", redirectPortInbound)).
-		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(envoy_common.APIV2).
+		Configure(envoy_listeners.FilterChain(envoy_listeners.NewFilterChainBuilder(proxy.APIVersion).
 			Configure(envoy_listeners.TcpProxy(inboundName, envoy_common.ClusterSubset{ClusterName: inboundName})))).
 		Configure(envoy_listeners.OriginalDstForwarder()).
 		Build()
diff --git a/pkg/xds/metrics/metrics.go b/pkg/xds/metrics/metrics.go
new file mode 100644
index 000000000..77660421a
--- /dev/null
+++ b/pkg/xds/metrics/metrics.go
@@ -0,0 +1,35 @@
+package metrics
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+
+	core_metrics "github.com/kumahq/kuma/pkg/metrics"
+)
+
+type Metrics struct {
+	XdsGenerations       prometheus.Summary
+	XdsGenerationsErrors prometheus.Counter
+}
+
+func NewMetrics(metrics core_metrics.Metrics) (*Metrics, error) {
+	xdsGenerations := prometheus.NewSummary(prometheus.SummaryOpts{
+		Name:       "xds_generation",
+		Help:       "Summary of XDS Snapshot generation",
+		Objectives: core_metrics.DefaultObjectives,
+	})
+	if err := metrics.Register(xdsGenerations); err != nil {
+		return nil, err
+	}
+	xdsGenerationsErrors := prometheus.NewCounter(prometheus.CounterOpts{
+		Name: "xds_generation_errors",
+		Help: "Counter of errors during XDS generation",
+	})
+	if err := metrics.Register(xdsGenerationsErrors); err != nil {
+		return nil, err
+	}
+
+	return &Metrics{
+		XdsGenerations:       xdsGenerations,
+		XdsGenerationsErrors: xdsGenerationsErrors,
+	}, nil
+}
diff --git a/pkg/xds/server/components.go b/pkg/xds/server/components.go
index 6daaf5ccf..a4ed0af29 100644
--- a/pkg/xds/server/components.go
+++ b/pkg/xds/server/components.go
@@ -1,22 +1,72 @@
 package server
 
 import (
-	"time"
+	"github.com/pkg/errors"
+	"google.golang.org/grpc"
 
-	xds_callbacks "github.com/kumahq/kuma/pkg/xds/server/callbacks"
+	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
+	core_system "github.com/kumahq/kuma/pkg/core/resources/apis/system"
+	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/core/resources/registry"
+	util_xds "github.com/kumahq/kuma/pkg/util/xds"
+	"github.com/kumahq/kuma/pkg/xds/cache/cla"
+	"github.com/kumahq/kuma/pkg/xds/cache/mesh"
+	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	xds_metrics "github.com/kumahq/kuma/pkg/xds/metrics"
+	v2 "github.com/kumahq/kuma/pkg/xds/server/v2"
 
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
 )
 
-func DefaultDataplaneStatusTracker(rt core_runtime.Runtime) xds_callbacks.DataplaneStatusTracker {
-	return xds_callbacks.NewDataplaneStatusTracker(rt, func(accessor xds_callbacks.SubscriptionStatusAccessor) xds_callbacks.DataplaneInsightSink {
-		return xds_callbacks.NewDataplaneInsightSink(
-			accessor,
-			func() *time.Ticker {
-				return time.NewTicker(rt.Config().XdsServer.DataplaneStatusFlushInterval)
-			},
-			rt.Config().XdsServer.DataplaneStatusFlushInterval/10,
-			xds_callbacks.NewDataplaneInsightStore(rt.ResourceManager()),
-		)
+var (
+	meshResources = meshResourceTypes(map[core_model.ResourceType]bool{
+		core_mesh.DataplaneInsightType:  true,
+		core_mesh.DataplaneOverviewType: true,
+		core_mesh.ServiceInsightType:    true,
+		core_system.ConfigType:          true,
 	})
+)
+
+func meshResourceTypes(exclude map[core_model.ResourceType]bool) []core_model.ResourceType {
+	types := []core_model.ResourceType{}
+	for _, typ := range registry.Global().ListTypes() {
+		r, err := registry.Global().NewObject(typ)
+		if err != nil {
+			panic(err)
+		}
+		if r.Scope() == core_model.ScopeMesh && !exclude[typ] {
+			types = append(types, typ)
+		}
+	}
+	return types
+}
+
+func RegisterXDS(rt core_runtime.Runtime, server *grpc.Server) error {
+	// Build common dependencies for V2 and V3 servers.
+	// We want to have same metrics (we cannot register one metric twice) and same caches for both V2 and V3.
+	statsCallbacks, err := util_xds.NewStatsCallbacks(rt.Metrics(), "xds")
+	if err != nil {
+		return err
+	}
+	xdsMetrics, err := xds_metrics.NewMetrics(rt.Metrics())
+	if err != nil {
+		return err
+	}
+	meshSnapshotCache, err := mesh.NewCache(rt.ReadOnlyResourceManager(), rt.Config().Store.Cache.ExpirationTime, meshResources, rt.LookupIP(), rt.Metrics())
+	if err != nil {
+		return err
+	}
+	claCache, err := cla.NewCache(rt.ReadOnlyResourceManager(), rt.DataSourceLoader(), rt.Config().Multizone.Remote.Zone, rt.Config().Store.Cache.ExpirationTime, rt.LookupIP(), rt.Metrics())
+	if err != nil {
+		return err
+	}
+	envoyCpCtx, err := xds_context.BuildControlPlaneContext(rt.Config(), claCache)
+	if err != nil {
+		return err
+	}
+
+	if err := v2.RegisterXDS(statsCallbacks, xdsMetrics, meshSnapshotCache, envoyCpCtx, rt, server); err != nil {
+		return errors.Wrap(err, "could not register V2 XDS")
+	}
+	return nil
 }
diff --git a/pkg/xds/server/v2/components.go b/pkg/xds/server/v2/components.go
index 58bd9a49b..b9c75326c 100644
--- a/pkg/xds/server/v2/components.go
+++ b/pkg/xds/server/v2/components.go
@@ -2,6 +2,7 @@ package v2
 
 import (
 	"context"
+	"time"
 
 	envoy_service_discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v2"
 	envoy_server "github.com/envoyproxy/go-control-plane/pkg/server/v2"
@@ -9,11 +10,15 @@ import (
 
 	"github.com/kumahq/kuma/pkg/core"
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
+	v2 "github.com/kumahq/kuma/pkg/core/xds/v2"
 	util_xds "github.com/kumahq/kuma/pkg/util/xds"
 	util_xds_v2 "github.com/kumahq/kuma/pkg/util/xds/v2"
 	"github.com/kumahq/kuma/pkg/xds/auth"
 	auth_components "github.com/kumahq/kuma/pkg/xds/auth/components"
-	xds_server "github.com/kumahq/kuma/pkg/xds/server"
+	"github.com/kumahq/kuma/pkg/xds/cache/mesh"
+	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
+	xds_metrics "github.com/kumahq/kuma/pkg/xds/metrics"
 	xds_callbacks "github.com/kumahq/kuma/pkg/xds/server/callbacks"
 	xds_sync "github.com/kumahq/kuma/pkg/xds/sync"
 	xds_template "github.com/kumahq/kuma/pkg/xds/template"
@@ -21,40 +26,32 @@ import (
 
 var xdsServerLog = core.Log.WithName("xds-server")
 
-func RegisterXDS(rt core_runtime.Runtime, server *grpc.Server) error {
-	callbacks, err := DefaultCallbacks(rt)
-	if err != nil {
-		return err
-	}
-	srv := envoy_server.NewServer(context.Background(), rt.XDS().Cache(), callbacks)
-
-	xdsServerLog.Info("registering Aggregated Discovery Service in Dataplane Server")
-	envoy_service_discovery.RegisterAggregatedDiscoveryServiceServer(server, srv)
-	return nil
-}
+func RegisterXDS(
+	statsCallbacks util_xds.Callbacks,
+	xdsMetrics *xds_metrics.Metrics,
+	meshSnapshotCache *mesh.Cache,
+	envoyCpCtx *xds_context.ControlPlaneContext,
+	rt core_runtime.Runtime,
+	server *grpc.Server,
+) error {
+	xdsContext := v2.NewXdsContext()
 
-func DefaultCallbacks(rt core_runtime.Runtime) (envoy_server.Callbacks, error) {
 	authenticator, err := auth_components.DefaultAuthenticator(rt)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	authCallbacks := auth.NewCallbacks(rt.ResourceManager(), authenticator)
 
 	metadataTracker := xds_callbacks.NewDataplaneMetadataTracker()
 	connectionInfoTracker := xds_callbacks.NewConnectionInfoTracker()
-
-	reconciler := DefaultReconciler(rt)
-	ingressReconciler := DefaultIngressReconciler(rt)
-	watchdogFactory, err := xds_sync.DefaultDataplaneWatchdogFactory(rt, metadataTracker, connectionInfoTracker, reconciler, ingressReconciler)
+	reconciler := DefaultReconciler(rt, xdsContext)
+	ingressReconciler := DefaultIngressReconciler(rt, xdsContext)
+	watchdogFactory, err := xds_sync.DefaultDataplaneWatchdogFactory(rt, metadataTracker, connectionInfoTracker, reconciler, ingressReconciler, xdsMetrics, meshSnapshotCache, envoyCpCtx, envoy_common.APIV2)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	statsCallbacks, err := util_xds.NewStatsCallbacks(rt.Metrics(), "xds")
-	if err != nil {
-		return nil, err
-	}
-	return util_xds_v2.CallbacksChain{
+	callbacks := util_xds_v2.CallbacksChain{
 		util_xds_v2.NewControlPlaneIdCallbacks(rt.GetInstanceId()),
 		util_xds_v2.AdaptCallbacks(statsCallbacks),
 		util_xds_v2.AdaptCallbacks(connectionInfoTracker),
@@ -62,12 +59,18 @@ func DefaultCallbacks(rt core_runtime.Runtime) (envoy_server.Callbacks, error) {
 		util_xds_v2.AdaptCallbacks(xds_callbacks.NewDataplaneSyncTracker(watchdogFactory.New)),
 		util_xds_v2.AdaptCallbacks(metadataTracker),
 		util_xds_v2.AdaptCallbacks(xds_callbacks.NewDataplaneLifecycle(rt.ResourceManager())),
-		util_xds_v2.AdaptCallbacks(xds_server.DefaultDataplaneStatusTracker(rt)),
-		newResourceWarmingForcer(rt.XDS().Cache(), rt.XDS().Hasher()),
-	}, nil
+		util_xds_v2.AdaptCallbacks(DefaultDataplaneStatusTracker(rt)),
+		newResourceWarmingForcer(xdsContext.Cache(), xdsContext.Hasher()),
+	}
+
+	srv := envoy_server.NewServer(context.Background(), xdsContext.Cache(), callbacks)
+
+	xdsServerLog.Info("registering Aggregated Discovery Service V2 in Dataplane Server")
+	envoy_service_discovery.RegisterAggregatedDiscoveryServiceServer(server, srv)
+	return nil
 }
 
-func DefaultReconciler(rt core_runtime.Runtime) xds_sync.SnapshotReconciler {
+func DefaultReconciler(rt core_runtime.Runtime, xdsContext v2.XdsContext) xds_sync.SnapshotReconciler {
 	return &reconciler{
 		&templateSnapshotGenerator{
 			ProxyTemplateResolver: &xds_template.SimpleProxyTemplateResolver{
@@ -75,17 +78,30 @@ func DefaultReconciler(rt core_runtime.Runtime) xds_sync.SnapshotReconciler {
 				DefaultProxyTemplate:    xds_template.DefaultProxyTemplate,
 			},
 		},
-		&simpleSnapshotCacher{rt.XDS().Hasher(), rt.XDS().Cache()},
+		&simpleSnapshotCacher{xdsContext.Hasher(), xdsContext.Cache()},
 	}
 }
 
-func DefaultIngressReconciler(rt core_runtime.Runtime) xds_sync.SnapshotReconciler {
+func DefaultIngressReconciler(rt core_runtime.Runtime, xdsContext v2.XdsContext) xds_sync.SnapshotReconciler {
 	return &reconciler{
 		generator: &templateSnapshotGenerator{
 			ProxyTemplateResolver: &xds_template.StaticProxyTemplateResolver{
 				Template: xds_template.IngressProxyTemplate,
 			},
 		},
-		cacher: &simpleSnapshotCacher{rt.XDS().Hasher(), rt.XDS().Cache()},
+		cacher: &simpleSnapshotCacher{xdsContext.Hasher(), xdsContext.Cache()},
 	}
 }
+
+func DefaultDataplaneStatusTracker(rt core_runtime.Runtime) xds_callbacks.DataplaneStatusTracker {
+	return xds_callbacks.NewDataplaneStatusTracker(rt, func(accessor xds_callbacks.SubscriptionStatusAccessor) xds_callbacks.DataplaneInsightSink {
+		return xds_callbacks.NewDataplaneInsightSink(
+			accessor,
+			func() *time.Ticker {
+				return time.NewTicker(rt.Config().XdsServer.DataplaneStatusFlushInterval)
+			},
+			rt.Config().XdsServer.DataplaneStatusFlushInterval/10,
+			xds_callbacks.NewDataplaneInsightStore(rt.ResourceManager()),
+		)
+	})
+}
diff --git a/pkg/xds/sync/components.go b/pkg/xds/sync/components.go
index 2638b6747..64daea133 100644
--- a/pkg/xds/sync/components.go
+++ b/pkg/xds/sync/components.go
@@ -5,42 +5,19 @@ import (
 	"github.com/kumahq/kuma/pkg/core/faultinjections"
 	"github.com/kumahq/kuma/pkg/core/logs"
 	"github.com/kumahq/kuma/pkg/core/permissions"
-	core_mesh "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
-	core_system "github.com/kumahq/kuma/pkg/core/resources/apis/system"
-	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
-	"github.com/kumahq/kuma/pkg/core/resources/registry"
 	core_runtime "github.com/kumahq/kuma/pkg/core/runtime"
-	"github.com/kumahq/kuma/pkg/xds/cache/cla"
 	"github.com/kumahq/kuma/pkg/xds/cache/mesh"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
+	xds_metrics "github.com/kumahq/kuma/pkg/xds/metrics"
 )
 
 var (
-	xdsServerLog  = core.Log.WithName("xds-server")
-	meshResources = meshResourceTypes(map[core_model.ResourceType]bool{
-		core_mesh.DataplaneInsightType:  true,
-		core_mesh.DataplaneOverviewType: true,
-		core_mesh.ServiceInsightType:    true,
-		core_system.ConfigType:          true,
-	})
+	xdsServerLog = core.Log.WithName("xds-server")
 )
 
-func meshResourceTypes(exclude map[core_model.ResourceType]bool) []core_model.ResourceType {
-	types := []core_model.ResourceType{}
-	for _, typ := range registry.Global().ListTypes() {
-		r, err := registry.Global().NewObject(typ)
-		if err != nil {
-			panic(err)
-		}
-		if r.Scope() == core_model.ScopeMesh && !exclude[typ] {
-			types = append(types, typ)
-		}
-	}
-	return types
-}
-
-func defaultDataplaneProxyBuilder(rt core_runtime.Runtime, metadataTracker DataplaneMetadataTracker) *dataplaneProxyBuilder {
-	return &dataplaneProxyBuilder{
+func defaultDataplaneProxyBuilder(rt core_runtime.Runtime, metadataTracker DataplaneMetadataTracker, apiVersion envoy.APIVersion) *DataplaneProxyBuilder {
+	return &DataplaneProxyBuilder{
 		ResManager:            rt.ReadOnlyResourceManager(),
 		LookupIP:              rt.LookupIP(),
 		DataSourceLoader:      rt.DataSourceLoader(),
@@ -49,15 +26,17 @@ func defaultDataplaneProxyBuilder(rt core_runtime.Runtime, metadataTracker Datap
 		LogsMatcher:           logs.TrafficLogsMatcher{ResourceManager: rt.ReadOnlyResourceManager()},
 		FaultInjectionMatcher: faultinjections.FaultInjectionMatcher{ResourceManager: rt.ReadOnlyResourceManager()},
 		Zone:                  rt.Config().Multizone.Remote.Zone,
+		apiVersion:            apiVersion,
 	}
 }
 
-func defaultIngressProxyBuilder(rt core_runtime.Runtime, metadataTracker DataplaneMetadataTracker) *ingressProxyBuilder {
-	return &ingressProxyBuilder{
+func defaultIngressProxyBuilder(rt core_runtime.Runtime, metadataTracker DataplaneMetadataTracker, apiVersion envoy.APIVersion) *IngressProxyBuilder {
+	return &IngressProxyBuilder{
 		ResManager:         rt.ResourceManager(),
 		ReadOnlyResManager: rt.ReadOnlyResourceManager(),
 		LookupIP:           rt.LookupIP(),
 		MetadataTracker:    metadataTracker,
+		apiVersion:         apiVersion,
 	}
 }
 
@@ -67,23 +46,13 @@ func DefaultDataplaneWatchdogFactory(
 	connectionInfoTracker ConnectionInfoTracker,
 	dataplaneReconciler SnapshotReconciler,
 	ingressReconciler SnapshotReconciler,
+	xdsMetrics *xds_metrics.Metrics,
+	meshSnapshotCache *mesh.Cache,
+	envoyCpCtx *xds_context.ControlPlaneContext,
+	apiVersion envoy.APIVersion,
 ) (DataplaneWatchdogFactory, error) {
-	claCache, err := cla.NewCache(rt.ReadOnlyResourceManager(), rt.DataSourceLoader(), rt.Config().Multizone.Remote.Zone, rt.Config().Store.Cache.ExpirationTime, rt.LookupIP(), rt.Metrics())
-	if err != nil {
-		return nil, err
-	}
-	dataplaneProxyBuilder := defaultDataplaneProxyBuilder(rt, metadataTracker)
-	ingressProxyBuilder := defaultIngressProxyBuilder(rt, metadataTracker)
-	meshSnapshotCache, err := mesh.NewCache(rt.ReadOnlyResourceManager(), rt.Config().Store.Cache.ExpirationTime, meshResources, rt.LookupIP(), rt.Metrics())
-	if err != nil {
-		return nil, err
-	}
-
-	envoyCpCtx, err := xds_context.BuildControlPlaneContext(rt.Config(), claCache)
-	if err != nil {
-		return nil, err
-	}
-
+	dataplaneProxyBuilder := defaultDataplaneProxyBuilder(rt, metadataTracker, apiVersion)
+	ingressProxyBuilder := defaultIngressProxyBuilder(rt, metadataTracker, apiVersion)
 	xdsContextBuilder := newXDSContextBuilder(envoyCpCtx, connectionInfoTracker, rt.ReadOnlyResourceManager(), rt.LookupIP())
 
 	deps := DataplaneWatchdogDependencies{
@@ -96,7 +65,7 @@ func DefaultDataplaneWatchdogFactory(
 		meshCache:             meshSnapshotCache,
 	}
 	return NewDataplaneWatchdogFactory(
-		rt.Metrics(),
+		xdsMetrics,
 		rt.Config().XdsServer.DataplaneConfigurationRefreshInterval,
 		deps,
 	)
diff --git a/pkg/xds/sync/dataplane_proxy_builder.go b/pkg/xds/sync/dataplane_proxy_builder.go
index 7ae19ed16..c1d2c8c86 100644
--- a/pkg/xds/sync/dataplane_proxy_builder.go
+++ b/pkg/xds/sync/dataplane_proxy_builder.go
@@ -16,12 +16,13 @@ import (
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	"github.com/kumahq/kuma/pkg/core/xds"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
 	xds_topology "github.com/kumahq/kuma/pkg/xds/topology"
 )
 
 var syncLog = core.Log.WithName("sync")
 
-type dataplaneProxyBuilder struct {
+type DataplaneProxyBuilder struct {
 	ResManager            manager.ReadOnlyResourceManager
 	LookupIP              lookup.LookupIPFunc
 	DataSourceLoader      datasource.Loader
@@ -30,10 +31,11 @@ type dataplaneProxyBuilder struct {
 	LogsMatcher           logs.TrafficLogsMatcher
 	FaultInjectionMatcher faultinjections.FaultInjectionMatcher
 
-	Zone string
+	Zone       string
+	apiVersion envoy.APIVersion
 }
 
-func (p *dataplaneProxyBuilder) build(key core_model.ResourceKey, streamId int64, meshCtx *xds_context.MeshContext) (*xds.Proxy, error) {
+func (p *DataplaneProxyBuilder) build(key core_model.ResourceKey, streamId int64, meshCtx *xds_context.MeshContext) (*xds.Proxy, error) {
 	ctx := context.Background()
 
 	dp, err := p.resolveDataplane(ctx, key)
@@ -52,16 +54,17 @@ func (p *dataplaneProxyBuilder) build(key core_model.ResourceKey, streamId int64
 	}
 
 	proxy := &xds.Proxy{
-		Id:        xds.FromResourceKey(key),
-		Dataplane: dp,
-		Metadata:  p.MetadataTracker.Metadata(streamId),
-		Routing:   *routing,
-		Policies:  *matchedPolicies,
+		Id:         xds.FromResourceKey(key),
+		APIVersion: p.apiVersion,
+		Dataplane:  dp,
+		Metadata:   p.MetadataTracker.Metadata(streamId),
+		Routing:    *routing,
+		Policies:   *matchedPolicies,
 	}
 	return proxy, nil
 }
 
-func (p *dataplaneProxyBuilder) resolveDataplane(ctx context.Context, key core_model.ResourceKey) (*core_mesh.DataplaneResource, error) {
+func (p *DataplaneProxyBuilder) resolveDataplane(ctx context.Context, key core_model.ResourceKey) (*core_mesh.DataplaneResource, error) {
 	dataplane := core_mesh.NewDataplaneResource()
 
 	// we use non-cached ResourceManager to always fetch fresh version of the Dataplane.
@@ -78,7 +81,7 @@ func (p *dataplaneProxyBuilder) resolveDataplane(ctx context.Context, key core_m
 	return resolvedDp, nil
 }
 
-func (p *dataplaneProxyBuilder) resolveRouting(
+func (p *DataplaneProxyBuilder) resolveRouting(
 	ctx context.Context,
 	meshContext *xds_context.MeshContext,
 	dataplane *core_mesh.DataplaneResource,
@@ -107,7 +110,7 @@ func (p *dataplaneProxyBuilder) resolveRouting(
 	return routing, destinations, nil
 }
 
-func (p *dataplaneProxyBuilder) matchPolicies(ctx context.Context, meshContext *xds_context.MeshContext, dataplane *core_mesh.DataplaneResource, outboundSelectors xds.DestinationMap) (*xds.MatchedPolicies, error) {
+func (p *DataplaneProxyBuilder) matchPolicies(ctx context.Context, meshContext *xds_context.MeshContext, dataplane *core_mesh.DataplaneResource, outboundSelectors xds.DestinationMap) (*xds.MatchedPolicies, error) {
 	healthChecks, err := xds_topology.GetHealthChecks(ctx, dataplane, outboundSelectors, p.ResManager)
 	if err != nil {
 		return nil, err
diff --git a/pkg/xds/sync/dataplane_watchdog.go b/pkg/xds/sync/dataplane_watchdog.go
index 2560f576e..129b3da64 100644
--- a/pkg/xds/sync/dataplane_watchdog.go
+++ b/pkg/xds/sync/dataplane_watchdog.go
@@ -17,9 +17,9 @@ import (
 
 type DataplaneWatchdogDependencies struct {
 	resManager            manager.ResourceManager
-	dataplaneProxyBuilder *dataplaneProxyBuilder
+	dataplaneProxyBuilder *DataplaneProxyBuilder
 	dataplaneReconciler   SnapshotReconciler
-	ingressProxyBuilder   *ingressProxyBuilder
+	ingressProxyBuilder   *IngressProxyBuilder
 	ingressReconciler     SnapshotReconciler
 	xdsContextBuilder     *xdsContextBuilder
 	meshCache             *mesh.Cache
diff --git a/pkg/xds/sync/dataplane_watchdog_factory.go b/pkg/xds/sync/dataplane_watchdog_factory.go
index 63d62748b..267c163c7 100644
--- a/pkg/xds/sync/dataplane_watchdog_factory.go
+++ b/pkg/xds/sync/dataplane_watchdog_factory.go
@@ -3,48 +3,28 @@ package sync
 import (
 	"time"
 
-	"github.com/prometheus/client_golang/prometheus"
-
 	"github.com/kumahq/kuma/pkg/core"
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
-	core_metrics "github.com/kumahq/kuma/pkg/metrics"
 	util_watchdog "github.com/kumahq/kuma/pkg/util/watchdog"
+	xds_metrics "github.com/kumahq/kuma/pkg/xds/metrics"
 )
 
 type dataplaneWatchdogFactory struct {
-	xdsGenerations       prometheus.Summary
-	xdsGenerationsErrors prometheus.Counter
-	refreshInterval      time.Duration
+	xdsMetrics      *xds_metrics.Metrics
+	refreshInterval time.Duration
 
 	deps DataplaneWatchdogDependencies
 }
 
 func NewDataplaneWatchdogFactory(
-	metrics core_metrics.Metrics,
+	xdsSyncMetrics *xds_metrics.Metrics,
 	refreshInterval time.Duration,
 	deps DataplaneWatchdogDependencies,
 ) (DataplaneWatchdogFactory, error) {
-	xdsGenerations := prometheus.NewSummary(prometheus.SummaryOpts{
-		Name:       "xds_generation",
-		Help:       "Summary of XDS Snapshot generation",
-		Objectives: core_metrics.DefaultObjectives,
-	})
-	if err := metrics.Register(xdsGenerations); err != nil {
-		return nil, err
-	}
-	xdsGenerationsErrors := prometheus.NewCounter(prometheus.CounterOpts{
-		Name: "xds_generation_errors",
-		Help: "Counter of errors during XDS generation",
-	})
-	if err := metrics.Register(xdsGenerationsErrors); err != nil {
-		return nil, err
-	}
-
 	return &dataplaneWatchdogFactory{
-		deps:                 deps,
-		refreshInterval:      refreshInterval,
-		xdsGenerations:       xdsGenerations,
-		xdsGenerationsErrors: xdsGenerationsErrors,
+		deps:            deps,
+		refreshInterval: refreshInterval,
+		xdsMetrics:      xdsSyncMetrics,
 	}, nil
 }
 
@@ -58,12 +38,12 @@ func (d *dataplaneWatchdogFactory) New(key core_model.ResourceKey, streamId int6
 		OnTick: func() error {
 			start := core.Now()
 			defer func() {
-				d.xdsGenerations.Observe(float64(core.Now().Sub(start).Milliseconds()))
+				d.xdsMetrics.XdsGenerations.Observe(float64(core.Now().Sub(start).Milliseconds()))
 			}()
 			return dataplaneWatchdog.Sync()
 		},
 		OnError: func(err error) {
-			d.xdsGenerationsErrors.Inc()
+			d.xdsMetrics.XdsGenerationsErrors.Inc()
 			log.Error(err, "OnTick() failed")
 		},
 		OnStop: func() {
diff --git a/pkg/xds/sync/ingress_proxy_builder.go b/pkg/xds/sync/ingress_proxy_builder.go
index ecc5ce538..001c36ad0 100644
--- a/pkg/xds/sync/ingress_proxy_builder.go
+++ b/pkg/xds/sync/ingress_proxy_builder.go
@@ -9,18 +9,21 @@ import (
 	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	"github.com/kumahq/kuma/pkg/core/xds"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/ingress"
 	xds_topology "github.com/kumahq/kuma/pkg/xds/topology"
 )
 
-type ingressProxyBuilder struct {
+type IngressProxyBuilder struct {
 	ResManager         manager.ResourceManager
 	ReadOnlyResManager manager.ReadOnlyResourceManager
 	LookupIP           lookup.LookupIPFunc
 	MetadataTracker    DataplaneMetadataTracker
+
+	apiVersion envoy.APIVersion
 }
 
-func (p *ingressProxyBuilder) build(key core_model.ResourceKey, streamId int64) (*xds.Proxy, error) {
+func (p *IngressProxyBuilder) build(key core_model.ResourceKey, streamId int64) (*xds.Proxy, error) {
 	ctx := context.Background()
 
 	dp, err := p.resolveDataplane(ctx, key)
@@ -47,15 +50,16 @@ func (p *ingressProxyBuilder) build(key core_model.ResourceKey, streamId int64)
 	}
 
 	proxy := &xds.Proxy{
-		Id:        xds.FromResourceKey(key),
-		Dataplane: dp,
-		Metadata:  p.MetadataTracker.Metadata(streamId),
-		Routing:   *routing,
+		Id:         xds.FromResourceKey(key),
+		APIVersion: p.apiVersion,
+		Dataplane:  dp,
+		Metadata:   p.MetadataTracker.Metadata(streamId),
+		Routing:    *routing,
 	}
 	return proxy, nil
 }
 
-func (p *ingressProxyBuilder) resolveDataplane(ctx context.Context, key core_model.ResourceKey) (*core_mesh.DataplaneResource, error) {
+func (p *IngressProxyBuilder) resolveDataplane(ctx context.Context, key core_model.ResourceKey) (*core_mesh.DataplaneResource, error) {
 	dataplane := core_mesh.NewDataplaneResource()
 
 	if err := p.ReadOnlyResManager.Get(ctx, dataplane, core_store.GetBy(key)); err != nil {
@@ -70,7 +74,7 @@ func (p *ingressProxyBuilder) resolveDataplane(ctx context.Context, key core_mod
 	return resolvedDp, nil
 }
 
-func (p *ingressProxyBuilder) resolveRouting(ctx context.Context, dataplane *core_mesh.DataplaneResource, dataplanes *core_mesh.DataplaneResourceList) (*xds.Routing, error) {
+func (p *IngressProxyBuilder) resolveRouting(ctx context.Context, dataplane *core_mesh.DataplaneResource, dataplanes *core_mesh.DataplaneResourceList) (*xds.Routing, error) {
 	destinations := ingress.BuildDestinationMap(dataplane)
 	endpoints := ingress.BuildEndpointMap(destinations, dataplanes.Items)
 	routes := &core_mesh.TrafficRouteResourceList{}


Test patch:
diff --git a/app/kuma-dp/cmd/run_test.go b/app/kuma-dp/cmd/run_test.go
index cb9a54e28..b868df163 100644
--- a/app/kuma-dp/cmd/run_test.go
+++ b/app/kuma-dp/cmd/run_test.go
@@ -15,8 +15,6 @@ import (
 
 	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
 
-	envoy_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
-	"github.com/golang/protobuf/proto"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
 	. "github.com/onsi/gomega"
@@ -25,7 +23,6 @@ import (
 	kumadp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 	"github.com/kumahq/kuma/pkg/core"
 	"github.com/kumahq/kuma/pkg/test"
-	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 )
 
 var _ = Describe("run", func() {
@@ -36,13 +33,10 @@ var _ = Describe("run", func() {
 	BeforeEach(func() {
 		backupSetupSignalHandler = core.SetupSignalHandler
 		backupBootstrapGenerator = bootstrapGenerator
-		bootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, version envoy.EnvoyVersion) (proto.Message, error) {
-			bootstrap := envoy_bootstrap.Bootstrap{}
+		bootstrapGenerator = func(_ string, cfg kumadp.Config, _ *rest.Resource, version envoy.EnvoyVersion) ([]byte, error) {
 			respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "bootstrap-config.golden.yaml"))
 			Expect(err).ToNot(HaveOccurred())
-			err = util_proto.FromYAML(respBytes, &bootstrap)
-			Expect(err).ToNot(HaveOccurred())
-			return &bootstrap, nil
+			return respBytes, nil
 		}
 	})
 	AfterEach(func() {
diff --git a/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file_test.go b/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file_test.go
index 66353fc93..d8d7f5942 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/bootstrap_file_test.go
@@ -8,9 +8,6 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
-	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
-	envoy_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
-
 	kuma_dp "github.com/kumahq/kuma/pkg/config/app/kuma-dp"
 )
 
@@ -35,18 +32,16 @@ var _ = Describe("Bootstrap File", func() {
 
 		It("should create Envoy bootstrap file on disk", func() {
 			// given
-			config := &envoy_bootstrap.Bootstrap{
-				Node: &envoy_core.Node{
-					Id: "example",
-				},
-			}
+			config := `node:
+  id: example
+`
 			// and
 			runtime := kuma_dp.DataplaneRuntime{
 				ConfigDir: configDir,
 			}
 
 			// when
-			filename, err := GenerateBootstrapFile(runtime, config)
+			filename, err := GenerateBootstrapFile(runtime, []byte(config))
 			// then
 			Expect(err).ToNot(HaveOccurred())
 			// and
diff --git a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
index f48d4af5f..76cd3f8df 100644
--- a/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
+++ b/app/kuma-dp/pkg/dataplane/envoy/envoy_test.go
@@ -12,11 +12,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/golang/protobuf/proto"
-
-	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
-	envoy_bootstrap "github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v2"
-
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 
@@ -73,12 +68,9 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) (proto.Message, error) {
-				return &envoy_bootstrap.Bootstrap{
-					Node: &envoy_core.Node{
-						Id: "example",
-					},
-				}, nil
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
+				return []byte(`node:
+  id: example`), nil
 			}
 			expectedConfigFile := filepath.Join(configDir, "bootstrap.yaml")
 
@@ -144,8 +136,8 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) (proto.Message, error) {
-				return &envoy_bootstrap.Bootstrap{}, nil
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
+				return nil, nil
 			}
 
 			By("starting a mock dataplane")
@@ -186,8 +178,8 @@ var _ = Describe("Envoy", func() {
 					ConfigDir:  configDir,
 				},
 			}
-			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) (proto.Message, error) {
-				return &envoy_bootstrap.Bootstrap{}, nil
+			sampleConfig := func(string, kuma_dp.Config, *rest.Resource, EnvoyVersion) ([]byte, error) {
+				return nil, nil
 			}
 
 			By("starting a mock dataplane")
diff --git a/pkg/api-server/config_ws_test.go b/pkg/api-server/config_ws_test.go
index a8817b99d..b0ba3c329 100644
--- a/pkg/api-server/config_ws_test.go
+++ b/pkg/api-server/config_ws_test.go
@@ -78,6 +78,7 @@ var _ = Describe("Config WS", func() {
             "readOnly": false
           },
           "bootstrapServer": {
+            "apiVersion": "v2",
             "params": {
               "adminAccessLogPath": "/dev/null",
               "adminAddress": "127.0.0.1",
diff --git a/pkg/config/loader_test.go b/pkg/config/loader_test.go
index 1026ec7db..7009cb305 100644
--- a/pkg/config/loader_test.go
+++ b/pkg/config/loader_test.go
@@ -14,6 +14,7 @@ import (
 	config_core "github.com/kumahq/kuma/pkg/config/core"
 	"github.com/kumahq/kuma/pkg/config/core/resources/store"
 	"github.com/kumahq/kuma/pkg/config/plugins/resources/postgres"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/test/testenvconfig"
 )
 
@@ -82,6 +83,7 @@ var _ = Describe("Config loader", func() {
 			}
 
 			// then
+			Expect(cfg.BootstrapServer.APIVersion).To(Equal(envoy.APIV3))
 			Expect(cfg.BootstrapServer.Params.AdminPort).To(Equal(uint32(1234)))
 			Expect(cfg.BootstrapServer.Params.XdsHost).To(Equal("kuma-control-plane"))
 			Expect(cfg.BootstrapServer.Params.XdsPort).To(Equal(uint32(4321)))
@@ -245,6 +247,7 @@ store:
     conflictRetryBaseBackoff: 4s
     conflictRetryMaxTimes: 10
 bootstrapServer:
+  apiVersion: v3
   params:
     adminPort: 1234
     adminAccessLogPath: /access/log/test
@@ -387,6 +390,7 @@ sdsServer:
 		}),
 		Entry("from env variables", testCase{
 			envVars: map[string]string{
+				"KUMA_BOOTSTRAP_SERVER_API_VERSION":                                                        "v3",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_ADMIN_PORT":                                                  "1234",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_XDS_HOST":                                                    "kuma-control-plane",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_XDS_PORT":                                                    "4321",
diff --git a/pkg/config/xds/bootstrap/config_test.go b/pkg/config/xds/bootstrap/config_test.go
index f564a00f9..ad6207d44 100644
--- a/pkg/config/xds/bootstrap/config_test.go
+++ b/pkg/config/xds/bootstrap/config_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/kumahq/kuma/pkg/config"
 	. "github.com/kumahq/kuma/pkg/config/xds/bootstrap"
+	"github.com/kumahq/kuma/pkg/xds/envoy"
 )
 
 var _ = Describe("BootstrappServerConfig", func() {
@@ -53,6 +54,7 @@ var _ = Describe("BootstrappServerConfig", func() {
 		It("should be loadable from environment variables", func() {
 			// setup
 			env := map[string]string{
+				"KUMA_BOOTSTRAP_SERVER_API_VERSION":                  "v2",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_ADMIN_ADDRESS":         "192.168.0.1",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_ADMIN_PORT":            "4321",
 				"KUMA_BOOTSTRAP_SERVER_PARAMS_ADMIN_ACCESS_LOG_PATH": "/var/log",
@@ -74,6 +76,7 @@ var _ = Describe("BootstrappServerConfig", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			// and
+			Expect(cfg.APIVersion).To(Equal(envoy.APIV2))
 			Expect(cfg.Params.AdminAddress).To(Equal("192.168.0.1"))
 			Expect(cfg.Params.AdminPort).To(Equal(uint32(4321)))
 			Expect(cfg.Params.AdminAccessLogPath).To(Equal("/var/log"))
diff --git a/pkg/config/xds/bootstrap/testdata/default-config.golden.yaml b/pkg/config/xds/bootstrap/testdata/default-config.golden.yaml
index 10fdbe7ce..999dacb4f 100644
--- a/pkg/config/xds/bootstrap/testdata/default-config.golden.yaml
+++ b/pkg/config/xds/bootstrap/testdata/default-config.golden.yaml
@@ -1,3 +1,4 @@
+apiVersion: v2
 params:
   adminAccessLogPath: /dev/null
   adminAddress: 127.0.0.1
diff --git a/pkg/config/xds/bootstrap/testdata/valid-config.input.yaml b/pkg/config/xds/bootstrap/testdata/valid-config.input.yaml
index c00b37d2e..400ad0063 100644
--- a/pkg/config/xds/bootstrap/testdata/valid-config.input.yaml
+++ b/pkg/config/xds/bootstrap/testdata/valid-config.input.yaml
@@ -1,3 +1,4 @@
+apiVersion: v2
 params:
   adminAddress: 192.168.0.1
   adminPort: 4321
diff --git a/pkg/core/xds/types_test.go b/pkg/core/xds/types_test.go
index 421c09ab9..929501679 100644
--- a/pkg/core/xds/types_test.go
+++ b/pkg/core/xds/types_test.go
@@ -1,7 +1,6 @@
 package xds_test
 
 import (
-	envoy_core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
 	. "github.com/onsi/gomega"
@@ -16,14 +15,14 @@ var _ = Describe("xDS", func() {
 
 		Context("valid input", func() {
 			type testCase struct {
-				node     *envoy_core.Node
+				nodeID   string
 				expected core_xds.ProxyId
 			}
 
 			DescribeTable("should successfully parse",
 				func(given testCase) {
 					// when
-					proxyId, err := core_xds.ParseProxyId(given.node)
+					proxyId, err := core_xds.ParseProxyIdFromString(given.nodeID)
 
 					// then
 					Expect(err).ToNot(HaveOccurred())
@@ -31,17 +30,13 @@ var _ = Describe("xDS", func() {
 					Expect(*proxyId).To(Equal(given.expected))
 				},
 				Entry("mesh and name without namespace", testCase{
-					node: &envoy_core.Node{
-						Id: "demo.example",
-					},
+					nodeID: "demo.example",
 					expected: core_xds.ProxyId{
 						Mesh: "demo", Name: "example",
 					},
 				}),
 				Entry("name with namespace and mesh", testCase{
-					node: &envoy_core.Node{
-						Id: "demo.example.sample",
-					},
+					nodeID: "demo.example.sample",
 					expected: core_xds.ProxyId{
 						Mesh: "demo", Name: "example.sample",
 					},
@@ -51,38 +46,30 @@ var _ = Describe("xDS", func() {
 
 		Context("invalid input", func() {
 			type testCase struct {
-				node        *envoy_core.Node
+				nodeID      string
 				expectedErr interface{}
 			}
 
 			DescribeTable("should fail to parse",
 				func(given testCase) {
 					// when
-					key, err := core_xds.ParseProxyId(given.node)
+					key, err := core_xds.ParseProxyIdFromString(given.nodeID)
 
 					// then
 					Expect(err).To(MatchError(given.expectedErr))
 					// and
 					Expect(key).To(BeNil())
 				},
-				Entry("`nil`", testCase{
-					node:        nil,
-					expectedErr: "Envoy node must not be nil",
-				}),
 				Entry("empty", testCase{
-					node:        &envoy_core.Node{},
+					nodeID:      "",
 					expectedErr: "Envoy ID must not be nil",
 				}),
 				Entry("mesh without name and namespace", testCase{
-					node: &envoy_core.Node{
-						Id: "demo",
-					},
+					nodeID:      "demo",
 					expectedErr: "the name should be provided after the dot",
 				}),
 				Entry("mesh with empty name", testCase{
-					node: &envoy_core.Node{
-						Id: "demo.",
-					},
+					nodeID:      "demo.",
 					expectedErr: "name must not be empty",
 				}),
 			)
diff --git a/pkg/sds/server/server_suite_test.go b/pkg/sds/server/v2/server_suite_test.go
similarity index 89%
rename from pkg/sds/server/server_suite_test.go
rename to pkg/sds/server/v2/server_suite_test.go
index 79fef435b..598e9a551 100644
--- a/pkg/sds/server/server_suite_test.go
+++ b/pkg/sds/server/v2/server_suite_test.go
@@ -1,4 +1,4 @@
-package server_test
+package v2_test
 
 import (
 	"testing"
diff --git a/pkg/sds/server/server_test.go b/pkg/sds/server/v2/server_test.go
similarity index 96%
rename from pkg/sds/server/server_test.go
rename to pkg/sds/server/v2/server_test.go
index 752ec1edd..c6a0a2b56 100644
--- a/pkg/sds/server/server_test.go
+++ b/pkg/sds/server/v2/server_test.go
@@ -1,4 +1,4 @@
-package server_test
+package v2_test
 
 import (
 	"context"
@@ -60,8 +60,8 @@ var _ = Describe("SDS Server", func() {
 		port, err := test.GetFreePort()
 		Expect(err).ToNot(HaveOccurred())
 		cfg.DpServer.Port = port
-		cfg.DpServer.TlsCertFile = filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem")
-		cfg.DpServer.TlsKeyFile = filepath.Join("..", "..", "..", "test", "certs", "server-key.pem")
+		cfg.DpServer.TlsCertFile = filepath.Join("..", "..", "..", "..", "test", "certs", "server-cert.pem")
+		cfg.DpServer.TlsKeyFile = filepath.Join("..", "..", "..", "..", "test", "certs", "server-key.pem")
 		cfg.DpServer.Auth.Type = dp_server_cfg.DpServerAuthDpToken
 
 		builder, err := runtime.BuilderFor(cfg)
@@ -141,7 +141,7 @@ var _ = Describe("SDS Server", func() {
 
 		// wait for SDS server
 		Eventually(func() error {
-			creds, err := credentials.NewClientTLSFromFile(filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), "")
+			creds, err := credentials.NewClientTLSFromFile(filepath.Join("..", "..", "..", "..", "test", "certs", "server-cert.pem"), "")
 			if err != nil {
 				return err
 			}
@@ -204,7 +204,7 @@ var _ = Describe("SDS Server", func() {
 		Expect(dpInsight.Spec.MTLS.CertificateExpirationTime.Seconds).To(Equal(expirationSeconds))
 
 		// and metrics are published
-		Expect(test_metrics.FindMetric(metrics, "sds_cert_generation").GetGauge().GetValue()).To(Equal(1.0))
+		Expect(test_metrics.FindMetric(metrics, "sds_cert_generation").GetCounter().GetValue()).To(Equal(1.0))
 		Expect(test_metrics.FindMetric(metrics, "sds_generation")).ToNot(BeNil())
 
 		close(done)
diff --git a/pkg/test/runtime/runtime.go b/pkg/test/runtime/runtime.go
index 0b2b8f4d7..bd9ec05b8 100644
--- a/pkg/test/runtime/runtime.go
+++ b/pkg/test/runtime/runtime.go
@@ -19,7 +19,6 @@ import (
 	secret_cipher "github.com/kumahq/kuma/pkg/core/secrets/cipher"
 	secret_manager "github.com/kumahq/kuma/pkg/core/secrets/manager"
 	secret_store "github.com/kumahq/kuma/pkg/core/secrets/store"
-	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	"github.com/kumahq/kuma/pkg/dns/resolver"
 	"github.com/kumahq/kuma/pkg/events"
 	"github.com/kumahq/kuma/pkg/metrics"
@@ -55,8 +54,7 @@ func BuilderFor(cfg kuma_cp.Config) (*core_runtime.Builder, error) {
 	}
 	builder.
 		WithComponentManager(component.NewManager(leader_memory.NewAlwaysLeaderElector())).
-		WithResourceStore(resources_memory.NewStore()).
-		WithXdsContext(core_xds.NewXdsContext())
+		WithResourceStore(resources_memory.NewStore())
 
 	metrics, _ := metrics.NewMetrics("Standalone")
 	builder.WithMetrics(metrics)
diff --git a/pkg/xds/bootstrap/generator_test.go b/pkg/xds/bootstrap/generator_test.go
index 53f55a022..7a9a1eaff 100644
--- a/pkg/xds/bootstrap/generator_test.go
+++ b/pkg/xds/bootstrap/generator_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/kumahq/kuma/pkg/core"
 	"github.com/kumahq/kuma/pkg/core/resources/model"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
@@ -81,7 +82,7 @@ var _ = Describe("bootstrapGenerator", func() {
 	})
 
 	type testCase struct {
-		config             func() *bootstrap_config.BootstrapParamsConfig
+		config             func() *bootstrap_config.BootstrapServerConfig
 		dpAuthEnabled      bool
 		request            types.BootstrapRequest
 		expectedConfigFile string
@@ -112,10 +113,11 @@ var _ = Describe("bootstrapGenerator", func() {
 		},
 		Entry("default config with minimal request", testCase{
 			dpAuthEnabled: false,
-			config: func() *bootstrap_config.BootstrapParamsConfig {
-				cfg := bootstrap_config.DefaultBootstrapParamsConfig()
-				cfg.XdsHost = "localhost"
-				cfg.XdsPort = 5678
+			config: func() *bootstrap_config.BootstrapServerConfig {
+				cfg := bootstrap_config.DefaultBootstrapServerConfig()
+				cfg.Params.XdsHost = "localhost"
+				cfg.Params.XdsPort = 5678
+				cfg.APIVersion = envoy_common.APIV2
 				return cfg
 			},
 			request: types.BootstrapRequest{
@@ -127,10 +129,11 @@ var _ = Describe("bootstrapGenerator", func() {
 		}),
 		Entry("default config", testCase{
 			dpAuthEnabled: true,
-			config: func() *bootstrap_config.BootstrapParamsConfig {
-				cfg := bootstrap_config.DefaultBootstrapParamsConfig()
-				cfg.XdsHost = "localhost"
-				cfg.XdsPort = 5678
+			config: func() *bootstrap_config.BootstrapServerConfig {
+				cfg := bootstrap_config.DefaultBootstrapServerConfig()
+				cfg.Params.XdsHost = "localhost"
+				cfg.Params.XdsPort = 5678
+				cfg.APIVersion = envoy_common.APIV2
 				return cfg
 			},
 			request: types.BootstrapRequest{
@@ -144,14 +147,17 @@ var _ = Describe("bootstrapGenerator", func() {
 		}),
 		Entry("custom config with minimal request", testCase{
 			dpAuthEnabled: false,
-			config: func() *bootstrap_config.BootstrapParamsConfig {
-				return &bootstrap_config.BootstrapParamsConfig{
-					AdminAddress:       "192.168.0.1", // by default, Envoy Admin interface should listen on loopback address
-					AdminPort:          9902,          // by default, turn off Admin interface of Envoy
-					AdminAccessLogPath: "/var/log",
-					XdsHost:            "localhost",
-					XdsPort:            15678,
-					XdsConnectTimeout:  2 * time.Second,
+			config: func() *bootstrap_config.BootstrapServerConfig {
+				return &bootstrap_config.BootstrapServerConfig{
+					Params: &bootstrap_config.BootstrapParamsConfig{
+						AdminAddress:       "192.168.0.1", // by default, Envoy Admin interface should listen on loopback address
+						AdminPort:          9902,          // by default, turn off Admin interface of Envoy
+						AdminAccessLogPath: "/var/log",
+						XdsHost:            "localhost",
+						XdsPort:            15678,
+						XdsConnectTimeout:  2 * time.Second,
+					},
+					APIVersion: envoy_common.APIV2,
 				}
 			},
 			request: types.BootstrapRequest{
@@ -163,14 +169,17 @@ var _ = Describe("bootstrapGenerator", func() {
 		}),
 		Entry("custom config", testCase{
 			dpAuthEnabled: true,
-			config: func() *bootstrap_config.BootstrapParamsConfig {
-				return &bootstrap_config.BootstrapParamsConfig{
-					AdminAddress:       "192.168.0.1", // by default, Envoy Admin interface should listen on loopback address
-					AdminPort:          9902,          // by default, turn off Admin interface of Envoy
-					AdminAccessLogPath: "/var/log",
-					XdsHost:            "localhost",
-					XdsPort:            15678,
-					XdsConnectTimeout:  2 * time.Second,
+			config: func() *bootstrap_config.BootstrapServerConfig {
+				return &bootstrap_config.BootstrapServerConfig{
+					Params: &bootstrap_config.BootstrapParamsConfig{
+						AdminAddress:       "192.168.0.1", // by default, Envoy Admin interface should listen on loopback address
+						AdminPort:          9902,          // by default, turn off Admin interface of Envoy
+						AdminAccessLogPath: "/var/log",
+						XdsHost:            "localhost",
+						XdsPort:            15678,
+						XdsConnectTimeout:  2 * time.Second,
+					},
+					APIVersion: envoy_common.APIV2,
 				}
 			},
 			request: types.BootstrapRequest{
@@ -244,11 +253,11 @@ var _ = Describe("bootstrapGenerator", func() {
 		Expect(err).ToNot(HaveOccurred())
 
 		// given
-		params := bootstrap_config.DefaultBootstrapParamsConfig()
-		params.XdsHost = "localhost"
-		params.XdsPort = 5678
+		cfg := bootstrap_config.DefaultBootstrapServerConfig()
+		cfg.Params.XdsHost = "localhost"
+		cfg.Params.XdsPort = 5678
 
-		generator, err := NewDefaultBootstrapGenerator(resManager, params, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
+		generator, err := NewDefaultBootstrapGenerator(resManager, cfg, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
 		Expect(err).ToNot(HaveOccurred())
 		request := types.BootstrapRequest{
 			Mesh:      "mesh",
@@ -309,11 +318,11 @@ var _ = Describe("bootstrapGenerator", func() {
 		Expect(err).ToNot(HaveOccurred())
 
 		// given
-		params := bootstrap_config.DefaultBootstrapParamsConfig()
-		params.XdsHost = "localhost"
-		params.XdsPort = 5678
+		cfg := bootstrap_config.DefaultBootstrapServerConfig()
+		cfg.Params.XdsHost = "localhost"
+		cfg.Params.XdsPort = 5678
 
-		generator, err := NewDefaultBootstrapGenerator(resManager, params, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
+		generator, err := NewDefaultBootstrapGenerator(resManager, cfg, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
 		Expect(err).ToNot(HaveOccurred())
 		request := types.BootstrapRequest{
 			Mesh:      "mesh",
@@ -331,9 +340,9 @@ var _ = Describe("bootstrapGenerator", func() {
 
 	It("should fail bootstrap due to invalid hostname", func() {
 		// given
-		params := bootstrap_config.DefaultBootstrapParamsConfig()
+		cfg := bootstrap_config.DefaultBootstrapServerConfig()
 
-		generator, err := NewDefaultBootstrapGenerator(resManager, params, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
+		generator, err := NewDefaultBootstrapGenerator(resManager, cfg, filepath.Join("..", "..", "..", "test", "certs", "server-cert.pem"), false)
 		Expect(err).ToNot(HaveOccurred())
 		request := types.BootstrapRequest{
 			Mesh:      "mesh",
diff --git a/pkg/xds/bootstrap/server_test.go b/pkg/xds/bootstrap/server_test.go
index aad81b9d2..215d495ca 100644
--- a/pkg/xds/bootstrap/server_test.go
+++ b/pkg/xds/bootstrap/server_test.go
@@ -35,7 +35,6 @@ var _ = Describe("Bootstrap Server", func() {
 
 	var stop chan struct{}
 	var resManager manager.ResourceManager
-	var config *bootstrap_config.BootstrapParamsConfig
 	var baseUrl string
 	var metrics core_metrics.Metrics
 
@@ -59,9 +58,9 @@ var _ = Describe("Bootstrap Server", func() {
 
 	BeforeEach(func() {
 		resManager = manager.NewResourceManager(memory.NewStore())
-		config = bootstrap_config.DefaultBootstrapParamsConfig()
-		config.XdsHost = "localhost"
-		config.XdsPort = 5678
+		config := bootstrap_config.DefaultBootstrapServerConfig()
+		config.Params.XdsHost = "localhost"
+		config.Params.XdsPort = 5678
 
 		port, err := test.GetFreePort()
 		baseUrl = "https://localhost:" + strconv.Itoa(port)
diff --git a/pkg/xds/bootstrap/testdata/bootstrap.k8s.golden.yaml b/pkg/xds/bootstrap/testdata/bootstrap.k8s.golden.yaml
index 59a1d6590..6f2e49443 100644
--- a/pkg/xds/bootstrap/testdata/bootstrap.k8s.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/bootstrap.k8s.golden.yaml
@@ -17,6 +17,7 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:5678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/bootstrap.overridden.golden.yaml b/pkg/xds/bootstrap/testdata/bootstrap.overridden.golden.yaml
index 72f4fe8c9..51f2f19d5 100644
--- a/pkg/xds/bootstrap/testdata/bootstrap.overridden.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/bootstrap.overridden.golden.yaml
@@ -23,6 +23,7 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:5678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/bootstrap.universal.golden.yaml b/pkg/xds/bootstrap/testdata/bootstrap.universal.golden.yaml
index c22a92877..25238f3f3 100644
--- a/pkg/xds/bootstrap/testdata/bootstrap.universal.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/bootstrap.universal.golden.yaml
@@ -17,6 +17,7 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:5678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/generator.custom-config-minimal-request.golden.yaml b/pkg/xds/bootstrap/testdata/generator.custom-config-minimal-request.golden.yaml
index 77e5846fb..7d130ff21 100644
--- a/pkg/xds/bootstrap/testdata/generator.custom-config-minimal-request.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.custom-config-minimal-request.golden.yaml
@@ -15,6 +15,7 @@ dynamicResources:
                 inlineBytes: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMRENDQWhTZ0F3SUJBZ0lRSGRQaHhPZlhnV3VOeG9GbFYvRXdxVEFOQmdrcWhraUc5dzBCQVFzRkFEQVAKTVEwd0N3WURWUVFERXdScmRXMWhNQjRYRFRJd01Ea3hOakV5TWpnME5Gb1hEVE13TURreE5ERXlNamcwTkZvdwpEekVOTUFzR0ExVUVBeE1FYTNWdFlUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCCkFPWkdiV2hTbFFTUnhGTnQ1cC8yV0NLRnlIWjNDdXdOZ3lMRVA3blM0Wlh5a3hzRmJZU3VWM2JJZ0Y3YlQvdXEKYTVRaXJlK0M2MGd1aEZicExjUGgyWjZVZmdJZDY5R2xRekhNVlljbUxHalZRdXlBdDRGTU1rVGZWRWw1STRPYQorMml0M0J2aWhWa0toVXo4eTVSUjVLYnFKZkdwNFoyMEZoNmZ0dG9DRmJlT0RtdkJzWUpGbVVRUytpZm95TVkvClAzUjAzU3U3ZzVpSXZuejd0bWt5ZG9OQzhuR1JEemRENUM4Zkp2clZJMVVYNkpSR3lMS3Q0NW9RWHQxbXhLMTAKNUthTjJ6TlYyV3RIc2FKcDlid3JQSCtKaVpHZVp5dnVoNVV3ckxkSENtcUs3c205VG9kR3p0VVpZMFZ6QWM0cQprWVZpWFk4Z1VqZk5tK2NRclBPMWtOOENBd0VBQWFPQmd6Q0JnREFPQmdOVkhROEJBZjhFQkFNQ0FxUXdIUVlEClZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWUQKVlIwT0JCWUVGR01EQlBQaUJGSjNtdjJvQTlDVHFqZW1GVFYyTUI4R0ExVWRFUVFZTUJhQ0NXeHZZMkZzYUc5egpkSUlKYkc5allXeG9iM04wTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDLzE3UXdlT3BHZGIxTUVCSjhYUEc3CjNzSy91dG9XTFgxdGpmOFN1MURnYTZDRFQvZVRXSFpyV1JmODFLT1ZZMDdkbGU1U1JJREsxUWhmYkdHdEZQK1QKdlprcm9vdXNJOVVTMmFDV2xrZUNaV0dUbnF2TG1Eb091anFhZ0RvS1JSdWs0bVFkdE5Ob254aUwvd1p0VEZLaQorMWlOalVWYkxXaURYZEJMeG9SSVZkTE96cWIvTU54d0VsVXlhVERBa29wUXlPV2FURGtZUHJHbWFXamNzZlBHCmFPS293MHplK3pIVkZxVEhiam5DcUVWM2huc1V5UlV3c0JsbjkrakRKWGd3Wk0vdE1sVkpyWkNoMFNsZTlZNVoKTU9CMGZDZjZzVE1OUlRHZzVMcGw2dUlZTS81SU5wbUhWTW8zbjdNQlNucEVEQVVTMmJmL3VvNWdJaXE2WENkcAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
           statPrefix: ads
           targetUri: localhost:15678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
index 7acfbaa98..f87779f22 100644
--- a/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.custom-config.golden.yaml
@@ -23,6 +23,7 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:15678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/generator.default-config-minimal-request.golden.yaml b/pkg/xds/bootstrap/testdata/generator.default-config-minimal-request.golden.yaml
index ec03e2539..52dc4a8a9 100644
--- a/pkg/xds/bootstrap/testdata/generator.default-config-minimal-request.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.default-config-minimal-request.golden.yaml
@@ -9,6 +9,7 @@ dynamicResources:
                 inlineBytes: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMRENDQWhTZ0F3SUJBZ0lRSGRQaHhPZlhnV3VOeG9GbFYvRXdxVEFOQmdrcWhraUc5dzBCQVFzRkFEQVAKTVEwd0N3WURWUVFERXdScmRXMWhNQjRYRFRJd01Ea3hOakV5TWpnME5Gb1hEVE13TURreE5ERXlNamcwTkZvdwpEekVOTUFzR0ExVUVBeE1FYTNWdFlUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCCkFPWkdiV2hTbFFTUnhGTnQ1cC8yV0NLRnlIWjNDdXdOZ3lMRVA3blM0Wlh5a3hzRmJZU3VWM2JJZ0Y3YlQvdXEKYTVRaXJlK0M2MGd1aEZicExjUGgyWjZVZmdJZDY5R2xRekhNVlljbUxHalZRdXlBdDRGTU1rVGZWRWw1STRPYQorMml0M0J2aWhWa0toVXo4eTVSUjVLYnFKZkdwNFoyMEZoNmZ0dG9DRmJlT0RtdkJzWUpGbVVRUytpZm95TVkvClAzUjAzU3U3ZzVpSXZuejd0bWt5ZG9OQzhuR1JEemRENUM4Zkp2clZJMVVYNkpSR3lMS3Q0NW9RWHQxbXhLMTAKNUthTjJ6TlYyV3RIc2FKcDlid3JQSCtKaVpHZVp5dnVoNVV3ckxkSENtcUs3c205VG9kR3p0VVpZMFZ6QWM0cQprWVZpWFk4Z1VqZk5tK2NRclBPMWtOOENBd0VBQWFPQmd6Q0JnREFPQmdOVkhROEJBZjhFQkFNQ0FxUXdIUVlEClZSMGxCQll3RkFZSUt3WUJCUVVIQXdFR0NDc0dBUVVGQndNQk1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0hRWUQKVlIwT0JCWUVGR01EQlBQaUJGSjNtdjJvQTlDVHFqZW1GVFYyTUI4R0ExVWRFUVFZTUJhQ0NXeHZZMkZzYUc5egpkSUlKYkc5allXeG9iM04wTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDLzE3UXdlT3BHZGIxTUVCSjhYUEc3CjNzSy91dG9XTFgxdGpmOFN1MURnYTZDRFQvZVRXSFpyV1JmODFLT1ZZMDdkbGU1U1JJREsxUWhmYkdHdEZQK1QKdlprcm9vdXNJOVVTMmFDV2xrZUNaV0dUbnF2TG1Eb091anFhZ0RvS1JSdWs0bVFkdE5Ob254aUwvd1p0VEZLaQorMWlOalVWYkxXaURYZEJMeG9SSVZkTE96cWIvTU54d0VsVXlhVERBa29wUXlPV2FURGtZUHJHbWFXamNzZlBHCmFPS293MHplK3pIVkZxVEhiam5DcUVWM2huc1V5UlV3c0JsbjkrakRKWGd3Wk0vdE1sVkpyWkNoMFNsZTlZNVoKTU9CMGZDZjZzVE1OUlRHZzVMcGw2dUlZTS81SU5wbUhWTW8zbjdNQlNucEVEQVVTMmJmL3VvNWdJaXE2WENkcAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
           statPrefix: ads
           targetUri: localhost:5678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml b/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
index 8b32be3d3..c476372a3 100644
--- a/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
+++ b/pkg/xds/bootstrap/testdata/generator.default-config.golden.yaml
@@ -23,6 +23,7 @@ dynamicResources:
           credentialsFactoryName: envoy.grpc_credentials.file_based_metadata
           statPrefix: ads
           targetUri: localhost:5678
+    transportApiVersion: V2
   cdsConfig:
     ads: {}
   ldsConfig:
diff --git a/pkg/xds/cache/cla/cache_test.go b/pkg/xds/cache/cla/cache_test.go
index 63307ed13..3458c689c 100644
--- a/pkg/xds/cache/cla/cache_test.go
+++ b/pkg/xds/cache/cla/cache_test.go
@@ -13,6 +13,7 @@ import (
 	"github.com/kumahq/kuma/pkg/core/secrets/cipher"
 	secret_manager "github.com/kumahq/kuma/pkg/core/secrets/manager"
 	secret_store "github.com/kumahq/kuma/pkg/core/secrets/store"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 
 	core_metrics "github.com/kumahq/kuma/pkg/metrics"
 	test_metrics "github.com/kumahq/kuma/pkg/test/metrics"
@@ -102,7 +103,7 @@ var _ = Describe("ClusterLoadAssignment Cache", func() {
 
 	It("should cache Get() queries", func() {
 		By("getting CLA for the first time")
-		cla, err := claCache.GetCLA(context.Background(), "mesh-0", "", "backend")
+		cla, err := claCache.GetCLA(context.Background(), "mesh-0", "", "backend", envoy_common.APIV2)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(countingManager.getQueries).To(Equal(1))
 		Expect(countingManager.listQueries).To(Equal(2))
@@ -115,7 +116,7 @@ var _ = Describe("ClusterLoadAssignment Cache", func() {
 		Expect(js).To(MatchJSON(string(expected)))
 
 		By("getting cached CLA")
-		_, err = claCache.GetCLA(context.Background(), "mesh-0", "", "backend")
+		_, err = claCache.GetCLA(context.Background(), "mesh-0", "", "backend", envoy_common.APIV2)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(countingManager.getQueries).To(Equal(1))
 		Expect(countingManager.listQueries).To(Equal(2))
@@ -130,7 +131,7 @@ var _ = Describe("ClusterLoadAssignment Cache", func() {
 
 		<-time.After(2 * time.Second)
 
-		cla, err = claCache.GetCLA(context.Background(), "mesh-0", "", "backend")
+		cla, err = claCache.GetCLA(context.Background(), "mesh-0", "", "backend", envoy_common.APIV2)
 		Expect(err).ToNot(HaveOccurred())
 		Expect(countingManager.getQueries).To(Equal(2))
 		Expect(countingManager.listQueries).To(Equal(4))
@@ -147,7 +148,7 @@ var _ = Describe("ClusterLoadAssignment Cache", func() {
 		for i := 0; i < 100; i++ {
 			wg.Add(1)
 			go func() {
-				cla, err := claCache.GetCLA(context.Background(), "mesh-0", "", "backend")
+				cla, err := claCache.GetCLA(context.Background(), "mesh-0", "", "backend", envoy_common.APIV2)
 				Expect(err).ToNot(HaveOccurred())
 
 				marshalled, err := json.Marshal(cla) // to imitate Read access to 'cla'
diff --git a/pkg/xds/generator/direct_access_proxy_generator_test.go b/pkg/xds/generator/direct_access_proxy_generator_test.go
index ac4078ae9..4f868e74c 100644
--- a/pkg/xds/generator/direct_access_proxy_generator_test.go
+++ b/pkg/xds/generator/direct_access_proxy_generator_test.go
@@ -14,6 +14,7 @@ import (
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	util_yaml "github.com/kumahq/kuma/pkg/util/yaml"
 	"github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
 	. "github.com/onsi/ginkgo"
@@ -80,7 +81,8 @@ var _ = Describe("DirectAccessProxyGenerator", func() {
 			}
 
 			proxy := &xds.Proxy{
-				Dataplane: dataplane,
+				Dataplane:  dataplane,
+				APIVersion: envoy_common.APIV2,
 			}
 
 			// when
diff --git a/pkg/xds/generator/inbound_proxy_generator_test.go b/pkg/xds/generator/inbound_proxy_generator_test.go
index b1db38ea8..8c0393082 100644
--- a/pkg/xds/generator/inbound_proxy_generator_test.go
+++ b/pkg/xds/generator/inbound_proxy_generator_test.go
@@ -16,6 +16,7 @@ import (
 	model "github.com/kumahq/kuma/pkg/core/xds"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
@@ -71,6 +72,7 @@ var _ = Describe("InboundProxyGenerator", func() {
 					},
 					Spec: &dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 				Policies: model.MatchedPolicies{
 
 					TrafficPermissions: model.TrafficPermissionMap{
diff --git a/pkg/xds/generator/ingress_generator_test.go b/pkg/xds/generator/ingress_generator_test.go
index 037cdbf81..8393fa2d6 100644
--- a/pkg/xds/generator/ingress_generator_test.go
+++ b/pkg/xds/generator/ingress_generator_test.go
@@ -14,6 +14,7 @@ import (
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 )
 
@@ -40,6 +41,7 @@ var _ = Describe("IngressGenerator", func() {
 					},
 					Spec: dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 				Routing: core_xds.Routing{
 					OutboundTargets:  given.outboundTargets,
 					TrafficRouteList: given.trafficRoutes,
diff --git a/pkg/xds/generator/outbound_proxy_generator_test.go b/pkg/xds/generator/outbound_proxy_generator_test.go
index 2b01ef445..0e629579a 100644
--- a/pkg/xds/generator/outbound_proxy_generator_test.go
+++ b/pkg/xds/generator/outbound_proxy_generator_test.go
@@ -13,6 +13,7 @@ import (
 	model "github.com/kumahq/kuma/pkg/core/xds"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
@@ -167,6 +168,7 @@ var _ = Describe("OutboundProxyGenerator", func() {
 					},
 					Spec: dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 				Routing: model.Routing{
 					TrafficRoutes: model.RouteMap{
 						mesh_proto.OutboundInterface{
@@ -496,6 +498,7 @@ var _ = Describe("OutboundProxyGenerator", func() {
 				},
 				Spec: dataplane,
 			},
+			APIVersion: envoy_common.APIV2,
 			Routing: model.Routing{
 				TrafficRoutes: model.RouteMap{
 					mesh_proto.OutboundInterface{
diff --git a/pkg/xds/generator/probe_generator_test.go b/pkg/xds/generator/probe_generator_test.go
index a3d567cca..ed94a2732 100644
--- a/pkg/xds/generator/probe_generator_test.go
+++ b/pkg/xds/generator/probe_generator_test.go
@@ -14,6 +14,7 @@ import (
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 )
 
@@ -37,6 +38,7 @@ var _ = Describe("ProbeGenerator", func() {
 					},
 					Spec: dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 			}
 
 			// when
diff --git a/pkg/xds/generator/prometheus_endpoint_generator_test.go b/pkg/xds/generator/prometheus_endpoint_generator_test.go
index 04ca77059..92e06fbf6 100644
--- a/pkg/xds/generator/prometheus_endpoint_generator_test.go
+++ b/pkg/xds/generator/prometheus_endpoint_generator_test.go
@@ -9,6 +9,7 @@ import (
 	. "github.com/onsi/ginkgo/extensions/table"
 	. "github.com/onsi/gomega"
 
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
@@ -62,6 +63,7 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 					},
 					Spec: &mesh_proto.Dataplane{},
 				},
+				APIVersion: envoy_common.APIV2,
 			},
 		}),
 		Entry("Datalane has Prometheus configuration while Mesh doesn't", testCase{
@@ -76,7 +78,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -121,7 +124,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -167,7 +171,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -242,7 +247,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -286,7 +292,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -357,7 +364,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -419,7 +427,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -482,7 +491,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 				},
 			},
 			proxy: &model.Proxy{
-				Id: model.ProxyId{Name: "demo.backend-01"},
+				Id:         model.ProxyId{Name: "demo.backend-01"},
+				APIVersion: envoy_common.APIV2,
 				Dataplane: &mesh_core.DataplaneResource{
 					Meta: &test_model.ResourceMeta{
 						Name: "backend-01",
@@ -536,7 +546,8 @@ var _ = Describe("PrometheusEndpointGenerator", func() {
 					},
 				}
 				proxy := &model.Proxy{
-					Id: model.ProxyId{Name: "demo.backend-01"},
+					Id:         model.ProxyId{Name: "demo.backend-01"},
+					APIVersion: envoy_common.APIV2,
 					Dataplane: &mesh_core.DataplaneResource{
 						Meta: &test_model.ResourceMeta{
 							Name: "backend-01",
diff --git a/pkg/xds/generator/proxy_template_profile_source_test.go b/pkg/xds/generator/proxy_template_profile_source_test.go
index 4c2121a0d..55fdf0623 100644
--- a/pkg/xds/generator/proxy_template_profile_source_test.go
+++ b/pkg/xds/generator/proxy_template_profile_source_test.go
@@ -6,8 +6,9 @@ import (
 	"path/filepath"
 	"time"
 
-	envoy_api_v2 "github.com/envoyproxy/go-control-plane/envoy/api/v2"
+	"github.com/golang/protobuf/proto"
 
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/envoy/endpoints/v2"
 
 	"github.com/golang/protobuf/ptypes"
@@ -28,10 +29,12 @@ type dummyCLACache struct {
 	outboundTargets model.EndpointMap
 }
 
-func (d *dummyCLACache) GetCLA(ctx context.Context, meshName, meshHash, service string) (*envoy_api_v2.ClusterLoadAssignment, error) {
+func (d *dummyCLACache) GetCLA(ctx context.Context, meshName, meshHash, service string, version envoy_common.APIVersion) (proto.Message, error) {
 	return endpoints.CreateClusterLoadAssignment(service, d.outboundTargets[service]), nil
 }
 
+var _ model.CLACache = &dummyCLACache{}
+
 var _ = Describe("ProxyTemplateProfileSource", func() {
 
 	type testCase struct {
@@ -100,6 +103,7 @@ var _ = Describe("ProxyTemplateProfileSource", func() {
 					},
 					Spec: dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 				Routing: model.Routing{
 					TrafficRoutes: model.RouteMap{
 						mesh_proto.OutboundInterface{
diff --git a/pkg/xds/generator/proxy_template_raw_source_test.go b/pkg/xds/generator/proxy_template_raw_source_test.go
index 5efd74be3..b5f99ea63 100644
--- a/pkg/xds/generator/proxy_template_raw_source_test.go
+++ b/pkg/xds/generator/proxy_template_raw_source_test.go
@@ -11,6 +11,7 @@ import (
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 )
 
@@ -59,6 +60,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -87,6 +89,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:     "raw-name",
@@ -114,6 +117,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -143,6 +147,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -183,6 +188,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -258,6 +264,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw:      nil,
 				expected: `{}`,
@@ -281,6 +288,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -341,6 +349,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
@@ -399,6 +408,7 @@ var _ = Describe("ProxyTemplateRawSource", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				raw: []*mesh_proto.ProxyTemplateRawResource{{
 					Name:    "raw-name",
diff --git a/pkg/xds/generator/proxy_template_test.go b/pkg/xds/generator/proxy_template_test.go
index 09a3c82ab..0d7f3b72a 100644
--- a/pkg/xds/generator/proxy_template_test.go
+++ b/pkg/xds/generator/proxy_template_test.go
@@ -14,6 +14,7 @@ import (
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 )
 
@@ -81,6 +82,7 @@ var _ = Describe("ProxyTemplateGenerator", func() {
 							},
 						},
 					},
+					APIVersion: envoy_common.APIV2,
 				},
 				template: &mesh_proto.ProxyTemplate{
 					Conf: &mesh_proto.ProxyTemplate_Conf{
@@ -158,7 +160,8 @@ var _ = Describe("ProxyTemplateGenerator", func() {
 						},
 						Spec: dataplane,
 					},
-					Metadata: &model.DataplaneMetadata{},
+					APIVersion: envoy_common.APIV2,
+					Metadata:   &model.DataplaneMetadata{},
 				}
 
 				// when
diff --git a/pkg/xds/generator/tracing_generator_test.go b/pkg/xds/generator/tracing_generator_test.go
index fda52b24b..c213d1539 100644
--- a/pkg/xds/generator/tracing_generator_test.go
+++ b/pkg/xds/generator/tracing_generator_test.go
@@ -10,6 +10,7 @@ import (
 
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
@@ -50,6 +51,7 @@ var _ = Describe("TracingProxyGenerator", func() {
 						Mesh: "demo",
 					},
 				},
+				APIVersion: envoy_common.APIV2,
 			},
 		}),
 	)
@@ -89,6 +91,7 @@ var _ = Describe("TracingProxyGenerator", func() {
 						},
 					},
 				},
+				APIVersion: envoy_common.APIV2,
 				Policies: model.MatchedPolicies{
 					TracingBackend: &mesh_proto.TracingBackend{
 						Name: "zipkin",
diff --git a/pkg/xds/generator/transparent_proxy_generator_test.go b/pkg/xds/generator/transparent_proxy_generator_test.go
index a27518981..b4de38a1c 100644
--- a/pkg/xds/generator/transparent_proxy_generator_test.go
+++ b/pkg/xds/generator/transparent_proxy_generator_test.go
@@ -10,6 +10,7 @@ import (
 	model "github.com/kumahq/kuma/pkg/core/xds"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/generator"
 
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
@@ -62,6 +63,7 @@ var _ = Describe("TransparentProxyGenerator", func() {
 						Version: "v1",
 					},
 				},
+				APIVersion: envoy_common.APIV2,
 			},
 			expected: `
         {}
@@ -83,6 +85,7 @@ var _ = Describe("TransparentProxyGenerator", func() {
 						},
 					},
 				},
+				APIVersion: envoy_common.APIV2,
 				Policies: model.MatchedPolicies{
 					Logs: map[model.ServiceName]*mesh_proto.LoggingBackend{ // to show that is not picked
 						"some-service": {
@@ -169,6 +172,7 @@ var _ = Describe("TransparentProxyGenerator", func() {
 						},
 					},
 				},
+				APIVersion: envoy_common.APIV2,
 				Policies: model.MatchedPolicies{
 					Logs: map[model.ServiceName]*mesh_proto.LoggingBackend{ // to show that is is not picked
 						"pass_through": {
diff --git a/pkg/xds/server/v2/reconcile_test.go b/pkg/xds/server/v2/reconcile_test.go
index 667070180..eb6977e6c 100644
--- a/pkg/xds/server/v2/reconcile_test.go
+++ b/pkg/xds/server/v2/reconcile_test.go
@@ -14,10 +14,11 @@ import (
 	mesh_proto "github.com/kumahq/kuma/api/mesh/v1alpha1"
 	"github.com/kumahq/kuma/pkg/core"
 	mesh_core "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
-	core_xds "github.com/kumahq/kuma/pkg/core/xds"
 	xds_model "github.com/kumahq/kuma/pkg/core/xds"
+	core_xds_v2 "github.com/kumahq/kuma/pkg/core/xds/v2"
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 )
 
 var _ = Describe("Reconcile", func() {
@@ -41,10 +42,10 @@ var _ = Describe("Reconcile", func() {
 			}
 		})
 
-		var xdsContext core_xds.XdsContext
+		var xdsContext core_xds_v2.XdsContext
 
 		BeforeEach(func() {
-			xdsContext = core_xds.NewXdsContext()
+			xdsContext = core_xds_v2.NewXdsContext()
 		})
 
 		snapshot := envoy_cache.Snapshot{
@@ -109,7 +110,8 @@ var _ = Describe("Reconcile", func() {
 					Mesh: "demo",
 					Name: "example",
 				},
-				Dataplane: dataplane,
+				Dataplane:  dataplane,
+				APIVersion: envoy_common.APIV2,
 			}
 			err := r.Reconcile(xds_context.Context{}, proxy)
 			// then
diff --git a/pkg/xds/server/v2/snapshot_generator_test.go b/pkg/xds/server/v2/snapshot_generator_test.go
index 90199754b..1372604b9 100644
--- a/pkg/xds/server/v2/snapshot_generator_test.go
+++ b/pkg/xds/server/v2/snapshot_generator_test.go
@@ -13,9 +13,10 @@ import (
 	model "github.com/kumahq/kuma/pkg/core/xds"
 	"github.com/kumahq/kuma/pkg/plugins/resources/memory"
 	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
-	util_cache "github.com/kumahq/kuma/pkg/util/cache"
+	util_cache_v2 "github.com/kumahq/kuma/pkg/util/cache/v2"
 	util_proto "github.com/kumahq/kuma/pkg/util/proto"
 	xds_context "github.com/kumahq/kuma/pkg/xds/context"
+	envoy_common "github.com/kumahq/kuma/pkg/xds/envoy"
 	"github.com/kumahq/kuma/pkg/xds/template"
 )
 
@@ -73,6 +74,7 @@ var _ = Describe("Reconcile", func() {
 					},
 					Spec: &dataplane,
 				},
+				APIVersion: envoy_common.APIV2,
 				Policies: model.MatchedPolicies{
 					TrafficPermissions: model.TrafficPermissionMap{
 						mesh_proto.InboundInterface{
@@ -116,7 +118,7 @@ var _ = Describe("Reconcile", func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			// when
-			resp, err := util_cache.ToDeltaDiscoveryResponse(s)
+			resp, err := util_cache_v2.ToDeltaDiscoveryResponse(s)
 			// then
 			Expect(err).ToNot(HaveOccurred())
 			// when