如何利用kubernetes實現應用的水平擴充套件(HPA)
雲端計算具有水平彈性的特性,這個是雲端計算區別於傳統IT技術架構的主要特性。對於Kubernetes中的POD叢集來說,HPA就是實現這種水平伸縮的控制器, 它能在當POD中業務負載上升的時候,建立新的POD來保證業務系統穩定執行,當POD中業務負載下降的時候,可以銷燬POD來提高資源利用率。
HPA介紹
Horizontal Pod Autoscaling,簡稱HPA,是Kubernetes中實現POD水平自動伸縮的功能。為什麼要水平而不叫垂直, 那是因為自動擴充套件主要分為兩種:
- 水平擴充套件(scale out),針對於例項數目的增減
- 垂直擴充套件(scal up),即單個例項可以使用的資源的增減, 比如增加cpu和增大記憶體
而HPA屬於前者。它可以根據CPU使用率或應用自定義metrics
架構介紹
獲取metrics的兩種方式:
- Heapster: heapster提供metrics服務, 但是在v1(autoscaling/v1)版本中僅支援以CPU作為擴充套件度量指標, 而其他比如:記憶體, 網路流量, qps等目前處於beta階段(autoscaling/v2beta1)
- Cousom: 同樣處於beta階段(autoscaling/v2beta1), 但是涉及到自定義的REST API的開發, 複雜度會大一些, 並且當需要從自定義的監控中獲取資料時,只能設定絕對值,無法設定使用率
工作流程:
- 1.建立HPA資源,設定目標CPU使用率限額,以及最大、最小例項數, 一定要設定Pod的資源限制引數: request, 負責HPA不會工作。
- 2.控制管理器每隔30s(可以通過–horizontal-pod-autoscaler-sync-period修改)查詢
metrics
的資源使用情況 - 3.然後與建立時設定的值和指標做對比(平均值之和/限額),求出目標調整的例項個數
- 4.目標調整的例項數不能超過1中設定的最大、最小例項數,如果沒有超過,則擴容;超過,則擴容至最大的例項個數
- 重複2-4步
自動伸縮演算法:
HPA Controller會通過調整副本數量使得CPU使用率儘量向期望值靠近,而且不是完全相等.另外,官方考慮到自動擴充套件的決策可能需要一段時間才會生效:例如當pod所需要的CPU負荷過大,從而在建立一個新pod的過程中,系統的CPU使用量可能會同樣在有一個攀升的過程。所以,在每一次作出決策後的一段時間內,將不再進行擴充套件決策。對於擴容而言,這個時間段為3分鐘,縮容為5分鐘(可以通過--horizontal-pod-autoscaler-downscale-delay
--horizontal-pod-autoscaler-upscale-delay
進行調整)。
- HPA Controller中有一個tolerance(容忍力)的概念,它允許一定範圍內的使用量的不穩定,現在預設為0.1,這也是出於維護系統穩定性的考慮。例如,設定HPA排程策略為cpu使用率高於50%觸發擴容,那麼只有當使用率大於55%或者小於45%才會觸發伸縮活動,HPA會盡力把Pod的使用率控制在這個範圍之間。
- 具體的每次擴容或者縮容的多少Pod的演算法為: Ceil(前採集到的使用率 / 使用者自定義的使用率) * Pod數量)
- 每次最大擴容pod數量不會超過當前副本數量的2倍
依賴部署
這是kuberntes整個系列的第四篇, 該篇依賴Heapster服務成功安裝, 如果未安裝請參考:
因為基於HeapsterHPA僅支援CPU為維度的自動伸縮, 因此寫了個計算100位π的HTTP服務, 該程式已打包放於百度網盤下, 下載hpa-test-app.tar 匯入然後推到自己私有倉庫, 等待後續測試(放心該映象僅有5M多點)
原始碼如下:
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
)
func main() {
http.HandleFunc("/", compPiHandler)
log.Println("Server work at :8080 ...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func compPiHandler(w http.ResponseWriter, r *http.Request) {
var (
lenI int
err error
)
lenS := r.URL.Query().Get("length")
if lenS != "" {
lenI, err = strconv.Atoi(lenS)
if err != nil {
lenI = 100
}
} else {
lenI = 100
}
fmt.Fprintf(w, compPi(lenI))
}
func compPi(L int) string {
var Pi []string
N := (L)/4 + 1
s := make([]int, N+3)
w := make([]int, N+3)
v := make([]int, N+3)
q := make([]int, N+3)
n := (int)(float32(L)/1.39793 + 1)
w[0] = 16 * 5
v[0] = 4 * 239
for k := 1; k <= n; k++ {
div(w, 25, w, N)
div(v, 57121, v, N)
sub(w, v, q, N)
div(q, 2*k-1, q, N)
if k%2 != 0 {
add(s, q, s, N)
} else {
sub(s, q, s, N)
}
}
Pi = append(Pi, fmt.Sprintf("%d.", s[0]))
for k := 1; k < N; k++ {
Pi = append(Pi, fmt.Sprintf("%04d", s[k]))
}
return strings.Join(Pi, "")
}
func add(a []int, b []int, c []int, N int) {
i, carry := 0, 0
for i = N + 1; i >= 0; i-- {
c[i] = a[i] + b[i] + carry
if c[i] < 10000 {
carry = 0
} else {
c[i] = c[i] - 10000
carry = 1
}
}
}
func sub(a []int, b []int, c []int, N int) {
i, borrow := 0, 0
for i = N + 1; i >= 0; i-- {
c[i] = a[i] - b[i] - borrow
if c[i] >= 0 {
borrow = 0
} else {
c[i] = c[i] + 10000
borrow = 1
}
}
}
func div(a []int, b int, c []int, N int) {
i, tmp, remain := 0, 0, 0
for i = 0; i <= N+1; i++ {
tmp = a[i] + remain
c[i] = tmp / b
remain = (tmp % b) * 10000
}
}
服務測試
基於上面提供的hpa-test-app映象, 我們建立一個hpa-test-app service, 然後為該service新增HPA機制。
注意事項:
- 當Pod沒有設定request時,HPA不會工作。
建立Deployment
注意根據需要修改自己的映象地址
cat << EOF > hpa-test-app-deploy.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: hpa-test-app-deploy
labels:
app: hpa
version: v0.0.1
spec:
replicas: 1
selector:
matchLabels:
name: hpa-test-app-deploy
app: hpa
version: v0.0.1
template:
metadata:
labels:
name: hpa-test-app-deploy
app: hpa
version: v0.0.1
spec:
containers:
- name: hpa-test-app-deploy
image: 192.168.204.15/kubernetes/hpa-test-app:v0.0.1
ports:
- containerPort: 8080
name: http
protocol: TCP
resources:
requests:
cpu: 0.006
memory: 32Mi
limits:
cpu: 0.06
memory: 128Mi
EOF
kubectl create -f hpa-test-app-deploy.yaml
建立service
cat << EOF > hpa-test-app-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: hpa-test-app-svc
labels:
app: hpa
version: v0.0.1
spec:
selector:
name: hpa-test-app-deploy
app: hpa
version: v0.0.1
ports:
- name: http
port: 8080
protocol: TCP
EOF
kubectl create -f hpa-test-app-svc.yaml
建立HPA
cat << EOF > hpa-test-app-hpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: hpa-test-app-hpa
labels:
app: hpa
version: v0.0.1
spec:
scaleTargetRef:
apiVersion: v1
kind: Deployment
name: hpa-test-app-deploy
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 70
EOF
kubectl create -f hpa-test-app-hpa.yaml
檢查服務
[[email protected] hpa-test]# kubectl get pods
NAME READY STATUS RESTARTS AGE
hpa-test-app-deploy-69dbc4cf84-5jvb6 1/1 Running 0 23s
[[email protected] hpa-test]# kubectl logs hpa-test-app-deploy-69dbc4cf84-5jvb6
2018/02/05 05:13:14 Server work at :8080 ...
[[email protected] hpa-test]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hpa-test-app-hpa Deployment/hpa-test-app-deploy 0% / 70% 1 10 1 38s
增壓和減壓測試
首先我們啟動一個busybox的pod, 用來對hap-test-app服務進行壓力測試
cat << EOF > pod-busybox.yaml
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
restartPolicy: Always
EOF
kubectl create -f pod-busybox.yaml
然後我們找到hpa-test-app服務的clusterIP:
[[email protected] apps]# kubectl get svc hpa-test-app-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hpa-test-app-svc ClusterIP 10.254.155.132 <none> 8080/TCP 1m
應用擴張
進入容器, 持續訪問hpa-test-app的API(預設計算100位的π, 如果想加大計算可以通過query string: ?length=1000來增大單個請求對cpu的壓力),
kubectl exec -it busybox /bin/sh
/ # while true; do wget -q -O- 10.254.155.132:8080; done
然後我們持續關係HPA的擴張
[[email protected] ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hpa-test-app-hpa Deployment/hpa-test-app-deploy 345% / 70% 1 10 4 52m
[[email protected] ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hpa-test-app-hpa Deployment/hpa-test-app-deploy 287% / 70% 1 10 8 56m
[[email protected] ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hpa-test-app-hpa Deployment/hpa-test-app-deploy 0% / 70% 1 10 10 1h
pod數量的變化情況 1–>2–>4–>8–>10, 最終達到最大的擴充套件上線而停止.
應用收縮
中斷對app的訪問, 會發現容器又收縮為原來的1個:
[[email protected] ~]# kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
hpa-test-app-hpa Deployment/hpa-test-app-deploy 0% / 70% 1 10 1 1h
[[email protected] ~]# kubectl describe hpa hpa-test-app-hpa
Name: hpa-test-app-hpa
Namespace: default
Labels: app=hpa
version=v0.0.1
Annotations: <none>
CreationTimestamp: Mon, 05 Feb 2018 00:47:31 -0500
Reference: Deployment/hpa-test-app-deploy
Metrics: ( current / target )
resource cpu on pods (as a percentage of request): 0% (0) / 70%
Min replicas: 1
Max replicas: 10
Conditions:
Type Status Reason Message
---- ------ ------ -------
AbleToScale True ReadyForNewScale the last scale time was sufficiently old as to warrant a new scale
ScalingActive True ValidMetricFound the HPA was able to succesfully calculate a replica count from cpu resource utilization (percentage of request)
ScalingLimited True TooFewReplicas the desired replica count was less than the minimum replica count
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedGetResourceMetric 46m (x57 over 1h) horizontal-pod-autoscaler missing request for cpu on container hpa-test-app-deploy in pod default/hpa-test-app-deploy-75b4fd6cf6-sf78p
Normal SuccessfulRescale 41m (x2 over 1h) horizontal-pod-autoscaler New size: 4; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 33m horizontal-pod-autoscaler New size: 10; reason: cpu resource utilization (percentage of request) above target
Normal SuccessfulRescale 28m horizontal-pod-autoscaler New size: 1; reason: All metrics below target
總結
本文主要介紹了HPA的相關原理和使用方法,此功能可以能對服務的容器數量做自動伸縮,對於服務的穩定性是一個很好的提升。但是當前穩定版本中只有cpu使用率這一個指標,是一個很大的弊端。但是kubernetes中關於HPA的架子已經成熟, 期待v2進入stable, 到時候HPA才能爆發它正在的魅力。