k8s多集群管理方案 - clusternet

简介

腾讯云开源的k8s多集群管理方案,可发布应用到多个k8s集群。
https://github.com/clusternet/clusternet
GitHub Star:891

架构

image.png
image.png
包含clusternet-hub和clusternet-agent两个组件。

clusternet-hub部署在父集群,用途:

  • 接收子集群的注册请求,并为每个自己群创建namespace、serviceaccount等资源
  • 提供父集群跟各个子集群的长连接
  • 提供restful api来访问各个子集群,同时支持子集群的互访

clusternet-agent部署在子集群,用途:

  • 自动注册子集群到父集群
  • 心跳报告到中心集群,包括k8s版本、运行平台、健康状态等
  • 通过websocket协议跟父集群通讯

CRD抽象

ClusterRegistrationRequest

clusternet-agent在父集群中创建的CR,一个k8s集群一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: clusters.clusternet.io/v1beta1
kind: ClusterRegistrationRequest
metadata:
labels:
clusters.clusternet.io/cluster-id: 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
clusters.clusternet.io/cluster-name: clusternet-cluster-pmlbp
clusters.clusternet.io/registered-by: clusternet-agent
name: clusternet-2bcd48d2-0ead-45f7-938b-5af6af7da5fd
spec:
clusterId: 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
clusterName: clusternet-cluster-pmlbp # 集群名称
clusterType: EdgeCluster # 集群类型
syncMode: Dual
status:
caCertificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeE1USXhNekV3TWpjeE5Wb1hEVE14TVRJeE1URXdNamN4TlZvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTHVCCmJwdXNaZXM5RXFXaEw1WUVGb2c4eHRJai9jbFh2S1lTMnZYbGR4cUt1MUR3VHV3aUcxZUN4VGlHa2dLQXVXd1IKVFFheEh5aGY3U29lc0hqMG43NnFBKzhQT05lS1VGdERJOWVzejF2WTF5bXFoUHR0QVo0cGhkWmhmbXJjZTJLRQpuMS84MzNWbWlXd0pSZmNWcEJtTU52MjFYMVVwNWp6RGtncS9tY0JOOGN0ZU5PMEpKNkVVeTE2RXZLbjhyWG90ClErTW5PUHE4anFNMzJjRFFqYWVEdGxvM2kveXlRd1NMa2wzNFo4aElwZDZNWWVSTWpXcmhrazF4L0RYZjNJK3IKOGs5S1FBbEsvNWZRMXk5NHYwT25TK0FKZTliczZMT2srYVFWYm5SbExab2E2aVZWbUJNam03UjBjQ2w0Y1hpRwpyekRnN1ZLc3lsY1o3aGFRRTNFQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZPaGwxMHUwNnJvenZJUm9XVmpNYlVPMzFDbERNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFBQytqMXpPQVZYVXpNUFJ0U29Kd3JhMU1FSkJOUTNMWitmWnZRYjdIbk53b00zaCthMgoyc25yUitSWTYrOFFDbXVKeis1eU5yYStEZDlnNzQ1Q0hDaFpVZzlsY3RjQTRzZVR5OXZqcUVQNVBNSzEvbi9PCnFEcDQyMUpqTjYvUXJmamIvbVM0elUvZXNGZGowQXRYQ0FLMWJsNmF0ai9jZXVBbzh1bTRPaUVlNnJhanBYTHUKWFlmQ1FVZFV5TWpQdEZHTDMwNytna0RpdlVsdXk3RkQ4aUpURTh2QWpxOVBXOW40SmxFMjdQWXR5QnNocy9XSApCZ2czQjZpTG10SjZlNzJiWnA3ZmptdmJWTC9VdkxzYXZqRXltZDhrMnN1bFFwQUpzeVJrMkEzM1g3NWJpS0RGCk1CU29DaHc3U2JMSkJrN0FNckRzTjd1Q2U3WWVIWGdZemdhRAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
dedicatedNamespace: clusternet-sdqrh
managedClusterName: clusternet-cluster-pmlbp
result: Approved
token: ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkluZFpWMUV6UmxCbFdUUldWa1Y0UmxBeWMzaDJOV3RuUzBabVJsaG9jWG94TkhKM2JtUkxiRTlrYUZVaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpqYkhWemRHVnlibVYwTFhOa2NYSm9JaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbU5zZFhOMFpYSnVaWFF0YmpneWFHc3RkRzlyWlc0dGN6SnpjRFVpTENKcmRXSmxjbTVsZEdWekxtbHZMM05sY25acFkyVmhZMk52ZFc1MEwzTmxjblpwWTJVdFlXTmpiM1Z1ZEM1dVlXMWxJam9pWTJ4MWMzUmxjbTVsZEMxdU9ESm9heUlzSW10MVltVnlibVYwWlhNdWFXOHZjMlZ5ZG1salpXRmpZMjkxYm5RdmMyVnlkbWxqWlMxaFkyTnZkVzUwTG5WcFpDSTZJamN3TWpRMk1tTTBMV0prTlRFdE5HVXpNeTA1WXpnekxUWmpPV1F6TUdGall6bG1aU0lzSW5OMVlpSTZJbk41YzNSbGJUcHpaWEoyYVdObFlXTmpiM1Z1ZERwamJIVnpkR1Z5Ym1WMExYTmtjWEpvT21Oc2RYTjBaWEp1WlhRdGJqZ3lhR3NpZlEuUjJCdEQ1YzQxRm1seC1hTEFKSWQzbGxvOThSY25TakVUak5pSnVuTXg1US1jYXhwc3VkamUxekVtUF9mTHR6TjU4d21Dd3RXcHpoaXhSMWFTUHE5LXJTRlBIYzk3aDlTT3daZGdicC10alFBSjA4dllfYWdiVFRKLU1WN1dpN0xQVzRIcmt1U3RlemUzVHh2RW11NWwySlpzbG5UTXdkR3NGRVlVZzVfeWFoLUQwQ2pnTkZlR1ljUjJ1TlJBWjdkNW00Q1c5VmRkdkNsanNqRE5WX1k0RkFEbGo2cHgzSlh0SDQ4U1ZUd254TS1sNHl0eXBENjZFbG1PYXpUMmRwSWd4eWNSZ0tJSUlacWNQNnZVc0ZOM1Zka21ZZ29ydy1NSUcwc25YdzFaZ1lwRk9SUDJRa3hjd2NOUVVOTjh6VUZqSUZSSmdSSFdjNlo4aXd2eURnZTVn

