K8S-Pod的生命周期与调度

一、Pod生命周期

我们一般将pod对象从创建至终的这段范围称为pod的生命周期

1.pod创建过程

2.运行初始化容器(init container)过程

3.运行主容器(main container)

4.容器启动后的钩子(post start)、容器终止前的钩子(pre stop)

5.容器的存活性探针(liveness probe)、就绪性探测(readiness probe)

6.pod终止过程

在整个生命周期中,Pod 会出现 5 种状态(相位),分别如下:

  • 挂起(Pending):apiserver 已经创建了 pod 资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
  • 运行中(Running):pod 已经被调度至某节点,并且所有容器都已经被 kubelet 创建完成
  • 成功(Succeeded):pod 中的所有容器都已经成功终止并且不会被重启
  • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非 0 值的退出状态
  • 未知(Unknown):apiserver 无法正常获取到 pod 对象的状态信息,通常由网络通信失败所导致

1.pod的创建和终止

pod创建过程

1.用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer

2.apiServer开始生成pod对象的信息,并将信息存如etcd,然后返回确认信息至客户端

3.apiServer开始反馈etcd对象的变化,其他组件使用watch机制来跟踪检查apiServer上的变动

4.schedulter发现有新的pod对象要创建,开始为Pod分配主机并将结果信息更新至apiServer

5.node节点上的kubectl发现有pod调度并尝试调用docker启动容器,将结果返回给apiServer

6.apiServer将接受到的pod状态信息存入到etcd中

pod的终止过程

1.用户向 apiServer 发送删除 pod 对象的命令

2.apiServcer 中的 pod 对象信息会随着时间的推移而更新,在宽限期内(默认 30s),pod 被视为 dead

3.将 pod 标记为 terminating 状态

4.kubelet 在监控到 pod 对象转为 terminating 状态的同时启动 pod 关闭过程

5.端点控制器监控到 pod 对象的关闭行为时将其从所有匹配到此端点的 service 资源的端点列表中移除

6.如果当前 pod 对象定义了 preStop 钩子处理器,则在其标记为 terminating 后即会以同步的方式启动执行

7.pod 对象中的容器进程收到停止信号

8.宽限期结束后,若 pod 中还存在仍在运行的进程,那么 pod 对象会收到立即终止的信号

9.kubelet 请求 apiServer 将此 pod 资源的宽限期设置为 0 从而完成删除操作,此时 pod 对于用户已不可见

2.初始化容器

初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,它具有两大特征:

1.初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成

2.初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行

初始化容器有很多应用场景,常见以下:

1.提供主容器镜像中不具备的工具程序或自定义代码

2.初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足

测试案例:

假设要以主容器来运行nginx,但是要求在运行nginx之前先要能够连接上mysql和redis所在服务器

创建一个文件,pod-init.yaml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-init
  namespace: nginx-ns
spec:
  initContainers:
  - name: test-mysql
    image: busybox:1.30
    command: ['sh','-c','until ping 192.168.90.14 -c 1 ; do echo waiting for mysql...; sleep 2; done;']
  - name: test-redis
    image: busybox:1.30
    command: ['sh','-c','until ping 192.168.90.15 -c 1 ; do echo waiting for redis...; sleep 2; done;']
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP

其中初始化容器执行了一条命令

