Kubernetes 上运行有状态应用的最佳实践

2022 年 6 月 11 日 AI前线

作者 | Gilad David Maayan
译者 | 张卫滨
策划 | 丁晓昀

在容器化的早期阶段,它们被设定为一种运行无状态应用的机制。

在过去的几年间,社区意识到在容器中运行有状态工作负载的价值,而且像 Kubernetes 这样的编排器引入了必要的特性。

Kubernetes 提供了持久化卷(Persistent Volume,PV)架构以及像 StatefulSet 和 DaemonSet 这样的控制器,它们能够让我们创建有状态工作负载的 Pod,即便是在 Kubernetes 扩展和供应资源的时候,这些工作负载也能保持运行,并且能够确保现有的客户端连接不会中断。

这种方式虽然远远谈不当简单直接,但是能够行之有效,任何采用 Kubernetes 作为运行时基础设施的人都必须熟悉它。

在本文中,我将会阐述在 Kubernetes 中运行有状态应用的重要性,给出运行有状态应用的三个可选方案,并详细描述它们的运行机制。

1什么是有状态应用?

有状态应用允许用户重复返回该应用并恢复之前的操作,比如电子邮件或者网上银行应用。有状态的应用会记录之前事务的上下文,这些上下文可能会对当前或未来事务产生影响。所以,有状态的应用必须确保每个用户始终访问同一个应用程序实例,或者有某种在实例之间同步数据的机制。

有状态进程的优点是,应用程序可以存储每个事务的历史和上下文,跟踪最近的活动、配置偏好和窗口位置等元素,并允许用户恢复事务。有状态的事务的表现就像始终和同一台服务器进行对话一样。

如今,大多数的应用都是有状态的。容器和微服务等技术的进步推动了基于云的应用开发,然而由于它们的动态性,使得有状态进程的管理更具挑战性。

2容器化有状态应用的使用场景

在容器上运行有状态应用的需求正变得越来越大。容器化的应用可以简化复杂环境中的部署和运维,如边缘云计算和混合云环境。状态性对于持续集成和持续交付(CI/CD)也很重要,因为 CI/CD 流水线必须保持状态,以确保从开发到生产环境部署过程的连贯性。

容器化有状态应用的常见使用场景包括:

  • 机器学习运维(MLOps):在 MLOps 环境中,容器需要是有状态的,这样做有多个目的,包括共享推理和训练的结果以及训练 job 的检查点。

  • AI 和数据分析处理:数据处理和机器学习框架,如 Apache Spark、Hadoop、Kubeflow、Tensorflow 和 PyTorch,对容器化的支持在不断增强。这些平台必须反复处理大量的数据,需要有保持状态的机制。

  • 消息系统和数据库:你可能更喜欢使用本地闪存来获取低延迟性,但是这会使得容器很难在不同的 worker 节点间进行移动,因为数据会持久化到节点上。高性能共享存储对各种应用都很重要,比如单实例数据库(如 MySQL)、内存数据库(如 Redis)、NoSQL 数据库(如 MongoDB)、业务关键型的应用(如 SAP 或 Oracle)以及消息应用(如 Kafka)。

3在 Kubernetes 中实现有状态部署的三个可选方案

在 Kubernetes 集群中运行有状态的工作负载主要有三个可选方案,即在集群之外运行、作为集群旁的云服务或者在 Kubernetes 集群中运行。

在 Kubernetes 之外运行有状态的应用

一种常见的方式就是在 VM 或裸机中运行有状态的应用,并让 Kubernetes 中的资源与之进行通信。从集群中 pod 的角度来看,有状态应用会作为一个外部的集成。

这种方式的好处在于,它允许我们按照原样运行现有的有状态应用,无需重构或重新架构。如果应用能够根据 Kubernetes 集群中工作负载的需要进行扩展,那么我们就不需要 Kubernetes 复杂的自动扩展和资源供应机制。

这种方式的缺点在于,在集群外维护非 Kubernetes 的资源,这就需要我们有某种方式来监控进程、执行配置管理,并为应用执行负载均衡和服务发现。我们在 Kubernetes 之外搭建了一个并行的软件工作流,所以基本是在进行重复的工作。

以云服务的形式运行有状态的工作负载
第二种同样常见的方法是将有状态的应用作为托管云服务来运行。 例如,如果你需要在一个容器化的应用中运行一个 SQL 数据库,并且应用在 AWS 上运行,那么你可以使用 Amazon 的 Relational Database Service(RDS)。 托管数据库往往是可以进行弹性扩展的,所以随着 Kubernetes 资源的扩展,有状态的服务也可以适应不断增加的需求。

