404频道

学习笔记

ackdistro

阿里云的k8s发行版,跟阿里云的ack采用了相同的源码。该项目采用sealer来部署k8s集群,并通过sealer支持k8s集群的扩容、缩容节点等运维操作。该项目并没有将k8s相关的源码开源,而主要维护了安装k8s集群需要的yaml文件、helm chart。

ack的k8s发行版比较简洁,并没有公有云ack的丰富的组件。除了k8s原生的几个组件外,网络插件集成了hybridnet,存储插件集成了open-local

hybridnet

容器网络插件,支持underlay网络和overlay网络,且可以支持一个k8s集群内的网络同时支持overlay网络和underlay网络。underlay网络模式下,可以支持vlan网络,也可以支持bgp网络。

image-syncer

镜像仓库同步工具,用于两个镜像仓库之间的数据同步。在公有云的产品ack one中有所应用。

kube-eventer

![https://user-images.githubusercontent.com/8912557/117400612-97cf3a00-af35-11eb-90b9-f5dc8e8117b5.png](https://kuring.oss-cn-beijing.aliyuncs.com/common/sealer.png)

k8s的Event会存放在etcd中,并跟对象关联。Event有一定的有效时常,默认为1小时。业界通常做法是将event存放在单独的etcd集群中,并将event的生命周期设长。对于异常类型的Event,是一种非常理想的告警机制。

kube-eventer组件可以将k8s的event对象收集起来,可以发送到钉钉、kafka等数据sink端。

kubernetes-cronhpa-controller

k8s提供了hpa机制,可以针对pod的监控信息对pod进行扩缩容。该组件提供了定期扩缩容的机制,可以定期对pod的数量进行设置。

相关资料:容器定时伸缩(CronHPA)

log-pilot

针对容器类型的服务研发的日志收集agent,可以在k8s pod上通过简单配置环境变量的方式即可采集日志,使用起来非常简洁。

open-local

k8s对于本地磁盘设备的使用相对较弱,提供了emptyDir、hostPath和local pv的能力来使用本地磁盘设备,但这些功能并没有使用到k8s的动态创建pv的功能,即在pod在使用pvc之前,pv必须实现要创建出来。

该项目提供了本地存储设备的动态供给能力,可以将本地的一块完整磁盘作为一个pv来动态创建。也可以将本地的磁盘切分成多块,通过lvm的方式来分配本地的不同pod来使用,以满足磁盘的共享,同时又有完整的磁盘quota能力。

相关资料:LVM数据卷

sealer

https://user-images.githubusercontent.com/8912557/117400612-97cf3a00-af35-11eb-90b9-f5dc8e8117b5.png

当前的应用发布经历了三个阶段:

  • 阶段一 裸部署在物理机或者vm上。直接裸部署在机器上的进程,存在操作系统、软件包的依赖问题,比如要部署一个python应用,那么需要机器上必须要包含对应版本的python运行环境以及应用依赖的所有包。
  • 阶段二 通过镜像的方式部署在宿主机上。docker通过镜像的方式将应用以及依赖打包到一起,解决了单个应用的依赖问题。
  • 阶段三 通过k8s的方式来标准化部署。在k8s时代,可以将应用直接部署到k8s集群上,将应用的发布标准化,实现应用的跨机器部署。

在阶段三中,应用发布到k8s集群后,应用会对k8s集群有依赖,比如k8s管控组件的配置、使用的网络插件、应用的部署yaml文件,对镜像仓库和dockerd的配置也有所依赖。当前绝大多数应用发布是跟k8s集群部署完全独立的,即先部署k8s集群,然后再部署应用,跟阶段一的发布单机应用模式比较类似,先安装python运行环境,然后再启动应用。

sealer项目是个非常有意思的开源项目,旨在解决k8s集群连同应用发布的自动化问题,可以实现类似docker镜像的方式将整个k8s集群连同应用一起打包成集群镜像,有了集群镜像后既可以标准化的发布到应用到各个地方。sealer深受docker的启发,提出的很多概念跟docker非常类似,支持docker常见的子命令run、inspect、save、load、build、login、push、pull等。

  • Kubefile概念跟Dockerfile非常类似,且可以执行sealer build命令打包成集群镜像,语法也类似于Dockerfile。
  • CloudImage:集群镜像,将Kubefile打包后的产物,类比与dockerimage。基础集群镜像通常为裸k8s集群,跟docker基础镜像为裸操作系统一致。
  • Clusterfile:要想运行CloudImage,需要配合Clusterfile文件来启动,类似于Docker Compose。Clusterfile跟Docker Compose一致,并不是必须的,也可以通过sealer run的方式来启动集群镜像。

sealer要实现上述功能需要实现将k8s集群中的所有用到镜像全部打包到一个集群镜像中。

相关资料:集群镜像:实现高效的分布式应用交付

k8s虽然已经发展了多个版本,但在多region和多zone的场景下支持还是相对比较弱的,且很多的特性在alpha版本就已经废弃,说明k8s官方对于region和zone方面的支持情况有很大的不确定性。业界支持多reigon和容灾的特性更多是从上层的应用层来解决。本文主要是介绍k8s自身以及社区在region和zone方面的支持情况。

k8s标签

https://kubernetes.io/docs/reference/labels-annotations-taints/#topologykubernetesiozone
官方推荐的跟region和zone有关的标签:

  • failure-domain.beta.kubernetes.io/region:1.17版本已经废弃,被topology.kubernetes.io/region取代
  • failure-domain.beta.kubernetes.io/zone:1.17版本已经废弃,被topology.kubernetes.io/zone取代
  • topology.kubernetes.io/region:1.17版本开始支持
  • topology.kubernetes.io/zone:1.17版本开始支持

服务拓扑ServiceTopology

支持版本:在1.17版本引入,在1.21版本废弃

该特性在Service对象上增加了spec.topologyKeys字段,表示访问该Service的流量优先选用的拓扑域列表。访问Service流量的具体过程如下:

  1. 当访问该Service时,一定是从某个k8s的node节点上发起,会查看当前node的label topology.kubernetes.io/zone对应的value,如果发现有endpoint所在的node的lable topology.kubernetes.io/zone对应的value相同,那么会将流量转发到该拓扑域的endpoint上。
  2. 如果没有找到topology.kubernetes.io/zone相同拓扑域的endpoint,则尝试找topology.kubernetes.io/region相同拓扑域的endpoint。
  3. 如果没有找到任何拓扑域的endpoint,那么该service会转发失败。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    apiVersion: v1
    kind: Service
    metadata:
    Name: my-app-web
    spec:
    selector:
    app: my-app
    ports:
    - protocol: TCP
    port: 80
    targetPort: 8080
    topologyKeys:
    - "topology.kubernetes.io/zone"
    - "topology.kubernetes.io/region"
    在底层实现上,使用了1.16版本新引入的alpha版本特性 EndpointSlice,该特性主要是用来解决Endpoints对象过多时带来的性能问题,从而将Endpoints拆分为多个组,Service Topology特性恰好可以借助该特性来实现,本质上也是为了将Endpoints进行拆分。k8s会根据Service的topologyKeys来将Service拆分为多个EndpointSlice对象,kube-proxy根据EndpointSlice会将Service流量进行拆分。

拓扑感知提示Topology Aware Hints

该特性在1.21版本引入,在1.23版本变为beta版本,用来取代Topology Key功能。
该特性会启用两个组件:EndpointSlice控制器和kube-proxy。

在Service Topology功能中,需要给Service来指定topologyKeys字段。该特性会更自动化一些,只需要在Service上增加annotation service.kubernetes.io/topology-aware-hints:auto,EndpointSlice控制器会watch到Service,发现开启了拓扑感知功能,会自动向endpoints的hints中增加forZones字段,该字段的value会根据endpoint所在node的topology.kubernetes.io/zone来决定。

值得一提的是,当前的hints中并没有包含forRegions的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: example-hints
labels:
kubernetes.io/service-name: example-svc
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.1.2.3"
conditions:
ready: true
hostname: pod-1
zone: zone-a
hints:
forZones:
- name: "zone-a"

参考

查看磁盘是否为ssd

如果其中的rota值为1,说明为hdd磁盘;如果rota值为0,说明为ssd。

1
2
3
4
5
# lsblk -o name,size,type,rota,mountpoint
NAME SIZE TYPE ROTA MOUNTPOINT
vdc 20G disk 1 /var/lib/kubelet/pods/eea3a54c-b211-4ee3-bcbe-70ba3fe84c05/volumes/kubernetes.io~csi/d-t4n36xdqey47v9e0ej8r/mount
vda 120G disk 1
└─vda1 120G part 1 /

但该规则在很多虚拟机的场景下并不成立,即使虚拟机的磁盘为ssd,但rota值仍然为1。可以通过修改rota的值的方式来标记磁盘的类型:echo ‘0’> /sys/block/vdd/queue/rotational

iostat

只能观察磁盘整体性能,不能看到进程级别的

  • iops = r/s+w/s
  • 吞吐量 = rkB/s + wkB/s
  • 响应时间 = r_await + w_await

iostat -xm $interval: interval为秒

1
2
3
4
5
6
7
8
9
$ iostat -xm 1
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-52-sh-100-I03.yidian.com) 12/11/2016 _x86_64_ (32 CPU)

