行业报告 AI展会 数据标注 标注供求
数据标注数据集
主页 > 数据挖掘 正文

使用Kubernetes最常犯的10个错误

在我们多年使用Kubernetes的经验中,我们有幸看到了很多集群(包括托管和非托管——在GCP,AWS和Azure上),并且看到了一些错误不断的被重复。这没什么丢人的,大多数错误我们也会犯!
 
我会试着展示一些我们经常看到的错误,并且讨论怎样避免这样的错误。
 
资源(resources)——requests和limits
这个是最值得注意以及最先拿出来讲解的。
 
通常我们要么不设置CPU请求(request)要么将CPU请求设置得很低(这样我们就可以在每个节点上容纳很多Pod),因此节点的使用量会过大。在需求旺盛的时候,节点的CPU会被充分利用,我们的工作负载仅获得“所请求的部分”,并且受到CPU的限制,从而导致应用程序时延,超时等问题。
 
BestEffort(不要使用):
resources: {}
非常低的CPU(不要使用):
resources:
  requests:
    cpu: "1m"
另一方面,CPU设限可能会限制Pod的使用,即使节点的CPU还没有被充分使用,这又会导致应用程序时延的增加。有一个公开的讨论是关于内核中CPU CFS配额(quota)的以及基于设置CPU限额(limits)和关闭CFS配额相关的CPU限流(throttling)。
 
CPU限额可能会导致更多的问题无法解决。可以在下面的链接中查看更多信息。
 
内存的过量使用会带来更多的问题。达到CPU的限额会产生限流后果,达到内存限额会导致pod被杀掉。曾经见过OOMkill吗?没错,这就是我们现在讨论到的。想要最小化这种事情发生的频率?不要过度使用内存,并且使用Guaranteed QoS(服务质量)设置内存请求等于限额,就像下面的例子所示。可以参考Henning Jacobs’ (Zalando)[1]获得更多这方面的主题。
 
突增型的(更有可能导致OOMkilled发生):
resources:
  requests:
    memory: "128Mi"
    cpu: "500m"
  limits:
    memory: "256Mi"
    cpu: 2
保证型的(Guaranteed):
resources:
  requests:
    memory: "128Mi"
    cpu: 2
  limits:
    memory: "128Mi"
    cpu: 2
 
那么在设置资源时还有什么可以帮助你的呢?
你可以通过metrics-server查看Pod(以及Pod中的容器)的当前CPU和内存的使用情况。很有可能你已经在使用它了。可以通过如下方式简单的使用它们:
kubectl top pods
kubectl top pods --containers
kubectl top nodes
 
但是这些都是展示的当前使用情况。如果你只是想要获得粗略的情况,这些足够了,但是你有可能想要及时查看这些使用情况指标(比如:CPU峰值使用情况,昨天早上使用情况等)。对于这种需求,你可以使用Prometheus,DataDog以及很多其他工具。这些工具仅仅是从metrics-server获取指标并且存储它们,然后你可以查询并且以图表方式查看。
 
VerticalPodAutoscaler[2]可以帮助你自动完成这个手动过程--及时查看CPU、内存使用情况并且基于这些指标设置新的requests和limits。
 
有效的利用你的电脑不是一件容易的任务。就像一直不停的玩俄罗斯方块一样。如果你发现你花了很多钱配置了一个高配电脑,但是利用率很低(比如说大约10%),你可能想去查看基于AWS Fargate或者Virtual Kubelet的产品,这些产品更多的使用无服务(Serverless)/按使用量计费模式,这种模式对你来说可能更便宜。
 
Liveness和Readiness探针
默认情况下Liveness和Readiness探针是没有被设置的。并且有时候一直保持这种情况。
 
但是当出现不可恢复的错误,你的服务该怎样重启?负载平衡器如何知道特定的Pod可以开始处理流量?或者处理更多的流量?
 
人们通常不知道这两者的区别。
 
