Cheyne's Blog


  • Home
  • Archive
  • Categories
  •    

© 2026 John Doe

Theme Typography by Makito

Proudly published with Hexo

从0到1:搭建一个完整的 Kubernetes 集群

Posted at 2025-02-02 Kubernetes 

部署 Kubernetes 的 Master 节点

在上篇文章中介绍了如何使用 kubeadm 一键部署 Master 节点。不过,既然要在本篇文章中部署一个“完整”的 Kubernetes 集群,那不妨提高一下难度:通过配置文件来开启一些新功能。

所以,这里编写了一个 kubeadm.yaml 文件:

1
2
3
4
5
6
7
8
9
apiVersion: kubeadm.k8s.io/v1alpha1
kind: MasterConfiguration
controllerManagerExtraArgs:
horizontal-pod-autoscaler-use-rest-clients: "true"
horizontal-pod-autoscaler-sync-period: "10s"
node-monitor-grace-period: "10s"
apiServerExtraArgs:
runtime-config: "api/all=true"
kubernetesVersion: "stable-1.11"

由于本课程使用的 kubeadm 版本比较旧,因此在这里做了新版本的适配。具体应该切换到什么版本可以使用指令 kubeadm config print init-defaults 查看。
修改后的 kubeadm.yaml 文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controllerManager:
extraArgs:
- name: "horizontal-pod-autoscaler-use-rest-clients"
value: "true"
- name: "horizontal-pod-autoscaler-sync-period"
value: "10s"
- name: "node-monitor-grace-period"
value: "10s"
apiServer:
extraArgs:
- name: runtime-config
value: "api/all=true"
kubernetesVersion: "1.32.0"

在这里,给 kube-controller-manager 设置了:

1
horizontal-pod-autoscaler-use-rest-clients: "true"

这意味着将来部署的 kube-controller-manager 能够使用自定义资源(Custom Metrics)进行自动水平扩展。

然后,只需要执行一句指令:

1
$ kubeadm init --config kubeadm.yaml

就可以完成 Master 节点的部署了。
部署完成后,kubeadm 会生成一行指令:

1
2
kubeadm join 9.135.86.87:6443 --token 4dy57l.zgnk637pgkiykmc0 \
--discovery-token-ca-cert-hash sha256:7aebe209176c11510de2b967ee984f9591a5270dde84beb9a623b567b405c04e

这个 kubeadm join 指令,就是用来给这个节点添加更多工作节点(Worker)的。在后面部署 Worker 节点的时候马上会用到它。

此外,kubeadm 还会提示我们第一次使用 Kubernetes 集群所需要的配置命令:

1
2
3
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

需要这些配置文件的原因是:Kubernetes 集群默认需要加密方式访问。所以这几条命令就是将刚刚部署生成的 Kubernetes 集群的安全配置文件,保存到当前用户的 .kube 目录下,kubectl 默认会使用这个目录下的授权信息访问 Kubernetes 集群。

如果不这样做的话,我们每次都需要通过 export KUBECONFIG 环境变量告诉 kubectl 这个安全配置文件的位置。

现在,我们就可以使用 kubectl get 命令来查看当前节点的状态了:

1
2
3
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
vm-86-87-tencentos NotReady control-plane 153m v1.32.0

注意到此时节点的 ROLE 为 control-plane 而不是 master,实际上只是过去的 master 节点名称改为了现在的 control-plane

可以看到,此时的输出结果里,节点的状态是 NotReady,这是为什么呢?

在调试 Kubernetes 集群时,最重要的手段就是用 kubectl describe 来查看这个节点对象的详细信息、状态和事件:

1
2
3
4
5
6
7
$ kubectl describe node vm-86-87-tencentos

...
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
Ready False Wed, 08 Jan 2025 15:07:38 +0800 Wed, 08 Jan 2025 12:06:28 +0800 KubeletNotReady container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized

可以看到,NodeNotReady 的原因在于我们尚未部署任何的网络插件。

另外,我们还可以通过 kubectl 检查这个节点上各个系统 Pod 的状态,其中 kube-system 是 Kubernetes 项目预留的系统 Pod 的工作空间:

1
2
3
4
5
6
7
8
9
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-668d6bf9bc-k2sqz 0/1 Pending 0 13h
coredns-668d6bf9bc-ndkrg 0/1 Pending 0 13h
etcd-vm-86-87-tencentos 1/1 Running 8 13h
kube-apiserver-vm-86-87-tencentos 1/1 Running 8 13h
kube-controller-manager-vm-86-87-tencentos 1/1 Running 0 13h
kube-proxy-bkl6j 1/1 Running 0 13h
kube-scheduler-vm-86-87-tencentos 1/1 Running 8 13h