ClusterRole和Role

ClusterRegistrationRequest被接收后会创建ClusterRole

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
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
clusternet.io/autoupdate: "true"
labels:
clusternet.io/created-by: clusternet-hub
clusters.clusternet.io/bootstrapping: rbac-defaults
clusters.clusternet.io/cluster-id: 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
name: clusternet-2bcd48d2-0ead-45f7-938b-5af6af7da5fd
rules:
- apiGroups:
- clusters.clusternet.io
resources:
- clusterregistrationrequests
verbs:
- create
- get
- apiGroups:
- proxies.clusternet.io
resourceNames:
- 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
resources:
- sockets
verbs:
- '*'

并会在cluster对应的namespace下创建Role

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
clusternet.io/autoupdate: "true"
labels:
clusternet.io/created-by: clusternet-hub
clusters.clusternet.io/bootstrapping: rbac-defaults
name: clusternet-managedcluster-role
namespace: clusternet-sdqrh
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'

ManagedCluster

clusternet-hub在接受ClusterRegistrationRequest后,会创建一个ManagedCluster。一个k8s集群一个namespace

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
34
35
36
37
38
39
40
41
apiVersion: clusters.clusternet.io/v1beta1
kind: ManagedCluster
metadata:
labels:
clusternet.io/created-by: clusternet-agent
clusters.clusternet.io/cluster-id: 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
clusters.clusternet.io/cluster-name: clusternet-cluster-pmlbp
name: clusternet-cluster-pmlbp
namespace: clusternet-sdqrh
spec:
clusterId: 2bcd48d2-0ead-45f7-938b-5af6af7da5fd
clusterType: EdgeCluster
syncMode: Dual
status:
allocatable:
cpu: 7600m
memory: "30792789992"
apiserverURL: https://10.233.0.1:443 # default/kubernetes的service clusterip
appPusher: true
capacity:
cpu: "8"
memory: 32192720Ki
clusterCIDR: 10.233.0.0/18
conditions:
- lastTransitionTime: "2021-12-15T07:47:10Z"
message: managed cluster is ready.
reason: ManagedClusterReady
status: "True"
type: Ready
healthz: true
heartbeatFrequencySeconds: 180
k8sVersion: v1.21.5
lastObservedTime: "2021-12-15T07:47:10Z"
livez: true
nodeStatistics:
readyNodes: 1
parentAPIServerURL: https://172.21.115.160:6443 # 父集群地址
platform: linux/amd64
readyz: true
serviceCIDR: 10.233.64.0/18
useSocket: true