Liveness探针在失败的时候会重启Pod
Readiness探针在失败后会将失败的Pod和Kubernetes服务(可以通过kubectl get endpoints检测)断开连接,并且流量不再分发给这个Pod,除非探测再次成功
 
在整个Pod的生命周期这两个都会一直运行。这是很重要的。
 
人们常常认为Readiness探针仅仅在开始的时候运行,用于通知Pod什么时候准备好了并且可以接受流量了。但是这仅仅是其中的一点。
 
另一方面是在一个Pod的生命周期中,这个Pod在处理太多流量(或者昂贵的计算)变得太繁忙(hot),这样我们就不给她安排更多的工作,同时等待她不繁忙,然后Readiness探针成功,接着我们可以重新给她安排更多的工作。在这种情况下(当Readiness探针失败时),如果Liveness探针也失败,那么它会产生非常适得其反的效果。为什么你会重启一个健康的并且正在做很多工作的Pod?
 
有时候一个探针都不定义要比他们定义错了好。正如上面提到的,如果Liveness探针和Readiness探针定义相同,你就会遇到大麻烦。你可能想在开始的时候只定义Readiness探针,因为Liveness探针很危险。
 
如果你共享依赖的任何一个Pod挂机了,那么不要让这些探针失败,因为它会导致所有相关Pod级联失败。那你就是自己搬起石头砸自己的脚。
 
为每个http服务都设置负载均衡
很大可能在你的集群中存在很多个http服务,这些服务可能都会暴露给外部世界。
 
如果你按照type:LoadBalancer方式把Kubernetes服务暴露出去,它的控制器(和特定提供商(vendor)相关)会提供以及调谐一个外部负载均衡器(不一定是L7负载均衡器,更可能是L4负载均衡器),并且如果你创建很多资源(外部的静态IPv4地址,算力,每秒钟的价格……),这些资源可能会很昂贵。
 
在这种情况下,共享一个外部负载均衡器可能更有意义,并且你可以把你的服务按照type:NodePort方式暴露出去。或者更好的方式是部署类似于nginx-ingress-controller(或traefik)作为单个NodePort端点,然后再把它暴露给外部的负载均衡器,并且在集群中基于Kubernetes的Ingress资源方式路由流量。
 
集群中其他需要互相通信的(微)服务可以通过ClusterIP服务或者开箱即用的dns服务发现来完成。注意不要使用公共DNS / IP,因为这可能会影响服务时延和云成本。
 
非Kubernetes感知的集群自动扩展
当向集群中添加或者从集群中删除节点时,你不应该只考虑一些简单的指标,比如节点CPU的使用率。当调度Pod时,你可以基于许多调度约束条件(constraints)做决定,比如Pod和节点的亲缘性(affinities),污点(taints)和容忍(tolerations),以及资源请求,QoS等。如果使用的外部自动扩展器不理解这些约束条件,就会比较麻烦。
 
想象一下,有一个新的Pod要被调度,但是所有可获得的CPU都被请求(requested)了并且这个pod此时卡在挂起状态。外部的扩展器查看当前被使用的(used)CPU的平均值(非请求值)并且不会做扩展(也就是不会添加一个新的节点)。这个Pod就不会被调度成功。
 
缩容(从集群中删除节点)一直以来更难。想象一下,你有一个有状态的Pod(附加持久卷)并且因为持久卷通常是属于特定可用性区域的资源,并且在该区域中不可复制,如果你自定义的扩展器删除了一个有这种pod的节点,那么调度器不会把这个Pod调度到其他不同的节点上,因为它被严格限定在拥有持久化磁盘所在的可用性区域。此时Pod又会处于挂起状态。
 
社区现在广泛使用的是集群自动扩展器[3],它运行在你的集群中并且和大部分主要的公有云厂商API集成在一起,理解所有这些约束条件才能在上面提到的案例中成功扩展。它也能够分辨出在不影响你设定的约束条件前提下,做出优雅的缩容,这可以给你在算力上省出一笔。
 
没有利用IAM/RBAC的强大功能
不要给IAM用户提供机器和应用程序的永久私钥,也不要在使用角色和服务账号产生临时私钥。
 
