【云原生】k8s 如何运行 Container?

引语

在上篇文章 Kubernetes 的组件与架构 中,我们介绍了 Kubernetes 的 控制平面组件、Node 结点、插件,也知道了 Pod 是 Kubernetes 的最小调度单元,而我们使用 Container 正是运行在 Pod 中,在本文 Kubernetes 终于和我们之前所了解的 Docker 这类 Container 联系起来了,关于 Pod 我们将在本文详细去了解:

  • 什么是 Pod
  • Pod 基本操作
  • Pod 的 Labels
  • Pod 的 生命周期
  • Container 特性
  • Pod 的资源限制
  • Pod 中 Init 容器
  • 结点亲和性分配 Pod

1、什么是 Pod

摘取官网:kubernetes.io/zh-cn/docs/…

1.1 简介

Pod 是 Kubernetes 中可以创建、管理的最小的可以部署的计量单元。Pod 翻译为 豌豆荚,类似于豌豆荚中有很多的豌豆,Pod 管理一组(一个 | 多个)容器,这些容器共用存储、网络、以及怎么运行这组容器的声明。Pod 中的内容总是并置的,并且一并调度,在共享的上下文中运行

1.2 Pod 怎样管理多个容器?

Pod 中的容器被自动安排到集群中的一台物理机或者虚拟机上,一同被调度。容器之间可以共享资源和负载、彼此通信、协调何时以何种方式终止自身

例如:你可能有一个 Pod,有两个 容器 共享存储,也就是共享卷:

  • 其中一个容器为共享卷提供 Web 服务支持,也就是读(下图中的WebServer);
  • 另一个容器负责从远端更新这些文件(下图中的FilePuller拉取数据卷的数据)
  • 也就是一个 Pod 中是可以运行多个协同工作的容器的

在这里插入图片描述

1.3 如何使用 Pod?

Pod 是 Kubernetes 中最小的一个调度单元,我们通常不直接创建 Pod ,而是通过诸如 Deployment 或者 Job 这类的控制器来创建 Pod,不过在下面的学习中,我们会直接创建 Pod 去演示 Pod 的基本操作,如果需要跟踪 Pod 的状态,可以考虑 StatefulSet 资源。
因为,如果直接使用 Pod 其实只是对 Container 做了一个共享资源和网络,而做不到其他的事情,也就是说 Pod 是把 Container 做了一层包装,实现了容器之间的共享资源和网络

Kubernetes 集群中的 Pod 主要有两种用法:

  • 运行单个容器的 Pod,在这种情况下,可以把 Pod 看做单个容器的包装器,而且 Kubernetes 直接管理 Pod,而不是容器
  • 运行多个协同工作的容器的 Pod。Pod 可以封装多个紧密耦合并且需要共享资源的共处容器,把这些容器和存储打包成为一个可以管理的实体,其实就是上面那幅图。

说明:

  • 将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景,也就是上述的运行多个协同工作的容器
  • 一个 Pod 可以看做一个应用程序的单实例,如果想跑多个实例,就需要跑多个 Pod,当然,跑多个 Pod 通常是通过控制器来做

2、Pod 基本操作

2.1 查看 Pod

# 查看指定命名空间的 Pod
kubectl get po|pod|pods -n 命名空间
# 查看所有的 Pod
kubectl get po|pod|pods -A
# 查看 Pod 的输出情况(具体情况)
kubectl get po|pod|pods -o wide
# 监视 Pod 的变化
kubectl get po|pod|pods -w
# 查看默认的命名空间的 Pod
kubectl get po|pod|pods
# 查看指定 Pod 的相信信息
kubectl describe pod pod名称

2.2 创建 Pod

Pod 可以直接通过命令进行创建:pod: kubectl run nginx(Pod 名称) --image=nginx:1.19
这种创建方式的缺点也很明显,这个命令执行了一次之后就使用不了了,而我们可以把它当做一个模版记录下来,也就是下面提供的通过配置文件的方式进行创建:

# nginx-pod.yml
apiVersion: v1
kind: Pod
metaData:
  name: nginx
spec: 
  containers:
    - name: nginx
    - image: nginx:1.19
    - ports:
	    - containerPord: 80

然后通过下面的两个命令其中一个进行创建

kubectl create -f nginx-pod.yml

kubectl apply -f nginx-pod.yml

