Kubernetes StatefulSet 终极指南 – CodesCode
探索使用Kubernetes StatefulSets管理有状态应用 - 使用时机,部署MongoDB方法,限制条件以及实施最佳实践
StatefulSet是Kubernetes的一种工作负载API,专门用于管理有状态应用程序。这是一份关于设置和使用StatefulSets的全面指南,我们将涵盖以下主题:
- 什么是StatefulSet,何时使用它?
- 示例:设置和运行指定StatefulSet的MongoDB
- StatefulSets的限制和注意事项
- 在实施StatefulSets时的最佳实践
有状态和无状态应用程序
让我们先来区分有状态和无状态应用程序。无状态应用程序是指每个请求都被视为一个新的、独立的事务,与任何先前的事务无关。它不在客户端或服务器端存储会话特定的数据。
Kubernetes以在管理无状态服务方面表现出色而闻名。对于无状态应用程序,Pod是完全可互换的——扩展和缩减不会导致任何数据丢失。我们使用Kubernetes Deployment来管理无状态应用程序的Pod。
相反,有状态应用程序在会话和事务之间保持数据。它们记住过去的活动,并根据这些记录的状态来定制用户交互。例如,所有数据库都是有状态的。
那么,在Kubernetes中如何管理有状态应用程序,考虑到我们无法随机重启或杀死Pod呢?这就是StatefulSets发挥作用的地方。
什么是StatefulSet?
StatefulSet是一个专为管理需要稳定网络标识和持久化存储的有状态应用程序而构建的Kubernetes工作负载API对象。
它提供了关于Pod的部署和扩展的顺序和唯一性的一些保证(见下文)。
- 顺序保证:当我们使用StatefulSet进行部署时,Pod按顺序依次创建(与Deployments或ReplicaSets不同)。这对于启动顺序很重要的系统(如分布式数据库)是相关的。
- 持久标识:每个StatefulSet中的Pod都具有稳定且可预测的主机名,通常的格式是<pod名称>-<序号>。即使Pod被重新调度,其标识符也保持不变。
- 稳定存储:使用StatefulSet时,每个Pod都与持久化存储相关联。即使Pod迁移到另一个节点,该存储仍然绑定到特定的Pod上。
- 优雅的扩展和更新:StatefulSet允许以受控的方式扩展或缩减应用程序,确保滚动更新等操作不会损害应用程序的完整性。
StatefulSet控制器
StatefulSet控制器是一个Kubernetes控制器,负责监视并管理根据StatefulSet Pod规范创建的Pod的生命周期。它位于控制平面,并负责按照StatefulSet定义中的确切顺序编排Pod的创建、扩展和删除。
StatefulSets的优点
- 可预测性:StatefulSet确保Pod的部署、扩展和删除具有可预测的顺序,这对于像数据库这样操作顺序很重要的应用程序至关重要。
- 稳定性:即使StatefulSet中的Pod崩溃或托管Pod的节点失败,Pod的标识(名称、主机名和存储)仍保持一致。
- 数据安全性:结合持久卷申明,StatefulSet确保每个Pod的数据得到保护。如果Pod被重新调度,其数据将保持完好。
- 易于发现和通信:每个Pod都有其自己的DNS,使得服务发现和Pod间通信更加简单。
- 手动干预的可行性:对于那些需要更多控制的特殊情况,StatefulSet允许手动干预,而不会立即进行“自动修复”。
StatefulSet的设计和优势使其与其他Kubernetes对象有明显区别,成为管理有状态应用程序的首选之一。
Deployment与StatefulSet的区别
让我们看看StatefulSet与Deployment的区别:
1. Pod名称和标识
Deployment:Pod具有包含部署名称和随机哈希的ID
StatefulSet:每个Pod获得一个持久化标识,其中包含StatefulSet名称和序号
2. Pod创建序列
部署:Pod随机创建和删除
状态化集合:按顺序创建的Pod不能随机删除
3. 可互换性
部署:所有Pod都是相同的,可以互换
状态化集合:Pod不相同,不能互换
4. 重新调度
部署:Pod随时可以被新的副本替换
状态化集合:当Pod在另一个节点上重新调度时保留其标识
5. 卷索取
部署:所有副本共享相同的持久卷索取(PVC)和卷
状态化集合:每个Pod获得一个唯一的PVC和卷
6. Pod交互
部署:需要服务与Pod交互
状态化集合:无头服务处理Pod的网络标识
何时使用状态化集合
当应用程序具有状态时,请使用状态化集合。请问你的应用程序是否需要为其Pod提供稳定的标识?当Pod副本被替换时,您的系统是否会受到干扰?
复制的数据库是使用状态化集合的一个很好的例子。一个Pod充当主数据库节点,处理读写操作,而其他Pod作为只读副本。每个Pod可能正在运行相同的容器镜像,但每个Pod都需要配置来设置其处于主节点还是只读模式。
类似于:
- mongodb-0 – 主节点(读写)。
- mongodb-1 – 只读副本。
- mongodb-2 – 只读副本。
如果缩小一个副本集或部署,随机删除Pod,可能会包括这个MongoDB系统中的主节点。
然而,当我们使用状态化集合时,Kubernetes按照相反的顺序终止Pod,这保证在这个例子中首先销毁mongodb-2。
状态化集合示例:在Kubernetes中运行MongoDB
现在,让我们看一个例子,并使用状态化集合在Kubernetes中运行一个MongoDB集群。
第一步:设置一个无头服务
状态化集合中的Pod的标识与其稳定的网络标识密切相关,因此头无服务至关重要。
头无服务的定义是将其clusterIP
设置为None
,以确保Pod的稳定网络标识。
这是我们MongoDB服务的YAML:
apiVersion: v1kind: Servicemetadata: name: mongodb labels: app: mongodbspec: ports: - name: mongodb port: 27017 clusterIP: None selector: app: mongodb
让我们将服务部署到集群中:
$ kubectl apply -f mongodb-service.yamlservice/mongodb created
第二步:部署MongoDB状态化集合
以下YAML用于状态化集合。它描述了运行三个MongoDB镜像的副本:
apiVersion: apps/v1kind: StatefulSetmetadata: name: mongodbspec: selector: matchLabels: app: mongodb serviceName: mongodb replicas: 3 template: metadata: labels: app: mongodb spec: containers: - name: mongodb image: mongo:latest ports: - name: mongodb containerPort: 27017 volumeMounts: - name: data mountPath: /data/db volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1Gi
现在,让我们应用状态化集合配置:
$ kubectl apply -f mongodb-statefulset.yamlstatefulset.apps/mongodb created
让我们检查新的Pod,观察它们的运行情况:
Pod的状态
可以看到,三个Pod是顺序创建的,确保每个Pod初始化完成后才创建下一个Pod。
这个MongoDB状态化集合中的每个新Pod都具有其独特的持久卷和持久卷索取。这些索取是从状态化集合的volumeClaimTemplates
字段生成的。
持久卷在集群中提供了独立于使用它的任何单个 Pod 的存储空间。它们是集群中的资源,就像节点是集群资源一样。PV 的生命周期独立于使用来自 PV 的存储卷的任何单个 Pod。
让我们来看看 PV:
$ kubectl get pv...
输出显示了集群中可用的持久卷。对于我们的三个副本 MongoDB StatefulSet,我们看到以下内容:
$ kubectl get pvc...
我们可以看到上述三个 Pod 提出的 Persistent Volume Claim。拥有专用存储可以确保我们的 MongoDB 实例在 Pod 生命周期无关的情况下保留其数据,这对于任何数据库系统都是至关重要的。
第三步:扩展 MongoDB 集群
现在让我们看看如何扩展我们的 MongoDB 实例:
$ kubectl scale sts mongodb --replicas=5statefulset.apps/mongodb scaled
让我们确认 Pod 是按顺序创建的。
$ kubectl get pods...
从年龄上我们可以看到 Pod 是按顺序创建的。
类似地,让我们看看如何缩小规模:
$ kubectl scale sts mongodb --replicas=2statefulset.apps/mongodb scaled
Kubernetes 现在将按相反的创建顺序终止具有相同卷的 Pod。
正如你可以看到的,最后的 Pod 首先被终止。
这个例子展示了 StatefulSet 如何确保 MongoDB(一个有状态的应用程序)在需要时平稳运行,使用 Kubernetes 保留关键数据并进行扩展。
StatefulSet 的限制
虽然 Kubernetes StatefulSet 提供了许多管理有状态应用程序的选项,但也存在一些限制。了解这些限制将帮助我们为我们的特定用例做出明智的决策。
1. 较慢的发布过程
StatefulSet 的顺序缩放过程确保一致性和顺序,但对于大规模应用程序来说,发布过程会变得较慢。
2. 手动干预以清理/恢复状态
如果 StatefulSet 中的一个 Pod 变得损坏,仅删除该 Pod 并不能总是解决问题。相关的持久化存储可能仍然包含一个或多个损坏的 Pod。在这种情况下,可能需要手动干预来清理或恢复状态。
3. 调整大小复杂
StatefulSet 与其持久卷声明(PVC)的存储资源紧密绑定。一旦创建了 PVC,它很难被调整大小。如果我们需要扩展存储卷,我们经常需要进行更复杂的过程,具体取决于所使用的存储提供程序。
4. 备份复杂性
在由 StatefulSet 管理的有状态应用程序中备份之前的数据需要更加慎重的规划。我们需要确保在多个 Pod 之间保持数据一致性,尤其是在数据可以进行分区的分布式数据库中。
5. 网络配置方面的挑战
StatefulSet 依赖于无头服务进行网络标识。尽管这可以确保唯一的主机名,但在设置 Pod 间通信或处理特定 Pod 需要在外部可访问的情况下,会引入复杂性。
StatefulSet 最佳实践
考虑到我们对 StatefulSet 的了解,以下是一些需要记住的最佳实践
1. 为 StatefulSet 的 Pod 使用唯一且相关的名称
这有助于更轻松地识别和管理特定 StatefulSet 的 Pod 及其资源。
2. 管理初始化和顺序
使用 Init 容器按顺序执行预初始化任务。这确保关键的设置任务(如数据填充或配置初始化)在主应用容器启动之前完成。
spec: template: spec: initContainers: - name: init-container-1 image: init-image-1 # 在此处添加初始化容器配置 - name: init-container-2 image: init-image-2 # 在此处添加初始化容器配置 containers: - name: main-container image: main-image # 在此处添加主容器配置
如果 StatefulSet 要求特定顺序的 Pod 初始化(例如,数据库主服务器必须在副本之前启动),可以在初始化容器内实施自定义逻辑,或使用像 wait-for-it 或 wait-for 这样的工具来管理 Pod 的可用性。
3. 谨慎处理扩展
在扩展StatefulSet之前,请确保您了解有状态应用程序的依赖性和要求。扩展应该保持数据一致性,而缩小规模可能需要适当的数据迁移或备份策略。使用基于仲裁的复制或分区等策略来在扩展时维护数据完整性。
4. 定义Pod中断预算(PDB)
PDB允许您设置策略,限制在缩小规模或维护等事件期间可以同时中断的Pod数量。要为您的StatefulSet定义Pod中断预算,创建一个PodDisruptionBudget
资源,并指定maxUnavailable
字段,该字段确定任何给定时间内不可用的Pod的最大数量。例如,为了确保在扩展或维护过程中最多只有一个Pod不可用,设置maxUnavailable: 1
。
以下是一个快速操作指南
A. 为您的StatefulSet创建一个Pod中断预算YAML文件(例如,pdb.yaml
)
apiVersion: policy/v1beta1kind: PodDisruptionBudgetmetadata: name: example-pdbspec: maxUnavailable: 1 # 根据您的需求调整该值 selector: matchLabels: app: your-statefulset-label
B. 将PDB添加到您的Kubernetes集群
kubectl apply -f pdb.yaml
5. 备份和还原数据
为持久卷中存储的数据实施备份和还原机制。这样可以确保在发生故障或灾难恢复场景中数据的可用性和弹性。您可以使用许多备份工具或编写自定义脚本。
对StatefulSets进行可观察性和故障排除
总的来说,在Kubernetes中对StatefulSet进行观测很类似于对Deployment进行观测(两者都可以使用相同的度量和日志解决方案,如Prometheus和FluentD),但也有一些要考虑的差异。
在某些方面,使用Statefulset跟踪各个Pod更容易,因为StatefulSets为其Pod分配了稳定且可预测的名称,例如pod-0,pod-1等。这使得我们能够更轻松地进行故障排除和调试。相反,Deployments使用随机的Pod名称,我们需要更多地依赖于标签和选择器。
然而,我们可能还希望跟踪StatefulSets的一些额外指标和日志,例如捕获各个Pod的状态、数据分布、数据复制、同步以及跨Pod的一致性。我们还可能希望监视各种Pod状态的变化,比如“Pending”、“Running”或“Terminating”,以确保在这些过渡期间正确移动或复制数据。
类似地,对于警报 – 我们通常希望包括与特定Pod行为或状态转换相关的附加警报,例如副本计数、标识或存储利用率的更改。
有关如何设置良好的可观察性的更详细阅读,请参见这里。
通常,最棘手的问题是由StatefulSets引起的,并且它们更难以进行模拟和调试。如果您在演练环境中,并尝试确定问题是否由有状态应用程序引起,您可以使用像Klone这样的开源工具来避免完全模拟并更快地进行调试。还有一类新兴的AI产品,如ZeroK.ai,通过在可观察性数据上运行自动化调查,使用AI自动识别可能的原因,值得探索。
摘要
在本文中,我们探讨了StatefulSets的工作原理以及它们与Deployment的区别。我们设置并运行了一个MongoDB作为StatefulSet,并在实施StatefulSets时研究了限制和最佳实践。StatefulSets在减少在Kubernetes中部署和管理有状态应用程序的复杂性方面大有裨益。
Leave a Reply