应用扩容是指在应用接收到的并发请求已经处于其处理请求极限边界的情形下,扩展处理能力而确保应用高可用的技术手段。

云原生下应用扩缩容都有哪些方式呢?

cat hpa.yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
# HPA 的伸缩对象描述,HPA会动态修改该对象的Pod 数量
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
# HPA 的最小 Pod 数量和最大 Pod 数量
minReplicas: 1
maxReplicas: 10
# 监控的指标数组,支持多种类型的指标共存
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
- type: Resource
resource:
name: cpu
# Utilization 类型的目标值,Resource 类型的指标只支持 Utilization 和 AverageValue 类型的目标值
target:
type: Utilization
averageUtilization: 50
- type: Pods
pods:
metric:
name: packets-per-second
# AverageValue 类型的目标值,Pods 指标类型下只支持 AverageValue 类型的目标值
target:
type: AverageValue
averageValue: 1k
按照 Pod 中所有 container 的资源使用平均值来计算,期望副本数=当前副本数 *(当前指标 / 期望指标)
当前度量值为 200m,目标设定值为 100m,那么由于 200.0/100.0== 2.0,副本数量将会翻倍。
如果当前指标为 50m,副本数量将会减半,因为 50.0/100.0== 0.5。
如果计算出的扩缩比例接近1.0(根据–horizontal-pod-autoscaler-tolerance 参数全局配置的容忍值,默认为 0.1),将会放弃本次扩缩。
因为replicas字段被HPA控制的时候,可能replicas不需要更新。如果使用int类型,需要给定replicas字段一个值。而使用指针类型,只需要不填replicas就可以了。
扩容时:假如一个应用平时的cpu使用率是20%,突然使用率达到了100%,根据HPA的算法会使pod数量翻5倍。对基础架构和业务都有压力。
缩容时:假如pod数量是10个,需要缩容80%,如果一次性全部缩掉,业务可能会有影响。
这种情况就可以使用HPA的高级策略。
在 spec 字段的 behavior 部分可以指定一个或多个扩缩策略。
kubectl explain --api-version=autoscaling/v2beta2 hpa.spec.behavior.scaleUp

behavior:
scaleUp:
policies:
- type: Percent
value: 900
periodSeconds: 5
behavior:
scaleUp:
policies:
- type: pods
value: 1
behavior:
scaleDown:
policies:
- type: Pods
value: 5
periodSeconds: 60
- type: Percent
value: 10
periodSeconds: 60
这样就能保证业务的缩容是平滑的,数据指标下降也是平滑的,不是突然的下降。防止突然的流量高峰导致部分请求失败。
behavior:
scaleDown:
policies:
- type: pods
value: 0
behavior:
scaleUp:
stabilizationWindowSeconds: 300
policies:
- type: pods
value: 20
behavior:
scaleDown:
stabilizationWindowSeconds: 600
policies:
- type: pods
value: 5
Kubernetes 默认提供 CPU 和内存作为 HPA 弹性伸缩的指标,如果有更复杂的场景需求,比如基于业务单副本 QPS 大小来进行自动扩缩容,可以安装 prometheus-adapter 来实现基于自定义指标的 Pod 扩缩容。
官方地址:https://github.com/kubernetes-sigs/prometheus-adapter
httpserver镜像,暴露了 httpserver_requests_total 指标,记录 HTTP 的请求,通过这个指标可以计算出该业务程序的 QPS 值。
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpserver
spec:
replicas: 1
selector:
matchLabels:
app: httpserver
template:
metadata:
labels:
app: httpserver
spec:
containers:
- name: httpserver
image: httpserver:v1
imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: httpserver
labels:
app: httpserver
annotations:
prometheus.io/scrape: "true"
prometheus.io/path: "/metrics"
prometheus.io/port: "http"
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
name: http
selector:
app: httpserver
如果部署的是prometheus-opreator,修改配置文件参考这里的附录
- job_name: httpserver
scrape_interval: 5s
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- action: keep
source_labels:
- __meta_kubernetes_service_label_app
regex: httpserver
- action: keep
source_labels:
- __meta_kubernetes_endpoint_port_name
regex: http
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: httpserver
spec:
endpoints:
- port: http
interval: 5s
#namespaceSelector:
#matchNames:
#- xxx
selector:
matchLabels:
app: httpserver
sum(rate(http_requests_total[2m])) by (pod)
cat values.yaml
rules:
default: false
custom:
- seriesQuery: 'httpserver_requests_total'
resources:
template: <<.Resource>>
name:
matches: "httpserver_requests_total"
as: "httpserver_requests_qps" # PromQL 计算出来的 QPS 指标
metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)
prometheus:
url: http://prometheus.monitoring.svc.cluster.local # 替换 Prometheus API 的地址 (不写端口)
port: 9090
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install prometheus-adapter prometheus-community/prometheus-adapter -f values.yaml
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/httpserver_requests_qps
metrics-server已经部署在集群中了。以php为例。
cat php-apache.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
run: php-apache
replicas: 1
template:
metadata:
labels:
run: php-apache
spec:
containers:
- name: php-apache
image: cncamp/hpa-example
ports:
- containerPort: 80
resources:
limits:
cpu: 500m
requests:
cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
labels:
run: php-apache
spec:
ports:
- port: 80
selector:
run: php-apache
k apply -f php-apache.yaml

k apply -f hpa.yaml
# 或者
k autoscale deployment php-apache --cpu-percent=50 --min=1 --max=3

watch 'kubectl top po | grep php'
kubectl run -i --tty load-generator --rm --image=busybox --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"

pod的cpu到达500m,pod的limits设定是500m

pod已经自动扩容


如果创建了2个hpa对象,指定了同样的target后,这2个规则会冲突,可能会出问题。
基于指标的弹性有滞后效应,因为弹性控制器操作的链路过长。
从应用负载超出國值到 HPA 完成扩容之间的时间差包括:
很可能在突发流量出现时,还没完成弹性扩容,服务实例已经被流量击垮。
由于hpa是按照 Pod 中所有 container 的资源使用平均值来计算的,如果 Pod 中有多个 container,它们的资源使用相差较大,可能导致某个 container 高负载了还不扩容。
比如有个pod,它有2个容器,一个是业务容器,一个是sidecar,业务容器CPU使用为100%,而sidecar容器CPU使用为0,那么这个pod CPU使用率就会被当作50%,而hpa设置的CPU阈值是60%,那么这个pod就不会被扩容。
kubectl explain --api-version=autoscaling/v2beta2 hpa.spec.metrics.containerResource

pod有2个容器:application和log。当application容器的CPU使用率达到50%即进行扩容,并且不管log容器CPU使用率;或者pod整体的CPU使用率达到50%进行扩容。
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: mission-critical
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mission-critical
minReplicas: 1
maxReplicas: 10
metrics:
- type: ContainerResource
resource:
name: cpu
container: application
target:
type: Utilization
averageUtilization: 50
- type: PodResource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50