在 Kubernetes 中,扩展应用程序的 Pod 副本数量称为水平 Pod 自动扩展。它是水平的,因为您是在增加副本数量以应对增加的流量,而不是垂直的,后者意味着增加每个副本可用的资源。通常,要扩展系统,您需要的是水平扩展。
Kubernetes 包含一个名为水平 Pod 自动扩缩器(HPA)的功能,这是一个系统,您可以指定要观察和目标的 Pod 指标,如 CPU 使用率,以及一些扩缩限制(最小和最大副本)。HPA 将尝试通过创建和删除 Pods 来满足您的指标。在 CPU 的情况下,如果您的目标是 20% 的 CPU 利用率,当您的平均利用率(所有 Pods 的平均值)超过 20%(即 Pod 在其资源请求中请求的值的 20%)时,HPA 将添加副本,当它低于 20% 时将删除副本。这些操作受您提供的最小和最大限制的约束,并且有冷却时间以避免过多的波动。我们可以为我们的 Deployment 创建一个 HPA,如以下列表所示。
清单 6.2 Chapter06/6.2_HPA/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: timeserver
spec:
minReplicas: 1 ?
maxReplicas: 10 ?
metrics:
- resource:
name: cpu
target:
averageUtilization: 20 ?
type: Utilization
type: Resource
scaleTargetRef: ?
apiVersion: apps/v1 ?
kind: Deployment ?
name: timeserver ?
? 最小副本数量
? 最大副本数量
? CPU 利用率目标。当 Pod 的 CPU 利用率高于此值时,HPA 将创建更多副本。
可扩展的部署
您也可以以命令式创建它。和往常一样,我更喜欢配置方法,因为这使得后续编辑更容易。但这里是等效的命令式命令以供完整性:
kubectl autoscale deployment timeserver --cpu-percent=20 --min=1 --max=10
为了测试这一点,我们需要让 CPU 非常忙碌。使用以下两个代码清单,让我们为我们的时间服务器应用程序添加一个真正消耗 CPU 的路径:计算π。
清单 6.3 Chapter06/timeserver4/pi.py
from decimal import *
# Calculate pi using the Gregory-Leibniz infinity series
def leibniz_pi(iterations):
precision = 20
getcontext().prec = 20
piDiv4 = Decimal(1)
odd = Decimal(3)
for i in range(0, iterations):
piDiv4 = piDiv4 - 1/odd
odd = odd + 2
piDiv4 = piDiv4 + 1/odd
odd = odd + 2
return piDiv4 * 4
清单 6.3 是我们计算π的方法,清单 6.4 显示了向 server.py 添加新 URL 路径的过程,该路径调用了它。
清单 6.4 Chapter06/timeserver4/server.py
from pi import *
# ...
case '/pi': ?
pi = leibniz_pi(1000000) ?
self.respond_with(200, str(pi)) ?
新 HTTP 路径
以下列表提供了一个修订后的部署,引用了带有新增路径的容器新版本。为了与 HPA 正常工作,设置资源请求非常重要,我们在第 5 章中添加了这些请求,并在此处列出。
清单 6.5 Chapter06/6.2_HPA/deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: timeserver
spec:
replicas: 1 ?
selector:
matchLabels:
pod: timeserver-pod
template:
metadata:
labels:
pod: timeserver-pod
spec:
containers:
- name: timeserver-container
image: docker.io/wdenniss/timeserver:4 ?
resources: ?
requests: ?
cpu: 250m ?
memory: 250Mi ?
? 副本初始设置为 1
新应用版本
资源请求对 HPA 的正常工作至关重要。
我们现在可以创建部署、服务和 HPA:
$ cd Chapter06/6.2_HPA
$ kubectl create -f deploy.yaml -f service.yaml -f hpa.yaml
deployment.apps/timeserver created
service/timeserver created
horizontalpodautoscaler.autoscaling/timeserver created
$ kubectl get svc -w
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.22.128.1 443/TCP 6m35s
timeserver LoadBalancer 10.22.131.179 80:32650/TCP 18s
timeserver LoadBalancer 10.22.131.179 203.0.113.16 80:32650/TCP 26s
在等待外部 IP 配置的同时,您可以使用以下命令开始监视 Pods 的 CPU 利用率(我建议在新窗口中执行):
kubectl top pods
一旦您获得了外部 IP,请在端点上生成一些负载。Apache Bench,您可以在大多数系统上安装,效果很好。以下命令将同时向我们的端点发送 50 个请求,直到发送了 10,000 个请求——这应该足够了:
EXTERNAL_IP=203.0.113.16
ab -n 10000 -c 5 http://$EXTERNAL_IP/pi
您可以通过以下方式查看部署的扩展状态:
kubectl get pods -w
Linux watch 命令方便通过单个命令监视所有资源( kubectl 本身无法做到这一点):
watch -d kubectl get deploy,hpa,pods
如果一切顺利,您应该会看到 CPU 利用率增加,如 kubectl top pods 所示,并且会创建更多的 Pod 副本。一旦您停止向端点发送负载(例如,通过中断 ab 或等待其完成),您应该会观察到这些副本逐渐被移除。
您可能会观察到,在响应高请求负载时,扩展时副本的调度速度比在请求停止时缩减时移除副本的速度要快。这只是系统在移除容量时稍微谨慎,以避免在需求激增的情况下造成波动。以下是我样本运行的情况:
$ kubectl get deploy,hpa,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/timeserver 2/6 6 2 7m7s
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
hpa/timeserver Deployment/timeserver 100%/30% 1 6 6
NAME READY STATUS RESTARTS AGE
pod/timeserver-b8789946f-2b969 1/1 Running 0 7m7s
pod/timeserver-b8789946f-fzbnk 0/1 Pending 0 96s
pod/timeserver-b8789946f-httwn 1/1 Running 0 96s
pod/timeserver-b8789946f-vvnhj 0/1 Pending 0 96s
pod/timeserver-b8789946f-xw9zf 0/1 ContainerCreating 0 36s
pod/timeserver-b8789946f-zbzw9 0/1 ContainerCreating 0 36s
这里显示的 HPA 在使用 CPU 指标时表现得相当不错,但有一个问题:你的工作负载可能并不是 CPU 绑定的。与演示中使用的 CPU 密集型请求不同,许多 HTTP 服务花费大量时间等待外部服务,如数据库。这些部署可能需要使用其他指标进行扩展,例如每秒请求数(RPS),而不是 CPU 利用率。Kubernetes 提供了两个内置指标:CPU(在前面的示例中演示)和内存。它并不直接支持像 RPS 这样的指标,但可以通过使用你的监控服务暴露的自定义和外部指标进行配置。下一部分将涵盖这种情况。
垂直 Pod 自动扩缩怎么办?
垂直 Pod 自动扩缩 (VPA) 是一个通过调整 Pod 的 CPU 和内存资源来实现垂直扩缩的概念。在 Kubernetes 中,VPA 的实现通过观察 Pod 的资源使用情况并动态地改变 Pod 的资源请求来实现。Kubernetes 并没有提供开箱即用的 VPA 实现,尽管有一个开源实现可用,云服务提供商,包括 GKE,提供他们自己的版本。
由于 VPA 可以自动确定 Pod 的资源请求,它可以为您节省一些精力并提供一定的资源效率。如果您需要 Pod 的资源请求随着时间动态调整(对于资源需求波动较大的 Pod),它也是合适的工具。
使用 VPA 会增加其自身的复杂性,并且可能并不总是与 HPA 良好配合。我会首先专注于设置适当的 Pod 资源请求和副本的水平扩展。
https://github.com/kubernetes/autoscaler
6.2.1 外部指标
一个流行的扩展指标是每秒请求数(RPS)。使用 RPS 指标进行扩展的基础是测量您的应用程序实例每秒可以处理多少请求(副本的容量)。然后,您将当前请求数量除以这个数值, voilà,您就得到了所需的副本数量:
副本数量 = RPS ÷ 副本容量
RPS 指标的好处在于,如果您确信您的应用程序能够处理您测试过的 RPS,那么您可以确信它在负载下能够扩展,因为自动扩展器的工作是提供足够的容量。
实际上,即使您没有进行自动扩展,这个指标仍然是规划容量的一个非常好的方法。您可以测量副本的容量,预测流量,并相应地增加副本。但是使用 Kubernetes,我们还可以使用 RPS 指标配置 HPA 进行自动扩展。
现在,在这种情况下,我们将使用 HPA 的外部指标属性。这样做的问题在于,正如其名称所示,指标是来自集群外部的。因此,如果您使用的监控解决方案与我在示例中使用的不同,您需要查找相关的 RPS 指标。幸运的是,RPS 是一个相当常见的指标,任何值得信赖的监控解决方案都会提供它。
在前面的章节中,我们讨论了几种通过所谓的第 4 层负载均衡器将流量引入集群的方法,该负载均衡器在 TCP/IP 层操作,以及所谓的第 7 层 Ingress,它在 HTTP 层操作。由于请求是 HTTP 的概念,您需要使用 Ingress 来获取此指标。Ingress 将在下一章中深入讨论;目前,了解这个对象可以查看和检查您的 HTTP 流量,因此可以暴露您收到的请求数量的指标就足够了。
在这个例子中,如以下两个列表所示,我们将使用相同的部署,但通过类型为 NodePort 的服务在入口上暴露它(而不是前几章中的类型 LoadBalancer )。
清单 6.6 Chapter06/6.2.1_ExternalMetricGCP/service.yaml
apiVersion: v1
kind: Service
metadata:
name: timeserver-internal ?
spec:
selector:
pod: timeserver-pod
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort ?
此内部服务的名称
? 类型 NodePort 用于入口。
清单 6.7 Chapter06/6.2.1_ExternalMetricGCP/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: timeserver-ingress
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: timeserver-internal ?
port:
number: 80
? 参考列表 6.6 中的内部服务
现在,如果您正在使用 Google Cloud,以下 HPA 定义可以从 Ingress 中获取 RPS 指标,一旦您将转发规则名称替换为您自己的名称。
清单 6.8 Chapter06/6.2.1_ExternalMetricGCP/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: timeserver-autoscaler
spec:
minReplicas: 1
maxReplicas: 6
metrics: ?
- type: External ?
external: ?
metric: ?
name: loadbalancing.googleapis.com|https|request_count ?
selector: ?
matchLabels: ?
resource.labels.forwarding_rule_name: "k8s2-fr-21mgs2fl" ?
target: ?
type: AverageValue ?
averageValue: 5 ?
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: timeserver
外部指标
forwarding_rule_name 是度量服务器知道您正在谈论哪个 Ingress 对象的方式。您可以完全省略 selector 字段,但那样它将匹配所有 Ingress 对象——这可能不是您想要的。
使事情复杂化的是,这个转发规则名称是一个特定于平台的资源名称,而不是 Kubernetes 对象名称(在这个例子中,该名称是由 GKE 自动设置的)。要发现平台资源名称,在等待几分钟以便配置您的 Ingress 后,您可以描述您的 Ingress 对象:
$ kubectl describe ingress timeserver-ingress
Name: timeserver-ingress
Namespace: default
Address: 203.0.113.16
Default backend: default-http-backend:80 (10.22.0.202:8080)
Rules:
Host Path Backends
---- ---- --------
*
/ timeserver-internal:80 (10.22.0.130:80,10.22.0.131:80,10.22.0.196:80 + 1 more...)
Annotations: ingress.kubernetes.io/backends:
{"k8s -be-32730":"HEALTHY","k8s1-a5225067":"HEALTHY"}
ingress.kubernetes.io/forwarding-rule: k8s2-fr-21mgs2fl ?
ingress.kubernetes.io/target-proxy: k8s2-tp-21mgs2fl
ingress.kubernetes.io/url-map: k8s2-um-21mgs2flEvents:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 6m28s (x31 over 5h6m) loadbalancer-controller Scheduled for sync
转发规则名称
查询此信息的另一种方法是了解数据在对象结构中的位置,并使用 JsonPath 格式 kubectl ,这在配置自动化工具时非常重要
$ kubectl get ingress -o=jsonpath="{.items[0].metadata.annotations['ingress\.
? kubernetes\.io\/forwarding-rule ']}"
k8s2-fr-21mgs2fl
提示 我通过首先查询 -o=json 版本的 Ingress,然后结合查看 JsonPath 文档、Stack Overflow 和反复试验来构建 JsonPath 表达式。
一旦您准备好对象,还有最后一步,即确保为集群中的工作负载启用云监控,并安装一些连接器,以便 HPA 可以访问指标。请按照说明 1 安装自定义指标 - Stackdriver 适配器。
通过我们的部署,类型为 NodePort 的服务、Ingress、HPA 和指标适配器都已配置完成,现在我们可以尝试一下!向 Ingress 生成一些请求(替换为您的 Ingress 的 IP,通过 kubectl get ingress 获取):
ab -n 100000 -c 100 http://203.0.113.16/
在一个单独的窗口中,观察扩展的规模:
$ kubectl get hpa,ingress,pods
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
hpa/timeserver Deployment/timeserver 94%/30% 1 6 4
NAME CLASS HOSTS ADDRESS PORTS
ingress/timeserver-ingress * 203.0.113.16 80
NAME READY STATUS RESTARTS AGE
pod/timeserver-b8789946f-8dpmg 1/1 Running 0 5h51m
pod/timeserver-b8789946f-gsrt5 0/1 ContainerCreating 0 110s
pod/timeserver-b8789946f-sjvqb 1/1 Running 0 110s
pod/timeserver-b8789946f-vmhsw 0/1 ContainerCreating 0 110s
您可能会注意到的一件事是,验证系统的性能是否更符合预期变得更加容易。Apache Bench 允许您指定并发请求;您可以查看它们所需的时间(因此可以计算 RPS),并查看副本数量以确定是否正确。使用 CPU 指标进行测试的过程稍微困难一些,因为为了进行测试,您可能需要尽可能让 Pod 繁忙,就像我们在之前的示例中所做的那样。基于用户请求进行扩展的这一特性是它成为一种流行指标的原因之一。
观察和调试
要查看 HPA 的状态,您可以运行 kubectl describe hpa 。特别注意 ScalingActive 条件。如果它是 False ,这可能意味着您的指标未激活,这可能有多种原因:(1)指标适配器未安装(或未经过身份验证),(2)您的指标名称或选择器错误,或(3)尚无可用的监控样本。请注意,即使配置正确,当没有监控数据样本时(例如,没有请求),您也会看到 False ,因此请确保向端点发送一些请求,并等待一两分钟以便数据传输,然后再进行进一步调查。
平均值 vs. 值
在前面的示例中,我们使用了 targetAverageValue 。 targetAverageValue 是该指标的每个 Pod 的目标值。 targetValue 是一个替代值,即目标绝对值。由于 RPS 容量是按每个 Pod 级别计算的,因此我们想要的是 targetAverageValue 。
其他指标
处理后台任务时另一个流行的外部指标(在第 10 章中讨论)是 Pub/Sub 队列长度。Pub/Sub 是一个排队系统,允许您拥有一个需要执行的工作队列,您可以在 Kubernetes 中设置工作负载来处理该队列。对于这样的设置,您可能希望通过添加或删除 Pod 副本(可以处理队列的工作者)来响应队列大小。您可以在 GKE 文档中找到一个完整的示例;本质上,它归结为一个看起来与之前相似的 HPA,只是使用了不同的指标:
metric:
name: pubsub.googleapis.com|subscription|num_undelivered_messages
selector:
matchLabels:
resource.labels.subscription_id: echo-read
此配置由指标名称和指标的资源标识符组成——在本例中,是 Google Cloud Pub/Sub 订阅标识符。
其他监控解决方案
外部指标是您应该能够为任何 Kubernetes 监控系统配置的内容。虽然之前给出的示例使用了 Google Cloud 上的 Cloud Monitoring,但如果您使用 Prometheus 或其他云监控系统,应该适用相同的原则。要开始,您需要确定(1)如何为您的监控解决方案安装指标适配器,(2)该系统中的指标名称是什么,以及(3)选择指标资源的正确方法。