注意:

  • create 仅仅是不存在时创建,如果已经存在则会报错!
  • apply 不存在则创建,存在则更新配置,推荐使用 apply

2.3 删除 pod

# 根据 Pod 的名称进行删除
kubectl delete pod pod名称 
# 根据 配置文件 进行删除,因为配置文件中也有 Pod 的名称
kubectl delete -f pod.yml

2.4 进入 Pod 中容器

# 默认进入 Pod 中的第一个容器
kubectl exec -it nginx(Pod名称) --(固定写死) bash(执行命令)
# 指定进入 Pod 的某个容器
kubectl exec -it nginx(Pod名称) -c 容器名称 --(固定写死) bash(执行命令)

2.5 查看 Pod 日志

# 查看 Pod 中所有容器的日志
kubectl logs -f nginx(pod 名称)
# 查看 Pod 中指定容器的日志
kubectl logs -f nginx(pod 名称) -c 容器名称

2.6 查看 Pod 的描述信息

kubectl describe pod nginx(pod 名称)

3、Pod 运行多个容器

3.1 创建 Pod

apiVersion: v1  
kind: Pod  
metadata:  
  name: mypod  
  labels:  
    app: mypod  
spec:  
  containers:
    # 容器一:Nginx:1.19
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
      ports:  
        - containerPort: 80  
    # 容器二:Redis:5.0.10
    - name: redis  
      image: redis:5.0.10  
      imagePullPolicy: IfNotPresent  
      ports:  
        - containerPort: 6379
kubectl apply -f my-pod.yml

3.2 查看指定容器日志

kubectl logs -f mypod -c nginx(容器名称)

kubectl logs -f mypod -c redis(容器名称)

3.3 进入容器

kubectl exec -it mypod -c nginx -- sh
kubectl exec -it mypod -c redis -- sh

4、Pod 的 Labels

标签(Labels) 是附加到 Kubernetes 对象(比如 Pod)上的 键值对,用于指定对用户有意义且相关的对象的标识属性
标签作用:给 K8s 对象起别名,有了别名可以过滤和筛选
例如:给在杭州的服务器和在广州的服务器加上地区的标识,这样我们就可以根据地区去筛选出 Pod,并对 Pod 做一个合理的调配

4.1 语法

标签由键值对组成,对于值有以下约束:

  1. 必须为 63 个字符或更少(可以为空)
  2. 除非标签值为空,否则要以数字或者字符开头和结尾
  3. 包含破折号(-),下划线(_),点(.)

4.2 标签基本操作

# 查看标签
kubectl get pods --show-labels

# 给指定的 Pod 动态添加 标签键值对
kubectl label pod mypod env=prod

# 重写指定 Pod 标签的 键值对
kubectl label --overwrite pod mypod env=test

# 删除标签 `-`表示删除标签
kubectl label pod mypod env-

# 根据标签进行筛选:
# 1. 查看 key=value 的标签
kubectl get pod -l env=test
# 2. 查看键值对中有 key 的标签
kubectl get pod -l env
# 3. 查看键值对中没有 key 的标签
kubectl get pod -l '!env'
# 4. 查看键值对的值为某些值其中的标签
kubectl get pod -l 'env in (test,prod)'
# 5. 查看键值对的值不为某些值其中的标签
kubectl get pod -l 'env not in (test, prod)'

5、Pod 的生命周期

Pod 遵循预定义的生命周期:

  • 起始于Pending阶段,该阶段存在至少一个容器未创建
  • 如果此时至少一个主要容器正常启动,则进入Running阶段
  • 之后,如果有容器有失败状态结束则变为Failed,否则进入Succeeded阶段。

5.1 生命周期

之前,在Kubernetes 的组件与架构 中我们讲到 Node 结点 是运行 Pod 的,一个 Pod 被创建、同时赋予一个唯一的 ID(UID),并被调度到 Node 结点,并在终止或者删除之前一直运行在这个结点上
如果节点死掉了,调度到这个节点上的 Pod 也会被计划在给定时限结束后删除。

也就是说 Pod 自身不具备自愈能力,会因为节点的失效而被删除,同样也会因为节点资源耗尽或者节点维护而无法存活,不过 Kubernetes 会使用 控制器 来保证 Pod 的自愈能力自愈能力并不代表会重新调度到另外的节点,因为任意给定的 Pod 是不会重新被调度到另外的节点的,只能做到创建一个新的,属性完全相同,但是 UID 不同的 Pod 调度到另外一个节点