bash 复制代码
# 作用就是循环执行ping命令如果执行不成功就持续循环
command: ['sh','-c','until ping 192.168.90.14 -c 1 ; do echo waiting for mysql...; 

如果要成功初始化容器就得让这个他ping的同这个ip地址但是这个ip地址是不存在的,因此需要绑定网卡,先检查他在哪个节点上

bash 复制代码
kubectl get pod -n nginx-ns -o wide

在node1节点上添加两张网卡,让他能够ping通就能初始化容器成功

先添加网卡

bash 复制代码
ifconfig ens33:1 192.168.90.14 netmask 255.255.255.0 up
ifconfig ens33:2 192.168.90.15 netmask 255.255.255.0 up





# 删除网卡操作
ifconfig ens33:1 down
ifconfig ens33:2 down

此时先添加14这张网卡

此时有有一个已经初始化成功了,在添加一张15网卡

此时容器就已经是在运行的状态了

3.钩子函数

钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。

kubernetes在主容器的启动之后和停止之前提供了两个钩子函数:

post start:容器创建之后执行,如果失败了会重启容器

pre stop:容器终止之前执行,执行完成之后容器将成功终止,在其完成之前会阻塞删除容器的操作

钩子处理器支持使用下面三种方式自定义动作:

Exec命令:在容器内执行一次命令

bash 复制代码
......
  lifecycle:
    postStart:
      exec:
        command:
        - cat
        - /tmp/healthy
......

TCPSocket:在当前容器尝试访问指定的socket

bash 复制代码
......
  lifecycle:
    postStart:
      tcpSocket:
        port: 8080
......

HTTPGet:在当前容器中向某url发起http请求

bash 复制代码
......
  lifecycle:
    postStart:
      httpGet:
        path: / #URL地址
        port: 80 #端口号
        host:192.168.22.121 # 主机地址
        scheme:HTTP  #支持指定协议,http或者https
......

示例:以exec方式使用钩子函数,创建pod-hook-exec-yaml文件

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: nginx-ns
spec:
  containers:
  - name: main-container
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh","-c","echo postStart... > /usr/share/nginx/html/index.html"]
      preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]

此时访问页面发现这就是我们自定义的页面

通过配置Pod终止宽限期,Pod的默认宽限期市30,当开始停止这个pod的时候如过pod在30秒内还没有处理掉所有的进程就会强制结束这个pod,但是也可以自定义结束时间

创建一个yaml文件

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-hook-exec
  namespace: dev
spec:
  terminationGracePeriodSeconds: 18    # 将宽限期调整为了18秒
  containers:
  - name: main-container
    image: nginx:1.17.1
    ports:
    - name: nginx-port
      containerPort: 80
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo 'postStart...' > /usr/share/nginx/html/index.html "]
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 60;"]

4.容器探针

容器探针的作用就是检测应用实例是否正常工作,其中有两中探针实现容器检测

1.liveness probes: 存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s 会重启容器

2.readiness probes: 就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s 不会转发流量

livenessProbe 决定是否重启容器,readinessProbe 决定是否将请求转发给容器

上述探针有三种探测方式

Exec 命令: 在容器内执行一次命令,如果命令执行的退出码为 0,则认为程序正常,否则不正常

bash 复制代码
......
livenessProbe:
  exec:
    command:
    - cat
    - /tmp/healthy
......

TCPSocket: 将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常

bash 复制代码
......
livenessProbe:
  tcpSocket:
    port: 8080
......

HTTPGet: 调用容器内 Web 应用的 URL,如果返回的状态码在 200 和 399 之间,则认为程序正常,否则不正常

bash 复制代码
......
livenessProbe:
  httpGet:
    path: / #URI地址
    port: 80 #端口号
    host: 127.0.0.1 #主机地址
    scheme: HTTP #支持的协议,http或者https
......

用exec方式创建一个容器探针pod-liveness-exec.yml

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness-exec
  namespace: nginx-ns
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP
    livenessProbe:
      exec:
        command: ["/bin/cat","/tmp/hello.txt"]

这里面读取已读取hello.txt文件的状态来判断是否存活,明显如果不存在的话就会不断重启

只有自己创建一个文件使其读取返回正常才能判断容器正常

5.重启策略

在容器探针中,容器出现问题,k8s会对容器所在的Pod进行重启,这是Pod的重启策略决定的,有三种重启策略

Always:容器失效时,自动重启该容器,这也是默认值

OnFailure:容器终止退出且退出码不为0时重启

Never:不论状态为何,都不重启该容器

重启策略适用pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为105、20s、40s、80s、160s和300s,300s是最大延迟时长。

创建一个pod-restartpolicy.yaml:

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-restartpolicy
  namespace: nginx-ns
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP
    livenessProbe:
      httpGet:
        scheme: HTTP
        port: 80
        path: /hello
  restartPolicy: Never    # 设置重启策略为Never

整个状态流程

二、pod的调度