avg-cpu: %user %nice %system %iowait %steal %idle
4.99 0.00 1.46 0.01 0.00 93.54

Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 0.26 0.02 1.62 0.00 0.02 29.31 0.00 0.89 3.37 0.87 0.05 0.01
sdb 0.00 0.81 10.23 67.59 1.19 29.07 796.57 0.01 0.10 0.85 2.85 0.19 1.46
  • r/s: 每秒发送给磁盘的读请求数
  • w/s: 每秒发送给磁盘的写请求数
  • rMB/s: 每秒从磁盘读取的数据量
  • wMB/s: 每秒向磁盘写入的数据量
  • %iowait:cpu等待磁盘时间
  • %util:表示磁盘使用率,该值较大说明磁盘性能存在问题,io队列不为空的时间。由于存在并行io,100%不代表磁盘io饱和
  • r_await:读请求处理完成时间,包括队列中的等待时间和设备实际处理时间,单位为毫秒
  • w_await:写请求处理完成时间,包括队列中的等待时间和设备实际处理时间,单位为毫秒
  • svctm: 瓶颈每次io操作的时间,单位为毫秒,可以反映出磁盘的瓶颈
  • avgrq-sz:平均每次请求的数据大小
  • avgqu-sz:平均请求io队列长度
  • svctm:处理io请求所需要的平均时间,不包括等待时间,单位为毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ iostat -d -x 1
Linux 3.10.0-327.10.1.el7.x86_64 (103-17-42-sh-100-i08.yidian.com) 01/19/2019 _x86_64_ (24 CPU)

Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 7.58 0.31 266.07 9.54 888.01 6.74 0.03 0.12 8.84 0.11 0.03 0.90
sdb 0.00 0.67 4.39 40.68 386.20 9277.38 428.79 0.03 0.64 3.10 0.37 0.30 1.33
sdd 0.00 0.82 5.08 66.78 451.99 15587.89 446.42 0.02 0.30 3.50 0.05 0.28 2.01
sde 0.00 0.67 4.14 53.77 312.74 12121.84 429.43 0.00 0.03 4.00 1.44 0.30 1.72
sdc 0.00 0.50 0.05 27.47 2.74 6098.56 443.28 0.03 0.95 12.42 0.93 0.21 0.58
sdf 0.00 0.47 1.07 34.58 57.05 7504.68 424.24 0.00 0.05 2.60 2.63 0.25 0.88
sdg 0.00 0.72 0.14 63.87 13.79 14876.74 465.22 0.03 0.42 10.57 0.40 0.19 1.24
sdj 0.00 0.79 1.85 37.81 206.01 7881.89 407.90 0.03 0.67 2.27 0.59 0.36 1.42
sdi 0.00 0.65 0.29 59.54 22.01 13501.71 452.05 0.01 0.25 5.27 0.22 0.19 1.14
sdk 0.00 0.50 0.30 29.46 16.74 6330.68 426.57 0.05 1.58 4.03 1.55 0.24 0.70
sdm 0.00 0.63 0.73 55.70 63.73 13008.16 463.34 0.02 0.35 5.98 0.28 0.23 1.29
sdh 0.00 0.47 0.06 15.68 5.16 2970.09 378.01 0.03 1.82 7.22 1.80 0.27 0.42
sdl 0.00 0.56 1.32 38.14 81.04 8945.16 457.50 0.09 2.22 4.30 2.15 0.19 0.76
  • wrqm: 每秒合并的写请求数
  • rrqm:每秒合并的读请求数

pidstat

如果要想查看进程级别的磁盘 io 使用情况,可以使用 pidstat -d 指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ pidstat -d 1
Linux 3.10.0-1160.88.1.el7.x86_64 (izbp1feliilr5yri84y6saz) 05/08/2023 _x86_64_ (16 CPU)

09:44:24 AM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
09:44:25 AM 0 371 0.00 184.91 0.00 jbd2/vda1-8
09:44:25 AM 0 1395 0.00 22.64 7.55 rsyslogd
09:44:25 AM 0 19053 0.00 3.77 0.00 jbd2/dm-0-8
09:44:25 AM 1000 25479 0.00 3.77 0.00 org_start
09:44:25 AM 1000 25754 0.00 3.77 0.00 org_start
09:44:25 AM 0 38784 0.00 505.66 0.00 systemd-journal
09:44:25 AM 0 40474 0.00 143.40 0.00 jbd2/dm-1-8
09:44:25 AM 0 41130 0.00 11.32 0.00 jbd2/dm-2-8
09:44:25 AM 1000 41675 0.00 3.77 0.00 start
09:44:25 AM 0 41730 0.00 3.77 0.00 org_start

iotop

该命令可以查看所有进程读写 io 情况,该命令通常 os 不会默认安装。在 CentOS 下可以执行 yum install iotop 命令安装。

本文记录常用的kubectl命令,不定期更新。

1. 统计k8s node上的污点信息

1
kubectl get nodes -o=custom-columns=NodeName:.metadata.name,TaintKey:.spec.taints[*].key,TaintValue:.spec.taints[*].value,TaintEffect:.spec.taints[*].effect

2. 查看不ready的pod

1
kubectl get pod --all-namespaces -o wide -w | grep -vE "Com|NAME|Running|1/1|2/2|3/3|4/4"

3. pod按照重启次数排序

1
2
3
kubectl get pods -A  --sort-by='.status.containerStatuses[0].restartCount' | tail

kubectl get pod -A --no-headers | sort -k5 -nr | head

4. kubectl proxy命令代理k8s apiserver

该命令经常用在开发的场景下,用来测试k8s api的结果。执行如下命令即可在本地127.0.0.1开启10999端口。

1
kubectl proxy --port=10999

在本地即可通过curl的方式来访问k8s的apiserver,而无需考虑鉴权问题。例如,curl http://127.0.0.1:10999/apis/batch/v1,即可直接返回结果。

5. --raw命令

该命令经常用在开发的场景下,用来测试k8s api的结果。执行如下的命令,即可返回json格式的结果。

1
kubectl get --raw /apis/batch/v1

6. 查看集群中的pod的request和limit情况

1
kubectl get pod -n kube-system  -o=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace,PHASE:.status.phase,Request-cpu:.spec.containers\[0\].resources.requests.cpu,Request-memory:.spec.containers\[0\].resources.requests.memory,Limit-cpu:.spec.containers\[0\].resources.limits.cpu,Limit-memory:.spec.containers\[0\].resources.limits.memory

得到的效果如下:

1
2
3
4
5
NAME                                              NAMESPACE     PHASE       Request-cpu   Request-memory   Limit-cpu   Limit-memory
cleanup-for-np-processor-9pjkm kube-system Succeeded <none> <none> <none> <none>
coredns-6c6664b94-7rnm8 kube-system Running 100m 70Mi <none> 170Mi
coredns-6c6664b94-djxch kube-system Failed 100m 70Mi <none> 170Mi
coredns-6c6664b94-khvrb kube-system Running 100m 70Mi <none> 170Mi

7. 修改对象的status

因为status资源实际上为对象的subResource资源,实际上无法通过kubectl来完成,但该操作还是放到了该文档中。

下述命令的需求为修改service的status

1
2
3
4
APISERVER=https://kube-cn-nb-nbsjzxpoc-d01-a.intra.nbsjzx.ali.com:6443
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1jazdkciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjhkZWE4MWQ4LTU2YTgtMTFlYy05MDMyLTgwNjE1ZjA4NDI0YSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.O25G_MSmKRU_pIPO_9tFqDvbZm9SOM_Mix7jCJeFiZHzLiSc7n5RanP3QoEldR1IcFN4AZXzlzI1Rb0GyFQH7XmS1eLESMbKnrTR3N5s3wlRp-D5QT0c_RAVAiLKrP7KsSKNcLjQkIO8-Csf_kmizIh6fP0-b7Mp90cw0J8oSlM-ScEeUEksQyXvyisVN9qvvTIkmbsgh7pX5IFcB4mGbvV9loOUC3-LdiiaCkMJzMisEeSJRaajmLlwpWXtb2BSi9HmBMktUE9IVB8ryZ06wbPRianbjoBwtAhcXuRyj1LaEog3aJHsyMA_DOZJtvjYis60AIRZ1iBnc-gZtEFCxw
obj=nginx-ingress-lb
curl -XPATCH -H "Accept: application/json" -H "Content-Type: application/merge-patch+json" --header "Authorization: Bearer $TOKEN" --insecure -d '{"status": {"loadBalancer": {"ingress": [{"ip": "10.209.97.170"}]}}}' $APISERVER/api/v1/namespaces/acs-system/services/nginx-ingress-lb/status

APISERVER变量可以通过kubeconfig文件获取到。

TOKEN变量可以直接使用kube-system下的admin账号。

  1. 执行 kubectl get secrets -n kube-system | grep admin 获取到sa对应的secret
  2. 执行 kubectl get secrets -n kube-system xxxx -o yaml 获取到base64之后的token
  3. 执行 echo "xxxx" | base64 -d 即可获取到对应的token

obj变量为要修改的service对象名称。

8. 查看 secret 内容

