Skip to content

Pod 创建流程

  1. 用户发起请求:通过 kubectl 或 API 客户端向 API Server 提交 Pod 对象(YAML/JSON)。

  2. API Server 处理:认证、鉴权、准入控制(Admission Webhook 等),将 Pod 对象持久化存入 etcd。

  3. Scheduler 调度:

    • 通过 watch 机制发现未调度的 Pod(spec.nodeName 为空)。

    • 执行预选(过滤节点)和优选(打分),选择最合适的 Node。

    • 将选定的 Node 名称写入 Pod 的 spec.nodeName,更新 etcd。

  4. kubelet 创建 Pod(目标节点上的 kubelet):

  5. 监听到被分配到自己节点的 Pod,开始创建过程。

    • Init 容器阶段:按顺序逐个启动 initContainers,每个 Init 容器必须运行成功(退出码 0)后才会启动下一个;如果任一 Init 容器失败,kubelet 会根据 Pod 的 restartPolicy 决定是否重试(通常为 Always,即反复重试)。

    • 主容器启动:所有 Init 容器成功完成后,kubelet 开始启动主容器。

    • 通过容器运行时拉取主容器镜像,创建和启动容器。

    • 启动后立即执行 postStart 钩子(如果定义):该钩子与容器主进程异步执行,不会阻塞容器进入 Running 状态;但若钩子执行失败,容器会被重启(根据 restartPolicy)。

    • 网络配置:CNI 插件为 Pod 分配 IP,设置网络接口,完成后 Pod 可与其他 Pod 通信。

  6. 状态上报:kubelet 将 Pod 的状态(Init:Running、PodInitializing、Running 等)更新到 API Server,用户最终可以看到 Pod 成为 Running 状态。

Pod 删除流程

  1. 用户执行删除:kubectl delete pod ...,API Server 收到请求后,设置 Pod 的 deletionTimestamp 和 deletionGracePeriodSeconds(默认 30s)。

  2. 状态变为 Terminating:Pod 进入 Terminating 状态,但 kubelet 不会立即强杀。

  3. 执行 PreStop Hook(若配置):容器如果定义了 lifecycle.preStop,会先执行该钩子。

  4. 发送 SIGTERM:kubelet 向容器主进程发送 SIGTERM 信号,通知应用开始优雅退出。

  5. 等待宽限期:等待 terminationGracePeriodSeconds 设置的时间,让应用完成清理工作。

  6. 强制终止:超过宽限期后,kubelet 发送 SIGKILL 强制终止容器。

  7. 清理资源:容器删除后,kubelet 通知 API Server 移除 Pod 对象,相关资源(IP、卷等)被释放。

如何优雅的终止pod或者说如何保证pod不丢失流量

在一些重点领域,比如涉及金钱交易的系统,保证pod终止时不丢失流量是很重要的,这就涉及到如何保证pod优雅的退出的问题。

  1. 在开发层面,程序务必在业务代码里处理 SIGTERM 信号。主要逻辑是不接受新的流量进入,继续处理剩余流量,保证所有连接全部断开程序才退出。
  2. 在k8s层面,Pod里面可以设置preStop构子,preStop构子的作用是在容器终止之前立即被调用,主要用于优雅关闭应用程序、或者完成一些清理工作等等,这个钩子是同步的,即具有阻塞性的,也就是说它是会阻塞删除容器的操作。举个例子假设是容器运行的是nginx进程,则可以设置preStop钩子为nginx -s quit 让nginx进程优雅退出。
  3. 在一些程序终止时常较长的场景下,可以适当增加pod终止宽限期,即terminationGracePeriodSeconds参数,默认pod终止宽限期是30s,参数具体设置在deployment.spec.template.spec.terminationGracePeriodSeconds。当终止终止宽限期到达之后,无论Pod是否完成终止,也无论Pod是否正在被preStop阻塞,k8s都会发送强制退出信号给Pod让其终止。

腾讯云的这篇文章讲的不错:https://www.tencentcloud.com/zh/document/product/457/42070

deployment怎么扩容或缩容?

答:直接修改pod副本数即可,可以通过下面的方式来修改pod副本数: 1、直接修改yaml文件的replicas字段数值,然后kubectl apply -f xxx.yaml来实现更新; 2、使用kubectl edit deployment xxx 修改replicas来实现在线更新; 3、使用kubectl scale --replicas=5 deployment/deployment-nginx命令来扩容缩容。

deployment的更新升级策略有哪些?

答:deployment的升级策略主要有两种。 1、Recreate 重建更新:这种更新策略会杀掉所有正在运行的pod,然后再重新创建的pod; 2、rollingUpdate 滚动更新:这种更新策略,deployment会以滚动更新的方式来逐个更新pod,同时通过设置滚动更新的两个参数maxUnavailable、maxSurge来控制更新的过程。

