kubelet 关于pod方面
kubelet 是运行在每个节点上的负责启动容器的重要的守护进程。
1. 在启动时,Kubelet进程加载配置参数,向API Server 处创建一个Node 对象来注册自身的节点信息,例如操作系统、Kernel 版本、IP 地址、总容量(Capacity)和可供分配的容量(Allocatable Capacity)等。
2. 然后kubelet 须定时(默认值是每10s 通过NodeStatusUpdateFrequency 设置参数)向API Server 汇报自身情况,例如磁盘空间是否用满、CPU 和Memory 是否有压力,自身服务是否Ready 等,这些信息都将被调度器使用,在调度Pod 时给节点打分。
kube-scheduler :
(Kubernetes 的默认调度器还要负责对调度器缓存(即:scheduler cache)进行更新。事实上,Kubernetes 调度部分进行性能优化的一个最根本原则,就是尽最大可能将集群信息 Cache 化,以便从根本上提高 Predicate 和 Priority 调度算法的执行效率。)
Predicates 算法需要的 Node 信息,都是从 Scheduler Cache 里直接拿到的,这是调度器保证算法执行效率的主要手段之一。
3. 如果kubelet停止汇报这些信息,那么NodeLifecycle 控制器将认为kubelet 已经不能正常工作,会将Node 状态设置为Unknown,并在一段时间后开始驱逐其上的Pod 对象。
- 普通Pod,也就是通过API Server 创建的Pod,是被Scheduler 调度到该节点上的。
- 静态Pod 是不经过API Server的 ,kubelet 通过观测本地目录或者HTTP URL 下的定义文件所创建的Pod。静态Pod 始终绑定到kubelet 所在的节点上。
在创建容器之前,kubelet 首先会调用容器运行时为该Pod 创建容器沙箱,容器运行时为容器沙箱设置网络环境。当容器沙箱成功启动后,kubelet 才会调用容器运行时在该容器沙箱的网络命名空间(Net Namespace)中创建和启动容器。用户的容器可能因为各种原因退出,但是因为有容器沙箱存在,容器的网络命名空间不会被摧毁,当重新创建用户容器时,无须再为它设置网络了。
[root@node1 ~]# cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container-image=kubesphere/pause:3.2 --node-status-update-frequency=5s"
[root@node1 ~]# docker ps | grep pause
beadb8451a56 kubesphere/pause:3.2 "/pause" 21 minutes ago Up 21 minutes k8s_POD_grafana-5d794f46d5-5nk4t_monitor_3709e4a0-9b61-4afa-a103-2d20073dfed2_0
d4d323887a49 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_rollingupdate-strategy-76bc87cfbf-zjmtp_devops_ae80edc5-0ed4-40a1-805e-c44c6c46ff53_15
9211be42e3d1 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_hostnames-766c597cd8-gxrdj_default_3bcc5430-96ee-4109-a9cd-dd3a59b68fbd_14
76c9ae5edcfb kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_example-pv-pod_default_9b748a48-8ce2-4739-85bb-34be8be4631f_4
8fa855d39993 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_nginx-green-7c47d45784-qkmjc_devops_4ca2b07b-6155-4079-8949-c251aecb6d28_7
a3a579120858 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_hostnames-766c597cd8-2gfnr_default_4665618b-4ed9-4748-8be1-29f64feda83e_15
bd1d7173ef44 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_example-app-7d95cbf666-l8msj_default_2ab21d1a-e08f-4595-baa4-d83c146ca4f0_12
922e161e53c7 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_dns-test_default_dd07f32b-b42b-4347-acbf-9367c3aea488_39
01638ddab8de kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_rollingupdate-strategy-76bc87cfbf-t5hn7_devops_7fa7751d-45fb-4047-a5e7-4724697a8e8d_17
abae2badf8fb kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_eureka-2_ms_c7654d45-14ba-4b7b-8bae-3e4714c082af_38
0a8f3be703e8 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_localprovi-provisioner-nfztt_kube-system_b24c3f0e-9b71-417d-88f4-c1be40ed7f99_4
4044da91a280 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_hostnames-766c597cd8-mbfdk_default_4646cd67-04f1-4e5d-ab5a-08b80f978ade_13
4debbbff4026 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_alertmanager-inst-0_monitoring_53fa2e2d-f3be-4fd8-8523-6977494709ec_10
02a794ba6f68 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_example-app-7d95cbf666-mgl82_default_1f811e50-9bed-45b0-adb9-6756df88a824_11
63cd691528f1 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_eureka-0_ms_054fe755-b85d-466b-b0cb-5e348bea5f4a_39
019bf89bbab1 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_prometheus-server-79b6f5f545-p2wsz_monitor_a0ab3856-ef9b-4324-8745-0eb088f317b3_2
c0b518fe4c4e kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_calico-node-6b6nz_kube-system_b39849d4-7c48-4ac8-9123-7fda91b77a61_45
2e84819246d6 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_kube-proxy-bvgqt_kube-system_c469a7eb-3b50-4f24-8d0b-1fa2028fd0ab_35
fab993500283 kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_nodelocaldns-26x9q_kube-system_4ced12c3-f58b-447b-90c3-ce5ebe1c0be7_45
554e2489561e kubesphere/pause:3.2 "/pause" 22 minutes ago Up 22 minutes k8s_POD_node-exporter-jbbcq_monitor_090eccee-f405-4ac4-aca1-d98749a13bde_41
可以看出,kubelet 并不是直接进行容器操作的,如图1-14 所示。它都是通过容器运行时的接口(Container Runtime Interface,CRI)调用容器运行时对容器和镜像进行操作的,例如创建、启动、停止和删除容器,下载镜像等。
容器运行时的选用,这里有多条路可选:使用内置的dockershim 和远端的容器运行时等。目前默认情况下,kubelet 是通过内置的dockershim 调用 Docker 来完成容器操作的。我们也可以指定 remote 模式(通过参数--container-runtime 来指定),使用外部的遵循CRI 的容器运行时。虽然kubelet 不直接参与容器的创建与运行,但是它是管理和监控该节点上Pod 及Pod 中容器 “生老病死” 的核心。
图1-14 kubelet 的组织架构
如图1-15 所示,kubelet 的核心函数是syncLoop。此函数是由事件驱动的。kubelet 会从API Server 的静态Pod 的本地目录和HTTP URL 处监听到Pod 资源对象的变化,产生新增、更改、删除事件。kubelet 还会启动一个PLEG(Pod Lifecycle Event Generator)线程,每秒钟重新查询一次容器运行时容器的状态,更新Pod 的缓存,并根据容器的状态产生同步的事件。
- 对容器的Liveness 和Readiness 进行检测。Liveness 用来探测容器是否处于 “存活状态”,如果kubelet 检测容器当前处于 “死亡状态”,则kubelet 会停止此容器,并重新创建新的容器。
- 保护节点不被容器抢占所有资源。如果镜像占用磁盘空间的比例超过高水位(默认值为90%,可以通过参数ImageGCHighThresholdPercent 进行配置),kubelet 就会清理不用的镜像。
kubelet关于pv pvc方面
而 Kubernetes 需要做的工作,就是使用这些存储服务,来为容器准备一个持久化的宿主机目录,以供将来进行绑定挂载时使用。而所谓“持久化”,指的是容器在这个目录里写入的文件,都会保存在远程存储中,从而使得这个目录具备了“持久性”。
这个准备“持久化”宿主机目录的过程,我们可以形象地称为“两阶段处理”。
接下来,我通过一个具体的例子为你说明。
当一个 Pod 调度到一个节点上之后,kubelet 就要负责为这个 Pod 创建它的 Volume 目录。默认情况下,kubelet 为 Volume 创建的目录是如下所示的一个宿主机上的路径:
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
接下来,kubelet 要做的操作就取决于你的 Volume 类型了。
[root@node1 volumes]# ls
kubernetes.io~nfs kubernetes.io~secret
[root@node1 volumes]# pwd
/var/lib/kubelet/pods/3709e4a0-9b61-4afa-a103-2d20073dfed2/volumes
如果你的 Volume 类型是远程块存储,比如 Google Cloud 的 Persistent Disk(GCE 提供的远程磁盘服务),那么 kubelet 就需要先调用 Goolge Cloud 的 API,将它所提供的 Persistent Disk 挂载到 Pod 所在的宿主机上。
备注:你如果不太了解块存储的话,可以直接把它理解为:一块磁盘。
这相当于执行:
$ gcloud compute instances attach-disk <虚拟机名字> --disk <远程磁盘名字>
这一步为虚拟机挂载远程磁盘的操作,对应的正是“两阶段处理”的第一阶段。在 Kubernetes 中,我们把这个阶段称为 Attach。
Attach 阶段完成后,为了能够使用这个远程磁盘,kubelet 还要进行第二个操作,即:格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上。不难理解,这个挂载点,正是我在前面反复提到的 Volume 的宿主机目录。所以,这一步相当于执行:
# 通过lsblk命令获取磁盘设备ID
$ sudo lsblk
# 格式化成ext4格式
$ sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/<磁盘设备ID>
# 挂载到挂载点
$ sudo mkdir -p /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
这个将磁盘设备格式化并挂载到 Volume 宿主机目录的操作,对应的正是“两阶段处理”的第二个阶段,我们一般称为:Mount。
Mount 阶段完成后,这个 Volume 的宿主机目录就是一个“持久化”的目录了,容器在它里面写入的内容,会保存在 Google Cloud 的远程磁盘中。
而如果你的 Volume 类型是远程文件存储(比如 NFS)的话,kubelet 的处理过程就会更简单一些。
因为在这种情况下,kubelet 可以跳过“第一阶段”(Attach)的操作,这是因为一般来说,远程文件存储并没有一个“存储设备”需要挂载在宿主机上。
所以,kubelet 会直接从“第二阶段”(Mount)开始准备宿主机上的 Volume 目录。
在这一步,kubelet 需要作为 client,将远端 NFS 服务器的目录(比如:“/”目录),挂载到 Volume 的宿主机目录上,即相当于执行如下所示的命令:
$ mount -t nfs <NFS服务器地址>:/ /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
通过这个挂载操作,Volume 的宿主机目录就成为了一个远程 NFS 目录的挂载点,后面你在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。所以,我们也就完成了对这个 Volume 宿主机目录的“持久化”。
而经过了“两阶段处理”,我们就得到了一个“持久化”的 Volume 宿主机目录。所以,接下来,kubelet 只要把这个 Volume 目录通过 CRI 里的 Mounts 参数,传递给 Docker,然后就可以为 Pod 里的容器挂载这个“持久化”的 Volume 了。其实,这一步相当于执行了如下所示的命令:
$ docker run -v /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>:/<容器内的目标目录> 我的镜像 ...
以上,就是 Kubernetes 处理 PV 的具体原理了。