有状态服务 VS 无状态服务
一般来说,业务的服务类型可分为无状态服务和有状态服务。举个简单的例子,像打网络游戏这类的服务,就是有状态服务,而正常浏览网页这类服务一般都是无状态服务。其实判断两种请求的关键在于,两个来自相同发起者的请求在服务器端是否具备上下文关系。
- 如果是有状态服务,其请求是状态化的,服务器端需要保存请求的相关信息,这样每个请求都可以默认地使用之前的请求上下文。
- 而无状态服务就不需要这样,每次请求都包含了需要的所有信息,每次请求都和之前的没有任何关系。
有状态服务和无状态服务分别有各自擅长的业务类型和技术优势,在 Kubernetes 中,分别有不同的工作负载控制器来负责承载这两类服务。
Kubernetes 中的无状态工作负载
Kubernetes 中各个对象的 metadata 字段都有 label(标签)和 annotation(注解) 两个对象,可以用来标识一些元数据信息。不论是 label 还是 annotation,都是一组键值对,键值不允许相同。
- annotation 主要用来记录一些非识别的信息,并不用于标识和选择对象。
- label 主要用来标识一些有意义且和对象密切相关的信息,用来支持labelSelector(标签选择器)以及一些查询操作,还有选择对象。
为了让这种抽象的对象可以跟 Pod 关联起来,Kubernetes 使用了labelSelector来跟 label 进行选择匹配,从而达到这种松耦合的关联效果。
$ kubectl get pod -l label1=value1,label2=value2 -n my-namespace
比如可以通过上述命令,查询出 my-namespace 这个命名空间下面,带有标签label1=value1和label2=value2的 pod。label 中的键值对在匹配的时候是“且”的关系。
ReplicationController
Kubernetes 中有一系列的工作负载可以用来部署无状态服务。在最初,Kubernetes 中使用ReplicationController来做 Pod 的副本控制,即确保该服务的 Pod 数量维持在特定的数量。为了简洁并便于使用ReplicationController通常缩写为“rc”,并作为 kubectl 命令的快捷方式,例如:
$ kubectl get rc -n my-namespace
如果副本数少于预定的值,则创建新的 Pod。如果副本数大于预定的值,就删除多余的副本。因此即使你的业务应用只需要一个 Pod,你也可以使用 rc 来自动帮你维护和创建 Pod。
ReplicaSet
随后社区开发了下一代的 Pod 控制器 ReplicaSet(可简写为 rs) 用来替代
ReplicaController。虽然 ReplicaController
目前依然可以使用,但是社区已经不推荐继续使用了。这两者的功能和目的完全相同,但是 ReplicaSet 具备更强大的基于集合的标签选择器,这样你可以通过一组值来进行标签匹配选择。目前支持三种操作符:in、notin和exists。
例如,你可以用environment in (production, qa)来匹配 label 中带有environment=production或environment=qa的 Pod。
同样你也可以使用tier notin (frontend,backend)来匹配 label 中不带有tier=frontend或tier=backend的 Pod。
或者你可以用 partition来匹配 label 中带有 partition 这个 key 的 Pod。
了解了标签选择器,我们就可以通过如下的 kubectl 命令查找 Pod:
kubectl get pods -l environment=production,tier=frontend
或者使用:
kubectl get pods -l 'environment in (production),tier in (frontend)'
虽然 Replicaset 可以独立使用,但是为了能够更好地协调 Pod 的创建、删除以及更新等操作,我们都是直接使用更高级的 Deployment来管理 Replicaset,社区也是一直这么定位和推荐的。比如一些业务升级的场景,使用单一的 ReplicaController 或者 Replicaset 是无法实现滚动升级的诉求,至少需要定义两个该对象才能实现,而且这两个对象使用的标签选择器中的 label 至少要有一个不相同。通过不断地对这两个对象的副本进行增减,也可以称为调和(Reconcile),才可以完成滚动升级。这样使用起来不方便,也增加了用户的使用门槛,极大地降低了业务发布的效率。
Deployment
通过 Deployment,我们就不需要再关心和操作 ReplicaSet 了。
Deployment、ReplicaSet 和 Pod 这三者之间的关系见上图。通过 Deployment,我们可以管理多个 label 各不相同的 ReplicaSet,每个 ReplicaSet 负责保证对应数目的 Pod 在运行。
我们来看一个定义 Deployment 的例子:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-demo
namespace: demo
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
我们这里定义了副本数spec.replicas为 3,同时在spec.selector.matchLabels中设置了app=nginx的 label,用于匹配spec.template.metadata.labels的 label。我们还增加了version=v1的 label 用于后续滚动升级做比较。
我们将上述内容保存到deploy-demo.yaml文件中。
注意,spec.selector.matchLabels中写的 label 一定要能匹配得了spec.template.metadata.labels中的 label。否则该 Deployment 无法关联创建出来的 ReplicaSet。
然后我们通过下面这些命令,便可以在命名空间 demo 中创建名为 nginx-deployment-demo 的 Deployment。
$ kubectl create ns demo
$ kubectl create -f deploy-demo.yaml
deployment.apps/nginx-deployment-demo created
# 创建完成后,我们可以查看自动创建出来的 rs。
$ kubectl get rs -n demo
NAME DESIRED CURRENT READY AGE
nginx-deployment-demo-5d65f98bd9 3 3 0 5s
接下来,我们可以通过如下的几个命令不断地查询系统,看看对应的 Pod 是否运行成功。
$ kubectl get pod -n demo -l app=nginx,version=v1
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 0/1 ContainerCreating 0 30s
nginx-deployment-demo-5d65f98bd9-d78fx 0/1 ContainerCreating 0 30s
nginx-deployment-demo-5d65f98bd9-ssstk 0/1 ContainerCreating 0 30s
$ kubectl get pod -n demo -l app=nginx,version=v1
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 1/1 Running 0 63s
nginx-deployment-demo-5d65f98bd9-d78fx 1/1 Running 0 63s
nginx-deployment-demo-5d65f98bd9-ssstk 1/1 Running 0 63s
我们可以看到,从 Deployment 到这里,最后的 Pod 已经创建成功了。
现在我们试着做下镜像更新看看。更改spec.template.metadata.labels中的version=v1为version=v2,同时更新镜像nginx:1.14.2为nginx:1.19.2。
你可以直接通过下述命令来直接更改:
$ kubectl edit deploy nginx-deployment-demo -n demo
# 然后运行这些命令:
$ kubectl apply -f deploy-demo.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/nginx-deployment-demo configured
# 这个时候,我们来看看 ReplicaSet 有什么变化:
$ kubectl get rs -n demo
NAME DESIRED CURRENT READY AGE
nginx-deployment-demo-5d65f98bd9 3 3 3 4m10s
nginx-deployment-demo-7594578db7 1 1 0 3s
可以看到,这个时候自动创建了一个新的 rs 出来:nginx-deployment-demo-7594578db7。一般 Deployment 的默认更新策略是 RollingUpdate,即先创建一个新的 Pod,并待其成功运行以后,再删除旧的。
还有一个更新策略是Recreate,即先删除现有的 Pod,再创建新的。关于 Deployment,我们还可以设置最大不可用(maxUnavailable)和最大增量(maxSurge),来更精确地控制滚动更新操作,具体配置可参照这个文档。
我建议你使用默认的策略来保证可用性。后面 Deployment 控制器会不断对这两个 rs 进行调和,直到新的 rs 副本数为 3,老的 rs 副本数为 0。
我们可以通过如下命令,能够观察到 Deployment 的调和过程。
$ kubectl get pod -n demo -l app=nginx,version=v1
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 1/1 Running 0 4m15s
nginx-deployment-demo-5d65f98bd9-d78fx 1/1 Running 0 4m15s
nginx-deployment-demo-5d65f98bd9-ssstk 1/1 Running 0 4m15s
$ kubectl get pod -n demo -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 1/1 Running 0 4m22s
nginx-deployment-demo-5d65f98bd9-d78fx 1/1 Running 0 4m22s
nginx-deployment-demo-5d65f98bd9-ssstk 1/1 Running 0 4m22s
nginx-deployment-demo-7594578db7-zk8jq 0/1 ContainerCreating 0 15s
$ kubectl get pod -n demo -l app=nginx,version=v2
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-7594578db7-zk8jq 0/1 ContainerCreating 0 19s
$ kubectl get pod -n demo -l app=nginx,version=v2
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-7594578db7-4g4fk 0/1 ContainerCreating 0 1s
nginx-deployment-demo-7594578db7-zk8jq 1/1 Running 0 31s
$ kubectl get pod -n demo -l app=nginx,version=v1
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 1/1 Running 0 4m40s
nginx-deployment-demo-5d65f98bd9-d78fx 1/1 Running 0 4m40s
nginx-deployment-demo-5d65f98bd9-ssstk 1/1 Terminating 0 4m40s
$ kubectl get pod -n demo -l app=nginx,version=v2
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-7594578db7-4g4fk 1/1 Running 0 5s
nginx-deployment-demo-7594578db7-ftzmf 0/1 ContainerCreating 0 2s
nginx-deployment-demo-7594578db7-zk8jq 1/1 Running 0 35s
$ kubectl get pod -n demo -l app=nginx,version=v1
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-5d65f98bd9-7w5gp 0/1 Terminating 0 4m52s
nginx-deployment-demo-5d65f98bd9-ssstk 0/1 Terminating 0 4m52s
$ kubectl get pod -n demo -l app=nginx,version=v2
NAME READY STATUS RESTARTS AGE
nginx-deployment-demo-7594578db7-4g4fk 1/1 Running 0 17s
nginx-deployment-demo-7594578db7-ftzmf 1/1 Running 0 14s
nginx-deployment-demo-7594578db7-zk8jq 1/1 Running 0 47s
$ kubectl get rs -n demo
NAME DESIRED CURRENT READY AGE
nginx-deployment-demo-5d65f98bd9 0 0 0 5m5s
nginx-deployment-demo-7594578db7 3 3 3 58s
$ kubectl get pod -n demo -l app=nginx,version=v1
No resources found in demo namespace.
或者可以通过 kubectl 的 watch 功能:
$ kubectl get pod -n demo -l app=nginx -w
至此,我们完成了 Deployment 的升级过程。