在我们多年使用kubernetes的经验中,我们有幸看到了很多集群(在GCP,AWS和Azure上都是托管的和非托管的),并且我们看到一些错误在不断重复。

我将尝试展示我们经常看到的内容,并讨论如何修复它们。

resources - requests 和 limits

这绝对值得在此列表中获得最多的关注。

通常未设置CPU请求或将CPU请求设置得太低(这样我们就可以在每个节点上容纳很多Pod),因此节点的使用量过大。在需求旺盛的时间,节点的CPU被充分利用,我们的工作负载仅获得“所申请的资源”,并且受到CPU throttled,从而导致应用程序延迟,超时等增加。

BestEffort (请不要这么做):

resources: {}

非常低的CPU(请不要这么做):

    resources:
      requests:
        cpu: "1m"

另一方面,即使节点的CPU没有得到充分利用,拥有CPU限制也会不必要地限制Pod,这又会导致延迟增加。围绕linux内核中的CPU CFS配额和基于设置的cpu限制并关闭CFS配额的cpu节制进行了公开讨论。 CPU限制可能导致更多的问题,无法解决。

内存过量使用会给您带来更多麻烦。达到CPU限制将导致节流,达到内存限制将使您的Pod被杀死。见过OOMkill吗?是的,这就是我们正在谈论的那个。想要最小化它发生的频率?请勿过度使用您的内存,并使用保证的QoS(服务质量)设置的内存请求等于限制。

Burstable (更容易被OOM杀死):

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(及其中的容器)的当前cpu和内存使用情况。很有可能,您已经在运行它。只需运行以下命令:

kubectl top pods
kubectl top pods --containers
kubectl top nodes

但是,这些仅显示当前用法。但是您最终想及时查看这些使用情况指标(以回答诸如:高峰,昨天早晨等情况下的cpu使用情况之类的问题)。为此,您可以使用Prometheus,DataDog等。他们只是从指标服务器中获取指标并进行存储,然后就可以对其进行查询和绘制图形。

VerticalPodAutoscaler可以帮助您自动执行此手动过程-及时查看cpu/mem的使用情况,并再次基于此设置新的请求和限制。

有效利用计算资源并非易事。就像一直在玩俄罗斯方块。如果发现自己在平均利用率较低(例如〜10%)的情况下为计算付出了高昂的代价,则可能需要检查基于AWS Fargate或Virtual Kubelet的产品,这些产品更多地利用了无服务器/按使用量计费模式。

liveness 和 readiness probes

默认情况下,未指定活动性和就绪性探针。有时它会一直保持下去……

但是,如果出现不可恢复的错误,您的服务将如何重新启动?负载平衡器如何知道特定的Pod可以开始处理流量?或处理更多流量?

人们通常不知道这两者之间的区别。

  • 如果探测失败,活动探测将重新启动您的Pod
  • 就绪探针会在kubernetes服务失败的Pod失败时断开连接(您可以在kubectl get端点中进行检查),并且不再有流量发送给它,直到探针再次成功

并且在整个POD生命周期内都运行。这个很重要。

人们常常认为,准备就绪探针仅在开始时就运行,以告知Pod何时就绪,并且可以开始为流量提供服务。但这只是其用例之一。

另一个是要判断在Pod的生命周期内,Pod是否变得太热而无法处理过多的流量(或昂贵的计算),以至于我们不让它做更多的工作来让她冷静下来,那么就绪性探测成功了,我们开始再次发送更多流量。在这种情况下(当准备就绪探测失败时),活动探测也失败会适得其反。您为什么要重新启动运行良好的Pod?

有时,未定义任何一个探针比定义错误的探针要好。如上所述,如果活力探针等于准备就绪探针,那么您将遇到很大麻烦。您可能想从仅定义就绪探针开始,因为活动探针很危险

如果您的任何共享依赖项均关闭,则不要使任何一个探针失败,否则将导致所有Pod的级联失败。

为每一个http 服务设置LoadBalancer

您的集群中可能有更多的http服务,您想向外界公开。

如果您将kubernetes服务公开为以下类型:LoadBalancer,则其控制器(特定于供应商)将配置和协调外部LoadBalancer(不一定是L7负载均衡器,更可能是L4 lb),并且这些资源可能会变得昂贵(外部静态ipv4地址,每秒钟的价格…)。

在这种情况下,共享一个外部负载均衡器可能更有意义,并且您将服务公开为类型:NodePort。更好的是,部署像nginx-ingress-controller(或traefik)之类的东西作为暴露给外部负载均衡器的单个NodePort端点,并基于kubernetes Ingress资源在集群中路由流量。

彼此对话的其他集群内(微)服务可以通过ClusterIP服务和开箱即用的dns服务发现进行对话。注意不要使用其公共DNS/IP,因为这可能会影响其延迟和云成本。

无集群感知的 autoscaling

在群集中添加节点或从群集中删除节点时,您不应考虑一些简单的指标,例如这些节点的cpu利用率。在调度Pod时,您需要根据Pod和节点的亲和力,污点和容忍度,资源请求,QoS等许多调度约束进行决策。拥有无法理解这些约束的外部自动缩放器可能很麻烦。

想象有一个新的Pod要调度,但是请求所有可用的CPU并且Pod停留在Pending状态。外部自动缩放器可查看当前使用的平均CPU(未请求),并且不会扩展(不会添加其他节点)。该Pod不会被调度。