说明:如果有组件在声明时表示与 Pod 的生命周期相同,那么在 Pod 存在期间它会一直存在,但是 Pod 因为任何原因删除、或者被相同的Pod 替代的时候,这个对象会被删除并重建

5.2 Pod 阶段

阶段 描述
Pending(悬停) Pod 已经被 Kubernetes 接受,但是有一个或者多个容器没有创建,该阶段存于 等待 Pod 被调度到某个节点的时间 以及 通过网络下载镜像 的时间
Running(运行中) Pod 已经被调度到某个节点,所有容器都被创建了,并且至少有一个容器存在于运行状态
Succeeded(成功) Pod 所有容器都成功运行Failed(失败)Pod 所有容器都已经终止,并且至少有一个容器是非正常退出
Unknown(未知) 因为某些原因无法获得 Pod 的状态,一般是因为 Pod 与主机通信失败

说明:

  1. 当一个 Pod 被删除的时候,此时它的状态会变为 Terminating,但是这个状态并不是 Pod 的阶段之一,Pod 被赋予一个可以体面终止的期限,默认为 30 秒,也可以强制关闭这个容器:
    kubectl delete pod <pod_name> --force --grace-period=0
  2. 如果某节点死掉,或者与其他的节点失联,该节点上的所有 Pod 都会变为 Failed

6、Container 特性

6.1 容器生命周期

Kubernetes 会跟踪 Pod 每个 容器 的状态,并且可以根据 容器生命周期回调 来在容器生命周期中特定的时间点触发事件。其实就是 Kubernetes 在容器的生命周期中增加了几个 钩子函数

一旦调度器给 Pod 分配了节点,那么kubelet就会通过 容器运行时(Container Runtime)为 Pod 创建容器,而容器有三种状态:

  • Waiting (等待)
    如果容器不存在 Running或Terminated状态,它就处在Waiting状态,在这个状态,容器仍然在运行完成启动所需要的操作,例如从镜像仓库拉取镜像、向容器应用Secret数据

  • Running (运行中)
    该状态表明容器正在执行状态,并没有问题发生,而如果配置了postStart回调,就会在转为Running状态之前完成这个回调,例如在开始Java应用程序服务之前确定MySQL服务是否开启了

  • Terminated (已终止)
    表明容器已经开始执行或者正常结束、失败,如果配置了preStop回调,就会在终止之前,也就是进入Terminated状态之前执行该回调

6.2 容器生命周期回调 / 事件 / 钩子

回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时 运行在处理程序中实现的代码,有两个回调暴露给容器:

  • PostStart :
    这个回调在容器被创建之后立即执行,但是不能保证回调在容器入口点之前执行,没有参数传递给处理程序,也就是无法确定容器执行的顺序和这个事件执行的顺序。
    这里可能就无法完成在Java应用程序服务开始之前确定MySQL服务是否开启,但是这个可以通过Init容器来实现,Init 容器我们后面会讲到

  • PreStop :
    在容器因 API 请求或者管理事件而被终止,那么在这之前,此回调会被调用。而停止 Pod 是有一个时间限制的,不管PreStop是否执行结束

  • 使用容器生命周期回调实例:

apiVersion: v1  
kind: Pod  
metadata:  
  name: mypod  
  labels:  
    app: mypod  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
      lifecycle:  
        postStart:  
          exec:  
            # 写入 start.txt
            command: ["/bin/sh", "-c", "echo postStart >> /start.txt"]  
        preStop:  
          exec:  
            # 写入 stop.txt 并睡眠 5s
            command: ["/bin/sh", "-c", "echo preStop >> /stop.txt && sleep 5"]  
      ports:  
        - containerPort: 80

6.3 容器重启策略

Pod 中的 spec 包含一个 restartPolicy字段,它表示容器重启的策略,其可能取值包括Always(总是重启)、OnFailure(容器异常退出时重启)、Never(永不重启)

它是包含于spec中,而不是container中,这是因为我们之前讲到,Kubernetes 的最小操作单元为 Pod,Pod 中的所有容器有同一套声明规则

说明:Pod 的重启策略也并不是一失败就立马重启,当 Pod 中的容器退出时,kubelet 会按照 指数回退 方式计算重启的延时(10s,20s,40s……),其最长延时为五分钟,一旦某容器执行了 10 分钟并且没有出现问题,kubelet 会对该容器的重启回退计时器执行 重置操作。