我们经常看到这种情况--硬编码访问以及把私钥保存在应用程序配置中,即使使用Cloud IAM,也从来不循环(rotating)使用私钥。在适当的地方使用IAM角色和服务账号代替用户方式。

 

 
跳过kube2iam,直接为服务账号设定IAM角色,可以参考Štěpán Vrany这篇博客[4]。
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default
 
只需一个注解(annotation)。不是很难,对吧?
 
另外,如果没有必要,就不要给服务账号或者实例配置admin和cluster-admin权限。做到这点可能有点难,特别是在Kubernetes的RBAC中,但是仍然值得努力去做。
 
Pod的自我反亲缘性(self anti-affinities)
例如运行3个Pod副本在一个节点上,然后这个节点宕机了。啊?所有副本运行在一个节点上?Kubernetes不应该是具有魔法性并且提供HA吗?!
 
你不能预期的认为kubernetes调度器会为你的Pod强制实行反亲缘性。你必须明确的定义它们。
// 省略其他部分
      labels:
        app: zk
// 省略其他部分
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"
就是这样。这能够确保pod会被调度到不同节点上(这仅仅在调度期间被检测,运行起来后不会,所以叫做requiredDuringSchedulingIgnoredDuringExecution)。
 
我们讨论的pod反亲缘性是基于不同的节点的名称 -- topologyKey: "kubernetes.io/hostname" --而不是不同的可用性区域。如果你真的需要合适的HA相关功能,你可以在这个主题上深挖下去。
 
没有使用PodDisruptionBudget
你在生产环境中使用Kubernetes。随着时间的推移,你的节点或者集群肯定需要做升级或者停止。PodDisruptionBudget(pdb)是一种API,用于在群集管理员和群集用户之间提供服务保证。
 
确保创建pdb以避免由于节点耗尽而造成不必要的服务中断。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: zookeeper
通过这个配置,集群用户就可以告诉集群管理员:“嗨,我这有ZooKeeper服务,不管你做什么,我这边总是有至少两个副本可用”。
 
我在这篇博客[5]中对这个主题讨论的更深入。
 
共享集群中有更多的租户或环境
Kubernetes的命名空间不提供强隔离。
 
人们看上去很期待这种情况,如果他们把非生产环境和生产环境工作负载分割到不同的命名空间,其中一方的工作负载永远不会影响到另一方。这是有可能达到的,比如一定级别的公平——资源请求和限制,配额,优先级类——以及隔离——亲缘性,容忍,污点(或者节点选择器)——当然也可以“物理上”分割数据面板中的工作负载,不过这种分割十分复杂。
 
如果你需要在同一个集群中拥有两种类型的工作负载,那么你就不得不忍受这种复杂性。如果你不需要并且再建一个集群对你来说相对更便宜(比如在公有云中),那么把它放到不同的集群中能够获得更强级别的隔离。
 
externalTrafficPolicy: Cluster
经常看到这种情况,所有的流量都被路由到集群中的一个NodePort服务上,默认情况下,这个服务有externalTrafficPolicy: Cluster标签。这意味着这个NodePort在每个节点上都是打开着的,所以你可以使用任何一个节点去和期望的服务(Pod集合)进行通信。
 

 

通常,以NodePort服务为目标的实际Pod仅在这些节点的子集上运行。这意味着,我和一个节点通信时,如果该节点没有我们预期的Pod在上面运行,它会把流量转发到其他节点,这会产生额外的网络跳跃(hop)以及增加的时延(如果节点位于不同的AZ/数据中心中,这个时延可能会很高并且还有额外的出口成本)。
 
在kubernetes服务上设置externalTrafficPolicy: Local就不会在每个节点上打开NodePort了,而是仅仅在运行对应pod的节点上打开。如果你使用外部负载均衡器(如AWS ELB)对其端点进行健康检查,它会仅将流量发送到应该去往的那些节点,从而改善了时延,计算开销,出口费用。
 