deployment的滚动更新策略有两个特别主要的参数,解释一下它们是什么意思?

答:deployment的滚动更新策略,rollingUpdate 策略,主要有两个参数,maxUnavailable、maxSurge。

  1. maxUnavailable:最大不可用数,maxUnavailable用于指定deployment在更新的过程中不可用状态的pod的最大数量,maxUnavailable的值可以是一个整数值,也可以是pod期望副本的百分比,如25%,计算时向下取整。
  2. maxSurge:最大激增数,maxSurge指定deployment在更新的过程中pod的总数量最大能超过pod副本数多少个,maxUnavailable的值可以是一个整数值,也可以是pod期望副本的百分比,如25%,计算时向上取整。

deployment更新的命令有哪些?

答:可以通过三种方式来实现更新deployment。

  1. 直接修改yaml文件的镜像版本,然后kubectl apply -f xxx.yaml来实现更新;
  2. 使用kubectl edit deployment xxx 实现在线更新;
  3. 使用kubectl set image deployment/nginx busybox=busybox nginx=nginx:1.9.1 命令来更新。

简述一下deployment的更新过程

deployment是通过控制replicaset来实现,由replicaset真正创建pod副本,每更新一次deployment,都会创建新的replicaset,下面来举例deployment的更新过程: 假设要升级一个nginx-deployment的版本镜像为nginx:1.9,deployment的定义滚动更新参数如下:

yaml
replicas: 3
deployment.spec.strategy.type: RollingUpdate
maxUnavailable:25%
maxSurge:25%

通过计算我们得出,3*25%=0.75,maxUnavailable是向下取整,则maxUnavailable=0,maxSurge是向上取整,则maxSurge=1,所以我们得出在整个deployment升级镜像过程中,不管旧的pod和新的pod是如何创建消亡的,pod总数最大不能超过3+maxSurge=4个,最大pod不可用数3-maxUnavailable=3个。

现在具体讲一下deployment的更新升级过程:

使用kubectl set image deployment/nginx nginx=nginx:1.9 --record 命令来更新;

  1. deployment创建一个新的replaceset,先新增1个新版本pod,此时pod总数为4个,不能再新增了,再新增就超过pod总数4个了;旧=3,新=1,总=4;
  2. 减少一个旧版本的pod,此时pod总数为3个,这时不能再减少了,再减少就不满足最大pod不可用数3个了;旧=2,新=1,总=3;
  3. 再新增一个新版本的pod,此时pod总数为4个,不能再新增了;旧=2,新=2,总=4;
  4. 减少一个旧版本的pod,此时pod总数为3个,这时不能再减少了;旧=1,新=2,总=3;
  5. 再新增一个新版本的pod,此时pod总数为4个,不能再新增了;旧=1,新=3,总=4;
  6. 减少一个旧版本的pod,此时pod总数为3个,更新完成,pod都是新版本了;旧=0,新=3,总=3;

deployment的回滚使用什么命令

在升级deployment时kubectl set image 命令加上 --record 参数可以记录具体的升级历史信息,使用kubectl rollout history deployment/deployment-nginx 命令来查看指定的deployment升级历史记录,如果需要回滚到某个指定的版本,可以使用kubectl rollout undo deployment/deployment-nginx --to-revision=2 命令来实现。

讲一下都有哪些存储卷,作用分别是什么?

作用常用场景
emptyDir用于存储临时数据的简单空目录一个pod中的多个容器需要共享彼此的数据,emptyDir的数据随着容器的消亡也会销毁
hostPath用于将目录从工作节点的文件系统挂载到pod中不常用,缺点是pod的调度不是固定的,当pod消失后deployment重新创建的pod可能无法访问之前节点的数据
configMap用于将非敏感的数据保存到键值对中,可作为环境变量、命令行参数或存储卷被pods挂载使用将应用程序的不敏感配置文件创建为configmap卷,在pod中挂载configmap卷,可实现热更新
secret主要用于存储和管理敏感数据,通过Volume挂载或环境变量方式访问将应用程序的账号密码等敏感信息通过secret卷的形式挂载到pod中使用,pod会自动解密Secret信息
downwardApi用于暴露pod元数据(如pod的名字)pod中的应用程序需要指定pod的name等元数据时,通过downwardApi卷的形式挂载给pod使用
projected特殊卷,用于将多种卷类型一次性挂载给pod使用将configMap、secret等卷一次性的挂载给pod使用
pvc存储卷声明通常会创建pvc表示对存储的申请,然后在pod中使用pvc
网络存储卷pod挂载网络存储卷,将数据持久化到后端存储常见的网络存储卷有nfs存储、glusterfs卷、ceph rbd存储卷