1
kubectl get secret -n ark-system ark.cmdb.https.origin.tls -o jsonpath='{.data.ca\.pem}' | base64 -d

9. 修改 secret 或 cm 的内容

很多场景下使用 kubectl edit 修改不能完全满足需求,比如某个 key 对应的 value 非常长且包含空格,很难直接编辑。可以通过导出 key 对应的 value 到文件,然后再重新 apply 的方式合入。

导出 configmap 中特定的 key:

1
kubectl get cm -n kube-system  networkpolicy-config -o jsonpath='{.data.config\.yaml}'

修改完成后,将文件重新 apply cm

1
kubectl create --save-config cm  networkpolicy-config -n kube-system --from-file /tmp/config.yaml -o yaml --dry-run | kubectl apply -f -

9. 删除所有 pod(或特定状态 pod)

1
kubectl get pods --all-namespaces -o wide --no-headers | grep -v Running | awk '{print $1 " " $2}' | while read AA BB; do kubectl get pod -n $AA $BB --no-headers; done

10. kubectl debug

常用于网络问题排查,其中镜像 netshoot 中包含了丰富的网络命令。

1
kubectl exec -it nginx-statefulset-0 bash

进入到 k8s node 网络,其中 ${node} 为 k8s 的 node 名字。

1
kubectl debug node/${node} -it --image=nicolaka/netshoot

11. kubectl patch

1
kubectl patch -p '{"spec":{"filed1": "value1"}}' --type=merge xxx -n yyy

12. 查询对象的 labels

1
2
3
4
5
6
7
# 查看所有 k8s 节点及其 label
kubectl get nodes -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.topology\.kubernetes\.io/zone}{"\n"}{end}'
# 查看单个 k8s 节点的 label
kubectl get node c25e04016.cloud.e04.amtest130 -o jsonpath="{.metadata.labels['topology\.kubernetes\.io/zone']}"

# 获取没有污点的节点对应的 label machine-type
kubectl get nodes -o json | jq -r '.items[] | select(.spec.taints | not) | [.metadata.name, .metadata.labels["machine-type"]] | @tsv'

ipv6在k8s中的支持情况

ipv6特性在k8s中作为一个特定的feature IPv6DualStack来管理。在1.15版本到1.20版本该feature为alpha版本,默认不开启。从1.21版本开始,该feature为beta版本,默认开启,支持pod和service网络的双栈。1.23版本变为稳定版本。

配置双栈

alpha版本需要通过kube-apiserver、kube-controller-manager、kubelet和kube-proxy的–feature-gates=”IPv6DualStack=true”命令来开启该特性,beta版本该特性默认开启。

kube-apiserver:

  • --service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>

kube-controller-manager:

  • --cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
  • --service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
  • --node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6 对于 IPv4 默认为 /24,对于 IPv6 默认为 /64

kube-proxy:

  • --cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>

使用kind创建k8s集群

由于ipv6的环境并不容易找到,可以使用kind快速在本地拉起一个k8s的单集群环境,同时kind支持ipv6的配置。

检查当前系统是否支持ipv6,如果输出结果为0,说明当前系统支持ipv6.

1
2
$ sysctl -a | grep net.ipv6.conf.all.disable_ipv6
net.ipv6.conf.all.disable_ipv6 = 0

创建ipv6单栈k8s集群执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
cat > kind-ipv6.conf <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: ipv6-only
networking:
ipFamily: ipv6
# networking:
# apiServerAddress: "127.0.0.1"
# apiServerPort: 6443
EOF
kind create cluster --config kind-ipv6.conf

创建ipv4/ipv6双栈集群执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
cat > kind-dual-stack.conf <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: dual-stack
networking:
ipFamily: dual
# networking:
# apiServerAddress: "127.0.0.1"
# apiServerPort: 6443
EOF
kind create cluster --config kind-dual-stack.conf

service网络

spec定义

k8s的service网络为了支持双栈,新增加了几个字段。

.spec.ipFamilies用来设置地址族,service一旦场景后该值不可认为修改,但可以通过修改.spec.ipFamilyPolicy来简介修改该字段的值。为数组格式,支持如下值:

  • [“IPv4”]
  • [“IPv6”]
  • [“IPv4”,”IPv6”] (双栈)
  • [“IPv6”,”IPv4”] (双栈)

上述数组中的第一个元素会决定.spec.ClusterIP中的字段。

.spec.ClusterIPs:由于.spec.ClusterIP的value为字符串,不支持同时设置ipv4和ipv6两个ip地址。因此又扩展出来了一个.spec.ClusterIPs字段,该字段的value为宿主元祖。在Headless类型的Service情况下,该字段的值为None。

.spec.ClusterIP:该字段的值跟.spec.ClusterIPs中的第一个元素的值保持一致。

.spec.ipFamilyPolicy支持如下值:

  • SingleStack:默认值。会使用.spec.ipFamilies数组中配置的第一个协议来分配cluster ip。如果没有指定.spec.ipFamilies,会使用service-cluster-ip-range配置中第一个cidr中来配置地址。
  • PreferDualStack:如果 .spec.ipFamilies 没有设置,使用 k8s 集群默认的 ipFamily。
  • RequireDualStack:同时分配ipv4和ipv6地址。.spec.ClusterIP的值会从.spec.ClusterIPs选择第一个元素。

service配置场景

先创建如下的deployment,以便于后面试验的service可以关联到pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: MyApp
spec:
replicas: 3
selector:
matchLabels:
app: MyApp
template:
metadata:
labels:
app: MyApp
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80

未指定任何协议栈信息

在没有指定协议栈信息的Service,创建出来的service .spec.ipFamilyPolicy为SingleStack。同时会使用service-cluster-ip-range配置中第一个cidr中来配置地址。如果第一个cidr为ipv6,则service分配的clusterip为ipv6地址。创建如下的service

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Service
metadata:
name: my-service
labels:
app: MyApp
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80

会生成如下的service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: v1
kind: Service
metadata:
labels:
app: MyApp
name: my-service
namespace: default
spec:
clusterIP: 10.96.80.114
clusterIPs:
- 10.96.80.114
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: MyApp
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

指定.spec.ipFamilyPolicy为PreferDualStack

创建如下的service

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: my-service-2
labels:
app: MyApp
spec:
ipFamilyPolicy: PreferDualStack
selector:
app: MyApp
ports:
- protocol: TCP
port: 80

提交到环境后会生成如下的service,可以看到.spec.clusterIPs中的ip地址跟ipFamilies中的顺序一致。

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: Service
metadata:
labels:
app: MyApp
name: my-service-2
namespace: default
spec:
clusterIP: 10.96.221.70
clusterIPs:
- 10.96.221.70
- fd00:10:96::7d1
ipFamilies:
- IPv4
- IPv6
ipFamilyPolicy: PreferDualStack
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: MyApp
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

查看Endpoints,可以看到subsets中的地址为pod的ipv4协议地址。Endpoints中的地址跟service的.spec.ipFamilies数组中的第一个协议的值保持一致。

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
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: MyApp
name: my-service-2
namespace: default
subsets:
- addresses:
- ip: 10.244.0.5
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-vrfps
namespace: default
resourceVersion: "16875"
uid: 30a1d787-f799-4250-8c56-c96564ca9239
- ip: 10.244.0.6
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-wgz72
namespace: default
resourceVersion: "16917"
uid: 8166d43e-2702-45c6-839e-b3005f44f647
- ip: 10.244.0.7
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-x4lt5
namespace: default
resourceVersion: "16896"
uid: f9c2968f-ca59-4ba9-a69f-358c202a964b
ports:
- port: 80
protocol: TCP

接下来指定.spec.ipFamilies的顺序再看一下执行的结果,创建如下的service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: my-service-3
labels:
app: MyApp
spec:
ipFamilyPolicy: PreferDualStack
ipFamilies:
- IPv6
- IPv4
selector:
app: MyApp
ports:
- protocol: TCP
port: 80

在环境中生成的service如下,可以看到.spec.clusterIPs中的顺序第一个为ipv6地址,.spec.clusterIP同样为ipv6地址。

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: Service
metadata:
labels:
app: MyApp
name: my-service-3
namespace: default
spec:
clusterIP: fd00:10:96::c306
clusterIPs:
- fd00:10:96::c306
- 10.96.147.82
ipFamilies:
- IPv6
- IPv4
ipFamilyPolicy: PreferDualStack
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: MyApp
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

查看Endpoints,可以看到subsets中的地址为pod的ipv6协议地址。Endpoints中的地址跟service的.spec.ipFamilies数组中的第一个协议的值保持一致。

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
apiVersion: v1
kind: Endpoints
metadata:
labels:
app: MyApp
name: my-service-3
namespace: default
subsets:
- addresses:
- ip: fd00:10:244::5
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-vrfps
namespace: default
resourceVersion: "16875"
uid: 30a1d787-f799-4250-8c56-c96564ca9239
- ip: fd00:10:244::6
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-wgz72
namespace: default
resourceVersion: "16917"
uid: 8166d43e-2702-45c6-839e-b3005f44f647
- ip: fd00:10:244::7
nodeName: dual-stack-control-plane
targetRef:
kind: Pod
name: nginx-deployment-65b5dd4c68-x4lt5
namespace: default
resourceVersion: "16896"
uid: f9c2968f-ca59-4ba9-a69f-358c202a964b
ports:
- port: 80
protocol: TCP