在默认情况下,一个pod节点在那个Node上运行是通过Scheduler组件计算出来的,如果要自己控制需要自己调度

1.自动调度:在那个节点上完全由Scheduler计算得出

2.定向调度:NodeName、NodeSelector

3.亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity

4.污点(容忍)调度:Taints、Toleration

1.定向调度

指在pod上声明nodeName或者nodeSelector,从此将Pod调度到期望的node节点上,这里是强制的技术调度目标的Node不存在也会强行在上面调度,但是pod会运行失败

NodeName

用于青强制约束将Pod调度指定Name或者Node上,直接条多Scheduler调度

创建一个pod-nodename.yaml文件

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: nginx-ns
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP
  nodeName: node1

执行之后发现就是在node1节点上

NodeSelector

在创建节点之前通过Scheduler使用MatchNodeselector调度进行label匹配,找出目标node,将pod调度到目标节点,匹配规则是强制约束

bash 复制代码
# 分别给两个节点打上对应的标签
kubectl label nodes node1 nodeenv=node1
kubectl label nodes node2 nodeenv=node2

创建一个pod-nodeselector.yaml文件,并使用他创建Pod

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: nginx-ns
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - name: nginx-port
      containerPort: 80
      protocol: TCP
  nodeSelector:
    nodeenv: node1

同样的创建出来就是在node1节点上

2.亲和性调度

在上述的调度方式若没有满足条件的node则pod就不会运行,即使有别的node也不能使用,因此就有了亲和性调度三类

Affinity分为三类:

1.nodeAffinity(node亲和性):以node为目标,解决pod可以调度到哪些node的问题

2.podAffinity(pod亲和性):以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题

3.podAntiAffinity(pod反亲和性):以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题

亲和性:如果有两个应用交互频繁,就有必要让两个应用靠近,可以减少因网络通信带来的性能损耗(微服务中若有两个模块交互平凡就应当靠经最好在同一个节点上)

反亲和性:当应用采用多副本部署时,采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。

NodeAffinity

其中可配置项

bash 复制代码
pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  # Node节点必须满足指定的所有规则才可以,相当于硬限制
    nodeSelectorTerms  # 节点选择列表
      matchFields    # 按节点字段列出的节点选择器要求列表
      matchExpressions   # 按节点标签列出的节点选择器要求列表(推荐)
        key    # 键
        values # 值
        operator # 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution # 优先调度到满足指定的规则的Node,相当于软限制(倾向)
    preference    # 一个节点选择器项,与相应的权重相关联
      matchFields   # 按节点字段列出的节点选择器要求列表
      matchExpressions  # 按节点标签列出的节点选择器要求列表(推荐)
        key    # 键
        values # 值
        operator # 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
    weight # 倾向权重,在范围1-100。

关系符使用说明:

bash 复制代码
- matchExpressions:
  - key: nodeenv          # 匹配存在标签的key为nodeenv的节点
    operator: Exists
  - key: nodeenv          # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv          # 匹配标签的key为nodeenv,且value大于"xxx"的节点
    operator: Gt
    values: "xxx"

软限制:定义preferredDuringSchedulingIgnoredDuringExecution的例子如下:

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: nginx-ns
spec:
  containers:
  - name: nginx
    image: nginx:latest
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 60    # 权重,可以定义多个你调度的节点,定义不同的权重优先级不同
        preference:
          matchExpressions:
          - key: nodeenv
            operator: In
            values: ["node2", "harddisk"]   # 你选择调度的节点

此时我只有两个节点node1、node2此时上面有连个节点但是只会调度到node2上因为第二个是不存在的,若当两个都不存在会不会调度呢,将最后一行values中节点的值修改为两个不存在的

bash 复制代码
values: ["test", "test1"]

此时在执行

自动到node2节点上了

硬限制:创建pod-nodeaffinity-required.yaml (requiredDuringSchedulingIgnoredDuringExecution)

也就是说只要values里面没有对应的节点就不会运行pod

bash 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:latest
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: nodeenv
            operator: In
            values: ["node1","node2"]

执行yml文件

自动调度到了node2节点

