0

xcbeyond的个人空间

 2 years ago
source link: https://my.oschina.net/xcbeyond/blog/5073730
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文将对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)。

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

原因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: v1
kind: Pod
metadata:
  name: frontend
  namespace: test	# 如果没有命名空间test,需提前创建。也可以使用默认命名空间default,即:namespace属性标签可以不定义
  labels:
    app: frontend
spec:
  containers:
  - name: frontend
    image: xcbeyond/vue-frontend:latest		# 发布在DockerHub中的镜像
    ports:
      - name: port
        containerPort: 80
        hostPort: 8080

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

[xcbeyond@bogon ~]$ kubectl explain pod
KIND:     Pod
VERSION:  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.yml
pod/frontend created

2.2 查看状态

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

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

[xcbeyond@localhost ~]$ kubectl get pods -n test
NAME       READY   STATUS    RESTARTS   AGE
frontend   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 yaml
apiVersion: v1
kind: Pod
metadata:
  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-7c1dd542f647
spec:
  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-bbmj5
status:
  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-labels
NAME       READY   STATUS    RESTARTS   AGE   LABELS
frontend   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=frontend
pod/frontend labeled
[xcbeyond@localhost ~]$ kubectl get pods -n test --show-labels
NAME       READY   STATUS    RESTARTS   AGE    LABELS
frontend   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 --overwrite
pod/frontend labeled
[xcbeyond@localhost ~]$ kubectl get pods -n test --show-labels
NAME       READY   STATUS    RESTARTS   AGE    LABELS
frontend   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 --force
pod "frontend" deleted
pod/frontend replaced

2.6 删除

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

[xcbeyond@localhost ~]$ kubectl delete pod frontend -n test
pod "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/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  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 test
deployment.apps/nginx-deployment created

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

[xcbeyond@localhost ~]$ kubectl get deployments -n test
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           17m

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

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

[xcbeyond@localhost ~]$ kubectl get rs -n test
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-86b8cc866b   3         3         3       24m
[xcbeyond@localhost ~]$ kubectl get pods -n test
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-86b8cc866b-7rqzt   1/1     Running   0          27m
nginx-deployment-86b8cc866b-k2rwp   1/1     Running   0          27m
nginx-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: v1
kind: Pod
metadata:
  name: mysql
  labels:
    env: test
spec:
  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: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  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/v1beta1
kind: PriorityClass
metadata:
  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: v1
kind: Pod
metadata:
  name: frontend
  namespace: test
  labels:
    app: frontend
spec:
  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/v1beta1
kind: CronJob
metadata:
  name: test
spec:
  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 test
cronjob.batch/test created

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

[xcbeyond@localhost k8s]$ kubectl get cronjob test -n test
NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
test   */1 * * * *   False     1        61s             69s
[xcbeyond@localhost k8s]$ kubectl get cronjob test -n test
NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
test   */1 * * * *   False     2        7s              75s
[xcbeyond@localhost k8s]$ kubectl get cronjob test -n test
NAME   SCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGE
test   */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/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  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 test
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-86b8cc866b-7rqzt   1/1     Running   2          53d
nginx-deployment-86b8cc866b-k2rwp   1/1     Running   2          53d
nginx-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 test
deployment.apps/nginx-deployment image updated

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

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

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

[xcbeyond@localhost k8s]$ kubectl get pod -n test
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-79fbf694f6-kplgz   0/1     ContainerCreating   0          96s
nginx-deployment-86b8cc866b-7rqzt   1/1     Running             2          53d
nginx-deployment-86b8cc866b-k2rwp   1/1     Running             2          53d
nginx-deployment-86b8cc866b-rn7l7   1/1     Running             2          53d

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

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

[xcbeyond@localhost k8s]$ kubectl get pod -n test
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-79fbf694f6-h7nfs   1/1     Running   0          2m43s
nginx-deployment-79fbf694f6-kplgz   1/1     Running   0          7m21s
nginx-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 test
Name:         nginx-deployment-79fbf694f6-h7nfs
Namespace:    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 test
Name:                   nginx-deployment
Namespace:              test
CreationTimestamp:      Thu, 26 Nov 2020 19:32:04 +0800
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod 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    NewReplicaSetAvailable
OldReplicaSets:  <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 test
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-79fbf694f6   3         3         3       39m
nginx-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 test
deployment.apps/nginx-deployment 
REVISION  CHANGE-CAUSE
1         <none>
2         <none

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

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

[xcbeyond@localhost k8s]$ kubectl rollout history deployment/nginx-deployment --revision=2 -n test
deployment.apps/nginx-deployment with revision #2
Pod 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 test
deployment.apps/nginx-deployment rolled back

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

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

[xcbeyond@localhost k8s]$ kubectl describe deployment/nginx-deployment -n test
Name:                   nginx-deployment
Namespace:              test
CreationTimestamp:      Thu, 26 Nov 2020 19:32:04 +0800
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 3
Selector:               app=nginx
Replicas:               3 desired | 2 updated | 4 total | 3 available | 1 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod 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    ReplicaSetUpdated
OldReplicaSets:  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/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  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 test
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-86b8cc866b-7g4xp   1/1     Running   0          24h
nginx-deployment-86b8cc866b-pgh6c   1/1     Running   0          24h
nginx-deployment-86b8cc866b-qg26j   1/1     Running   0          23h

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

[xcbeyond@localhost ~]$ kubectl scale deployment nginx-deployment --replicas 5 -n test
deployment.apps/nginx-deployment scaled
[xcbeyond@localhost ~]$ kubectl get pod -n test
NAME                                READY   STATUS              RESTARTS   AGE
nginx-deployment-86b8cc866b-7g4xp   1/1     Running             0          24h
nginx-deployment-86b8cc866b-dbkms   0/1     ContainerCreating   0          5s
nginx-deployment-86b8cc866b-pgh6c   1/1     Running             0          24h
nginx-deployment-86b8cc866b-qg26j   1/1     Running             0          23h
nginx-deployment-86b8cc866b-xv5pm   0/1     ContainerCreating   0          5s
[xcbeyond@localhost ~]$ kubectl get pod -n test
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-86b8cc866b-7g4xp   1/1     Running   0          24h
nginx-deployment-86b8cc866b-dbkms   1/1     Running   0          79s
nginx-deployment-86b8cc866b-pgh6c   1/1     Running   0          24h
nginx-deployment-86b8cc866b-qg26j   1/1     Running   0          23h
nginx-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)基于autoscaling/v1版本的HorizontalPodAutoscaler配置:

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx
spec:
  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/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx
spec:
  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/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx
spec:
  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,设置指标的名称为queue_messages_ready,具有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,并提供名为http_requests_total的自定义指标值。

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

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

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


参考文章:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK