404频道

学习笔记

在k8s官方的Github kubernetes group下除了k8s的核心组件外,还有很多开源项目,本文用来简要分析这些开源项目的用途。

apimachinery

关于k8s的scheme等的sdk项目,代码跟kubernetes项目的如下路径保持同步:
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery

autoscaler

跟k8s的弹性扩缩容相关的组件,有如下两个功能:

vpa(pod垂直扩缩容)

k8s的kube-controller-manager默认支持了hpa功能,即水平扩缩容。同时k8s还提供了vpa功能,即垂直扩缩容,会根据pod历史的资源占用,修改pod的request值,并不会修改pod的limit值。之所以k8s没有默认提供vpa功能,原因是因为vpa实现要复杂很多,需要通过webhook的技术来在pod创建的时候修改pod的request值。vpa的功能通常用于大型单体应用。

autoscaler的功能之一即提供了vpa的实现。

Cluster Autoscaler(node节点自动扩缩容)

该功能是为了重新利用k8s node的节点资源,在节点资源不足的时候可以动态创建资源,在节点资源空闲的时候可以自动回收资源。k8s node的创建和释放需要公有云平台的支持,该功能对接了多个公有云厂商的api。

cloud-provider

k8s的cloud provider功能用来跟云厂商对接以实现对k8s的node、LoadBalancer Service管理,比如LoadBalancer Service申请vip需要跟云厂商的LB API进行对接。

在k8s 1.6版本之前,cloud provider的代码完全是耦合在k8s的kube-apiserver、kubelet、kube-contorller-manager的代码中的,如果要支持更多的云厂商,只能修改k8s的代码,扩展性比较差。

从k8s 1.6版本之后,cloud provider作为单独的二进制组件来提供服务,并提供了接口定义。本项目为非二进制项目,仅包含k8s的cloud-provider机制的接口定义,为了让第三方的cloud-provider的实现项目引用接口定义方便,将kubernetes项目中的定义为单独的项目,且代码跟kubernetes项目中的保持同步。

参考资料:

Descheduler

项目地址:https://github.com/kubernetes-sigs/descheduler

k8s的pod调度完全动态的,kube-scheduler组件在调度pod的时候会根据当时k8s集群的运行时环境来决定pod可以调度的节点,可以保证pod的调度在当时是最优的。但是随着的推移,比如环境中增加了新的node、创建了一批亲和节点的pod,都有可能会导致如果相同的pod再重新调度会到其他的节点上。但k8s的设计是,一旦pod调度完成后,就不会再重新调度。

Descheduler组件用来解决pod的重新调度问题,可以根据配置的一系列的规则来触发驱逐pod,让pod可以重新调度,从而使k8s集群内的pod尽可能达到最优状态,有点类似于计算机在运行了一段时间后的磁盘脆片整理功能。Descheduler组件可以以job、cronjob或者deployment的方式运行在k8s集群中。

kubernetes-template-project

用来存放了k8s项目的标准模板,里面包含了一些必要的文件,新建的项目可以fork该项目。

node-problem-detector(npd)

项目地址:https://github.com/kubernetes/node-problem-detector

k8s的管控组件对于iaas层的node运行状态是完全不感知的,比如节点出现了ntp服务挂掉、硬件告警(cpu、内存、磁盘故障)、内核死锁。node-problem-detector旨在将node节点的问题转换k8s node event,以DaemonSet的方式部署在所有的k8s节点上。

上报故障的方式支持如下两种方式:

  • 对于永久性故障,通过修改node status中的condition上报给apiserver
  • 对于临时性故障,通过Event的方式上报

node-problem-detector在将节点的故障信息上报给k8s后,通常会配合一些自愈系统搭配使用,比如Draino和Descheduler 。

sample-apiserver

k8s提供了aggregated apiserver的方式来扩展api,该项目为一个简单的aaggregated apiserver的例子,可以直接编译出二进制文件。要想开发一个AA类型的服务,只需要frok一下该项目,并在项目的基础上进行修改即可完成。

相关资料:

  • [Set up an Extension API Server](Set up an Extension API Server)

题图为一点资讯近期推出的一款定位线上社交的App啫喱,每个人可以给自己订制一个卡通形象,是国内厂商对于线上社交的一次新的尝试。

距离上期技术分享已经约有一年半的时间,这次不定期更新的时间有些久。2022年期望在博客方面增加时间投入,多关注开源技术,预计会大幅度提升更新的频率。

资源

1. Katacoda

可以一键创建一个k8s集群的工具,甚至无需登录,比上期推荐的工具Play with Kubernetes更为方便。