这种方式的好处在于,它的搭建过程非常容易,有状态工作负载的持续维护应该会非常简单,而且你使用的是一个与 Kubernetes 兼容的云原生资源。

这种方式的缺点在于,托管云服务是有成本的,它的定制能力通常会比较有限,并且不一定能提供你所需要的性能或延迟属性。同时,采取这种方式,会让你锁定到特定云供应商上。

在 Kubernetes 中运行有状态的工作负载

这种方式最难实现,但是从长远来看,它会带给我们最大的灵活性和运维效率。我们可以使用 Kubernetes 提供的两个原生控制器来运行有状态的应用,即 StatefulSet 和 DaemonSet。

 StatefulSet 控制器

StatefulSet 是一个 Kubernetes 的控制器,它管理具有唯一身份标识的多个 pod,并且它们是不能互相交换的(这与常规的 Kubernetes Deployment 有所差异,在 Deployment 中,pod 是无状态的,可以根据需要经常销毁和重建)。

在 StatefulSet 中,每个 pod 都有一个持久化的、唯一的 ID。每个 pod 可以有自己的持久化存储卷。如果 Kubernetes 需要扩展和伸缩的话,它会保持与外部用户或者集群中其他应用的现有连接。

 DaemonSet 控制器

DaemonSet 是一个 pod,Kubernetes 能够确保它会在集群的所有节点,或者通过选择器定义的特定节点子集上运行。每当符合条件的节点被添加到集群中,这个 pod 都会在它上面启动。

对于需要以后台进程的形式运行的有状态应用来说,DaemonSet 非常有用,比如监控或日志聚合应用。一般来讲,DaemonSets 的灵活性较差,但是比 StatefulSet 更易于管理,资源的使用也更加可预测。

4Kubernetes 中的持久化存储

卷(volume)是一个 Kubernetes 实体,它提供了持久化的存储。Pod 中所有的容器可以共享卷。我们可以借助持久化卷,让运行在同一个 pod 中的多个服务使用同一个挂载的文件系统。

非持久化存储卷

在 Kubernetes 中,要授予容器对持久化存储的访问权,我们需要声明所需的卷以及所需的位置,以便于在容器的文件系统中挂载该卷。

Kubernetes 中的常规存储卷会有一个确定的生命周期:每个卷都与 pod 的生命周期绑定。当 pod 处于活跃状态的时候,卷会保持在 pod 内,如果重启 pod 的话,卷会被重置。这个模型不适合有状态的工作负载,这也是 Kubernetes 引入持久化卷(Persistent Volumes)概念的原因。

PersistentVolumes (PV)

Kubernetes PersistentVolumes(PV)是存在于集群级别的存储对象。将 PV 绑定到集群上会扩展它们的生命周期,不再局限于 pod 的生命周期。因为 PV 位于集群级别,所以 pod 可以共享数据。我们可以扩展持久化卷的大小和规模,但是不能减少它的大小。

我们有两种方式来提供 PV:

  • 静态方式(Statically) :能够让我们预先分配存储资源。这样会假定集群可用的物理存储资源是静态的。

  • 动态方式(Dynamically):能够让我们扩展可用的存储空间,以满足不断增长的需求。我们可以通过使用 Kubernetes API 服务器启用 DefaultStorageClass admission 控制器来使用该方案。

PersistentVolumeClaim(PVC)

PVC 能够让 Kubernetes 用户请求存储。它的运行方式与 pod 类似,只不过 pod 消费节点资源,而 PVC 消费 PV 资源。除此之外,与 pod 能够请求特定级别的资源一样,PVC 也可以请求特定的访问模式和大小。

PV 和 PVC 的主要差异在于:

5StatefulSets 和 DaemonSets
StatefulSets

StatefulSet 是一个工作负载 API 对象,旨在管理有状态的应用。它能够管理 pod 集合的扩展和部署,并且能够保证这些 pod 的唯一性和顺序。

StatefulSet 可以帮助我们处理提供持久化的存储卷。请注意,即便 StatefulSet 中的单个 pod 很容易发生故障,有状态的工作负载也能对故障保持弹性。持久化的 pod 标识符能够将现有的卷与 Kubernetes 新供应的新 pod 进行匹配,以取代发生故障的 pod。

StatefulSet 是如下场景的理想选择:

  • 稳定的、唯一的网络标识符。

  • 有序、优雅的部署和扩展。

  • 稳定的、持久化的存储。

  • 有序的、自动的滚动更新。

如下是一个来自 Kubernetes 文档的样例,展示了 StatefulSet 组件。

