三、资源调度
1. 为什么要资源调度
在传统架构中,我们总在考虑或者面临一个问题,我们的应用需要部署在哪里?我们的应用现在在哪里运行着?有一个服务不可访问了,去哪里排查?诸如此类的问题总是会出现在工作中。
但是在使用Kubernetes部署应用后,我们无须再关心应用到底部署在了哪台服务器,也无须登录某台服务器去排查应用的问题,而是使用Kubernetes提供的Kubectl或者Dashboard即可快速地定位到应用,之后便可以进行执行命令、查看日志、监控等操作。
对于Kubernetes的应用部署,之前提到过Pod就是用来配置容器的,容器就是用来运行应用的。也提到过,在实际使用时,不会创建单独的Pod运行应用,而是使用更高级的资源进行调度。例如:Deployment(无状态应用管理)、StatefulSet(有状态应用管理)等。
2. ReplicaSet(RS)
ReplicaSet 这种资源对象就可以来帮助我们实现这个功能,ReplicaSet(RS)的主要作用就是维持一组 Pod 副本的运行,保证一定数量的 Pod 在集群中正常运行,ReplicaSet 控制器会持续监听它说控制的这些 Pod 的运行状态,在 Pod 发送故障数量减少或者增加时会触发调谐过程,始终保持副本数量一定。
ReplicaSet 是支持基于集合的标签选择器的下一代Replication Controller,它主要用作Deployment协调创建、删除和更新Pod,和Replication Controller唯一的区别是,ReplicaSet支持标签选择器。在实际应用中,虽然ReplicaSet可以单独使用,但是一般建议使用Deployment来自动管理ReplicaSet,除非自定义的Pod不需要更新或有其他编排等。
Tips:ReplicaSet(复制集,RS)只是简单部署Pod的方式,在生产环境中主要使用更高级的Deployment等方式进行Pod的管理和部署。
一个yaml示例
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
namespace: default
spec:
replicas: 3 # 期望的 Pod 副本数量,默认值为1
selector: # Label Selector,必须匹配 Pod 模板中的标签
matchLabels:
app: nginx
template: # Pod 模板,实际上就是以前我们定义的 Pod 内容,相当于把一个 Pod 的描述以模板的形式嵌入到了 ReplicaSet 中来。
metadata:
labels:
app: nginx #匹配这个标签
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
3. Replication Controller(RC)
Replication Controller(简称 RC)可确保 Pod 副本数达到期望值,也就是 RC 定义的数量。换句话说,Replication Controller 可确保一个 Pod 或一组同类 Pod 总是可用。
如果存在的 Pod 大于设定的值,则 Replication Controller 将终止额外的 Pod。如果太小,Replication Controller 将启动更多的 Pod 用于保证达到期望值。
与手动创建 Pod 不同的是,用Replication Controller 维护的 Pod 在失败、删除或终止时会自动替换。因此即使应用程序只需要一个 Pod,也应该使用 Replication Controller 或其他方式管理。Replication Controller 类似于进程管理程序,但是 Replication Controller 不是监视单个节点上的各个进程,而是监视多个节点上的多个 Pod。
一个yaml示例
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
4. Deployment(无状态应用管理)
Deployment是一个更高级的概念,它管理ReplicaSet并为Pod和ReplicaSet提供声明性更新以及许多其他有用的功能,Deployment一般用于部署公司的无状态服务,这个也是最常用的控制器,因为企业内部现在都是以微服务为主,而微服务实现无状态化也是最佳实践,可以利用Deployment的高级功能做到无缝迁移、自动扩容缩容、自动灾难恢复、一键回滚等功能。
4.1 创建Deployment
Deployment部署过程
1.在Master节点上定义一个yaml文件,其中kind为Deployment。使用kubectl创建一个Deployment文件,副本数为3。
2.在执行kubectl create命令时,提交给api-server
3.api-server持久化实例
4.假设在Default命名空间创建Deployment文件,先创建Replica Set,其命名规则为Deployment名称-[POD-TEMPLATE-HASH-VALUE]
5.由RS在不同的工作节点上创建Pod
下面通过kubectl命令来生成一个Deployment的yaml文件,写上参数并指定-oyaml
输出yaml格式的Deployment定义,--dry-run=client
在创建之前模拟一次创建操作,不会实际创建对象。
kubectl create deployment nginx --image=nginx:1.15.12 --replicas=3 -oyaml --dry-run=client
从上面命令生成的yaml进行修改,一个简单的三副本nginx的Deployment描述清单如下。
[root@k8s-master01 deployment]# cat /yaml/deployment/deployment-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: deploy-nginx #Deployment的名称
spec:
replicas: 3 #创建Pod的副本数
selector: #selector:定义Deployment如何找到要管理的Pod,与template的label (标签)对应
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx #使用label(标签)标记Pod
spec:
containers:
- name: deploy-nginx
image: nginx:1.15.12 #运行此Pod使用的镜像
ports:
- containerPort: 80 #容器用于发送和接收流量的端口
使用deployment-test.yaml创建三副本nginx的Deployment,为什么下面READY 没有3/3呢?因为可能另外一个Pod被调度到的那个节点还没有它配置清单里面的image,所以需要先去拉取镜像。
- NAME:集群中Deployment的名称。
- READY:Pod就绪个数和总副本数。
- UP-TO-DATE:显示已达到期望状态的被更新的副本数。
- AVAILABLE:显示用户可以使用的应用程序副本数,当前为2,因为部分Pod仍在创建过程中。
- AGE:显示应用程序运行的时间。
果然如此,有一个Pod在node01上,因为没镜像,还得拉取,所以READY没有达到。
Deployment通过ReplicaSet管理Pod,可以查看此Deployment当前对应的ReplicaSet。能通过kubectl get rs
查到相关信息,同时在kubectl get pod
查看此Deployment创建的Pod会发现两者的NAME字段部分HASH内容一致,这也是如何通过这些NAME来确定是哪个Deployment。
ReplicaSet的命名格式为[DEPLOYMENT-NAME]-[POD-TEMPLATE-HASH-VALUE]
,POD-TEMPLATE-HASH- VALUE,是自动生成的,不用手动指定。
如果Deployment有过更新,对应的ReplicaSet可能不止一个,可以通过-o yaml获取当前对应的ReplicaSet是哪个,其余的ReplicaSet为保留的历史版本,用于回滚等操作。
可以通过label来筛选Deployment的rs、Pod对应的资源。
关于ReplicaSet控制器的特性
ReplicaSet控制器会持续监听它说控制的这些 Pod 的运行状态,在 Pod 发送故障数量减少或者增加时会触发调谐过程,始终保持副本数量一定。下面通过删除某个Pod,看看是否会再某个Node节点上进行自动恢复。那如下图,当然是没问题的!
4.2 Deployment滚动更新
通过Deployment部署应用后,经常会有Deployment文件的配置更改或者镜像版本迭代的需求,更改配置后该Deployment会创建新的ReplicaSet,之后会对管理的Pod进行滚动升级。
Deployment滚动更新过程
- 在master节点上定义一个yaml文件,其中kind为Deployment。使用kubectl创建一个Deployment
- 在执行kubectl set/edit命令时,提交给api-server**(开始滚动更新操作)**
- api-server持久化实例
- 假设在Default命名空间创建的Deployment文件,会再创建一个新的Replica Set。
- 由新的Replica Set在不同的工作节点上创建Pod逐步替代旧Replica Set创建的旧Pod。
什么情况下会进行滚动更新?
当且仅当Deployment的Pod模板(即.spec.template)更改时,才会触发Deployment更新,例如更改内存、CPU配置或者容器的image。
推荐的两种Deployment滚动更新的方式
第一种:kubectl set
#格式:kubectl set image deployment deployment名称 容器名称=nginx:1.14.2 --record
kubectl set image deployment deploy-nginx deploy-nginx=nginx:1.14.2 --record
第二种:使用 kubectl edit 命令直接编辑 Deployment
下面进行一次滚动更新镜像,yaml配置还是nginx:1.15.12、副本数是3,创建起Deployment后查看相关信息,还有Pod的信息,然后就使用kubectl set image
进行滚动更新。
Tips:可以在滚动更新执行后迅速在另外一个ssh会话上进行查看滚动更新的状态。 kubectl rollout status deployment deploy-nginx
还可以在滚动过程中进行暂停,更好的查看滚动过程变化。
滚动更新暂停:kubectl rollout pause deployment deploy-nginx
滚动更新恢复:kubectl rollout resume deployment deploy-nginx
通过kubectl describe deployment deploy-nginx
查看该deployment的详细信息。
- 从Events事件区域的变化就能看出一开始的ReplicaSet资源对象是
deploy-nginx-68666bd8d7
,且第一次就将副本直接将其扩展为 3 个副本。 - 执行滚动更新部署时,它创建了一个新的 ReplicaSet,命名为
deploy-nginx-68666bd8d7
,将其副本数扩展为 1。 - 然后将旧的 ReplicaSet 缩小为 2,这样至少可以有 2 个 Pod 可用。
- 然后将新的 ReplicaSet 扩展为 2,此时最多的可用就是4 个 Pod(两新两旧)。
- 接下来旧的 ReplicaSet进一步缩小为1。
- 然后将新的 ReplicaSet 扩展为 3,此时新的ReplicaSet已达到三副本(但此时旧ReplicaSet还有1个Pod)。
- 最后旧的 ReplicaSet 缩小为0,即最后一个旧Pod被删除。
查看ReplicaSet对象,已经有两个ReplicaSet。
- DESIRED:定义的副本数
- CURRENT:正在运行的副本数
- READY:就绪状态的副本数
- AGE:运行的时间。
4.3 Deployment回滚
当更新了版本不稳定或配置不合理时,可以对其进行回滚操作,假设我们又进行了几次更新。此时可查看之前滚动更新的版本,通常来说都是回滚到上一个版本。
使用 kubectl rollout history 查看更新历史
#命令格式:kubectl rollout history deployment <deployment名称>
kubectl rollout history deployment deploy-nginx
其实 rollout history中记录的 revision是和 ReplicaSet一一对应。如果我们手动删除某个ReplicaSet,对应的rollout history就会被删除,也就是说你无法回滚到这个revison了,
还可以查看一个revison的详细信息,使用如下命令:
kubectl rollout history deployment deploy-nginx --revision=上图查出来的版本号
查看此时的ReplicaSet和Pod对象,并现在要直接回退到当前版本的前一个版本
#命令格式:kubectl rollout undo deployment <deployment名称>
kubectl rollout undo deployment deploy-nginx
#回滚指定版本,回滚操作revision会递增的,需要回滚时,重新查看rollout history
kubectl rollout undo deployment deploy-nginx --to-revision=查出来的版本号
查看是否回滚,已经回滚了。
4.4 Deployment扩容和缩容
因为 ReplicaSet 就可以实现,所以 Deployment 控制器只需要去修改它缩控制的 ReplicaSet 的 Pod 副本数量就可以了。比如现在把 Pod 的副本调整到 4 个,那么 Deployment 所对应的 ReplicaSet 就会自动创建一个新的 Pod 出来,这样就水平扩展了可以使用kubectl scale
命令来完成这个操作:
#命令格式:kubectl scale deployment <deployment名称> --replicas=副本数量
kubectl scale deployment deploy-nginx --replicas=4
尝试缩回两个副本,也是没问题的!
4.5 Deployment暂停和恢复
之前的操作均为更改某一处的配置,更改后立即触发更新,大多数情况下可能需要针对一个资源文件更改多处地方,而并不需要多次触发更新,此时可以使用Deployment暂停功能,临时禁用更新操作,对Deployment进行多次修改后再进行更新。使用kubectl rollout pause命令即可暂停Deployment更新。
#命令格式:kubectl rollout pause deployment <deployment名称>
kubectl rollout pause deployment deploy-nginx
下面进行修改Deployment的镜像版本、资源限制。先行查看目前的history,然后暂停更新操作。
Tips:也可以通过此命令查看Deployment详情,好做比对。kubectl describe deployment <Deployment名称>
修改Deployment,可以通过set命令多次修改,或者用kubectl edit进行一次性多次修改。
#修改镜像
kubectl set image deployment deploy-nginx deploy-nginx=nginx:1.15.12
#修改资源限制
kubectl set resources deployment deploy-nginx -c=deploy-nginx --limits=cpu=200m,memory=512Mi
通过kubectl get deployment deploy-nginx -o yaml
,是有看到被修改了,但查看目前正在运行的Pod并没有任何新增的修改项。此时进行使用kubectl rollout resume
恢复 Deployment 更新。
#命令格式:kubectl rollout resume deployment <deployment名称>
kubectl rollout resume deployment deploy-nginx
此时从AGE和NAME都能看出已更新,还有ReplicaSet也是新增的。
Tips:可以用过kubectl describe pod来查看新Pod是否已经修改!
4.6 Deployment更新策略
历史版本清理策略:在默认情况下,revision保留10个旧的ReplicaSet,其余的将在后台进行垃圾回收,可以在.spec.revisionHistoryLimit设置保留ReplicaSet的个数。当设置为0时,不保留历史记录。
更新和Ready策略:
#可以修改Deployment对象配置,在spec的下一级。
minReadySeconds: 5 #容器起来后不会立刻加入服务,表示设置多少时间后才进行升级(提供服务),默认是0。
strategy:
type: RollingUpdate # 指定更新策略:RollingUpdate(表示滚动更新)和Recreate(表示重建,先删掉旧的Pod,再创建新的Pod),默认是滚动更新。
rollingUpdate:
#以下两个配置可以为数字或百分比,默认为25%
maxSurge: 1 #表示升级过程中最多可以比原先设置多出的Pod的数量
maxUnavailable: 1 #表示升级过程中最多有多少个Pod处于无法提供服务的状态,该值不能为0。
5. StatefulSet(有状态应用管理)
5.1 了解无状态服务和有状态服务
Deployment 并不能编排所有类型的应用,对无状态服务编排是非常容易的,但是对于有状态服务就无能为力了。我们需要先明白一个概念:什么是有状态服务,什么是无状态服务。
无状态服务(Stateless Service)
:该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的,比如 WordPress 实例,我们是不是可以同时启动多个实例,但是我们访问任意一个实例得到的结果都是一样的吧?因为他唯一需要持久化的数据是存储在MySQL数据库中的,所以我们可以说 WordPress 这个应用是无状态服务,但是 MySQL 数据库就不是了,因为他需要把数据持久化到本地。有状态服务(Stateful Service)
:就和上面的概念是对立的了,该服务运行的实例需要在本地存储持久化数据,比如上面的 MySQL 数据库,你现在运行在节点 A,那么他的数据就存储在节点 A 上面的,如果这个时候你把该服务迁移到节点 B 去的话,那么就没有之前的数据了,因为他需要去对应的数据目录里面恢复数据,而此时没有任何数据。
5.2 StatefulSet为什么适合有状态服务
由于无状态组件没有预定义的启动顺序、集群要求、点对点 TCP 连接、唯一的网络标识符、正常的启动和终止要求等,因此可以很容易地进行容器化。诸如数据库,大数据分析系统,分布式 key/value 存储、消息中间件需要有复杂的分布式体系结构,都可能会用到上述功能。
StatefulSet它可以处理 Pod 的启动顺序,为保留每个 Pod 的状态设置唯一标识,具有以下几个功能特性:
- 稳定的、唯一的网络标识符
- 稳定的、持久化的存储
- 有序的、优雅的部署和缩放
- 有序的、优雅的删除和终止
- 有序的、自动滚动更新
5.3 提前了解Headless Service
Service 是应用服务的抽象,通过 Labels 为应用提供负载均衡和服务发现,每个 Service 都会自动分配一个 cluster IP 和 DNS 名,在集群内部可以通过该地址或者通过 FDQN 的形式来访问服务。比如,一个 Deployment 有 3 个 Pod,那么就可以定义一个 Service,有如下两种方式来访问这个 Service:
- cluster IP 的方式,比如:当我访问 192.168.0.100 这个 Service 的 IP 地址时,192.168.0.100 其实就是一个 VIP,它会把请求转发到该 Service 所代理的 Endpoints 列表中的某一个 Pod 上。具体原理需要学习到Service 章节再深入了解。
- Service 的 DNS 方式,比如我们访问
“mysvc.mynamespace.svc.cluster.local”
这条 DNS 记录,就可以访问到 mynamespace 这个命名空间下面名为 mysvc 的 Service 所代理的某一个 Pod。(通常末尾svc.cluster.local没修改就不变)
对于 DNS 这种方式实际上也有两种情况:
- 第一种就是普通的 Service,访问
“mysvc.mynamespace.svc.cluster.local”
的时候是通过集群中的 DNS 服务解析到的 mysvc 这个 Service 的 cluster IP 。 - 第二种情况就是Headless Service,对于这种情况,访问
“mysvc.mynamespace.svc.cluster.local”
的时候是直接解析到的 mysvc 这个Service代理的某一个具体的 Pod 的 IP 地址,中间少了 cluster IP 的转发,这就是二者的最大区别,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 的记录方式解析到后面的 Pod 的 IP 地址。
定义一个Headless Service
[root@k8s-master01 StatefulSet]# cat /yaml/StatefulSet/headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: headless-nginx
namespace: default
labels:
app: nginx
spec:
ports:
- name: http
port: 80
clusterIP: None
selector:
app: nginx
创建该Service并查询Service服务信息
实际上 Headless Service 在定义上和普通的 Service 几乎一致, 只是他的 clusterIP=None,所以,这个 Service 被创建后并不会被分配一个 cluster IP,而是会以 DNS 记录的方式暴露出它所代理的 Pod,而且还有一个非常重要的特性,对于 Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个 DNS 记录正是 Kubernetes 集群为 Pod 分配的一个唯一标识,只要知道 Pod 的名字,以及它对应的 Service 名字,就可以组装出这样一条 DNS 记录访问到 Pod 的 IP 地址。
5.4 StatefulSet的基本概念
StatefulSet主要用于管理有状态应用程序的工作负载API对象。比如在生产环境中,可以部署ElasticSearch集群、MongoDB集群或者需要持久化的RabbitMQ集群、Redis集群、Kafka集群和ZooKeeper集群等。
StatefulSet为每个Pod维护一个持久的标识符,在重新调度时也会保留,一般格式为StatefulSetName-Number。比如定义一个名字是Redis-Sentinel的StatefulSet,指定创建3个Pod,那么创建出来的Pod名字就为Redis-Sentinel-0、Redis-Sentinel-1、Redis-Sentinel-2。
Tips:StatefulSet创建的Pod一般使用Headless Service(无头服务)进行通信。
Headless一般的格式:statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
- serviceName为Headless Service的名字,创建StatefulSet时必须指定Headless Service名称。
- 0..N‒1为Pod所在的序号,从0开始到N‒1。
- statefulSetName为StatefulSet的名字。
- namespace为服务所在的命名空间。
- .cluster.local为Cluster Domain(集群域)。
Tips:StatefulSet目前使用Headless Service(无头服务)负责Pod的网络身份和通信,需要提前创建此服务。
注意事项
删除一个StatefulSet时,不保证对Pod的终止,要在StatefulSet中实现Pod的有序和正常终止,可以在删除之前将StatefulSet的副本缩减为0。
StatefulSet无序创建Pod参数
如果要实现无序地同时创建0到N-1个Pod,你需要将 StatefulSet 的 .spec.podManagementPolicy
设置为 "Parallel"
。默认情况下,.spec.podManagementPolicy
的值为 "OrderedReady"
,它指示 Kubernetes 按顺序逐个创建 Pod,并等待每个 Pod 就绪才能创建下一个 Pod。将 .spec.podManagementPolicy
设置为 "Parallel"
后,Kubernetes 将会同时创建所有 Pod。你还需要设置 .spec.updateStrategy.type
为 "RollingUpdate"
,这样 Kubernetes 才会在启动和更新时同时创建所有 Pod。
5.5 创建StatefulSety以及Headless Service通信原理
一个Headless Service与StatefulSet的yaml文件如下:
[root@k8s-master01 StatefulSet]# cat /yaml/StatefulSet/sts-web.yaml
apiVersion: v1
kind: Service #定义了一个Service类型的资源
metadata:
name: head-nginx #给这个Service取了一个名字叫"head-nginx"。
labels:
app: nginx #标签用于标识和分类资源,这里给Service添加了一个"app: nginx"的标签。
spec:
ports:
- port: 80 #将80端口映射到Service上
name: web #给这个端口起了一个名字叫"web"
clusterIP: None #将clusterIP设置为None,表示这个Service不暴露Cluster内部的IP(Headless Service)。
selector:
app: nginx #定义了Service所选取的Pod的标签。
---
apiVersion: apps/v1
kind: StatefulSet #定义了一个StatefulSet类型的资源,用于管理Stateful应用程序的部署。
metadata:
name: web #给这个StatefulSet取了一个名字叫"web"。
spec:
serviceName: "head-nginx" #指定了与这个StatefulSet关联的Service的名字,与上面对应。
replicas: 3 #指定了要创建的Pod副本数量。
selector: #定义了StatefulSet所选取的Pod的标签。
matchLabels: #选择具有"app: nginx"标签的Pod来管理。
app: nginx
template: #定义了要创建的Pod的模板配置。
metadata:
labels:
app: nginx #给Pod添加了一个"app: nginx"的标签。
spec:
containers:
- name: nginx #给容器起了一个名字叫"nginx"。
image: nginx:1.14.2 #指定了容器所使用的镜像。
ports:
- containerPort: 80 #将容器的80端口暴露出来。
name: web #给容器的端口起了一个名字叫"web"。
Tips:StatefulSet中必须设置Pod选择器(.spec.selector)用来匹配其标签(.spec.template.metadata.labels)
创建Service和StatefulSet,创建一个CLUSTER-IP为None,名为head-nginx的Headless Service,创建一个名为web的StatefulSet,其中Pod分别为web-0、web-1、web-2。在同一个命名空间内使用web-0.head-nginx、web-1.head-nginx、web-2.head-nginx即可访问这两个Pod,跨命名空间可以使用web-0.head-nginx.default访问(跨命名空间访问资源的情况很少,应当尽量规避)。
Headless Service通信原理
查看head-nginx这个Headless Service的详细信息,Endpoints已经将StatefulSet中的各个Pod的IP添加进来了。
创建一个测试Pod,该Pod带有nslookup命令,可以进行验证Headless Service的DNS记录。
#Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
访问“head-nginx.default.svc.cluster.local”这条DNS记录,就可以访问到default这个命名空间下面名为head-nginx的Service所代理的某一个 Pod。
Tips:在同一命名空间下各Pod进行互访只用pod-name.svc-name这样子的FQDN域名格式。
5.6 StatefulSet创建、删除Pod的流程
当创建上面的 Nginx 实例时,Pod 将按 Nginx-0、Nginx-1、Nginx-2 的顺序部署 3 个 Pod。
在 Nginx-0 处于 Running 或者 Ready 之前,Nginx-1 不会被部署,相同的,Nginx-2 在 Nginx-1 未处于 Running和 Ready 之前也不会被部署。
如果在 Nginx-1 处于 Running 和 Ready 状态时,Nginx-0 变成 Failed(失败)状态,那么 Nginx-2 将不会被启动,直到 Nginx-0 恢复为 Running 和 Ready 状态。
如果用户将 StatefulSet 的 replicas 设置为 1,那么 Nginx-2 将首先被终止,在完全关闭并删除Nginx-2 之前,不会删除 Nginx-1。
如果 Nginx-2 终止并且完全关闭后,Nginx-0 突然失败,那么在 Nginx-0 未恢复成 Running 或者 Ready 时,Nginx-1 不会被删除。
总结:
1.按顺序创建、扩容Pod,按逆序删除、缩容Pod。
2.创建、扩容、缩容、删除时,之前的Pod不论哪个环节没Running 或者 Ready,不会再创建、扩容、缩容、删除Pod。
StatefulSet 管理的 Pod 部署和扩展规则如下:
- 对于具有N个副本的StatefulSet,将按顺序从0到N-1开始创建Pod。
- 当删除Pod时,将按照N-1到0的反顺序终止。
- 在缩放Pod之前,必须保证当前的Pod是Running(运行中)或者Ready(就绪)。
- 在终止Pod之前,它所有的继任者必须是完全关闭状态。
关于StatefulSet终止Pod的等待时间说明
StatefulSet 的 pod.Spec.TerminationGracePeriodSeconds(终止 Pod 的等待时间)不应该指定为 0,设置为 0 对 StatefulSet 的 Pod 是极其不安全的做法,优雅地删除 StatefulSet 的 Pod 是非常有必要的,而且是安全的,因为它可以确保在 Kubelet 从 APIServer 删除之前,让 Pod 正常关闭。
下面进行创建和删除演示
起多一个ssh会话,在另外一个会话进行动态查询Pod的情况,使用这个命令kuebectl get pod -w
,下面进行创建StatefulSet。
kubectl create -f sts-web.yaml
StatefulSet创建Pod的过程,注意先后顺序,已经Running后才开始下一个Pod的创建。
Age信息就能看出创建启动的顺序。
删除也同理,逆序删除。
5.7 StatefulSet扩容和缩容
和Deployment类似,可以通过更新replicas字段扩容/缩容StatefulSet,也可以使用kubectl scale、kubectl edit和kubectl patch来扩容/缩容一个StatefulSet。
扩缩容操作一样,这里演示扩容,将StatefulSet副本从3个增加到5个。
#命令格式:kubectl scale sts StatefulSet名称 --replicas=副本数量
kubectl scale sts web --replicas=5
开多一个ssh会话,使用下面命令观察动态变化及最终结果。
5.8 StatefulSet更新策略
StatefulSet和Deployment一样,也提供了多种更新策略,可以在.spec.updateStrategy字段中指定StatefulSet的更新策略。
OnDelete策略
OnDelete更新策略实现了传统(1.7版本之前)的行为,当我们选择这个更新策略并修改StatefulSet的.spec.updateStrategy字段时,StatefulSet控制器不会自动更新Pod,必须手动删除Pod才能使控制器创建新的Pod。
RollingUpdate策略
RollingUpdate(滚动更新)策略会自动更新一个StatefulSet中所有的Pod,采用与序号索引相反的顺序进行滚动更新。
#spec的下级
updateStrategy: #指定应用程序的更新策略
type: RollingUpdate #更新期间会逐步替换旧的Pod,滚动更新。
下图进行了StatefulSet对象资源修改了容器的镜像后,通过kubectl get pod -w
进行实时监控Pod的状态信息的结果,进行逆序删除旧Pod,创建新Pod,Pod达到Running才会进行下一个。
5.9 StatefulSet灰度发布
StatefulSet可以使用RollingUpdate更新策略的partition参数来分段更新一个StatefulSet。分段更新将会使StatefulSet中其余的所有Pod(序号小于partition设定)保持当前版本,只更新序号大于等于partition的Pod,利用此特性可以简单实现金丝雀发布(灰度发布)或者分阶段推出新功能等。
修改示例
#spec的下级
updateStrategy: #指定应用程序的更新策略
type: RollingUpdate #更新期间会逐步替换旧的Pod,滚动更新。
rollingUpdate: #定义了滚动更新策略的相关配置
partition: 0 #指定了更新期间要先更新的Pod的分区。在此配置中,分区被设置为0,表示所有的Pod都会同时进行更新。
下面就是一个创建四副本且RollingUpdate更新策略partition设定为2的StatefulSet,然后Running后模拟修改更新镜像版本,最后apply进行更新StatefulSet资源。
在另外一个ssh会话中通过kubectl get pod -w
进行实时监控Pod的状态信息的结果。只更新序号大于等于partition的Pod,那就3和4。下图即也证明只更新了web-3、web-2。
通过查询目前运行Pod中的image信息也能够证明只替换了web-3、web-2的Pod镜像。
6. DaemonSet(守护进程)
DaemonSet用来部署守护进程的,在每个 Kubernetes 节点中将守护进程的副本作为后台进程运行,说白了就是在每个节点部署一个 Pod副本,当节点加入到 Kubernetes 集群中,Pod 会被调度到该节点上运行,当节点从集群只能够被移除后,该节点上的这个 Pod 也会被移除,当然,如果我们删除 DaemonSet,所有和这个对象相关的Pods都会被删除。
6.1 业务场景
哪种情况下会需要用到这种业务场景呢?其实这种场景还是比较普通的,比如:
- 集群存储守护程序,如 glusterd、ceph 要部署在每个节点上以提供持久性存储。
- 节点监控守护进程,如 Prometheus 监控集群,可以在每个节点上运行一个 node-exporter 进程来收集监控节点的信息。
- 日志收集守护程序,如 fluentd 或 logstash,在每个节点上运行以收集容器的日志。
- 节点网络插件,比如 flannel、calico,在每个节点上运行为 Pod 提供网络服务。
DaemonSet 运行的 Pod 的调度问题,正常情况下,Pod 运行在哪个节点上是由 Kubernetes 的调度器策略来决定的,然而,由 DaemonSet 控制器创建的 Pod 实际上提前已经确定了在哪个节点上了(Pod创建时指定了.spec.nodeName)。
Tips:关于调度问题,看后面章节。
6.2 创建DaemonSet
集群中的 Pod 和 Node 是一 一对应的,而 DaemonSet 会管理全部机器上的 Pod 副本,负责对它们进行更新和删除。DaemonSet 控制器是如何保证每个 Node 上有且只有一个被管理的 Pod 呢?
- 首先控制器从 Etcd 获取到所有的 Node 列表,然后遍历所有的 Node。
- 根据资源对象定义是否有调度相关的配置,然后分别检查 Node 是否符合要求。
- 在可运行 Pod 的节点上检查是否已有对应的 Pod。
- 如果没有,则在这个 Node 上创建该 Pod。
- 如果有,并且数量大于 1,那就把多余的 Pod 从这个节点上删除。如果有且只有一个 Pod,那就说明是正常情况。
Tips:不一定会到所有Node进行创建Pod,与调度的配置有关,后续会学到,目前默认都能在Node上创建。
一个DaemonSet的示例yaml
[root@k8s-master01 DaemonSet]# cat nginx-ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ds
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx:1.15.12
name: nginx
ports:
- name: http
containerPort: 80
创建DaemonSet,查看Pod的状态信息,能看到目前在所有的Node上都创建了。
6.3 指定节点创建DaemonSet
如果指定了.spec.template.spec.nodeSelector,DaemonSet Controller将在与Node Selector(节点选择器)匹配的节点上创建Pod,比如部署在磁盘类型为ssd的节点上(需要提前给节点定义标签Label):
nodeSelector:
disktype: ssd
比如就在node01上进行创建DaemonSet,那可以先给node01定义个标签Label,然后修改yaml后进行apply更新DaemonSet资源。
#增加标签
kubectl label node 节点 disktype=ssd
#删除标签
kubectl label node 节点 disktype-
在之前创建的DaemonSet基础,打上一个Label,kubectl label node k8s-node01 disktype=ssd
。然后在DaemonSet的yaml文件上增加标签,最后进行apply更新DaemonSet资源,查询Pod的信息就能看到只在有disktype=ssd
这个标签Label上进行运行。
如果添加了新节点或修改了节点标签(Label),DaemonSet 将立刻向新匹配上的节点添加Pod,同时删除不能匹配的节点上的 Pod。下面将Node01的标签Label删除,将disktype=ssd
这个标签添加到Node02,再来看DaemonSet的Pod变化。
6.4 DaemonSet更新策略
DaemonSet 更新策略和 StatefulSet 类似,也有 OnDelete 和 RollingUpdate 两种方式。
OnDelete策略
当我们选择这个更新策略并修改DaemonSet的.spec.updateStrategy字段时,StatefulSet控制器不会自动更新Pod,必须手动删除Pod才能使控制器创建新的Pod。
RollingUpdate策略
RollingUpdate(滚动更新)策略会自动更新一个DaemonSet中所有的Pod,一节点先删旧Pod,创建新Pod,然后再下一个节点。
#spec的下级
updateStrategy: #指定应用程序的更新策略
type: RollingUpdate #更新期间会逐步替换旧的Pod,滚动更新。
下面通过一个更新容器image的示例看看更新的策略,先创建DaemonSet,修改镜像,然后apply更新DaemonSet资源。
通过kubectl describe daemonset nginx-ds
进行查看DaemonSet的Events事件区域变化情况,Pod是先删后建,且单一节点进行完才会在下一个节点进行。(同时可以注意Image的变化,或者使用其他方法查看Pod的详细信息也可以)
6.5 DaemonSet回滚
当更新了版本不稳定或配置不合理时,可以对其进行回滚操作,假设我们又进行了几次更新。此时可查看之前滚动更新的版本,通常来说都是回滚到上一个版本。
使用 kubectl rollout history 查看更新历史
kubectl rollout history daemonset <daemonset名称>
查看一个revison的详细信息,使用如下命令:
kubectl rollout history daemonset <daemonset名称> --revision=上图查出来的版本号
回滚上一个版本或指定版本
#回滚上一个版本
命令格式:kubectl rollout undo deployment <daemonset名称>
#回滚指定版本,回滚操作revision会递增的,需要回滚时,重新查看rollout history
kubectl rollout undo daemonset <daemonset名称> --to-revision=查出来的版本号
7. 标签(Label)和选择器(Selector)
7.1 作用概述
使用标签同样可以对Kubernetes的其他资源进行配置,当Kubernetes对系统的任何API对象(如Pod和节点)进行“分组”时,会对其添加标签(key=value形式的“键-值对”),用以精准地选择对应的API对象。而选择器则是针对匹配对象的查询方法。
很多情况下,我们需要对各类资源进行分组管理,或者同一个应用的Pod可能会根据不同的功能进行划分,比如有一个后端的应用,需要提供API接口供前端调用,也需要进行一些周期性的计划任务,相当于同一个镜像分两个身份运行,所以就需要对这两个资源文件用标签进行区分,这样的话就可以让流量进入提供API接口的Pod,执行计划任务的Pod不接收任何流量。
此时其他应用调用该应用的Service(Service为容器服务的入口,可以通过Service访问符合选择器(Selector)过滤的容器的应用程序,缩写为svc)时,流量只能到达api的Pod,不会到达cron的Pod,因为选择器进行匹配时是“与”的关系,需要所有的标签(Label)都要匹配,所以role=cronjob的Pod不会被接入流量。
7.2 添加标签(Label)
#例如给某个Node节点打上一个disktype: ssd
kubectl label node k8s-node01 disktype=ssd
7.3 修改标签(Label)
#例如给某个Node节点修改已有标签disktype: ssd为disktype: HDD,使用这个参数 --overwrite
kubectl label node k8s-node01 disktype=HDD --overwrite
[root@k8s-master01 deployment]# kubectl get node k8s-node01 --show-labels
NAME STATUS ROLES AGE VERSION LABELS
k8s-node01 Ready <none> 6d21h v1.23.17 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disktype=HDD,kubernetes.io/arch=amd64,kubernetes.io/hostname=k8s-node01,kubernetes.io/os=linux
7.4 删除标签(Label)
#例如给某个Node节点删除disktype: ssd这个标签(只用写“键”加减号)
kubectl label node k8s-node01 disktype-
[root@k8s-master01 deployment]# kubectl label node k8s-node01 disktype-
node/k8s-node01 unlabeled
7.5 选择器(Selector)
#选择符合的资源,加上--show-labels即可
kubectl get node --show-labels
kubectl get deployment deploy-nginx --show-labels
kubectl get pod --show-labels、
kubectl get service --show-labels
......
#选择匹配 app 为 details 或者 productpage 的 Service
kubectl get svc -l 'app in (details, productpage)' --show-labels
#选择 app 为 productpage 或 reviews 但不包括 version=v1 的 Service
kubectl get svc -l version!=v1,'app in (details, productpage)' --show-labels
#选择 label 的 key 名为 app 的 Service
kubectl get svc -l app --show-labels
例如只把某个deploymentSet在有SSD硬盘上部署,那可以给这些节点打上disktype=ssd
这个标签,然后在deploymentSet的yaml文件进行选择器(Selector)选择标签(Label)。
#示例,只在Node01上部署,给Node01节点打上标签。
[root@k8s-master01 deployment]# kubectl label node k8s-node01 disktype=ssd
node/k8s-node01 labeled
书写yaml文件
[root@k8s-master01 deployment]# cat nginx-label.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: deploy-nginx #Deployment的名称
spec:
replicas: 3 #创建Pod的副本数
selector: #selector:定义Deployment如何找到要管理的Pod,与template的label (标签)对应
matchLabels:
app: nginx
minReadySeconds: 3
template:
metadata:
labels:
app: nginx #使用label(标签)标记Pod
spec:
containers:
- name: deploy-nginx
image: nginx:1.14.2 #运行此Pod使用的镜像
ports:
- containerPort: 80 #容器用于发送和接收流量的端口
nodeSelector: #根据节点的标签选择适合的节点来运行Pod
disktype: ssd #根据这个标签
创建deployment,查看Pod所在节点情况,没问题!
标签:kubectl,StatefulSet,Service,调度,nginx,Deployment,Pod,资源 From: https://blog.51cto.com/YinJayChen/7887387本篇文章内容参考杜宽的《云原生Kubernetes全栈架构师》,视频、资料文档等,大家可以多多支持!还有k8s训练营、“我为什么这么菜”知乎博主等资料文档,感谢无私奉献!