Ladies and gentlemen!在前一篇k8s的文章中,我提了一嘴deployment的弊端,在本篇中将详细讲讲一个更好玩的api对象,叫做StatefulSet
Deployment下的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod。但是,在实际的场景中,并不是所有的应用都可以满足这样的要求。尤其是分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。所以,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)。
下面将从拓扑状态和存储状态依次对StatefulSet进行分析。
为了实现在部署“有状态应用”的时候,应用的每个实例拥有唯一并且稳定的“网络标识”,在创建StatefulSet前往往会先创建一个Headless Service,如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。之后这个service代理的pod我们都可以用dns的方式去访问:<pod-name>.<svc-name>.<namespace>.svc.cluster.local 下面再编写一个简单的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
可以看到与Deployment不同的是多出来了一个serviceName的字段,这正是部署有状态应用的精髓!create后我们将看到如下信息:
创建出的pod名字以StatefulSet+编号的格式出现。如果在create后你手速过快,使用watch进行观看时,你会发现是web-0先进行创建,然后再进行创建web-1。我们可以在集群用dns来访问一下它们
nslookup web-0.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
注意,如果你无法看见正确的dns解析的话,不妨试试nslookup web-0.nginx.default.svc.cluster.local ,我的k8s版本为v1.17.4。
通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。用一句话来说这个过程:StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。
在k8s中提供了一种叫做PV和PVC的机制,它们以“接口”的方式大大简化了持久化存储的过程。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。下面来编写一个类似的yaml文件:
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
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
这里的volumeClaimTemplates 字段是我们着重应该了解的。它会为我们的每一个pod分配一个专属的PVC。些 PVC,都以<PVC名字>- <StatefulSet名字>-<编号>的方式命名.当这个PVC与PV绑定后,即使你现在手误删除了pod,StatefulSet控制器会通过控制循环自动帮你生成新的pod,该pod就会直接找到旧 Pod 遗留下来的同名的 PVC,进而找到跟这个 PVC 绑定在一起的 PV,实现持久化存储。但要注意,在之前需要手动创建PV,但创建PV时会出现一些问题,k8s为我们提供了storageClass来解决一些问题,在之后会讲到。
这么一看,原本非常复杂的 StatefulSet,是不是也很容易理解了呢?