这个例子使用nginx服务来控制一个网络域。该 StatefulSet 名为 web,它有一个 Spec,表明必须在特定 pod 中启动nginx容器的三个副本。它还声明,当使用由 PV Provisioner 提供的 PV 时,由volumeClaimTemplates提供稳定的存储。

apiVersion: v1kind: Servicemetadata:  name: nginx  labels:    app: nginxspec:  ports:  - port: 80    name: web  clusterIP: None  selector:    app: nginx---apiVersion: apps/v1kind: StatefulSetmetadata:  name: webspec:  selector:    matchLabels:      app: nginx # has to match .spec.template.metadata.labels  serviceName: "nginx"  replicas: 3 # by default is 1  template:    metadata:      labels:        app: nginx # has to match .spec.selector.matchLabels    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:  - metadata:      name: www    spec:      accessModes: [ "ReadWriteOnce" ]      storageClassName: "my-storage-class"      resources:        requests:          storage: 1Gi
DaemonSets

DaemonSets 负责确保所有或特定节点上会运行 pod 的副本。一旦节点被添加到集群中,DaemonSet 所声明的 pod 就会添加到节点中。当节点在集群中移除时,DaemonSet pod 就会被垃圾回收掉。删除 DaemonSet 时,会清理掉它所创建的 pod。

如下是 DaemonSets 的常见使用场景:

  • 在每个节点上运行集群存储的 daemon

  • 在每个节点上运行日志收集的 daemon

  • 在每个节点上运行节点监控的 daemon

针对每种 daemon 类型,你可以定义一个 DaemonSet 涵盖所有的节点。也可以为每种 daemon 类型定义多个 DaemonSets,针对不同类型的硬件使用不同的标记、内存和 CPU。

 创建 DaemonSet

运行如下的命令在 Kubernetes 集群中创建 DaemonSet:

kubectl apply -f [Path to Daemonset spec].yaml
定义 DaemonSet 参数

Kubernetes 允许我们使用 YAML 文件来描述 DaemonSet。下面的daemonset.yaml文件样例定义了一个运行fluentd-elasticsearchDocker 镜像的 DaemonSet。这个例子也来自官方文档。

apiVersion: apps/v1kind: DaemonSetmetadata:  name: fluentd-elasticsearch  namespace: kube-system  labels:    k8s-app: fluentd-loggingspec:  selector:    matchLabels:      name: fluentd-elasticsearch  template:    metadata:      labels:        name: fluentd-elasticsearch    spec:      tolerations:      # this toleration is to have the daemonset runnable on master nodes      # remove it if your masters can't run pods      - key: node-role.kubernetes.io/master        operator: Exists        effect: NoSchedule      containers:      - name: fluentd-elasticsearch        image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2        resources:          limits:            memory: 200Mi          requests:            cpu: 100m            memory: 200Mi        volumeMounts:        - name: varlog          mountPath: /var/log        - name: varlibdockercontainers          mountPath: /var/lib/docker/containers          readOnly: true      terminationGracePeriodSeconds: 30      volumes:      - name: varlog        hostPath:          path: /var/log      - name: varlibdockercontainers        hostPath:          path: /var/lib/docker/containers
6Kubernetes 中有状态应用的最佳实践

到此为止,我介绍了在 Kubernetes 上运行有状态工作负载的几种方法。这里有一些建议,可以更有效地运行有状态的应用:

  • 有效利用命名空间:最好是将每个有状态的应用分割到自己的命名空间中,以确保明确的隔离并且更易于进行资源管理。

  • 使用 ConfigMap:所有的脚本和自定义配置应该放到 ConfigMap 中,以确保所有的应用配置都会以声明式的方式来进行处理。

  • 服务路由:随着应用程序的增长,考虑服务路由的可管理性,应该倾向于使用 headless 服务而不是负载均衡器。

  • Secret 管理:明文 secret 会给生产应用带来严重的安全风险,要确保所有的 secret 都在一个强大的 secret 管理系统中理。

  • 谨慎规划存储:确定应用的持久化存储需求,确保物理存储设备可供集群使用,并以确保每个应用组件所需存储资源的方式定义 Storage Classes 和 PVC。

7结论

在本文中,我阐述了有状态容器化应用的基础知识,并介绍了如何在 Kubernetes 中管理有状态工作负载。这包括以下关键的构件:

  • PersistentVolume(PV):允许我们定义持久化存储单元并将其挂载到 Kubernetes 集群中的 pod 上的构造。

  • PersistentVolumeClaim(PVC):允许 pod 动态请求符合其要求的存储的机制。

  • StatefulSet:控制器,允许创建具有持久化 ID 的 pod,即便 Kubernetes 动态扩展集群中的应用,它也会保持原样。

  • DaemonSets:控制器,允许集群中的所有节点或特定子集上运行有状态的工作负载。

