在前面的学习中,发现做的例子都是 Deployment ,且大部分使用 web 应用去做示例,为什么不用 mysql 类似数据库来做示例呢。
在 LNMP 架构中,应该了解过动静分离,也就是动态数据和静态数据,是以是否需要去访问数据库去进行读写数据来区分的。
Kubernetes 同样也区分了有状态应用和无状态应用,之前常用的 Deployment 就是为无状态应用而使用的。本文中提到的 StatefulSet 就是为有状态应用使用的。
介绍
无状态和有状态的区别
在日常的非容器服务中,需要做负载的服务(如web服务),一般都是无状态的应用,因为每个服务中的数据都一样,只是为了防止一个服务掉了,另一个服务可以继续提供服务。需要做分布式服务(如MySQL、Redis、kafka、etcd等),相同服务不同机器中,数据都是不一样的,这样的服务被叫做有状态服务。
有状态服务在部署时指定多个实例的 ip,且每个实例都有独立数据,这样存储也是个问题。在使用 Deployment 过程中,如果一个有状态服务的 pod 挂掉,就会重新起一个并且 ip 已经变换了。这时还需要人工手动干预服务,修改 ip,显然对于 Deployment 的使用是不现实的,也很笨重,降低效率。
StatefulSet 就可以解决以上的问题。无状态服务适用于 Deployment。有状态服务适用于 StatefulSet
StatefulSet 的特点
- 部署有状态应用
- 解决pod独立生命周期,保持pod启动顺序和唯一性
- 稳定,唯一的网络标识符,持久存储
- 有序,优雅的部署和扩展、删除和终止
- 有序,滚动更新
- 应用场景:分布式应用、数据库集群
有序:其实就是有顺序的部署、启动、升级等等,需要有序的原因就是,如果是主从服务,必然是先部署主,再部署从,有序就是这个意思了
唯一的网络标识符:使用 Headless Service (无头服务,相比普通的 svc,只是将 clusterIP 定义为了 None) 来维护 pod 的网络身份。
因为有状态服务的实例之间不是对等关系,不能使用一个 ip 来负载所有实例
需要在创建 StatefulSet 时指定 svc 的 name
DNS 名称:<StatefulSet_pod_name>.<service-name>.<namespace-name>.svc.cluster.local
无论如何重启该 DNS 都不会改变
持久存储:StatefulSet 的存储卷使用 volumeClaimTemplates 创建,成为卷申请模板。
当使用 volumeClaimTemplates 参数创建一个 PV 时,就会为每个 pod 分配并创建一个 PVC
初步使用
这里使用官方文档中提供的 应用示例 来演示
$ vim statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # Headless service
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx" # 指定要使用 Headless service
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # 将通过 PersistentVolumes 驱动提供的 PersistentVolumes 来提供稳定的存储。Deployment不支持该参数
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ] # 访问模式为只能单个pod挂载使用
storageClassName: "managed-nfs-storage" # 改为自己现有的 storageclass
resources:
requests:
storage: 1Gi
如果没有现有的 storageclass,请参考 Kubernetes—StorageClass
执行文件部署
kubectl apply -f statefulset.yaml
查看 pvc 部署情况:
因为有 3 个 replicas,也因为实例之间的数据要不一样,所以不能用相同的pvc,故而 statefulset 中为每个实例创建了一个 pvc,每个 pod 都有一个独立的存储
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-79144efd-a075-47bc-b43b-2c87a4e6335a 1Gi RWO managed-nfs-storage 101s
www-web-1 Bound pvc-07ddb513-5548-4a8b-8c03-2c9d4afcdf70 1Gi RWO managed-nfs-storage 74s
www-web-2 Bound pvc-c8cad882-d4f6-45da-980e-22b4409e81c9 1Gi RWO managed-nfs-storage 70s
查看 pod 部署情况:从部署时间可以看出,启动顺序依次是 0-2,这是根据 pod_name 后的数字来判断的
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m9s
web-1 1/1 Running 0 2m42s
web-2 1/1 Running 0 2m38s
验证存储是否独立
上面可以看到有三个 pvc,这里对 pvc 对应的存储路径下的文件进行更改,来确认每个 pod 的存储是否独立
因为这里使用的是 NFS 创建的 StorageClass ,所在可以在 NFS 的共享目录下找到对应的 PVC 目录
$ ls /nfsdata/
default-www-web-0-pvc-79144efd-a075-47bc-b43b-2c87a4e6335a default-www-web-2-pvc-c8cad882-d4f6-45da
default-www-web-1-pvc-07ddb513-5548-4a8b-8c03-2c9d4afcdf70
StatefulSet 的部署文件中,是将 /usr/share/nginx/html
目录挂载了,所以在对应 PVC 目录里面添加 html 文件,是可以访问到的
echo web-0 > /nfsdata/default-www-web-0-pvc-79144efd-a075-47bc-b43b-2c87a4e6335a/index.html
echo web-1 > /nfsdata/default-www-web-1-pvc-07ddb513-5548-4a8b-8c03-2c9d4afcdf70/index.html
echo web-2 > /nfsdata/default-www-web-2-pvc-c8cad882-d4f6-45da-980e-22b4409e81c9/index.html
此时来访问三个 pod
$ curl 192.168.146.80
web-0
$ curl 192.168.146.78
web-1
$ curl 192.168.146.81
web-2
验证唯一网络标识
进入一个容器中,使用域名 <StatefulSet_pod_name>.<service-name>.<namespace-name>.svc.cluster.local
访问,因为 nginx 镜像中没有 nslookup
解析命令
$ kubectl run -it --image=busybox:1.28 -- 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: 192.168.146.78 web-1.nginx.default.svc.cluster.local
Address 2: 192.168.146.81 web-2.nginx.default.svc.cluster.local
Address 3: 192.168.146.80 web-0.nginx.default.svc.cluster.local
已经将部署的 StatefulSet
的3个web pod 都解析出来了,这里是根据 svc 的名称来进行解析的。刚才创建的 StatefulSet
的 svc 名称就是 nginx,所以可以解析出来3个 pod ,web-0.nginx.default.svc.cluster.local
就是一个 StatefulSet pod
在 Kubernetes中的 FQDN。这就是 pod 的唯一网络标识符,即使 pod 重启也不会变。包括 pod 本身的 主机名 以及 存储(PVC)位置 都不会发生变化。
温馨提示
一般不建议企业将动态应用放在容器内,因为考虑到拓扑关系复杂,维护成本太大。
示例:当环境中一个节点宕机后,怎么自动化的去恢复,或者给动态应用中添加新的集群角色,加大维护难度。
建议:单实例可以尝试在 k8s 应用
另一方面来说 I/O 也是问题,肯定会削减。