apiVersion: v1  
kind: Pod  
metadata:  
  name: myapp  
  labels:  
    app: myapp  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.0.19  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always # OnFailure Never

6.4 自定义容器启动命令

和 Docker 一样,K8s 也可以通过 command、args 来修改 容器启动时默认执行命令 以及 传递相关的参数一般使用 command 修改启动命令,使用 args 为启动命令传递参数

apiVersion: v1  
kind: Pod  
metadata:  
  name: redis  
  labels:  
    app: redis  
spec:  
  containers:  
    - name: redis  
      image: redis:5.0.10  
      imagePullPolicy: IfNotPresent  
      command: ["redis-server"]  
      # 开启 AOF 
      args: ["--appendonly yes"]  
  restartPolicy: Always

6.5 容器探针

probe 是由 Kubelet 对容器执行的定期诊断,要执行诊断,Kubelet 既可以在容器内执行代码,也可以发出一个网络请求。可以理解为定期对容器进行健康检查

  • 探针类型
    针对运行中的容器,kubelet可以选择是否执行以下三种探针,以及如何对探测结果作出反应:

    • livenessProbe 指示容器是否正在运行。如果存活态探测失败,则 Kubelet 就会杀死容器,并且容器将根据重启策略决定未来的操作。
    • readinessProbe 指示容器是否准备好为请求提供服务。如果就绪态探测失败,则 端点控制器 会与 Pod 匹配的端点列表中删除该 Pod 的 IP地址。
    • startupProbe 1.7+ 指示容器中的应用是否已经启动。如果提供了启动探针,其他所有探针都会被禁用,直到这个探针成功为止,同样的,如果启动探针失败,Kubelet 会杀死容器
  • 探针机制
    使用探针来检查容器有以下四种机制来进行检测容器:

    • exec
      通过执行一个 Shell 命令来判断,即在容器执行指定命令,如果命令退出时返回 0 则表示成功

    • grpc
      使用 gRPC 执行一个远程调用,目标应该实现 gRPC 健康检查,如果相应的状态是”SERVING“,则认为诊断成功

    • httpGet
      对容器的 IP地址 上指定端口和路径执行 HTTP GET 请求,如果相应的状态码大于等于 200 且小于 400 则诊断认为是成功的

    • tcpSocket
      对容器的 IP地址 上的指定端口执行 TCP 检查,如果端口能够正常通信则认为是成功的

  • 探针结果
    每次探测、每种探针、每种机制的结果都是以下三种:

    • Success(成功) 容器通过了诊断
    • Failure(失败) 容器未通过诊断
    • Unknown(未知) 诊断失败,因此不会采取任何行动
  • 探针参数

# 容器开始多少秒之后开始探针
initialDelaySeconds: 5
# 检测间隔时间
periodSeconds: 4
# 默认检测超时时间
timeoutSeconds: 1
# 默认失败次数为 3 次,达到 3 次后重启 Pod
failureThreshold: 3
# 默认成功次数为 1 次,1次检测成功代表成功
successThreshold: 1
  • 使用探针

    • exec

      apiVersion: v1  
      kind: Pod  
      metadata:  
        name: nginx  
        labels:  
          app: nginx  
      spec:  
        containers:  
          - name: nginx  
            image: nginx:1.19  
            ports:  
              - containerPort: 80  
            args:  
              - /bin/sh  
              - -c  
              - sleep 7 && nginx -g "daemon off;"  
            imagePullPolicy: IfNotPresent  
            livenessProbe:  
              exec:  
                # 查看 Nginx 是否有启动成功
                command:  
                  - ls  
                  - /var/run/nginx.pid  
              initialDelaySeconds: 5  
              periodSeconds: 4  
              timeoutSeconds: 1  
              failureThreshold: 3  
              successThreshold: 1  
        restartPolicy: Always
      

说明:

如果睡眠 7s,第一次检测发现失败,但是第二次检测发现成功,只有 3次失败 才回重启 如果睡眠 30s,会超过三次检测失败,K8s
就会杀死容器重新启动,反复循环这个过程

  • tcpSocket
apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      ports:  
        - containerPort: 80  
      args:  
        - /bin/sh  
        - -c  
        - sleep 7 && nginx -g "daemon off;"  
      imagePullPolicy: IfNotPresent  
      livenessProbe:  
        # 查看 Nginx 的 80 端口是否能够正常通信
        tcpSocket:  
          port: 80  
        initialDelaySeconds: 5  
        periodSeconds: 4  
        timeoutSeconds: 1  
        failureThreshold: 3  
        successThreshold: 1  
  restartPolicy: Always
  • httpGet
apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      ports:  
        - containerPort: 80  
      args:  
        - /bin/sh  
        - -c  
        - sleep 7 && nginx -g "daemon off;"  
      imagePullPolicy: IfNotPresent  
      livenessProbe:  
        # 请求主页 index.html 查看是否能够正常返回 200
        httpGet:  
          port: 80  
          path: /index.html  
        initialDelaySeconds: 5  
        periodSeconds: 4  
        timeoutSeconds: 1  
        failureThreshold: 3  
        successThreshold: 1  
  restartPolicy: Always
  • gRPC

官网参考地址:配置存活、就绪和启动探针 | Kubernetes

6.6 资源限制

如果不对容器进行内存限制和CPU限制,容器可以无限制的使用当前结点的资源,这样如果某个容器使用率过高就会对其他的容器造成影响

在 Kubernetes 中对于容器资源限制主要有两方面的限制:

  • 内存资源限制:分为内存请求和内存限制,我们保证容器拥有它请求数量的内存,但不能使用超过限制数量的内存
  • CPU 资源限制:同样分为 request(请求) 和 limit(限制)。容器使用的 CPU 不能超过所配置的限制,如果系统有空闲的 CPU 时间,则可以保证给容器分配器请求数量的 CPU 资源

请求 request memory cpu : 可以使用的基础资源
限制 limit memory cpu : 可以使用的最大资源 如果超过最大资源之后 容器会被

6.6.1 metrics-server

官网地址:github.com/kubernetes-…

Kubernetes Metrics Server(Kubernetes 指标服务器),它是一个可扩展的、高效的容器资源度量源。Metrics Server 用于监控每个 Node 和 Pod 的负载,从 Kubelets 收集资源指标,并通过 Metrics API 在 Kubernetes apiserver 中公开,这样我们就可以通过 kubectl top 访问

  • 查看 Metrics-server 是否正在运行,可以键入以下指令:
# 查看所有的资源指标 API
kubectl get apiservices
  • 如果资源指标 API 可用,则输出会包含一个对 metrics.k8s.io 的引用
NAME
v1betal.metrics.k8s.io

安装 metrics-server ,使用 github 上的源

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

查看容器正在使用内存情况:

kubectl top pod pod名称 

6.6.2 指定内存请求和限制

为容器指定内存请求,直接在配置文件中添加resources:limits,这里要简单提一下内存的单位:内存的基本单位是字节(byte),可以使用E P T G M K Ei Pi Ti Gi Mi Ki

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx-memory  
  labels:  
    app: nginx-memory  
spec:  
  containers:  
    - name: nginx-memory  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
      resources:  
        requests:  
          memory: 100M  
        limits:  
          memory: 200M  
  restartPolicy: Always

查看容器内存使用情况(包括容器内存的请求和限制):

kubectl get pod pod名称 --output=yaml

内存请求和限制的目的
通过给集群中的容器配置内存请求和限制,可以有效利用集群节点上可用的内存资源,通过将 Pod 的内存请求保持在较低的水平,可以更好的安排 Pod 调度,而限制大于请求的好处是:

  • Pod 可以进行一些突发活动,从而更好的利用可用内存
  • Pod 可以在突发活动期间,可使用的内存被限制为合理的数量

如果没有指定内存限制
如果没有指定内存限制,则自动遵循以下情况之一:

  • 容器无限制的使用内存,会使用节点所有的可用内存,进而可能导致 OOM Killer
  • 如果运行的容器所在的命名空间有默认的内存限制,则会被自动分配默认限制

如果只指定了内存限制,没有指定内存请求
Kubernetes 会自动设置与限制相同数值的请求值,这在 内存限制 和 CPU限制 中都是这样的

6.6.3 指定 CPU 请求和限制

CPU 请求和限制和 内存请求和限制 差不多,唯一的区别就是,CPU 在超出 limit限制 后不会 Kill,而会继续运行,它的使用也和内存类似:

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx-memory  
  labels:  
    app: nginx-memory  