Subscription

应用发布的抽象,人工提交到环境中。要发布的资源即可以是helm chart,也可以是原生的k8s对象。clusternet在部署的时候会查看部署的优先级,并且支持weight,优先部署cluster级别的资源,这点跟helm部署的逻辑一致。

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
apiVersion: apps.clusternet.io/v1alpha1
kind: Subscription
metadata:
name: app-demo
namespace: default
spec:
# 定义应用要发布到哪个k8s集群
subscribers: # defines the clusters to be distributed to
- clusterAffinity:
matchLabels:
clusters.clusternet.io/cluster-id: dc91021d-2361-4f6d-a404-7c33b9e01118 # PLEASE UPDATE THIS CLUSTER-ID TO YOURS!!!
# 定义了要发布哪些资源
feeds: # defines all the resources to be deployed with
- apiVersion: apps.clusternet.io/v1alpha1
kind: HelmChart
name: mysql
namespace: default
- apiVersion: v1
kind: Namespace
name: foo
- apiVersion: v1
kind: Service
name: my-nginx-svc
namespace: foo
- apiVersion: apps/v1
kind: Deployment
name: my-nginx
namespace: foo

HelmChart

Subscriber发布的一个子资源之一,对应一个helm chart的完整描述

1
2
3
4
5
6
7
8
9
10
apiVersion: apps.clusternet.io/v1alpha1
kind: HelmChart
metadata:
name: mysql
namespace: default
spec:
repo: https://charts.bitnami.com/bitnami
chart: mysql
version: 8.6.2
targetNamespace: abc

Localization

差异化不同于其他集群的配置,用来描述namespace级别的差异化配置。

Globalization

用来描述不同集群的cluster级别的差异化配置。

Base

Description

跟Base对象根据Localization和Globalization渲染得到的最终要发布到集群中的最终对象。

安装

本文使用kind进行测试,使用kind创建两个k8s集群host和member1,两个集群的apiserver均监听在宿主机的端口,确保从一个集群可以访问到另外一个集群的apiserver。

在父集群执行如下命令安装clusternet管控组件:

1
2
3
helm repo add clusternet https://clusternet.github.io/charts
helm install clusternet-hub -n clusternet-system --create-namespace clusternet/clusternet-hub
kubectl apply -f https://raw.githubusercontent.com/clusternet/clusternet/main/manifests/samples/cluster_bootstrap_token.yaml

会在clusternet-system namespace下创建deployment clusternet-hub。

最后一条命令,会创建一个secret,其中的token信息为07401b.f395accd246ae52d,在子集群注册的时候需要用到。

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
apiVersion: v1
kind: Secret
metadata:
# Name MUST be of form "bootstrap-token-<token id>"
name: bootstrap-token-07401b
namespace: kube-system

# Type MUST be 'bootstrap.kubernetes.io/token'
type: bootstrap.kubernetes.io/token
stringData:
# Human readable description. Optional.
description: "The bootstrap token used by clusternet cluster registration."