很大可能你正在使用traefik或者nginx-ingress-controller类似的东西,同时把它们作为NodePort(或者LoadBalancer,这也是使用NodePort机制)暴露出去,然后用来处理入口http流量路由,并且这种设置对于这类请求可以在很大程度上减少时延。
 
可以查看这篇externalTrafficPolicy and their trade-offs[6]博客,了解更深入的相关信息。
 
宠物(Pet)集群过多的对控制面板施加压力
你以前可能会给你的服务命名为Anton,HAL9000以及Colossus,或者为你的节点产生随机的id,但是你有通过名字来命名你的集群吗?
 
你知道在Kubernetes中如何以概念验证(Proof Of Concept)开始,将集群命名为“测试”并且仍在生产环境中使用它,最后每个人都不敢碰它?(真实的故事)
 
宠物集群并不好玩,随着时间的推移你可能想去删除你的集群,那么要做好灾难恢复的练习以及对控制面板要格外关注。害怕去操作控制面板是一个不好的信号。etcd挂了?好吧,你惹上大麻烦了。
 
另一方面,过多的操作控制面板也是不好的。随着时间的推移,当控制面板变慢了,很大可能是你创建了许多对象(objects)而从没有循环使用它们(非常常见,比如在使用helm时,它的默认配置并没有循环利用configmaps/secrets中的状态(state),这会导致你的控制面板中会有成千上万个对象),又或者你时常通过kube-api删除以及编辑大量的内容(比如自动伸缩,cicd,监控,事件日志,控制器等)。
 
另外,也要检测托管的kubernetes提供的“SLA”/SLO以及服务保证(guarantee)。厂商可能会保证控制面板(或它的子组件)的可用性,但不能保证你发送请求的低延迟。换句话说,你可能使用kubectl get nodes,然后在10分钟内获得正确响应,但是这个仍然在服务保证的范围内。
 
补充:使用latest标签
这是一个典型的问题。我很晚才意识到它,这种情况我看到的不多,因为我们大多数人被这种情况折腾过好多次,所以我们都不使用:lastest标签并且都会固定使用相应的版本。
 
ECR有个很好的功能——标签不能变更,值得一看。
 
总结
不要期待任何东西都可以自动的工作很好——Kubernetes不是银弹。一个坏的应用即使在Kubernetes上也是坏的(实际上可能比坏更糟糕)。如果你不够细心,你可能会遇到很多复杂性,过多压力并且缓慢的控制面板以及没有相应的DR策略。不要期待开箱即用的多租户和高可用性。多花点时间让你的应用变成云原生的。
 
可以查看由Henning整理的失败故事合集[7]。
 
想看其他容易犯的错误?可以在Twitter上关注我们(@MarekBartik、@MstrsObserver)。
 
相关链接:
https://www.slideshare.net/try_except_/optimizing-kubernetes-resource-requestslimits-for-costefficiency-and-latency-highload
https://cloud.google.com/kubernetes-engine/docs/concepts/verticalpodautoscaler
https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler
https://blog.pipetail.io/posts/2020-04-13-more-eks-tips/
https://blog.marekbartik.com/posts/2018-06-29_kubernetes-in-production-poddisruptionbudget/
https://www.asykim.com/blog/deep-dive-into-kubernetes-external-traffic-policies
https://k8s.af/
 
原文链接:https://blog.pipetail.io/posts/2020-05-04-most-common-mistakes-k8s/
 
声明:文章收集于网络,版权归原作者所有,为传播信息而发,如有侵权,请联系小编删除,谢谢!
 
 

微信公众号

声明:本站部分作品是由网友自主投稿和发布、编辑整理上传,对此类作品本站仅提供交流平台,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,不为其版权负责。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。

网友评论:

发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
SEM推广服务

Copyright©2005-2028 Sykv.com 可思数据 版权所有    京ICP备14056871号

关于我们   免责声明   广告合作   版权声明   联系我们   原创投稿   网站地图  

可思数据 数据标注

扫码入群
扫码关注

微信公众号

返回顶部