单栈和双栈之间的切换

虽然.spec.ipFamilies字段不允许直接修改,但.spec.ipFamilyPolicy字段允许修改,但并不影响单栈和双栈之间的切换。
单栈变双栈只需要修改.spec.ipFamilyPolicy从SingleStack变为PreferDualStack或者RequireDualStack即可。
双栈同样可以变为单栈,只需要修改.spec.ipFamilyPolicy从PreferDualStack或者RequireDualStack变为SingleStack即可。此时.spec.ipFamilies会自动变更为一个元素,.spec.clusterIPs同样会变更为一个元素。

LoadBalancer类型的Service

对于LoadBalancer类型的Service,单栈的情况下,会在.status.loadBalancer.ingress设置vip地址。如果是ingress中的ip地址为双栈,此时应该是将双栈的vip地址同时写到.status.loadBalancer.ingress中,并且要保证其顺序跟serivce的.spec.ipFamilies中的顺序一致。

pod网络

pod网络要支持ipv6,需要容器网络插件的支持。为了支持ipv6特性,新增加了.status.podIPs字段,用来展示pod上分配的ipv4和ipv6的信息。.status.podIP字段的值跟.status.podIPs数组的第一个元素的值保持一致。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
apiVersion: v1
kind: Pod
metadata:
labels:
k8s-app: kube-dns
pod-template-hash: 558bd4d5db
name: coredns-558bd4d5db-b2zbj
namespace: kube-system
spec:
containers:
- args:
- -conf
- /etc/coredns/Corefile
image: k8s.gcr.io/coredns/coredns:v1.8.0
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 5
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
name: coredns
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 9153
name: metrics
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /ready
port: 8181
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_BIND_SERVICE
drop:
- all
readOnlyRootFilesystem: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/coredns
name: config-volume
readOnly: true
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-hgxnc
readOnly: true
dnsPolicy: Default
enableServiceLinks: true
nodeName: dual-stack-control-plane
nodeSelector:
kubernetes.io/os: linux
preemptionPolicy: PreemptLowerPriority
priority: 2000000000
priorityClassName: system-cluster-critical
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: coredns
serviceAccountName: coredns
terminationGracePeriodSeconds: 30
tolerations:
- key: CriticalAddonsOnly
operator: Exists
...
volumes:
- configMap:
defaultMode: 420
items:
- key: Corefile
path: Corefile
name: coredns
name: config-volume
- name: kube-api-access-hgxnc
projected:
defaultMode: 420
sources:
- serviceAccountToken:
expirationSeconds: 3607
path: token
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2022-01-16T12:51:24Z"
status: "True"
type: Initialized
...
containerStatuses:
- containerID: containerd://6da36ab908291ca1b4141a86d70f8c2bb150a933336d852bcabe2118aa1a3439
image: k8s.gcr.io/coredns/coredns:v1.8.0
imageID: sha256:296a6d5035e2d6919249e02709a488d680ddca91357602bd65e605eac967b899
lastState: {}
name: coredns
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2022-01-16T12:51:27Z"
hostIP: 172.18.0.2
phase: Running
podIP: 10.244.0.3
podIPs:
- ip: 10.244.0.3
- ip: fd00:10:244::3
qosClass: Burstable
startTime: "2022-01-16T12:51:24Z"

pod中要想获取到ipv4、ipv6地址,可以通过downward api的形式将.status.podIPs以环境变量的形式传递到容器中,在pod中通过环境变量获取到的格式为: 10.244.1.4,a00:100::4

k8s node

在宿主机上可以通过ip addr show eth0的方式来查看网卡上的ip地址。

1
2
3
4
5
6
7
8
9
# ip addr show eth0
23: eth0@if24: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fc00:f853:ccd:e793::2/64 scope global nodad
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe12:2/64 scope link
valid_lft forever preferred_lft forever

宿主机的网卡上有ipv6地址,k8s node上的.status.addresses中有所体现,type为InternalIP即包含了ipv4地址,又包含了ipv6地址,但此处并没有字段标识当前地址为ipv4,还是ipv6。

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
apiVersion: v1
kind: Node
metadata:
annotations:
kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock
node.alpha.kubernetes.io/ttl: "0"
volumes.kubernetes.io/controller-managed-attach-detach: "true"
labels:
beta.kubernetes.io/arch: amd64
beta.kubernetes.io/os: linux
kubernetes.io/arch: amd64
kubernetes.io/hostname: dual-stack-control-plane
kubernetes.io/os: linux
node-role.kubernetes.io/control-plane: ""
node-role.kubernetes.io/master: ""
node.kubernetes.io/exclude-from-external-load-balancers: ""
name: dual-stack-control-plane
spec:
podCIDR: 10.244.0.0/24
podCIDRs:
- 10.244.0.0/24
- fd00:10:244::/64
providerID: kind://docker/dual-stack/dual-stack-control-plane
status:
addresses:
- address: 172.18.0.2
type: InternalIP
- address: fc00:f853:ccd:e793::2
type: InternalIP
- address: dual-stack-control-plane
type: Hostname
allocatable:
cpu: "4"
ephemeral-storage: 41152812Ki
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: 15936568Ki
pods: "110"
capacity:
cpu: "4"
ephemeral-storage: 41152812Ki
hugepages-1Gi: "0"
hugepages-2Mi: "0"
memory: 15936568Ki
pods: "110"
conditions:
- lastHeartbeatTime: "2022-01-16T15:01:48Z"
lastTransitionTime: "2022-01-16T12:50:54Z"
message: kubelet has sufficient memory available
reason: KubeletHasSufficientMemory
status: "False"
type: MemoryPressure
...
daemonEndpoints:
kubeletEndpoint:
Port: 10250
images:
- names:
- k8s.gcr.io/kube-proxy:v1.21.1
sizeBytes: 132714699
...
nodeInfo:
architecture: amd64
bootID: 7f95abb9-7731-4a8c-9258-4a91cdcfb2ca
containerRuntimeVersion: containerd://1.5.2
kernelVersion: 4.18.0-305.an8.x86_64
kubeProxyVersion: v1.21.1
kubeletVersion: v1.21.1
machineID: 8f6a98bffc184893ab6bc260e705421b
operatingSystem: linux
osImage: Ubuntu 21.04
systemUUID: f7928fdb-32be-4b6e-8dfd-260b6820f067

Ingress

Ingress 可以通过开关 disable-ipv6 来控制是否开启 ipv6,默认 ipv6 开启。

在开启 ipv6 的情况下,如果 nginx ingress 的 pod 本身没有ipv6 的 ip地址,则在 nginx 的配置文件中并不会监听 ipv6 的端口号。

1
2
listen 80  ;
listen 443 ssl http2 ;

如果 nginx ingress 的 pod 本身包含 ipv6 地址,则 nginx 的配置文件如下:

1
2
3
4
listen 80  ;
listen [::]:80 ;
listen 443 ssl http2 ;
listen [::]:443 ssl http2 ;

参考资料:

引用


背景

Federation V1

Federation v1 在 K8s v1.3 左右就已经着手设计(Design Proposal),并在后面几个版本中发布了相关的组件与命令行工具(kubefed),用于帮助使用者快速建立联邦集群,并在 v1.6 时,进入了 Beta 阶段;但 Federation v1 在进入 Beta 后,就没有更进一步的发展,由于灵活性和 API 成熟度的问题,在 K8s v1.11 左右正式被弃用。

Federation V2架构

v2 版本利用 CRD 实现了整体功能,通过定义多种自定义资源(CR),从而省掉了 v1 中的 API Server;v2 版本由两个组件构成:

  • admission-webhook 提供了准入控制
  • controller-manager 处理自定义资源以及协调不同集群间的状态

image
image

代码行数在2万行左右。

术语

  • Host Cluster:主集群/父集群
  • Member Cluster:子集群/成员集群

CRD扩展

KubeFedCluster

