在前面的学习中,发现做的例子都是 Deployment ,且大部分使用 web 应用去做示例,为什么不用 mysql 类似数据库来做示例呢。

在 LNMP 架构中,应该了解过动静分离,也就是动态数据和静态数据,是以是否需要去访问数据库去进行读写数据来区分的。

Kubernetes 同样也区分了有状态应用和无状态应用,之前常用的 Deployment 就是为无状态应用而使用的。本文中提到的 StatefulSet 就是为有状态应用使用的。

介绍

无状态和有状态的区别

在日常的非容器服务中,需要做负载的服务(如web服务),一般都是无状态的应用,因为每个服务中的数据都一样,只是为了防止一个服务掉了,另一个服务可以继续提供服务。需要做分布式服务(如MySQL、Redis、kafka、etcd等),相同服务不同机器中,数据都是不一样的,这样的服务被叫做有状态服务。

有状态服务在部署时指定多个实例的 ip,且每个实例都有独立数据,这样存储也是个问题。在使用 Deployment 过程中,如果一个有状态服务的 pod 挂掉,就会重新起一个并且 ip 已经变换了。这时还需要人工手动干预服务,修改 ip,显然对于 Deployment 的使用是不现实的,也很笨重,降低效率。

StatefulSet 就可以解决以上的问题。无状态服务适用于 Deployment。有状态服务适用于 StatefulSet

StatefulSet 的特点

  • 部署有状态应用
  • 解决pod独立生命周期,保持pod启动顺序和唯一性
  1. 稳定,唯一的网络标识符,持久存储
  2. 有序,优雅的部署和扩展、删除和终止
  3. 有序,滚动更新
  • 应用场景:分布式应用、数据库集群

有序:其实就是有顺序的部署、启动、升级等等,需要有序的原因就是,如果是主从服务,必然是先部署主,再部署从,有序就是这个意思了

唯一的网络标识符:使用 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 也是问题,肯定会削减。

评论




正在载入...
PoweredHexo
HostedAliyun
DNSAliyun
ThemeVolantis
UV
PV
BY-NC-SA 4.0