EKSにおけるAutoScalingパターン
December 3, 2019
Amazon EKS Advent Calendar 2019の3日目です
最近EKSにおけるAutoScalingについて仕事でいろいろ試して、知見がだいぶたまったのでそれをまとめてみようと思います。 (といっても商用環境でこれを適用したというわけでなく、検証した程度なので商用環境で耐えれる内容ではないかもしれません..)
k8sのAutoScalingについて
最初にk8sにおけるAutoScalingについて触れておこうと思います。 k8sの世界でスケールさせる必要があるリソースはなんでしょうか?
PodとNodeですね。 Podはk8sの世界に置いてデプロイできるアトミックなリソースですし PodはNodeの上に配置されるため、Pod数が増加してきたときに配置に耐えれるようにスケールできる必要があります。
これらのスケール方法ですが Podであれば、単純に新しいPodを配置したりDeployment,ReplicaSetリソースのreplica数を変更してあげればスケールできます。 Nodeであれば、新しいインスタンスを立ち上げてクラスタにジョインさせてあげればいいですね。 ただこれだと、 単なるスケールでありオートスケーリングとは呼べないので、次にk8sが提供しているオートスケーリングのための機能を紹介します。
Podのオートスケーリング手法
Podのオートスケーリングのためにk8sは2つの機能を提供しています。
HPA(Horizontal Pod AutoScaler)
https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
水平スケーリングです。Podの数を増やすスケーリングになります。 Deploymentリソースなどと組み合わせることで、Podの負荷に応じて動的にreplica数を増減してくれます。
VPA(Vertical Pod AutoScalier)
垂直オートスケーリングです。 podに割り当てるcpuやmemoryを負荷に合わせて動的にスケールしてくれます。 2019/12時点でまだbetaであり、また自分自身ほぼ使ったことがないので(昔軽く動作確認したくらい)、今回のパターンには載っていません。
Nodeのオートスケーリング手法
Cluster AutoScalerというのがk8sから提供されています。 全てのpodが動作できるように自動的にノード台数を調整してくれます。(リソース不足でpodがPendingになる場合は増やして、余分なノードが立っているときは減らしてくれます) EC2 AtuoScalingに対してAPIを叩いて、調整してくれるような仕組みになっています。 AWSの他に、GCP, Azureでも動作します。
k8sのオートスケーリング基礎知識としてはこのあたりになります。 以降は実際に自分が試してみた手法を3つ紹介していきます
- Podの負荷量に合わせてスケーリング(HPA+MetricsServer+ClusterAutoScaler)
- 外部メトリクスを利用してのスケーリング(HPA+CloudWatch+ClusterAutoScaler)
- 閉域でのスケーリング(HPA+CloudWatch Alarm)
Podの負荷量に合わせてスケーリング(HPA+MetricsServer+ClusterAutoScaler)
おそらく一番一般的なオートスケーリングになるのではないかと思います。 スケール対象となるDeployment/PodのCPUやMemory利用率を監視して、閾値を超えたらPod数を増やしてあげます。 さらにNodeにPodが配置できなくなったらClusterAutoScalerでNodeを増やしてあげます。
実際に動かして、動作を確認してみます。
EKSクラスタの構築
検証なので、お手軽にeksctlを使って構築します。 下記のような構成になります。
eksctlで、インターネットリーチャブルなEKSクラスタを立ち上げます。 立ち上げ後のkubectl操作はローカルPCから実行します。(以降のコマンドは全てローカルPC上で実行となります。
eksctlでクラスタ構築
macであればbrewでインストールできます https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-eksctl.html
$ brew tap weaveworks/tap
$ brew install weaveworks/tap/eksctl
$ eksctl version
[ℹ] version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.10.2"}
awsの設定をした後に
$ aws configure
以下のコマンドでクラスタを構築できます
$ eksctl create cluster --name <cluster-name> \
--version 1.14 \
--nodegroup-name <worker-group-name> \
--node-type t3.small \
--nodes 1 \
--nodes-min 1 \
--nodes-max 5 \
--managed \
--asg-access
--managed
をつけると、先月発表されたEKS Managed Worker Groupが使われます
--asg-access
をつけるとClusterAutoScaler用にWorkerのIAMにautoscalingへのアクセス権限が付与されます。
大体10分ほど待つと構築されます。 完了したら確認します。
$ aws eks update-kubeconfig --name <clustername>
$ kubectl get no
NAME STATUS ROLES AGE VERSION
ip-192-168-4-177.ap-northeast-1.compute.internal Ready <none> 77s v1.14.7-eks-1861c5
ip-192-168-63-13.ap-northeast-1.compute.internal Ready <none> 74s v1.14.7-eks-1861c5
Cluster AutoScalerの準備
https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md
こちらのマニフェストを落として、以下を変更した後にapplyします
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=aws
- --skip-nodes-with-local-storage=false
- - --nodes=1:10:k8s-worker-asg-1
+ - --nodes=<minsize>:<maxsize>:<autoscaling group name>
minsize
はautoscalingの最小数、maxsize
はautoscalingの最大数を指定します。
<autoscaling group name>
には、以下のコマンドで対象のasgのAutoScalingGroupName
を確認して指定してください
$ aws autoscaling describe-auto-scaling-groups
ログを確認して、特にエラーが出ていなければおkです。
$ kubectl get po -n kube-system | grep auto
cluster-autoscaler-7848897864-9jwn2 1/1 Running 0 3m5s
$ kubectl -n kube-system logs cluster-autoscaler-7848897864-9jwn2
HPAの設定
公式の手順にそって進めていきます。
まず、PodのCPU利用率などを回収するためのmetric-serverを作成します。
$ git clone https://github.com/kubernetes-sigs/metrics-server.git
$ kubectl apply -f metrics-server/deploy/1.8+/
次にapacheのdeploymentとserviceを作成します
apiVersion: apps/v1
kind: Deployment
metadata:
name: php-apache
spec:
selector:
matchLabels:
app: php-apache
replicas: 1
template:
metadata:
labels:
app: php-apache
spec:
containers:
- name: php-apache
image: k8s.gcr.io/hpa-example
resources:
requests:
cpu: 200m
limits:
cpu: 500m
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: php-apache
spec:
selector:
app: php-apache
ports:
- protocol: TCP
port: 80
targetPort: 80
metric serverを使ったhpaの場合は、resourcesを指定しないと動作しないのでここだけは注意しましょう 次にこのdeploymentに対してhpaを作成します
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache-hpa
spec:
maxReplicas: 50
minReplicas: 1
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
targetCPUUtilizationPercentage: 50
CPU利用率が50%になるように、1~50の間でpod数を増減してくれます。
この2つをdeployしたら確認します
$ kubectl get hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache-hpa Deployment/php-apache 0%/50% 1 50 1 2m11s
これで準備が完了しました。
負荷をかける
apacheに負荷をかけていきます。以下のload-generator podをいくつかたてます。
$ kubectl run --generator=run-pod/v1 -it --rm load-generator --image=busybox /bin/sh
// プロンプトが表示されたら以下のコマンドを叩く
while true; do wget -q -O- http://php-apache.default.svc.cluster.local; done
結果
$ kubectl get hpa -w 365ms Mon Dec 2 17:27:18 2019
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
php-apache-hpa Deployment/php-apache 0%/50% 1 50 1 9m24s
php-apache-hpa Deployment/php-apache 249%/50% 1 50 1 10m
php-apache-hpa Deployment/php-apache 249%/50% 1 50 4 10m
php-apache-hpa Deployment/php-apache 249%/50% 1 50 5 10m
php-apache-hpa Deployment/php-apache 62%/50% 1 50 5 11m
php-apache-hpa Deployment/php-apache 71%/50% 1 50 5 12m
負荷が高まってPod数が増えていってます。 さらに以下でpod数をcloudwatchに集めて、増減を確認しました。
#!/bin/bash
while true
do
POD_NUM=$(kubectl get deployment php-apache -ojson | jq .status.availableReplicas)
aws cloudwatch put-metric-data --metric-name sqs-app-pop-pod-nums --namespace "eks-sample" --value $POD_NUM
sleep 5
done
負荷をかけるとPod数とNode数が増加して, 負荷を止めるとPod数とNode数が縮小することが確認できました。
外部メトリクスを利用してのスケーリング(HPA+CloudWatch+ClusterAutoScaler)
1つ前の手法では、外部からリクエストが送られてくるようなPod(例えばWebアプリのような)だとうまくスケールさせることができます。 しかし、Pod自体が外部にリクエストしにいくような場合(例えばQueueにデータを取りにいって処理するようなアプリ)だとうまくスケールできない可能性があります。
いわゆるPull型とPush型の問題で、後者の場合はCPU利用率やメモリ利用率が上がりきらない可能性があるためです。 例えば毎秒30件のデータをQueueから取得できるアプリがあったとして、そのキューに毎秒100件データが挿入される場合も、毎秒30件データが挿入される場合も、アプリのCPU利用率などは変わらないことが想像できます。
この場合は外部メトリクスをスケール判断に利用する必要があるかと思います。 幸いにもHPAにはカスタムメトリックという外部メトリクスでスケール判断を行う機能があるので、これを利用してオートスケールするのを確認してみます。
例として、シンプルにSQSからデータを取得するアプリを用意して、それをスケールさせてみようと思います。 外部メトリクスとしては、SQSを使うので収集が楽なCloudWatchを利用します。 CPU利用率などをみる必要がなくなったので、このhpaはMetrics Serverがなくても動作します。 またClusterAutoScalerについては、1つ前の手法と変わりありません。
SQSとやりとりするアプリを用意
SQSでキューを1つ用意したら、そこにPushやPopを行うプログラムを組みます
import boto3
import uuid
import os
import time
queue_name = os.environ['QUEUE_NAME']
action_type = os.environ['ACTION_TYPE']
sqs = boto3.resource('sqs', endpoint_url='https://sqs.ap-northeast-1.amazonaws.com', region_name="ap-northeast-1")
queue = sqs.get_queue_by_name(QueueName=queue_name)
if action_type == 'push':
while True:
msg_list = [{'Id' : '{}'.format(uuid.uuid4()), 'MessageBody' : 'msg_{}'.format(uuid.uuid4())} for i in range(10)]
response = queue.send_messages(Entries=msg_list)
if action_type == 'pop':
while True:
msg_list = queue.receive_messages(MaxNumberOfMessages=10)
if msg_list:
for message in msg_list:
print(message.body)
message.delete()
else:
print('queue is empty!!')
time.sleep(5)
これをdockerでbuildしておきます。以下にあげてます https://hub.docker.com/repository/docker/esaka/sqs-poppush
次にこれをk8s上にdeployしたいのですが、現在のWorkerのIAMロールにはSQSとやりとりするための権限がないので 以下のIAMポリシーを作成して、Workerロールにアタッチしておきます
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:DeleteMessageBatch",
"sqs:SendMessageBatch",
"sqs:ReceiveMessage",
"sqs:SendMessage"
],
"Resource": "arn:aws:sqs:ap-northeast-1:<accountID>:<queueName>"
}
]
}
これを以下のマニフェストで動かして、キューにデータをpushします
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqs-app-push
spec:
selector:
matchLabels:
app: sqs-app-push
replicas: 1
template:
metadata:
labels:
app: sqs-app-push
spec:
containers:
- name: sqs-app-push
image: esaka/sqs-poppush:latest
command: ["python", "sqs-poppush.py"]
env:
- name: QUEUE_NAME
value: eks-sample-queue
- name: ACTION_TYPE
value: push
- name: AWS_DEFAULT_REGION
value: "ap-northeast-1"
環境変数でPushするQueueを指定してあげてください。eks-sample-queue
というところを使うようにしました。
k8s-cloudwatch-adapterの用意
AWSは公式でk8s-cloudwatch-adapterというcloudwatch用のadapterを用意しています。 これを利用することで、hpaのスケール条件にcloudwatchのメトリクスが使えるようになります。
まずこれもIAMの設定が必要です。メトリクスを取得するためのポリシーを作成してWorkerのIAMロールにアタッチします
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricData"
],
"Resource": "*"
}
]
}
あとは提供しているマニフェストをapplyします
$ kubectl apply -f https://raw.githubusercontent.com/awslabs/k8s-cloudwatch-adapter/master/deploy/adapter.yaml
これでカスタムリソースとして、CloudWatchのメトリクスをk8s内で扱えるようになりました。 SQSのキューの長さをメトリクスとして登録しておきます。
apiVersion: metrics.aws/v1alpha1
kind: ExternalMetric
metadata:
name: eks-sample-queue-length
spec:
name: eks-sample-queue-length
resource:
resource: "deployment"
queries:
- id: sqs_queue_length
metricStat:
metric:
namespace: "AWS/SQS"
metricName: "ApproximateNumberOfMessagesVisible"
dimensions:
- name: QueueName
value: "eks-sample-queue"
period: 300
stat: Average
unit: Count
returnData: true
hpaの設定
最後にキューをpopするアプリをdeployし、それのhpaを設定します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqs-app-pop
spec:
selector:
matchLabels:
app: sqs-app-pop
replicas: 1
template:
metadata:
labels:
app: sqs-app-pop
spec:
containers:
- name: sqs-app-pop
image: esaka/sqs-poppush:latest
command: ["python", "sqs-poppush.py"]
env:
- name: QUEUE_NAME
value: eks-sample-queue
- name: ACTION_TYPE
value: pop
- name: AWS_DEFAULT_REGION
value: "ap-northeast-1"
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2beta1
metadata:
name: sqs-app-pop-scaler
spec:
scaleTargetRef:
apiVersion: apps/v1beta1
kind: Deployment
name: sqs-app-pop
minReplicas: 1
maxReplicas: 40
metrics:
- type: External
external:
metricName: eks-sample-queue-length # cloudwatch-metrics.ymlのname
targetValue: 100
動作確認
pushするアプリをreplica数=2で動かしている状態で, popするアプリをreplica数=1で開始してみました。
最初はpushの方が量多いので、どんどんキューに溜まっていきますが 途中からhpaが始まって、pod数が増えると共にキューの長さの傾きが緩やかになり 最終的には傾きが逆になって、pod数も減っていきました。 ゆっくりですが、pushとpopで均衡が取れるreplica数が変化していく様子が見れます。
閉域でのスケーリング(HPA+CloudWatch Alarm)
仕事でAWS使う場合などは、インターネットとのアクセスが禁止されるというのはよくあると思います。 しかしながら、これまで紹介したパターンは2つともインターネットに繋がるVPCというのが前提となっています。 その理由がec2autoscalingエンドポイントのせいです。
Node数の増減を行っていたClusterAutoScalerはec2autoscalingエンドポイントを叩いて、Node数を増減させます。 EC2 AutoScalingはEC2の機能の一部だからec2エンドポイントでAPI叩けるだろうと思ってしまうのですが、これが厳密に分かれており、また2019/12時点でec2autoscalingにはVPCエンドポイントが提供されていないため閉域で叩くことができません。 (CloudWatchはVPCエンドポイントがありますので、HPAはこれまで通り実行できます)
EKS Managed Worker Groupが導入されたことで、EKSのエンドポイントを使ってノード数の増減ができるようになったのですが、ClusterAutoScalerがまだ対応していないようですし、そもそもEKSのエンドポイント(eks.ap-northeast-1.amazonaws.com)もまだVPC Endpointが提供されていないため、閉域での利用は難しいです。
現状では、以下のように CloudWatch Alarmを利用するのが解決策の一つかと思います。
図に書いたように、CloudWatch AlarmからEC2 AutoScalingまでがAWS内のネットワークを通ってリクエストが行われるので, 閉域でもEC2 AutoScalingが実行できます。
これも実際に試しみてみます。
閉域のEKSクラスタを構築
InternetGatewayをなくして、Imageのpullも外部のDocker HubではなくECRを利用するように変更します。 検証なのでkubectlを打つのは手抜きでローカルのままです。。
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cluster-endpoint.html エンドポイントのパブリックアクセス, エンドポイントのプライベートアクセス共に有効の状態です。 厳密にやるなら、エンドポイントのパブリックアクセス=無効, エンドポイントのプライベートアクセス=有効にして、EKSのVPC内にkubectlを打つためのインスタンスを用意してあげて、そこに踏み台経由でアクセスして操作することになるかと思います。
作成方法なのですが、これまで通りeksctlそのままだと作れないので、以下の4つの手順に分けて構築します。
- ネットワーク周りのリソース作成
- EKSクラスタを構築
- EKSクラスタの設定をアップデートして、エンドポイントのプライベートアクセスを有効にする
- ワーカーグループを作成する
ネットワーク周りのリソース作成
CloudFormationのtemplateを用意したので、これで作成してください
VPCとprivate subnet2つ、それから以下のVPC Endpointを作成します
- ecr.api
- ecr.dkr
- ec2
- monitoring
- s3
- sqs
sqs, monitoringは今回の検証用で使うためでEKSの動作に必須ではありません。 ecr.api, ecr.dkr, ec2, s3はEKSを閉域で動かすのに必須になります。 (s3は不要かと思ったのですが、ecrからpullするときにs3に認証情報っぽい?のを取りにいってるようだったので付与してます)
EKSクラスタを構築
クラスタはeksctlで構築します。(IAMの設定など一括でやってくれるので) 1つ前の手順で作成したsubnet2つのsubnet-idを指定して以下を実行します
$ eksctl create cluster --name <cluster-name> \
--vpc-private-subnets <private-subentid1>,<private-subnetid2> \
--without-nodegroup
--vpc-private-subnets
を指定することで、既存のネットワークを利用でき
--without-nodegroup
を指定することで、ワーカーグループを作成せずにクラスタだけ構築できます。
これも10分ほど待てば作られます。
EKSクラスタの設定をアップデートして、エンドポイントのプライベートアクセスを有効にする
https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cluster-endpoint.html 一つ前のステップでこれも設定できればいいのですが、現在eksctlではサポートしていないようなので、クラスタ作成後にプライベートアクセスを有効にしてやります。
以下のコマンドで実行します。
$ aws eks update-cluster-config --name <cluster-name> --resources-vpc-config endpointPublicAccess=true,endpointPrivateAccess=true
ワーカーグループを作成する
最後にworker groupを作成します。 eksctlで作りたいのですが、現在eksctlを使って、manage worker groupかつ、private subnetのみのnodegroupは作成負荷のようなので、前のパターンで作成されていた、CFnテンプレートを参考にして、CloudFormationで作成します。
以下にテンプレートを用意しておきました。 https://github.com/esakat/advent-calender-eks/blob/master/CFn/private-managed-worker.yml
これで作成後にnodeがちゃんとReadyになればおkです。
$ kubectl get no
NAME STATUS ROLES AGE VERSION
ip-10-192-0-79.ap-northeast-1.compute.internal Ready <none> 21m v1.14.7-eks-1861c5
またここで、作成されたWorker用のIAM Roleに1つ前のパターンで作成した2つのポリシー(CloudWatchとSQS)をアタッチしておいてください
外部ImageをECRへPushしておく
外部にアクセスできなかったので、利用するDocker Imageを全てECRにあげておく必要があります。 以下のImageを内部にあげておきます。
- chankh/k8s-cloudwatch-adapter:v0.7.0
- esaka/sqs-poppush:latest
$ aws ecr create-repository --repository-name chankh/k8s-cloudwatch-adapter
$ aws ecr create-repository --repository-name esaka/sqs-poppush
$ $(aws ecr get-login --no-include-email --region ap-northeast-1)
$ docker pull chankh/k8s-cloudwatch-adapter:v0.7.0
$ docker pull esaka/sqs-poppush:latest
$ docker tag chankh/k8s-cloudwatch-adapter:v0.7.0 <accountId>.dkr.ecr.ap-northeast-1.amazonaws.com/chankh/k8s-cloudwatch-adapter:v0.7.0
$ docker tag esaka/sqs-poppush:latest <accountId>.dkr.ecr.ap-northeast-1.amazonaws.com/esaka/sqs-poppush:latest
$ docker push <accountId>.dkr.ecr.ap-northeast-1.amazonaws.com/chankh/k8s-cloudwatch-adapter:v0.7.0
$ docker push <accountId>.dkr.ecr.ap-northeast-1.amazonaws.com/esaka/sqs-poppush:latest
k8s-cloudwatch-adapterをデプロイしておきます。 manifestを落とし後に
$ wget https://raw.githubusercontent.com/awslabs/k8s-cloudwatch-adapter/master/deploy/adapter.yaml
imageをecrの方にむけてください
- name: k8s-cloudwatch-adapter
- image: chankh/k8s-cloudwatch-adapter:v0.7.0
+ image: <accountId>.dkr.ecr.ap-northeast-1.amazonaws.com/chankh/k8s-cloudwatch-adapter:v0.7.0
applyします
$ kubectl apply -f adapter.yaml
$ kubectl get po -n custom-metrics
NAME READY STATUS RESTARTS AGE
k8s-cloudwatch-adapter-687bbc8c86-rp5nb 1/1 Running 0 27s
正常に動作してくれました。
CloudWatch Alarmの設定
以下にCloudFormationのtemplateを用意したので、それを設定してください
https://github.com/esakat/advent-calender-eks/blob/master/CFn/cloudwatch-alarm.yml
分かりづらいので、具体的に何をやっているか書きます。 作成されるリソースとしては以下3つになります。
- AutoScalingGroupのScaleIn Policy
- AutoScalingGroupのScaleOut Policy
- CloudWatch Alarm
ScaleIn/Out Policyについて
AutoScalingのスケーリングポリシーは3つあるんですが、その中で一番単純なシンプルスケーリングを使っています。
上記のように2つのポリシーが対象のAutoScaling Groupに割り当てられます。
アクションを実行
のところに書かれているとおり、スケールアウトが実行されると1台追加、スケールインが実行されると1台削除という挙動になります。
またその後待機
はクールタイムを設定しており、一度スケールが行われた後、再度スケール可能になるまでの時間となります。
スケールアウトは早く実行して欲しいので60秒, スケールインはゆっくり実行して欲しいので300秒の設定になっています。
また、これだとスケールインで0台になったり、スケールアウトで99台立ったりする?とか思うかもしれませんが AutoScalingGroupのMin/Maxで指定した範囲内でのスケールになりますのでご安心を
そして、これらはあくまでpolicyなので、次のCloudWatch Alarmからこれらを呼び出すようになっています。
CloudWatch Alarmについて
Alarmについては以下2つ説明した方が良いと思っています。
- アラームが行うアクション
- アラームに使うメトリクス
まずアラームが行うアクションですが、上記で説明したAutoScalingGroupのScaleInとScaleOutを呼び出しています。 ここはCloudFormationのテンプレートをみるとわかるのですが、以下のようにアクションを設定しています。
AlarmActions:
- !Ref ASGScaleOutPolicy
OKActions:
- !Ref ASGScaleInPolicy
名前の通りなんですが、アラーム時はAlarmActionsで指定したScaleOutが, OK時はOKActionsで指定したScaleInが呼ばれます。 これでAutoScalingGroupのインスタンス台数を増減できます。
次にアラームに使うメトリクスですが、HPAでやってるようにキューの長さだけでもいいのですが それだと、PushとPopで均衡が取れててキューの長さが0の時もスケールインしようとしてしまいます。 ので、インスタンスのCPU利用率も取得して、これらを組み合わせた算術式メトリクスというのを利用しています。
上が実際にアラームで使っているメトリクスなのですが、e1
, m1
, m2
という3つのメトリクスを用意しています。
m1
はキューの長さを集めていて、m2
は対象AutoScalingGroupのインスタンスの平均CPU利用率を集めています。
それでこれらをOR条件のように、どちらかがアラームをあげていればアラームという状況にしたいです。
そのためにm1,m2のMAXとしてe1
を作っています。
具体的には以下のような算術式にしています。
MAX([(m1/1000),(m2/40)])
最初にm1とm2をそれぞれの閾値で割っています(キューの長さは1000より大きいとアラーム, CPU利用率が40%より大きいとアラーム)
これによって2つの閾値が揃って1になります(閾値で割っているので、閾値を超えると1以上になりますし、閾値を下回ると1以下になります)
あとはこれのMAXをとることで、どちらかが閾値を超えていればe1 >= 1
になります。
あとはe1 >= 1
をアラーム条件に設定すれば、どちらかがアラームの場合にアラームをあげるという設定ができます。
結果確認
パターン2の時と同じような試験を試しました。 pushするアプリをreplica数=1で動かしている状態で, popするアプリをreplica数=1で開始してみました。
いい感じにスケールアウト/インするのが確認できました。 (途中でpush側のpodが乗っているインスタンスが削除されて、そのあとpush側がPendingになったので後半はただのスケールインになってしまった…)
余談1
閉域でもできるという利点の他に、このパターンではスケール時間の短縮を実現できる可能性があります。 前2つのパターンで使っていたClusterAutoScalerでは、HPAでreplica数を増やして、Nodeに配置できなくなったというのを検知してノードを増やすため段階的な処理になり、スケールに時間がかかってしまいます。
このパターンではClusterAutoScalerを利用せず、CloudWatch Alarmを利用するため、NodeとPodのオートスケーリングが非同期的になり また金さえつめばCloudWatchも秒単位Alarmあげれるので、スケールにかかる時間を大幅に減らせると思います。
そのため投機的実行を行いたい場合もこの方法を検討した方が良いかもしれません。
余談2
今回のシングルテナントような、単一のアプリがクラスタを占有する構成であれば、利用するメトリクスはそこまで悩まなくて良いと思うのですが、マルチテナントのように、様々な種類のアプリが稼働するクラスタでは利用するメトリクスを変える必要があるかと思います。 それこそ、ClusterAutoScalerが内部でやってるようにPodのリソース利用率の合計からNode数を割り出すのが良いかと。 ただ、自分で触ってもらえればわかると思うのですが CloudWatch Alarmの発火条件の設定はあまり複雑な設定ができないので, 別の箇所でメトリクスの計算を行って、CloudWatchへカスタムメトリクスとして登録してあげるという手段もありかもです。
最後に
EKSにおけるオートスケーリングパターンを3つ紹介しました。
EKSというかコンテナ周り、どんどん機能が追加されて便利になってきてますね。 (Managed WorkerノードもRe:inventで出るかなと思ってたのですが、kubeconで出たので焦って調べました(今回の記事に関わってくる内容なので))
https://github.com/aws/containers-roadmap/projects/1 あたりをみると、今後追加されそうな機能とか見れて楽しいです。
個人的にはEKS on Fargateがくることを期待してます。 最近Coming Soonになったので、今回のRe:Inventで発表されるのでは?と期待してます