背景

现在有多个k8s集群,在每个集群中部署一个dapr服务。不同k8s集群中的服务,无法跨集群实现服务调用。

假设集群中pod的ip是互通的。(前提条件,必须!!!)

命名解析

介绍

Name resolvers provide a common way to interact with different name resolvers, which are used to return the address or IP of other services your applications may connect to.

命名解析器提供了一种与不同命名解析器互动的通用方法,这些解析器用于返回你的应用程序可能要连接到的其他服务的地址或IP。

接口定义

兼容的名称解析器需要实现 nameresolution.go 文件中的 Resolver 接口。

// Resolver是命名解析器的接口。
type Resolver interface {
	// Init initializes name resolver.
	Init(metadata Metadata) error
	// ResolveID resolves name to address.
	ResolveID(req ResolveRequest) (string, error)
}


// ResolveRequest 表示服务发现解析器请求。
type ResolveRequest struct {
	ID        string
	Namespace string
	Port      int
	Data      map[string]string
}

使用方式

命名解析在service invoke 流程中的使用方式

解析地址

name resolver 被调用的地方只有一个:

func (d *directMessaging) getRemoteApp(appID string) (remoteApp, error) {
  // 从appID中获取id和namespace
  // appID 可能是类似 "appID.namespace" 的格式
	id, namespace, err := d.requestAppIDAndNamespace(appID)
	if err != nil {
		return remoteApp{}, err
	}

  // 执行 resolver 的解析
	request := nr.ResolveRequest{ID: id, Namespace: namespace, Port: d.grpcPort}
	address, err := d.resolver.ResolveID(request)
	if err != nil {
		return remoteApp{}, err
	}

  // 返回 remoteApp 的地址
	return remoteApp{
		namespace: namespace,
		id:        id,
		address:   address,
	}, nil
}

解析出来的地址在 directMessaging 的 Invoke() 中使用,用来执行远程调用:

// Invoke takes a message requests and invokes an app, either local or remote.
func (d *directMessaging) Invoke(ctx context.Context, targetAppID string, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) {
	app, err := d.getRemoteApp(targetAppID)
	if err != nil {
		return nil, err
	}

  // 如果目标应用的 id 和 namespace 都和 directMessaging 的一致,则执行 invokeLocal()
	if app.id == d.appID && app.namespace == d.namespace {
		return d.invokeLocal(ctx, req)
	}
  
  // 这是在带有重试机制的情况下调用 invokeRemote
	return d.invokeWithRetry(ctx, retry.DefaultLinearRetryCount, retry.DefaultLinearBackoffInterval, app, d.invokeRemote, req)
}

daprd启动时,注入了3中命名解释器。这里是针对k8s环境下的dapr

关闭kubernetes集群_ip地址

kubernetes 命名解析实现

kubernetes 的实现超级简单,直接按照 Kubernetes services 的格式要求,拼出一个 Kubernetes services 的 name 即可:

// ResolveID resolves name to address in Kubernetes.
func (k *resolver) ResolveID(req nameresolution.ResolveRequest) (string, error) {
	// Dapr requires this formatting for Kubernetes services
	return fmt.Sprintf("%s-dapr.%s.svc.%s:%d", req.ID, req.Namespace, k.clusterDomain, req.Port), nil
}


其中, req.ID 和 req.Namespace 对应到 Kubernetes 的 service name 和 namespace,注意这里的 Kubernetes service 是在 ID 后面加了 “-dapr” 后缀。Port 来自请求参数,简单拼接而已。


注意这里的service格式,其实是普通的k8s service经过dapr的injector注入后生成新的service,本质上就是k8s的一个service。

clusterDomain 的设置

clusterDomain 稍微复杂一点,默认值是 “cluster.local”,在构建 Resolver 时设置:

const (
	DefaultClusterDomain = "cluster.local"
)

type resolver struct {
	logger        logger.Logger
	clusterDomain string
}

// NewResolver creates Kubernetes name resolver.
func NewResolver(logger logger.Logger) nameresolution.Resolver {
	return &resolver{
		logger:        logger,
		clusterDomain: DefaultClusterDomain,
	}
}

k8s service如何解析到服务的ip地址

关闭kubernetes集群_关闭kubernetes集群_02

我们知道k8s service,可以通过对应的endpoint,可以获取到后端服务的对应pod的ip地址。这个时候,如果我们在集群A中手动创建集群B的Service,以及对应的endpoint,那么,就可以在集群A中调用集群B的服务。从而实现dapr跨集群服务。或者在etcd中存储其他服务的ip地址,通过etcd可以获取其他集群中服务的ip地址。

关闭kubernetes集群_dapr_03

最后

本文的思路只针对k8s环境,且要求多个集群ip互通,只支持dapr的服务调用,其他功能不支持。条件比较严格,思路未经实现仅供参考。