Kubernetes 反模式避坑指南
Kubernetes 是部署运维云原生应用的事实标准和强大工具,本文介绍了在使用 Kubernetes 过程中的常见问题,并提供了避坑指南。原文: Most common mistakes to avoid when using Kubernetes: Anti-Patterns
💮 简介
无论你是刚开始使用 Kubernetes,还是正在考虑将其用于应用程序,肯定都知道 Kubernetes 是一套强大的工具,可用于管理支持可伸缩、高可用性的分布式云原生应用程序,但很多人都会犯一些常见错误。
本文将探讨使用 Kubernetes 时最常见的一些陷阱,并提供如何避免踩坑提示。
📢 公告:我刚刚推出了一个 GitHub 仓库,专门用于收集资源、练习和实验,以帮助用户从基础开始学习 Kubernetes。该资源库旨在提供实用练习,指导用户使用 Kubernetes 部署、管理和扩展容器化应用程序。欢迎提供反馈意见,以增强和改进学习体验。链接:https://github.com/seifrajhi/Kubernetes-practical-exercises-Hands-on
🙅 未设置资源请求
这绝对是最值得关注的点,在本榜单排第一位。
通常情况下,要么没有设置 CPU 请求,要么设置得很低(这样我们就能在每个节点上安装大量 pod),因此节点会超负荷运行。在需求旺盛时,节点的 CPU 会被用光,而工作负载只能获得所请求的资源,CPU 会被限流(CPU throttled) ,从而导致应用程序出现延迟、超时等情况。
BestEffort(最好不要用):
非常少的 CPU(最好不要):
另一方面,即使节点的 CPU 没有被充分利用,CPU 限制也会对 pod 的运行造成不必要的限制,这同样会导致延迟增加。
关于 Linux 内核中的 CPU CFS 配额,以及基于 CPU 设置限制和关闭 CFS 配额的 CPU 限流,有很多讨论。
CPU 限制带来的问题可能比解决的更多。
内存过量使用会给我们带来更多麻烦。达到 CPU 限制会导致限流,而达到内存限制则会导致 pod 被杀。看到过 OOMkill 吗?没错,说的就是这个。想尽量减少这种情况的发生频率吗?那就不要过度占用内存,并使用 Guaranteed QoS 将内存请求设置为等于内存限制,就像下面的例子一样。请在 Henning Jacobs(Zalando)的演讲中阅读更多相关内容。
Burstable(更容易经常被 OOMkilled):
Guaranteed:
那么,在设置资源时,有什么可以帮助我们呢?
可以通过 metrics-server 查看 pod(以及其中的容器)当前的 CPU 和内存使用情况。很可能你已经用过了,只需运行以下命令即可:
不过,显示的只是当前的使用情况。也很不错了,可以有个大致的了解,但我们最终还是希望及时看到使用指标(从而回答以下问题:在高峰期、昨天上午等的 CPU 使用率是多少)。为此,可以使用 Prometheus、DataDog 和其他许多工具,它们只需从 metrics-server 获取指标并存储,然后就可以查询和绘制图表了。
VerticalPodAutoscaler 可以帮助我们将这一过程自动化,辅助我们及时查看 CPU/内存使用情况,并根据这些情况重新设置新的资源请求和限制。
有效利用计算机并非易事,这就像一直在玩俄罗斯方块。如果你发现自己为计算支付了高昂的费用,而平均利用率却很低(例如约 10%),可能需要查看 AWS Fargate 或基于 Virtual Kubelet 的产品,它们更多利用的是无服务器/按使用付费的计费模式,价格可能更便宜。
🚫 省略健康检查
将服务部署到 Kubernetes 时,健康检查在维护服务方面发挥着重要作用。
在 Kubernetes 环境中,健康检查的利用率非常低😿。通过健康检查,可以密切关注 pod 及其容器的健康状况。
Kubernetes 有三种主要工具可用于健康检查:
存活检查(Liveness Check) 允许 Kubernetes 检查应用程序是否存活,每个节点上运行的 Kubelet 代理都会使用存活探针来确保容器按预期运行。就绪检查(Readiness checks) 在容器的整个生命周期内运行,Kubernetes 使用该探针了解容器何时准备好接受流量。启动探针(startup probe) 确定容器应用何时成功启动,如果启动检查失败,就会重新启动 pod。
🛑 使用 latest 标签
这个问题非常经典。latest 标签没有说明性,难以使用。关于在生产环境中使用 images:latest 标签的容器,Kubernetes 文档说得很清楚:
在生产环境中部署容器时,应避免使用 :latest 标签,因为它很难跟踪运行的是哪个版本的映像,也很难回滚。
最近我们很少看到这种情况了,因为很多人被伤害了太多次,因此不再使用:latest
,每个人都开始把版本固定下来。
ECR 有一个很棒的标签不变性功能,绝对值得一试。
📛 容器权限过高
容器权限过高是指容器被赋予了过多的权限,比如可以访问普通容器无法访问的资源。
这是开发人员在使用 Kubernetes 时常犯的错误,而且会带来安全风险。
例如,在 Docker 容器内运行 Docker 守护进程就是特权容器的一个例子,而特权容器并不一定安全。
为避免这种情况,建议避免为容器提供 CAP_SYS_ADMIN 能力,因为它占所有内核漏洞的 25% 以上。
此外,避免赋予容器完整权限以及主机文件系统权限也很重要,因为这两个权限会让容器可以通过替换恶意二进制文件来入侵整个主机。
为防止容器权限过高,必须仔细配置权限设置,切勿以高于所需的权限运行进程。
此外,通过监控和日志来发现和解决问题也很重要。
👀 缺乏监控和日志记录
Kubernetes 环境中缺乏监控和日志记录会对其安全性和整体性能造成损害,日志记录和监控不足会给事件调查和响应工作带来挑战,从而难以有效发现和解决问题。
一个常见的陷阱是,由于缺乏相关日志或指标,无法找到 Kubernetes 平台和应用程序中的故障点。
要解决这个问题,必须设置适当的监控和日志工具,如 Prometheus、Grafana、Fluentd 和 Jaeger,以收集、分析和可视化指标、日志和跟踪,深入了解 Kubernetes 环境的性能和健康状况。
通过实施强大的监控和日志记录实践,企业可以有效关联信息,获得更深入的见解,并克服与 Kubernetes 环境的动态和复杂性相关的挑战。
😵 所有对象使用默认命名空间
对 Kubernetes 中的所有对象使用默认命名空间会带来组织和管理方面的挑战。
默认(default
)命名空间是创建服务和应用程序的默认位置,除非明确指定,否则也是活跃命名空间。
完全依赖默认命名空间会导致群集中不同组件或团队缺乏隔离和组织,从而导致资源管理、访问控制和可见性方面的困难。为避免这种情况,建议为不同项目、团队或应用创建自定义命名空间,以便在 Kubernetes 集群内实现更好的组织、资源分配和访问控制。
通过利用多个命名空间,用户可以有效分割和管理资源,提高 Kubernetes 环境的整体运行效率和安全性。
➖ 缺少安全配置
在部署应用程序时,应始终牢记安全性。那么,在安全方面有哪些最重要的事项需要考虑呢?例如,使用集群外部可访问的端点、secrets 缺乏保护、不考虑如何安全运行特权容器等。
Kubernetes 安全是任何 Kubernetes 部署不可分割的一部分。安全挑战包括:
授权 -- 身份验证和授权对于控制 Kubernetes 集群中的资源访问至关重要。
网络 -- Kubernetes 网络涉及管理 overlay 网络和服务端点,以确保容器之间的流量在集群内路由的安全性。
存储 -- 确保集群中的存储安全,包括确保数据不会被未经授权的用户或进程访问。
Kubernetes API 服务器有 REST 接口,可以访问存储的所有信息。这意味着用户只需向 API 发送 HTTP 请求,就能访问 API 中存储的任何信息。为防止未经身份验证的用户访问这些数据,需要使用用户名/密码或基于令牌的身份验证等方法为 API 服务器配置身份验证。
这不仅关系到群集本身的安全,还关系到群集上的 secrets 和配置的安全。为了保护群集免受漏洞攻击,需要在群集上配置一套安全控制。
利用基于角色的访问控制(RBAC)确保 Kubernetes 集群安全就是这样一种强大的安全控制。基于角色的访问控制可根据分配给用户的角色限制对资源的访问,从而确保 Kubernetes 集群的安全。这些角色可以配置为"管理员(admin)"或"运维人员(operator)"。
管理员角色拥有完整访问权限,而运维人员角色对群集内的资源拥有有限的权限。我们可以通过这种方式控制和管理访问群集的任何人。
🙅缺少 PodDisruptionBudget:
当你在 kubernetes 上运行生产工作负载时,节点和集群时常需要升级或退役。PodDisruptionBudget (pdb) 相当于集群管理员和集群用户之间的服务保证 API。
确保创建 pdb
,以避免因节点耗尽而造成不必要的服务中断。
作为集群用户,可以这样告诉集群管理员:"嘿,我这里有一个数据库服务,无论如何,我希望至少有两个副本始终可用。"
😠pod 的 anti-affinities 功能:
例如,在运行某个部署的 3 个 pod 复本时,节点宕机,所有复本也随之宕机。什么意思?所有副本都在一个节点上运行?Kubernetes 不是应该提供 HA 吗?
你不能指望 kubernetes 调度器为 pod 强制执行anti-affinites
,而是必须显式定义。
就是这样。这将确保 pod 被调度到不同的节点上(仅在调度时检查,而不是在执行时,因此需要配置requiredDuringSchedulingIgnoredDuringExecution
)。
我们讨论的是不同节点上的 podAntiAffinity - topologyKey:"kubernetes.io/hostname"
,而不是不同的可用区。如果真的需要适当的 HA,请深入了解这一主题。
⚖️用于所有 HTTP 服务的负载均衡:
集群中可能有很多 http 服务,希望向外部公开。
如果将 kubernetes 服务以type: LoadBalancer
对外暴露,其控制器(特定于供应商)将提供和配置外部负载均衡器,而这些资源可能会变得很昂贵(外部静态 IPv4 地址、计算、按秒计价......),因为我们需要创建很多这样的资源。
在这种情况下,共享同一个外部负载均衡器可能更有意义,可以将服务公开为 type: NodePort
类型,或者部署类似 nginx-ingress-controller(或 traefik 或 Istio)的东西作为暴露给外部负载均衡器的单个 NodePort 端点,并根据 kubernetes ingress 资源在集群中路由流量,这样会更好。
集群内其他(微)服务可通过 ClusterIP 服务和开箱即用的 DNS 服务发现进行通信。
注意不要使用公共 DNS/IP,因为这可能会影响时延和云成本。
非 kubernetes 网络感知集群自动扩容
在向集群添加或移除节点时,不应考虑简单指标,如节点 CPU 利用率等。在调度 pod 时,需要根据大量调度约束条件(如 pod 和节点亲和性、taints 和 tolerations、资源请求、QoS 等)来做出决定,如果外部自动调度器不了解这些约束条件,可能会造成麻烦。
想象一下,有一个新的 pod 需要调度,但所有可用的 CPU 都被请求完了,该 pod 被卡在 "Pending" 状态。外部自动扩容器看到的是当前使用的 CPU 平均值(而不是请求的),因此不会触发扩容(不会添加新节点),从而造成 pod 无法被调度。
缩容(从集群中移除节点)总是比较困难。试想一下,有一个有状态的 pod(附加了持久卷),由于持久卷通常是属于特定可用性区域的资源,在区域内没有复本,因此自定义自动扩容器会移除装有此 pod 的节点,而调度器无法将其调度到其他节点上,因为它受到装有持久化存储的唯一可用区的极大限制,Pod 再次卡在了 Pending 状态。
社区正在广泛使用 cluster-autoscaler,它可在集群中运行,并与大多数主要公有云供应商的 API 集成,了解所有限制,并会在上述情况下进行扩容。它还能在不影响我们设置的任何限制条件的情况下优雅的缩容,为我们节约计算费用。
📌 结论
总之,Kubernetes 是管理容器化应用的利器,但也有自己的一系列挑战。为避免常见错误和陷阱,必须密切关注与 Kubernetes 的交互,并了解与已部署服务的交互方式之间的差异。
不要指望一切都能自动运行,要投入一些时间让应用成为云原生的。通过避免这些错误,高效完成 Kubernetes 部署,并提高 Kubernetes 环境的稳定性、性能和安全性。
你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
版权声明: 本文为 InfoQ 作者【俞凡】的原创文章。
原文链接:【http://xie.infoq.cn/article/16f0fca5503443bbac675faf8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论