2.2 部署分布式Kubernetes集群
本节会尝试带领读者一起在本地独立的主机环境(独立管理的虚拟机或物理服务器)上搭建一个完整意义上的多节点、分布式Kubernetes集群。该集群能够完全模拟出真实生产环境的使用需求,并为本书后续章节中的示例提供基础实验环境。
2.2.1 准备基础环境
截至目前,kubeadm可以支持在Ubuntu 16.04+、Debian 9+、CentOS 7与RHEL 7、Fedora 25+、HypriotOS v1.0.1+和Container Linux等系统环境上构建Kubernetes集群。部署的基础环境要求还包括每个独立的主机应该有2GB以上的内存及2个以上的CPU核心,有唯一的主机名、MAC地址和产品标识(product_uuid),禁用了Swap设备且各主机彼此间具有完整意义上的网络连通性。
注意
当前版本(随Kubernetes v1.19发行)的kubeadm与较新Linux发行版系统环境上的nftables兼容性不好,用户需要事先将nftables转为传统的iptables模式,这类Linux发行版包括Debian 10、Ubuntu 19.04和Fedora 29和RHEL 8等。具体部署方法请参考相关文档。
部署Kubernetes集群的基本前提是准备好所需要的机器,本节的示例中会用到4个主机,如表2-2所示,其中k8s-master01是控制平面节点,另外3个是工作节点。
下面讲解具体的基础环境的准备工作。
1. 基础系统环境设置
Kubernetes项目目前仍然处于快速迭代阶段,对Kubernetes的后续版本来说,示例中环境要求和配置方式可能会存在某些变动,因此读者测试使用的版本若与示例不同,具体特性的变动需要进一步参考Kubernetes的ChangeLog或其他相关文档中的说明。为了便于读者确认各配置和功能的可用性,本书示例中使用的操作系统、容器引擎、etcd及Kubernetes的相关版本如下。
▪操作系统:Ubuntu 18.04 x86_64。
▪容器运行时引擎:Docker 19.03.ce。
▪Kubernetes:v1.19。
时间同步服务与名称解析服务是规模化网络环境的基础设施,Kubernetes集群的正确运行同样依赖于这些基础设施,例如各节点时间通过网络时间服务保持同步,通过DNS服务进行各主机名称解析等。为简单起见,本示例中的时间同步服务直接基于系统的默认配置:从互联网的时间服务获取,主机名称解析则由hosts文件进行。
(1)主机时间同步
如果各主机可直接访问互联网,则直接启动各主机上的chronyd服务即可。否则需要使用本地网络中的时间服务器,例如可以把Master配置为chrony server,而后其他节点均从Master同步时间。
~$ sudo systemctl start chronyd.service ~$sudo systemctl enable chronyd.service
(2)各节点防火墙设定
各Node运行的kube-proxy组件要借助iptables或ipvs构建Service资源对象,该资源对象是Kubernetes的核心资源类型之一。出于简化问题复杂度之需,这里事先关闭所有主机之上的iptables相关的服务。
~$ sudo ufw disable && sudo ufw status
(3)禁用Swap设备
系统内存资源吃紧时,Swap能在一定程度上起到缓解作用,但Swap是磁盘上的空间,性能与内存相差很多,进而会影响Kubernetes调度和编排应用程序运行的效果,因而需要将其禁用。
~$ sudo swapoff -a
注意
永久禁用Swap设备需要修改/etc/fstab配置文件,注释所有文件系统类型为swap的配置行。
(4)确保MAC地址及product_id的唯一性
一般来讲,网络接口卡会拥有唯一的MAC地址,但是在大规模虚拟化环境中,有些虚拟机的MAC地址可能会重复。Kubernetes使用这些信息来唯一标识集群中的节点,因此非唯一的MAC地址或product_id可能会导致安装失败。建议用户在部署或模板化生成主机及操作系统时直接规避这些问题,或借助Ansible这类编排工具收集并对比排查此类问题。
2. 配置容器运行引擎
kubelet基于CRI插件体系支持Docker、containerd、frakti和CRI-O等多种类型的容器引擎,而自1.14.0版本起,kubeadm将通过监测已知的UNIX Domain Sock自动检测Linux系统上的容器引擎。若同时检测到Docker和containerd,则优先选择Docker,因为Docker自18.09版本起附带了containerd,并且两者都可以被检测到。如果同时检测到其他两个或多个容器运行引擎时,kubeadm将返回一个错误信息并退出。本示例将使用目前应用最为广泛的Docker-CE引擎。
1)更新apt索引信息后安装必要的程序包。
~$ sudo apt update ~$ sudo apt install apt-transport-https ca-certificates \ curl gnupg-agent software-properties-common
2)添加Docker官方的GPG证书,以验证程序包签名,代码如下。
~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
3)为apt添加稳定版本的Docker-CE仓库。
~$ sudo add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable"
提示
为了提升程序包下载速度,建议读者使用Docker-CE在国内的镜像仓库,例如将上述链接https://download.docker.com/linux/ubuntu替换为https://mirrors.aliyun.com/docker-ce/linux/ubuntu,即可使用阿里云的镜像。
4)更新apt索引后安装docker-ce。
~$ sudo apt update ~$ sudo apt install docker-ce docker-ce-cli containerd.io
5)配置Docker。编辑配置文件/etc/docker/daemon.json,并确保存在如下配置内容。
{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" }
6)启动Docker服务,并设置服务可随系统引导启动。
~$ sudo systemctl daemon-reload ~$ sudo systemctl -start docker.service ~$ sudo systemctl -enable docker.service
提示
国内访问DockerHub下载镜像的速度较缓慢,建议使用国内的镜像对其进行加速,例如https://registry.docker-cn.com。另外,中国科技大学也提供了公共可用的镜像加速服务,其URL为https://docker.mirrors.ustc.edu.cn,将其定义在daemon.json中重启Docker即可使用。
3. 安装kubeadm、kubelet和kubectl
kubeadm不会自行安装或者管理kubelet和kubectl,所以需要用户确保它们与通过kubeadm安装的控制平面的版本相匹配,否则可能存在版本偏差的风险,从而导致一些预料之外的错误和问题。
Google提供的kubeadm、kubelet和kubectl程序包的仓库托管在Google站点的服务器主机上,访问起来略有不便。幸运的是,目前国内的阿里云镜像站(http://mirrors.aliyun.com)及Azure镜像站(http://mirror.azure.cn/)等为此项目提供了镜像服务,用户可自行选择其中一种。下面的安装步骤是基于阿里云镜像服务进行的。
1)更新apt索引信息后安装必要的程序包:
~$ sudo apt update && apt install -y apt-transport-https
2)添加Kubernetes官方程序密钥:
~$ sudo curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -
3)在配置文件/etc/apt/sources.list.d/kubernetes.list中添加如下内容,为apt添加Kubernetes程序包仓库:
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
4)更新程序包索引并安装程序包:
~$ sudo apt update ~$ sudo apt install -y kubelet kubeadm kubectl
提示
若期望安装指定版本的kubelet、kubeadm和kubectl程序包,则需要为每个程序包单独指定发布的版本号(各程序包的版本号应该相同),格式为PKG_NAME-VERSION-RELEASE。例如,对1.18.8版本来说,需要使用的命令为apt install -y kubelet=1.18.8-00 kubeadm=1.18.8-00 kubectl=1.18.8-00。
需要再次说明的是,不同版本的Kubernetes在功能特性、支持的API群组及版本方面存在不小的差异。本书的大部分内容以长达一年支持周期的1.19版本为基础,同时兼顾1.18版本。读者在基于本书的内容进行应用实践时,建议先采用1.18或1.19版本的Kubernetes,以免因版本变动导致的差异引起测试错误,待使用熟练后再尝试新版本的新特性。
2.2.2 单控制平面集群
多数情况下,两个及以上独立运行的Node主机即可测试分布式集群的核心功能,因此其数量可按需定义,但两个主机是模拟分布式环境的最低需求。本节将使用kubeadm部署由1个Master主机和3个Node主机组成的Kubernetes集群,用到的各主机分别是k8s-master01、k8s-node01、k8s-node02和k8s-node3,各主机和网络规划等如图2-8所示。
分布式系统环境中的多主机通信通常基于主机名称进行,需要为主机提供固定的访问入口,而IP地址存在变化的可能,因此一般要有专用的DNS服务负责解析各节点主机名。为了降低系统的复杂度,这里将采用hosts文件进行主机名称解析,因此需要编辑Master和各Node上的/etc/hosts文件,确保其内容类似如下所示。
172.29.9.1 k8s-master01.ilinux.io k8s-master01 k8s-api.ilinux.io 172.29.9.11 k8s-node01.ilinux.io k8s-node01 172.29.9.12 k8s-node02.ilinux.io k8s-node02 172.29.9.13 k8s-node03.ilinux.io k8s-node03
1. 初始化控制平面
kubeadm init初始化的控制平面组件kube-apiserver、kube-controller-manager和kube-scheduler,以及集群状态存储系统etcd均以静态Pod方式运行,相关镜像的默认获取仓库位于gcr.io站点。因某些原因,该站点上的服务通常需要借助国内的镜像服务完成,诸如gcr.azk8s.cn/google_containers和registry.aliyuncs.com/google_containers等。然而,这些镜像站点上的镜像同步很可能会晚于gcr.io站点一定的时长,迫切需要较新版本的读者可能需要借助其他途径完成镜像下载才能执行下面的初始化步骤。
kubeadm init命令可从命令行选项读取简单配置参数,它也支持使用配置文件进行精细化配置设定。下面给出以命令行选项方式进行初始化的常用命令格式,并在k8s-master01主机上运行。
~$ sudo kubeadm init \ --image-repository registry.aliyuncs.com/google_containers \ --kubernetes-version v1.19.0 \ --control-plane-endpoint k8s-api.ilinux.io \ --apiserver-advertise-address 172.29.9.1 \ --pod-network-cidr 10.244.0.0/16 \ --token-ttl 0
上面命令的选项及参数设置决定了集群运行环境的众多特性设定,这些设定对于此后在集群中部署、运行应用程序至关重要。
▪--image-repository:指定要使用的镜像仓库,默认为gcr.io。
▪--kubernetes-version:Kubernetes程序组件的版本号,它必须与安装的kubelet程序包的版本号相同。
▪--control-plane-endpoint:控制平面的固定访问端点,可以是IP地址或DNS名称,作为集群管理员与集群组件的kubeconfig配置文件的API Server的访问地址。单控制平面部署时可以不使用该选项。
▪--pod-network-cidr:Pod网络的地址范围,其值为CIDR格式的网络地址,通常Flannel网络插件的默认值为10.244.0.0/16,Project Calico插件的默认值为192.168.0.0/16。
▪--service-cidr:Service的网络地址范围,其值为CIDR格式的网络地址,默认为10.96.0.0/12。通常,仅Flannel一类的网络插件需要手动指定该地址。
▪--apiserver-advertise-address:API Server通告给其他组件的IP地址,一般为Master节点用于集群内通信的IP地址,0.0.0.0表示节点上所有可用地址。
▪--token-ttl:共享令牌的过期时长,默认为24小时,0表示永不过期。为防止不安全存储等原因导致的令牌泄露危及集群安全,建议为其设定过期时长。
提示
更多参数请参考kubeadm的文档,链接地址为https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/;我们也可以把关键的配置信息以kubeadm init配置文件形式提供,将上述各选项转为ClusterConfiguration的配置,并由kubeadm init命令的--config选项加载即可。
kubeadm init命令的执行请参考2.1.3节。上面命令执行结果的最后一部分如下所示,本示例特将需要注意的部分以粗体格式予以标识,它们是后续步骤的重要提示信息。
# 下面是成功完成第一个控制平面节点初始化的提示信息及后续需要完成的步骤 Your Kubernetes control-plane has initialized successfully! # 为了完成初始化操作,管理员需要额外手动完成几个必要的步骤 To start using your cluster, you need to run the following as a regular user: # 第1个步骤提示:Kubernetes集群管理员认证到Kubernetes # 集群时使用的kubeconfig配置文件 mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config # 第2个步骤提示:为Kubernetes集群部署一个网络插件,具体选用的插件取决于管理员 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/ # 第3个步骤提示:向集群添加其他控制平面节点;但本节会略过此步骤,具体执行过程将 # 在12.4节详细说明 You can now join any number of control-plane nodes by copying certificate authorities and service account keys on each node and then running the following as root: # 在部署好kubeadm等程序包的其他控制平面节点上,以root用户的身份运行类似如下命令, # 命令中的hash信息对于不同的部署环境来说各不相同 kubeadm join k8s-api.ilinux.io:6443 --token dnacv7.b15203rny85vendw \ --discovery-token-ca-cert-hash sha256:61ea08553de1cbe76a3f8b14322cd276c57cbe bd5369bc362700426e21d70fb8 \ --control-plane # 第4个步骤提示:向集群添加工作节点 Then you can join any number of worker nodes by running the following on each as root: # 在部署好kubeadm等程序包的各工作节点上以root用户运行类似如下命令 kubeadm join k8s-api.ilinux.io:6443 --token dnacv7.b15203rny85vendw \ --discovery-token-ca-cert-hash sha256:61ea08553de1cbe76a3f8b14322cd276c57cbe bd5369bc362700426e21d70fb8
在上面给出的命令显示结果中,控制平面已然成功完成初始化,但后面还给出了需要进一步操作的4个步骤:配置命令行工具kubectl、选定并部署一个网络插件、添加其他控制平面,以及添加工作节点。为了便于读者识别,这里把需要执行的操作以加粗字体进行标示。不过,如果暂时不打算构建高可用的控制平面,第3个步骤可以忽略。下面先完成其他3步操作,以配置出一个单控制平面的Kubernetes集群。
2. 配置命令行工具kubectl
kubectl是Kubernetes集群的最常用命令行工具,默认情况下它会搜索当前用户主目录(保存于环境变量HOME中的值)中名为.kube的隐藏目录,定位其中名为config的配置文件以读取必要的配置,包括要接入Kubernetes集群以及用于集群认证的证书或令牌等信息。使用kubeadm init命令初始化控制平面时会自动生成一个用于管理员权限的配置文件/etc/kubernetes/admin.conf,将它复制为常用用户的$HOME/.kube/config文件便可以集群管理员身份进行访问。在k8s-master01主机以普通用户身份运行如下命令。
~$ mkdir -p $HOME/.kube ~$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config ~$ sudo chown $(id -u):$(id -g) $HOME/.kube/config
接下来可通过kubectl get nodes命令获取集群节点相关的状态信息。例如,下面命令输出结果的NotReady(未就绪)状态是因为集群中尚未部署网络插件所致。
~$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master01.ilinux.io NotReady master 3m29s v1.19.0
用户可在任何能够通过k8s-api.ilinux.io与API Server通信的主机上安装kubectl,并为其复制或生成kubeconfig文件以访问控制平面,包括后面章节中部署的每个工作节点。
3. 部署Flannel网络插件
较为流行的为Kubernetes提供Pod网络的插件有Flannel、Calico和WeaveNet等。相较来说,Flannel以其简单、模式丰富、易部署、易使用等特性颇受用户欢迎。类似于CoreDNS和kube-proxy,Flannel同样可运行为Kubernetes的集群附件,由DaemonSet控制器为每个节点(包括Master)运行一个相应的Pod实例。Flannel代码托管在GitHub上,其README.md文件中给出了部署命令,如图2-9所示。
Kubernetes资源对象的创建一般要基于JSON或YAML格式的配置清单进行,图2-9中的kube-flannel.yaml便属于这类配置文件。本书后面的章节会讲解多种资源对象及其定义格式,此处只需要按提示运行相关的命令即可。
~$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
在当前节点获取到Flannel的Docker镜像并启动Pod资源对象后,该命令才算真正运行完成,当前节点也会随之转为Ready(就绪)状态。
~$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master01.ilinux.io Ready master 12m v1.19.0
提示
kubectl get pods -n kube-system | grep flannel命令的结果显示Pod的状态为Running时,表示网络插件Flannel部署完成。
4. 添加工作节点
在准备好基础环境的主机上运行kubeadm join命令便可将其加入集群中,该命令需要借助共享令牌进行首次与控制平面通信时的认证操作,相关的令牌信息及完成的命令由初始化控制平面的命令结果给出。例如,在k8s-node01上运行如下命令将其加入集群中。
~$ sudo kubeadm join k8s-api.ilinux.io:6443 --token dnacv7.b15203rny85vendw \ > --discovery-token-ca-cert-hash sha256:61ea08553de1cbe76a3f8b14322cd276c57cbebd5369bc362700426e21d70fb8
为满足后续Master与Node组件间的双向TLS认证的需求,kubeadm join命令发起的TLS Bootstrap在节点上生成私钥及证书签署请求,并提交给控制平面的CA,由其自动进行签署。随后,当前节点上的kubelet组件的相关私钥及证书文件在命令执行结束后就会自动生成,它们默认保存于/var/lib/kubelet/pki目录中,然后以之创建一个kubeconfig格式的配置文件/etc/kubernetes/kubelet.conf,供kubelet进程与API Server安全通信时使用。
等命令运行完成后,可根据命令结果的提示在k8s-master01使用kubectl get nodes命令验证集群节点状态,包括各节点的名称、状态(就绪与否)、角色(是否为Master节点)、加入集群的时长以及程序版本等信息。
~$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master01.ilinux.io Ready master 24m v1.19.0 k8s-node01.ilinux.io Ready <none> 39s v1.19.0
随后,分别在k8s-node02和k8s-node03上重复上面的步骤便可将它们也加入集群中,将来集群需要进一步扩容时,其添加过程与此处类似。当本节部署示例中设定的3个节点全部添加到集群中并启动后,再次获取节点信息的命令结果应该类似如下命令结果所示。
~$ kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master01.ilinux.io Ready master 16m v1.19.0 k8s-node01.ilinux.io Ready <none> 2m49s v1.19.0 k8s-node02.ilinux.io Ready <none> 110s v1.19.0 k8s-node03.ilinux.io Ready <none> 70s v1.19.0
需要提醒读者的是,若是在kubeadm init命令初始化控制平面时未将token设置为永不过期,则过期后再次使用token时需要通过kubeadm token命令手动重新生成。
另外,功能完整的Kubernetes集群应当具备的附加组件还包括Dashboard、Ingress Controller、Logging和Prometheus等,后续章节中的某些概念将会依赖这些组件,读者可选择在用到时再进行部署。