使用 Kube-vip 搭建高可用的 Kubernetes 集群(完整版)
作者:阳明 2021-09-09 07:45:25云计算 前面我们课程中的集群是单 master 的集群,对于生产环境风险太大了,非常有必要做一个高可用的集群,这里的高可用主要是针对控制面板来说的。
[[422792]]
前面我们课程中的集群是单 master 的集群,对于生产环境风险太大了,非常有必要做一个高可用的集群,这里的高可用主要是针对控制面板来说的,比如 kube-apiserver、etcd、kube-controller-manager、kube-scheduler 这几个组件,其中 kube-controller-manager 于 kube-scheduler 组件是 Kubernetes 集群自己去实现的高可用,当有多个组件存在的时候,会自动选择一个作为 Leader 提供服务,所以不需要我们手动去实现高可用,apiserver 和 etcd 就需要手动去搭建高可用的集群的。高可用的架构有很多,比如典型的 haproxy + keepalived 架构,或者使用 nginx 来做代理实现。
环境准备
4个节点,都是 Centos 7.6 系统,内核版本:3.10.0-1062.4.1.el7.x86_64,在每个节点上添加 hosts 信息:
~cat/etc/hosts192.168.31.10api.k8s.local#vip192.168.31.31master1192.168.31.32master2192.168.31.33master3192.168.31.100node1
其中 192.168.31.10 为 vip,使用域名 api.k8s.local 进行映射。
节点的 hostname 必须使用标准的 DNS 命名,另外千万不用什么默认的 localhost 的 hostname,会导致各种错误出现的。在 Kubernetes 项目里,机器的名字以及一切存储在 Etcd 中的 API 对象,都必须使用标准的 DNS 命名(RFC 1123)。可以使用命令 hostnamectl set-hostname node1 来修改 hostname。
禁用防火墙:
~systemctlstopfirewalld~systemctldisablefirewalld
禁用 SELINUX:
~setenforce0~cat/etc/selinux/configSELINUX=disabled
由于开启内核 ipv4 转发需要加载 br_netfilter 模块,所以加载下该模块:
~modprobebr_netfilter
创建/etc/sysctl.d/k8s.conf文件,添加如下内容:
net.bridge.bridge-nf-call-ip6tables=1net.bridge.bridge-nf-call-iptables=1net.ipv4.ip_forward=1
执行如下命令使修改生效:
~sysctl-p/etc/sysctl.d/k8s.conf
安装 ipvs:
~cat>/etc/sysconfig/modules/ipvs.modules<<EOF#!/bin/bashmodprobe--ip_vsmodprobe--ip_vs_rrmodprobe--ip_vs_wrrmodprobe--ip_vs_shmodprobe--nf_conntrack_ipv4EOF~chmod755/etc/sysconfig/modules/ipvs.modules&&bash/etc/sysconfig/modules/ipvs.modules&&lsmod|grep-eip_vs-enf_conntrack_ipv4
上面脚本创建了的/etc/sysconfig/modules/ipvs.modules文件,保证在节点重启后能自动加载所需模块。使用lsmod | grep -e ip_vs -e nf_conntrack_ipv4命令查看是否已经正确加载所需的内核模块。
接下来还需要确保各个节点上已经安装了 ipset 软件包:
~yuminstallipset
为了便于查看 ipvs 的代理规则,最好安装一下管理工具 ipvsadm:
~yuminstallipvsadm
同步服务器时间
~yuminstallchrony-y~systemctlenablechronyd~systemctlstartchronyd~chronycsources210Numberofsources=4MSName/IPaddressStratumPollReachLastRxLastsample===============================================================================^+sv1.ggsrv.de261732-823us[-1128us]+/-98ms^-montreal.ca.logiplex.net261732-17ms[-17ms]+/-179ms^-ntp6.flashdance.cx261732-32ms[-32ms]+/-161ms^*119.28.183.184263332+661us[+357us]+/-38ms~dateTueAug3114:36:14CST2021
关闭 swap 分区:
~swapoff-a
修改/etc/fstab文件,注释掉 SWAP 的自动挂载,使用free -m确认 swap 已经关闭。swappiness 参数调整,修改/etc/sysctl.d/k8s.conf添加下面一行:
vm.swappiness=0
执行 sysctl -p /etc/sysctl.d/k8s.conf 使修改生效。
安装 Containerd
我们已经了解过容器运行时 containerd 的一些基本使用,接下来在各个节点上安装 Containerd。
由于 containerd 需要调用 runc,所以我们也需要先安装 runc,不过 containerd 提供了一个包含相关依赖的压缩包 cri-containerd-cni-${VERSION}.${OS}-${ARCH}.tar.gz,可以直接使用这个包来进行安装。首先从 release 页面下载最新版本的压缩包,当前为 1.5.5 版本:
~wgethttps://github.com/containerd/containerd/releases/download/v1.5.5/cri-containerd-cni-1.5.5-linux-amd64.tar.gz#如果有限制,也可以替换成下面的URL加速下载#wgethttps://download.fastgit.org/containerd/containerd/releases/download/v1.5.5/cri-containerd-cni-1.5.5-linux-amd64.tar.gz
直接将压缩包解压到系统的各个目录中:
~tar-C/-xzfcri-containerd-cni-1.5.5-linux-amd64.tar.gz
然后要将 /usr/local/bin 和 /usr/local/sbin 追加到 ~/.bashrc 文件的 PATH 环境变量中:
exportPATH=$PATH:/usr/local/bin:/usr/local/sbin
然后执行下面的命令使其立即生效:
~source~/.bashrc
containerd 的默认配置文件为 /etc/containerd/config.toml,我们可以通过如下所示的命令生成一个默认的配置:
~mkdir-p/etc/containerd~containerdconfigdefault>/etc/containerd/config.toml
对于使用 systemd 作为 init system 的 Linux 的发行版,使用 systemd 作为容器的 cgroup driver 可以确保节点在资源紧张的情况更加稳定,所以推荐将 containerd 的 cgroup driver 配置为 systemd。
修改前面生成的配置文件 /etc/containerd/config.toml,在 plugins.”io.containerd.grpc.v1.cri”.containerd.runtimes.runc.options 配置块下面将 SystemdCgroup 设置为 true:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]...[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]SystemdCgroup=true....
然后再为镜像仓库配置一个加速器,需要在 cri 配置块下面的 registry 配置块下面进行配置 registry.mirrors:
[plugins."io.containerd.grpc.v1.cri"]...#sandbox_image="k8s.gcr.io/pause:3.5"sandbox_image="registry.aliyuncs.com/k8sxio/pause:3.5"...[plugins."io.containerd.grpc.v1.cri".registry][plugins."io.containerd.grpc.v1.cri".registry.mirrors][plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]endpoint=["https://bqr1dr1n.mirror.aliyuncs.com"][plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]endpoint=["https://registry.aliyuncs.com/k8sxio"]
由于上面我们下载的 containerd 压缩包中包含一个 etc/systemd/system/containerd.service 的文件,这样我们就可以通过 systemd 来配置 containerd 作为守护进程运行了,现在我们就可以启动 containerd 了,直接执行下面的命令即可:
~systemctldaemon-reload~systemctlenablecontainerd--now
启动完成后就可以使用 containerd 的本地 CLI 工具 ctr 和 crictl 了,比如查看版本:
~ctrversionClient:Version:v1.5.5Revision:72cec4be58a9eb6b2910f5d10f1c01ca47d231c0Goversion:go1.16.6Server:Version:v1.5.5Revision:72cec4be58a9eb6b2910f5d10f1c01ca47d231c0UUID:cd2894ad-fd71-4ef7-a09f-5795c7eb4c3b~crictlversionVersion:0.1.0RuntimeName:containerdRuntimeVersion:v1.5.5RuntimeApiVersion:v1alpha2
负载均衡器
为 apiserver 提供负载均衡器有很多方法,比如传统的 haproxy+keepalived,或者使用 nginx 代理也可以,这里我们使用一个比较新颖的工具 kube-vip。
kube-vip(https://kube-vip.io/) 可以在你的控制平面节点上提供一个 Kubernetes 原生的 HA 负载均衡,我们不需要再在外部设置 HAProxy 和 Keepalived 来实现集群的高可用了。
在以前我们在私有环境下创建 Kubernetes 集群时,我们需要准备一个硬件/软件的负载均衡器来创建多控制面集群,更多的情况下我们会选择使用 HAProxy + Keepalived 来实现这个功能。一般情况下我们创建2个负载均衡器的虚拟机,然后分配一个 VIP,然后使用 VIP 为负载均衡器提供服务,通过 VIP 将流量重定向到后端的某个 Kubernetes 控制器平面节点上。
haproxy+keepalived
如果我们使用 kube-vip 的话会怎样呢?
kube-vip
kube-vip 可以通过静态 pod 运行在控制平面节点上,这些 pod 通过 ARP 会话来识别每个节点上的其他主机,我们可以选择 BGP 或 ARP 来设置负载平衡器,这与 Metal LB 比较类似。在 ARP 模式下,会选出一个领导者,这个节点将继承虚拟 IP 并成为集群内负载均衡的 Leader,而在 BGP 模式下,所有节点都会通知 VIP 地址。
集群中的 Leader 将分配 vip,并将其绑定到配置中声明的选定接口上。当 Leader 改变时,它将首先撤销 vip,或者在失败的情况下,vip 将直接由下一个当选的 Leader 分配。当 vip 从一个主机移动到另一个主机时,任何使用 vip 的主机将保留以前的 vip <-> MAC 地址映射,直到 ARP 过期(通常是30秒)并检索到一个新的 vip <-> MAC 映射,这可以通过使用无偿的 ARP 广播来优化。
kube-vip 可以被配置为广播一个无偿的 arp(可选),通常会立即通知所有本地主机 vip <-> MAC 地址映射已经改变。
要使用 kube-vip 来实现集群的高可用,首先在 master1 节点上生成基本的 Kubernetes 静态 Pod 资源清单文件:
~mkdir-p/etc/kubernetes/manifests/#配置vip地址~exportVIP=192.168.31.10#设置网卡名称~exportINTERFACE=ens33~ctrimagepulldocker.io/plndr/kube-vip:v0.3.8#使用下面的容器输出静态Pod资源清单~ctrrun--rm--net-hostdocker.io/plndr/kube-vip:v0.3.8vip\/kube-vipmanifestpod\--interface$INTERFACE\--vip$VIP\--controlplane\--services\--arp\--leaderElection|tee/etc/kubernetes/manifests/kube-vip.yamlapiVersion:v1kind:Podmetadata:creationTimestamp:nullname:kube-vipnamespace:kube-systemspec:containers:-args:-managerenv:-name:vip_arpvalue:"true"-name:vip_interfacevalue:ens33-name:portvalue:"6443"-name:vip_cidrvalue:"32"-name:cp_enablevalue:"true"-name:cp_namespacevalue:kube-system-name:vip_ddnsvalue:"false"-name:svc_enablevalue:"true"-name:vip_leaderelectionvalue:"true"-name:vip_leasedurationvalue:"5"-name:vip_renewdeadlinevalue:"3"-name:vip_retryperiodvalue:"1"-name:vip_addressvalue:192.168.31.10image:ghcr.io/kube-vip/kube-vip:v0.3.8imagePullPolicy:Alwaysname:kube-vipresources:{}securityContext:capabilities:add:-NET_ADMIN-NET_RAW-SYS_TIMEvolumeMounts:-mountPath:/etc/kubernetes/admin.confname:kubeconfighostNetwork:truevolumes:-hostPath:path:/etc/kubernetes/admin.confname:kubeconfigstatus:{}
这里我们将 vip 设置为 192.168.31.10,首先会将 master1 节点选举为 Leader,然后接下来我们使用该 vip 来初始化控制器平台。
初始化控制平面
上面的相关环境配置也完成了,现在我们就可以来安装 Kubeadm 了,我们这里是通过指定 yum 源的方式来进行安装的:
~cat<<EOF>/etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=1repo_gpgcheck=1gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpghttps://packages.cloud.google.com/yum/doc/rpm-package-key.gpgEOF
当然了,上面的 yum 源是需要科学上网的,如果不能科学上网的话,我们可以使用阿里云的源进行安装:
~cat<<EOF>/etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=http://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0repo_gpgcheck=0gpgkey=http://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpghttp://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpgEOF
然后安装 kubeadm、kubelet、kubectl:
#--disableexcludes禁掉除了kubernetes之外的别的仓库~yummakecachefast~yuminstall-ykubelet-1.22.1kubeadm-1.22.1kubectl-1.22.1--disableexcludes=kubernetes~kubeadmversionkubeadmversion:&version.Info{Major:"1",Minor:"22",GitVersion:"v1.22.1",GitCommit:"632ed300f2c34f6d6d15ca4cef3d3c7073412212",GitTreeState:"clean",BuildDate:"2021-08-19T15:44:22Z",GoVersion:"go1.16.7",Compiler:"gc",Platform:"linux/amd64"}
可以看到我们这里安装的是 v1.22.1 版本,然后将 master 节点的 kubelet 设置成开机启动:
~systemctlenable--nowkubelet
到这里为止上面所有的操作都需要在所有节点执行配置。
当我们执行 kubelet –help 命令的时候可以看到原来大部分命令行参数都被 DEPRECATED了,这是因为官方推荐我们使用 –config 来指定配置文件,在配置文件中指定原来这些参数的配置,可以通过官方文档 Set Kubelet parameters via a config file 了解更多相关信息,这样 Kubernetes 就可以支持动态 Kubelet 配置(Dynamic Kubelet Configuration)了,参考 Reconfigure a Node’s Kubelet in a Live Cluster。
然后我们可以通过下面的命令在 master1 节点上输出集群初始化默认使用的配置:
~kubeadmconfigprintinit-defaults--component-configsKubeletConfiguration>kubeadm.yaml
然后根据我们自己的需求修改配置,比如修改 imageRepository 指定集群初始化时拉取 Kubernetes 所需镜像的地址,kube-proxy 的模式为 ipvs,另外需要注意的是我们这里是准备安装 flannel 网络插件的,需要将 networking.podSubnet 设置为10.244.0.0/16:
对于上面的资源清单的文档比较杂,要想完整了解上面的资源对象对应的属性,可以查看对应的 godoc 文档,地址: https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3。#kubeadm.yamlapiVersion:kubeadm.k8s.io/v1beta3bootstrapTokens:-groups:-system:bootstrappers:kubeadm:default-node-tokentoken:abcdef.0123456789abcdefttl:24h0m0susages:-signing-authenticationkind:InitConfigurationlocalAPIEndpoint:advertiseAddress:192.168.31.31#指定当前节点内网IPbindPort:6443nodeRegistration:criSocket:/run/containerd/containerd.sock#使用containerd的Unixsocket地址imagePullPolicy:IfNotPresentname:master1taints:#给master添加污点,master节点不能调度应用-effect:"NoSchedule"key:"node-role.kubernetes.io/master"---apiVersion:kubeproxy.config.k8s.io/v1alpha1kind:KubeProxyConfigurationmode:ipvs#kube-proxy模式---apiVersion:kubeadm.k8s.io/v1beta3certificatesDir:/etc/kubernetes/pkiclusterName:kubernetescontrollerManager:{}dns:{}etcd:local:dataDir:/var/lib/etcdimageRepository:registry.aliyuncs.com/k8sxiokind:ClusterConfigurationkubernetesVersion:1.22.1controlPlaneEndpoint:api.k8s.local:6443#设置控制平面Endpoint地址apiServer:extraArgs:authorization-mode:Node,RBACtimeoutForControlPlane:4m0scertSANs:#添加其他master节点的相关信息-api.k8s.local-master1-master2-master3-192.168.31.30-192.168.31.31-192.168.31.32networking:dnsDomain:cluster.localserviceSubnet:10.96.0.0/12podSubnet:10.244.0.0/16#指定pod子网scheduler:{}---apiVersion:kubelet.config.k8s.io/v1beta1authentication:anonymous:enabled:falsewebhook:cacheTTL:0senabled:truex509:clientCAFile:/etc/kubernetes/pki/ca.crtauthorization:mode:Webhookwebhook:cacheAuthorizedTTL:0scacheUnauthorizedTTL:0sclusterDNS:-10.96.0.10clusterDomain:cluster.localcpuManagerReconcilePeriod:0sevictionPressureTransitionPeriod:0sfileCheckFrequency:0shealthzBindAddress:127.0.0.1healthzPort:10248httpCheckFrequency:0simageMinimumGCAge:0skind:KubeletConfigurationcgroupDriver:systemd#配置cgroupdriverlogging:{}memorySwap:{}nodeStatusReportFrequency:0snodeStatusUpdateFrequency:0srotateCertificates:trueruntimeRequestTimeout:0sshutdownGracePeriod:0sshutdownGracePeriodCriticalPods:0sstaticPodPath:/etc/kubernetes/manifestsstreamingConnectionIdleTimeout:0ssyncFrequency:0svolumeStatsAggPeriod:0s
这里需要注意的是我们在 ClusterConfiguration 块的配置中新增了控制平面的地址以及将 api.k8s.local 这个域名加入到了证书签名中,该域名将映射到 vip:
controlPlaneEndpoint:api.k8s.local:6443#设置控制平面Endpoint地址apiServer:extraArgs:authorization-mode:Node,RBACtimeoutForControlPlane:4m0scertSANs:#添加其他master节点的相关信息-api.k8s.local-master1-master2-master3-192.168.31.30-192.168.31.31-192.168.31.32
在开始初始化集群之前可以使用 kubeadm config images pull –config kubeadm.yaml 预先在各个服务器节点上拉取所k8s需要的容器镜像。
配置文件准备好过后,可以使用如下命令先将相关镜像 pull 下面:
~kubeadmconfigimagespull--configkubeadm.yaml[config/images]Pulledregistry.aliyuncs.com/k8sxio/kube-apiserver:v1.22.1[config/images]Pulledregistry.aliyuncs.com/k8sxio/kube-controller-manager:v1.22.1[config/images]Pulledregistry.aliyuncs.com/k8sxio/kube-scheduler:v1.22.1[config/images]Pulledregistry.aliyuncs.com/k8sxio/kube-proxy:v1.22.1[config/images]Pulledregistry.aliyuncs.com/k8sxio/pause:3.5[config/images]Pulledregistry.aliyuncs.com/k8sxio/etcd:3.5.0-0failedtopullimage"registry.aliyuncs.com/k8sxio/coredns:v1.8.4":output:time="2021-08-31T15:09:13+08:00"level=fatalmsg="pullingimage:rpcerror:code=NotFounddesc=failedtopullandunpackimage\"registry.aliyuncs.com/k8sxio/coredns:v1.8.4\":failedtoresolvereference\"registry.aliyuncs.com/k8sxio/coredns:v1.8.4\":registry.aliyuncs.com/k8sxio/coredns:v1.8.4:notfound",error:exitstatus1Toseethestacktraceofthiserrorexecutewith--v=5orhigher
上面在拉取 coredns 镜像的时候出错了,没有找到这个镜像,我们可以手动 pull 该镜像,然后重新 tag 下镜像地址即可:
~ctr-nk8s.ioipulldocker.io/coredns/coredns:1.8.4~ctr-nk8s.ioitagdocker.io/coredns/coredns:1.8.4registry.aliyuncs.com/k8sxio/coredns:v1.8.4
然后就可以使用上面的配置文件在 master1 节点上进行初始化:
~kubeadminit--upload-certs--configkubeadm.yaml[init]UsingKubernetesversion:v1.22.1[preflight]Runningpre-flightchecks[preflight]PullingimagesrequiredforsettingupaKubernetescluster......YourKubernetescontrol-planehasinitializedsuccessfully!Tostartusingyourcluster,youneedtorunthefollowingasaregularuser:mkdir-p$HOME/.kubesudocp-i/etc/kubernetes/admin.conf$HOME/.kube/configsudochown$(id-u):$(id-g)$HOME/.kube/configAlternatively,ifyouaretherootuser,youcanrun:exportKUBECONFIG=/etc/kubernetes/admin.confYoushouldnowdeployapodnetworktothecluster.Run"kubectlapply-f[podnetwork].yaml"withoneoftheoptionslistedat:https://kubernetes.io/docs/concepts/cluster-administration/addons/Youcannowjoinanynumberofthecontrol-planenoderunningthefollowingcommandoneachasroot:kubeadmjoinapi.k8s.local:6443--tokenabcdef.0123456789abcdef\--discovery-token-ca-cert-hashsha256:435fbc28490d1f897337923c19ec27bcf3639e9fe84e8448177777d23cae4176\--control-plane--certificate-key7892cd62c5ab60b28b462af32c7e49aa73d5fd4f723352f3af6546a74e465abcPleasenotethatthecertificate-keygivesaccesstoclustersensitivedata,keepitsecret!Asasafeguard,uploaded-certswillbedeletedintwohours;Ifnecessary,youcanuse"kubeadminitphaseupload-certs--upload-certs"toreloadcertsafterward.Thenyoucanjoinanynumberofworkernodesbyrunningthefollowingoneachasroot:kubeadmjoinapi.k8s.local:6443--tokenabcdef.0123456789abcdef\--discovery-token-ca-cert-hashsha256:435fbc28490d1f897337923c19ec27bcf3639e9fe84e8448177777d23cae4176
这里初始化的 –upload-certs 标志用来将在所有控制平面实例之间的共享证书上传到集群。然后根据安装提示拷贝 kubeconfig 文件:
~mkdir-p$HOME/.kube~sudocp-i/etc/kubernetes/admin.conf$HOME/.kube/config~sudochown$(id-u):$(id-g)$HOME/.kube/config
接着我们可以根据上面的提示添加其他的控制平面节点。
添加控制平面
对于每个其他控制平面节点,执行先前在第一个节点 master1 上的 kubeadm init 输出提供的 join 命令来添加控制平面节点:
~kubeadmjoinapi.k8s.local:6443--tokenabcdef.0123456789abcdef--discovery-token-ca-cert-hashsha256:435fbc28490d1f897337923c19ec27bcf3639e9fe84e8448177777d23cae4176--control-plane--certificate-key7892cd62c5ab60b28b462af32c7e49aa73d5fd4f723352f3af6546a74e465abc[preflight]Runningpre-flightchecks[preflight]Readingconfigurationfromthecluster...[preflight]FYI:Youcanlookatthisconfigfilewith'kubectl-nkube-systemgetcmkubeadm-config-oyaml'[preflight]Runningpre-flightchecksbeforeinitializingthenewcontrolplaneinstance......Thisnodehasjoinedtheclusterandanewcontrolplaneinstancewascreated:*Certificatesigningrequestwassenttoapiserverandapprovalwasreceived.*TheKubeletwasinformedofthenewsecureconnectiondetails.*Controlplane(master)labelandtaintwereappliedtothenewnode.*TheKubernetescontrolplaneinstancesscaledup.*Anewetcdmemberwasaddedtothelocal/stackedetcdcluster.Tostartadministeringyourclusterfromthisnode,youneedtorunthefollowingasaregularuser:mkdir-p$HOME/.kubesudocp-i/etc/kubernetes/admin.conf$HOME/.kube/configsudochown$(id-u):$(id-g)$HOME/.kube/configRun'kubectlgetnodes'toseethisnodejointhecluster.
需要注意需要在另外两个节点 master2 和 master3 都执行上面的 join 命令,上面的命令中的 –control-plane 就是通知 kubeadm join 创建一个新的控制平面,–certificate-key 会从集群中的 kubeadm-certs Secret 下载控制平面证书并使用给定的密钥进行解密。
当这两个节点被添加到集群后,我们接下来也需要在节点上运行 kube-vip,将当前节点作为 kube-vip 的成员,同样执行下面的命令即可:
#配置vip地址~exportVIP=192.168.31.10#设置网卡名称~exportINTERFACE=ens33~ctrimagepulldocker.io/plndr/kube-vip:v0.3.8#使用下面的容器输出静态Pod资源清单~ctrrun--rm--net-hostdocker.io/plndr/kube-vip:v0.3.8vip\/kube-vipmanifestpod\--interface$INTERFACE\--vip$VIP\--controlplane\--services\--arp\--leaderElection|tee/etc/kubernetes/manifests/kube-vip.yaml
当 kube-vip 的静态 Pod 清单创建完成后,正常将能够看到 kube-vip 的 Pod 会按预期启动并运行:
~kubectlgetpods-A|grepvipkube-systemkube-vip-master11/1Running17m42skube-systemkube-vip-master21/1Running04m24skube-systemkube-vip-master31/1Running014s
这个时候控制平面节点就都准备好了:
~kubectlgetnodesNAMESTATUSROLESAGEVERSIONmaster1Readycontrol-plane,master9m18sv1.22.1master2Readycontrol-plane,master7m11sv1.22.1master3Readycontrol-plane,master5m9sv1.22.1
添加工作节点
接下来我们可以将 node1 工作节点加入到集群中,同样使用在 master1 上初始化后的提示 join 命令,记得将 master1 节点上面的 $HOME/.kube/config 文件拷贝到 node 节点对应的文件中,安装 kubeadm、kubelet、kubectl(可选),然后执行上面初始化完成后提示的 join 命令即可:
如果忘记了上面的 join 命令可以使用命令 kubeadm token create –print-join-command 重新获取。~kubeadmjoinapi.k8s.local:6443--tokenabcdef.0123456789abcdef\>--discovery-token-ca-cert-hashsha256:435fbc28490d1f897337923c19ec27bcf3639e9fe84e8448177777d23cae4176[preflight]Runningpre-flightchecks[preflight]Readingconfigurationfromthecluster...[preflight]FYI:Youcanlookatthisconfigfilewith'kubectl-nkube-systemgetcmkubeadm-config-oyaml'[kubelet-start]Writingkubeletconfigurationtofile"/var/lib/kubelet/config.yaml"[kubelet-start]Writingkubeletenvironmentfilewithflagstofile"/var/lib/kubelet/kubeadm-flags.env"[kubelet-start]Startingthekubelet[kubelet-start]WaitingforthekubelettoperformtheTLSBootstrap...Thisnodehasjoinedthecluster:*Certificatesigningrequestwassenttoapiserverandaresponsewasreceived.*TheKubeletwasinformedofthenewsecureconnectiondetails.Run'kubectlgetnodes'onthecontrol-planetoseethisnodejointhecluster.
执行成功后运行 get nodes 命令:
~kubectlgetnodesNAMESTATUSROLESAGEVERSIONmaster1Readycontrol-plane,master9m18sv1.22.1master2Readycontrol-plane,master7m11sv1.22.1master3Readycontrol-plane,master5m9sv1.22.1node1NotReady<none>24sv1.22.1
可以看到是 NotReady 状态,这是因为还没有安装网络插件,接下来安装网络插件,可以在文档 https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/ 中选择我们自己的网络插件,这里我们安装 flannel:
~wgethttps://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml#如果有节点是多网卡,则需要在资源清单文件中指定内网网卡#搜索到名为kube-flannel-ds的DaemonSet,在kube-flannel容器下面~vikube-flannel.yml......containers:-name:kube-flannelimage:quay.io/coreos/flannel:v0.14.0command:-/opt/bin/flanneldargs:---ip-masq---kube-subnet-mgr---iface=eth0#如果是多网卡的话,指定内网网卡的名称......~kubectlapply-fkube-flannel.yml#安装flannel网络插件
隔一会儿查看 Pod 运行状态:
当我们部署完网络插件后执行 ifconfig 命令,正常会看到新增的cni0与flannel1这两个虚拟设备,但是如果没有看到cni0这个设备也不用太担心,我们可以观察/var/lib/cni目录是否存在,如果不存在并不是说部署有问题,而是该节点上暂时还没有应用运行,我们只需要在该节点上运行一个 Pod 就可以看到该目录会被创建,并且cni0设备也会被创建出来。~kubectlgetpods-nkube-systemNAMEREADYSTATUSRESTARTSAGEcoredns-7568f67dbd-lvcd51/1Running030mcoredns-7568f67dbd-shfrk1/1Running030metcd-master11/1Running045metcd-master21/1Running045metcd-master31/1Running1(46mago)54mkube-apiserver-master11/1Running4(45mago)58mkube-apiserver-master21/1Running2(45mago)56mkube-apiserver-master31/1Running1(46mago)54mkube-controller-manager-master11/1Running15(48mago)58mkube-controller-manager-master21/1Running1(47mago)56mkube-controller-manager-master31/1Running054mkube-flannel-ds-4js7f1/1Running038mkube-flannel-ds-hch261/1Running038mkube-flannel-ds-l6xzv1/1Running038mkube-flannel-ds-qpzqq1/1Running038mkube-proxy-fpxp81/1Running054mkube-proxy-qdsfq1/1Running056mkube-proxy-ww9b21/1Running058mkube-proxy-zcw981/1Running050mkube-scheduler-master11/1Running15(48mago)58mkube-scheduler-master21/1Running056mkube-scheduler-master31/1Running1(47mago)54mkube-vip-master11/1Running2(48mago)58mkube-vip-master21/1Running1(47mago)55mkube-vip-master31/1Running051m
网络插件运行成功了,node 状态也正常了:
~kubectlgetnodesNAMESTATUSROLESAGEVERSIONmaster1Readycontrol-plane,master9m18sv1.22.1master2Readycontrol-plane,master7m11sv1.22.1master3Readycontrol-plane,master5m9sv1.22.1node1Ready<none>24sv1.22.1
测试高可用
上面我们搭建了3个 master 节点的高可用 Kubernetes 集群,接下来我们来测试下高可用是否生效。
首先查看其中任一个 kube-vip 的 Pod 日志:
~kubectllogs-fkube-vip-master1-nkube-systemtime="2021-09-07T08:53:24Z"level=infomsg="serverstarted"time="2021-09-07T08:53:24Z"level=infomsg="StartingKube-vipManagerwiththeARPengine"time="2021-09-07T08:53:24Z"level=infomsg="Namespace[kube-system],Hybridmode[true]"time="2021-09-07T08:53:24Z"level=infomsg="Beginningclustermembership,namespace[kube-system],lockname[plndr-svcs-lock],id[master1]"I090708:53:24.2056691leaderelection.go:243]attemptingtoacquireleaderleasekube-system/plndr-svcs-lock...time="2021-09-07T08:53:24Z"level=infomsg="Beginningclustermembership,namespace[kube-system],lockname[plndr-cp-lock],id[master1]"I090708:53:24.2061621leaderelection.go:243]attemptingtoacquireleaderleasekube-system/plndr-cp-lock.........time="2021-09-07T08:55:55Z"level=infomsg="Node[master3]isassumingleadershipofthecluster"time="2021-09-07T08:55:55Z"level=infomsg="newleaderelected:master3"
可以看到 master3 现在是我们的 Leader,接下来我们将 master3 节点关掉,然后观察另外的 kube-vip 的日志变化:
~kubectllogs-fkube-vip-master2-nkube-system......time="2021-09-07T08:55:55Z"level=infomsg="Node[master3]isassumingleadershipofthecluster"time="2021-09-07T08:55:55Z"level=infomsg="newleaderelected:master3"time="2021-09-07T10:28:58Z"level=infomsg="Node[master1]isassumingleadershipofthecluster"......
可以看到 master1 节点获取了 kube-vip 的 Leader,也就是这个时候 vip 是绑定到 master1 节点的,而且这个时候集群仍然可以正常访问的。
Dashboard
v1.22.1 版本的集群需要安装最新的 2.0+ 版本的 Dashboard:
#推荐使用下面这种方式~wgethttps://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml~virecommended.yaml#修改Service为NodePort类型......kind:ServiceapiVersion:v1metadata:labels:k8s-app:kubernetes-dashboardname:kubernetes-dashboardnamespace:kubernetes-dashboardspec:ports:-port:443targetPort:8443selector:k8s-app:kubernetes-dashboardtype:NodePort#加上type=NodePort变成NodePort类型的服务......
直接创建:
~kubectlapply-frecommended.yaml
新版本的 Dashboard 会被默认安装在 kubernetes-dashboard 这个命名空间下面:
~kubectlgetpods-nkubernetes-dashboard-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESdashboard-metrics-scraper-856586f554-pllvt1/1Running024m10.88.0.7master<none><none>kubernetes-dashboard-76597d7df5-829981/1Running021m10.88.0.2node2<none><none>
我们仔细看可以发现上面的 Pod 分配的 IP 段是 10.88.xx.xx,包括前面自动安装的 CoreDNS 也是如此,我们前面不是配置的 podSubnet 为 10.244.0.0/16 吗?我们先去查看下 CNI 的配置文件:
~ls-la/etc/cni/net.d/total8drwxr-xr-x21001docker67Aug3116:45.drwxr-xr-x.31001docker19Jul3001:13..-rw-r--r--11001docker604Jul3001:1310-containerd-net.conflist-rw-r--r--1rootroot292Aug3116:4510-flannel.conflist
可以看到里面包含两个配置,一个是 10-containerd-net.conflist,另外一个是我们上面创建的 Flannel 网络插件生成的配置,我们的需求肯定是想使用 Flannel 的这个配置,我们可以查看下 containerd 这个自带的 cni 插件配置:
~cat/etc/cni/net.d/10-containerd-net.conflist{"cniVersion":"0.4.0","name":"containerd-net","plugins":[{"type":"bridge","bridge":"cni0","isGateway":true,"ipMasq":true,"promiscMode":true,"ipam":{"type":"host-local","ranges":[[{"subnet":"10.88.0.0/16"}],[{"subnet":"2001:4860:4860::/64"}]],"routes":[{"dst":"0.0.0.0/0"},{"dst":"::/0"}]}},{"type":"portmap","capabilities":{"portMappings":true}}]}
可以看到上面的 IP 段恰好就是 10.88.0.0/16,但是这个 cni 插件类型是 bridge 网络,网桥的名称为 cni0:
~ipa...6:cni0:<BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP>mtu1500qdiscnoqueuestateUPgroupdefaultqlen1000link/ether9a:e7:eb:40:e8:66brdff:ff:ff:ff:ff:ffinet10.88.0.1/16brd10.88.255.255scopeglobalcni0valid_lftforeverpreferred_lftforeverinet62001:4860:4860::1/64scopeglobalvalid_lftforeverpreferred_lftforeverinet6fe80::98e7:ebff:fe40:e866/64scopelinkvalid_lftforeverpreferred_lftforever...
但是使用 bridge 网络的容器无法跨多个宿主机进行通信,跨主机通信需要借助其他的 cni 插件,比如上面我们安装的 Flannel,或者 Calico 等等,由于我们这里有两个 cni 配置,所以我们需要将 10-containerd-net.conflist 这个配置删除,因为如果这个目录中有多个 cni 配置文件,kubelet 将会使用按文件名的字典顺序排列的第一个作为配置文件,所以前面默认选择使用的是 containerd-net 这个插件。
~mv/etc/cni/net.d/10-containerd-net.conflist/etc/cni/net.d/10-containerd-net.conflist.bak~ifconfigcni0down&&iplinkdeletecni0~systemctldaemon-reload~systemctlrestartcontainerdkubelet
然后记得重建 coredns 和 dashboard 的 Pod,重建后 Pod 的 IP 地址就正常了:
~kubectlgetpods-nkubernetes-dashboard-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATESdashboard-metrics-scraper-856586f554-tp8m51/1Running042s10.244.1.6node2<none><none>kubernetes-dashboard-76597d7df5-9rmbx1/1Running066s10.244.1.5node2<none><none>~kubectlgetpods-nkube-system-owide-lk8s-app=kube-dnsNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEREADINESSGATEScoredns-7568f67dbd-n7bfx1/1Running05m40s10.244.1.2node2<none><none>coredns-7568f67dbd-plrv81/1Running03m47s10.244.1.4node2<none><none>
查看 Dashboard 的 NodePort 端口:
~kubectlgetsvc-nkubernetes-dashboardNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGEdashboard-metrics-scraperClusterIP10.99.37.172<none>8000/TCP25mkubernetes-dashboardNodePort10.103.102.27<none>443:31050/TCP25m
然后可以通过上面的 31050 端口去访问 Dashboard,要记住使用 https,Chrome 不生效可以使用Firefox 测试,如果没有 Firefox 下面打不开页面,可以点击下页面中的信任证书即可:
信任证书
信任后就可以访问到 Dashboard 的登录页面了:
Dashboard 登录页面
然后创建一个具有全局所有权限的用户来登录 Dashboard:
#admin.yamlkind:ClusterRoleBindingapiVersion:rbac.authorization.k8s.io/v1metadata:name:adminroleRef:kind:ClusterRolename:cluster-adminapiGroup:rbac.authorization.k8s.iosubjects:-kind:ServiceAccountname:adminnamespace:kubernetes-dashboard---apiVersion:v1kind:ServiceAccountmetadata:name:adminnamespace:kubernetes-dashboard
直接创建:
~kubectlapply-fadmin.yaml~kubectlgetsecret-nkubernetes-dashboard|grepadmin-tokenadmin-token-lwmmxkubernetes.io/service-account-token31d~kubectlgetsecretadmin-token-lwmmx-ojsonpath={.data.token}-nkubernetes-dashboard|base64-d#会生成一串很长的base64后的字符串
然后用上面的 base64 解码后的字符串作为 token 登录 Dashboard 即可,新版本还新增了一个暗黑模式:
k8s dashboard
最终我们就完成了使用 kubeadm 搭建 v1.22.1 版本的高可用 kubernetes 集群,使用 coredns、ipvs、flannel、containerd、kube-vip 这些组件。
清理
如果你的集群安装过程中遇到了其他问题,我们可以使用下面的命令来进行重置:
~kubeadmreset~ifconfigcni0down&&iplinkdeletecni0~ifconfigflannel.1down&&iplinkdeleteflannel.1~rm-rf/var/lib/cni/