10个常见的 Kubernetes 陷阱和挑战_k8s

Kubernetes 是最流行的容器编排和部署平台。它的强大功能特性,可以保障在生产中可靠地运行容器化应用程序。

然而,有灵活性的同时也带来了复杂性,在本文中,我们将探讨许多团队遇到的 10个常见 Kubernetes 陷阱。能够识别并避免这些挑战将提高应用程序的可扩展性、可靠性和安全性,同时让你更好地控制集群及其部署。

1.使用 latest Tag 部署容器

可以说,Kubernetes 最常被违反的最佳实践之一就是在部署容器时使用latest标签。这将使我们面临无意中接收重大变更的风险,而这些变更可能影响系统稳定性。

每个人使用 latest 标签的方式各不相同,但大多数人都会将 latest 指向其项目的最新版本。例如,今天使用 helm:latest 将提供 Helm v3,但在v4版本发布后,重启就会更新到v4,但是我们可能还认为系统运行的是 v3 版本,从而引发不可预知的风险。

2.不使用Liveness和Readiness 探针

探针可以使我们的应用程序更具弹性。它们会告知 Kubernetes Pod 的健康状况。

当容器出现问题时,比如内存溢出,Liveness探针请求超时,那么Liveness探针会通知 Kubernetes 重启容器。

有时候应用会暂时性地无法为请求提供服务。 例如,应用在启动时可能需要加载大量的数据或配置文件,或是启动后要依赖等待外部服务。 在这种情况下,既不想杀死应用,也不想给它发送请求。 Kubernetes 提供了Readiness探针来发现并缓解这些情况,容器所在 Pod 上报还未就绪的信息,并且不接受通过 Kubernetes Service 的流量。

下面是一个包含有效性和就绪性探针的简单 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: probes-demo
spec:
  containers:
    - name: probes-demo
      image: nginx:latest
      livenessProbe:
        httpGet:
          path: /
          port: 80
      readinessProbe:
        httpGet:
          path: /
          port: 80
3. 缺少节点选择器导致调度效率低下

集群的整体性能取决于 Pod 是否被正确调度到合适的节点上。许多集群包含多种类型的节点,例如用于标准应用程序的小型 2 CPU/4 GB节点和用于密集后端服务的较大8 CPU/16GB节点。

如果Pod无法可靠地调度到我们想要的节点池,那么集群利用率将会很低。例如即使有未充分利用的较小节点,也会强制创建不必要的新的较大节点,从而增加集群的成本。通过在节点上设置标签,然后使用节点选择器将每个 Pod 分配给兼容的节点来避免此问题:

apiVersion: v1
kind: Pod
metadata:
  name: pod-node-selector-demo
spec:
  containers:
    - name: nginx
      image: nginx:latest
  nodeSelector:
    node-class: 2vcpu4gb

此 Pod 只会调度到设置了标签的节点node-class: 2vcpu4gb

使用命令在匹配的节点上设置标签:kubectl label

10个常见的 Kubernetes 陷阱和挑战_kubernates_02

设置适当的调度规则将最大限度地提高节点利用率并保持稳定的集群性能。

4.破坏 Pod 亲和性/反亲和性规则

Pod 亲和性和反亲和性规则允许指示 Kubernetes 哪个节点最适合部署Pod。规则可以以节点级特征(例如标签)或已在节点上运行的其他 Pod 的特征为条件。

亲和性规则将Pod吸引到 Node,使得 Pod 更有可能调度到特定Node上,而反亲和性则具有排斥作用,降低了调度的概率。 Kubernetes 会评估每个可用于调度的可能节点的 Pod 亲和性规则,然后选择最合适的一个。

亲和力系统能够支持复杂的调度行为,但也很容易错误配置亲和力规则,Pod会意外地调度到不正确的节点,或者拒绝调度。

比如一个服务的两个副本,应该调度在两个Node上,这样如果一个 Node 节点发生故障时,可以保证服务的另一个副本可用,如果规则设置不正确都调度到一个Node 上那么会导致服务不可用。

5. 没有监控/记录

当在Kubernetes 中扩容应用程序时,了解集群资源利用率、应用程序错误和实时性能数据至关重要。内存消耗激增、Pod 驱逐和容器崩溃都是我们应该了解的问题,但标准 Kubernetes不具备任何可观测性功能,以便在故障发生时发出告警。