子集群抽象,通过kubefedctl join命令创建。后续controller会使用该信息来访问子k8s集群。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
kind: KubeFedCluster
metadata:
name: kind-cluster1
namespace: kube-federation-system
spec:
apiEndpoint: https://172.21.115.165:6443
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeE1USXlOREUyTlRNME9Wb1hEVE14TVRJeU1qRTJOVE0wT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTmxjCnF3UHd5cHNFc2M0aXB3QnFuRndDN044RXo2Slhqajh3ZzRybnlXTDNSdlZ0cmVua1Nha1VyYlRjZWVIck9lQTUKWGlNUVo2T1FBY25tUGU0Q2NWSkFoL2ZLQzBkeU9uL0ZZeXgyQXppRjBCK1ZNaUFhK2dvME1VMmhMZ1R5eVFGdQpDbWFmUGtsNmJxZUFJNCtCajZJUWRqY3dVMHBjY3lrNGhSTUxnQmhnTUh4NWkzVkpQckQ2Y284dHcwVnllWncyCkdDUlh2ZzlSd0QweUs5eitOVS9LVS83QjBiMTBvekpNRlVJMktPZmI4N1RkQ0h2NmlBdlVRYVdKc1BqQ0M3MzQKcnBma1ZGZXB2S2liKy9lUVBFaDE4VE5qaitveEVGMnl0Vmo2ZWVKeFc3VVZrbit0T3BxdGpEeUtKVDllNnlwdAp6U1VDTnRZWTAzckdhNTlLczBVQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZOTnRQU3hsMlMxUldOb1BCeXNIcHFoRXJONlVNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFDME0vaktCL2dqcWRSNlRLNXJQVktSSFU2cy8xTTh2eTY2WFBiVEx4M0srazdicUFWdAoxVUFzcUZDZmNrVk91WHY3eFQ0ZHIzVzdtMHE1bDYzRGg3ZDJRZDNiQ00zY2FuclpNd01OM0lSMlpmYzR0VlBGCnRTMFFONElTa0hsYnBudXQxb0F3cy9CaXNwaXNRQ0VtbHF3Zy9xbmdPMStlWWRoWm5vRW40SEFDamF4Slc5MS8KNXlOR1pKRXdia2dYQTVCbSs3VEZRL2FiYnp5a1JvOWtTMnl5c29pMnVzcUg0ZnlVS0ZWK2RETnp3Ujh0ck16cgpjWkRBNHpaVFB1WGlYRkVsWlNRa2NJSGIyV0VsYmZNRGpKNjlyVG5wakJCOWNPQ25jaHVmK0xiOXQwN1lJQ01wCmNlK0prNVp2RElRUFlKK3hTeGdRaVJxMTFraWlKcFE4Wm1FWgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
proxyURL: ""
secretRef:
name: kind-cluster1-dbxns
status:
conditions:
- lastProbeTime: "2021-12-24T17:00:47Z"
lastTransitionTime: "2021-12-24T17:00:07Z"
message: /healthz responded with ok
reason: ClusterReady
status: "True"
type: Ready

Fedrated

发布应用到子集群时手工创建。每一个要被联邦管理的资源都会对应一个Fedrated类型的资源,比如ConfigMap对应的是FederatedConfigMap。FederatedConfigMap包含了三部分的信息:

  • template:联邦的资源详细spec信息,需要特别注意的是并没有包含metadata部分的信息。
  • placement:template中的spec信息要部署的k8s子集群信息
  • overrides:允许对部分k8s的部分资源进行自定义修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    apiVersion: types.kubefed.io/v1beta1
    kind: FederatedConfigMap
    metadata:
    name: test-configmap
    namespace: test-namespace
    spec:
    template:
    data:
    A: ala ma kota
    placement:
    clusters:
    - name: cluster2
    - name: cluster1
    overrides:
    - clusterName: cluster2
    clusterOverrides:
    - path: /data
    value:
    foo: bar

FederatedTypeConfig

通过kubefedctl enable 创建。定义了哪些k8s api资源需要联邦,下面的例子描述了k8s ConfigMap要被联邦资源FederatedConfigMap所管理。

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
apiVersion: core.kubefed.io/v1beta1
kind: FederatedTypeConfig
metadata:
annotations:
meta.helm.sh/release-name: kubefed
meta.helm.sh/release-namespace: kube-federation-system
finalizers:
- core.kubefed.io/federated-type-config
labels:
app.kubernetes.io/managed-by: Helm
name: configmaps
namespace: kube-federation-system
spec:
federatedType:
group: types.kubefed.io
kind: FederatedConfigMap
pluralName: federatedconfigmaps
scope: Namespaced
version: v1beta1
propagation: Enabled # 是否启用该联邦对象
targetType:
kind: ConfigMap
pluralName: configmaps
scope: Namespaced
version: v1
status:
observedGeneration: 1
propagationController: Running
statusController: NotRunning

ReplicaSchedulingPreference

用来管理相同名字的FederatedDeployment或FederatedReplicaset资源,保证所有子集群的副本数为spec.totalReplicas。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: scheduling.kubefed.io/v1alpha1
kind: ReplicaSchedulingPreference
metadata:
name: test-deployment
namespace: test-ns
spec:
targetKind: FederatedDeployment
totalReplicas: 9
clusters:
A:
minReplicas: 4
maxReplicas: 6
weight: 1
B:
minReplicas: 4
maxReplicas: 8
weight: 2

安装

使用kind来在本机创建多个k8s集群的方式测试。通过kind创建两个k8s集群 kind-cluster1和kind-cluster2。

安装kubefedctl

1
2
3
4
wget https://github.com/kubernetes-sigs/kubefed/releases/download/v0.9.0/kubefedctl-0.9.0-linux-amd64.tgz
tar zvxf kubefedctl-0.9.0-linux-amd64.tgz
chmod u+x kubefedctl
mv kubefedctl /usr/local/bin/

在第一个集群安装KubeFed,在第一个集群执行如下的命令

1
2
3
4
5
6
7
8
9
$ helm repo add kubefed-charts https://raw.githubusercontent.com/kubernetes-sigs/kubefed/master/charts
$ helm repo list
$ helm --namespace kube-federation-system upgrade -i kubefed kubefed-charts/kubefed --version=0.9.0 --create-namespace

# 会安装如下两个deployment,其中一个是controller,另外一个是webhook
$ kubectl get deploy -n kube-federation-system
NAME READY UP-TO-DATE AVAILABLE AGE
kubefed-admission-webhook 1/1 1 1 7m40s
kubefed-controller-manager 2/2 2 2 7m40s

子集群注册

将cluster1和cluster2集群加入到cluster1中

1
2
kubefedctl join kind-cluster1 --cluster-context kind-cluster1 --host-cluster-context kind-cluster1 --v=2
kubefedctl join kind-cluster2 --cluster-context kind-cluster2 --host-cluster-context kind-cluster1 --v=2

在第一个集群可以看到在kube-federation-system中创建出了两个新的kubefedcluster对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ kubectl -n kube-federation-system get kubefedcluster
NAME AGE READY
kind-cluster1 17s True
kind-cluster2 16s True

$ apiVersion: core.kubefed.io/v1beta1
kind: KubeFedCluster
metadata:
name: kind-cluster1
namespace: kube-federation-system
spec:
apiEndpoint: https://172.21.115.165:6443
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM1ekNDQWMrZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJeE1USXlOREUyTlRNME9Wb1hEVE14TVRJeU1qRTJOVE0wT1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTmxjCnF3UHd5cHNFc2M0aXB3QnFuRndDN044RXo2Slhqajh3ZzRybnlXTDNSdlZ0cmVua1Nha1VyYlRjZWVIck9lQTUKWGlNUVo2T1FBY25tUGU0Q2NWSkFoL2ZLQzBkeU9uL0ZZeXgyQXppRjBCK1ZNaUFhK2dvME1VMmhMZ1R5eVFGdQpDbWFmUGtsNmJxZUFJNCtCajZJUWRqY3dVMHBjY3lrNGhSTUxnQmhnTUh4NWkzVkpQckQ2Y284dHcwVnllWncyCkdDUlh2ZzlSd0QweUs5eitOVS9LVS83QjBiMTBvekpNRlVJMktPZmI4N1RkQ0h2NmlBdlVRYVdKc1BqQ0M3MzQKcnBma1ZGZXB2S2liKy9lUVBFaDE4VE5qaitveEVGMnl0Vmo2ZWVKeFc3VVZrbit0T3BxdGpEeUtKVDllNnlwdAp6U1VDTnRZWTAzckdhNTlLczBVQ0F3RUFBYU5DTUVBd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0hRWURWUjBPQkJZRUZOTnRQU3hsMlMxUldOb1BCeXNIcHFoRXJONlVNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFDME0vaktCL2dqcWRSNlRLNXJQVktSSFU2cy8xTTh2eTY2WFBiVEx4M0srazdicUFWdAoxVUFzcUZDZmNrVk91WHY3eFQ0ZHIzVzdtMHE1bDYzRGg3ZDJRZDNiQ00zY2FuclpNd01OM0lSMlpmYzR0VlBGCnRTMFFONElTa0hsYnBudXQxb0F3cy9CaXNwaXNRQ0VtbHF3Zy9xbmdPMStlWWRoWm5vRW40SEFDamF4Slc5MS8KNXlOR1pKRXdia2dYQTVCbSs3VEZRL2FiYnp5a1JvOWtTMnl5c29pMnVzcUg0ZnlVS0ZWK2RETnp3Ujh0ck16cgpjWkRBNHpaVFB1WGlYRkVsWlNRa2NJSGIyV0VsYmZNRGpKNjlyVG5wakJCOWNPQ25jaHVmK0xiOXQwN1lJQ01wCmNlK0prNVp2RElRUFlKK3hTeGdRaVJxMTFraWlKcFE4Wm1FWgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
proxyURL: ""
secretRef:
name: kind-cluster1-dbxns
status:
conditions:
- lastProbeTime: "2021-12-24T17:00:47Z"
lastTransitionTime: "2021-12-24T17:00:07Z"
message: /healthz responded with ok
reason: ClusterReady
status: "True"
type: Ready

要想将cluster2从主集群中移除,可以执行 kubefedctl unjoin kind-cluster2 –cluster-context kind-cluster2 –host-cluster-context kind-cluster1 –v=2

