1 StatefulSet解决的问题
对于kubernetes中的应用,如果同一个deployment中的pod存在依赖,或者对于数据存储应用往往有多个实例,但每个实例都会在本地保存一份数据,如果应用实例重建,
那么实例与本地数据的对应关系会丢失。这种实例的不对等关系以及对外部数据有依赖的应用,我们称之为“有状态的应用”。
对于这种“有状态的应用”如何管理,StatefulSet为这个问题提供了解决办法。
2 StatefulSet功能
StatefulSet把“有状态的应用”抽象为了两种情况:
- 拓扑状态,应用之间不对等,启动有先后顺序
- 存储状态,一个数据库应用的多个实例
StatefulSet核心功能就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。
3 StatefulSet之拓扑状态
3.1 原理
Service 将一组Pod 暴露给外界访问,那么Service又改如何被访问呢?
-
以 Service 的 VIP(Virtual IP,即:虚拟 IP)方式
-
以 Service 的 DNS 方式。比如:这时候,只要我访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。可以分为两种处理方法:
-
一是 Normal Service。这种情况下,你访问“my-svc.my-namespacece.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致了。
-
二是 Headless Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是my-svc 代理的某一个 Pod 的 IP 地址。
Normal Service和Headless Service 的区别是,后者不需要分配一个 VIP,可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。
-
Headless Service,其实仍是一个标准Service的YAML文件。只不过,它的 clusterIP 字段的值是:None,没有VIP。
3.2 实例
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
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
执行操作:
kubectl create -f statefulset.yaml -n cfd
# 查看过程
kubectl get pod -w -l app=nginx -n cfd
# 创建一个pod,用于访问web pod
kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh -n cfd
# 查看dns是否正常
nslookup web-0.nginx
nslookup web-1.nginx
# 删除pod,查看启动过程
kubectl delete pod -l app=nginx -n cfd
kubectl get pod -w -l app=nginx -n cfd
4 StatefulSet之存储状态
StatefulSet对存储状态的管理,主要使用的是Persistent Volume Claim的功能,同时也避免了用户名、授权文件位置等信息的暴露。
对于一个pvc对象,Kubernetes会自动为它绑定一个符合条件的Volume,这个Volume一般由运维人员维护。对于PVC和PV的设计,类似于“接口”和“实现”的思想。
4.1 实例
PV的YAML文件如下:
kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-volume
labels:
type: local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
rbd:
monitors:
- '10.16.154.78:6789'
- '10.16.154.82:6789'
- '10.16.154.83:6789'
pool: kube
image: foo
fsType: ext4
readOnly: true
user: admin
keyring: /etc/ceph/keyring
imageformat: "2"
imagefeatures: "layering"
使用PVC的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
执行的操作:
kubectl create -f statefulset.yaml
# pvc 以“pvc名字-statefulset名字-编号”的方式命名
kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s
# 在volume中写入index.html文件
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done
# 访问nginx服务器,返回index.html的内容
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done
hello web-0
hello web-1
# 删除pod
kubectl delete pod -l app=nginx
# 在被重新创建出来的 Pod 容器里访问 http://localhost
$ kubectl exec -it web-0 -- curl localhost
hello web-0
4.2 原理
根据以上的实例发现,在pod删除时,并没有删除Pod对应的PVC和PV,而Volume写入的数据也依然保存在远程存储服务中。
基于以上的现象,我们得出StatefulSet管理存储状态的原理如下所示:
-
- StatefulSet的控制器直接管理的是Pod,StatefulSet会在每个Pod实例后加一个编号,用于区分实例的不同;
-
- Kubernetes通过Headless Service,为这些有编号的Pod,在DNS服务器中生成带同样编号的DNS记录;
-
- StatefulSet为每个pod创建一个同样编号的PVC,这样Kubernetes通过PV机制为每个PVC绑定上对应的PV,保证每一个Pod一直拥有同一个独立的Volume。