要启用对集群的监控,我们应该部署可观测性平台,例如 Prometheus、夜莺。这会从 Kubernetes 收集指标,同时在Grafana查看相关数据指标。同时也有告警机制。

6. 标签选择器不匹配

部署和服务等对象依赖正确的标签选择器来识别 Pod 及其管理的其他对象。选择器与实际分配给对象的标签之间的不匹配将导致部署失败。

示例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-demo-app
  template:
    metadata:
      labels:
        # Label does not match the deployment's selector!
        app: nginx-demo-application
    spec:
      containers:
        name: nginx-demo-app
        image: nginx:latest

以上文件部署会抛出selector does not match template labelsspec.selector.matchLabelsspec.template.metadata.labels要解决此问题,请调整清单的 和 字段,使它们具有相同的键值对

7. 服务端口不匹配

同样,确保Service将流量路由到Pod上的正确端口也很重要。不正确的端口定义可能会使 Pod 看起来像是发生了故障,而实际上流量根本没有到达该 Pod。

以下清单包含此问题的示例。该Service监听9000端口并将流量转发到其 Pod上的8080端口 ,但容器实际上端口是80,所以流量无法到达。

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
  labels:
    app: demo-app
spec:
  image: nginx:latest
  ports:
    - containerPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: demo-service
spec:
  ports:
    - port: 9000
      protocol: TCP
      targetPort: 8080
  selector:
    app: demo-app
8. 意外部署到错误的命名空间

Kubernetes命名空间将一组服务逻辑分组在一起,在集群中提供一定程度的隔离。为每个团队、应用和环境创建命名空间可以防止名称冲突并简化管理体验。

使用命名空间时,请记住为每个服务和Kubectl命令指定目标命名空间。否则,将使用默认命名空间default。如果服务没有部署在合适的命名空间下,会导致相关服务器请求不可达。

以下清单为Istio Ingress转发配置,此文件部署在seg空间下,然后根据/dp-manager请求,转发到seg空间下的dp-manager-backend服务,如果dp-manager-backend不在seg空间下,那么会导致请求异常。

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: dp-manager-vs
  namespace: seg
spec:
  gateways:
    - dp-manager-gateway
  hosts:
    - control-dpmanager.test.com
  http:
    - match:
        - uri:
            prefix: /dp-manager
      route:
        - destination:
            host: dp-manager-backend
            port:
              number: 80
9. 没有资源请求和限制的 Pod

正确的资源管理对于保持集群的稳定性至关重要。Pod默认没有任何资源限制,除非我们对其进行配置,因此这可能会导致Node节点CPU 和内存耗尽。

在所有 Pod 上设置适当的资源请求和限制以减少资源争用。请求Kubernetes为我们的 Pod 预留特定数量的资源,防止其调度到无法提供足够容量的节点上。Limits设置Pod可以使用的最大资源量;超过 CPU 限制的 Pod 将受到限制,而达到内存限制则会提示内存不足 (OOM) 从而终止 Pod 允许。

请求和限制在 Pod 清单的 字段中定义:spec.container.resources,

apiVersion: v1
kind: Pod
metadata:
  name: demo-pod
spec:
  containers:
    - name: demo-container
      image: nginx:latest
      resources:
        requests:
          cpu: 100m
          memory: 1Gi
        limits:
          memory: 2Gi

以上Pod 请求100m(1000m等于1核)CPU 时间和 1Gi 内存。它只会调度到可以提供足够资源的节点上。 Pod 还设置了内存限制,最大可以申请2Gi 内存。 最佳实践是将 Pod 的内存限制设置为等于其请求。通常不需要 CPU 限制,因为 Kubernetes 会按比例限制超出其请求的 Pod。

10. 集群自动扩容错误

选择Kubernetes原因之一就是它的弹性扩容。正确配置可以使Kubernetes在需求高峰时自动添加新的 Pod 和节点,从而动态的水平和垂直扩容。但是不幸的是,很多团队它们的自动扩容是不可预测的。

因此定期检查集群的利用率,以检查它是否仍然适合您的工作负载。使用负载测试工具(例如Locust)测试自动扩缩规则,将多余的流量引导至集群。这可以使我们更早地发现问题,确保Pod 在实际流量到达时能够无缝扩容。