应用发布

集群联邦API

kubefed将资源分为普通k8s资源类型和联邦的资源类型。在默认的场景下,kubefed已经内置将很多资源类型做了集群联邦。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ k get FederatedTypeConfig -n kube-federation-system
NAME AGE
clusterrolebindings.rbac.authorization.k8s.io 19h
clusterroles.rbac.authorization.k8s.io 19h
configmaps 19h
deployments.apps 19h
ingresses.extensions 19h
jobs.batch 19h
namespaces 19h
replicasets.apps 19h
secrets 19h
serviceaccounts 19h
services 19h

将crd资源类型集群联邦,执行 kubefedctl enable 命令

1
2
3
4
$ kubefedctl enable customresourcedefinitions
I1224 20:32:54.537112 687543 util.go:141] Api resource found.
customresourcedefinition.apiextensions.k8s.io/federatedcustomresourcedefinitions.types.kubefed.io created
federatedtypeconfig.core.kubefed.io/customresourcedefinitions.apiextensions.k8s.io created in namespace kube-federation-system

可以看到会多出一个名字为federatedcustomresourcedefinitions.types.kubefed.io 的CRD 资源,同时会新创建一个FederatedTypeConfig类型的资源。当创建了FederatedTypeConfig后,就可以通过创建federatedcustomresourcedefinition类型的实例来向各个子集群发布CRD资源了。

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
$ k get crd federatedcustomresourcedefinitions.types.kubefed.io
NAME CREATED AT
federatedcustomresourcedefinitions.types.kubefed.io 2021-12-24T12:32:54Z

$ k get FederatedTypeConfig -n kube-federation-system customresourcedefinitions.apiextensions.k8s.io -o yaml
apiVersion: core.kubefed.io/v1beta1
kind: FederatedTypeConfig
metadata:
finalizers:
- core.kubefed.io/federated-type-config
name: customresourcedefinitions.apiextensions.k8s.io
namespace: kube-federation-system
spec:
federatedType:
group: types.kubefed.io
kind: FederatedCustomResourceDefinition
pluralName: federatedcustomresourcedefinitions
scope: Cluster
version: v1beta1
propagation: Enabled
targetType:
group: apiextensions.k8s.io
kind: CustomResourceDefinition
pluralName: customresourcedefinitions
scope: Cluster
version: v1
status:
observedGeneration: 1
propagationController: Running
statusController: NotRunning

在example/sample1下包含了很多例子,可以直接参考。接下来使用sample1中的例子进行试验。

在父集群创建namespace test-namespace

1
2
$ kubectl create -f namespace.yaml
namespace/test-namespace created

在主集群中创建federatednamespace资源

1
2
$ k apply -f federatednamespace.yaml
federatednamespace.types.kubefed.io/test-namespace created

在子集群中即可查询到新创建的namespace资源

1
2
3
$ k get ns test-namespace
NAME STATUS AGE
test-namespace Active 4m49s

其他的对象均可以按照跟上述namespace同样的方式来创建,比较特殊的对象为clusterrulebinding,该对象在默认情况下没有联邦api FederatedClusterRoleBinding,因此需要手工先创建FederatedClusterRoleBinding联邦api类型。

1
2
3
4
$ kubefedctl enable clusterrolebinding
I1225 01:46:42.779166 818254 util.go:141] Api resource found.
customresourcedefinition.apiextensions.k8s.io/federatedclusterrolebindings.types.kubefed.io created
federatedtypeconfig.core.kubefed.io/clusterrolebindings.rbac.authorization.k8s.io created in namespace kube-federation-system

在创建完成FederatedClusterRoleBinding联邦api类型后,即可以创建出FederatedClusterRoleBinding类型的对象。

子集群的个性化配置

通过Fedrated中的spec.overrides来完成,可以覆盖spec.template中的内容

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
apiVersion: types.kubefed.io/v1beta1
kind: FederatedDeployment
metadata:
name: test-deployment
namespace: test-namespace
spec:
template:
metadata:
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
placement:
clusters:
- name: cluster2
- name: cluster1
overrides:
- clusterName: cluster2
clusterOverrides:
- path: "/spec/replicas"
value: 5
- path: "/spec/template/spec/containers/0/image"
value: "nginx:1.17.0-alpine"
- path: "/metadata/annotations"
op: "add"
value:
foo: bar
- path: "/metadata/annotations/foo"
op: "remove"

多集群应用调度

发布应用到特定的子集群

在Fedrated的spec.placement.clusters中,定义了要发布到哪个子集群的信息。

1
2
3
4
placement:
clusters:
- name: cluster2
- name: cluster1

不过上述方法需要指定特定的集群,为了有更丰富的灵活性,kubefed还提供了label selector的机制,可以提供spec.placement.clusterSelector来指定一组集群。

1
2
3
4
5
6
spec:
placement:
clusters: []
clusterSelector:
matchLabels:
foo: bar

标签选择器通过跟kubefedclusters对象的label来进行匹配,可以执行下述命令给kubefedclusters来打标签。

1
kubectl label kubefedclusters -n kube-federation-system kind-cluster1 foo=bar

子集群差异化调度

上述调度功能被选择的子集群为平等关系,如果要想子集群能够有所差异,可以使用ReplicaSchedulingPreference来完成,目前仅支持deployment和replicaset两种类型的资源,还不支持statefulset。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: scheduling.kubefed.io/v1alpha1
kind: ReplicaSchedulingPreference
metadata:
name: test-deployment
namespace: test-ns
spec:
targetKind: FederatedDeployment
totalReplicas: 9
clusters:
A:
minReplicas: 4
maxReplicas: 6
weight: 1
B:
minReplicas: 4
maxReplicas: 8
weight: 2

spec.targetKind用来指定管理的类型,仅支持FederatedDeployment和FederatedReplicaset。
spec.totalReplicas用来指定所有子集群的副本数总和。优先级要高于FederatedDeployment和FederatedReplicaset的spec.template中指定的副本数。
spec.clusters用来指定每个子集群的最大、最小副本数和权重。
spec.rebalance自动调整整个集群中的副本数,比如一个集群中的pod挂掉后,可以将pod迁移到另外的集群中。

子集群之间的交互

缺点

  1. 父集群充当了所有集群的单一控制面
  2. 通过联邦CRD来管理资源,无法直接使用k8s原生的资源,集群间维护CRD版本和API版本一致性导致升级比较复杂。

参考资料

使用openssl创建自签名证书

创建CA

1
2
3
openssl genrsa -out root.key 4096
openssl req -new -x509 -days 1000 -key root.key -out root.crt
openssl x509 -text -in root.crt -noout

创建私钥和公钥文件

1
2
3
4
# 用来产生私钥文件server.key
openssl genrsa -out server.key 2048
# 产生公钥文件
openssl rsa -in server.key -pubout -out server.pem

创建签名请求

1
2
3
4
openssl req -new -key server.key -out server.csr

# 查看创建的签名请求
openssl req -noout -text -in server.csr

创建自签名证书
新建自签名证书的附加信息server.ext,内容如下

1
2
3
4
5
6
7
8
9
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
IP.2 = 127.0.0.1
IP.3 = 10.66.3.6

使用ca签发ssl证书,此时会产生server.crt文件,即为证书文件

1
2
3
openssl x509 -req -in server.csr -out server.crt -days 3650 \
-CAcreateserial -CA root.crt -CAkey root.key \
-CAserial serial -extfile server.ext

查看证书文件内容

1
openssl x509 -in server.crt   -noout -text

使用ca校验证书是否通过

1
openssl verify -CAfile root.crt server.crt

使用cfssl签发证书

cfssl是CloudFlare开源的一款tls工具,使用go语言编写,地址:https://github.com/cloudflare/cfssl。

安装

mac用户:brew install cfssl

linux用户可以直接下载二进制文件:https://github.com/cloudflare/cfssl/releases

签发证书

待补充

证书格式

证书按照格式可以分为二进制和文本文件两种格式。

二进制格式分为:

  1. .der或者.cer:用来存放证书信息,不包含私钥。

文本格式分为:

  1. .pem:存放证书或者私钥。一般是.key文件存放私钥信息。对于pem或者key文件,如果存在——BEGIN CERTIFICATE——,则说明这是一个证书文件。如果存在—–BEGIN RSA PRIVATE KEY—–,则说明这是一个私钥文件。
  2. *.key:用来存放私钥文件。
  3. *.crt:证书请求文件,格式的开头为:—–BEGIN CERTIFICATE REQUEST—–

证书格式的转换

将cert证书转换为pem格式

1
2
openssl rsa -in server.key -text > server-key.pem
openssl x509 -in server.crt -out server.pem

将pem格式转换为cert格式

证书查看

查看特定域名的证书内容:echo | openssl s_client -connect www.baidu.com:443 -servername www.baidu.com 2>/dev/null | openssl x509 -noout -text

获取特定域名的证书内容:openssl s_client -servername www.baidu.com -connect www.baidu.com:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'

在 k8s 中,证书通常存放在 secret 中,可以使用 kubeclt + openssl 命令查看证书内容:kubectl get secret -n test-cluster sharer -o jsonpath='{.data.tls\.crt}' | base64 --decode | openssl x509 -text -noout

