我已经在本地部署和管理的Kubernetes集群上工作了超过七年。可以说,容器极大地改变了托管领域的格局!
这带来了许多便利,以前需要复杂的设置的地方很多。有多个实例,rolling restart,零停机,健康检查等功能。在之前(实现VRRP解决方案、类似monit的监控重启、类似haproxy的负载均衡等)真是非常痛苦且耗时!
所以现在一切在 Kubernetes 中都更方便了,但如果你想为应用程序的整个生命周期定制最合适的环境,你仍然需要了解它的运作方式,并根据实际情况选择合适的策略。
在这篇文章中,我将解释为什么和怎样使用Kubernetes实现无停机时间的应用程序,以及如果您使用Qovery,它会为您处理哪些事情。
容器镜像的位置如果你对 Docker 比较熟悉了,拉取和使用一个容器镜像很简单。
然而,在生产环境中,如果你不是你所拥有的镜像,你通常不会依赖远程且不可控的镜像仓库。为什么呢?
- 注册表可能消失,你将无法再拉取镜像(在 Kubernetes 上会出现 ImagePullBackOff 错误)
- 你使用的镜像标签已被移除(同样会出现 ImagePullBackOff 错误)
- 镜像标签没有变化,但是镜像内容已经改变(非不可变镜像,因此镜像哈希值不同)。在集群中的不同节点上,镜像的行为可能不一致(取决于标签更改时间和集群节点的拉取时间)
- 它不符合你的安全要求(例如 SOC2、HIPPA 等),需要控制这些镜像。
有几个解决方案。其中一个方法是将源仓库中的容器镜像同步到您自己的注册表。在Qovery,我们用Dregsy同步我们在所有管理集群中部署的镜像,如 cert-manager、nginx-ingress、cluster-autoscaler 等。
Pod数量(应用实例)Qovery 会为你处理!默认情况下,我们会在你的云提供商账户上创建一个镜像仓库。当你部署应用时,我们首先检查这个镜像是否已经在你的镜像仓库中,如果存在就直接使用。如果不存在,我们会将镜像复制到你的镜像仓库中,并从那里使用它。对于你来说,这一切都是透明的,但你可以在部署日志中找到相关记录。因此,Qovery 确保不可用的外部容器仓库不会影响你的工作负载,或者在扩展过程中遇到配额限制……你可以在集群高级设置中调整这个时限。
听起来很显然,但如果你在寻找高可用,你需要至少2个Kubernetes副本(也就是2个pod)。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 2 # 指定运行 2 个与模板匹配的 pod
template:
关于 Kubernetes 的一个常见误解是:“我认为两个实例是不必要的,因为 Kubernetes 进行滚动更新,所以在关闭当前实例前会先启动一个新实例”。这是真的,但仅在进行部署更新时适用!
下面是一些例外情况:
- 当你运行应用程序的节点丢失(节点崩溃、硬件故障等)。你的应用程序/ Pod 将不得不从头开始:
1/ 拉取容器镜像(如果节点上还没有):拉取时间取决于容器镜像大小。
2/ 数据卷挂载(如果有):需要几秒钟(通常观察到的最大时间是1分钟)。
3/ 应用程序启动:取决于受影响的资源和应用程序类型(Java应用程序一般需要更长时间来启动)。
4/ 探针过程:它们在等待你的应用程序准备好服务,增加了几秒钟的时间。 - 当集群要求进行节点排水(例如在EKS升级或节点类型更改期间):
即将替换的节点上的 Pod 将收到优雅停止的 SIGTERM 信号。Pod 将从 运行中 状态切换到 终止 状态。在此期间,Kubernetes 服务将停止向 终止 Pod 发送流量。因此你不再接收流量并出现停机时间。然后会创建一个新的 Pod(参见上面的场景),在此期间,你将不再接收任何流量。不幸的是,Kubernetes 不会先创建一个新 Pod 再杀死旧的。在这种情况下,杀死操作会触发新的 Pod 的创建。
这就是为什么至少需要设置两个副本是避免宕机的最低要求(参阅 Pod 反亲和性部分)。
Qovery 允许你在应用的设置中调整实例数量。至少选择两个实例作为运行应用的基础。
Pod中断预算项PodDisruptionBudget (PDB) 是一个 Kubernetes 对象,它定义了在部署、维护期间或任何时候可以不可用的 pod 数量。这有助于确保即使某些 pod 被终止或驱逐,您的应用程序仍然保持可用性。
咱们来看一个例子,我的应用有三个 pod;我总是希望至少有两个 pod 在运行;我可以设置一个 PDB,这将保证我总能保持至少有两个 pod 在运行!
apiVersion: policy/v1 # 定义API版本为策略v1
kind: PodDisruptionBudget # 定义Pod中断预算
metadata: # 元数据
name: my-pdb # 资源名称为my-pdb
spec: # 规格
maxUnavailable: 1 # 确保最多有1个Pod不可用
selector: # 选择器
matchLabels: # 匹配标签
app: my-app # 应用标签为my-app
部署方案Qovery 还会为您处理这种情况。当你部署多个实例时,Qovery 会部署一个比如说 PDB,并确保至少有 10% 的应用程序保持可用。结合 Qovery 提供的自动扩展功能,您再也不用担心应用程序的可用性问题了。
Kubernetes部署有以下两种策略:
逐步更新:默认选项,使部署更加顺畅
滚动更新的部署方案动画
强制关闭:在启动应用程序的新版本前,强制应用程序完全关闭。
重新部署部署策略的动画
默认情况下,会应用滚动更新策略。但是,你可以通过如 Max Unavailable percent 和 Max Surge 等选项来调整部署方式,例如。
在这种情况下使用这些选项特别有用,网络流量很大时,你可以控制部署速度,从而减少对性能的影响。
Qovery 提供“更新策略选项”,“最大不可用百分比设置”和“最大扩展百分比”选项在应用和容器高级设置中.
自动回退部署不幸的是,自动回滚并不是 Kubernetes 默认提供的一项功能。通常,你需要使用第三方工具,比如 Helm、ArgoCD、Spinnaker 等工具来实现自动回滚。
很简单:大多数人想要的是,如果我的应用启动不了,就不要向它发送请求,并回退。
以 Helm 为例,一些选项很有趣,可以用来通过 Helm 实现目标。
-
``` 等待,
- — 等待工作,
- — 原子
为了获得良好的工作状态,探针必须被设置并正确配置(参见下一节)。
Qovery 提供自动回滚功能,并且默认开启。如果存活探针检测到 pod 启动有问题,将自动回滚。
探针(探头)探针常常被低估,但它们真的非常重要,有助于实现零宕机时间!
最重要的两个探针是用来验证你的应用程序的健康状况的,它们是“Liveness”和“Readiness”。如图所示,以下是解释这两个探针目标的图示,让内容更加清晰。
Kubernetes 探测工作流
存活探测**确保您的应用程序是活跃的,并决定这个 pod 是否应该继续运行或被终止。
如果存活检测失败了:
- Pod 停止接收数据
- Pod 重启,尝试恢复正常状态。任何后续的重启都将采用指数退避策略
就绪探针会判断是否将流量发送给您的 pod 中。
- 如果你遇到突发流量(且就绪探针还在正常工作),但你的应用程序开始变慢,那么就绪探针可以决定停止向你的应用程序发送流量。让它能够恢复到一个更健康的状态。
- 如果就绪探针没有收到响应,它不会重启你的容器。它只会让负载均衡器停止向该 pod 发送请求。
在什么情况下配置自定义的存活状态和就绪状态检查会比较有用?
“总是”是答案!当然,你可以使用简单的TCP检查,但它永远没有像自己在应用程序中构建的自定义探测那么可靠,例如,REST API中的专用端点。
Qovery 允许你在服务设置中直接访问探针。
初始启动延迟启动时间可能需要稍微延迟一下,这种情况可能出现在各种情况下。
- 您的应用程序在启动时使用了大量的CPU,比如SpringBoot应用程序。
- 您的应用程序在启动时需要执行更多操作(由于新功能),而您没有增加分配的CPU资源量。
- 您的应用程序需要在数据库中加载一个模式及其包含的数据,并且在数据库准备好之前无法提供服务。
- 还有很多类似的情况……
但这正是可能发生启动问题的示例。如果你遇到这种情况或想要预防它,你应该将 initialDelaySeconds 更新为一个更大的值,例如:
initialDelaySeconds: 更大的延迟时间值
例如,将 initialDelaySeconds 设置为一个更大的值。
存活探针:
初始延迟秒数: 60
HTTP GET请求:
……
注意:虽然有专门的启动探测,但大多数情况下可能用处不大。通常,initialDelaySeconds (initialDelaySeconds)选项足够了。
来自Qovery的“初始延迟秒”参数
要优雅的结束期这个 Kubernetes 选项并非直接关联到零停机时间特性,而是更多地关注忽视应用程序优雅停止重要性所带来的不利影响。
只有当应用程序能够捕获SIGTERM信号(即操作系统发送的终止信号)时,优雅地终止才能实现。
如果应用程序没有被编写为拦截 SIGTERM,它将直接硬性终止应用,即使有超过 30 秒的优雅终止时间,也可能导致数据丢失。
如果未能适当地处理SIGTERM,会导致一些问题:
- 糟糕的用户体验:用户遇到错误信息,看到空白页面,甚至更糟糕的情况
- 数据丢失:数据未提交,用户的事务也随之丢失
- 无法恢复的数据:磁盘写入数据时突然中断,应用程序无法应对
当然还有其他原因,但你能看出来让应用程序尽快关闭有多重要。
ℹ️ 硬件故障总是有可能发生的,因此你的应用应该总能在这种故障后恢复。然而,类似的常规故障不应该频繁出现。
给应用程序一些额外时间优雅地关闭通常是一个好做法(少于5分钟,约5分钟内)。Kubernetes 的默认值是30秒,但你可以通过设置 terminationGracePeriodSeconds 来调整这个时间。
宽限期终止设置可在Qovery的应用/容器高级设定中.
Pod 反亲和Pod 反亲和性功能允许你避免在同一个节点上运行多个相同的 Pod 实例。如果所有实例都在同一个节点上,一旦该节点发生故障,你就能想到可能会出现宕机时间。
为了避免这种情况发生,可以让Kubernetes避免将所有Pod放在同一个节点上。有两种选择:
- 软反亲和性 preferredDuringSchedulingIgnoredDuringExecution:它会尽量避免将两个实例所在的Pod放在同一个节点上,但如果做不到(因资源不足),则会将这两个实例所在的Pod放在同一个节点上。这种版本不仅成本效益高,而且在95%的情况下都能有效地工作。
- 硬反亲和性 requiredDuringSchedulingIgnoredDuringExecution:这是一种强制要求,即不能将两个Pod部署在同一节点上。但是如果你请求50个相同应用的Pod,你可能需要50个节点。这很快就会变得非常昂贵,成本显著增加。
这是它在 Kubernetes 中的样貌:
亲和性策略:
Pod亲和性:
强制性调度忽略执行:
- 标签选择器:
匹配表达式:
- key: security
操作符: 包含
values:
- S1
拓扑键: topology.kubernetes.io/zone
Pod反亲和性:
优选调度忽略执行:
- weight: 100
pod亲和性条款:
标签选择器:
匹配表达式:
- key: security
操作符: 包含
values:
- S2
拓扑键: topology.kubernetes.io/zone
在 Qovery,默认情况下,每个包含多个实例的应用程序将采用优选的反亲和性部署。在高级设置中,您可以找到选项来更改此默认行为。您可以使用这些选项来自定义默认设置。
资源列表资源是其中一个最常见的问题。当资源不足时,您的应用程序可能会:
- 停止
- 性能下降,
- 出错
在列表项之间添加空格以提高可读性,最终调整如下:
- 停止
- 性能下降,
-
出错
- 内存耗尽(OOM),导致被内核驱逐。因此你会经历停机时间,连接意外关闭等状况。
- 你的应用程序可能会长时间无响应,甚至在存活检查通过前都无法启动。运行在100%CPU可能导致自动扩展器添加过多实例。通常你只需利用现有的CPU数量。除非你清楚自己在做什么,否则将CPU限制设得低于100m通常不是一个好主意。
自动伸缩是避免在负载增加时停机的好方法。默认情况下,它基于CPU(也可以根据其他自定义指标进行调整)。这提供了一个简单的方法来自动增加更多实例(pod)。
⚠️ 自动伸缩不是魔法! 您必须确保您的应用程序在 Kubernetes 上正确设置(参见本文中讨论的所有主题)。
因此,例如,当您的 pod 的 CPU 使用率在短时间内超过 60% 时,Kubernetes 会启动一个新的 pod 来处理负载并减轻当前运行应用程序的负担。
这是一个Kubernetes上的例子。
apiVersion: autoscaling/v2
kind: HorizontalPod自动扩展器
spec:
...
minReplicas: 1
maxReplicas: 10
metrics:
- type: 指标
resource:
name: cpu
target:
type: 利用率
averageUtilization: 50
一旦您配置了不同的最小和最大实例数,Qovery 将会基于 CPU 使用情况自动创建一个自动伸缩器。您还可以在高级设置中调整默认行为。
结尾Kubernetes 能做到神奇的事情,但只有当应用程序尽可能地云原生且正确配置时,它才能真正展现其神奇之处。
总之,当你想要将你的应用迁移到Kubernetes时,至少你应该注意以下几个方面:
- 至少需要两个实例
- 添加健康检查(探测)
- 应用必须处理Sigterm信号终止
- 配置自动扩缩容
- 确保资源充足
- 启用Pod反亲和性
- 添加一个PDB(Pod可删除预算)
只要设置正确,Kubernetes 的体验极其出色,你将再也不会遇到停机的问题。
—
Qovery是一款让Kubernetes对开发人员更友好的平台