kubeadm官方推荐方案,也在大力发展。小问题比较多,扩展还是需要配合其它方案一起做。高可用上面还是需要自己花一些精力,如果只是玩玩,还是非常推荐的,但是想要正式环境使用,我还是推荐大家三思。
由于kubeadm更像是一套完整的脚本封装,所以想要扩展它,还是需要配合其它的方案一起做。升级之类的,可以参考官方的升级指南,还是比较容易的。
主机名 | 节点类型 | 操作系统 | IP 地址 | 所需组件 |
---|---|---|---|---|
node-128 | master1 | CentOS 7.9 | 192.168.17.128 | docker,kubectl,kubeadm,containerd,keepalived,haproxy |
node-129 | master2 | CentOS 7.9 | 192.168.17.129 | docker,kubectl,kubeadm,containerd,keepalived,haproxy |
node-130 | node | CentOS 7.9 | 192.168.17.130 | docker,kublet, kube-proxy,containerd |
node-131 | node | CentOS 7.9 | 192.168.17.130 | docker,kublet, kube-proxy,containerd |
[root@node-251 opt]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
[root@node-251 opt]# cat << eof >> /etc/hosts
> 192.168.71.253 node-253
> eof
[root@node-251 opt]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.71.251 node-251
192.168.71.252 node-252
192.168.71.253 node-253
[root@node-251 opt]# scp /etc/hosts node-252:/etc/hosts
...
hosts 100% 230 213.2KB/s 00:00
[root@node-251 opt]# scp /etc/hosts node-253:/etc/hosts
...
hosts 100% 230 228.2KB/s 00:00
#关闭系统防火墙
systemctl stop firewalld
systemctl disable firewalld
#关闭selinux
sed -i 's/enforcing/disabled/' /etc/selinux/config #永久
setenforce 0 # 临时
#关闭swap
swapoff -a #临时
sed -ri 's/.*swap.*/#&/' /etc/fstab #永久
#将桥接的IPV4流量传递到iptables的链
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system #生效
#时间同步
#使用阿里云时间服务器进行临时同步
[root@k8s-node1 ~]# ntpdate ntp.aliyun.com
4 Sep 21:27:49 ntpdate[22399]: adjust time server 203.107.6.88 offset 0.001010 sec
在 node-128 上配置 SSH 密钥对,并将公钥发送给其余主机
[root@node-251 opt]# ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:ztGq/7P5XHswguUSHqM0AjeO4La7jVx1z4FVFQ0bC14 root@node-251
The key's randomart image is:
+---[RSA 2048]----+
| o.E+ |
| . . o o o +.|
| . . = . . . o |
| o . o o++ . |
| . . .oS+oB |
| . . +.=+.o o |
| .. + o. ..o |
| ..+ . .o . ..|
| +.. ....++o .. |
+----[SHA256]-----+
[root@node-251 opt]# ssh-copy-id root@node-252
...
[root@node-251 opt]# ssh-copy-id root@node-253
...
[root@node-251 opt]# ssh node-252
Last login: Wed Apr 19 16:23:06 2023 from 192.168.20.252
[root@node-252 ~]# exit
logout
Connection to node-252 closed.
需要配置各个节点相互免密,方便后面的文件拷贝
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
不配置初始化会出错
modprobe br_netfilter
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/ipv4/ip_forward
汇总
systemctl stop firewall
systemctl stop firewalld
systemctl disable firewalld
getenforce
sed -i 's/enforcing/disabled/' /etc/selinux/config
setenforce 0
swapoff -a
sed -ri 's/.*swap.*/#&/' /etc/fstab
cat > /etc/sysctl.d/k8s.conf << EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sysctl --system
ntpdate ntp.aliyun.com
添加docker安装源
sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装docker
yum install -y docker-ce
添加k8s安装源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
EOF
安装k8s组件
yum install -y kubelet kubeadm kubectl
开机启动
systemctl enable kubelet
systemctl start kubelet
systemctl enable docker
systemctl start docker
kubelet启动报错
systemctl status docker
journalctl -xe
kubelet[8783]: E0714 04:42:02.417578 8783 run.go:74] "command failed" err="failed to load kubelet config file, error: open /var/lib/kubelet/config.yaml: no such file or directory, path: /var/lib/kubelet/config.yaml"
原因是没有还需要执行:
kubeadm init
不要急,等master初始化之后就可以正常启动了
kuberneters官方推荐docker等使用systemd作为cgroupdriver,否则kubelet启动不了
cat <<EOF > /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.docker-cn.com"],
"exec-opts": ["native.cgroupdriver=systemd"]
}
EOF
笔者这个版本默认就是systemd,所以不需要修改,另外如果修改完配置文件,daemon-reload重启后报错
可能是因为和启动项冲突了
/lib/systemd/system/docker.service
# 找到并删除下面这句话,保存退出,即可解决
# --exec-opt native.cgroupdriver=cgroupfs \
验证docker的Cgroup是否是systemd
[root@node-128 ~]# docker info |grep Cgroup
WARNING: You're not using the default seccomp profile
Cgroup Driver: systemd
调度服务中通过Keepalived负责将集群对用户显示为一个整体,提供VIP,并且提供调度服务器的故障转移,确保调度服务的高可用。HaProxy工具负责进行负载均衡功能,与服务器集群相连接。
一般来说,会有 2 台服务器运行 Keepalived,一台为主服务器,一台为备份服务器,对外表现为一个虚拟 IP。
Keepalived 一旦检测到 Haproxy 进程停止,会尝试重启 Haproxy,如果重启失败,Keepalived 就会自杀,另一台 Keepalived 节点检测到对方消失,则会自动接管服务,此时的虚拟 IP 就漂移至另一台 Keepalived 节点上,由另一台 Haproxy 分发请求。
用一个生活常见的场景来展示这个过程。
从前小c要去斯坦福报道。虽然他不知道学校在哪,但是斯坦福安排了接站的服务,可以坐接站车去学校。小c满心欢喜的下了高铁准备,去找接站的指示牌,看见了一个个指示牌,上面写着那边是接站的老师。小辰跟着指示牌走,找到了接站的老师。老师见到小c,知道了小c是要坐接站车的同学,就负责安排,让小c上了第六排第一辆车。到此小c成功了坐上了接站车。
上面这个故事中,指示牌相当于就是Keepalived提供的VIP,老师相当于是负责负载均衡调度的HaProxy,小c跟着指示牌找到了(通过Keepalived的VIP)负责安排上车的老师(提供负载均衡调度的HaProxy),在老师的安排下小c上了车(客户端的信息/请求到达了后端的提供服务的服务器)
yum install keepalived haproxy -y
所有master节点执行,注意替换网卡名字和master节点IP地址
[root@node-128 kubernetes]# cat /etc/haproxy/haproxy.cfg
global
maxconn 2000
ulimit-n 16384
log 127.0.0.1 local0 err
stats timeout 30s
defaults
log global
mode http
option httplog
timeout connect 5000
timeout client 50000
timeout server 50000
timeout http-request 15s
timeout http-keep-alive 15s
frontend monitor-in
bind *:33305
mode http
option httplog
monitor-uri /monitor
frontend k8s-master
bind 0.0.0.0:7443
bind 127.0.0.1:7443
mode tcp
option tcplog
tcp-request inspect-delay 5s
default_backend k8s-master
backend k8s-master
mode tcp
option tcplog
option tcp-check
balance roundrobin
default-server inter 10s downinter 5s rise 2 fall 2 slowstart 60s maxconn 250 maxqueue 256 weight 100
server node-128 192.168.17.128:6443 check
server node-129 192.168.17.129:6443 check
在k8s-master1节点,注意mcast_src_ip换成实际的master1 ip地址,virtual_ipaddress换成lb地址
[root@node-128 kubernetes]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id LVS_DEVEL
script_user root
enable_script_security
}
vrrp_script chk_apiserver {
script "/etc/keepalived/check_apiserver.sh"
interval 5
weight -5
fall 2
rise 1
}
vrrp_instance VI_1 {
state MASTER
interface ens37
mcast_src_ip 192.168.17.128
virtual_router_id 60
priority 101
advert_int 2
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
virtual_ipaddress {
192.168.17.120
}
track_script {
chk_apiserver
}
}
在k8s-master2修改/etc/keepalived/keepalived.conf,注意修改mcast_src_ip和virtual_ipaddress和state 改为BACKUP
[root@node-129 kubernetes]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id LVS_DEVEL
script_user root
enable_script_security
}
vrrp_script chk_apiserver {
script "/etc/keepalived/check_apiserver.sh"
interval 5
weight -5
fall 2
rise 1
}
vrrp_instance VI_1 {
state BACKUP
interface ens37
mcast_src_ip 192.168.17.129
virtual_router_id 60
priority 101
advert_int 2
authentication {
auth_type PASS
auth_pass K8SHA_KA_AUTH
}
virtual_ipaddress {
192.168.17.120
}
track_script {
chk_apiserver
}
}
所有master节点配置KeepAlived健康检查文件:
$ cat > /etc/keepalived/check_apiserver.sh <<EOF
#!/bin/bash
err=0
for k in $(seq 1 3)
do
check_code=$(pgrep haproxy)
if [[ $check_code == "" ]]; then
err=$(expr $err + 1)
sleep 1
continue
else
err=0
break
fi
done
if [[ $err != "0" ]]; then
echo "systemctl stop keepalived"
/usr/bin/systemctl stop keepalived
exit 1
else
exit 0
fi
EOF
启动haproxy和keepalived---->所有master节点
$ chmod +x /etc/keepalived/check_apiserver.sh
$ systemctl daemon-reload
$ systemctl enable --now haproxy
$ systemctl enable --now keepalived
测试lbip是否生效
$ telnet 192.168.2.120 7443
# 显示一下信息视为成功
Trying 192.168.2.120…
Connected to 192.168.2.120.
Escape character is ‘^]’.
Connection closed by foreign host.
Docker 强势崛起,云计算开始容器时代,Dockers以独特的容器架构和容器“镜像”快速发展,对其他容器技术进行致命的降维打击,包括 Google在内的很多公司无法与之匹敌。Google和其它互联网公司为了不被Docker占领全部市场,与 Docker 公司联合推进一个开源的容器运行时作为 Docker 的核心依赖——Containerd,Containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性。其诞生于Docker,提供如下功能:
1、管理容器的生命周期(从创建容器到销毁容器)
2、拉取/推送容器镜像
3、存储管理(管理镜像及容器数据的存储)
4、调用 runc 运行容器(与 runc 等容器运行时交互)
5、管理容器网络接口及网络
而后,Google 联合 Red Hat等与 Docker 公司商讨将libcontainer捐给中立的社区(OCI,Open Container Intiative),并改名为Runc。Docker公司退役后,Google 等又合伙成立了CNCF(Cloud Native Computing Fundation)进行大规模容器编排,以此与Docker抗衡。Docker 公司推出了Swarm与Kubernetes进行抗衡,但结果一目了然。
Kubernetes 设计了一套接口规则CRI(Container Runntime Interface),第一个支持该接口规则的是Containerd。为继续支持Docker,专门组件中集成了一个shim,其可以将 CRI 调用翻译成 Docker 的 API,以此支持Docker使用。本文通过Kubeadm部署指定版本的Kubernetes,并同时安装Containerd+Docker,支持两种容器运行时。
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum list | grep containerd
yum -y install containerd
Containerd 的默认配置文件为/etc/containerd/config.toml,我们可以通过命令来生成一个默认的配置,,需要把Containerd相关的文件都放入/etc/containerd文件夹,创建/etc/containerd文件夹并生成Containerd的配置文件。
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
在国内拉取公共镜像仓库的速度比较慢,为了节约拉取时间,需要为 Containerd 配置镜像仓库的mirror,其中Containerd和Docker相比的区别(来源于文章Containerd 使用教程)
- Containerd 只支持通过CRI拉取镜像的mirro,也就是说,只有通过crictl或者K8s调用时mirro 才会生效,通过ctr拉取是不会生效的。
- Docker只支持为Docker Hub配置mirror,而Containerd支持为任意镜像仓库配置mirror。
需要修改配置文件/etc/containerd/config.toml中的registry 配置块,在plugins."io.containerd.grpc.v1.cri".registry.mirrors
部分添加
...
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = false
...
restrict_oom_score_adj = false
sandbox_image = "registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.6"
...
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = ""
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.headers]
# [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://dockerhub.mirrors.nwafu.edu.cn"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
endpoint = ["https://registry.aliyuncs.com/k8sxio"]
registry.mirrors.“xxx” : 表示需要配置 mirror 的镜像仓库。例如,registry.mirrors.“docker.io” 表示配置 docker.io 的 mirror。
endpoint : 表示提供 mirror 的镜像加速服务。例如,这里推荐使用西北农林科技大学提供的镜像加速服务作为 docker.io 的 mirror。
启动Containerd
systemctl daemon-reload
systemctl enable containerd
systemctl restart containerd
什么是kubeadm?
我们来看看官网的介绍
Kubeadm是一个工具,它提供kubeadm init和kubeadm join作为创建Kubernetes集群的最佳实践“快捷路径”。
kubeadm执行必要的操作来启动和运行最小可行集群。按照设计,它只关心引导,而不关心配置机器。同样,安装各种漂亮的插件(比如Kubernetes Dashboard、监控解决方案和特定于云的插件)也不在讨论范围之内。
相反,我们期望在kubeadm的基础上构建更高级、更定制化的工具,理想情况下,使用kubeadm作为所有部署的基础将使创建符合规范的集群变得更容易。
kubeadm 让k8s使用容器化的方案运行。
初始化配置文件
$ kubeadm config print init-defaults > kubeadm.yaml
[root@node-129 kubernetes]# cat kubeadm.yaml
apiVersion: kubeadm.k8s.io/v1beta3
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: abcdef.0123456789abcdef
ttl: 24h0m0s
usages:
- signing
- authentication
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.17.128
bindPort: 6443
nodeRegistration:
criSocket: unix:///var/run/containerd/containerd.sock
imagePullPolicy: IfNotPresent
name: node-128
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
---
apiServer:
certSANs:
- 192.168.17.120
timeoutForControlPlane: 4m0s
apiVersion: kubeadm.k8s.io/v1beta3
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
controlPlaneEndpoint: 192.168.17.120:7443
controllerManager: {}
dns: {}
# type: CoreDNS
etcd:
local:
dataDir: /var/lib/etcd
imageRepository: registry.cn-hangzhou.aliyuncs.com/google_containers
kind: ClusterConfiguration
kubernetesVersion: 1.27.0
networking:
dnsDomain: cluster.local
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
scheduler: {}
以上配置详细参考 https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3
# 查看需要使用的镜像列表,若无问题,将得到如下列表
[root@node-128 kubernetes]# kubeadm config images list --config kubeadm.yaml
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.27.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.27.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.27.0
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.27.0
registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9
registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.7-0
registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.10.1
# 提前下载镜像到本地
[root@node-128 kubernetes]# kubeadm config images pull --config kubeadm.yaml
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.27.0
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.27.0
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-scheduler:v1.27.0
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/kube-proxy:v1.27.0
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.7-0
[config/images] Pulled registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.10.1
这里如果失败了,可能是没安装containerd。笔者查找了下相关资料,大概是从K8S 1.24版本就弃用dockershim,详细资料查看
容器运行时探讨–从dockershim正式从K8s移除说起
[root@node-128 kubernetes]# kubeadm init --config kubeadm.yaml --upload-certs
[init] Using Kubernetes version: v1.27.0
...
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Alternatively, if you are the root user, you can run:
export KUBECONFIG=/etc/kubernetes/admin.conf
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
You can now join any number of the control-plane node running the following command on each as root:
kubeadm join 192.168.17.120:7443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:fe4890019230110147607a228c79af30ebdb4b3d7cecf7a0e9f36a2b3e74784f \
--control-plane --certificate-key ec9e266ded35047f431b9efe82ce903672c589199973d88f6df210e80e5d3b75
Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.17.120:7443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:fe4890019230110147607a228c79af30ebdb4b3d7cecf7a0e9f36a2b3e74784f
接下来按照上述提示信息操作,配置kubectl客户端的认证,master操作
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
若执行初始化过程中出错,根据错误信息调整后,执行
kubeadm reset -f ; ipvsadm --clear ; rm -rf ~/.kube
kubeadm join 192.168.17.120:7443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:fe4890019230110147607a228c79af30ebdb4b3d7cecf7a0e9f36a2b3e74784f \
--control-plane --certificate-key ec9e266ded35047f431b9efe82ce903672c589199973d88f6df210e80e5d3b75
[root@node-128 kubernetes]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-128 NotReady control-plane 17m v1.27.3
node-129 NotReady control-plane 58s v1.27.3
kubeadm join 192.168.17.120:7443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:fe4890019230110147607a228c79af30ebdb4b3d7cecf7a0e9f36a2b3e74784f
#如果报错,请用--v=5来查看,若出现下面错误:
[preflight] Some fatal errors occurred:
[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]: /proc/sys/net/bridge/bridge-nf-call-iptables contents are not set to 1
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
error execution phase preflight
#报错原因:这个错误通常发生在 kubeadm init 或 kubeadm join 命令执行前的预检阶段,是由于系统中的 /proc/sys/net/bridge/bridge-nf-call-iptables 文件的内容未设置为 1。修复该错误可以使用以下方法:
#解决办法:
modprobe br_netfilter
echo 1 > /proc/sys/net/bridge/bridge-nf-call-iptables
echo 1 > /proc/sys/net/ipv4/ip_forward
[root@node-129 kubernetes]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-128 NotReady control-plane 5m24s v1.27.3
node-129 NotReady control-plane 4m46s v1.27.3
node-130 NotReady <none> 3m36s v1.27.3
node-131 NotReady <none> 4s v1.27.3
可以通过如下命令生成
kubeadm token create --print-join-command
可以看到节点都是NotReady呢?因为我们还需要安装一个网络插件,他们才能工作。
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
namespace/kube-flannel unchanged
clusterrole.rbac.authorization.k8s.io/flannel unchanged
clusterrolebinding.rbac.authorization.k8s.io/flannel unchanged
serviceaccount/flannel unchanged
configmap/kube-flannel-cfg unchanged
daemonset.apps/kube-flannel-ds unchanged
如果没有办法下载,那就想办法科学上网吧
节点状态
[root@node-128 kubernetes]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
node-128 Ready control-plane 14m v1.27.3
node-129 Ready control-plane 13m v1.27.3
node-130 Ready <none> 12m v1.27.3
node-131 Ready <none> 9m15s v1.27.3