pv的访问模式有哪几种

pv的访问模式有3种,如下:

  • ReadWriteOnce:简写:RWO 表示,只仅允许单个节点以读写方式挂载;
  • ReadOnlyMany:简写:ROX 表示,可以被许多节点以只读方式挂载;
  • ReadWriteMany:简写:RWX 表示,可以被多个节点以读写方式挂载;

pv的回收策略有哪几种

主要有2中回收策略:retain 保留、delete 删除。

  • Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,PV被视为已释放,管理员可以手动回收卷。 Delete:删除,如果Volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete,目前支持Delete的存储后端包括AWS EBS,GCE PD,Azure Disk,OpenStack Cinder等。
  • Recycle:回收,如果Volume插件支持,Recycle策略会对卷执行rm -rf清理该PV,并使其可用于下一个新的PVC,但是本策略将来会被弃用,目前只有NFS和HostPath支持该策略。(这种策略已经被废弃,不用记)

在pv的生命周期中,一般有几种状态

pv一共有4中状态,分别是: 创建pv后,pv的的状态有以下4种:Available(可用)、Bound(已绑定)、Released(已释放)、Failed(失败)

  • Available:表示pv已经创建正常,处于可用状态;
  • Bound:表示pv已经被某个pvc绑定,注意,一个pv一旦被某个pvc绑定,那么该pvc就独占该pv,其他pvc不能再与该pv绑定;
  • Released:表示pvc被删除了,pv状态就会变成已释放;
  • Failed:表示pv的自动回收失败;

存储类的资源回收策略:

主要有2中回收策略,delete 删除,默认就是delete策略、retain 保留。

  • Retain:保留,该策略允许手动回收资源,当删除PVC时,PV仍然存在,PV被视为已释放,管理员可以手动回收卷。
  • Delete:删除,如果Volume插件支持,删除PVC时会同时删除PV,动态卷默认为Delete,目前支持Delete的存储后端包括AWS EBS,GCE PD,Azure Disk,OpenStack Cinder等。

注意:使用存储类动态创建的pv默认继承存储类的回收策略,当然当pv创建后你也可以手动修改pv的回收策略。

怎么使一个node脱离集群调度,比如要停机维护单又不能影响业务应用

要使一个节点脱离集群调度可以使用kubectl cordon <node-name> 命令使节点不可调度,该命令其实背后原理就是给节点打上node-status.kubernets.io/unschedulable污点,这样新的pod如果没有容忍将不会调度到该节点,但是已经存在于该节点的pod仍然可以继续在该节点上运行不受影响,除非pod消忙了被重新调度了。如果需要恢复节点重新调度,可以使用kubectl uncordon <node-name> 命令恢复节点可调度。 如果节点是要停机维护,则可以对节点上的pod 进行驱逐:使用kubectl drain <node-name>命令用于将节点上的pod驱逐出去,以便对节点进行维护。 kubectl drain 命令的语法如下:

bash
kubectl drain <node-name>

--ignore-daemonsets 参数用于忽略由DaemonSet控制器管理的pods,不加该参数会报错; --delete-local-data 参数用于在节点上删除所有本地数据,包括PersistentVolume和PersistentVolumeClaim等资源; --force 参数强制删除pod,默认删除的是ReplicationController, ReplicaSet, Job, DaemonSet 或者StatefulSet创建的pod,如果有静态pod,则需要设置强制执行删除的参数--force。 这个命令会将节点上所有的pods驱逐出去,包括由DaemonSet控制器管理的pods。但是需要注意的是, kubectl drain 命令会将节点上所有的pods驱逐出去,包括由DaemonSet控制器管理的pods,由于ds创建的pod会具有容忍,所以又会马上在正在清空的节点上启动新的pod,我们可以使用 --ignore-daemonsets 参数来忽略由DaemonSet控制器管理的pods。

kubectl drain 命令背后原理其实还是首先将指定的节点标记为不可调,从而阻止新 pod 分配到节点上(实质上是 kubectl cordon),然后删除pod。

综上所述,要停机维护:

bash
1、kubectl cordon node01								#设置节点不可调度
2、kubectl drain node01 --ignore-daemonsets  --force	#驱逐pod
3、kubectl uncordon node01  							#恢复节点调度

pv存储空间不足怎么扩容?

一般的,我们会使用动态分配存储资源,在创建storageclass时指定参数 allowVolumeExpansion:true,表示允许用户通过修改pvc申请的存储空间自动完成pv的扩容,当增大pvc的存储空间时,不会重新创建一个pv,而是扩容其绑定的后端pv。这样就能完成扩容了。但是allowVolumeExpansion这个特性只支持扩容空间不支持减少空间。

pod内容器之间通信,同节点pod通信、不同节点的pod的通信是如何实现的,大概的流程是什么