扩展(从群集中删除节点)总是比较困难。假设您有一个有状态的Pod(已附加持久性卷),并且由于持久性卷通常是属于特定可用性区域的资源,并且不会在该区域中复制,因此您的自定义自动伸缩器将删除带有该Pod的节点,并且调度程序无法对其进行调度转移到另一个节点上,因为它受到永久性磁盘所在的唯一可用性区域的很大限制。 Pod再次陷入待处理状态。

该社区广泛使用在群集中运行的集群自动缩放器,并与大多数主要的公共云供应商API集成在一起,可以理解所有这些限制,并且在上述情况下可以向外扩展。它还将确定它是否可以在不影响我们设置的任何约束的情况下正常扩展,并节省您的计算成本。

没有使用 IAM/RBAC

不要将具有永久秘钥的IAM用户用于机器和应用程序,而要使用角色和服务帐户生成临时秘钥。

我们经常看到它-在应用程序配置中对访问和秘密密钥进行硬编码,当您手握Cloud IAM时就永远不会rotate秘钥。在适当的地方使用IAM角色和服务帐户代替用户。

跳过kube2iam,直接按照此博文中的说明使用服务帐户的IAM角色。

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default

一个注解。很简单,不是吗?

另外,在不需要时,也不要授予服务帐户或实例配置文件管理员和群集管理员的权限。这有点困难,尤其是在k8s RBAC中,但仍然值得努力。

self anti-affinities for pods

运行例如某个部署的3个pod副本,节点关闭,所有副本都随之关闭。 所有副本都在一个节点上运行? Kubernetes是否应该具有魔力并提供HA ?!

您不能指望kubernetes调度程序对您的Pod强制执行反亲和。您必须明确定义它们。

// omitted for brevity
      labels:
        app: zk
// omitted for brevity
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: "app"
                    operator: In
                    values:
                    - zk
              topologyKey: "kubernetes.io/hostname"

这将确保将Pod调度到不同的节点(仅在调度时而不是在执行时进行检查,因此需要requiredDuringSchedulingIgnoredDuringExecution)。

我们正在谈论不同节点名称上的podAntiAffinity-拓扑关键字:“ http://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服务,无论您要做什么,我都希望至少有2个副本始终可用”。

共享集群中有更多租户或环境

Kubernetes命名空间不提供任何强隔离。

人们似乎希望,如果将非生产性工作负载分离到一个命名空间,然后将生产性工作转换为生产性命名空间,那么一个工作负载将永远不会影响另一个工作负载。可以实现某种程度的公平-资源请求和限制,配额,优先级类-和隔离-亲和力,容忍度,污点(或节点选择器)-以“物理”方式分离数据平面中的工作负载,但这种分离相当复杂。

如果您需要将两种类型的工作负载都放在同一集群中,则必须承担复杂性。如果您不需要它,并且拥有另一个集群对您而言相对简单(例如在公共云中),则将其放在其他集群中以实现更高的隔离级别。

externalTrafficPolicy: Cluster

经常看到这种情况,所有流量都在群集内路由到默认情况下具有externalTrafficPolicy:Cluster的NodePort服务。这意味着NodePort在群集中的每个节点上都打开,因此您可以使用它们中的任何一个与所需的服务(一组Pod)进行通信。

通常,以NodePort服务为目标的实际Pod仅在这些节点的子集上运行。这意味着,如果我与未运行Pod的节点通信,则会将流量转发到另一个节点,从而导致额外的网络跃点和增加的延迟(如果节点位于不同的AZ /数据中心中,则延迟可能会很高,并且有额外的出口成本)。

设置externalTrafficPolicy:kubernetes服务上的Local不会在每个Node上打开该NodePort,而只会在实际运行pod的节点上打开。如果您使用外部负载均衡器(如AWS ELB一样)对其端点进行健康检查,它将开始仅将流量发送到应该去往的那些节点,从而改善了延迟,计算开销,出口费用。

可能是,您有像traefik或nginx-ingress-controller这样的东西被暴露为NodePort(或也使用NodePort的LoadBalancer)也可以处理您的入口http流量路由,并且此设置可以大大减少此类请求的延迟。

bonus: 使用 latest tag

应该停止使用:latest并开始固定版本。

测试集群给control plane 太多压力

太多的测试集群,比如我们做一些压测实验或是故障恢复。最终您会拥有成千上万个对象)控制平面中的对象),或者您不断从kube-api中刮取并编辑大量内容(用于自动缩放,cicd,监视,事件日志,控制器等)。会给我们的控制面造成很大的压力。

另外,检查提供SLA/SLO和托管kubernetes。供应商可能会保证控制平面(或其子组件)的可用性,但不能保证您向其发送的请求的p99延迟。换句话说,您可能会在10分钟内执行kubectl获取节点并获得正确答案,但这仍然没有违反服务保证。

总结

不要指望一切都会自动进行-Kubernetes并非灵丹妙药。不好的应用甚至在kubernetes上也将是不好的应用(实际上甚至比坏更糟)。如果不小心,可能会导致很多复杂性,压力大且控制速度慢,并且没有DR策略。不要期望开箱即用的多租户和高可用性。花一些时间使您的应用程序云原生。


iyacontrol
1.4k 声望2.7k 粉丝

专注kubernetes,devops,aiops,service mesh。