[关闭]
@levinzhang 2022-05-29T15:03:08.000000Z 字数 7330 阅读 473

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

by

摘要:

在Kubernetes上运行有状态的应用并不是一种常见的情况,但这并非不能实现。本文介绍了在Kubernetes上安全运行有状态应用的一些技术,包括StatefulSet和DaemonSet控制器、secret管理、ConfigMap和有效使用命名空间。


核心要点

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

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

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

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

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

什么是有状态应用?

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

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

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

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

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

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

在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控制器

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

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

DaemonSet控制器

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

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

Kubernetes中的持久化存储

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

非持久化存储卷

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

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

PersistentVolumes (PV)

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

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

PersistentVolumeClaim(PVC)

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

PV和PVC的主要差异在于:

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

StatefulSets和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提供稳定的存储。

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. labels:
  6. app: nginx
  7. spec:
  8. ports:
  9. - port: 80
  10. name: web
  11. clusterIP: None
  12. selector:
  13. app: nginx
  14. ---
  15. apiVersion: apps/v1
  16. kind: StatefulSet
  17. metadata:
  18. name: web
  19. spec:
  20. selector:
  21. matchLabels:
  22. app: nginx # has to match .spec.template.metadata.labels
  23. serviceName: "nginx"
  24. replicas: 3 # by default is 1
  25. template:
  26. metadata:
  27. labels:
  28. app: nginx # has to match .spec.selector.matchLabels
  29. spec:
  30. terminationGracePeriodSeconds: 10
  31. containers:
  32. - name: nginx
  33. image: k8s.gcr.io/nginx-slim:0.8
  34. ports:
  35. - containerPort: 80
  36. name: web
  37. volumeMounts:
  38. - name: www
  39. mountPath: /usr/share/nginx/html
  40. volumeClaimTemplates:
  41. - metadata:
  42. name: www
  43. spec:
  44. accessModes: [ "ReadWriteOnce" ]
  45. storageClassName: "my-storage-class"
  46. resources:
  47. requests:
  48. storage: 1Gi

DaemonSets

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

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

针对每种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。这个例子也来自官方文档

  1. apiVersion: apps/v1
  2. kind: DaemonSet
  3. metadata:
  4. name: fluentd-elasticsearch
  5. namespace: kube-system
  6. labels:
  7. k8s-app: fluentd-logging
  8. spec:
  9. selector:
  10. matchLabels:
  11. name: fluentd-elasticsearch
  12. template:
  13. metadata:
  14. labels:
  15. name: fluentd-elasticsearch
  16. spec:
  17. tolerations:
  18. # this toleration is to have the daemonset runnable on master nodes
  19. # remove it if your masters can't run pods
  20. - key: node-role.kubernetes.io/master
  21. operator: Exists
  22. effect: NoSchedule
  23. containers:
  24. - name: fluentd-elasticsearch
  25. image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2
  26. resources:
  27. limits:
  28. memory: 200Mi
  29. requests:
  30. cpu: 100m
  31. memory: 200Mi
  32. volumeMounts:
  33. - name: varlog
  34. mountPath: /var/log
  35. - name: varlibdockercontainers
  36. mountPath: /var/lib/docker/containers
  37. readOnly: true
  38. terminationGracePeriodSeconds: 30
  39. volumes:
  40. - name: varlog
  41. hostPath:
  42. path: /var/log
  43. - name: varlibdockercontainers
  44. hostPath:
  45. path: /var/lib/docker/containers

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

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

结论

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

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

关于作者

Gilad David Maayan

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

查看英文原文:Best Practices for Running Stateful Applications on Kubernetes

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注