# Token ID and secret. Required.
token-id: 07401b
token-secret: f395accd246ae52d

# Expiration. Optional.
expiration: 2025-05-10T03:22:11Z

# Allowed usages.
usage-bootstrap-authentication: "true"
usage-bootstrap-signing: "true"

# Extra groups to authenticate the token as. Must start with "system:bootstrappers:"
auth-extra-groups: system:bootstrappers:clusternet:register-cluster-token

在子集群执行如下命令,其中parentURL要修改为父集群的apiserver地址

1
2
3
4
5
helm repo add clusternet https://clusternet.github.io/charts
helm install clusternet-agent -n clusternet-system --create-namespace \
--set parentURL=https://10.0.248.96:8443 \
--set registrationToken=07401b.f395accd246ae52d \
clusternet/clusternet-agent

其中parentURL为父集群的apiserver地址,registrationToken为父集群注册的token信息。

访问子集群

kubectl-clusternet命令行方式访问

安装kubectl krew插件,参考 https://krew.sigs.k8s.io/docs/user-guide/setup/install/,执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
yum install git -y
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)
echo 'export PATH="${PATH}:${HOME}/.krew/bin"' >> ~/.bashrc
source ~/.bashrc

安装kubectl插件clusternet:kubectl krew install clusternet

使用kubectl clusternet get可以看到发布的应用,非发布的应用看不到。

代码层面访问子集群

可以参照例子:https://github.com/clusternet/clusternet/blob/main/examples/clientgo/demo.go#L42-L45

改动代码非常小,仅需要获取到对应集群的config信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
config, err := rest.InClusterConfig()
if err != nil {
klog.Error(err)
os.Exit(1)
}

// This is the ONLY place you need to wrap for Clusternet
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
return clientgo.NewClusternetTransport(config.Host, rt)
})

// now we could create and visit all the resources
client := kubernetes.NewForConfigOrDie(config)
_, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "demo",
},
}, metav1.CreateOptions{})

应用发布

image.png
在主集群提交如下的yaml文件

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
apiVersion: apps.clusternet.io/v1alpha1
kind: Subscription
metadata:
name: app-demo
namespace: default
spec:
subscribers: # defines the clusters to be distributed to
- clusterAffinity:
matchLabels:
clusters.clusternet.io/cluster-id: dc91021d-2361-4f6d-a404-7c33b9e01118 # PLEASE UPDATE THIS CLUSTER-ID TO YOURS!!!
feeds: # defines all the resources to be deployed with
- apiVersion: apps.clusternet.io/v1alpha1
kind: HelmChart
name: mysql
namespace: default
- apiVersion: v1
kind: Namespace
name: foo
- apiVersion: apps/v1
kind: Service
name: my-nginx-svc
namespace: foo
- apiVersion: apps/v1
kind: Deployment
name: my-nginx
namespace: foo

实现分析

在主集群实现了两个aggregated apiservice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
annotations:
meta.helm.sh/release-name: clusternet-hub
meta.helm.sh/release-namespace: clusternet-system
labels:
app.kubernetes.io/instance: clusternet-hub
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: clusternet-hub
helm.sh/chart: clusternet-hub-0.2.1
name: v1alpha1.proxies.clusternet.io
spec:
group: proxies.clusternet.io
groupPriorityMinimum: 1000
insecureSkipTLSVerify: true
service:
name: clusternet-hub
namespace: clusternet-system
port: 443
version: v1alpha1
versionPriority: 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
annotations:
meta.helm.sh/release-name: clusternet-hub
meta.helm.sh/release-namespace: clusternet-system
labels:
app.kubernetes.io/instance: clusternet-hub
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: clusternet-hub
helm.sh/chart: clusternet-hub-0.2.1
name: v1alpha1.shadow
spec:
group: shadow
groupPriorityMinimum: 1
insecureSkipTLSVerify: true
service:
name: clusternet-hub
namespace: clusternet-system
port: 443
version: v1alpha1
versionPriority: 1

相关链接