证书的使用

curl命令关于证书的用法:

  • –cacert:指定ca来校验server端的证书合法性
  • –cert:指定客户端的证书文件,用在双向认证mTLS中
  • –key:私钥文件名,用在双向认证mTLS中

参考链接

简介

腾讯云开源的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

相关链接

经常有快速创建一个测试k8s集群的场景,为了能够快速完成,整理了如下的命令,即可在主机上快速启动一个k8s集群。部分命令需要外网访问,推荐直接使用海外的主机。

常用工具安装

1
2
3
4
5
6
7
8
9
yum install vim git make -y

# neovim
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux64.tar.gz
sudo rm -rf /opt/nvim
sudo tar -C /opt -xzf nvim-linux64.tar.gz
echo 'export PATH="$PATH:/opt/nvim-linux64/bin"' >>~/.bash_profile
source ~/.bash_profile
git clone https://github.com/LazyVim/starter ~/.config/nvim

安装docker

下面命令可以安装最新版本的docker-ce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
yum install -y yum-utils
yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
yum install docker-ce docker-ce-cli containerd.io -y
systemctl enable docker && systemctl start docker

安装特定版本的docker

如果要安装特定版本的docker-ce,可以使用如下方法。

使用如下命令查询yum源中的docker-ce版本

1
2
3
4
5
6
yum list docker-ce --showduplicates | sort -r
Last metadata expiration check: 0:00:27 ago on Sat 09 Apr 2022 12:39:09 AM CST.
docker-ce.x86_64 3:20.10.9-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.8-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.7-3.el8 docker-ce-stable
docker-ce.x86_64 3:20.10.6-3.el8 docker-ce-stable

选择特定版本的docker-ce和docker-ce-cli,执行如下命令

1
yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io

安装kubectl kind 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
29
30
31
32
33
# 安装kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/bin/
# 如果找不到rpm包,可以通过网络安全 rpm -ivh http://mirror.centos.org/centos/7/os/x86_64/Packages/bash-completion-2.1-8.el7.noarch.rpm
yum install -y bash-completion
echo -e '\n# kubectl' >> ~/.bash_profile
echo 'source <(kubectl completion bash)' >>~/.bash_profile
echo 'alias k=kubectl' >>~/.bash_profile
echo 'complete -F __start_kubectl k' >>~/.bash_profile
source ~/.bash_profile

# 安装helm
wget https://get.helm.sh/helm-v3.14.2-linux-amd64.tar.gz
tar zvxf helm-v3.14.2-linux-amd64.tar.gz
mv linux-amd64/helm /usr/bin/
rm -rf linux-amd64
rm -f helm-v3.14.2-linux-amd64.tar.gz

# 安装kubectx kubens
git clone https://github.com/ahmetb/kubectx /tmp/kubectx
cp /tmp/kubectx/kubens /usr/bin/kns
cp /tmp/kubectx/kubectx /usr/bin/kctx
wget https://github.com/junegunn/fzf/releases/download/0.29.0/fzf-0.29.0-linux_amd64.tar.gz -P /tmp
tar zvxf /tmp/fzf-0.29.0-llinux_amd64.tar.gz -C /tmp/
mv /tmp/fzf /usr/local/bin/

# 安装kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.18.0/kind-linux-amd64
chmod +x ./kind
mv ./kind /usr/local/bin/
echo -e "\n# kind" >> ~/.bash_profile
echo 'source <(kind completion bash)' >>~/.bash_profile

创建集群

其中将apiServerAddress指定为了本机,即创建出来的k8s集群仅允许本集群内访问。如果要是需要多个k8s集群之间的互访场景,由于kind拉起的k8s运行在docker容器中,而docker容器使用的是容器网络,此时如果设置apiserver地址为127.0.0.1,那么集群之间就没法直接通讯了,此时需要指定一个可以在docker容器中访问的宿主机ip地址。

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
if=eth0
ip=`ifconfig $if|grep inet|grep -v 127.0.0.1|grep -v inet6|awk '{print $2}'|tr -d "addr:"`
cat > kind.conf <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: kind
nodes:
- role: control-plane
# 如果需要 ingress,则需要指定该参数
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
# 指定 k8s 版本,默认不指定
# image: kindest/node:v1.23.17
- role: worker
- role: worker
- role: worker
networking:
apiServerAddress: "$ip"
apiServerPort: 6443
EOF
kind create cluster --config kind.conf

如果使用 nginx ingress,额外执行命令 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml

其他周边工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 安装kustomize
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
mv kustomize /usr/local/bin/

# 安装golang
wget https://go.dev/dl/go1.21.8.linux-amd64.tar.gz -P /opt
tar zvxf /opt/go1.21.8.linux-amd64.tar.gz -C /opt/
mkdir /opt/gopath
echo -e '\n# golang' >> ~/.bash_profile
echo 'export GOROOT=/opt/go' >> ~/.bash_profile
echo 'export GOPATH=/opt/gopath' >> ~/.bash_profile
echo 'export PATH=$PATH:$GOPATH/bin:$GOROOT/bin' >> ~/.bash_profile
source ~/.bash_profile

# 安装controller-gen,会将controller-gen命令安装到GOPATH/bin目录下
go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest

# 安装dlv工具
go install github.com/go-delve/delve/cmd/dlv@latest

安装krew

1
2
3
4
5
6
7
8
9
10
11
12
13
(
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 -e '\n# kubectl krew' >> ~/.bash_profile
echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bash_profile
source ~/.bash_profile

资料

概念

  1. 物理卷PV(Physical Volume):可以是整块物理磁盘或者物理磁盘上的分区
  2. 卷组VG(Volume Group):由一个或多个物理卷PV组成,可在卷组上创建一个或者多个LV,可以动态增加pv到卷组
  3. 逻辑卷LV(Logical Volume):类似于磁盘分区,建立在VG之上,在LV上可以创建文件系统 逻辑卷建立后可以动态的增加或缩小空间
  4. PE(Physical Extent): PV可被划分为PE的基本单元,具有唯一编号的PE是可以被LVM寻址的最小单元。PE的大小是可以配置的,默认为4MB。
  5. LE(Logical Extent): LV可被划分为LE的基本单元,LE跟PE是一对一的关系。

基本操作

lvm相关的目录如果没有按照,在centos下使用yum install lvm2进行安装。

初始磁盘状态如下,/dev/sda上有40g磁盘空间未分配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.8G 38G 7% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000

# fdisk -l

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a05f8

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 83886079 41942016 83 Linux

对磁盘的操作

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
42
43
44
45
46
47
48
49
50
# fdisk /dev/sda
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

# 创建主分区2,空间为1g,磁盘格式为lvm
Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p): p
Partition number (2-4, default 2): 2
First sector (83886080-167772159, default 83886080):
Using default value 83886080
Last sector, +sectors or +size{K,M,G} (83886080-167772159, default 167772159): +1G
Partition 2 of type Linux and of size 1 GiB is set

Command (m for help): t
Partition number (1,2, default 2): 2
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

# 创建主分区3,空间为5g,磁盘格式为lvm
Command (m for help): n
Partition type:
p primary (2 primary, 0 extended, 2 free)
e extended
Select (default p): p
Partition number (3,4, default 3): 3
First sector (85983232-167772159, default 85983232):
Using default value 85983232
Last sector, +sectors or +size{K,M,G} (85983232-167772159, default 167772159): +5G
Partition 3 of type Linux and of size 5 GiB is set

Command (m for help): t
Partition number (1-3, default 3): 3
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

# 保存上述操作
Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.

当前磁盘空间状态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# fdisk -l

Disk /dev/sda: 85.9 GB, 85899345920 bytes, 167772160 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a05f8

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 83886079 41942016 83 Linux
/dev/sda2 83886080 85983231 1048576 8e Linux LVM
/dev/sda3 85983232 96468991 5242880 8e Linux LVM

将上述两个lvm磁盘分区创建pv,使用pvremove /dev/sda2可以删除pv

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
# pvcreate /dev/sda2 /dev/sda3
Physical volume "/dev/sda2" successfully created.
Physical volume "/dev/sda3" successfully created.
[root@localhost vagrant]# pvdisplay
"/dev/sda2" is a new physical volume of "1.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sda2
VG Name
PV Size 1.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID MgarF2-Ka6D-blDi-8ecd-1SEU-y2GD-JtiK2c

"/dev/sda3" is a new physical volume of "5.00 GiB"
--- NEW Physical volume ---
PV Name /dev/sda3
VG Name
PV Size 5.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID ToKp2T-30mS-0P8c-ZrcB-2lO4-ayo9-62fuDx

接下来创建vg,vg的名字可以随便定义,并将创建的两个pv都添加到vg中,可以看到vg的空间为两个pv之和

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
# vgcreate vg1 /dev/sda1
# vgdisplay -v
--- Volume group ---
VG Name vg1
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 1
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 0
Open LV 0
Max PV 0
Cur PV 2
Act PV 2
VG Size 5.99 GiB
PE Size 4.00 MiB
Total PE 1534
Alloc PE / Size 0 / 0
Free PE / Size 1534 / 5.99 GiB
VG UUID XI8Biv-JtUv-tsur-wuvm-IJQz-HLZu-6a2u5G

