Kubernetes(K8s)系列——(二)关于Deployment、StatefulSet、DaemonSet、Job、CronJob
三、Deployment
之前有个ReplicationController也可以实现扩缩容,但目前他已经过时了。现在通过Deployment+ReplicaSet来实现这一功能,而且功能更加强大,因为Deployment具有更加便捷的滚动更新能力、ReplicaSet可以实现更加复杂的标签选择器等特性。
Deployment + ReplicaSet > ReplicationController
实际上在开发中我们并不会直接手动的去创建Pod,而是创建一个Deployment这样的工作负载(还有其他的工作负载类型,比如StatefulSet、DaemonSet等,后面会一一提到),由他们来创建并管理实际的Pod。我们可能会想,为什么要用Deployment来管理Pod呢,直接创建Pod不香吗?其实真的不香,通过Deployment管理的Pod具有自愈能力、动态扩缩容能力、滚动升级能力。Deployment才是真香!(至于为什么香,感兴趣的可以自己去看看已经过时的ReplicationController的滚动升级的过程。)
1、Deployment的自愈功能
我们可以直接通过一个实验就能明白什么是自愈功能。
-
首先部署一个Deployment(这时就会创建一个或多个pod,可以通过命令
kubectl get pod -owide
查看这个pod部署在哪个节点上)
-
然后在master上监听这些pod(命令:
watch -n 1 kubectl get pod
) -
接着在部署pod的节点上去删掉这个Pod中部署的容器(
docker rm -f 容器id
),模拟Pod内容器异常。 -
这时我们在查看Pod(kubectl get pod),会发现有一个Pod处于容器创建的状态,因为我们刚刚删除了这个Pod中的容器,所以他现在在重新创建这个Pod中的容器。
这就是Department的自愈能力,只要由Deployment管理的Pod内的容器出现了异常,那么他会重启这个容器。
而如果是Pod被删除了,那么他会重新拉起一个Pod。如果一个被单独创建的Pod被删除了是不会有人再重新拉起这个Pod的。
需要注意的是:自愈机制不能保证这个Pod还在当前机器上被重启!
通过下面这个图我们再来看看由Deployment管理的Pod的自愈能力:
2、Deployment升级应用
我们知道Pod中运行的是我们的应用程序,那如果我们对应用程序进行了修改,要怎么做到零停机的更新呢?Deployment可以实现这一操作。
2.1 通过Yaml创建Deployment
这里我们首先创建一个Deployment,部署一组Pod。
apiVersion: apps/v1
kind: Deployment
metadata:
name: dep-v2 ### 遵循域名编写规范
namespace: default
labels:
app: test-01
### 期望状态
spec:
replicas: 5 ### 期望副本你数量
selector: ### 选择器,指定Deployment要控制的所有的Pod的共同标签(即帮助Deployment筛选他要控制哪些Pod)
matchLabels:
pod-name: ppp ### 和模板template里面的Pod的标签必须一样
### 编写Pod
template:
metadata: ### Pod的metadata
labels:
pod-name: ppp
spec:
containers:
- name: nginx-01
image: nginx
部署:kubectl apply -f dep-v2.yaml --record (–record选项会记录历史版本号,后面会有用)
2.2 Deployment和ReplicaSet、Pod的关系
创建一个Deployment会产生三个资源:Deployment资源、ReplicaSet资源(RS只提供了副本数量的控制功能)、Pod资源。
他们之间的关系是:Deployment控制RS、RS创建和管理Pod
这里可能有一个疑问,为什么非得由Deployment来控制ReplicaSet呢?其实他是为了方便后面的滚动升级的过程。
我们在创建一个Deployment后可以发现,由他产生的5个Pod名称中均包含一串额外的数字,这是什么?
这个数字实际上对应Deployment和ReplicaSet中的Pod模板的哈希值。Pod是由ReplicaSet管理的,所以我们再看看ReplicaSet的样子
我们发现ReplicaSet的名称中也包含了其Pod模板的哈希值。后面会说到**同一个Deployment会创建多个ReplicaSet,用来对应和管理多组不同版本的Pod模板。**像这样使用Pod模板的哈希值,就可以让Deployment始终对给定版本的Pod模板创建相同的ReplicaSet。
2.3 升级Deployment
Deployment的升级策略有两种
-
滚动更新(RollingUpdate),这是默认的升级策略
该策略会逐渐地删除旧的Pod,与此同时创建新的Pod,使应用程序在整个升级过程中都处于可用状态,并确保其处理请求的能力没有因为升级而有所影响。
-
重新创建(Recreate)
该策略在会一次性删除所有旧版本的Pod,之后才开始创建新的Pod。如果你的应用程序不支持多个版本同时对外提供服务,需要在启动新版本之前完全停用旧版本,那么需要使用这种策略。但是使用这种策略的话,会导致应用程序出现短暂的不可用。
有一点我们需要了解:仅当Deployment中定义的Pod模板(即
.spec.template
)发生改变时,那么Deployment会使用更新后的Pod模板来创建新的实例,同时会删除旧的Pod。
- 如果仅仅是修改Deployment的副本数等是不会触发Deployment的升级动作的。
- 每次滚动升级后产生的新的Pod是会由一个新的RS进行控制,所以一个Deployment可能会对应多个RS。而旧的RS仍然会被保留,这个旧的RS会在下面回滚的时候被用到!
2.4 回滚Deployment
比如我使用V1版本的镜像部署了第一版本的Deployment,然后再对其进行升级,改为V2版本的镜像。但是这时V2版本的镜像存在一个BUG,这个BUG还不是那么好改。为了不让用户感知到升级导致的内部服务器错误,尽快修复问题。我们可以采用回滚,让他先回滚到V1版本,正常为用户提供服务,然后我们再下来慢慢修改V2版本,修改完后重新升级Deployment。
回滚命令(默认回滚到上一个版本)
kubectl rollout undo deployment dep-v2
查看滚动升级历史
kubectl rollout history deployment dep-v2
注意,我们前面在部署Deployment时在命令后面加了个 --record 参数,这样会在版本历史中的CHANGE-CAUSE栏记录部署信息,加上这个 --record参数主要是为了方便用户辨别每次版本做了哪些修改。
回滚到指定的Deployment版本
通过在undo命令中指定一个特定的版本号,便可以回滚到特定的版本。
例如,想要回滚到第一个版本: kubectl rollout undo deployment dep-v2 --to-revision=1
2.5 滚动更新的原理
创建新的RS,准备就绪后,替换旧的RS,最后旧的RS会被保留!
回滚升级之所以这么快能完成,主要是因为Deployment始终保持着升级的版本历史记录。这个历史版本号实际上是保存在ReplicaSet中的(每个RS都用特定的版本号来保存Deployment不同版本的完整信息),滚动升级成功后,老版本的ReplicaSet也不会被删掉,这也使得回滚操作可以回滚到任何一个历史版本,而不仅仅是上一个版本。
我们可以通过指定Deployment的
revisionHistoryLimit
属性来限制一个Deployment所能保存的ReplicaSet(历史版本)数量。
2.6 Deployment升级的一些属性
spec.strategy
:指定新Pod替换旧Pod的策略
spec.strategy.type
:指定替换策略,有两个属性值
- Recreate:重新创建。将之前的Pod直接先杀死再重新创建Pod(这种方式不推荐)
- RollingUpdate:滚动更新。如果选择的这个属性,则可以通过
spec.strategy.rollingUpdate
指定滚动更新策略
spec.strategy.rollingUpdate
:指定滚动更新速率下面这两个属性用于控制Deployment的滚动升级的速率。他们可以决定在Deployment滚动升级期间一起可以替换多少个Pod。
spec.strategy.rollingUpdate.maxSurge
:指定除Deployment期望的副本数外,最多允许创建超出的Pod的数量,可以指定百分比,也可以指定数字spec.strategy.rollingUpdate.maxUnavailable
:指定在滚动升级期间,最多可以允许有多少个Pod不可用(反过来说就是保证集群中至少有多少Pod是可以处理请求的)示例:
apiVersion: apps/v1 kind: Deployment metadata: name: mydeploy namespace: default labels: dep: test spec: strategy: type: RollingUpdate ### 滚动更新 rollingUpdate: ### 下面这个设置的含义是:在滚动更新时,新创建的Pod数量最多为副本数量的20%个,杀死的Pod数量最多不能超过2个 maxUnavailable: 2 maxSurge: 20% replicas: 10 selector: matchLabels: pod-name: zaq ### 和模板template里面的pod的标签必须一样 template: metadata: labels: pod-name: zaq spec: containers: - name: nginx-test image: nginx
3、Deployment怎么关联Pod?
我们前面也提到了Deployment不直接管理Pod,而是间接的通过ReplicaSet来管理Pod。那么ReplicaSet是怎么知道哪些Pod是需要他来进行管理的呢?
答:他通过标签选择器来选择具有特定标签的Pod
3.1 ReplicaSet的更富表达力的标签选择器
由于创建一个Deployment会默认创建一个RS,所以这里直接创建一个Deployment,在这个Deployment中我们可以指定标签选择器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
namespace: default
labels:
test: zaq
spec:
selector:
matchExpressions: ### 注意这里是匹配表达式而不是匹配标签
- key: test ### 该选择器要求该Pod包含名为 test 的标签
operator: In
values:
- zaq ### 标签的值必须是zaq
replicas: 2
template:
metadata:
labels:
test: zaq
spec:
containers:
- name: nginx
image: nginx
我们发现,新的标签选择器(matchExpressions)可以采用额外的表达式(这是ReplicationController所不能实现的)。
operator的四个值:
-
In:上面key的值必须与下面其中一个指定的values匹配。
-
NotIn:上面key的值与下面任何指定的values不匹配。
-
Exists:Pod必须包含一个指定名称的标签(这里指的是必须要有指定名称的key,values值是什么不重要),使用此运算符时,不应指定values字段。
matchExpressions: - key: test operator: Exists
-
DoesNotExist:Pod不得包含指定名称的标签。values属性不得指定。
如果指定了多个表达式,则所有这些表达式都必须为true才能使选择器与Pod匹配。如果同时指定matchLabels和matchExpressions,则所有标签都必须匹配,并且所有表达式必须计算为true以使该Pod与选择器匹配。
4、动态扩缩容(HPA)
5、蓝绿部署
蓝绿部署:即系统存在两个版本,一个绿版本(正常),一个蓝版本(等待验证的版本)。如果蓝版本经过反复的测试、修改、验证,确定达到上线标准之后,则可以将流量请求切换到蓝版本,绿版本不接受请求,但是还依然存在。如果蓝版本在生产环境出现了问题,则可以立刻将请求转为绿版本。当确定蓝版本可以稳定正常运行时,就可以将原来的绿版本进行销毁,释放资源,然后蓝版本变为绿版本。(注意,蓝绿版本的转换只是我们人为的这么定义的,而并不是说需要配置什么东西才能实现他们角色的转换。)
6、金丝雀部署
金丝雀部署:
金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。
金丝雀部署的意义在于,同时存在两个版本V1和V2,V1版本和V2版本都能接收到流量请求,但是V2刚开始只能接收到少量的请求,所以这时候V2版本也不需要部署到多台机器上。当到达V2版本的请求都能成功处理了,他不存在任何BUG了,那逐步开始增加V2版本的部署,移除V1版本的部署,让更多的流量请求来到V2版本。直到彻底消除V1版本。
蓝绿部署和金丝雀部署的区别:
蓝绿部署的方式两个版本同时存在,但是流量只会发送到一个版本上,而金丝雀部署的方式,两个版本同时存在,流量请求也会发送到这两个版本上。
注意:金丝雀部署和滚动发布是不一样的。
滚动发布的缺点?
他和金丝雀部署的相同点是,滚动发布也同时存在两个版本,都能接收流量。但是他没法控制流量
滚动发布短时间就直接结束,不能直接控制新老版本的存活时间。
6.1 金丝雀部署案例
这里涉及到了Service的概念,可以学完Service再来看这里。
-
准备一个Service
apiVersion: v1 kind: Service metadata: name: canary-test namespace: default spec: selector: app: canary-nginx type: NodePort ports: - name: canary-test port: 80 targetPort: 80 ### 指Pod的访问端口 protocol: TCP nodePort: 8888 ### 机器上开的端口,客户端访问的就是这个端口
-
准备版本v1的Deployment
apiVersion: apps/v1 kind: Deployment metadata: name: canary-dep-v1 namespace: default labels: app: canary-dep-v1 spec: selector: matchLabels: app: canary-nginx version: v1 replicas: 3 template: metadata: labels: app: canary-nginx version: v1 spec: containers: - name: nginx image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/nginx-test:env-msg ### 这个版本的nginx访问页会输出 1111111
-
准备版本v2的Deployment
apiVersion: apps/v1 kind: Deployment metadata: name: canary-dep-v2 namespace: default labels: app: canary-dep-v2 spec: selector: matchLabels: app: canary-nginx version: v2 replicas: 1 # 先让v2版本只有一个副本 template: metadata: labels: app: canary-nginx version: v2 spec: containers: - name: nginx image: nginx ### v2版本使用默认的nginx,主要是为了打印出默认页面,和v1版本的做区分罢了
-
kubectl apply -f 上面的资源后,接下来访问:主机ip + 8888,我们可以发现3/4的请求会分配到v1版本。
-
这时候我们再调大v2版本的副本数。
-
我们发现v2版本基本上稳定了,没什么问题,这时候就可以删除v1版本:kubectl delete -f canary-dev-v1.yaml
-
这时候v2版本就 上线成功了!
-
这些复杂的步骤到时候会放在DevOps流水线中自动完成的
四、StatefulSet
学习此处需要先了解Volume和Service的概念,所以之后回过来再看这里。
五、DaemonSet
Deployment用于在Kubernetes集群中部署特定数量的Pod,而且他还不能保证把Pod部署到特定节点上。如果我们现在需要在集群中的所有节点上都仅部署一个Pod,怎么办?
1、使用DaemonSet在每个节点上部署一个Pod
DaemonSet控制器的作用就是确保所有的(或者一部分)节点都运行了一个指定的Pod副本。
上面提到一部分,是通过pod模板中的nodeSelector属性指定的。
- 它不存在副本数的概念。因为他的工作是确保一个Pod在每个节点(或特定节点)都存在一份。
- 如果一个节点中通过DaemonSet部署的Pod被删除了,那么他会在该节点上重启一个新的Pod。
- 如果某个节点宕机,DaemonSet不会在其他机器上重新部署一个新的Pod。
- 当一个新的节点被加入到集群中,DaemonSet会立刻部署一个新的Pod到这个新加入的节点上。
2、DaemonSet 的典型使用场景有:
- 在每个节点上运行集群的存储守护进程,例如 glusterd、ceph
- 在每个节点上运行日志收集守护进程,例如 fluentd、logstash
- 在每个节点上运行监控守护进程,例如 Prometheus Node Exporter、Sysdig Agent、collectd、Dynatrace OneAgent、APPDynamics Agent、Datadog agent、New Relic agent、Ganglia gmond、Instana Agent 等
3、使用DaemonSet只在特定的节点上运行Pod
DaemonSet将Pod部署到集群中的所有节点上,除非指定这些Pod只在部分节点上运行。这是通过Pod模板中的nodeSelector属性指定的。
创建一个简单的DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ssd-monitor
spec:
selector:
matchLabels:
app: ssd-monitor
template:
metadata:
labels:
app: ssd-monitor
spec:
nodeSelector: ### 表示只会在有disk:ssd的节点上部署该Pod
disk: ssd
containers:
- name: main
image: luksa/ssd-monitor
kubectl apply -f test-daemonset.yaml
上面DaemonSet描述的意思是:该DaemonSet只会在标有disk:ssd标签的节点上部署一个Pod。
如果我们这时候再给Node2节点打上disk=app标签,那么这时就会在Node2上启动一个Pod。
如果我们这时候将Node3节点上的disk=app标签移除,那么这时Node3节点上的Pod就会被删除。
六、Job、CronJob
前面,通过Deployment、StatefulSet、DaemonSet创建的Pod都是一个持续运行的Pod,如果我们遇到一个只想运行完工作后就终止的情况怎么办?
1、Job
Kubernetes中的 Job 对象将创建一个或多个 Pod,并确保指定数量的 Pod 可以成功执行到进程正常结束:
- 当 Job 创建的 Pod 执行成功并正常结束时,Job 将记录成功结束的 Pod 数量
- 当成功结束的 Pod 达到指定的数量时,Job 将完成执行
- 删除 Job 对象时,将清理掉由 Job 创建的 Pod
- 在节点发送故障时,该节点上由Job管理的Pod将会被在其他的节点上被重新拉起。
1.1 一个简单的Job
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
completions: 4 ### 期望Pod成功运行的次数
template:
spec:
containers:
- name: pi
image: perl ### 注意:用于执行job的镜像都必须是非阻塞的,也就是容器启动后执行完会自己退出(如果用了nginx镜像,那么他会在执行第一次时就一直阻塞着)
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never #Job情况下,不支持Always
backoffLimit: 4 #任务4次都没成功就认为Job是失败的
activeDeadlineSeconds: 10
这里需要特别说明的是:在一个Pod的定义中,可以指定在容器中运行的进程结束时,K8s会做什么,这是通过Pod配置的属性restartPolicy完成的,默认是Always。Job的Pod是不能使用这个默认策略的,因为他们并不是要无限期地运行。所以,需要明确指定Job的Pod的restartPolicy为OnFailure或Never。
2、CronJob
CronJob 按照预定的时间计划(schedule)创建 Job
一个 CronJob 在时间计划中的每次执行时刻,都创建 大约 一个 Job 对象。这里用到了 大约 ,是因为在少数情况下会创建两个 Job 对象,或者不创建 Job 对象。尽管 K8S 尽最大的可能性避免这种情况的出现,但是并不能完全杜绝此现象的发生。因此,Job 程序必须是幂等的。
当以下两个条件都满足时,Job 将至少运行一次:
startingDeadlineSeconds
被设置为一个较大的值,或者不设置该值(默认值将被采纳)concurrencyPolicy
被设置为Allow
2.1 一个简单的CronJob
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *" #分、时、日、月、周。他是基于 master 所在时区来进行计算的。
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
未完,待续>>>
参考:Kubernetes in Action
转载:https://blog.csdn.net/ysf15609260848/article/details/125809203