メインコンテンツまでスキップ

KubernetesのHPAハンズオン

· 約6分

本稿ではKubernetesのHorizontal Pod Autoscaler(HPA)のクイックハンズオンを行います。 Kubernetes上にシンプルなCPU負荷の高いWebアプリをデプロイし、そのデプロイのためにHPAを設定します。 その後、負荷テストを行い、設定したメトリクスに基づいてレプリカの数が自動的にスケールすることを確認します。

検証環境

  • MacBook Pro 2021 (M1 Pro) 32GB
  • Kubernetes v1.26.4 on Rancher Desktop v1.8.1 using VM with 4 CPUs and 12 GB memory

概要

  1. 検証用に簡単なGoアプリケーションを作成する
  2. GoアプリケーションをDocker化する
  3. Kubernetes上にアプリケーションをデプロイする
  4. HPAを構成する
  5. Goアプリケーションのポートフォワーディングを確立する
  6. k6ロードテスターによるテスト

GitHubリポジトリはこちら

Step 1. 検証用Goアプリケーションの作成

まず、Ginフレームワークを用いてCPU負荷の大きなGoアプリケーションを作成します。

package main

import (
"fmt"
"math"
"math/rand"
"net/http"

"github.com/gin-gonic/gin"
)

func cpu_intensive(rnd int) float64 {
f := 0.
for i := 0; i < 10_000*rnd; i++ {
f += math.Sqrt(float64(i))
}
return f
}

func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
_rnd := rand.Intn(1000)
f := cpu_intensive(_rnd)
ret := fmt.Sprintf("%d: %.2f", _rnd, f)
c.String(http.StatusOK, ret)
})
r.Run(":8080")
}

上記のプログラムはポート8080GET /リクエストを受信し、sqrtを異なるループ回数行います。

Step 2: GoアプリケーションのDocker化

次にDockerfileを以下の通り作成します。

FROM golang:alpine AS builder
WORKDIR /src
COPY . /src
RUN CGO_ENABLED=0 go build -o app

FROM scratch
COPY --from=builder /src/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]

上記のDockerfileを用いてイメージをビルドし、DockerHub上にryojpn/go-cpu-intensiveとしてアップロードしておきました。 よろしければご利用ください。

Step 3: Kubernetes上にデプロイ

以下のDeploymentを作成します。

apiVersion: apps/v1
kind: Deployment
metadata:
name: go-cpu-intensive
labels:
app: go-cpu-intensive
spec:
replicas: 1
selector:
matchLabels:
app: go-cpu-intensive
template:
metadata:
labels:
app: go-cpu-intensive
spec:
containers:
- name: go-cpu-intensive
image: ryojpn/go-cpu-intensive
resources:
limits:
cpu: 1000m
memory: 500Mi
requests:
cpu: 500m
memory: 250Mi
ports:
- containerPort: 8080

続いて、作成したDeploymentをapplyします:

kubectl apply -f k8s

Step 4: HPAの設定

HPAを設定し、CPU利用率に基づいてPodのレプリカ数を自動調節します:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: go-cpu-intensive
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: go-cpu-intensive
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

こちらもapplyしましょう:

kubectl apply -f k8s

Step 5: ポートフォワード

まずGoアプリケーションのPod名を取得します:

kubectl get po

go-cpu-intensive Deploymentに対するPod名が取得できたら以下を実行し、8080番でのポートフォワードを行います:

kubectl port-forward go-cpu-intensive-54449f89d-rg4qz 8080:8080

Pod名はご利用の環境に適宜修正してください。

Step 6: k6負荷試験ツールによるテスト

k6をインストールし、以下のテスト設定ファイルを作成します:

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
stages: [
{ duration: '1m', target: 100 },
{ duration: '1m', target: 200 },
{ duration: '3m', target: 800 },
{ duration: '1m', target: 900 },
{ duration: '2m', target: 500 },
{ duration: '2m', target: 200 },
]
}

export default function () {
const res = http.get('http://localhost:8080/');
check(res, {
'status was 200': (r) => r.status == 200,
})
sleep(1);
}

続いて、ターミナルウィンドウを2つ開いてください。

一つ目では、HPAリソースをwatchして、メトリクス値の変化とレプリカ数の変動を監視します:

kubectl get hpa -w

もう一つのターミナルではk6を実行し、Podに負荷をかけます:

k6 run loadtest.js

以下が実行例のスクリーンショットです。 HPAスクリーンショット

ご覧のように、CPU使用率の指標は最初のうちは最大100%にまで達していますが、レプリカが4つにスケールすると45%程度で安定します。 負荷テストが終了した後、スケールインを開始するまでには数分(デフォルトでは5分)かかります。この時間は、必要に応じて調整することも可能です。

最後に

上記のデモでは、ターゲットメトリクスとしてCPU使用率を使用しました。 しかし、HPAは常に目標メトリクスを満たすようにレプリカ数を維持することができないことが確認されました。 リクエスト数の増加がある程度予測できる場合は、レプリカ数を手動で大まかに設定した上でHPAに調整させるのが良いかもしれません。

Prometheus Adapterを使うと、CPU/メモリ使用率以外の様々なメトリクスをオートスケールに利用することが可能です。 今度はHTTPレスポンスタイムを取得し、オートスケーリングのメトリクスに活用する方法について調べてみようと思います。

参考

GitHubリポジトリ