可以看到,CoreDNS 调度失败了。这是符合预期的,因为这个 Master 节点的网络尚未就绪。

在 Kubernetes 项目“一切皆容器”的设计理念指导下,部署网络插件非常简单,只需要执行一句 kubectl apply 指令,以 Weave 为例:

1
$ kubectl apply -f https://github.com/weaveworks/weave/releases/download/v2.8.1/weave-daemonset-k8s.yaml

按道理来说,这里装完网络插件后所有 Pod 都应该是正常的状态,但是试了很多次,装了各种 CNI 插件都没有解决问题,查日志才发现是起 Master 节点的时候给 ControllerManager 指定了 horizontal-pod-autoscaler-use-rest-clients 这个参数,但是这个参数从 1.21 版本开始就不支持了… 所以需要在 kubeadm.yaml 文件中去掉这个配置项并重启 Master。
kubernetes github issue

并且在安装完 Weave 后,并不能直接让 CoreDNS 运行起来,需要重启一下服务:systemctl restart containerd.service

部署完成后,我们可以通过 kubectl get 重新检查 Pod 的状态:

1
2
3
4
5
6
7
8
9
10
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-668d6bf9bc-k2sqz 1/1 Running 0 13h
coredns-668d6bf9bc-ndkrg 1/1 Running 0 13h
etcd-vm-86-87-tencentos 1/1 Running 8 13h
kube-apiserver-vm-86-87-tencentos 1/1 Running 8 13h
kube-controller-manager-vm-86-87-tencentos 1/1 Running 0 13h
kube-proxy-bkl6j 1/1 Running 0 13h
kube-scheduler-vm-86-87-tencentos 1/1 Running 8 13h
weave-net-t8r26 2/2 Running 0 18m

可以看到,所有的系统 Pod 都启动成功了,而刚刚部署的 Weave 网络插件则在 kube-system 下面新建了一个名叫 weave-net-t8r26 的 Pod,一般来说,这些 Pod 就是容器网络插件在每个节点上的控制组件。

Kubernetes 支持容器网络插件,使用的是一个 CNI 通用接口,它也是当前容器网络的事实标准,市面上的所有容器网络开源项目都可以通过 CNI 接入 Kubernetes,比如 Flannel、Calico、Canal、Romana等。

至此,Kubernetes 的 Master 节点就部署完成了。不过在默认情况下,Kubernetes 的 Master 节点是不能运行用户 Pod 的,所以还需要额外做一个小操作。

部署 Kubernetes 的 Worker 节点

Kubernetes 的 Worker 节点跟 Master 节点几乎是相同的,它们运行着的都是一个 kubelet 组件。唯一的区别在于,在 kubeadm init 的过程中,kubelet 启动后,Master 节点上还会自动运行 kube-apiserver、kube-scheduler、kube-controller-manager 这三个系统 Pod。
所以相比之下,部署 Worker 节点反而是最简单的,只需要两步:

  1. 在 Worker 节点安装 Docker 和 kubeadm 相关环境
  2. 执行部署 Master 节点时生成的 kubeadm join 指令

通过 Taint/Toleration 调整 Master 执行 Pod 的策略

在前面提到过,默认情况下 Master 节点是不允许运行用户 Pod 的。而 Kubernetes 做到这一点,依赖的是 Taint/Toleration 机制。
它的原理很简单:一旦某个节点被加上了一个 Taint,即“被打上了污点”,那么所有 Pod 将都不能在这个节点上运行,因为 Kubernetes 的 Pod 都有“洁癖”。除非有个别的 Pod 声明自己能“容忍”这个“污点”,即声明了 Toleration,它才可以在这个节点上运行。
其中为节点打上“污点”(Taint)的命令是:

1
$ kubectl taint nodes node1 foo=bar:NoSchedule

这时,该 node1 节点上就会增加一个键值对格式的 Taint。其中值里面的 NoSchedule,意味着这个 Taint 只会在调度新 Pod 时产生作用,而不会影响已经在 node1 上运行的 Pod,哪怕它们没有 Toleration。

那么 Pod 如何声明 Toleration 呢?
我们只需要在 Pod 的 yaml 文件中的 spec 部分,加入 tolerations 字段即可:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: "foo"
operator: "Equal"
value: "bar"
effect: "NoSchedule"

现在,回到我们已经搭建好的集群上来。这时如果你通过 kubectl describe 检查一下 Master 节点的 Taint 字段,就会有所发现了:

