写点什么

Kubernetes Pod 篇:带你轻松玩转 Pod

用户头像
xcbeyond
关注
发布于: 2021 年 01 月 27 日

本文将对 Kubernetes 如何发布与管理容器应用进行详细说明,主要包括 Pod 概述、基本用法、生命周期、Pod 的控制和调度管理、Pod 的升级和回滚,以及 Pod 的扩容机制等内容,并结合具体详细的示例,带你轻松玩转 Pod,开启 Kubernetes 容器的编排之路。



1、Pod 概述


1.1 Pod 是什么?


Pod是 Kubernetes 中的原子对象,是基本构建单元。


Pod表示集群上一组正在运行的容器。通常创建 Pod 是为了运行单个主容器。Pod 还可以运行可选的 sidecar 容器,以实现诸如日志记录之类的补充特性。(如:在 Service Mesh 中,和应用一起存在的istio-proxyistio-init容器)


一个Pod中可以包含多个容器(其他容器作为功能补充),负责处理容器的数据卷、秘钥、配置。


1.2 为什么要引入 Pod 概念?


原因 1:Kubernetes 可扩展


Kubernetes 不会直接和容器打交道,Kubernetes 的使用者能接触到的资源只有 Pod,而 Pod 里可以包含多个容器。当我们在 Kubernetes 里用 kubectl 执行各种命令操作各类资源时,是无法直接操作容器的,往往都是借助于 Pod。


Kubernetes 并不是只支持 Docker 这一个容器运行时。 为了让 Kubernetes 不和某种特定的容器运行时进行技术绑死,而是能无需重新编译源代码就能够支持多种容器运行时技术的替换,和我们面向对象设计中引入接口作为抽象层一样,在 Kubernetes 和容器运行时之间我们引入了一个抽象层,即容器运行时接口(CRI:Container Runtime Interface)。



借助 CRI 这个抽象层,使得 Kubernetes 不依赖于底层某一种具体的容器运行时实现技术,而是直接操作 Pod,Pod 内部再管理多个业务上紧密相关的用户业务容器,这种架构更便于 Kubernetes 的扩展。


原因 2:易管理


假设 Kubernetes 中没有 Pod 的概念,而是直接管理容器,那么有些容器天生需要紧密关联,如:在 ELK 中,日志采集 Filebeat 需要和应用紧密部署在一起。如果将紧密关联的一组容器作为一个单元,假设其中一个容器死亡了,此时这个单元的状态应该如何定义呢?应该理解成整体死亡,还是个别死亡?


这个问题不易回答的原因,是因为包含了这一组业务容器的逻辑单元,没有一个统一的办法来代表整个容器组的状态,这就是 Kubernetes 引入 Pod 的概念,并且每个 Pod 里都有一个 Kubernetes 系统自带的 pause 容器的原因,通过引入 pause 这个与业务无关并且作用类似于 Linux 操作系统守护进程的 Kubernetes 系统标准容器,以 pause 容器的状态来代表整个容器组的状态。



对于这些天生需要紧密关联的容器,可以放在同一个 Pod 里,以 Pod 为最小单位进行调度、扩展、共享资源及管理生命周期。


原因 3:通讯、资源共享


Pod 里的多个业务容器共享 Pause 容器的 IP,共享 Pause 容器挂接的 Volume,这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。


相同的 namespace 可以用 localhost 通信,可以共享存储等。


1.3 Pod 能够带来什么好处


搞清楚了 Pod 的由来,它到底能够为我们带来哪些好处呢?


  • Pod 做为一个可以独立运行的服务单元,简化了应用部署的难度,以更高的抽象层次为应用部署管提供了极大的方便。

  • Pod 做为最小的应用实例可以独立运行,因此可以方便的进行部署、水平扩展和收缩、方便进行调度管理与资源的分配。

  • Pod 中的容器共享相同的数据和网络地址空间,Pod 之间也进行了统一的资源管理与分配。


2、Pod 基本用法


无论通过命令kubectl,还是 Dashboard 图形管理界面来操作,都离不开资源清单文件的定义。如果采用 Dashboard 图形管理界面操作,最终还是基于 kubectl 命令操作的,这里只介绍使用 kubectl 命令来操作 Pod。


关于 kubectl 命令更多说明,可以参考官方文档:https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-strong-getting-started-strong-


Pod 资源清单中有几个重要属性:apiVersionkindmetadataspec以及status。其中apiVersionkind是比较固定的,status是运行时的状态,所以最重要的就是metadataspec两个部分。


(Kubernetes 资源清单的定义,可参考上一篇文章:Kubernetes资源清单篇:如何创建资源?)


先来定义一个简单的 Pod 资源文件,命名为 frontend-pod.yml:


示例中的 Pod 是在命名空间 test 中定义的,所以接下来的执行命令中都涉及指定命名空间的参数-n test。如果在默认命名空间 default 中定义,无需指定参数-n 执行。


apiVersion: v1kind: Podmetadata:  name: frontend  namespace: test	# 如果没有命名空间test,需提前创建。也可以使用默认命名空间default,即:namespace属性标签可以不定义  labels:    app: frontendspec:  containers:  - name: frontend    image: xcbeyond/vue-frontend:latest		# 发布在DockerHub中的镜像    ports:      - name: port        containerPort: 80        hostPort: 8080
复制代码


可以使用命令kubectl explain pod来查看各个属性标签的具体用法及含义。


[xcbeyond@bogon ~]$ kubectl explain podKIND:     PodVERSION:  v1
DESCRIPTION: Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.
FIELDS: apiVersion <string> APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string> Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <Object> Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object> Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
status <Object> Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
复制代码


2.1 创建


基于资源清单文件来创建 Pod,kubectl create -f <filename>


[xcbeyond@localhost ~]$ kubectl create -f frontend-pod.ymlpod/frontend created
复制代码


2.2 查看状态


创建完 Pod 后,想知道 Pod 的运行状态,可通过命令kubectl get pods -n <namespace>查看:


(default 命名空间,可不指定-n 参数,非 default 则需指定具体 namespace,否则查询不到。)


[xcbeyond@localhost ~]$ kubectl get pods -n testNAME       READY   STATUS    RESTARTS   AGEfrontend   1/1     Running   0          36s
复制代码


2.3 查看配置


如果想了解一个正在运行的 Pod 配置,可通过命令kubectl get pod <pod-name> -n <namespace> -o <json|yaml>查看:


(-o 参数用于指定输出配置格式,json、yaml 格式)


此时查看结果处于运行态的结果,其中包含很多属性,我们只需关注关键属性即可。