2. OperatorHub

k8s推出了CRD的机制后,大大增强了k8s的扩展能力,可以好不夸张的说,k8s之所以如此成功,跟CRD的扩展机制有很大关系。OperatorHub类似于DockerHub,收集了各种各样的operator实现。

3. lazykube

一款可以通过命令行终端来展示和管理k8s资源的工具。

4. LazyDocker

同上面的lazykube工具,LazyDocker是一个可以在命令行上查看本机docker的工具,可以查看docker上容器以及运行状态、本地的镜像以及分层信息、volume信息。

5. httpbin

一个非常简单的http服务,可以用来在测试服务的连通性,尤其是可以用curl测试api层面的连通性,再也不用访问curl http://www.baidu.com了。比如执行`curl http://httpbin.org/headers`可以以json的形式返回request的http header信息,执行curl http://httpbin.org/status/200可以返回状态码为200。

不过,在国内访问该网站连通性并不是太好。还提供了镜像版本,可以直接在本地以docker的方式运行该服务。

6. Flux

一款基于k8s的GitOps工具,采用k8s的声明式api基于operator实现。

https://github.com/fluxcd/flux/blob/master/docs/_files/flux-cd-diagram.png

7. OpenTelemetry

云原生领域的可观测性工具,用来制定规范、提供sdk和采集agent实现,实现了可观察性中的tracing和metric,并没有实现logging部分。不负责底层的后端存储,可以跟负责metric的prometheus集成,负责tracing的jager集成。

8. kspan

https://github.com/weaveworks-experiments/kspan/blob/main/example-2pod.png

该工具可以将k8s的event使用OpenTelemetry转换成tracing的形式,并将其存储在支持tracing的后端,比如jaeger中。

9. skopeo

skopeo是一款镜像操作工具,用来解决常用的镜像操作,但这些功能docker命令却不太具备,或者需要调用docker registry的api才可以完成操作。比如镜像从一个镜像仓库迁移到另外一个镜像仓库,从镜像仓库中删除镜像等。

10. KubeOperator

https://camo.githubusercontent.com/1045b27ad1186b915281a6fd77b35979d2038d97e152c34f0431e74ac43d4145/68747470733a2f2f6b7562656f70657261746f722e696f2f696d616765732f73637265656e73686f742f30322e6a7067

KubeOperator一个开源的轻量级 Kubernetes 发行版,提供可视化的 Web UI,可以用来在IaaS 平台上自动创建主机,通过 Ansible 完成自动化部署和变更操作。

另外,还提供了商业版本,支持更多的企业级特性,这种模式也是类似Redhat的最为常见的开源软件的商业变现方式。

11. Lens

https://kuring.oss-cn-beijing.aliyuncs.com/knowledge/kube-operator.jpeg

Kubernetes的客户端工具,非web端工具,可以通过本地访问远程的k8s集群,并且可以获取k8s集群的node、pod等信息,提供了mac、linux、windows三种客户端版本。

12. Caddy

一款使用Golang编写的七层负载均衡软件,Github上有3万+的star数量,相比与nginx有两个明显的优势:提供了默认的https服务,支持证书的自签发;使用Golang开发,只需要一个二进制配合caddyfile配置文件即可运行,更轻量。

13. LVScare

sealyun开源的一个轻量级的lvs管理工具,可以作为轻量级的负载均衡,基于Golang实现。输入这样一条指令 lvscare care --run-once --vs 10.103.97.12:6443 --rs 192.168.0.2:6443 --rs 192.168.0.3:6443 --rs 192.168.0.4:6443 即可在宿主机上创建对应的ipvs规则。自带了健康检查功能,支持四层和七层的健康检查,是该工具的最大优势。一般lvs会配合着keepalived来进行检查检查,一旦健康检查失败后会将rs的权重调整为0,但keepalived的配置相对复杂,该工具更轻量。

14. HUGO

非常火爆的使用Golang开发的开源博客系统,目前Github上的Star数量已经超过5万+,更老牌的开源博客系统Hexo的Star数量才3万+,基于Vue开发的VuePress Star数量还不到2万。

15. sealer

阿里巴巴开源的一款云原生的应用发布工具。该工具的设计思路非常先进,提出了集群镜像的概念,可以将k8s集群和应用build成为一个集群镜像,类似于docker镜像,一旦集群镜像构建完成后即可像容器一样运行在不同的硬件上。

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
kubectl get pods -A  --sort-by='.status.containerStatuses[0].restartCount' | tail

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
# 查看所有 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']}"

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

相关链接

0%