理解有状态应用—StatefulSet

在前面文章详细讲述了控制器模式的实现Deployment ,它对应用做了一个简单化假设,一个应用的所有 Pod,是完全一样的,可以通过replicas设置扩展的数目。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod。但是,在实际的场景中,并不是所有的应用都可以满足这样的要求。尤其是分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。所以,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)。
StatefulSet 的设计其实非常容易理解。它把真实世界里的应用状态,抽象为了两种情况:拓扑状态。这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
所以,StatefulSet 的核心功能,就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。
在开始讲述 StatefulSet 的工作原理之前,我就必须先为你讲解一个 Kubernetes 项目中非常实用的概念:Headless Service。我在和你一起讨论 Kubernetes 架构的时候就曾介绍过,Service 是 Kubernetes 项目中用来将一组 Pod 暴露给外界访问的一种机制。比如,一个 Deployment 有 3 个 Pod,那么我就可以定义一个 Service。然后,用户只要能访问到这个 Service,它就能访问到某个具体的 Pod。Headless Service就是通过内置的DNS服务,使得Pod之间通过隐形的DNS编号,实现服务的发现,不再依赖于IP。Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。

创建Headless Service服务

这是一个服务的配置文件:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx

Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。有了这个“可解析身份”,只要你知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址。这就实现了服务的发现、服务的编号等一些列问题,当服务遇到重建时,保持一致的状态。

创建StatefulSet服务

这个 YAML 文件,和我们在前面文章中用到的 nginx-deployment 的唯一区别,就是多了一个 serviceName=nginx 字段。表示这个类型为statefulSet的能创建出的是一种特殊pod组合体。

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

这个字段的作用,就是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。
通过上述两个yaml文件,创建出headless service和statefulset的nginx应用集群。

$ kubectl create -f svc.yaml
$ kubectl get service nginx
NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx     ClusterIP    None         <none>        80/TCP    10s

$ kubectl create -f statefulset.yaml
$ kubectl get statefulset web
NAME      DESIRED   CURRENT   AGE
web       2         1         19s

创建出名为web的statefulset,pod的编号自动就出来了web-0、web-1。

root@k8s-master:~# kubectl get statefulset -o wide
NAME   READY   AGE   CONTAINERS   IMAGES
web    2/2     22h   nginx        nginx:1.14.2
root@k8s-master:~# kubectl get pods -o wide
NAME    READY   STATUS    RESTARTS      AGE   IP           NODE           NOMINATED NODE   READINESS GATES
web-0   1/1     Running   1 (22m ago)   12h   10.10.1.38   k8s-worker01   <none>           <none>
web-1   1/1     Running   1 (22m ago)   12h   10.10.1.36   k8s-worker01   <none>           <none>

root@k8s-master:~# kubectl exec web-0 -- sh -c 'hostname'
web-0
root@k8s-master:~# kubectl exec web-1 -- sh -c 'hostname'
web-1

可以看到,这两个 Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。接下来,我们再试着以 DNS 的方式,访问一下这个 Headless Service。

root@k8s-master:~# kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup web-0.nginx
Server:    10.20.0.10
Address 1: 10.20.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.10.1.38 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server:    10.20.0.10
Address 1: 10.20.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.10.1.36 web-1.nginx.default.svc.cluster.local

删掉web这个应用的pod组。

root@k8s-master:~# kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

删除pod之后,发现pod会自动重建,并且编号保持一致。
我们可以看到,在这个 StatefulSet 中,这两个新 Pod 的“网络标识”(比如:web-0.nginx 和 web-1.nginx),再次解析到了正确的 IP 地址(比如:web-0 Pod 的 IP 地址 10.244.1.8)。通过这种方法,Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。这些状态,在 StatefulSet 的整个生命周期里都会保持不变,绝不会因为对应 Pod 的删除或者重新创建而失效。

暂无评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注


虚拟化 | 云计算 | 机器学习 | 股市复盘
© 2024 涛哥,版权所有, 京ICP备20014492-2号