[xcbeyond@localhost ~]$ kubectl get pod frontend -n test -o yamlapiVersion: v1kind: Podmetadata:  creationTimestamp: "2020-11-19T08:33:20Z"  labels:    app: frontend  managedFields:  - apiVersion: v1    fieldsType: FieldsV1    fieldsV1:      f:metadata:        f:labels:          .: {}          f:app: {}      f:spec:        f:containers:          k:{"name":"frontend"}:            .: {}            f:image: {}            f:imagePullPolicy: {}            f:name: {}            f:ports:              .: {}              k:{"containerPort":80,"protocol":"TCP"}:                .: {}                f:containerPort: {}                f:hostPort: {}                f:name: {}                f:protocol: {}            f:resources: {}            f:terminationMessagePath: {}            f:terminationMessagePolicy: {}        f:dnsPolicy: {}        f:enableServiceLinks: {}        f:restartPolicy: {}        f:schedulerName: {}        f:securityContext: {}        f:terminationGracePeriodSeconds: {}    manager: kubectl-create    operation: Update    time: "2020-11-19T08:33:20Z"  - apiVersion: v1    fieldsType: FieldsV1    fieldsV1:      f:status:        f:conditions:          k:{"type":"ContainersReady"}:            .: {}            f:lastProbeTime: {}            f:lastTransitionTime: {}            f:status: {}            f:type: {}          k:{"type":"Initialized"}:            .: {}            f:lastProbeTime: {}            f:lastTransitionTime: {}            f:status: {}            f:type: {}          k:{"type":"Ready"}:            .: {}            f:lastProbeTime: {}            f:lastTransitionTime: {}            f:status: {}            f:type: {}        f:containerStatuses: {}        f:hostIP: {}        f:phase: {}        f:podIP: {}        f:podIPs:          .: {}          k:{"ip":"172.18.0.5"}:            .: {}            f:ip: {}        f:startTime: {}    manager: kubelet    operation: Update    time: "2020-11-23T08:10:40Z"  name: frontend  namespace: test  resourceVersion: "28351"  selfLink: /api/v1/namespaces/test/pods/frontend  uid: be4ad65c-e426-4110-8337-7c1dd542f647spec:  containers:  - image: xcbeyond/vue-frontend:latest    imagePullPolicy: Always    name: frontend    ports:    - containerPort: 80      hostPort: 8080      name: port      protocol: TCP    resources: {}    terminationMessagePath: /dev/termination-log    terminationMessagePolicy: File    volumeMounts:    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount      name: default-token-bbmj5      readOnly: true  dnsPolicy: ClusterFirst  enableServiceLinks: true  nodeName: minikube  preemptionPolicy: PreemptLowerPriority  priority: 0  restartPolicy: Always  schedulerName: default-scheduler  securityContext: {}  serviceAccount: default  serviceAccountName: default  terminationGracePeriodSeconds: 30  tolerations:  - effect: NoExecute    key: node.kubernetes.io/not-ready    operator: Exists    tolerationSeconds: 300  - effect: NoExecute    key: node.kubernetes.io/unreachable    operator: Exists    tolerationSeconds: 300  volumes:  - name: default-token-bbmj5    secret:      defaultMode: 420      secretName: default-token-bbmj5status:  conditions:  - lastProbeTime: null    lastTransitionTime: "2020-11-19T08:33:20Z"    status: "True"    type: Initialized  - lastProbeTime: null    lastTransitionTime: "2020-11-23T08:10:40Z"    status: "True"    type: Ready  - lastProbeTime: null    lastTransitionTime: "2020-11-23T08:10:40Z"    status: "True"    type: ContainersReady  - lastProbeTime: null    lastTransitionTime: "2020-11-19T08:33:20Z"    status: "True"    type: PodScheduled  containerStatuses:  - containerID: docker://84d978ee70d806d38b9865021d9c68881cf096960c7eb45e87b3099da85b4f6d    image: xcbeyond/vue-frontend:latest    imageID: docker-pullable://xcbeyond/vue-frontend@sha256:aa31cdbca5ca17bf034ca949d5fc7d6e6598f507f8e4dad481e050b905484f28    lastState: {}    name: frontend    ready: true    restartCount: 0    started: true    state:      running:        startedAt: "2020-11-23T08:10:40Z"  hostIP: 172.17.0.2  phase: Running  podIP: 172.18.0.5  podIPs:  - ip: 172.18.0.5  qosClass: BestEffort  startTime: "2020-11-19T08:33:20Z"
复制代码


2.4 查看日志


如果想查看标准输出的日志,可以通过命令kubectl logs <pod-name> -n <namespace>查看:


[xcbeyond@localhost ~]$ kubectl logs frontend -n test
复制代码


如果 Pod 中有多个容器,查看特定容器的日志需要指定容器名称kubectl logs <pod-name> -c <container-name>


2.5 修改配置


如果想修改已创建的 Pod,如修改某个标签 label,有以下几种方式:


(1)通过标签管理命令kubectl label设置或更新资源对象的 labels。


*该种方式只是针对标签的修改。*


通过命令kubectl get pods -n <namespace> --show-labels查看标签


[xcbeyond@localhost ~]$ kubectl get pods -n test --show-labelsNAME       READY   STATUS    RESTARTS   AGE   LABELSfrontend   1/1     Running   0          4d    app=frontend
复制代码


通过命令kubectl label pod <pod-name> -n <namespace> <key=value>新增标签


[xcbeyond@localhost ~]$ kubectl label pod frontend -n test tir=frontendpod/frontend labeled[xcbeyond@localhost ~]$ kubectl get pods -n test --show-labelsNAME       READY   STATUS    RESTARTS   AGE    LABELSfrontend   1/1     Running   0          4d1h   app=frontend,tir=frontend
复制代码


通过命令kubectl label pod <pod-name> -n <namespace> <key=new-value> --overwrite修改标签


[xcbeyond@localhost ~]$ kubectl label pod frontend -n test tir=unkonwn --overwritepod/frontend labeled[xcbeyond@localhost ~]$ kubectl get pods -n test --show-labelsNAME       READY   STATUS    RESTARTS   AGE    LABELSfrontend   1/1     Running   0          4d1h   app=frontend,tir=unkonwn
复制代码


(2)通过命令kubectl apply -f <filename>命令对配置进行更新。


(3)通过命令kubectl edit -f <filename> -n <namespace>命令对配置进行在线更新。


(4)通过命令kubectl replace -f <filename> -n <namespace> --force命令强制替换资源对象。


实际上,先删除在替换。


[xcbeyond@localhost ~]$ kubectl replace -f frontend-pod.yml --forcepod "frontend" deletedpod/frontend replaced
复制代码


2.6 删除


通过命令kubectl delete (-f <filename> | pod [<pod-name> | -l label]) -n <namespace>进行删除。


[xcbeyond@localhost ~]$ kubectl delete pod frontend -n testpod "frontend" deleted
复制代码


3、Pod 生命周期


Pod对象自从其创建开始至其终止退出的时间范围称为其 Pod 生命周期。在这段时间中,Pod会处于多种不同的状态,并执行一些操作。其中,创建主容器(main container)为必需的操作,其他可选的操作还包括运行初始化容器(init container)、容器启动后钩子(post start hook)、容器的存活性探测(liveness probe)、就绪性探测(readiness probe)以及容器终止前钩子(pre stop hook)等,这些操作是否执行则取决于Pod的定义。如下图所示:



Podstatus字段是一个PodStatus的对象,PodStatus中有一个phase字段。


