# 什么是StatefulSet?
是用来创建有状态应用,可以通过过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。
什么是有状态应用?
首先是需要有数据的持久化,即使Pod被重启后,也能恢复,与重启前保持一致。然后是应用创建的所有pod有依赖关系,顺序的创建、需要运行在指定的宿主机上,并且都有对应的网络标志。
应用场景?
分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。
# 使用StatefulSet
# 创建StatefulSet
创建yaml文件定义StatefulSet对象如下,与Deployment比较,多了一个serviceName字段,这个是用来指定StatefulSet所管理的pod是用域名访问是通过该service所设定的。
代码语言:javascript复制apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-sts
spec:
serviceName: redis-svc
replicas: 2
selector:
matchLabels:
app: redis-sts
template:
metadata:
labels:
app: redis-sts
spec:
containers:
- image: redis
name: redis
ports:
- containerPort: 6379
创建StatefulSet对象
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl apply -f statefulset.yaml -n zwf
statefulset.apps/redis-sts created
[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME READY AGE
redis-sts 2/2 54s
查看创建的Pod会发现,命名不再是随机创建的名字,而是有了顺序号,从0开始,而k8s也会按照这个顺序一次创建。
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME READY STATUS RESTARTS AGE
redis-sts-0 1/1 Running 0 61s
redis-sts-1 1/1 Running 0 54s
输出pod中的hostname发现与pod的名称也保持一致,也就是应用可以自行决定依赖关系,比如该例子中可以使用0号pod作为主实例,而1号pod作为从实例。
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- hostname
redis-sts-0
# Service配置
定义匹配上面的创建StatefulSet对象所有管理的Service,也就是标签筛选需要和pod的标签保持一致,并且这里的metadata.name也要与StatefulSet中的serviceName一样。
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: redis-svc
spec:
selector:
app: redis-sts
ports:
- port: 6379
protocol: TCP
targetPort: 6379
创建Service对象,我们可以看到已经将StatefulSet所创建的pod加入到端点列表了,也就是可以稳定的通过Service来访问到Pod
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl apply -f service_statefulset.yaml -n zwf
kservice/redis-svc created
[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis-svc ClusterIP 10.0.0.246 <none> 6379/TCP 5s
[root@k8s-worker1 zwf]# kubectl describe svc redis-svc -n zwf
Name: redis-svc
Namespace: zwf
Labels: <none>
Annotations: <none>
Selector: app=redis-sts
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.0.0.246
IPs: 10.0.0.246
Port: <unset> 6379/TCP
TargetPort: 6379/TCP
Endpoints: 10.222.126.51:6379,10.222.194.84:6379
Session Affinity: None
Events: <none>
但是我们的Pod不是一般的的应用,是有状态的应用,需要有稳定的网络标识,所以会为每一个Pod也创建一个域名,格式是:<podName>.<serviceName>.<namesapce>.svc.cluster.local
。
我们进入pod中验证一下,通过ping redis-sts-1.redis-svc.zwf.svc.cluster.local
发现是可以ping通的,虽然Pod的IP会变化,但是通过固定的域名就能访问到指定Pod了。
[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- ping redis-sts-1.redis-svc.zwf.svc.cluster.local
PING redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51) 56(84) bytes of data.
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=1 ttl=62 time=0.964 ms
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=2 ttl=62 time=0.932 ms
既然我们的pod有了稳定的网络标识,Service也就不需要再分配ClusterIP了,这个时候,只需要添加字段clusterIP: None,这样就不会再分配IP了,这样的Service称为Headless Service
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: redis-svc
spec:
clusterIP: None
selector:
app: redis-sts
ports:
- port: 6379
protocol: TCP
targetPort: 6379
创建Headless Service,可以看到CLUSTER-IP为None
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demoapp-nodeport-svc NodePort 10.0.0.144 <none> 80:31999/TCP 21h
demoapp-svc ClusterIP 10.0.0.74 <none> 80/TCP 22h
redis-svc ClusterIP None <none> 6379/TCP 4s
Service和StatefulSet配置图如下:
# 持久化配置
接下来是给StatefulSet对象添加持久化配置。
定义StatefulSet描述如下:
代码语言:javascript复制apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-pv-sts
spec:
serviceName: redis-pv-svc
volumeClaimTemplates:
- metadata:
name: redis-100m-pvc
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Mi
replicas: 2
selector:
matchLabels:
app: redis-pv-sts
template:
metadata:
labels:
app: redis-pv-sts
spec:
containers:
- image: redis:5-alpine
name: redis
volumeMounts:
- name: redis-100m-pvc
mountPath: /data
参数:
- volumeClaimTemplates,用来将PVC的定义嵌入到StatefulSet中的字段,是创建PVC的模板,可以让每一个Pod都能自动创建PVC
- voulumeMounts,是用来选择上面的PVC挂载在容器的/data目录中
创建StatefulSet对象,可以看到pod已经创建起来了。
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl apply -f statefulset_pvc.yaml -n zwf
statefulset.apps/redis-pv-sts created
[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME READY AGE
redis-pv-sts 2/2 25
[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME READY STATUS RESTARTS AGE
redis-pv-sts-0 1/1 Running 0 63s
redis-pv-sts-1 1/1 Running 0 54s
我们查看pvc,会发现创建了2个对象 ,以PVC名称-pod名称
命名。
[root@k8s-worker1 zwf]# kubectl get pvc -n zwf
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
redis-100m-pvc-redis-pv-sts-0 Bound pvc-2712daa4-36b4-4a63-ac2f-7d3b31e2a887 100Mi RWX nfs-client 109s
redis-100m-pvc-redis-pv-sts-1 Bound pvc-a3781354-e182-42f7-b6f6-983999603653 100Mi RWX nfs-client 100s
进入到pod中,使用redis-cli运行redis客户端,添加一些数据
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0 -n zwf /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> quit
将pod删除,然后再重新进入Pod中,查询之前创建的redis的key,还是能够查询到。
代码语言:javascript复制[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0 -n zwf -- /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> get age
"18"
配置的关系图如下: