使用Kubernetes,你遇到过哪些错误?本文分享作者多年来使用Kubernetes最常犯的10个错误。在使用kubernetes的这些年里,我们看到了无数的集群(托管和非托管,在GCP、AWS和Azure上)和许多经常重复的错误。大多数这些错误都是我们自己犯的,这并不丢人!本文将向您展示我们经常遇到的一些问题,并讨论解决这些问题的方法。1.资源:请求和限制这无疑是这份清单中最值得关注和排名第一的。人们经常不设置CPU请求或将CPU请求设置得太低(以便我们可以在每个节点上容纳许多pod),结果导致节点过度使用。在高需求期间,节点的CPU满负荷运行,我们的负载只获得“它请求的东西”,这会限制CPU,导致应用程序延迟和超时等指标增加。BestEffort(不要这样做):resources:{}verylowcpu(不要这样做):resources:requests:cpu:"1m"另一方面,当节点的CPU不可用时,启用CPU节流可能会导致问题充分利用的Pod会受到不必要的限制,这也会导致延迟增加。也有关于Linux内核中的CPUCFS配额的讨论,以及由于设置CPU限制和关闭CFS配额而导致的CPU节流。CPU节流导致的问题多于它解决的问题。有关更多信息,请查看以下链接。内存过度使用会给我们带来更多麻烦。达到CPU限制将导致节流,达到内存限制将导致Pod被杀死。见过OOMkill(因内存不足而被杀死)吗?这就是我们的意思。想尽量减少这些情况?然后不要过度使用内存,并使用GuaranteedQoS(服务质量)将内存请求设置为等于限制,如下例所示。有关更多信息,请参阅HenningJacobs(Zalando)的演讲。https://www.slideshare.net/try_except_/optimizing-kubernetes-resource-requestslimits-for-costefficiency-and-latency-highloadBurstable(容易带来更多OOMkilled):resources:requests:memory:"128Mi"cpu:"500m"limits:memory:"256Mi"cpu:2Guaranteed:resources:requests:memory:"128Mi"cpu:2limits:memory:"128Mi"cpu:2那么我们在设置资源的时候有什么技巧呢?我们可以使用metrics-server查看Pod(及其中的容器)当前的CPU和内存使用情况。您可能已经启用了它。只需运行以下命令:kubectltoppodskubectltoppods--containerskubectltopnodes这些只会显示当前使用情况。这足以大致了解数据,但在一天结束时,我们希望及时看到这些使用指标(以回答诸如:昨天早上的CPU使用率峰值等问题)。为此,我们可以使用Prometheus和DataDog等工具。他们只是从metrics-server接收指标数据并存储它,然后我们可以查询和绘制这些数据。VerticalPodAutoscaler可以帮助我们自动化这个手动过程——实时查看cpu/内存使用情况,并根据这些数据设置新的请求和限制。https://cloud.google.com/kubernetes-engine/docs/concepts/verticalpodautoscaler有效利用计算资源并不是一件容易的事,就像不停地玩俄罗斯方块一样。如果我们发现自己在计算资源上花了很多钱,但平均利用率很低(比如说10%左右),那么我们可能需要AWSFargate或者基于VirtualKubelet的产品。他们大多使用无服务器/按需付费的计费模式,这对我们来说可能更便宜。2.LivenessandReadinessprobes默认情况下,Kubernetes没有指定任何liveness和readinessprobes。有时它会永远这样......但是如果出现不可恢复的错误,我们的服务将如何重新启动?负载均衡器如何知道特定Pod可以开始服务流量,或者可以处理更多流量?人们往往分不清两者的区别。如果探测失败,liveness探测将重启PodReadiness探测。失败时,它将断开失败的Pod与Kubernetes服务的连接(我们可以使用kubectlgetendpoints检查这一点)并且不会将任何流量发送到Pod。它们都贯穿于Pod的生命周期。这个非常重要。人们通常认为就绪探测仅在开始时运行,以确定Pod何时就绪并可以开始处理流量。但这只是它的一个用例。它的另一个用例是在pod的生命周期中判断它是否太热而无法处理过多的流量(或昂贵的计算),因此我们不会让它做更多的工作,而是让它冷却下来;等待就绪探测成功,我们将向其发送更多流量。在这种情况下(当就绪探测失败时),如果活性探测也失败,效率会非常低。为什么我们要重启一个健康的pod做很多工作?有时最好不要指定任何探测器,而不是指定错误的探测器。如上所述,如果livenessprobe等于readinessprobe,我们就麻烦大了。我们可能一开始只指定readiness探针,因为liveness探针太危险了。https://twitter.com/sszuecs/status/1175803113204269059https://srcco.de/posts/kubernetes-liveness-probes-are-dangerous.html如果您的任何共享依赖项失败,请不要让任何探测失败,否则会导致所有Pod的级联失败。我们搬起石头砸自己的脚。https://blog.colinbreck.com/kubernetes-liveness-and-readiness-probes-how-to-avoid-shooting-yourself-in-the-foot/3.在我们集群中的所有HTTP服务上启用负载均衡器那里里面可能有很多HTTP服务,我们要对外暴露这些服务。如果我们将Kubernetes服务公开为类型:LoadBalancer,那么它的控制器(取决于提供者)将提供并协调外部负载均衡器(不一定是L7,更可能是L4lb);当我们创建很多这样的资源时,它们会变得很昂贵(外部静态ipv4地址,计算,按秒计费...)。在这种情况下,共享同一个外部负载均衡器可能会更好,此时我们将服务公开为类型:NodePort。或者更好的是,将nginx-ingress-controller(或traefik)之类的东西部署为暴露给这个外部负载均衡器的单个NodePort端点,并根据Kubernetes入口资源在集群中路由流量。其他相互通信的集群内(微)服务可以通过ClusterIP服务和开箱即用的DNS服务发现进行通信。小心不要使用他们的公共DNS/IP,因为这可能会影响他们的延迟和云成本。4.没有Kubernetes感知的集群自动缩放在集群中添加节点或从集群中删除节点时,不应考虑这些节点的CPU利用率等简单指标。在调度pod时,我们需要根据许多调度约束做出决策,例如pod和节点之间的亲缘关系、污点和容忍度、资源请求、QoS等。让不了解这些约束的外部自动缩放器处理缩放可能会带来麻烦.假设有一个新的Pod需要调度,但是请求了所有可用的CPU,Pod卡在了Pending状态。但是外部自动缩放器查看当前的平均CPU使用率(而不是请求数)并决定不扩展(不添加新节点)。因此Pod也不会被调度。缩小规模(从集群中删除节点)总是更难。假设我们有一个有状态的Pod(附加了一个持久卷),因为持久卷(持久卷)通常是属于特定可用性区域的资源并且不会在该区域中复制,我们的自定义自动缩放器删除一个带有节点的pod有了这个Pod,但是调度器不能把它调度到别的节点上,因为这个Pod只能留在持久化盘所在的可用区。Pod会再次卡在Pending状态。社区广泛使用cluster-autoscaler,它在集群中运行并与大多数主要的公共云提供商API集成;它了解所有这些限制,并且可以在上述情况下进行扩展。它还会计算出我们是否可以在不影响我们设置的任何约束的情况下优雅地缩小规模,从而节省我们的计算成本。https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler5。不使用IAM/RBAC功能不使用IAM用户永久存储机器和应用程序密钥,而是使用角色和服务帐户生成临时密钥。我们经常通过在应用程序配置中硬编码访问和机密来看到这一点,并且在使用CloudIAM时从不轮换机密。我们应该尝试使用IAM角色和服务帐户而不是用户。请跳过kube2iam,直接使用服务帐户的IAM角色,正如?těpánVrany在这篇博文中所解释的那样。https://blog.pipetail.io/posts/2020-04-13-more-eks-tips/apiVersion:v1kind:ServiceAccountmetadata:annotations:eks.amazonaws.com/role-arn:arn:aws:iam::123456789012:role/my-app-rolename:my-serviceaccountnamespace:default只有一个注解。这并不难做到。此外,不要在不需要时向服务帐户或实例配置文件授予管理员和集群管理员权限。这有点难,尤其是对于k8sRBAC,但仍然值得一试。6.Podselfanti-affinities一个deployment有3个Podreplicas在运行,然后节点关闭,所有replicas关闭。这有什么理由吗?所有副本都在一个节点上运行?Kubernetes不应该很棒并提供高可用性吗?!我们不能依赖Kubernetes调度器来为我们的pod实施反亲和。我们必须明确定义它们。//omittedforbrevitylabels:app:zk//omittedforbrevityaffinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:"app"operator:Invalues:-zktopologyKey:"kubernetes.io/hostname"就是这样。这确保Pod被安排在不同的节点上(这仅在调度时检查,而不是在执行时检查,因此requiredDuringSchedulingIgnoredDuringExecution)。我们讨论的是不同节点名称(topologyKey:"kubernetes.io/hostname")上的podAntiAffinity,而不是不同可用区上的podAntiAffinity。如果您真的需要高水平的可用性,您可以更深入地研究这个主题。7.没有PodDisruptionBudget我们在Kubernetes上运行生产工作负载。我们的节点和集群必须不时升级或退役。PodDisruptionBudget(pdb)是一个API,用于在集群管理员和集群用户之间提供服务保证。确保创建pdb以避免由于节点耗尽而造成不必要的服务中断。apiVersion:policy/v1beta1kind:PodDisruptionBudgetmetadata:name:zk-pdbspec:minAvailable:2selector:matchLabels:app:zookeeper作为集群用户,我们可以告诉集群管理员:“嘿,我这里有一个zookeeper服务,无论如何我都想要它.至少有2个副本总是可用的”。我在这篇博文中更深入地讨论了这个话题。https://blog.marekbartik.com/posts/2018-06-29_kubernetes-in-production-poddisruptionbudget/8。共享集群中的多个租户或环境Kubernetes命名空间不提供任何强隔离。人们似乎期望,如果将非生产工作负载放入一个命名空间,然后将生产工作负载放入生产命名空间,那么这些工作负载将永远不会相互影响。我们可以通过某种程度的公平分配(例如资源请求和限制、配额、优先级)和隔离(例如亲和力、容忍度、污点或节点选择器)来“物理地”分离数据平面上的负载,但这种分离是相当复杂。如果我们需要在同一集群中同时拥有两种类型的负载,就必须承担这种复杂性。如果我们不需要局限于一个集群,并且添加另一个集群(例如在公共云上)更便宜,那么我们应该将它们放在不同的集群中以获得更强的隔离级别。9.externalTrafficPolicy:Cluster经常看到这种情况,集群内的所有流量都路由到一个NodePort服务,默认使用externalTrafficPolicy:Cluster。这意味着在集群中的每个节点上都打开了NodePorts,这样我们就可以选择一个与所需服务(一组Pod)进行通信。通常,NodePort服务所针对的那些Pod实际上只在这些节点的一个子集上运行。这意味着如果我与一个没有运行pod的节点通信,它会将流量转发到另一个节点,从而导致额外的网络跃点并增加延迟(如果节点位于不同的可用区或数据中心,则延迟可能会高并且会带来额外的出口成本)。设置externalTrafficPolicy:Local在Kubernetes服务上不会在每个节点上打开NodePort,只会在pod实际运行的节点上打开。如果我们使用外部负载均衡器来检查其端点的健康状况(就像AWSELB所做的那样),它只会将流量发送到应该接收流量的节点,这可以改善延迟并减少计算开销,降低导出成本并提高稳健性。我们可能会将traefik或nginx-ingress-controller之类的东西公开为NodePort(或使用NodePort的负载均衡器)来处理入口HTTP流量路由,并且此设置可以大大减少此类请求的延迟。这是一篇很棒的博客文章,更深入地讨论了externalTrafficPolicy及其权衡。https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies10。把集群当成宠物+控制面压力太大你有没有过这样的经历:服务端取Anton和HAL9000或者Colossus之类的名字(都是词干的名字,翻译笔记),或者随机生成ids给节点,但给集群起一个有意义的名字?也可能是这样的经历:一开始用Kubernetes做概念验证,集群命名为“testing”,但在生产环境没有改名,没人敢碰?(真实故事)宠爱集群可不是闹着玩的,我们可能需要不时删除集群,进行灾难恢复并管理我们的控制平面。害怕接触控制平面不是一个好兆头。etcd挂了?好吧,我们有大麻烦了。相反,不应过度使用控制平面。也许控制平面随着时间的推移变慢了。这很可能是因为我们创建了很多对象而不旋转它们(使用helm时的常见情况,它的默认设置不旋转configmaps/secrets的状态,我们最终在控制平面中有数千个对象),或者是因为我们一直在从kube-api中删除和编辑很多东西(用于自动缩放、CI/CD、监控、事件日志、控制器等)。此外,检查托管Kubernetes提供的“SLA”/SLO和保证。供应商可以保证控制平面(或其子组件)的可用性,但不能保证发送给它的请求的p99延迟级别。也就是说,即使我们在kubectlgetnodes之后花了10分钟才得到正确的结果,也没有违反服务保证。11.奖励:使用最新的标签是经典的。我觉得最近没那么普遍了,因为被骗的人太多了,所以都不用:latest了,开始加版本号了。现在安静了!ECR有一个强大的标签不变性特性,绝对值得一试。https://aws.amazon.com/about-aws/whats-new/2019/07/amazon-ecr-now-supports-immutable-image-tags/12。总结不要指望所有问题都能自动解决-Kubernetes不是灵丹妙药。即使在Kubernetes上,一个糟糕的应用程序就是一个糟糕的应用程序(事实上,它可能更糟)。如果我们不小心,我们最终会遇到一堆问题:太复杂、压力太大、控制平面缓慢、没有灾难恢复策略。不要指望开箱即用的多租户和高可用性。请花点时间让我们的应用程序成为云原生的。
