@levinzhang
2022-05-29T15:03:08.000000Z
字数 7330
阅读 501
by
在Kubernetes上运行有状态的应用并不是一种常见的情况,但这并非不能实现。本文介绍了在Kubernetes上安全运行有状态应用的一些技术,包括StatefulSet和DaemonSet控制器、secret管理、ConfigMap和有效使用命名空间。
在容器化的早期阶段,它们被设定为一种运行无状态应用的机制。
在过去的几年间,社区意识到在容器中运行有状态工作负载的价值,而且像Kubernetes这样的编排器引入了必要的特性。
Kubernetes提供了持久化卷(Persistent Volume,PV)架构以及像StatefulSet和DaemonSet这样的控制器,它们能够让我们创建有状态工作负载的Pod,即便是在Kubernetes扩展和供应资源的时候,这些工作负载也能保持运行,并且能够确保现有的客户端连接不会中断。
这种方式虽然远远谈不当简单直接,但是能够行之有效,任何采用Kubernetes作为运行时基础设施的人都必须熟悉它。
在本文中,我将会阐述在Kubernetes中运行有状态应用的重要性,给出运行有状态应用的三个可选方案,并详细描述它们的运行机制。
有状态应用允许用户重复返回该应用并恢复之前的操作,比如电子邮件或者网上银行应用。有状态的应用会记录之前事务的上下文,这些上下文可能会对当前或未来事务产生影响。所以,有状态的应用必须确保每个用户始终访问同一个应用程序实例,或者有某种在实例之间同步数据的机制。
有状态进程的优点是,应用程序可以存储每个事务的历史和上下文,跟踪最近的活动、配置偏好和窗口位置等元素,并允许用户恢复事务。有状态的事务的表现就像始终和同一台服务器进行对话一样。
如今,大多数的应用都是有状态的。容器和微服务等技术的进步推动了基于云的应用开发,然而由于它们的动态性,使得有状态进程的管理更具挑战性。
在容器上运行有状态应用的需求正变得越来越大。容器化的应用可以简化复杂环境中的部署和运维,如边缘云计算和混合云环境。状态性对于持续集成和持续交付(CI/CD)也很重要,因为CI/CD流水线必须保持状态,以确保从开发到生产环境部署过程的连贯性。
容器化有状态应用的常见使用场景包括:
在Kubernetes集群中运行有状态的工作负载主要有三个可选方案,即在集群之外运行、作为集群旁的云服务或者在Kubernetes集群中运行。
一种常见的方式就是在VM或裸机中运行有状态的应用,并让Kubernetes中的资源与之进行通信。从集群中pod的角度来看,有状态应用会作为一个外部的集成。
这种方式的好处在于,它允许我们按照原样运行现有的有状态应用,无需重构或重新架构。如果应用能够根据Kubernetes集群中工作负载的需要进行扩展,那么我们就不需要Kubernetes复杂的自动扩展和资源供应机制。
这种方式的缺点在于,在集群外维护非Kubernetes的资源,这就需要我们有某种方式来监控进程、执行配置管理,并为应用执行负载均衡和服务发现。我们在Kubernetes之外搭建了一个并行的软件工作流,所以基本是在进行重复的工作。
第二种同样常见的方法是将有状态的应用作为托管云服务来运行。例如,如果你需要在一个容器化的应用中运行一个SQL数据库,并且应用在AWS上运行,那么你可以使用Amazon的Relational Database Service(RDS)。托管数据库往往是可以进行弹性扩展的,所以随着Kubernetes资源的扩展,有状态的服务也可以适应不断增加的需求。
这种方式的好处在于,它的搭建过程非常容易,有状态工作负载的持续维护应该会非常简单,而且你使用的是一个与Kubernetes兼容的云原生资源。
这种方式的缺点在于,托管云服务是有成本的,它的定制能力通常会比较有限,并且不一定能提供你所需要的性能或延迟属性。同时,采取这种方式,会让你锁定到特定云供应商上。
这种方式最难实现,但是从长远来看,它会带给我们最大的灵活性和运维效率。我们可以使用Kubernetes提供的两个原生控制器来运行有状态的应用,即StatefulSet和DaemonSet。
StatefulSet是一个Kubernetes的控制器,它管理具有唯一身份标识的多个pod,并且它们是不能互相交换的(这与常规的Kubernetes Deployment有所差异,在Deployment中,pod是无状态的,可以根据需要经常销毁和重建)。
在StatefulSet中,每个pod都有一个持久化的、唯一的ID。每个pod可以有自己的持久化存储卷。如果Kubernetes需要扩展和伸缩的话,它会保持与外部用户或者集群中其他应用的现有连接。
DaemonSet是一个pod,Kubernetes能够确保它会在集群的所有节点,或者通过选择器定义的特定节点子集上运行。每当符合条件的节点被添加到集群中,这个pod都会在它上面启动。
对于需要以后台进程的形式运行的有状态应用来说,DaemonSet非常有用,比如监控或日志聚合应用。一般来讲,DaemonSets的灵活性较差,但是比StatefulSet更易于管理,资源的使用也更加可预测。
卷(volume)是一个Kubernetes实体,它提供了持久化的存储。Pod中所有的容器可以共享卷。我们可以借助持久化卷,让运行在同一个pod中的多个服务使用同一个挂载的文件系统。
在Kubernetes中,要授予容器对持久化存储的访问权,我们需要声明所需的卷以及所需的位置,以便于在容器的文件系统中挂载该卷。
Kubernetes中的常规存储卷会有一个确定的生命周期:每个卷都与pod的生命周期绑定。当pod处于活跃状态的时候,卷会保持在pod内,如果重启pod的话,卷会被重置。这个模型不适合有状态的工作负载,这也是Kubernetes引入持久化卷(Persistent Volumes)概念的原因。
Kubernetes PersistentVolumes(PV)是存在于集群级别的存储对象。将PV绑定到集群上会扩展它们的生命周期,不再局限于pod的生命周期。因为PV位于集群级别,所以pod可以共享数据。我们可以扩展持久化卷的大小和规模,但是不能减少它的大小。
我们有两种方式来提供PV:
PVC能够让Kubernetes用户请求存储。它的运行方式与pod类似,只不过pod消费节点资源,而PVC消费PV资源。除此之外,与pod能够请求特定级别的资源一样,PVC也可以请求特定的访问模式和大小。
PV和PVC的主要差异在于:
PV | PVC | |
---|---|---|
谁来创建它们 | 只有集群管理员和Kubernetes(通过动态供应)能够创建PV。 | 开发人员和用户都能创建PVC。 |
资源类型 | PV是一种集群资源。 | PVC是对存储资源的请求。 |
消费 | PVC消费PV资源。 | Pod消费PVC。 |
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: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
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负责确保所有或特定节点上会运行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。这个例子也来自官方文档。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
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中管理有状态工作负载。这包括以下关键的构件:
熟悉了这些构件后,你就可以直接在Kubernetes集群中创建安全的、可重复运行的有状态的工作负载了。就像Kubernetes中的所有内容一样,有状态的机制并不简单,需要时间来掌握,但当你掌握了这些机制后,它就会变得强大而可靠。稍微练习一下,你就能成为一个有状态Kubernetes的专家。
Gilad David Maayan是一位技术作家,曾与150多家技术公司合作,包括SAP、Imperva、三星NEXT、NetApp和Check Point,制作技术和思想领导力相关的内容,为开发者和IT领导层阐明技术解决方案。
查看英文原文:Best Practices for Running Stateful Applications on Kubernetes