1
2
3
4
$ kubectl describe node master
...
Taints: node-role.kubernetes.io/control-plane:NoSchedule
...

可以看到,Master 节点默认被加上了 node-role.kubernetes.io/control-plane:NoSchedule 这样一个“污点”,其中“键”是 node-role.kubernetes.io/control-plane,而没有提供“值”。

此时,你就需要像下面这样用 Exist 操作符来说明,该 Pod 能容忍所有以 foo 为键的 Taint,才能让这个 Pod 运行在该 Master 节点上:

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Pod
...
spec:
tolerations:
- key: "foo"
operator: "Exists"
effect: "NoSchedule"

此时,如果你想要一个单节点的 Kubernetes,删除这个 Taint 才是正确的选择:

1
$ kubectl taint nodes --all node-role.kubernetes.io/master-

如上所示,在 node-role.kubernetes.io/master 这个键后面加上了一个“-”,意味着移除所有以 node-role.kubernetes.io/master 为键的 Taint。

到了这一步,一个基本完整的 Kubernetes 集群就部署完毕了。接下来,我们再在这个 Kubernetes 集群上安装一些其他的辅助插件,比如 Dashboard 和存储插件。

部署 Dashboard 可视化插件

在 Kubernetes 社区中,有一个很受欢迎的 Dashboard 项目,它可以给用户提供一个可视化的 Web 界面来查看当前集群的各种信息。

在当前最新版本的 Dashboard 中只提供了基于 helm 的安装方式,而笔者学习过程中还没有接触过 helm 相关的知识,因此暂时不对此插件进行部署

部署容器存储插件

在前面介绍容器原理时提到过,很多时候我们需要用数据卷把外面宿主机上的目录或者文件挂载进容器的 Mount Namespace 中,从而达到容器和宿主机共享这些目录或者文件的目的。容器里的应用,也就可以在这些数据卷中新建和写入文件。
可是,如果你在某台机器上启动了一个容器,显然无法看到其他机器上的容器在它们的数据卷中写入的文件。这是容器最典型的特征之一:无状态。
而容器的持久化存储,就是用来保存容器存储状态的重要手段:存储插件会在容器里挂载一个基于网络或者其他机制的远程数据卷,使得在容器里创建的文件,实际上是保存在远程存储服务器上,或者以分布式的方式保存在多个节点上,而与当前的宿主机没有如何的绑定关系。这样,无论你在其他哪个宿主机上启动新的容器,都可以请求挂载指定的持久化存储卷,从而访问到数据卷里保存的内容。这就是“持久化”的含义。
由于 Kubernetes 本身的松耦合设计,绝大多数存储项目,比如 Ceph、GlusterFS、NFS 等,都可以为 Kubernetes 提供持久化存储能力。在这里选择了一个很重要的 Kubernetes 存储插件项 Rook 进行部署:

1
2
3
4
$ git clone --single-branch --branch v1.16.1 https://github.com/rook/rook.git 
$ cd rook/deploy/examples
$ kubectl create -f crds.yaml -f common.yaml -f operator.yaml
$ kubectl create -f cluster.yaml

部署完成后,可以看到 Rook 项目会将自己的 Pod 放置在由它自己管理的 Namespace 当中:

1
2
3
4
5
6
7
$ kubectl get pods -n rook-ceph
NAME READY STATUS RESTARTS AGE
csi-cephfsplugin-brtz7 3/3 Running 1 (47s ago) 90s
csi-cephfsplugin-provisioner-7f7ccd5b5-gc844 6/6 Running 1 (40s ago) 90s
csi-rbdplugin-m7cps 3/3 Running 1 (53s ago) 91s
csi-rbdplugin-provisioner-6f4b75cb65-zhcfb 6/6 Running 1 (47s ago) 91s
rook-ceph-operator-8447d8546f-gtr4f 1/1 Running 0 15m

这样,一个基于 Rook 的持久化存储集群就以容器的方式运行起来了,而接下来在 Kubernetes 项目上创建的所有 Pod 就能够通过 Persistent Volume(PV)和 Persistent Volume Claim(PVC)的方式,在容器里挂载由 Ceph 提供的数据卷了。
而 Rook 项目,则会负责这些数据卷的生命周期管理、灾难备份等运维工作。

Share 

 Previous post: 牛刀小试:我的第一个容器化应用 Next post: 从容器到容器云:谈谈Kubernetes的本质 

© 2026 John Doe

Theme Typography by Makito

Proudly published with Hexo