输入 kubectl apply 时,k8s在背后做了什么

本文需要读者对kubernetes(以下简称k8s)有一定的了解。

如果我们想在kubernetes集群中创建3个相同 nginx 镜像的pod,官方推荐的做法是定义一个更高一级抽象的deployment, 通过这个deployment来统一管理一组pod,并且使用声明式命令 apply 代替 指令式命令 create 来创建deployment对象

配置文件 deployment.yml 如下, 来自kubernetes 官网

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

在shell中我们敲下 “ kubectl apply -f deployment.yml ” 后, k8s就会为我们在etcd中生成deployment和pod对象并将pod通过一系列的算法自动调度到集群节点上启动,这一切都是k8s自动帮我们完成的,我们只需要提供一份配置文件即可。k8s在背后究竟是怎么做到的呢,让我们一探究竟。

1 构造请求

k8s集群使用REST API进行通信, 请求会以HTTP的方式发给 kube-apiserver 进行处理。当我们是在shell中输入“kubectl apply -f deployment.yml”时,本地的 kubectl 会解析yaml中的配置根据其内容构造相应对象的 HTTP 请求参数。首先 kubectl 会检查有没有语法错误(比如创建不支持的资源或使用格式错误的镜像名称),出现错误后会直接返回不会发送到 kube-apiserver 以节省网络负载。通过检查后 kubectl 就会构造出HTTP请求发送给 kube-apiserver。

2 认证,鉴权,准入控制

现在请求已经发送给了 kube-apiserver,kube-apiserver接下来会判断这个请求的发起者是否合法,即请求发起者对应的用户信息是否存储k8s集群中,此过程称为认证 (Authentication)。k8s提供了多种认证方式,这里我们不做过多的讨论,如果认证没有通过则会直接返回失败的错误信息,通过了就会进入一步 鉴权。

虽然我们的身份已经得到了k8s的认可,但是身份 (identity) 和许可 (permission) 并不是一个概念,就像mysql账号有的有读写权限而有的只有读取权限一样。此时kube-apiserver会检查用户的权限是否可以进行相应的操作,对应我们文章中的命令就是创建deployment的权限,这里k8s也提供了多种方式进行鉴权不再赘述。

好了 kube-apiserver 确认请求发起者有相应的权限,这样就可以执行创建deployment的动作了吗。 很“不幸”还有最后一步,准入控制。Kubernetes 准入控制器是控制和强制使用集群的一种插件。我们可以把它看作是拦截(已认证)API 请求的拦截器,它可以更改请求对象,甚至完全拒绝请求。这是可以配置的插件,也就是说你通过这套机制自己开发一套插件部署在集群中来控制请求的行为。k8s官方提供了很多“内置”的准入控制器。

3 etcd

终于我们的请求被验证通过,kube-apiserver会在etcd(服务发现的后端,存储了集群的状态及其配置)中创建我们的 Deployment 对象, 创建过程中出现的任何错误都会被捕获,最后kube-apiserver会构造HTTP响应返回给客户端,我们在输入完命下回车之后看到的信息就是kubectl 得到HTTP响应解析后的信息。注意此时我们部署的 Deployment 对象现在虽然保存在于 etcd 中,但是它还没有被部署到真正的 Node 上。

4 控制循环 (Control loops)

接下来的步骤对于请求调用者来说都是异步执行的,因为请求的响应已经在上一步得到了。

我们已经创建了Deployment,但是并没有创建涉及 Deployment 所依赖的资源拓扑(此例子中就是ReplicaSet 和Pod )这其实是k8s通过内置控制器 (Controller)自动帮我们创建的。

Controller 是一个用于将系统状态从当前状态调谐到期望状态的异步脚本。所有内置的 Controller 都通过组件 kube-controller-manager 并行运行,每种 Controller 都负责一种具体的控制流程。

比如我们本次使用到的 Deployment Controller