熟悉了这些构件后,你就可以直接在 Kubernetes 集群中创建安全的、可重复运行的有状态的工作负载了。就像 Kubernetes 中的所有内容一样,有状态的机制并不简单,需要时间来掌握,但当你掌握了这些机制后,它就会变得强大而可靠。稍微练习一下,你就能成为一个有状态 Kubernetes 的专家。

作者简介:

Gilad David Maayan 是一位技术作家,曾与 150 多家技术公司合作,包括 SAP、Imperva、三星 NEXT、NetApp 和 Check Point,制作技术和思想领导力相关的内容,为开发者和 IT 领导层阐明技术解决方案。

原文链接:

https://www.infoq.com/articles/kubernetes-stateful-applications/


你也「在看」吗?👇

登录查看更多
0

相关内容

【2022新书】Python DevOps,245页pdf
专知会员服务
89+阅读 · 2022年7月11日
《边缘计算网络安全最佳实践概述》
专知会员服务
34+阅读 · 2022年7月6日
【Manning新书】Kafka实战,272页pdf,Kafka in Action
专知会员服务
60+阅读 · 2022年1月30日
【2021新书】机器学习模型生产部署实践,161页pdf,
专知会员服务
111+阅读 · 2021年6月11日
专知会员服务
86+阅读 · 2020年8月2日
无服务器和 Kubernetes 原生 Java 部署实践
InfoQ
0+阅读 · 2022年6月28日
Docker 足够安全吗?
InfoQ
0+阅读 · 2022年3月23日
高级 Kubernetes 部署策略
InfoQ
1+阅读 · 2022年2月14日
容器会取代虚拟机吗?
InfoQ
0+阅读 · 2022年1月13日
Kubernetes 安全指南
InfoQ
2+阅读 · 2021年12月24日
容器并不能解决一切问题
InfoQ
0+阅读 · 2021年11月18日
Kubernetes 入门教程
阿里技术
0+阅读 · 2021年11月16日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
国家自然科学基金
0+阅读 · 2014年12月31日
国家自然科学基金
0+阅读 · 2014年12月31日
国家自然科学基金
2+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
3+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
1+阅读 · 2010年12月31日
Arxiv
0+阅读 · 2022年7月29日
Arxiv
0+阅读 · 2022年7月29日
Arxiv
27+阅读 · 2021年11月11日
Generalized Out-of-Distribution Detection: A Survey
Arxiv
15+阅读 · 2021年10月21日
Arxiv
19+阅读 · 2020年7月13日
A Comprehensive Survey on Transfer Learning
Arxiv
121+阅读 · 2019年11月7日
Arxiv
22+阅读 · 2018年2月14日
Arxiv
27+阅读 · 2017年12月6日
VIP会员
相关资讯
无服务器和 Kubernetes 原生 Java 部署实践
InfoQ
0+阅读 · 2022年6月28日
Docker 足够安全吗?
InfoQ
0+阅读 · 2022年3月23日
高级 Kubernetes 部署策略
InfoQ
1+阅读 · 2022年2月14日
容器会取代虚拟机吗?
InfoQ
0+阅读 · 2022年1月13日
Kubernetes 安全指南
InfoQ
2+阅读 · 2021年12月24日
容器并不能解决一切问题
InfoQ
0+阅读 · 2021年11月18日
Kubernetes 入门教程
阿里技术
0+阅读 · 2021年11月16日
浅谈 Kubernetes 在生产环境中的架构
DevOps时代
11+阅读 · 2019年5月8日
相关基金
国家自然科学基金
0+阅读 · 2014年12月31日
国家自然科学基金
0+阅读 · 2014年12月31日
国家自然科学基金
2+阅读 · 2013年12月31日
国家自然科学基金
0+阅读 · 2013年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2012年12月31日
国家自然科学基金
1+阅读 · 2012年12月31日
国家自然科学基金
3+阅读 · 2012年12月31日
国家自然科学基金
0+阅读 · 2011年12月31日
国家自然科学基金
1+阅读 · 2010年12月31日
相关论文
Arxiv
0+阅读 · 2022年7月29日
Arxiv
0+阅读 · 2022年7月29日
Arxiv
27+阅读 · 2021年11月11日
Generalized Out-of-Distribution Detection: A Survey
Arxiv
15+阅读 · 2021年10月21日
Arxiv
19+阅读 · 2020年7月13日
A Comprehensive Survey on Transfer Learning
Arxiv
121+阅读 · 2019年11月7日
Arxiv
22+阅读 · 2018年2月14日
Arxiv
27+阅读 · 2017年12月6日
Top
微信扫码咨询专知VIP会员