spec:  
  containers:  
    - name: nginx-memory  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
      resources:  
        requests:  
          cpu: 100m
        limits:  
          cpu: 200m  
  restartPolicy: Always

值得一提的是 CPU 的单位:小数值是可以用的,一个请求 0.5 CPU 的容器只会保证获得请求 1个CPU 容器的 CPU 的一半,可以使用 m 表示毫,例如 100m CPU 与 0.1 CPU 相同。

7 Pod 中 init 容器

Pod 中可能管理很多的容器,不同于 Nginx、Redis 这种应用容器,Init 容器在所有应用容器启动之前运行,因此 Init 容器 中可以包括一些应用镜像中不存在的实用工具和安装脚本
例如:现在有一个 Java 应用程序,那么就可以在 Init 容器中去验证 MySQL 服务是否开启,并可以执行一些 SQL 脚本

7.1 init 容器特点

  • 它们总是运行到完成,也就是说,如果 Pod 的 Init 容器失败,Kubelet 会不断的重启 Init 容器直至成功为止。如果restartPolicy = never,那么 Init 容器失败会把 Pod 状态设置为失败
  • 一个 Pod 中可以拥有多个 Init 容器,每个 Init 容器都必须在下一个启动之前完成
  • Init 容器不支持lifecycle livenessProbe readinessProbe startupProbe
  • Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷、安全设置

7.2 使用 Init 容器

在 Pod 的规约中用来描述应用容器的 containers 数组平行的位置指定 Init 容器

apiVersion: v1  
kind: Pod  
metadata:  
  name: init  
  labels:  
    app: init  
spec:  
  containers:  
    - name: init  
      image: busybox:1.28  
      command: ["sh", "-c", "echo The app is running! && sleep 3600"]  
      imagePullPolicy: IfNotPresent  
  initContainers:  
    - name: init-service  
      image: busybox:1.28  
      command: ["sh", "-c", "echo init-service is running! && sleep 5"]  
  
    - name: init-db  
      image: busybox:1.28  
      command: [ "sh", "-c", "echo init-db is running! && sleep 5" ]  
  restartPolicy: Always

查看启动详情:
在这里插入图片描述

8 节点亲和性分配 Pod

如果一个 Web 服务放在一个 Pod,而 日志服务 也放在一个 Pod,最好就是能够把两个 Pod 放在一个节点,这样就可以减少一些节点之间的通信,也就是说在某些特定的场景下,我们更希望能够指定某些 Pod 在哪个结点上:

我们可以约束一个 Pod 以便 限制 它只在特定的节点上运行,或者优先在特定的节点上运行,有以下四种方法可以选择 Kubernetes 对指定 Pod 的调度:

  • 与节点标签匹配的 nodeSelector 推荐
  • 亲和性和反亲和性
  • nodeName
  • Pod 拓扑分布约束

8.1 给节点添加标签

# 选择一个节点,给它添加一个标签
kubectl label nodes k8s-node1(节点名称) disk=ssd


# 查看节点的标签
kubectl get pods --show-labels

8.2 根据选择节点标签指派 pod 到指定节点 [nodeSelector]

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always  
  # 选择节点为标签为 ssd 的节点
  # 我们可以给 CPU 处理快的节点加上 cpu=fast cpu=slow 类似的节点
  # 这样就可以在给 Pod 选择节点的时候加上 cpu=fast
  nodeSelector:  
    disk: ssd

8.3 根据节点名称指派 pod 到指定节点 [nodeName]

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always 
  # 根据节点的名称指派 pod 到 指定节点
  # 这种方式并不推荐使用 
  # 如果找不到 nodeName 会处于一个 Pending 的状态,直至有对应名字的节点被创建
  nodeName: k8s-node1

8.4 根据亲和性和反亲和性指派 pod 到指定结点

说明
nodeSelector提供了一种最简单的方法来把 Pod 约束到有指定标签的 节点 上,而亲和性和反亲和性扩展了你可以定义的约束类型,具体好处有以下三点:

  • 亲和性、反亲和性的语言表达能力更强。对于 nodeSelector 和 nodeName 只能选择存在的标签,而亲和性和反亲和性提供对选择逻辑更强的控制能力
  • 可以表明某些规则是软需求或者偏好,这样对于不存在的标签和结点名称,仍然可以调度 Pod
  • 可以使用节点上运行的其他 Pod 标签来实施调度约束,让处于同一个网络域的 Pod 放在一起

