KubeVela 的集群是如何管理的

KubeVela 是多集群应用管理组件,所以在使用之前需要将集群纳管到 KubeVela 中,让 KubeVela 能感知并维护集群信息。在应用下发到指定集群时,KubeVela 能知道如何连接到目标集群并进行操作。

KubeVela 使用的是 Secret 来保存集群信息的,和 Cluster Gateway 共享的同一套 Secret 进行集群管理。当进行集群纳管时,KubeVela 会创建名字和集群名相同的 Secret,用于存储集群的连接信息。

当请求从 APIServer 转发到 Cluster Gateway 时,使用路径中提供的集群名去查询 Secret 并获取到纳管集群的连接信息。

Cluster Gateway 处理流程如下:

Cluster Gateway 处理流程图

集群信息 Secret

Secret 数据类似下面这样:

集群 Secret
1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Secret
metadata:
name: managed1
labels:
cluster.core.oam.dev/cluster-credential-type: ServiceAccountToken
type: Opaque # <--- Has to be opaque
data:
endpoint: "..." # Should NOT be 127.0.0.1
ca.crt: "..." # ca cert for cluster "managed1"
token: "..." # working jwt token

纳管集群实现

纳管集群时,KubeVela 有两套非常相似的处理逻辑,VelaUX 和 vela-cli:

  • VelaUX 业务逻辑入口在 clusterServiceImpl.createKubeCluster,在进行一些判断后通过 joinClusterByKubeConfigString() 函数注册纳管集群,最终调用 multicluster.JoinClusterByKubeConfig() 函数进行集群纳管处理;
  • vela-cli 处理入口在 NewClusterJoinCommand 中,直接调用 multicluster.JoinClusterByKubeConfig() 函数进行集群纳管处理。

JoinClusterByKubeConfig() 函数通过 Secret 来管理集群,而在些之上 VelaUX 更进一步。

VelaUX 的 createKubeCluster() 方法还会在 Store 创建 model.Cluster{} 结构的数据,保存集群信息。Store 如果是 kubeapi,则是存储到 ConfigMap 中,否则存储到 MongoDB。

JoinClusterByKubeConfig

multicluster.JoinClusterByKubeConfig() 函数会创建前面提到的 Secret 用于管理集群,但是在创建后会做一些判断,如果条件不满足会删除创建的数据。

pkg/multicluster/cluster_management.go:389
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// JoinClusterByKubeConfig add child cluster by kubeconfig path, return cluster info and error
func JoinClusterByKubeConfig(ctx context.Context, cli client.Client, kubeconfigPath string, clusterName string, options ...JoinClusterOption) (*KubeClusterConfig, error) {
args := newJoinClusterArgs(options...)
// 读取纳管集群的 kubeconfig 文件
clusterConfig, err := LoadKubeClusterConfigFromFile(kubeconfigPath)
if err != nil {
return nil, err
}
if err := clusterConfig.SetClusterName(clusterName).SetCreateNamespace(args.createNamespace).Validate(); err != nil {
return nil, err
}
// 纳管处理
switch args.engine {
case ClusterGateWayEngine:
if err = clusterConfig.RegisterByVelaSecret(ctx, cli); err != nil {
return nil, err
}
case OCMEngine:
if args.inClusterBootstrap == nil {
return nil, errors.Wrapf(err, "failed to determine the registration endpoint for the hub cluster "+
"when parsing --in-cluster-bootstrap flag")
}
if err = clusterConfig.RegisterClusterManagedByOCM(ctx, cli, args); err != nil {
return clusterConfig, err
}
}
if cfg, ok := ctx.Value(KubeConfigContext).(*rest.Config); ok {
if err = SetClusterVersionInfo(ctx, cfg, clusterConfig.ClusterName); err != nil {
return nil, err
}
}
return clusterConfig, nil
}

JoinClusterByKubeConfig() 函数在纳管集群时需要从本地文件读取 kubeconfig 配置,所以在 VelaUX 的 joinClusterByKubeConfigString() 中会先创建临时文件再调用 JoinClusterByKubeConfig() 函数处理。

上面代码中有两个分支的处理逻辑。这里主要关注直接通过 Cluster Gateway 管理的集群,OCM 集群先跳过。ClusterGateway 纳管集群通过 clusterConfig.RegisterByVelaSecret() 方法进行:

1
2
3
4
5
6
7
8
9
10
11
12
func (clusterConfig *KubeClusterConfig) RegisterByVelaSecret(ctx context.Context, cli client.Client) error {
// 检查集群是否已经存在
if err := ensureClusterNotExists(ctx, cli, clusterConfig.ClusterName); err != nil {
return errors.Wrapf(err, "cannot use cluster name %s", clusterConfig.ClusterName)
}
// 不存在,创建集群 Secret
if err := clusterConfig.createClusterSecret(ctx, cli, true); err != nil {
return errors.Wrapf(err, "failed to add cluster to kubernetes")
}
// 后置检查
return clusterConfig.PostRegistration(ctx, cli)
}

纳管逻辑很重要部分在后置检查这里,后置检查会保证配置的 clusterConfig.CreateNamespace 命名空间在纳管集群创建,此时会发起请求通过 Cluster Gateway 连接纳管集群进行操作。

[!warning]
这里请求会直接从本地向 APIServer 发起,而且请求 URL 是以下格式: /apis/cluster.core.oam.dev/v1alpha1/clustergateways/{clusterName}/proxy/{api}

Namespace 操作失败会回退纳管操作,删除 Secret,返回纳管失败错误。

Cluster Gateway 获取集群信息

当代理请求通过 Cluster Gateway 时,就需要去查询集群的信息。

在 Cluster Gateway 代码中,创建代理连接时的处理方法 Connect() 里会去获取集群信息。这个方法会通过 parentStorage.Get() 调用的父资源 ClusterGatewayGet() 方法:

pkg/apis/cluster/v1alpha1/clustergateway_types_secret.go:46
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (in *ClusterGateway) Get(ctx context.Context, name string, _ *metav1.GetOptions) (runtime.Object, error) {
if singleton.GetSecretControl() == nil {
return nil, fmt.Errorf("loopback secret client are not inited")
}

clusterSecret, err := singleton.GetSecretControl().Get(ctx, name)
if err != nil {
klog.Warningf("Failed getting secret %q/%q: %v", config.SecretNamespace, name, err)
return nil, err
}

if options.OCMIntegration {
if singleton.GetClusterControl() == nil {
return nil, fmt.Errorf("loopback cluster client are not inited")
}
managedCluster, err := singleton.GetClusterControl().Get(ctx, name)
if err != nil {
return convertFromSecret(clusterSecret)
}
return convertFromManagedClusterAndSecret(managedCluster, clusterSecret)
}

return convertFromSecret(clusterSecret)
}

这个方法内部是通过 SecretControl 获取集群同名的 Secret,并转换成 ClusterGateway 对象,最终传给 proxyHandler 使用,具体参考之前的文章《KubeVela 代理网关 Cluster Gateway 实现》。

总结

KubeVela 这样使用 Secret 管理集群也是有好处的:

  1. Secret 能进行权限管理,保证安全;
  2. 支持通过 Label 设置集群元信息,用于调度时基于 Label 进行应用分发。

在代码中我们可以看到,如果使用的 vela-cli 进行纳管,请求会从当前主机向 API Server 发出,而很多时候 Kubernetes 的 API Server 是不开放到外面访问的。

所以在生产环境中,更多的是使用 vela-cli in pod 的方式进行管理。

作者

Jakes Lee

发布于

2023-05-31

更新于

2023-06-15

许可协议

评论