web 3.0 基础设施、数据驱动决策、核心系统数字化转型……戳此了解qcon广州站专题方向
写点什么

kubernetes上运行有状态应用的最佳实践-金马国际

作者:gilad david maayan

  • 2022 年 5 月 31 日
  • 本文字数:5525 字

    阅读完需:约 18 分钟

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


在过去的几年间,社区意识到的价值,而且像 kubernetes 这样的编排器引入了必要的特性。


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


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


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

什么是有状态应用?

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


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


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

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

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


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


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

  • ai 和数据分析处理:数据处理和机器学习框架,如 apache spark、hadoop、kubeflow、tensorflow 和 pytorch,对容器化的支持在不断增强。这些平台必须反复处理大量的数据,需要有保持状态的机制。

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

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

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

1.在 kubernetes 之外运行有状态的应用

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


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


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

2. 以云服务的形式运行有状态的工作负载

第二种同样常见的方法是将有状态的应用作为托管云服务来运行。例如,如果你需要在一个容器化的应用中运行一个 sql 数据库,并且应用在 aws 上运行,那么你可以使用 amazon 的 relational database service(rds)。托管数据库往往是可以进行弹性扩展的,所以随着 kubernetes 资源的扩展,有状态的服务也可以适应不断增加的需求。


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


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

3. 在 kubernetes 中运行有状态的工作负载

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

statefulset 控制器

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


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

daemonset 控制器

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


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

kubernetes 中的持久化存储

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

非持久化存储卷

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


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

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 的主要差异在于:


pvpvc
谁来创建它们只有集群管理员和kubernetes(通过动态供应)能够创建pv。开发人员和用户都能创建pvc。
资源类型pv是一种集群资源。pvc是对存储资源的请求。
消费pvc消费pv资源。pod消费pvc。

statefulsets 和 daemonsets

statefulsets

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


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


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


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

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

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

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


如下是一个来自的样例,展示了 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-elasticsearch docker 镜像的 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
复制代码

kubernetes 中有状态应用的最佳实践

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


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

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

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

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

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

结论

在本文中,我阐述了有状态容器化应用的基础知识,并介绍了如何在 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 领导层阐明技术金马国际的解决方案。


原文链接:



2022 年 5 月 31 日 09:521826

评论

发布
暂无评论
发现更多内容

b站大型活动稳定性保障秘籍

b站大型活动稳定性保障秘籍

网站地图