假设k8s采用flannel插件,则同节点的pod通信、不同节点的pod通信是这样子的: 1、pod内容器之间通信 在创建一个pod时,首先会创建一个pause容器,也就是根容器,而根容器负责创建pod内所有容器共享的网络命名空间,而其他业务容器都会加入到这个网络命名空间中去,所以在一个pod里面,容器都是处于同一个命名空间,既然处于同一个命名空间,则容器进程彼此之间的访问直接通过localhost+端口即可。我们可以把pod想象成一个微型的虚拟机,虚拟机对外只有一个ip,而虚拟机内的应用彼此访问直接通过localhost+端口访问即可。 2、通节点pod的通信 在部署flannel插件的时候,k8s默认是采用daemonsets这种资源类型部署的,会在每个宿主机上有一个flanneld进程,这个进程就是用来管理k8s网络的。然后会在每个宿主机上生成一个网桥,这个网桥叫做cni0; 当一个pod被创建之后,cni插件会为pod创建一个虚拟以太网接口(veth pair),这个虚拟以太网接口(veth pair)它是一端接在pod里面,一端接在cni0网桥上,所以,当同一个节点上的pod相互通信,其实就是通过cni0进行通信的。 3、不同节点的pod的通信(待完善) pod发送数据给到cni0网桥,cni0根据路由规则转发给flannel.1,flannel.1转发给宿主机上的eth0物理网卡,这样数据就发送到对端pod所在的物理机网卡上。

一个用户请求流量是如何进入k8s集群内部的?

以一个内网的k8s集群为例: 1、用户访问域名,域名必要对应一个服务器ip地址,此时电脑开始解析域名,解析的流程可能是先检查浏览器是否缓存有这个域名的解析记录、检查window的host文件、最后是请求windows网络配置的dns服务器; 2、域名被解析到内网LB负载均衡服务器,LB负载均衡服务器上使用nginx定义了一组后端负载服务器ip端口,而这组后端服务器其实就是k8s的节点; 3、这组k8s节点是ingress-nginx-controller的暴露的端口,所以此时流量交给ingress-nginx-controller处理; 4、k8s上定义了一组ingress规则,规则里定义了对应的后端service,当用户流量来到ingress后会进行规则匹配并将交给后端service处理; 5、service会将流量分发给对应的endpoint,而endpoint对应这一组pod ip端口,所以此时,用户流量已经交给了pod中的容器处理了。 上面就是用户请求流量进入k8s集群内部的大致流程。

用户访问我们的k8s集群里面的应用网站,出现500报错,你是如何排查这种问题的?

1、按F12 打开开发者模式,切换到网络栏,按F5刷新一下页面,看哪个请求地址是红色的,红色就是访问有问题的,看地址,响应值、接口url等信息; 2、得到了接口,那么应该很容易知道是哪个后端服务出现问题,此时直接去查看日志即可。如果有elk,直接登录Kibana查看日志即可,如果没有,则直接去服务器上查看应用的日志即可。

kube-proxy有哪几种模式?

kube-proxy有3种模式,分别是userspace(用户空间)、iptables、ipvs,其中user namespace已经被废弃了这种就不在讲解了。 目前用的最多的就是iptables和ipvs这两种模式。

你们生产环境kube-proxy使用哪种模式,为什么?

生产环境中使用ipvs。因为ipvs性能比iptables高。 iptables本质上是Linux的一个高效的防火墙,提供数据包处理和过滤方面的能力,kube-proxy使用iptables这种模式的时候,其连接处理算法复杂度是O(n),其中的n随集群规模同步增长,换句话说,当k8s集群中的service数量很多,比如集群有1000个service,每个service后端又有多个Pod副本,那每个节点上的iptable规则将非常多,kube-proxy需要动态维护这些庞大的规则将带来严重的节点性能问题。

而ipvs是Linux内核功能中专门用于处理高性能负载均衡的功能,它使用更高效的数据结构,如hash表并支持索引,IPVS有一套优化过的 API,使用优化的查找算法,而不是简单的从列表中查找规则,ipvs还支持多种调度算法,如rr、wrr、lc、wlc,而iptables就只有一种随机平等的选择算法。其次,kube-proxy在IPVS模式下,连接处理算法复杂度是O(1)而不是O(n)。换句话说,多数情况下,连接处理效率是和集群规模大小无关。 iptables和ipvs ipvs与iptables相比较,其优势为: (1)ipvs为大型集群提供了更好的可扩展性和性能 (2)ipvs支持比iptables更复杂的负载均衡算法,如rr、wrr、lc、wlc,而iptables 就只有一种随机平等的选择算法。 (3)ipvs支持服务健康检查和链接重试等功能 (4)ipvs可以动态修改ipset集合