上一篇文章中,我们详细介绍了 Kubernetes 中的作业副本控制器 Deployment:
对于 Deployment 来说,每一种 Pod 都是完全一样的,因此,Deployment 可以按照配置任意增删任何一个 Pod,但在我们的实际场景中,情况并非总是如此,多个实例之间往往会有着复杂的依赖关系,比如主从、主备关系等等。这些情况下,实例之间的地位是不对等的,这样的应用就被称为“有状态应用”。
容器的解决方案是针对无状态应用场景的最佳实践,但对于有状态应用来说,就并非如此了。Kubernetes 用 StatefulSet 解决了有状态应用编排的问题,本文我们就来初步认识一下 StatefulSet。
StatefulSet 将应用设计抽象为了两种状态:
应用存在多个实例,但多个实例地位并不完全对等。
应用的多个实例必须按照某种顺序启动,并且必须成组存在,例如一个应用中必须存在一个 A Pod 和两个 B Pod,且 A Pod 必须先于 B Pod 启动的场景。
应用存在多个实例,但每个实例绑定的存储数据不同,那么对于一个 Pod 来说,无论它是否被重新创建,它读到的数据状态应该是一致的。
一个最简单的场景,我们用一个 nginx Headless Service 反向代理 Kubernetes 中的两个 Pod,并且这两个 Pod 具有不完全对等的网络身份,这个情况下,就是典型的拓扑状态下的 StatefulSet 的使用场景。
Kubernetes 的 Service 就是对外提供的可访问服务,它有两种访问方式:
下面的配置文件配置了一个 Headless 方式启动的 nginx Service:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
这个配置中,clusterIP 设置为了 None,表示不为这个 Service 分配 VIP,而是通过 Headless DNS 的方式来处理该 Service 的调用。
具体的 DNS 地址是这样的:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个 DNS 就是 Kubernetes 为 Pod 分配的唯一可解析身份,这样一来,只要有了 Pod 的名字和 Service 的名字,我们就能唯一确定一个能够访问这个 Pod 的 DNS 地址了。
下面,我们就来配置一个使用上述 nginx Service 的 StatefulSet:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web
这个 yaml 与 Deployment 的配置的唯一区别就是多了一个 ServiceName=nginx 的配置,它意味着 StatefulSet 控制器必须在执行控制循环时使用 nginx 这个 Service 来保证每一个 Pod 可解析。
通过 kubectl get 命令就可以查看 pod 的启动:
$ kubectl get statefulset web
通过命令 kubectl get pods -w -l app=nginx
可以看到这些 pod 的启动顺序,每一个 pod 都被命名为了 web-<编号>,比如 web-0 与 web-1,他们是严格按照编号顺次启动的。
如果我们通过 kubectl delete pod -l app=nginx
,再通过 kubectl get 观察,就可以看到两个 Pod 删除后,Kubernetes 会按照原先的编号和顺序再次启动一组新的 pod,并且他们各自的网络身份与原 Pod 是一一对应的。
通过上述实践,我们看到,只要我们使用 DNS 记录来访问 StatefulSet 控制器控制下的 Pod,即使 Pod 发生了宕机和重启,DNS 记录对应的 nginx 记录本身是不会发生变化的,同一个“名字-编号”组合的 Pod 在 StatefulSet 中总是稳定地对外提供服务的,进而实现了整个“网络状态”的稳定。