当k8s在etcd中新创建了一个Deployment对象, Deployment Controller会监听(ListAndWatch)到这个事件之后然后检查Deployment这个对象的期望状态,和实际状态作对比,比如这次检查到相关联的对象ReplicaSet(因为本质上Deployment是通过控制ReplicaSet来控制Pod的)没有被创建,Deployment Controller就会创建关联的ReplicaSet ,创建ReplicaSet之后Deployment Controller的并不会检查对应管理的Pod,这是ReplicaSet Controller的工作。

ReplicaSet Controller 和 Deployment Controller 工作类似,ReplicaSet Controller监听的是ReplicaSet 这个对象, 当ReplicaSet 被创建时就会检查这个ReplicaSet 对象对应的期望状态,创建Pod对象。

这里也可以看出Deployment并不是直接管理Pod,而是通过ReplicaSet,即Deployment管理ReplicaSet, ReplicaSet管理Pod。

实际上 Control loops 的细节有很多,包括 实现监听的**Informer机制,内部工作队列WorkQueue, 本地缓存等等,**如果全部展开如要大量的篇幅,而且作者也并没有完全掌握内部细节,我会在后续系列文章再次总结。

而此时我们也只是在etcd中创建了Deployment,ReplicaSet, Pod这3个对象,还没有在实际Node 中部署。

5 调度 (Scheduler)

接下来到了调度环节。

当所有的 Controller 正常运行后,etcd 中就会保存一个 Deployment、一个 ReplicaSet 和 三个 Pod, 并且可以通过 kube-apiserver 查看到。这时如果你在shell里 get pod查看刚才的pod状态 你会看到Pending状态(调度中,即它们还没有被调度到集群中合适的 Node 上)。

k8s是依靠Scheduler这个组件完成调度操作的。Scheduler 组件运行在集群控制平面上,工作方式与其他 Controller 相同:监听事件并调谐状态。具体来说, Scheduler 的作用是过滤 PodSpec 中 NodeName 字段为空的 Pod 并尝试将其调度到合适的节点。Scheduler会经过一系列的比如资源限制(cpu,内存)等算法首先选出一批符合条件的 Node, 然后通过第二轮算法(列如负载均衡情况)给Node打分,将Pod 调度最高分的Node上,调度器就会将Pod对象的nodeName字段的值,修改为上述Node的名字。度器对一个Pod调度成功,实际上就是将它的spec.nodeName字段填上调度结果的节点名字。

不可避免的,这里也包含了很多细节,我们也会在后续文章中详细讨论。

6 Kubelet

终于到了激动人心的真正的容器启动环节。

我们来总结一下已经完成的任务:

HTTP 请求通过了认证、授权和准入控制阶段;
一个 Deployment、ReplicaSet 和三个 Pod 被持久化到 etcd;
最后每个 Pod 都被调度到合适的节点。
到目前为止,所有的工作仅仅只是针对保存在 etcd 中的资源对象,接下来的步骤涉及到在工作节点之间运行具体的容器,这是分布式系统 Kubernetes 的关键因素。这些事情都是由 Kubelet 完成的。

在 Kubernetes 集群中,每个 Node 节点上都会启动一个 Kubelet 服务进程,该进程用于处理 Scheduler 下发到本节点的 Pod 并管理其生命周期。这意味着它将处理 Pod 与 Container Runtime 之间所有的转换逻辑,包括挂载卷、容器日志、垃圾回收等操作。

我们可以把 Kubelet 当成一种特殊的 Controller,它每隔 20 秒(可以自定义)向 kube-apiserver 查询 Pod,过滤 NodeName 与自身所在节点匹配的 Pod 列表。

当检测到新的pod对象还没有在Node上创建时,Kubelet进行一些前置操作,然后通过CRI(Container Runtime Interface)创建pause容器,通过CNI (Container Network Interface)为 Pod 设置网络,最后通过CRI拉取我们文件定义中的nginx镜像,创建并启动起来!

总结
在这里插入图片描述

(图片来自网络侵删)

最终我们的集群上会运行三个容器,这三个容器可能分布在不同的Node上,而这一切只需要我们编写一份文章开头的yml配置文件,Amazing!

感谢原文创作者解惑 chimission https://juejin.cn/post/6936557300312178701

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