--- Physical volumes ---
PV Name /dev/sda2
PV UUID MgarF2-Ka6D-blDi-8ecd-1SEU-y2GD-JtiK2c
PV Status allocatable
Total PE / Free PE 255 / 255

PV Name /dev/sda3
PV UUID ToKp2T-30mS-0P8c-ZrcB-2lO4-ayo9-62fuDx
PV Status allocatable
Total PE / Free PE 1279 / 1279

接下来创建lv,并从vg1中分配空间2g

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# lvcreate -L 2G -n lv1 vg1
Logical volume "lv1" created.

# lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID EesY4i-lSqY-ef1R-599C-XTrZ-IcVL-P7W46Q
LV Write Access read/write
LV Creation host, time localhost.localdomain, 2019-06-16 07:29:38 +0000
LV Status available
# open 0
LV Size 2.00 GiB
Current LE 512
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 8192
Block device 253:0

接下来给lv1格式化磁盘格式为ext4,并将磁盘挂载到/tmp/lvm目录下

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
# mkfs.ext4 /dev/vg1/lv1
mke2fs 1.42.9 (28-Dec-2013)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
131072 inodes, 524288 blocks
26214 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=536870912
16 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912

Allocating group tables: done
Writing inode tables: done
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done

# mkdir /tmp/lvm
[root@localhost vagrant]# mount /dev/vg1/lv1 /tmp/lvm
[root@localhost vagrant]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.9G 38G 8% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000
/dev/mapper/vg1-lv1 2.0G 6.0M 1.8G 1% /tmp/lvm

接下来对lv的空间从2G扩展到3G,此时通过df查看分区空间大小仍然为2g,需要执行resize2fs命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# lvextend -L +1G /dev/vg1/lv1
Size of logical volume vg1/lv1 changed from 2.00 GiB (512 extents) to 3.00 GiB (768 extents).
Logical volume vg1/lv1 successfully resized.

# resize2fs /dev/vg1/lv1
resize2fs 1.42.9 (28-Dec-2013)
Filesystem at /dev/vg1/lv1 is mounted on /tmp/lvm; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/vg1/lv1 is now 786432 blocks long.

# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 2.9G 38G 8% /
devtmpfs 236M 0 236M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
tmpfs 49M 0 49M 0% /run/user/1000
/dev/mapper/vg1-lv1 2.9G 6.0M 2.8G 1% /tmp/lvm

问题

试验时操作失误,出现了先格式化磁盘,后发现pv找不到对应设备。vg删除不成功。

正常删除vg的方式,此时lv会自动消失

1
2
vgreduce --removemissing vg1
vgremove vg1

lvremove操作执行的时候经常会出现提示“Logical volume xx contains a filesystem in use.”的情况,该问题一般是由于有其他进程在使用该文件系统导致的。网络上经常看到的是通过fuser或者lsof命令来查找使用方,但偶尔该命令会失效,尤其在本机上有容器的场景下。另外一个可行的办法是通过 grep -nr "/data" /proc/*/mount 命令,可以找到挂载该目录的所有进程,简单有效。

三种Logic Volume

LVM的机制可以类比于RAID,RAID一个核心的机制是性能和数据冗余,并提供了多种数据的冗余模块可供配置。lvm在性能和数据冗余方面支持如下三种Logic Volume: 线性逻辑卷、条带化逻辑卷和镜像逻辑卷。上述几种模式是在vg已经创建完成后创建lv的时候指定的模式,会影响到lv中的pe分配。

下面使用如下的机器配置来进行各个模式的介绍和功能测试。

1
2
3
4
5
6
7
8
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 40G 0 disk
└─vda1 253:1 0 40G 0 part /
vdb 253:16 0 100G 0 disk
vdc 253:32 0 200G 0 disk
vdd 253:48 0 300G 0 disk
vde 253:64 0 400G 0 disk

线性逻辑卷 Linear Logic Volume

当一个VG中有两个或者多个磁盘的时候,LV分配磁盘容量的时候是按照VG中的PV安装顺序分配的,即一个PV用完后才会分配下一块PV。也可以在创建LV的通过指定PV中的PE段来将数据分散到多个PV上。该模式也是LVM的默认模式。

使用/dev/vdb和/dev/vdc两块磁盘来进行测试,先创建对应的PV。

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
$ pvcreate /dev/vdb /dev/vdc
Physical volume "/dev/vdb" successfully created.
Physical volume "/dev/vdc" successfully created.

$ pvdisplay
"/dev/vdb" is a new physical volume of "100.00 GiB"
--- NEW Physical volume ---
PV Name /dev/vdb
VG Name
PV Size 100.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID 8NnlYc-4f3f-fkeW-a3l3-LXoC-9UEH-fvpb5V

"/dev/vdc" is a new physical volume of "200.00 GiB"
--- NEW Physical volume ---
PV Name /dev/vdc
VG Name
PV Size 200.00 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID I9ffpN-c1vc-PQOB-yKyd-MdzO-Ngff-6e116t

创建VG vg1,该容量为上面两个磁盘空间之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ vgcreate vg1 /dev/vdb /dev/vdc
Volume group "vg1" successfully created

$ vgdisplay
--- Volume group ---
VG Name vg1
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 1
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 0
Open LV 0
Max PV 0
Cur PV 2
Act PV 2
VG Size 299.99 GiB
PE Size 4.00 MiB
Total PE 76798
Alloc PE / Size 0 / 0
Free PE / Size 76798 / 299.99 GiB
VG UUID Gi0bJx-jqY8-YpSo-kB0l-9wdk-ZfCT-GpgFZY

从vg1中创建LV lv1,其大小为vg的全部大小

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
$ lvcreate --type linear -l 100%VG -n lv1 vg1
Logical volume "lv1" created.

$ lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID JQZ193-dz6A-I0Ue-rTKC-6XrQ-gb1F-Qy9kDl
LV Write Access read/write
LV Creation host, time iZt4nd6wiprf8foracovwqZ, 2022-01-08 23:28:45 +0800
LV Status available
# open 0
LV Size 299.99 GiB
Current LE 76798
Segments 2
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:0

$ lvs -o lv_name,lv_attr,lv_size,seg_pe_ranges
LV Attr LSize PE Ranges
lv1 -wi-a----- 299.99g /dev/vdc:0-51198
lv1 -wi-a----- 299.99g /dev/vdb:0-25598

条带化逻辑卷 Striped Logic Volume

类似于raid0模式,在该模式下,多块磁盘均会分配PE给LV。可以通过-i参数来指定可以使用VG中多少个PV。

优点:将数据的读写压力分散到了多个磁盘,可以提升读写性能。
缺点:一个磁盘损坏后会导致数据丢失。

但在使用striped模式时,需要注意:

  1. 如果PV来自于同一个磁盘的不同分区,会导致更多的随机读写,不仅不能提升磁盘性能,反而会导致性能下降。
  2. 如果VG中某一个PV过小,则无法将所有的PV平均使用起来,存在木桶效应。

使用--type striped来指定为striped模式,--stripes来指定需要使用的pv数量,--stripesize指定写足够数量的数据后再更换为另外一个pv。在下面的创建命令中,可以看到创建出来了12800个LE,PE是平均分配到了两个pv上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ lvcreate --type striped --stripes 2 --stripesize 32k -L 50G -n lv1 vg1
Logical volume "lv1" created.

$ lvs -o lv_name,lv_attr,lv_size,seg_pe_ranges
LV Attr LSize PE Ranges
lv1 -wi-a----- 50.00g /dev/vdb:0-6399 /dev/vdc:0-6399

$ lvdisplay
--- Logical volume ---
LV Path /dev/vg1/lv1
LV Name lv1
VG Name vg1
LV UUID FnOmCM-IkuE-3Rvv-fQqk-Cv1Q-lb8S-l5X7O3
LV Write Access read/write
LV Creation host, time iZt4nd6wiprf8foracovwqZ, 2022-01-09 00:07:08 +0800
LV Status available
# open 0
LV Size 50.00 GiB
Current LE 12800
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 252:0

如果指定的lv大小为250G,由于分配到两块磁盘上,由于最小的磁盘只有100G,100G * 2无法满足250G的磁盘大小需求,此时会报错。

1
2
lvcreate --type striped --stripes 2 --stripesize 32k -L 250G -n lv1 vg1
Insufficient suitable allocatable extents for logical volume lv1: 12802 more required

镜像逻辑卷 Mirror Logic Volume

类似于raid1。可以解决磁盘的单点问题,一块磁盘挂掉后不至于丢失数据。

通过使用--type mirror来指定为镜像模式,-m参数来指定冗余的数量。

常见问题

报错 “Device /dev/sdd excluded by a filter.”

当执行 pvcreate /dev/sdd 命令是报错信息如下:

1
Device /dev/sdd excluded by a filter.

检查下 /etc/lvm/lvm.conf 文件中的 filter 字段是否将磁盘过滤掉了。

执行 pvcreate /dev/sdd -vvv可以查看更详细的报错信息。

可以通过执行wipefs -a /dev/sdd 命令后再执行 pvcreate。

ref

0%