简要介绍:https://www.yuque.com/tiger-swicr/cqu22z/hyi6z1/edit#J7XAk
官网介绍:https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/
必要性
一些应用是“有状态的”:实例之间有不对等关系(主从、主备),以及实例对外部数据有依赖关系。
StatefulSet 的设计其实非常容易理解。它把真实世界里的应用状态,抽象为了两种情况:
1、拓扑状态。这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。
2、存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
Headless
不同于CLusterIP类型的Service:Service服务名 解析为 服务IP,再转发到Pod ip;
Headless类型:Service服务名 解析到所有Pod,不存在服务IP。
#clusterIP: None
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
Pod名格式:$(StatefulSet 名称)-$(序号)
例如web-0、web-1、web-2
Headless<svc-name>.<namespace>.svc.cluster.local
Pod的DNS记录<pod-name>.<svc-name>.<namespace>.svc.cluster.local
参考
深入理解StatefulSet,用Kubernetes编排有状态应用
https://mp.weixin.qq.com/s/y60q0-RMh8isd4u4PuLfUg
DNS记录
https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/
解析出每个Pod IP
进入一个pod,执行 **nslookup Headless记录格式地址**
,会解析出HeadlessService代理的两个Endpoint (Pod)对应的IP。这样客户端就能通过Headless Service拿到每个EndPoint的IP,如果有需要可以自己在客户端做些负载均衡策略。
/app # nslookup app-headless-svc.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: app-headless-svc.default.svc.cluster.local
Address: 10.1.0.38
Name: app-headless-svc.default.svc.cluster.local
Address: 10.1.0.39
Pod之间互相通信
进入其中一个pod,
分别执行nslookup **stat-go-app-0**的DNS域名
,nslookup** stat-go-app-1**的DNS域名
。
结果表明,Headless会为每个Endpoint也就是Pod添加DNS域名解析,这样Pod之间能够相互通信。
如果要用StatefulSet编排一个有主从关系的应用,就可以通过DNS域名访问的方式保证相互之间的通信,即使出现Pod重新调度它在内部的DNS域名也不会改变
/app # nslookup stat-go-app-0.app-headless-svc.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: stat-go-app-0.app-headless-svc.default.svc.cluster.local
Address: 10.1.0.46
/app # nslookup stat-go-app-1.app-headless-svc.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: stat-go-app-1.app-headless-svc.default.svc.cluster.local
Address: 10.1.0.47
作为对比,Deployment类型的,
执行nslookup clusterip的DNS域名
,只会解析出ClusterIP
kubectl exec -it my-go-app-69d6844c5c-gkb6z -- /bin/sh
/app # nslookup app-service.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: app-service.default.svc.cluster.local
Address: 10.108.26.155
执行nslookup <pod的DNS域名>
,则无法直接解析出Pod名对应的IP
/app # nslookup my-go-app-69d6844c5c-gkb6z.app-service.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
** server can't find my-go-app-69d6844c5c-gkb6z.app-service.default.svc.cluster.local: NXDOMAIN
StatefulSet
首先定义一个Headless类型的服务发现
#clusterIP: None
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
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=nginx 字段。
这个字段的作用,就是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。
拓扑状态
# -w 监视整个过程
[root@zm statefulset]# kubectl -n cka get pod -w
NAME READY STATUS RESTARTS AGE
dns-test 1/1 Running 0 4m6s
ready-if-service-ready 1/1 Running 0 4d4h
safari-5f5865b8c4-kn8ph 1/1 Running 0 4d5h
web-0 0/1 ContainerCreating 0 8s
web-0 1/1 Running 0 26s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
发现,StatefulSet类型,启动时,会等到第一个pod变为running,且ready后,再启动第二个pod,以此类推。
#启动一个临时pod。镜像为busybox,注意 不能选择高版本的,nslookup会失败。这是个大坑。
#--rm代表pod退出后就会被删除
#--restart=Never代表永不重启
[root@zm ~]# kubectl -n cka run dns-test --image=busybox:1.28.4 --rm -it --restart=Never -- /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx
Address 1: 10.244.0.16 web-0.nginx.cka.svc.cluster.local
Address 2: 10.244.0.17 web-1.nginx.cka.svc.cluster.local
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.0.16 web-0.nginx.cka.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.0.17 web-1.nginx.cka.svc.cluster.local
Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。
小结:
StatefulSet 如何保证应用实例之间“拓扑状态”的稳定性:
StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。
存储状态
StatefulSet 对存储状态的管理机制。这个机制,主要使用的是一个叫作 Persistent Volume Claim 的功能。
为什么要有PVC ?
如果你并不知道有哪些 Volume 类型可以用,要怎么办呢?更具体地说,作为一个应用开发者,我可能对持久化存储项目(比如 Ceph、GlusterFS 等)一窍不通,也不知道公司的 Kubernetes 集群里到底是怎么搭建出来的,我也自然不会编写它们对应的 Volume 定义文件。
所谓“术业有专攻”,这些关于 Volume 的管理和远程持久化存储的知识,不仅超越了开发者的知识储备,还会有暴露公司基础设施秘密的风险。
Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象,大大降低了用户声明和使用持久化 Volume 的门槛。
开发人员要做的:
第一步:首先定义一个PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pv-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
accessModes: ReadWriteOnce,表示这个 Volume 的挂载方式是可读写,并且只能被挂载在一个节点上而非被多个节点共享。
第二步:在应用的 Pod 中,声明使用这个 PVC:
apiVersion: v1
kind: Pod
metadata:
name: pv-pod
spec:
containers:
- name: pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-storage
volumes:
- name: pv-storage
persistentVolumeClaim:
claimName: pv-claim
这时候,只要我们创建这个 PVC 对象,Kubernetes 就会自动为它绑定一个符合条件的 Volume。可是,这些符合条件的 Volume 又是从哪里来的呢?答案是,它们来自于由运维人员维护的 PV(Persistent Volume)对象。
Kubernetes 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。开发者只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现,即:PV。这种解耦,就避免了因为向开发者暴露过多的存储系统细节而带来的隐患。
此外,这种职责的分离,往往也意味着出现事故时可以更容易定位问题和明确责任,从而避免“扯皮”现象的出现。
小结:
首先,StatefulSet 的控制器直接管理的是 Pod。 这是因为,StatefulSet 里的不同 Pod 实例,不再像 ReplicaSet 中那样都是完全一样的,而是有了细微区别的。比如,每个 Pod 的 hostname、名字等都是不同的、携带了编号的。而 StatefulSet 区分这些实例的方式,就是通过在 Pod 的名字里加上事先约定好的编号。
其次,Kubernetes 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录。 只要 StatefulSet 能够保证这些 Pod 名字里的编号不变,那么 Service 里类似于 web-0.nginx.default.svc.cluster.local 这样的 DNS 记录也就不会变,而这条记录解析出来的 Pod 的 IP 地址,则会随着后端 Pod 的删除和再创建而自动更新。这当然是 Service 机制本身的能力,不需要 StatefulSet 操心。
最后,StatefulSet 还为每一个 Pod 分配并创建一个同样编号的 PVC。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。 在这种情况下,即使 Pod 被删除,它所对应的 PVC 和 PV 依然会保留下来。所以当这个 Pod 被重新创建出来之后,Kubernetes 会为它找到同样编号的 PVC,挂载这个 PVC 对应的 Volume,从而获取到以前保存在 Volume 里的数据。
实践案例:MySQL主从
MySQL,Redis,MongoDB,Kafka,ES等
局限性
官网:
- 给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供。
- 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 当前需要Headless来负责 Pod 的网络标识。你需要负责创建此服务。
- 当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止,可以在删除之前将 StatefulSet 缩放为 0。
- 在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新,可能进入需要人工干预 才能修复的损坏状态。