删除修改values里面的值,使其不存在节点,执行此时一直是挂起状态(pending)

三、污点与容忍

污点(Taints)

前面的调度方式都是通过添加Pod属性来确定是否要调度到指定的Node上,同样的也可以添加Node属性来决定是否允许Pod调度过来

Node被设置上污点之后就和Pod形成了互斥的关系,可以拒绝Pod调度,甚至可以将存在的污点驱逐出去

污点的格式:key=value:effect,key 和 value 是污点的标签,effect 描述污点的作用,支持如下三个选项:

PreferNoSchedule: kubernetes 将尽量避免把 Pod 调度到具有该污点的 Node 上,除非没有其他节点可调度

NoSchedule: kubernetes 将不会把 Pod 调度到具有该污点的 Node 上,但不会影响当前 Node 上已存在的 Pod

NoExecute: kubernetes 将不会把 Pod 调度到具有该污点的 Node 上,同时也会将 Node 上已存在的 Pod 驱离

使用kubectl设置和去除污点的命令实例如下:

bash 复制代码
# 设置污点
kubectl taint nodes node1 key=value:effect
# 去除污点
kubectl taint nodes node1 key:effect-
# 去除所有污点
kubectl taint nodes node1 key-

测试准备:

1.准备节点node1(暂停node2节点,只看node1节点看效果)

2.为 node1 节点设置一个污点: tag=test:PreferNoSchedule; 然后创建 pod1 (pod1 可以正常调度)

3.修改为node1节点设置一个污点: tag=test:NoSchedule;然后创建pod2( pod1 正常 pod2 失败)

4.修改为node1节点设置一个污点: tag=test:NoExecute;然后创建pod3(3个pod都失败)

为node1设置污点PreferNoSchedule

bash 复制代码
kubectl taint nodes node1 tag=test:PreferNoSchedule

# 创建一个pod节点
kubectl run pod1 --image=nginx:latest -n nginx-ns

# 详细查看节点状态
kubectl get pod pod1 -n nginx-ns -o wide

发现他还是创建在node1节点上

重新将node1的状态设置为NoSchedule

bash 复制代码
kubectl taint nodes node1 tag:PreferNoSchedule-
kubectl taint nodes node1 tag=test:NoSchedule

设置好之后此时pod1节点还存在

此时再次创建pod2

此时就一直是挂起状态不让创建了

此时在将污点设置为NoExecute

bash 复制代码
kubectl taint nodes node1 tag:NoSchedule-
kubectl taint nodes node1 tag=test:NoExecute

当执行过后最初始的pod1都不见了,因为他是直接将pod1踢出了node1

将node2节点下线的方法

bash 复制代码
# 1. 标记节点为不可调度,防止新Pod调度进来
kubectl cordon node2

# 2. 优雅驱逐节点上的所有Pod(核心步骤)
kubectl drain node2 --ignore-daemonsets --delete-emptydir-data

# 3. 执行你的维护操作(关机/重启/升级)
# ...

# 4. 维护完成后,恢复节点调度
kubectl uncordon node2
相关推荐
江华森1 小时前
Docker 基础实战完整指南
运维·docker·容器
张忠琳11 小时前
【SR-IOV cni】(Part 4) SR-IOV Network Device Plugin 3.11.0 — 超深度架构分析
网络·云原生·kubernetes·cni·sriov
qq_4523962311 小时前
第二十篇:《Docker 故障排查常用命令与技巧》
运维·docker·容器
Henry-SAP17 小时前
SAP(ERP) BOM变更实时同步MRP方案
数据库·云原生
IT策士19 小时前
第45篇 k8s之实战:将 Web 应用迁移到 Kubernetes(下)
前端·容器·kubernetes
学代码的真由酱19 小时前
Docker基础
运维·docker·容器
devilnumber20 小时前
Kubernetes(K8s)重要知识点复习与记录
云原生·容器·kubernetes
IT策士21 小时前
第 47 篇 k8s之生产级考量:高可用、备份与升级
云原生·容器·kubernetes
小义_21 小时前
【Linux 1】
linux·运维·云原生·红帽