これはなに
KubernetesにはPodの起動数をリソース使用量などのメトリクスに応じて自動的に増減させる、HorizontalPodAutoscaling(以下、HPA)と呼ばれる仕組みがあります。HPAは広く使われるユースケースでは優れた仕組みですが、Podを完全にゼロにすることはできない仕組みになっています。KEDAはHPAの仕組みを拡張して、Podを完全にゼロにすることができます。
本記事ではKEDA自体のインストール方法などを確かめるためにデモ的な構成でKEDAによるPodのオートスケーリングを試してみます。
やってみよう
クラスタ構築
クラスタはおよそどんな環境でも動くと思います。
筆者はkindクラスタ上で動作確認をしています。
Prometheusのインストール
メトリクスを収集してKEDAからの問い合わせに答えてもらうためにprometheusをインストールします。
ちょっと大げさですが構築が簡単なのでkube-prometheus-stackというhelmチャートを利用します。
helm upgrade -i \
-n prometheus \
--create-namespace \
prom-op \
kube-prometheus-stack \
--repo https://prometheus-community.github.io/helm-charts \
--version 48.4.0
上記で設定しているリリース名prom-op
はこの後のingress-nginxの設定や、ScaledObject(KEDAのCustomResource)の設定などに現れるので、変更する場合は注意してください。
ingress-nginxのインストール
Ingress Controllerとしてingress-nginxを導入します。
今回の構成ではこのIngress Controllerが公開するメトリクスをautoscalingの入力として使います。
Helmチャートを使うので、次のvaluesファイルを作っておきます:
controller:
service:
type: ClusterIP
extraArgs:
metrics-per-host: 'false'
metrics:
enabled: true
serviceMonitor:
enabled: true
namespace: prometheus
additionalLabels:
release: prom-op
controller.extraArgs.metrics-per-host
は、この後作成するIngressリソースにhost名がないため設定しています。利用したいメトリクスはnginx_ingress_controller_requests
という、Ingressリソースごとに公開されるメトリクスなのですが、元となるIngressリソースにhost名が設定されていないとき、デフォルトではそのIngressリソースに関連付けられるメトリクスはingress-nginx controllerから公開されません1。公式ドキュメントに記載の通り、この引数を付与することで回避できます。
次のコマンドでインストールします:
helm upgrade -i \
-n ingress-nginx \
--create-namespace \
ingress-nginx \
ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--version 4.7.2 \
--values ingress-nginx-values.yaml
KEDAのインストール
KEDAをデフォルトでインストールします。
helm upgrade -i \
-n keda \
--create-namespace \
keda \
keda \
--repo https://kedacore.github.io/charts \
--version 2.11.2
サンプルアプリケーションのデプロイ
再現が簡単になるように、nginxイメージを使います。
操作を簡単にするためにapp/
ディレクトリを作って以下のマニフェストをそれぞれファイルとして作成します2。
マニフェスト一式(クリックして開く)
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- image: nginx:mainline-alpine
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
resources: {}
status: {}
apiVersion: v1
kind: Service
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
namespace: default
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: http
selector:
app: nginx
type: ClusterIP
status:
loadBalancer: {}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
creationTimestamp: null
name: nginx
namespace: default
spec:
ingressClassName: nginx
rules:
- http:
paths:
- backend:
service:
name: nginx
port:
name: http
path: /
pathType: Prefix
status:
loadBalancer: {}
最後に、目的のScaledObject
マニフェストを同じくapp/ディレクトリに作成しましょう。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: nginx
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx
idleReplicaCount: 0
minReplicaCount: 1
maxReplicaCount: 10
triggers:
- type: prometheus
metadata:
serverAddress: http://prom-op-kube-prometheus-st-prometheus.prometheus.svc:9090
query: 'sum by (exported_namespace,exported_service) (irate(nginx_ingress_controller_requests[1m]))'
activationThreshold: '0'
threshold: '2'
metricType: AverageValue
上記のScaledObjectでは、Prometheusから以下のqueryでメトリクスを取得しています。
sum by (exported_namespace,exported_service) (irate(nginx_ingress_controller_requests[1m]))
このメトリクスはingress-nginxが公開して、ServiceMonitorリソース(Prometheus operatorのCustomResource)によって自動的にPrometheusに収集設定がされたメトリクスに含まれています。したがって、動作検証に当たっては、ingress-nginxを通るようにリクエストを送信する必要があります。
以下のコマンドで作成したマニフェストをすべて適用します:
kubectl apply -f app/
applyできたかは以下のコマンドで確認3します:
kubectl get so,hpa,deploy,po,svc,ep
動作確認
ingress/nginxにリクエストを送信します。
リクエストをIngressに届ける方法は環境によっていろいろありますが、どの環境でも使える方法としてport-fowardによる方法を上げると、別のターミナルを立ち上げて以下のコマンドでport-forwardできます:
kubectl -n ingress-nginx port-forward service/ingress-nginx-controller 3080:80
ScaledObjectがDeactivateな状態にあると、nginx Podが一つもないため503になりますが、503エラーもScaledObjectのリクエスト数にカウントされるので、あきらめずにリクエストを送信し続けます。簡易的な以下のコマンドでもよいですし、スクリプト言語を利用しても構いません。
watch -p -n 1 curl -s localhost:3080/
上記コマンドなどで1秒に1回リクエストを送ると、Podが一つ立ち上がってくるはずです。
まずは普通のautoscalingと同じく、scale-outしてみます。
次のコマンド4でリクエストの頻度を約1秒に3回に増やしてみます。
watch -p -n 0.3 curl -s localhost:3080/
HPAオブジェクトを監視していると、メトリクスが3.3程度になることが確認でき、Podの数が2に増えるはずです。
次に、scale-in、特にゼロへのscale-inを試してみましょう。
リクエストを行っていたwatchコマンドをCtrl + Cで中断するだけです。scale-inにはcooldown期間として5分がデフォルトで設定されるので5分ほど待ちます。
するとnginx Podがすべて削除されるはずです。
もう一度リクエストを開始することでゼロからのscale-outも確認できます。
まとめ
KEDAでゼロへのscale-in/scale-outを試してみました。
KEDAではAmazon SQSやApache Kafkaなどのメッセージブローカーのメトリクス、例えばqueueの長さやメッセージの遅延時間などを利用して、ブローカーにメッセージがたまっていることを検知し、Podをautoscaleさせる使い方がより一般的です5が、今回は動作確認のしやすいWebサーバーをでもアプリとして構成してみました。
-
複数のIngressのメトリクスが合算されて爆発(オーバーフローのことなのか、急増することなのかはよくわかりません)することを避けるためだそうです。 ↩
-
1ファイルにまとめても構いませんし、kustomizeを使っても構いませんが、この後のコマンドに影響するので注意してください。 ↩
-
ScaledObject
リソースの省略形がso
です。 ↩ -
watch
コマンドが小数を取り扱えない場合はwatch -p -n 1 'for i in $(seq 3); do curl -s localhost:3080 & done'
でもよいでしょう。 ↩ -
今回の構成の場合、Podがゼロのときのリクエストはエラーになってしまい、利用側でリトライを行う必要があります。 ↩