Kubernetesに負荷をかけオートスケールを観察する

本番環境でKubernetesを使うためには、Kubernetesがどのように動作するか把握していないと安心して運用することができません。
まずはサービスを運用する上で重要になる、高負荷時の動きを確認してみたいと思います。

KubernetesにはHPAとCluster Autoscalerの二種類がありますが、両方の動きを見ていきます。

詳細な解説は他に譲りますが、
HPAは稼働中のPodを増加させることで高負荷に耐え、
Cluster AutoscalerはPodを増加させることができないときにKubernetesが扱えるリソースを追加するものです。

つまり、負荷が高まるとHPAがおこりPodが追加され、それでも負荷が高くHPAが続きKubernetesが管理しているリソースに空きがなくなったときにCluster AutoscalerによってVMが追加され、そこにPodが追加されます。

環境

  • GKE 1.9.7-gke.0

実行するアプリケーション

できるだけ手軽に実験したいので、goでリクエストを受け付けて、少し負荷がかかる処理をした後にレスポンスを返すアプリケーションをKubernetesで実行してみます。

Kubernetesの構成は
Ingress -> Service -> Deployment
となっています。

実行するgoのAPIのソースはこのようにしました。

package main

import (
    "fmt"
    "net/http"
    "strconv"
)

func handler(w http.ResponseWriter, r *http.Request) {
    n := 0
    for i := 0; i < 1*1000*1000*1000; i++ {
        n += i
    }
    fmt.Fprintf(w, strconv.Itoa(n)+"\n")
    fmt.Fprintf(w, "my-api")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":80", nil)
}

とても単純なものです。
これをDocker Imageにし、DockerのrunコマンドでAPIサーバーが起動するようにDockerfileを書きます。

FROM golang:1.9.2-alpine3.6

ADD ./server.go ./
RUN go build -o server
CMD ["/go/server"]

Imageをアップロードしますが、せっかくGKEを使用するので同じGCP上にあるGoogle Container Registryにアップロードします。

$ docker build -t asia.gcr.io/{your-project-id}/my-api:v1 .
$ gcloud docker -- push asia.gcr.io/{your-project-id}/my-api:v1

Kubernetesの設定ファイル

Kubernetesについては省略します。
今回は以下の設定ファイルを使用します。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: "test-ip"
spec:
  backend:
    serviceName: service-my-api
    servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: service-my-api
spec:
  type: NodePort
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    name: http
  selector:
    app: my-api
    version: v1
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: my-api
        version: v1
    spec:
      containers:
      - name: myapi
        image: gcr.io/{your-project-id}/myapi:v1
        resources:
          requests:
            cpu: "500m"
          limits:
            cpu: "1500m"
        ports:
        - containerPort: 80
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: my-api
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-api
  minReplicas: 2
  maxReplicas: 6
  targetCPUUtilizationPercentage: 70

オートスケールに関係のあるところだけ解説します。
今回負荷をかけるAPIがあるPodに対してはCPUを1.5個まで使用を許可しています。
このPodが初期値として2台稼働しており、閾値を70としています。

つまり、CPUの使用率が70%を超えたときにPodの追加が行われることになります。
また、公式ドキュメントのautoscaling-algorithmによると、10%の許容値があったり瞬間的に超えただけだとオートスケールしなかったりするようです。

負荷をかける

負荷をかけるツールはいろいろありますが、手軽に使えるheyを使用します。

github.com

このツールで負荷をかけてみた結果、このようになりました。

PodのCPU使用率 f:id:akaimo3:20180515231231p:plain

ノードのCPU使用率 f:id:akaimo3:20180515231244p:plain

ピーク時はPodは6個に、ノードは3台にまで増えていますね。
HPAは追加にかかる時間が数秒なので、EC2とかのオートスケールと比べるとだいぶ早いですね。
これは便利そうです。