亲和性功能有两种亲和性:

  • 节点亲和性:它的功能类似于nodeSelector,但是它能够写表达式和指定软规则,也就是说它能够表达的逻辑更强
  • Pod 亲和性:它可以做到和节点上的 Pod 去计算亲和性,如果 node1 节点上的 Pod 与当前要调度的 Pod 并不亲和,那么就可能会调度到 node2 节点上去

节点亲和性概念上类似于nodeSelector ,它也是根据节点上的标签来约束 Pod 调度到哪些节点上,节点亲和性有两种实现:

  • requiredDuringSchedulingIgnoredDuringExecution :它设置的规则是和 nodeSelector 一样,必须满足才能执行调度,但是它的语法表达能力更强
  • preferredDuringSchedulingIgnoredDuringExecution :它设置的是弱规则,即调度器会尝试去寻找满足对应规则的节点,如果没找到,Pod 不会处于 Pending 状态,调度器仍然会调度该 Pod
    注意:在上述类型中,IgnoredDuringExecution 意味着如果节点标签在 Kubernetes 中调度 Pod 后发生了变更, Pod 会继续运行
apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  # 亲和性设置  
  affinity:  
    # 节点亲和性  
    nodeAffinity:  
      # 必须满足的标签  
      requiredDuringSchedulingIgnoredDuringExecution:  
        nodeSelectorTerms:  
          - matchExpressions:  
              - key: disk  
                operator: In  
                values:  
                  - ssd  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always

注意:你可以使用In、NotIn、Exists、DoesNotExist、Gt、Lt 作为 operator 这个操作符,用于实现亲和性和反亲和性,其实这里就能看得出来,相比于 nodeSelector ,亲和性和反亲和性的逻辑表达能力更强

8.5 节点亲和性权重

当使用 preferredDuringSchedulingIgnoredDuringExecution 的时候就会涉及到权重weight,它的取值在 1 - 100 之间,权重越高,在选择的时候优先选择

apiVersion: v1  
kind: Pod  
metadata:  
  name: nginx  
  labels:  
    app: nginx  
spec:  
  # 亲和性设置  
  affinity:  
    # 节点亲和性  
    nodeAffinity:  
      preferredDuringSchedulingIgnoredDuringExecution:  
        - preference:  
            matchExpressions:  
              - key: cpu  
                operator: In  
                values:  
                  - fast  
          weight: 80  
        - preference:  
            matchExpressions:  
              - key: disk  
                operator: In  
                values:  
                  - ssd  
          weight: 50  
  containers:  
    - name: nginx  
      image: nginx:1.19  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always

说明: 这里就会优先选择 cpu=fast 的节点,然后如果没有再选择 disk=ssd 的节点,因为前者的权重相较于后者更高

8.6 pod 间亲和性和反亲和性及权重

与节点亲和性类似,Pod 的亲和性与反亲和性也有两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution
    例如,可以使用requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器,将两个服务器的 Pod 放在同一个云提供商可用区内,因为他们之间的通信比较频繁。使用 - preferredDuringSchedulingIgnoredDuringExecution 反亲和性来把同一个服务的多个 Pod 放在不同的云提供商可用区中。

要使用 Pod 间亲和性,要使用 Pod 规约中的 spec.affinity.podAffinity 字段:

apiVersion: v1  
kind: Pod  
metadata:  
  name: redis  
  labels:  
    app: redis  
spec:  
  containers:  
    - name: redis  
      image: redis:5.0.10  
      imagePullPolicy: IfNotPresent  
  restartPolicy: Always  
  affinity:  
    podAffinity:  
      requiredDuringSchedulingIgnoredDuringExecution:  
        - topologyKey: BeiJing  
          labelSelector:  
            matchExpressions:  
              - key: app  
                operator: In  
                values:  
                  - redis

说明:这里出现了topologyKey 这个约束,这个约束就是我们在调度 Pod 到节点的时候,该节点的标签需要有 BeiJing 这个 Key,我们可以把所有北京的节点都加上这个 Key,杭州的节点都加上 HangZhou,这样我们就可以使用节点的亲和性让 Pod 调度到我们想要的地区的节点上

猜你喜欢

转载自blog.csdn.net/u011397981/article/details/130705160