1 基础介绍
有状态应用:
- 实例之间有不对等关系
- 实例对外部数据有依赖关系的应用
就被称为“有状态应用”(Stateful Application)。
1.1 StatefulSet 介绍
Kubernetes StatefulSet是一种用于运行有状态应用的控制器。
StatefulSet是一个有序的、可标识的Pod组,并且每个Pod都有一个独特的标识符。
这使得StatefulSet能够管理有状态应用程序,例如数据库或队列服务,这些应用程序需要稳定的网络标识符或持久性存储,并且需要有序的、逐个更新的部署方式。
下面是关于Kubernetes StatefulSet的详细介绍:
- 稳定的网络标识符:在StatefulSet中,每个Pod都有一个稳定的网络标识符,可以通过DNS或其他服务发现机制进行访问。这个标识符由StatefulSet控制器在Pod创建时自动分配,并且在Pod重新启动时保持不变。这使得有状态应用程序能够在网络上稳定地被访问。
- 有序的、逐个更新的部署方式:StatefulSet能够按照一定的顺序逐个更新Pod。这意味着当需要更新有状态应用程序时,可以确保新的Pod在旧的Pod停止之前启动,并且在新的Pod启动之前,旧的Pod仍然可以提供服务。这种有序的、逐个更新的部署方式使得有状态应用程序在更新时更加稳定。
- 持久性存储:StatefulSet能够管理有状态应用程序的持久性存储。每个Pod都可以有自己的持久性存储卷,并且这些存储卷可以在Pod重新启动时保持不变。这使得有状态应用程序能够在重新启动时保留其状态和数据。
- 适合于有状态应用程序:StatefulSet适用于运行有状态应用程序,例如数据库或队列服务,这些应用程序需要稳定的网络标识符或持久性存储,并且需要有序的、逐个更新的部署方式。与Deployment不同,StatefulSet不适用于运行无状态应用程序。
总之,Kubernetes StatefulSet是一种用于运行有状态应用程序的控制器,它具有稳定的网络标识符、有序的、逐个更新的部署方式以及持久性存储等特点,适用于需要这些特性的有状态应用程序。
1.2 StatefulSet资源状态
StatefulSet 资源的状态主要包括以下几个方面:
- Replicas: 指定的副本数,即 .spec.replicas字段的值。表示 StatefulSet 管理的副本数量。
- ReadyReplicas: 表示已经就绪的副本数量,即当前运行且已经READY的Pod数。
- CurrentReplicas: 表示当前正在运行的副本数量,即运行中的Pod总数。
- UpdatedReplicas: 表示已经更新的副本数量,即最近一次更新中,更新成功的Pod数。
- CurrentRevision: 当前正在执行的修订版本号。
- UpdateRevision: StatefulSet 的当前修订版本号。 StatefulSet 的模板(.spec.template)每次更新时,这个值就会增加1
- collisionCount:表示在创建 Pod 时发生的命名冲突的次数。
- UpdateStatus: 最近一次更新的状态,可以是"Running"或者"Failed"。
- ObservedGeneration: 最近一次对 StatefulSet 资源的更改,已经被看到的 Generation 数。也就是说如果 StatefulSet 的 .spec 字段被修改,该值会更新。
1.3 示例讲解
1 yaml文件
apiVersion: apps/v1 kind: StatefulSet #资源类型 metadata: name: web spec: replicas: 3 selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: 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: - metadata: name: www spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi status: observedGeneration: 2 replicas: 3 readyReplicas: 3 currentReplicas: 3 updatedReplicas: 3 updateRevision: "2" currentRevision: "2"
从上面这个例子可以看出:
- observedGeneration 是 2,表示 StatefulSet 的 .spec 字段已经修改过两次。
- replicas 是 3,表示指定的副本数为 3。
- readyReplicas, currentReplicas 和 updatedReplicas 都是 3,表示所有的 3 个副本都已经准备就绪。
- updateRevision 和 currentRevision 都是 "2",表示 StatefulSet 的模板已经更新两次,当前正在运行的也是第 2 个版本。
- updateStatus 没有出现,表示最近一次更新状态是正常的。
2 StatefulSet 两种状态
StatefulSet 的设计,它把真实世界里的应用状态,抽象为了两种:
- 拓扑状态
- 存储状态
2.1拓扑状态简介
拓扑状态 :应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,
比如应用的主节点 A 要先于从节点 B 启动。而如果把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。
2.2 存储状态简介
存储状态。应用 的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
也可以说:StatefulSet 的核心功能,是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态
3 Headless Service
讲拓扑状态之前,先讲一下 k8s Headless Service
3.1Headless Service 介绍
在Kubernetes中,Headless Service是一种特殊类型的服务,它不会为Pods提供负载均衡或者访问IP,而是在DNS层面提供了一种服务发现的机制。
当一个Service被定义为Headless Service时,它将会返回与该Service关联的所有Pod的DNS记录,而不是一个虚拟IP地址。
这使得客户端可以直接访问每个Pod,而不需要经过负载均衡器。
3.2 使用场景
Headless Service可以被用来实现一些特定的功能,例如:
- 集群内部通信:Headless Service可以被用来实现Pod之间的直接通信,例如数据库集群中的各个节点之间的通信。
- 分布式计算:Headless Service可以被用来实现分布式计算中的任务分发和结果收集,例如MapReduce中的Map和Reduce节点之间的通信。
- 自定义服务发现:Headless Service可以被用来实现一些自定义的服务发现机制,例如一些复杂的应用程序中的服务发现和路由。
3.2 yaml示例讲解
下面是一个标准的 Headless Service 对应的 YAML 文件:
apiVersion: v1 kind: Service #资源类型 metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None # 为none,思考一下 selector: app: nginx
可以看到,这个 Headless Service,其实仍是一个标准 Service 的 YAML 文件。只不过,它的 clusterIP 字段的值是:None,
即:这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。
所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。
当你按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
这个 DNS 记录,是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。
有了这个“可解析身份”,只要知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址。
3.3 Headless Service 与Service对比
下图为Headless Service 与Service的简单对比
3.4 总结
总之,Headless Service是一种特殊类型的服务,它不提供负载均衡或者访问IP,而是在DNS层面提供了一种服务发现的机制。
Headless Service通常与StatefulSet一起使用,用于管理有状态应用程序的Pod。
当一个StatefulSet被创建时,Kubernetes会自动为它创建一个与之关联的Headless Service,以便客户端可以直接访问每个Pod。
在StatefulSet中,每个Pod都会被分配一个唯一的标识符和DNS记录,例如web-0.web.default.svc.cluster.local、web-1.web.default.svc.cluster.local等。
4 拓扑状态
4.1 问题:
思考一个问题:我们今天讲的是拓扑状态,那么………………
解答:
StatefulSet使用一个基于DNS的标识符来定义Pod的网络标识符。每个Pod都有一个唯一的标识符,由以下格式组成:
$(podname)-$(ordinal).$(servicename).$(namespace).svc.cluster.local
其中:
- $(podname)是Pod的名称
- $(ordinal)是Pod的序号
- $(servicename)是Service的名称
- $(namespace)是命名空间
这个标识符可以通过DNS查找进行访问。
通过这种方式,StatefulSet可以将Pod的状态维护在整个集群中。当Pod重新启动时,它将保留其唯一的网络标识符,并且其他应用程序可以使用这个标识符来发现和访问Pod。
总的来说,StatefulSet通过使用DNS标识符来维护Pod的状态,并确保它们在重新启动时保持不变,从而实现了有状态应用程序的可扩展性和健壮性。
4.2示例讲解
4.2.1 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
这个 YAML 文件,和在前面用到的 nginx-deployment 的唯一区别,就是多了一个 serviceName=nginx 字段。
这个字段的作用,是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。
所以,当通过 kubectl create 创建了上面这个 Service 和 StatefulSet 之后,就会看到如下两个对象:
4.2.2 创建
$ 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
StatefulSet 会给它所管理的所有 Pod 的名字,进行了编号,编号规则是:-。
这些编号都是从 0 开始累加,与 StatefulSet 的每个 Pod 实例一一对应,绝不重复。
更重要的是,这些 Pod 的创建,也是严格按照编号顺序进行的。
比如,在 web-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,web-1 会一直处于 Pending 状态。
4.2.3 查看hostname
使用 kubectl exec 命令进入到容器中查看它们的 hostname:
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
用 nslookup 命令,解析一下 Pod 对应的 Headless Service:
$ kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh $ nslookup web-0.nginx Server: *.*.*.* Address 1: *.*.*.* kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: *.*.*.* $ nslookup web-1.nginx Server: *.*.*.* Address 1: *.*.*.* kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: *.*.*.*
可以看到,在访问 web-0.nginx 的时候,最后解析到的,正是 web-0 这个 Pod 的 IP 地址;而当访问 web-1.nginx 的时候,解析到的则是 web-1 的 IP 地址。
4.2.3 测试
新开一个窗口,把这两个“有状态应用”的 Pod 删掉,执行命令如下:
kubectl delete pod -l app=nginx
结果如下:
pod "web-0" deleted pod "web-1" deleted
在上一个窗口,Watch 一下这两个 Pod 的状态变化,就会发现一个现象,执行命令如下:
kubectl get pod -w -l app=nginx
结果如下:
NAME READY STATUS RESTARTS AGE web-0 0/1 ContainerCreating 0 0s NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 32s
4.2.4 测试总结
可以看到,当把那两个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。
并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web-1.nginx。
通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。
通过这种方法,Kubernetes 就成功地将 Pod 的拓扑状态(比如:哪个节点先启动,哪个节点后启动),按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录.
这些状态,在 StatefulSet 的整个生命周期里都会保持不变,绝不会因为对应 Pod 的删除或者重新创建而失效。
不过,尽管 web-0.nginx 这条记录本身不会变,但它解析到的 Pod 的 IP 地址,并不是固定的。
这就意味着,对于“有状态应用”实例的访问,必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址。
5 总结
StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。
而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。
换句话,我们可以认为StatefulSet 其实是对 Deployment 的改良。
同时,通过 Headless Service 的方式,StatefulSet 为每个 Pod 创建了一个固定并且稳定的 DNS 记录,来作为它的访问入口。
总之,在部署“有状态应用”的时候,应用的每个实例拥有唯一并且稳定的“网络标识”,是一个非常重要的假设。