无论是手动创建还是通过Deployment等控制器创建,Pod对象总是处于其生命周期中以下几个阶段(phase)之一:


  • 挂起(PendingAPI Server已经创建了该Pod,并已存入etcd中,但它尚未被调度完成,或者仍处于从仓库下载镜像的过程中。

  • 运行中(RunningPod内所有容器均已创建,被调度至某节点,并且所有容器都已经被kubelet创建完成,且至少有一个容器处于运行状态、正在启动状态或正在重启状态。

  • 成功(SucceededPod内所有容器都已经成功执行后退出终止,且不会再重启。

  • 失败(FailedPod内所有容器都已退出,并且至少有一个容器是因为失败而终止退出。即容器以非0状态退出或者被系统禁止。

  • 未知(Unknown:由于某种原因Api Server无法正常获取到该Pod对象的状态信息,可能由于无法与所在工作节点的kubelet通信所致(网络通讯不畅)


3.1 Pod 的创建过程


Pod是 Kubernetes 中的基础单元,了解它的创建过程对于理解其生命周期有很大的帮助,如下图描述了一个Pod资源对象的典型创建过程。


(1)用户通过kubectl或其他API客户端提交了Pod SpecAPI Server。(create Pod)


(2)API Server尝试着将Pod对象的相关信息写入etcd存储系统中,待写入操作执行完成,API Server即会返回确认信息至客户端。


(3)API Server开始反馈etcd中的状态变化。


(4)所有的kubernetes组件均使用“watch”机制来跟踪检查API Server上的相关的变动。


(5)kube-scheduler(调度器)通过其“watcher”觉察到API Server创建了新的Pod对象但尚未绑定至任何工作节点。


(6)kube-schedulerPod对象挑选一个工作节点并将结果信息更新至API Server


(7)调度结果信息由API Server更新至etcd存储系统,而且API Server也开始反馈此Pod对象的调度结果。


(8)Pod被调度到的目标工作节点上的kubelet尝试在当前节点上调用Docker启动容器,并将容器的结果状态返回送至API Server


(9)API ServerPod状态信息存入etcd系统中。


(10)在etcd确认写入操作成功完成后,API Server将确认信息发送至相关的kubelet,事件将通过它被接受。


3.2 重要过程


3.2.1 初始化容器(Init Container)


初始化容器(init container)是一种专用的容器,用于在启动应用容器(app container)之前启动一个或多个初始化容器,完成应用容器所需的预置条件。



初始化容器与普通的容器非常相似,但它有如下独有特征:


  • 初始化容器总是运行到成功完成为止。

  • 每个初始化容器都必须在下一个初始化容器启动之前成功完成。


根据 Pod 的重启策略( restartPolicy ),当 init container 执行失败,而且设置了 restartPolicy 为 Never 时,Pod 将会启动失败,不再重启;而设置 restartPolicy 为 Always 时,Pod 将会被系统自动重启。


如果一个 Pod 指定了多个初始化容器,这些初始化容器会按顺序一次运行一个。只有当前面的初始化容器必须运行成功后,才可以运行下一个初始化容器。当所有的初始化容器运行完成后,Kubernetes 才初始化 Pod 和运行应用容器。


3.2.2 容器探测


容器探测(container probe)是Pod对象生命周期中的一项重要的日常任务,它是kubelet对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler)进行定义。Kubernetes支持三种处理器用于Pod探测:


  • ExecAction:在容器内执行指定命令,并根据其返回的状态码进行诊断的操作称为Exec探测,状态码为0表示成功,否则即为不健康状态。

  • TCPSocketAction:通过与容器的某TCP端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。

  • HTTPGetAction:通过向容器IP地址的某指定端口的指定path发起HTTP GET请求进行诊断,响应码为2xx3xx时即为成功,否则为失败。


任何一种探测方式都可能存在三种结果:“Success”(成功)“Failure”(失败)“Unknown”(未知),只有success表示成功通过检测。


容器探测分为两种类型:


  • 存活性探测(livenessProbe):用于判定容器是否处于“运行”(Running)状态。一旦此类检测未通过,kubelet将杀死容器并根据重启策略(restartPolicy)决定是否将其重启,未定义存活检测的容器的默认状态为“Success”。

  • 就绪性探测(readinessProbe):用于判断容器是否准备就绪并可对外提供服务。未通过检测的容器意味着其尚未准备就绪,端点控制器(如Service对象)会将其IP从所有匹配到此Pod对象的Service对象的端点列表中移除。检测通过之后,会再将其IP添加至端点列表中。


什么时候使用存活(liveness)和就绪(readiness)探针?


如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针,kubelet将根据PodrestartPolicy自动执行正确的操作。


如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定restartPolicyAlwaysOnFailure


如果要仅在探测成功时才开始向Pod发送流量,请指定就绪探针。在这种情况下,就绪探针可能与存活探针相同,但是spec中的就绪探针的存在意味着Pod将在没有接收到任何流量的情况下启动,并且只有在探针探测成功才开始接收流量。


如果希望容器能够自行维护,可以指定一个就绪探针,该探针检查与存活探针不同的端点。


注意:如果只想在Pod被删除时能够排除请求,则不一定需要使用就绪探针;在删除Pod时,Pod会自动将自身置于未完成状态,无论就绪探针是否存在。当等待Pod中的容器停止时,Pod仍处于未完成状态。


4、Pod 调度


在 Kubernetes 中,实际我们很少会直接创建一个 Pod,在大多数情况下会通过 RC(Replication Controller)、Deployment、DaemonSet、Job 等控制器来完成对一组 Pod 副本的创建、调度及全生命周期的自动控制任务。


在最早的 Kubernetes 版本里是没有这么多 Pod 副本控制器的,只有一个 Pod 副本控制器 RC,这个控制器是这样设计实现的:RC 独立于所控制的 Pod,并通过 Label 标签这个松耦合关联关系控制目标 Pod 实例的创建和销毁,随着 Kubernetes 的发展,RC 也出现了新的继任者 Deployment,用于更加自动地完成 Pod 副本的部署、版本更新、回滚等功能。


严谨地说,RC 的继任者其实并不是 Deployment,而是 ReplicaSet,因为 ReplicaSet 进一步增强了 RC 标签选择器的灵活性。之前 RC 的标签选择器只能选择一个标签,而 ReplicaSet 拥有集合式的标签选择器,可以选择多个 Pod 标签,如下所示:


selector:  matchLabels:    tier: frontend  matchExpressions:    - {key: tier, operator: In, values: [frontend]}
复制代码


与 RC 不同,ReplicaSet 被设计成能控制多个不同标签的 Pod 副本。一种常见的应用场景是,应用 APP 目前发布了 v1 与 v2 两个版本,用户希望 APP 的 Pod 副本数保持为 3 个,可以同时包含 v1 和 v2 版本的 Pod,就可以用 ReplicaSet 来实现这种控制,写法如下:


selector:  matchLabels:    version: v2  matchExpressions:    - {key: version, operator: In, values: [v1,v2]}
复制代码


其实,Kubernetes 的滚动升级就是巧妙运用 ReplicaSet 的这个特性来实现的,同时 Deployment 也是通过 ReplicaSet 来实现 Pod 副本自动控制功能的。


4.1 全自动调度


Deployment 或 RC 的主要功能之一就是全自动部署一个容器应用的多份副本,以及持续监控副本的数量,在集群内始终维护用户指定的副本数量。


示例:


(1)下面以一个 Deployment 配置的示例,使用这个资源清单配置文件 nginx-deployment.yml 可以创建一个 ReplicaSet,这个 ReplicaSet 会创建 3 个 Nginx 的 Pod:


apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploymentspec:  replicas: 3  selector:    matchLabels:      app: nginx  template:    metadata:      labels:        app: nginx    spec:      containers:        - name: nginx          image: nginx:latest          ports:           - name: http             containerPort: 80
复制代码


(2)执行kubectl create命令创建这个 Deployment:


[xcbeyond@localhost ~]$ kubectl create -f nginx-deployment.yml -n testdeployment.apps/nginx-deployment created
复制代码


(3)执行kubectl get deployments命令查看 Deployment 的状态:


[xcbeyond@localhost ~]$ kubectl get deployments -n testNAME               READY   UP-TO-DATE   AVAILABLE   AGEnginx-deployment   3/3     3            3           17m
复制代码


该结构表明 Deployment 已经创建好 3 个副本,并且所有副本都是最新可用的。


(4)通过执行kubectl get rskubectl get pods命令可以查看已经创建的 ReplicaSet(RS)和 Pod 信息:


[xcbeyond@localhost ~]$ kubectl get rs -n testNAME                          DESIRED   CURRENT   READY   AGEnginx-deployment-86b8cc866b   3         3         3       24m[xcbeyond@localhost ~]$ kubectl get pods -n testNAME                                READY   STATUS    RESTARTS   AGEnginx-deployment-86b8cc866b-7rqzt   1/1     Running   0          27mnginx-deployment-86b8cc866b-k2rwp   1/1     Running   0          27mnginx-deployment-86b8cc866b-rn7l7   1/1     Running   0          27m
复制代码


从调度策略上来说,这 3 个 Nginx Pod 有 Kubernetes 全自动调度的,它们各自被调度运行在哪个节点上,完全是由 Master 上的 Schedule 经过一系列计算得出,用户是无法干预调度过程和调度结果的。


除了使用自动调度,Kubernetes 还提供了多种调度策略供用户实际选择,用户只需在 Pod 的定义中使用NodeSelectorNodeAffinityPodAffinity、Pod 驱逐等更加细粒度的调度策略设置,就能完成对 Pod 的精准调度。


4.2 NodeSelector:定向调度


Kubernetes Master 上的 Scheduler(kube-scheduler)负责实现 Pod 的调度,整个调度过程通过执行一系列复杂的算法,最终为每个 Pod 都计算出一个最佳的目标节点,这一过程是自动完成的,通过我们无法知道 Pod 最终会被调度到哪个节点上。


在实际场景下,可能需要将 Pod 调度到指定的一些 Node 上,可以通过 Node 的标签(Label)和 Pod 的nodeSelector属性相匹配,来达到上述目的。比如希望将 MySQL 数据库调度到一个 SSD 磁盘的目标节点上,此时 Pod 模板中的NodeSelector属性就发挥作用了。


示例:


(1)执行kubectl label nodes <node-name> <label-key>=<label-value>命令给指定 Node 添加标签。


如,把 SSD 磁盘的 Node 添加标签disk-type=ssd


$ kubectl label nodes k8s-node-1 disk-type=ssd
复制代码


(2)在 Pod 的资源清单定义中添加上nodeSelector属性。


apiVersion: v1kind: Podmetadata:  name: mysql  labels:    env: testspec:  containers:  - name: mysql    image: mysql  nodeSelector:    disk-type: ssd
复制代码


(3)通过执行kubectl get pods -o wide命令查看是否生效。


除了用户可以给 Node 添加标签,Kubernetes 也给 Node 预定义了一些标签,方便用户直接使用,预定义的标签有:


  • kubernetes.io/arch:示例,kubernetes.io/arch=amd64。诸如混用 arm 和 x86 节点的情况下很有用。

  • kubernetes.io/os:示例,kubernetes.io/os=linux。在集群中存在不同操作系统的节点时很有用(例如:混合 Linux 和 Windows 操作系统的节点)。

  • beta.kubernetes.io/os (已弃用)

  • beta.kubernetes.io/arch (已弃用)

  • kubernetes.io/hostname:示例,kubernetes.io/hostname=k8s-node-1

  • ……


更多可参考:https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/


NodeSelector通过标签的方式,简单实现了限制 Pod 所在节点的方法,看似既简单又完美,但在真实环境中可能面临以下令人尴尬的问题:


(1)如果NodeSelector选择的 Label 不存在或者不符合条件,比如这些目标节点宕机或者资源不足时,该怎么办?


(2)如果要选择多种合适的目标节点,比如 SSD 磁盘的节点或者超高速硬盘的节点,该怎么办?Kubernetes 引入了NodeAffinity(节点亲和性)来解决上述问题。


(3)不同 Pod 之间的亲和性(Affinity)。比如 MySQL 和 Redis 不能被调度到同一个目标节点上,或者两种不同的 Pod 必须调度到同一个 Node 上,以实现本地文件共享或本地网络通信等特殊要求,这就是PodAffinity要解决的问题。


4.3 NodeAffinity:Node 亲和性调度


NodeAffinity,是 Node 亲和性的调度策略,是用于解决NodeSelector不足的一种全新调度策略。


目前有两种方式来表达:


  • RequiredDuringSchuedlingIgnoredDuringExecution必须满足指定的规则才可以调度 Pod 到 Node 上(功能与 nodeSelector 很像,但使用的是不同语法),相当于硬限制。

  • PreferredDuringSchedulingIgnoredDuringExection:强调优先满足指定规则,调度器会尝试调度 Pod 到 Node 上,但并不强求,相当于软限制。多个优先级规则还可以设置权重值,以定义执行的先后顺序。


lgnoredDuringExecution隐含的意思是,在Pod资源基于 Node 亲和性规则调度至某节点之后,节点标签发生了改变而不再符合此节点亲和性规则时 ,调度器不会将Pod对象从此节点上移出。因此,NodeAffinity仅对新建的Pod对象生效


Node 亲和性可通过pod.spec.affinity.nodeAffinity进行指定。


示例:


设置NodeAffinity调度规则如下:


  • requiredDuringSchedulingIgnoredDuringExecution要求只运行在 amd64 的节点上(kubernetes.io/arch In amd64)。

  • preferredDuringSchedulingIgnoredDuringExecution要求尽量运行在磁盘类型为 ssd 的节点上(disk-type In ssd)。


pod-with-node-affinity.yml定义如下:


apiVersion: v1kind: Podmetadata:  name: with-node-affinityspec:  affinity:    nodeAffinity:      requiredDuringSchedulingIgnoredDuringExecution:        nodeSelectorTerms:        - matchExpressions:          - key: kubernetes.io/arch            operator: In            values:            - amd64      preferredDuringSchedulingIgnoredDuringExecution:      - weight: 1        preference:          matchExpressions:          - key: disk-type            operator: In            values:            - ssd  containers:  - name: with-node-affinity    image: busybox
复制代码


从上面的示例中看到使用了In操作符。NodeAffinity语法支持的操作符包括: InNotInExistsDoesNotExistGtLt。 你可以使用 NotInDoesNotExist 来实现节点反亲和行为,即实现排查的功能。


NodeAffinity规则定义的注意事项如下:


  • 如果同时指定了 nodeSelectornodeAffinity,那么两者必须都要满足,才能将 Pod 调度到指定的节点上。

  • 如果 nodeAffinity 指定了多个 nodeSelectorTerms,那么其中一个能够匹配成功即可。

  • 如果在nodeSelectorTerms 中有多个matchExpressions,则一个节点必须满足所有matchExpressions才能运行该 Pod。


如果你指定了多个与 nodeSelectorTerms 关联的 matchExpressions


4.4 PodAffinity:Pod 亲和和互斥调度策略


Pod 间的亲和与互斥从 Kubernetes 1.4 版本开始引入的。这一功能让用户从另一个角度来限制 Pod 所能运行的节点:根据在节点上正在运行的 Pod 标签而不是节点的标签进行判断和调度,要求对节点和 Pod 两个条件进行匹配。这种规则可以描述为:如果在具有标签 X 的 Node 上运行了一个或者多个符合条件 Y 的 Pod,那么 Pod 应该运行在这个 Node 上。


NodeAffinity不同的是,Pod 是属于某个命名空间的,所以条件 Y 表达的是一个或者全部命名空间中的一个 Label Selector。


NodeAffinity相同,Pod 亲和和互斥的设置也是requiredDuringSchedulingIgnoredDuringExecutionPreferredDuringSchedulingIgnoredDuringExection


Pod 亲和性可通过pod.spec.affinity.podAffinity进行指定。


4.5 Taints 和 Tolerations(污点和容忍)


NodeAffinity 节点亲和性,是 Pod 中定义的一种属性,使得 Pod 能够被调度到某些 Node 上运行。而 Taints 则正好相反,它让 Node 拒绝 Pod 的运行。


Taints 需要和 Toleration 配合使用,让 Pod 避开一些不合适的 Node。在 Node 上设置一个或多个 Taint 之后,除非 Pod 明确声明能够容忍这些污点,否则无法在这些 Node 上运行。Toleration 是 Pod 的属性,让 Pod 能够运行在标注了 Taint 的 Node 上。


可使用kubectl taint命令为 Node 设置 Taint 信息:


[xcbeyond@localhost ~]$ kubectl taint nodes node1 key=value:NoSchedule
复制代码


4.6 Pod 优先级调度


在 Kubernetes 1.8 版本引入了基于 Pod 优先级抢占(Pod priority Preemption)的调度策略,此时 Kubernetes 会尝试释放目标节点上优先级低的 Pod,以腾出资源空间来安置优先级高的 Pod,这种调度方式被称为”抢占式调度“。


可以通过以下几个维度来定义:


  • Priority:优先级

  • QoS:服务质量等级

  • 系统定义的其他度量指标


示例:


(1)创建 PriorityClasses,不属于任何命名空间:


apiVersion: scheduling.k8s.io/v1beta1kind: PriorityClassmetadata:  name: high-priority  value: 1000000  globalDefault: false  description: "this priority class should be used for XYZ service pods only."
复制代码


说明:定义名为 high-priority 的优先级,优先级为 1000000,数字越大,优先级越高。优先级数字大于一亿的数字被系统保留,用于指派给系统组件。


(2)在 Pod 中引用上述定义的 Pod 优先级:

apiVersion: v1kind: Podmetadata:  name: frontend  namespace: test  labels:    app: frontendspec:  containers:  - name: frontend    image: xcbeyond/vue-frontend:latest    ports:      - name: port        containerPort: 80        hostPort: 8080    priorityClassName: high-priority  # 引用Pod优先级
复制代码


使用优先级抢占的调度策略可能会导致某些 Pod 永远无法被成功调度。优先级调度不但增加了系统的复杂性,还可能带来额外不稳定的因素。因此,一旦发生资源紧张的局面,首先要考虑的是集群扩容,如果无法扩容,则再考虑有监管的优先级调度特性,比如结合基于 Namespace 的资源配额限制来约束任意优先级抢占行为。


4.7 DaemonSet:在每个 Node 上都调度一个 Pod


DaemonSet 是 Kubernetes 1.2 版本新增的一种资源对象,用于管理在集群中每个 Node 上仅运行一份 Pod 的副本实例。如下图所示:



DaemonSet 的一些典型用法:


  • 在每个节点上运行集群守护进程。如:运行一个 GlusterFS 存储或 Ceph 存储的 Daemon 进程。

  • 在每个节点上运行日志收集守护进程。如:运行一个日志采集程序,Fluentd 或 Logstach。

  • 在每个节点上运行监控守护进程。如:采集该 Node 的运行性能数据,Prometheus Node Exporter、collectd 等。


4.8 Job:批处理调度


Job负责批量处理短暂的一次性任务 ,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束。


按照批处理任务实现方式的不同,有以下几种典型模式适用于不同的业务场景:


  • 基于 Job 模版进行扩展:


需要先编写一个通用的 Job 模版,根据不同的参数生成多个 Job json/yml 文件用于 Job 的创建,可以使用相同的标签进行 Job 管理。


  • 按每个工作项目排列的队列:


- 需要用户提前准备好一个消息队列服务,比如 rabbitMQ,该服务是一个公共组件,每个工作项目可以往里塞任务消息。

- 用户可以创建并行 Job,需要能适用于该消息队列,然后从该消息队列中消费任务,并进行处理直到消息被处理完。

- 该模式下,用户需要根据项目数量填写spec.completions, 并行数量.spec.parallelism可以根据实际情况填写。该模式下就是以所有的任务都成功完成了,job 才会成功结束。


  • 可变任务数量的队列:


  • 需要用户提前准备好一个存储服务来保存工作队列,比如 Redis。每个项目可以往该存储服务填充消息。

  • 用户可以启动适用于该工作队列的多个并行 Job,进行消息处理。与前一个 Rabbit 消息队列的差异在于,每个 Job 任务是可以知道工作队列已经空了,这时候便可以成功退出。

  • 该模式下,spec.completions需要置 1, 并行数量.spec.parallelism可以根据实际情况填写。只要其中有一个任务成功完成,该 Job 就会成功结束。


  • 普通的静态任务:


采用静态方式分配任务项。


考虑到批处理的并行问题,Kubernetes 将 Job 分为以下三种类型:


  • 非并行 Job:


通常一个 Job 只运行一个 Pod,一旦此 Pod 正常结束,Job 将结束。(Pod 异常,则会重启)


  • 固定完成次数的并行 Job:


并发运行指定数量的 Pod,直到指定数量的 Pod 成功,Job 结束。


  • 带有工作队列的并行 Job:


  • 用户可以指定并行的 Pod 数量,当任何 Pod 成功结束后,不会再创建新的 Pod。

  • 一旦有一个 Pod 成功结束,并且所有的 Pods 都结束了,该 Job 就成功结束。

  • 一旦有一个 Pod 成功结束,其他 Pods 都会准备退出。


4.9 Cronjob:定时任务


类似 Linux 中的 Cron 的定时任务 CronJob。


定时表达式,格式如下:


Minutes Hours DayofMonth Month DayofWeek Year


  • Minutes:分钟,有效范围为 0-59 的整数。可以为, - * /这 4 个字符。


  • Hours:小时,有效范围为 0-23 的整数。可以为, - * /这 4 个字符。

  • DayofMonth:每月的哪一天,有效范围为 0-31 的整数。可以为, - * / ? L W C这 8 个字符。

  • Month:月份,有效范围为 1-12 的整数或 JAN-DEC。可以为, - * /这 4 个字符。

  • DayofWeek:星期,有效范围为 1-7 的整数或 SUN-SAT,1 表示星期天,2 表示星期一,以此类推。可以为, - * / ? L C #这 8 个字符。


例如,每隔 1 分钟执行一次任务,则 Cron 表达式为*/1 * * * *


示例:


(1)定义 CronJob 的资源配置文件test-cronjob.yml


apiVersion: batch/v1beta1kind: CronJobmetadata:  name: testspec:  schedule: "*/1 * * * *"  jobTemplate:    spec:      template:        spec:          containers:            - name: hello              image: busybox              args:                - /bin/sh                - -c                - date; echo hello from the Kubernetes cluster          restartPolicy: OnFailure
复制代码


(2)执行kubectl crate命令创建 CronJob:


[xcbeyond@localhost k8s]$ kubectl create -f test-cronjob.yml -n testcronjob.batch/test created
复制代码


(3)每隔 1 分钟执行kubectl get cronjob命令查看任务状态,发现的确是每分钟调度一次:


[xcbeyond@localhost k8s]$ kubectl get cronjob test -n testNAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGEtest   */1 * * * *   False     1        61s             69s[xcbeyond@localhost k8s]$ kubectl get cronjob test -n testNAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGEtest   */1 * * * *   False     2        7s              75s[xcbeyond@localhost k8s]$ kubectl get cronjob test -n testNAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGEtest   */1 * * * *   False     3        28s             2m36s
复制代码


执行kubectl delete cronjob命令进行删除。


4.10 自定义调度器


如果在上述的调度器还是无法满足一些独特的需求,还可以使用任何语言实现简单或复杂的自定义调度器。


5、Pod 升级和回滚


当需要升级某个服务时,一般会停止与该服务相关的所有 Pod,然后下载新版本镜像并创建新的 Pod。如果集群规模较大,则这样的工作就变成了一个挑战,并且长时间的服务不可用,也是很难让用户接受的。


为了解决上述问题,Kubernetes 提供了滚动升级能够很好的解决。


如果 Pod 是通过 Deployment 创建,则可以在运行时修改 Deployment 的 Pod 定义(spc.template)或镜像名称,并应用到 Deployment 对象上,系统即可完成 Deployment 的自动更新操作。如果在更新过程中发生了错误,则可以通过回滚操作恢复 Pod 的版本。


5.1 Deployment 的升级


nginx-deployment.yml为例:


apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploymentspec:  replicas: 3  selector:    matchLabels:      app: nginx  template:    metadata:      labels:        app: nginx    spec:      containers:        - name: nginx          image: nginx:latest          ports:           - name: http             containerPort: 80
复制代码


(1)已运行的 Pod 副本数量为 3,查看 Pod 状态:


[xcbeyond@localhost k8s]$ kubectl get pod -n testNAME                                READY   STATUS    RESTARTS   AGEnginx-deployment-86b8cc866b-7rqzt   1/1     Running   2          53dnginx-deployment-86b8cc866b-k2rwp   1/1     Running   2          53dnginx-deployment-86b8cc866b-rn7l7   1/1     Running   2          53d
复制代码


(2)现将 nginx 版本更新为 nginx:1.9.1,可通过执行kubectl set image命令为 Deployment 设置新的镜像:


[xcbeyond@localhost k8s]$ kubectl set image deployment/nginx-deployment  nginx=nginx:1.9.1 -n testdeployment.apps/nginx-deployment image updated
复制代码


kubectl set image:更新现有的资源对象的容器镜像。


另一种升级方法是使用kubectl edit命令修改 Deployment 的配置。


(3)此时(升级过程中),查看 Pod 状态,发现正在进行升级:


[xcbeyond@localhost k8s]$ kubectl get pod -n testNAME                                READY   STATUS              RESTARTS   AGEnginx-deployment-79fbf694f6-kplgz   0/1     ContainerCreating   0          96snginx-deployment-86b8cc866b-7rqzt   1/1     Running             2          53dnginx-deployment-86b8cc866b-k2rwp   1/1     Running             2          53dnginx-deployment-86b8cc866b-rn7l7   1/1     Running             2          53d
复制代码


升级过程中,可以通过执行kubectl rollout status命令查看 Deployment 的更新过程。


(4)升级完成后,查看 Pod 状态:


[xcbeyond@localhost k8s]$ kubectl get pod -n testNAME                                READY   STATUS    RESTARTS   AGEnginx-deployment-79fbf694f6-h7nfs   1/1     Running   0          2m43snginx-deployment-79fbf694f6-kplgz   1/1     Running   0          7m21snginx-deployment-79fbf694f6-txrfj   1/1     Running   0          2m57s
复制代码


对比升级完成前后 Pod 状态列表中的 NAME,已经发生了变化。


查看 Pod 使用的镜像,已经更新为 nginx:1.9.1:


[xcbeyond@localhost k8s]$ kubectl describe pod/nginx-deployment-79fbf694f6-h7nfs -n testName:         nginx-deployment-79fbf694f6-h7nfsNamespace:    test……Containers:  nginx:    Container ID:   docker://0ffd43455aa3a147ca0795cf58c68da63726a3c77b40d58bfa5084fb879451d5    Image:          nginx:1.9.1    Image ID:       docker-pullable://nginx@sha256:2f68b99bc0d6d25d0c56876b924ec20418544ff28e1fb89a4c27679a40da811b    Port:           80/TCP……
复制代码




那么 Deployment 是如何完成 Pod 更新的呢?


使用kubectl describe deployment/nginx-deployment命令仔细查看 Deployment 的更新过程:


初始创建 Deployment 时,系统创建了一个 ReplicaSet(nginx-deployment-86b8cc866b),并创建了 3 个 Pod 副本。当更新 Deployment 时,系统创建了一个新的 ReplicaSet(nginx-deployment-79fbf694f6),并将其副本数量扩展到 1,然后将旧的 ReplicaSet 缩减为 2。之后,系统继续按照相同的更新策略对新旧两个 ReplicaSet 进行逐个调整。最后,新的 ReplicaSet 运行了 3 个新版本的 Pod 副本,旧的 ReplicaSet 副本数量则缩减为 0。


如下图所示:



执行kubectl describe deployment/nginx-deployment命令,查看 Deployment 的最终信息:


[xcbeyond@localhost k8s]$ kubectl describe deployment/nginx-deployment -n testName:                   nginx-deploymentNamespace:              testCreationTimestamp:      Thu, 26 Nov 2020 19:32:04 +0800Labels:                 <none>Annotations:            deployment.kubernetes.io/revision: 2Selector:               app=nginxReplicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailableStrategyType:           RollingUpdateMinReadySeconds:        0RollingUpdateStrategy:  25% max unavailable, 25% max surgePod Template:  Labels:  app=nginx  Containers:   nginx:    Image:        nginx:1.9.1    Port:         80/TCP    Host Port:    0/TCP    Environment:  <none>    Mounts:       <none>  Volumes:        <none>Conditions:  Type           Status  Reason  ----           ------  ------  Available      True    MinimumReplicasAvailable  Progressing    True    NewReplicaSetAvailableOldReplicaSets:  <none>NewReplicaSet:   nginx-deployment-79fbf694f6 (3/3 replicas created)Events:  Type    Reason             Age   From                   Message  ----    ------             ----  ----                   -------  Normal  ScalingReplicaSet  30m   deployment-controller  Scaled up replica set nginx-deployment-79fbf694f6 to 1  Normal  ScalingReplicaSet  25m   deployment-controller  Scaled down replica set nginx-deployment-86b8cc866b to 2  Normal  ScalingReplicaSet  25m   deployment-controller  Scaled up replica set nginx-deployment-79fbf694f6 to 2  Normal  ScalingReplicaSet  25m   deployment-controller  Scaled down replica set nginx-deployment-86b8cc866b to 1  Normal  ScalingReplicaSet  25m   deployment-controller  Scaled up replica set nginx-deployment-79fbf694f6 to 3  Normal  ScalingReplicaSet  24m   deployment-controller  Scaled down replica set nginx-deployment-86b8cc866b to 0
复制代码


执行kubectl get rs命令,查看两个 ReplicaSet 的最终状态:


[xcbeyond@localhost k8s]$ kubectl get rs -n testNAME                          DESIRED   CURRENT   READY   AGEnginx-deployment-79fbf694f6   3         3         3       39mnginx-deployment-86b8cc866b   0         0         0       53d
复制代码


在整个升级过程中,系统会保证至少有两个 Pod 可用,并且最多同时运行 4 个 Pod,这是 Deployment 通过复杂的算法完成。Deployment 需要确保在整个更新过程中只有一定数量的 Pod 可能处于不可用状态。在默认情况下,Deployment 确保可用的 Pod 总数至少所需的副本数量(desired)减 1,也就是最多 1 个不可用(maxUnavailable=1)。


这样,在升级过程中,Deployment 就能够保证服务不中断,并且副本数量始终维持为指定的数量(desired)。


更新策略


在 Deployment 的定义中,可以通过spec.strategy指定 Pod 更新策略。目前支持两种策略:Recreate(重建)和 RollingUpdate(滚动更新),默认值为 RollingUpdate。


  • Recreate: 设置spec.strategy.type=Recrate,表示 Deployment 在更新 Pod 时,会先杀掉所有正在运行的 Pod,然后创建新的 Pod。

  • RollingUpdate: 设置spec.strategy.type=RollingUpdate,表示 Deployment 会以滚动更新的方式来逐个更新 Pod。同时,可通过设置spec.strategy.rollingUpdate中的两个参数maxUnavailablemaxSurge来控制滚动更新的过程。

  • spec.strategy.rollingUpdate.maxUnavailable:用于指定 Deployment 在更新过程中最大不可用状态的 Pod 数量。 该值可以是具体数字,或者 Pod 期望副本数的百分比。

  • spec.strategy.rollingUpdate.maxSurge:用于指定在 Deployment 更新 Pod 的过程中 Pod 总数超过 Pod 期望副本数部分的最大值。该值可以是具体数字,或者 Pod 期望副本数的百分比。


5.2 Deployment 的回滚


在默认情况下,所有 Deployment 的发布历史记录都被保留在系统中,以便于我们随时进行回滚。(历史记录数量可配置)


可通过执行kubectl rollout undo命令完成 Deployment 的回滚。


(1)执行kubectl rollout history命令检查某个 Deployment 部署的历史记录:


[xcbeyond@localhost k8s]$ kubectl rollout history deployment/nginx-deployment -n testdeployment.apps/nginx-deployment REVISION  CHANGE-CAUSE1         <none>2         <none
复制代码


在创建 Deployment 时使用--record参数,就可以在 CHANGE-CAUSE 列看到每个版本创建/更新的命令。


(2)如果需要查看指定版本的详细信息,则可加上--revision=<N>参数:


[xcbeyond@localhost k8s]$ kubectl rollout history deployment/nginx-deployment --revision=2 -n testdeployment.apps/nginx-deployment with revision #2Pod Template:  Labels:	app=nginx	pod-template-hash=79fbf694f6  Containers:   nginx:    Image:	nginx:1.9.1    Port:	80/TCP    Host Port:	0/TCP    Environment:	<none>    Mounts:	<none>  Volumes:	<none>
复制代码


(3)先将撤销本次升级版本,并回滚到上一版本,即:nginx:1.9.1-> nginx:latest。执行kubectl rollout undo命令:


[xcbeyond@localhost k8s]$ kubectl rollout undo deployment/nginx-deployment -n testdeployment.apps/nginx-deployment rolled back
复制代码


当然,也可以使用--to-revision 参数指定回滚到具体某个版本号。


(4)可执行kubectl describe deployment/nginx-deployment命令查看回滚的整个过程:


[xcbeyond@localhost k8s]$ kubectl describe deployment/nginx-deployment -n testName:                   nginx-deploymentNamespace:              testCreationTimestamp:      Thu, 26 Nov 2020 19:32:04 +0800Labels:                 <none>Annotations:            deployment.kubernetes.io/revision: 3Selector:               app=nginxReplicas:               3 desired | 2 updated | 4 total | 3 available | 1 unavailableStrategyType:           RollingUpdateMinReadySeconds:        0RollingUpdateStrategy:  25% max unavailable, 25% max surgePod Template:  Labels:  app=nginx  Containers:   nginx:    Image:        nginx:latest    Port:         80/TCP    Host Port:    0/TCP    Environment:  <none>    Mounts:       <none>  Volumes:        <none>Conditions:  Type           Status  Reason  ----           ------  ------  Available      True    MinimumReplicasAvailable  Progressing    True    ReplicaSetUpdatedOldReplicaSets:  nginx-deployment-79fbf694f6 (2/2 replicas created)NewReplicaSet:   nginx-deployment-86b8cc866b (2/2 replicas created)Events:  Type    Reason             Age    From                   Message  ----    ------             ----   ----                   -------  Normal  ScalingReplicaSet  5m50s  deployment-controller  Scaled up replica set nginx-deployment-86b8cc866b to 1  Normal  ScalingReplicaSet  4m55s  deployment-controller  Scaled down replica set nginx-deployment-79fbf694f6 to 2  Normal  ScalingReplicaSet  4m55s  deployment-controller  Scaled up replica set nginx-deployment-86b8cc866b to 2
复制代码


5.3 RC 的滚动升级


对于 RC 的滚动升级,Kubernetes 还提供了一个kubectl rolling-update命令实现。该命令创建一个新的 RC,然后自动控制旧的 RC 中的 Pod 副本数量逐渐减少到 0,同时新的 RC 中的 Pod 副本数量从 0 逐步增加到目标值,来完成 Pod 的升级。


5.4 其他对象的更新策略


Kubernetes 从 1.6 版本开始,对 DaemonSet 和 StatefulSet 的更新策略也引入类似于 Deployment 的滚动升级,通过不同的策略自动完成应用的版本升级。


5.4.1 DaemonSet 的更新策略


DaemonSet 的升级策略包括两种:


  • OnDelete:DaemonSet 的默认策略。当使用OnDelete策略对 DaemonSet 进行更新时,在创建好新的 DaemonSet 配置之后,新的 Pod 并不会被自动创建,直到用户手动删除旧版本的 Pod,才会触发新建操作。

  • RollingUpdate:当使用RollingUpdate策略对 DaemonSet 进行更新时,旧版本的 Pod 将被自动删除,然后自动创建新版本的 Pod。


5.4.2 StatefulSet 的更新策略


Kubernetes 从 1.6 版本开始,针对 StatefulSet 的更新策略逐渐向 Deployment 和 DaemonSet 的更新策略看齐,也将实现 RollingUpdate、Partioned 和 onDelete 这些策略,以保证 StatefulSet 中 Pod 有序地、逐个地更新,并保留更新历史记录,也能够回滚到某个历史版本。


6、Pod 扩容


在实际生产环境下,我们面对不同场景,可能会进行服务实例的调整(增加或减少),以确保能够充分利用好系统资源。此时,可以利用 Deployment/RC 的 Scale 机制来完成这些工作。


Kubernetes 对 Pod 扩容提供了手动和自动两种模式:


  • 手动模式: 通过执行kubectl scale命令或通过 RESTful API 对一个 Deployment/RC 进行 Pod 副本数量的设置。

  • 自动模式: 用户根据某个性能指标或自定义业务指标,并指定 Pod 副本数量的范围,系统将自动在这个范围内根据性能指标的变化进行调整。


6.1 手动模式扩容


nginx-deployment.yml为例:


apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploymentspec:  replicas: 3  selector:    matchLabels:      app: nginx  template:    metadata:      labels:        app: nginx    spec:      containers:        - name: nginx          image: nginx:latest          ports:           - name: http             containerPort: 80
复制代码


(1)已运行的 Pod 副本数量为 3,查看 Pod 状态:


[xcbeyond@localhost ~]$ kubectl get pod -n testNAME                                READY   STATUS    RESTARTS   AGEnginx-deployment-86b8cc866b-7g4xp   1/1     Running   0          24hnginx-deployment-86b8cc866b-pgh6c   1/1     Running   0          24hnginx-deployment-86b8cc866b-qg26j   1/1     Running   0          23h
复制代码


(2)通过执行kubectl scale命令将 Pod 副本数量从初始的 3 个更新为 5 个:


[xcbeyond@localhost ~]$ kubectl scale deployment nginx-deployment --replicas 5 -n testdeployment.apps/nginx-deployment scaled[xcbeyond@localhost ~]$ kubectl get pod -n testNAME                                READY   STATUS              RESTARTS   AGEnginx-deployment-86b8cc866b-7g4xp   1/1     Running             0          24hnginx-deployment-86b8cc866b-dbkms   0/1     ContainerCreating   0          5snginx-deployment-86b8cc866b-pgh6c   1/1     Running             0          24hnginx-deployment-86b8cc866b-qg26j   1/1     Running             0          23hnginx-deployment-86b8cc866b-xv5pm   0/1     ContainerCreating   0          5s[xcbeyond@localhost ~]$ kubectl get pod -n testNAME                                READY   STATUS    RESTARTS   AGEnginx-deployment-86b8cc866b-7g4xp   1/1     Running   0          24hnginx-deployment-86b8cc866b-dbkms   1/1     Running   0          79snginx-deployment-86b8cc866b-pgh6c   1/1     Running   0          24hnginx-deployment-86b8cc866b-qg26j   1/1     Running   0          23hnginx-deployment-86b8cc866b-xv5pm   1/1     Running   0          79s
复制代码


如果--replicas 设置为比当前 Pod 副本小,则会删除一些运行中的 Pod,以实现缩容。


6.2 自动模式扩容


Kubernetes 从 1.1 版本开始,新增了 Pod 横向自动扩容的功能(Horizontal Pod Autoscaler,简称 HPA),用于实现基于 CPU 使用率进行自动 Pod 扩容的功能。


HPA 与 Deployment、Service 一样,也属于一种 Kubernetes 资源对象。


HPA 的目标是通过追踪集群中所有 Pod 的负载变化情况(基于 Master 的 kube-controller-manager 服务持续监测目标 Pod 的某种性能指标),来自动化地调整 Pod 的副本数,以此来满足应用的需求和减少资源的浪费。


目前 Kubernetes 支持的指标类型如下:


  • Pod 资源使用率: Pod 级别的性能指标,通常是一个比率指,例如 CPU 利用率。

  • Pod 自定义指标: Pod 级别的性能指标,通常是一个数值,例如服务在每秒之内的请求数(TPS 或 QPS)。

  • Object 自定义指标或外部自定义指标: 通常是一个数值,需要容器应用以某种方式提供,例如通过 HTTP URL “/metrics”提供,或使用外部服务提供的指标采集 URL(如,某个业务指标)。


如何统计和查询这些指标呢?


Kubernetes 从 1.11 版本开始,弃用基于Heapster组件完成 Pod 的 CPU 使用率采集的机制,全面转向基于Metrics Server完成数据采集。Metrics Server将采集到的 Pod 性能指标数据通过聚合 API(如,metrics.k8s.io、custom.metrics.k8s.io 和 external.metrics.k8s.io)提供给 HPA 控制器进行查询。


6.2.1 HPA 的工作原理


Kubernetes 中的某个 Metrics Server(Heapster 或自定义 Metrics Server)持续采集所有 Pod 副本的指标数据。HPA 控制器通过 Metrics Server 的 API 获取这些数据,基于用户定义的扩容规则进行计算,得到目标 Pod 副本数量。当目标 Pod 副本数量与当前副本数量不同时,HPA 控制器就向 Pod 的副本控制器(Deployment、RC 或 ReplicaSet)发起 scale 操作(等同于手动模式中执行kubectl scale命令),调整 Pod 的副本数量,完成扩容操作。


如下图描述了 HPA 体系中的关键组件和工作流程:



HPA 控制器是基于 Master 的kube-controller-manager服务启动参数--horizontal-pod-autoscaler-sync-period定义的探测周期(默认值为 15s)。


6.2.2 HPA 配置说明


自动模式扩容是通过 HorizontalPodAutoscaler 资源对象提供给用户来定义扩容规则。


HorizontalPodAutoscaler 资源对象位于 Kubernetes 的 API 组“autoscaling”中,目前包括 v1 和 v2 两个版本。


  • autoscaling/v1:仅支持基于 CPU 使用率的自动扩容。

  • autoscaling/v2*:支持基于任意指标的自动扩容配置,包括基于资源使用率、Pod 指标、其他指标等类型的指标数据。当前版本为 autoscaling/v2beta2。


下面对 HorizontalPodAutoscaler 的配置和用法进行具体说明。


详细配置项可参考:

>

1. https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#horizontalpodautoscaler-v1-autoscaling

>

2. https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#horizontalpodautoscaler-v2beta2-autoscaling


(1)基于 autoscaling/v1 版本的 HorizontalPodAutoscaler 配置:


apiVersion: autoscaling/v1kind: HorizontalPodAutoscalermetadata:  name: nginxspec:  scaleTargetRef:    apiVersion: apps/v1    kind: Deployment    name: nginx  minReplicas: 1  maxReplicas: 10  targetCPUUtilizationPercentage: 50
复制代码


参数说明:


  • scaleTargetRef:目标作用对象,可以是 Deployment、RC、ReplicaSet。

  • targetCPUUtilizationPercentage:期望每个 Pod 的 CPU 使用率。

  • minReplicasmaxReplicas:P 大副本数量的最小值和最大值,系统将在这个范围内进行自动扩容操作,并维持每个 Pod 的 CPU 使用率为设置值。


使用 autoscaling/v1 版本的 HorizontalPodAutoscaler,需预先安装 Heapster 组件或 Metrics Server,用于采集 Pod 的 CPU 使用率。


(2)基于 autoscaling/v2beta2 版本的 HorizontalPodAutoscaler 配置:


apiVersion: autoscaling/v2beta2kind: HorizontalPodAutoscalermetadata:  name: nginxspec:  scaleTargetRef:    apiVersion: apps/v1    kind: Deployment    name: nginx  minReplicas: 1  maxReplicas: 10  metrics:  - type: Resource    resource:      name: cpu      target:        type: Utilization        averageUtilization: 50
复制代码


参数说明:


  • scaleTargetRef:目标作用对象,可以是 Deployment、RC、ReplicaSet。

  • minReplicasmaxReplicas:P 大副本数量的最小值和最大值,系统将在这个范围内进行自动扩容操作,并维持每个 Pod 的 CPU 使用率为设置值。

  • metrics:目标指标值。

* type:定义指标的类型。可设置四种类型,支持设置一个或多个类型的组合:

* Resource:基于资源的指标值,如 CPU 和内存。对应 CPU 使用率,在 target 参数中设置 averageUtilization 定义目标平均 CPU 使用率;对应内存资源,在 target 参数中设置 AverageValue 定义目标平均内存使用值。

* Pods:基于 Pod 的指标,系统将对全部 Pod 副本的指标值进行平均值计算。其 target 指标类型只能使用 AverageValue。指标的数据通常需要搭建自定义 Metrics Server 和监控工具进行采集和处理。

* Object:基于某种资源对象的指标或应用系统的任意自定义指标。指标的数据通常需要搭建自定义 Metrics Server 和监控工具进行采集和处理。

* External:Kubernetes 从 1.10 版本开始,引入了对外部系统指标的支持。例如,用户使用了公有云服务商提供的消息服务或外部负载均衡,可以基于这些外部服务的性能指标对自己部署在 Kubernetes 中的服务进行自动扩容操作。

* target:定义相应的指标目标值,系统将在指标数据达到目标值时触发扩容操作。


例 1,设置指标的名称为requests-per-second,其值来源于 Ingress “main-route”,将目标值(value)设置为 2000,即在 Ingress 的每秒请求数量达到 2000 个时触发扩容操作:


metrics:- type: Object  object:    metric:      name: requests-per-second    describedObject:      apiVersion: extensions/v1beta1      kind: Ingress      name: main-route    target:      type: Value      value: 2k
复制代码


例 2,设置指标的名称为 http_requests,并且该资源对象具有标签“verb=GET”,在指标平均值达到 500 时触发扩容操作:


metrics:- type: Object  object:    metric:      name: http_requests      selector: 'verb=GET'    target:      type: AverageValue      averageValue: 500
复制代码


还可以在同一个 HorizontalPodAutoscaler 资源对象中定义多个类型的指标,系统将针对每种类型的指标都计算 Pod 副本的目标数量,以最大值为准进行扩容操作。例如:


apiVersion: autoscaling/v2beta2kind: HorizontalPodAutoscalermetadata:  name: nginxspec:  scaleTargetRef:    apiVersion: apps/v1    kind: Deployment    name: nginx  minReplicas: 1  maxReplicas: 10  metrics:  - type: Resource    resource:      name: cpu      target:        type: AverageUtilization        averageUtilization: 50  - type: Pods    pods:      metric:        name: packets-per-second      target:        type: AverageValue        averageValue: 1k  - type: Object    object:      metric:        name: requests-per-second      describedObject:        apiVersion: extensions/v1beta1        kind: Ingress      target:        type: Value        value: 10k
复制代码


例 3,设置指标的名称为 queuemessagesready,具有 queue=worker_tasks 标签在目标指标平均值为 30 时触发自动扩容操作:


metrics:- type: External  external:    metric:      name: queue_messages_ready      selector: 'queue=worker_tasks'    target:      type: AverageValue      averageValue: 30
复制代码


在使用外部服务的指标时,需安装、部署能够对接到 Kubernetes HPA 模型的监控系统,并且完成了解监控系统采集这些指标的机制,后续的自动扩容操作才能完成。


6.2.3 基于自定义指标的 HPA 实践


下面通过一个完整的示例,对如何搭建和使用基于自定义指标的 HPA 体系进行说明。


基于自定义指标进行自动扩容时,需预先部署自定义 Metrics Server,目前可以使用基于 Prometheus、Microsoft Azure、Google Stackdriver 等系统的 Adapter 实现自定义 Metrics Server。自定义 Metrice Server 可参考https://github.com/kubernetes/metrics/blob/master/IMPLEMENTATIONS.md#custom-metrics-api的说明。


本节是基于 Prometheus 监控系统对 HPA 的基础组件部署和 HPA 配置进行详细说明。


基于 Prometheus 的 HPA 架构如下图所示:



关键组件说明如下:


  • Prometheus: 是一个开源的服务监控系统,用于定期采集各 Pod 的性能指标数据。

  • Custom Metrics Server: 自定义 Metrics Server,用 Prometheus Adapter 进行具体实现。它从 Prometheus 服务采集性能指标数据,通过 Kubernetes 的 Metrics Aggregation 层将自定义指标 API 注册到 Master 的 API Server 中以/apis/custom.metrics.k8s.io路径提供指标数据。

  • HPA Controller: Kubernetes 的 HPA 控制器,基于用户自定义的 HorizontalPodAutoscaler 进行自动扩容操作。


整个部署过程如下:


(1)开启kube-apiserverkube-controller-manager服务的相关启动参数。


kube-apiserverkube-controller-manager服务默认已经部署在kube-system命名空间。

>

```

[xcbeyond@localhost minikube]$ kubectl get pod -n kube-system

NAME READY STATUS RESTARTS AGE

coredns-6c76c8bb89-p26xx 1/1 Running 11 103d

etcd-minikube 1/1 Running 11 103d

kube-apiserver-minikube 1/1 Running 11 103d

kube-controller-manager-minikube 1/1 Running 11 103d

kube-proxy-gcd8d 1/1 Running 11 103d

kube-scheduler-minikube 1/1 Running 11 103d

storage-provisioner 1/1 Running 29 103d

```

注:本 Kubernetes 环境是基于 Minikube 方式部署的本地环境。

可通过kubectl describe命令查看服务目前的启动参数情况,例如:

```

[xcbeyond@localhost minikube]$ kubectl describe pod/kube-apiserver-minikube -n kube-system

Name: kube-apiserver-minikube

Namespace: kube-system

……

Containers:

kube-apiserver:

……

Command:

kube-apiserver

--advertise-address=172.17.0.2

--allow-privileged=true

--authorization-mode=Node,RBAC

--client-ca-file=/var/lib/minikube/certs/ca.crt

--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota

--enable-bootstrap-token-auth=true

--etcd-cafile=/var/lib/minikube/certs/etcd/ca.crt

--etcd-certfile=/var/lib/minikube/certs/apiserver-etcd-client.crt

--etcd-keyfile=/var/lib/minikube/certs/apiserver-etcd-client.key

--etcd-servers=https://127.0.0.1:2379

--insecure-port=0

--kubelet-client-certificate=/var/lib/minikube/certs/apiserver-kubelet-client.crt

--kubelet-client-key=/var/lib/minikube/certs/apiserver-kubelet-client.key

--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname

--proxy-client-cert-file=/var/lib/minikube/certs/front-proxy-client.crt

--proxy-client-key-file=/var/lib/minikube/certs/front-proxy-client.key

--requestheader-allowed-names=front-proxy-client

--requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt

--requestheader-extra-headers-prefix=X-Remote-Extra-

--requestheader-group-headers=X-Remote-Group

--requestheader-username-headers=X-Remote-User

--secure-port=8443

--service-account-key-file=/var/lib/minikube/certs/sa.pub

--service-cluster-ip-range=10.96.0.0/12

--tls-cert-file=/var/lib/minikube/certs/apiserver.crt

--tls-private-key-file=/var/lib/minikube/certs/apiserver.key

……

```

可通过kubectl edit命令对服务启动参数进行更新,如:

```

[xcbeyond@localhost minikube]$ kubectl edit pod kube-apiserver-minikube -n kube-system

```


在 Master 的 API Server 启动 Aggregation 层,通过设置 kube-apiserver 服务的下列启动参数进行开启。


  • --requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt:客户端 CA 证书。

  • --requestheader-allowed-names=front-proxy-client:允许访问的客户端 common names 列表,通过 header 中由--requestheader-username-headers参数指定的字段获取。客户端 common names 的名称需要在 client-ca-file 中进行配置,将其设置为空值时,表示任意客户端都可以访问。

  • --requestheader-extra-headers-prefix=X-Remote-Extra-:请求头中需要坚持的前缀名。

  • --requestheader-group-headers=X-Remote-Group:请求头中需要检查的组名。

  • --requestheader-username-headers=X-Remote-User:请求头中需要检查的用户名。

  • --proxy-client-cert-file=/var/lib/minikube/certs/front-proxy-client.crt:在请求期间验证 Aggregator 的客户端 CA 证书。

  • --proxy-client-key-file=/var/lib/minikube/certs/front-proxy-client.key:在请求期间验证 Aggregator 的客户端私钥。


配置 kube-controller-manager 服务中 HPA 的相关启动参数(可选配置)如下:


  • --horizontal-pod-autoscaler-sync-period=10s:HPA 控制器同步 Pod 副本数量的空间间隔,默认值为 15s。

  • --horizontal-pod-autoscaler-downscale-stabilization=1m:执行扩容操作的等待时长,默认值为 5min。

  • --horizontal-pod-autoscaler-initial-readiness-delay=30s:等待 Pod 达到 Read 状态的时延,默认值为 30min。

  • --horizontal-pod-autoscaler-tolerance=0.1:扩容计算结果的容忍度,默认值为 0.1,表示[-10% - +10%]。


(2)部署 Prometheus。


这里使用 Prometheus-operator 来部署。


Prometheus Operator 为监控 Kubernetes service、deployment 和 Prometheus 实例的管理提供了简单的定义,简化在 Kubernetes 上部署、管理和运行。


(3)部署自定义 Metrics Server。


以 Prometheus Adapter 的实现进行部署。


(4)部署应用程序。


该应用程序提供 RESTful 接口/metrics,并提供名为 httprequeststotal 的自定义指标值。


(5)创建一个 Prometheus 的 ServiceMonitor 对象,用于监控应用程序提供的指标。


(6)创建一个 HorizontalPodAutoscaler 对象,用于为 HPA 控制器提供用户期望的自动扩容配置。


(7)对应用程序发起 HTTP 访问请求,验证 HPA 自动扩容机制。


参考文章:


  1. https://www.cnblogs.com/cocowool/p/kubernetespoddetail.html

  2. https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#-strong-getting-started-strong-

  3. https://www.cnblogs.com/linuxk/p/9569618.html

  4. https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20


发布于: 2021 年 01 月 27 日阅读数: 43
用户头像

xcbeyond

关注

不为别的,只为技术沉淀、分享。 2019.06.20 加入

公众号:程序猿技术大咖,专注于技术输出、分享。

评论

发布
暂无评论
Kubernetes Pod篇:带你轻松玩转Pod