在 Kubernetes 上开发应用程序的最佳实践。
应用开发
健康检查相关
就绪探针确定容器何时可以接收流量。
kubelet 执行检查并决定应用程序是否可以接收流量。
活性探针确定何时应重新启动容器。
kubelet 执行检查并决定是否应该重新启动容器。
-
容器具有readiness探针
*注意,readiness 和 liveness 没有默认值。
如果您没有设置就绪探测,kubelet 会假设应用程序在容器启动后就准备好接收流量。
如果容器需要 2 分钟才能启动,那么在这 2 分钟内对它的所有请求都将失败。
出现致命错误时容器崩溃
如果应用程序遇到不可恢复的错误,您应该让它崩溃。
此类不可恢复错误的示例包括:
• 未捕获的异常
• 代码中的错字(对于动态语言)
• 无法加载标头或依赖项
请注意,您不应发出失败的 Liveness 探测信号。
相反,您应该立即退出进程并让 kubelet 重新启动容器。 -
配置被动 Liveness 探针
Liveness 探针旨在在您的容器卡住时重新启动它。
考虑以下场景:如果您的应用程序正在处理无限循环,则无法退出或寻求帮助。
当进程消耗 100% CPU 时,它将没有时间回复(其他)Readiness 探针检查,它最终将从服务中删除。
但是,Pod 仍然注册为当前部署的活动副本。
如果您没有 Liveness 探测器,它会保持运行状态但与服务分离。
换句话说,该进程不仅不处理任何请求,而且还在消耗资源。
你该怎么办?
- 从您的应用程序公开端点
- 端点总是以成功响应进行回复
-
使用 Liveness 探针中的端点
请注意,您不应使用 Liveness 探针来处理应用程序中的致命错误并请求 Kubernetes 重新启动应用程序。
相反,您应该让应用程序崩溃。
只有在进程没有响应的情况下,才应将 Liveness 探针用作恢复机制。 -
Liveness 探针值与 Readiness 不同
当 Liveness 和 Readiness 探针指向同一端点时,探针的效果会结合在一起。
当应用程序的信号,它是没有准备好或者现场时,kubelet分离从服务容器,并删除它在同一时间。
您可能会注意到断开连接,因为容器没有足够的时间来耗尽当前连接或处理传入的连接。 -
不要把“Liveness/Readiness”探针的逻辑与你的程序解耦
对于Kubernetes,了解应用程序是否正在运行非常重要。如果Liveness/Readiness逻辑被解耦了在新进程中运行,则结果不准确。 -
不要在Liveness处理程序里实现任何逻辑
如果主线程正在运行,它需要返回状态200,如果不是,则返回5xx
这个探针让Kubernetes知道应用程序是正在运行还是停止运行。通过检查/.well-known/live的状态代码来做出决定,如果应用程序被检测为停止运行,Kubernetes会重新启动Pod。从可靠性的角度来看,如果应用程序的主线程已启动并正在运行,则存活探针的探测的结果为true,否则为false。 -
在“Readiness”探针的处理程序中实现逻辑,以便提供有关应用程序准备情况的详情
就绪探针让Kubernetes知道Pod是否已准备好接收HTTP请求。作为开发人员,在此处实现一些逻辑来检查应用程序的所有后端依赖的可用性非常重要。当实现就绪处理程序时,需要清楚的知道您的应用程序依赖于哪些功能。换句话说,在就绪处理程序里,需要运行所有步骤以保证应用程序已准备好接收和处理https请求,这非常重要。例如,如果应用程序需要建立与数据库的连接以准备处理HTTP请求,那么在“就绪”的处理程序中就必须检查是否已建立与数据库连接并能正常使用。 -
不要尝试在Readiness处理程序上重新建立应用程序的就绪状态。这个探针只是为了检查应用程序是否准备就绪,而不是让应用程序就绪。
优雅停止
当 Pod 终止时,该 Pod 的端点将从服务中删除,可能需要一些时间才能将端点更改通知给 kube-proxy 或 Ingress 控制器等组件。因此,尽管 Pod 被标记为已终止,但流量可能仍会流向该 Pod。
正确的正常关机顺序是:
- 收到 SIGTERM 后
- 服务器停止接受新连接
- 完成所有活动请求
- 然后立即杀死所有保持活动的连接并
- 进程退出
- 定义 postStart 和 preStop 处理函数
可能需要考虑使用容器生命周期事件(例如preStop 处理程序)来自定义删除 Pod 之前发生的事情。
https://pracucci.com/graceful-shutdown-of-kubernetes-pods.html
容错能力
由于以下几个原因,您的集群节点可能随时消失:
- 物理机的硬件故障
- 云提供商或管理程序故障
- 内核错误
部署在这些节点中的 Pod 也会丢失。
此外,还有其他场景可以删除 Pod:
- 集群管理员错误地删除虚拟机(实例)
- 云提供商或虚拟机管理程序中的故障导致的虚拟机消失
- 节点由于集群网络隔离从集群中消失
- 由于节点资源不足导致 pod 被驱逐。
上述任何一种情况都可能影响您的应用程序的可用性并可能导致停机。
您应该防止出现所有 Pod 都不可用且无法提供实时流量的情况。
- 部署运行多个副本
永远不要单独运行单个 Pod。
而是考虑将 Pod 部署为 Deployment、DaemonSet、ReplicaSet 或 StatefulSet 的一部分。
https://cloudmark.github.io/Node-Management-In-GKE/#replicas
- 避免将 Pod 放置在单个节点中
即使运行了多个 Pod 副本,也不能保证丢失节点不会中断服务。
考虑以下场景:单个集群节点上有 11 个副本。
如果该节点不可用,则 11 个副本将丢失,并且将停机。
通过配置Pod反亲和来调度到集群的不同节点中。
https://cloudmark.github.io/Node-Management-In-GKE/#replicas
- 设置pod干扰
当一个节点耗尽时,该节点上的所有 Pod 都会被删除并重新调度。
但是,如果负载很重并且丢失的 Pod 不能超过 50%,该怎么办?
排空事件可能会影响可用性。
为了保护部署免受可能同时关闭多个 Pod 的意外事件的影响,可以定义 Pod 中断干扰。
想象一下:“Kubernetes,请确保始终至少有 5 个 Pod 在为我的应用程序运行”。
如果最终状态导致该部署的 Pod 少于 5 个,Kubernetes 将阻止排空事件。
https://kubernetes.io/zh/docs/concepts/workloads/pods/disruptions/
资源利用
-
为所有容器设置内存limit和request
资源限制用于限制容器可以使用多少 CPU 和内存,并使用containerSpec指定.
调度程序使用这些作为指标之一来决定哪个节点最适合当前 Pod。
根据调度程序,没有内存限制的容器的内存利用率为零。
如果可在任何节点上调度会导致资源过度使用和潜在节点(和 kubelet)崩溃,则 Pod 数量不受限制。
这同样适用于 CPU 限制。
但是您是否应该始终为内存和 CPU 设置限制和请求?
是和否。
如果您的进程超过内存限制,则该进程将终止。
由于 CPU 是一种可压缩资源,如果您的容器超过限制,则进程会受到限制。
即使它可以使用当时可用的一些 CPU。 -
将 CPU 请求设置为 1 CPU 或以下
除非是计算密集型作业,否则建议将请求设置为 1 CPU 或以下。
https://www.youtube.com/watch?v=xjpHggHKm78
- 禁用 CPU 限制——除非有很好的用例
CPU 以每个时间单位的 CPU 时间单位来衡量。
cpu: 1 表示每秒 1 个 CPU 秒。
如果你有1个线程,你都不能消耗超过每秒1 CPU秒。
如果有2个线程,就可以消耗在0.5秒1个CPU秒。
8个线程可以0.125秒消耗1个CPU秒。
之后,您的进程将受到限制。
如果您不确定应用程序的最佳设置是什么,最好不要设置 CPU 限制。
https://medium.com/@betz.mark/understanding-resource-limits-in-kubernetes-cpu-time-9eff74d3161b
标记资源
标签是用于组织 Kubernetes 对象的机制。
标签是没有任何预先定义的含义的键 - 值对。
他们可以从荚服务,入口舱单,端点等被应用到所有资源在集群中
您可以使用标签的目的,所有者,环境或其他条件进行分类的资源。
所以,你可以选择一个标签的环境标记荚如“这个吊舱在生产环境中运行”或“支付团队拥有该部署”。
您也可以完全省略标签。
但是,您可能需要考虑使用标签来涵盖以下类别:
- 技术标签,例如环境
- 自动化标签
- 与您的业务相关的标签,例如成本中心分配
- 与安全相关的标签,例如合规性要求
- 资源已定义技术标签
可以使用以下标签标记您的 Pod:
- name, 应用程序的名称,例如“用户 API”
- instance, 标识应用程序实例的唯一名称(您可以使用容器映像标签)
- version,当前版本的应用程序(增量计数器)
- component,架构内的组件,例如“API”或“数据库”
- part-of,这是一个更高级别应用程序的名称,例如“支付网关”
- managed-by,用于管理应用程序操作的工具,例如“kubectl”或“Helm”
labels:
app.kubernetes.io/name: user-api
app.kubernetes.io/instance: user-api-5fa65d2
app.kubernetes.io/version: “42”
app.kubernetes.io/component: api
app.kubernetes.io/part-of: payment-gateway
app.kubernetes.io/managed-by: kubectl
- 资源定义了业务标签
可以使用以下标签标记您的 Pod:
- owner, 用于标识谁对资源负责
- project,用于确定资源所属的项目
- business-unit,用于标识与资源关联的成本中心或业务单位;通常用于成本分配和跟踪
labels:
owner: payment-team
project: fraud-detection
business-unit: “80432”
- 资源定义了安全标签
可以使用以下标签标记 Pod:
- confidentiality,资源支持的特定数据机密级别的标识符
- compliance,旨在遵守特定合规性要求的工作负载标识符
labels:
confidentiality: official
compliance: pci
日志
-
应用程序日志stdout和stderr
有两种日志记录策略:被动和主动。
使用被动日志记录的应用程序不知道日志记录基础结构并将消息记录到标准输出。
此最佳实践是十二要素应用程序的一部分。
在主动日志记录中,应用程序与中间聚合器建立网络连接,将数据发送到第三方日志记录服务,或直接写入数据库或索引。
主动日志记录被认为是一种反模式,应该避免使用。 -
避免使用 sidecar 进行日志记录(如果可以的话)
如果希望将日志转换应用于具有非标准日志事件模型的应用程序,可能需要使用 sidecar 容器。
使用 sidecar 容器,可以在将日志条目发送到其他地方之前对其进行规范化。
例如,可能希望在将 Apache 日志传送到日志记录基础架构之前将其转换为 Logstash JSON 格式。
但是,如果可以控制应用程序,则可以从一开始就输出正确的格式。
这样可以节省为集群中的每个 Pod 运行一个额外的容器。
缩放 -
容器不在其本地文件系统中存储任何状态
容器有一个本地文件系统,你可能想用它来持久化数据。
但是,将持久数据存储在容器的本地文件系统中会阻止包含的 Pod 被水平扩展(即,通过添加或删除 Pod 的副本)。
这是因为,通过使用本地文件系统,每个容器维护自己的“状态”,这意味着 Pod 副本的状态可能会随着时间的推移而发散。这会导致从用户的角度来看不一致的行为(例如,当请求命中一个 Pod 时,特定的用户信息可用,但当请求命中另一个 Pod 时不可用)。
相反,任何持久信息都应该保存在 Pod 之外的中心位置。例如在集群中的一个 PersistentVolume 中,或者在集群外的一些存储服务中甚至更好。 -
将 Horizontal Pod Autoscaler 用于具有可变使用模式的应用程序
HPA是一个内置的Kubernetes功能,监控应用程序,并自动添加或删除pod副本,根据当前的使用情况。配置 HPA 可让您的应用在任何流量条件下(包括意外峰值)保持可用和响应。
HPA 可以监控内置资源指标(Pod 的 CPU 和内存使用情况)或自定义指标。对于自定义指标,您还负责收集和公开这些指标,例如,您可以使用Prometheus和Prometheus Adapter 来完成。 -
不要使用处于测试阶段的 Vertical Pod Autoscaler
VPA 可以自动调整你的 Pod 的资源请求和限制,以便当一个 Pod 需要更多资源时,它可以得到它们(增加/减少单个 Pod 的资源称为垂直扩展,而不是水平扩展,这意味着增加/减少单个 Pod 的资源)/减少 Pod 的副本数)。 -
如果工作负载变化很大,请使用 Cluster Autoscaler
Cluster Autoscaler 可以通过添加或删除工作节点来自动扩展集群的大小。
当 Pod 由于现有工作节点上的资源不足而无法调度时,会发生纵向扩展操作。在这种情况下,Cluster Autoscaler 会创建一个新的工作节点,以便可以调度 Pod。类似地,当现有工作节点的利用率较低时,Cluster Autoscaler 可以通过从其中一个工作节点驱逐所有工作负载并将其删除来缩小规模。
使用 Cluster Autoscaler 对高度可变的工作负载很有意义,例如,当 Pod 的数量可能在短时间内成倍增加,然后又回到之前的值时。在这种情况下,Cluster Autoscaler 允许您通过过度配置工作节点来满足需求高峰,而不会浪费资源。
但是,如果您的工作负载变化不大,则可能不值得设置 Cluster Autoscaler,因为它可能永远不会被触发。如果您的工作负载缓慢且单调地增长,监控现有工作节点的利用率并在达到临界值时手动添加额外的工作节点可能就足够了。
配置和secret -
外部化所有配置
配置应该在应用程序代码之外维护。
这有几个好处。首先,更改配置不需要重新编译应用程序。其次,可以在应用程序运行时更新配置。第三,相同的代码可以在不同的环境中使用。
在 Kubernetes 中,配置可以保存在 ConfigMaps 中,然后可以将其挂载到容器中,因为卷作为环境变量传入。
在 ConfigMaps 中仅保存非敏感配置。对于敏感信息(例如凭据),请使用 Secret 资源。 -
将 Secret 挂载为卷,而不是环境变量
Secret 资源的内容应该作为卷挂载到容器中,而不是作为环境变量传入。
这是为了防止秘密值出现在用于启动容器的命令中,这可能会被不应访问秘密值的个人检查。