xcbeyond的个人空间
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-proxy
、istio-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资源清单中有几个重要属性:apiVersion
、kind
、metadata
、spec
以及status
。其中apiVersion
和kind
是比较固定的,status
是运行时的状态,所以最重要的就是metadata
和spec
两个部分。
(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
的定义。如下图所示:
Pod
的status
字段是一个PodStatus
的对象,PodStatus
中有一个phase
字段。
无论是手动创建还是通过Deployment
等控制器创建,Pod
对象总是处于其生命周期中以下几个阶段(phase
)之一:
- 挂起(
Pending
):API Server
已经创建了该Pod
,并已存入etcd
中,但它尚未被调度完成,或者仍处于从仓库下载镜像的过程中。 - 运行中(
Running
):Pod
内所有容器均已创建,被调度至某节点,并且所有容器都已经被kubelet
创建完成,且至少有一个容器处于运行状态、正在启动状态或正在重启状态。 - 成功(
Succeeded
):Pod
内所有容器都已经成功执行后退出终止,且不会再重启。 - 失败(
Failed
):Pod
内所有容器都已退出,并且至少有一个容器是因为失败而终止退出。即容器以非0
状态退出或者被系统禁止。 - 未知(
Unknown
):由于某种原因Api Server
无法正常获取到该Pod
对象的状态信息,可能由于无法与所在工作节点的kubelet
通信所致(网络通讯不畅)。
3.1 Pod的创建过程
Pod
是Kubernetes中的基础单元,了解它的创建过程对于理解其生命周期有很大的帮助,如下图描述了一个Pod
资源对象的典型创建过程。
(1)用户通过kubectl
或其他API
客户端提交了Pod Spec
给API 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-scheduler
为Pod
对象挑选一个工作节点并将结果信息更新至API Server
。
(7)调度结果信息由API Server
更新至etcd
存储系统,而且API Server
也开始反馈此Pod
对象的调度结果。
(8)Pod
被调度到的目标工作节点上的kubelet
尝试在当前节点上调用Docker
启动容器,并将容器的结果状态返回送至API Server
。
(9)API Server
将Pod
状态信息存入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
请求进行诊断,响应码为2xx
或3xx
时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:“Success”(成功)
、“Failure”(失败)
、“Unknown”(未知)
,只有success
表示成功通过检测。
容器探测分为两种类型:
- 存活性探测(livenessProbe):用于判定容器是否处于“运行”(
Running
)状态。一旦此类检测未通过,kubelet
将杀死容器并根据重启策略(restartPolicy
)决定是否将其重启,未定义存活检测的容器的默认状态为“Success
”。 - 就绪性探测(readinessProbe):用于判断容器是否准备就绪并可对外提供服务。未通过检测的容器意味着其尚未准备就绪,端点控制器(如
Service
对象)会将其IP
从所有匹配到此Pod
对象的Service
对象的端点列表中移除。检测通过之后,会再将其IP
添加至端点列表中。
什么时候使用存活(liveness)和就绪(readiness)探针?
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针,kubelet
将根据Pod
的restartPolicy
自动执行正确的操作。
如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定restartPolicy
为Always
或OnFailure
。
如果要仅在探测成功时才开始向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 rs
和kubectl 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的定义中使用NodeSelector
、NodeAffinity
、PodAffinity
、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
语法支持的操作符包括: In
,NotIn
,Exists
,DoesNotExist
,Gt
,Lt
。 你可以使用 NotIn
和 DoesNotExist
来实现节点反亲和行为,即实现排查的功能。
NodeAffinity
规则定义的注意事项如下:
- 如果同时指定了
nodeSelector
和nodeAffinity
,那么两者必须都要满足,才能将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亲和和互斥的设置也是requiredDuringSchedulingIgnoredDuringExecution
和PreferredDuringSchedulingIgnoredDuringExection
。
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
中的两个参数maxUnavailable
和maxSurge
来控制滚动更新的过程。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使用率。minReplicas
和maxReplicas
: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。minReplicas
和maxReplicas
: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-apiserver
、kube-controller-manager
服务的相关启动参数。
kube-apiserver
、kube-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自动扩容机制。
参考文章:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK