昨年は内部的なことを多くやっていたり、10年ぶりに格ゲー復帰したりで、なんとなくご無沙汰になります。が、元同僚エンジニアに名指しされたり、
パルが年末にアドベで連続更新してるのをみて、俺も頑張らなきゃなと思い直した次第でありやす。
久々すぎてなんか文章のノリがノらないので、最近調べ直した AWS の Auto Scaling 周りについて、今はこんだけ抑えておけばえぇんちゃうくらいの感じで、まとまり悪いかもですがまとめてみたいと思います。
目次
- 色々な「Auto Scaling」を理解する
- AWS Auto Scaling は EC2 Auto Scaling の代わりになるのか
- EC2 Auto Scaling を構築する
- 構成
- LaunchTemplates
- AutoScalingGroup
- AutoScalingPolicy
- LifecycleHook
- Notifications
- Plans
- スポット対策 Lambda
- OSに埋め込むスポット対策
- インスタンスタイプの混合
- インスタンス増減の特徴
- AutoScalingの各種設定
- 費用比較
色々な「Auto Scaling」を理解する
AWSにおいて「Auto Scaling」というと、今のところ3つの用語があるようです。……ハッキリいって、この時点で初見ユーザーは頭痛が痛くなって離脱しちゃうね。
俺もこれ書くために調べ直して、下書き破棄しようか迷っちゃったし、
100%正しいことを書けてるか自信ありません!と先に予防しておく。
Amazon EC2 Auto Scaling は、EC2内に古くからある1機能で、
Application Auto Scaling は、RDSやDynamoDB等のその他のAuto Scaling機能を管理する仕組みで、
AWS Auto Scaling は2018年末に東京リージョン対応された、全てのまとめ+α な1サービス、
という位置づけです。いい感じの末期感!
多分、中の人もデータ構造とか命名とかでヤバいのがわかってて、
Auto Scaling に変わる名前は無いし、ここらで一発1つにまとめて食い止めよう!
って一念発起した感が伝わってきますね。知らんけど。
このまま理解しないで放置もよくないので頑張ってまとめていくわけですが、この先は頭痛必至なのでカフェイン摂りながらゆっくりしていってね!
EC2 Auto Scaling
旧来の方法だと、LaunchConfiguration + AutoScalingGroup でやっていた、主にCPU利用率を条件にインスタンス数を増減させるEC2の機能です。最近では LaunchTemplates を使って、より柔軟に多機能をこなすのが良さげな流れで、他にも数年前とは変わっているので詳しくは後述していきます。とりあえず、あぁEC2にはそんなんあったね、知ってる知ってる、で次へいきます。
Application Auto Scaling
EC2以外にも色んなサービスでAuto Scaling対応していて、それらを主にAPIで操作するための仕組みのまとまり、といった位置づけのようです。ドキュメントにありますが、管理できる他サービスは今の所こんな感じです。
今回はこれらには詳しくは触れませんが、そういうものがあるんだと知って次へいきます。
AWS Auto Scaling
こちらはAWSの数あるサービス群のうちの1つ、という立ち位置です。簡単に説明すると、各サービスに既に存在している AutoScaling な機能を、この一箇所でタグ等を使って管理しようというものです。プラス、予測スケジューリングという機能を使うことができ、それは現在は EC2 Auto Scaling にのみ対応しているという状況です。スケジューリングには「動的(Dynamic)」スケジューリングと「予測(Predictive)」スケジューリングの2種類があり、
動的スケジューリングはこれまで既に存在していた各サービスのAuto Scaling機能のことを指しています。そして、その中でも通常のEC2インスタンスのスケーリングは Amazon EC2 Auto Scaling を、それ以外はApplication Auto Scalingを活用することでAWS Auto Scalingでの集約を実現しています。
予測スケジューリングは、最低1日分、最大2週間分のデータを用いて、24時間毎に向こう48時間のトラフィックを予測し、その結果を EC2 Auto Scaling Group の Scheduled Action に1時間毎のデータとして自動入力されます。データは主に min size を変更させる内容でトラフィック変化に備えてくれます。より事前に準備させるためのバッファ時間といった調整点もあります。
どちらか片方だけでも設定できますが、2つとも用いて増減させるとすると、仮に予測外のトラフィック増が起きても、動的の方である程度はなんとかなるというヒテンミツルギスタイルです。EC2の動的スケーリングポリシーとしては、任意で Simple / Step / Target から選ぶことができますが、AWS Auto Scalingから作成すると、ターゲット追跡ポリシーがスケーリングポリシーに追加されます(※上書きも可能なようですが、Terraformなど構成管理ツールのことを考えるとそんな横暴はやらないでしょう)。最近はこのターゲットの利用が推奨されている流れのようです。
また、予測に含まない急増トラフィックを人間様が予定していた場合、既にスケジューリングされたデータを手動編集することで対応できます。なので、予測で大丈夫。動的で何かあっても大体なんとかなる。それ以上は手動で調整してあげれば問題なし!ということで、明王の安慈さんを倒せそうな雰囲気はありますね。
APIで理解する関係性
自分、LambdaでPython使うので boto3のリンクで恐縮なんスけど、Auto Scalingに関わるAPIはこれらになります。AutoScaling / ApplicationAutoScaling で各サービスのAutoScaling設定をできますが、AutoScalingPlans でもできるところが、APIとしては混乱を招くポイントかと思います。
また、各サービスのAPIを覗いてみると、
RDS Aurora の Replica Auto Scaling は対応されてないため、AutoScalingPlans で実行することになるのですが、わりと新しい Aurora Serverless の AutoScaling は存在していたり、
DynamoDB の Capacity Auto Scaling は普通に対応されていたり、
と、他のもサービスによってまちまちになっています。だからこそ、1つにまとめたのかなと思うわけですが、APIを使うにせよ、構成管理ツールを使うにせよ、事例なしには取っつきづらい環境であることは間違いないです。とっつく人はガンバ!
AWS Auto Scaling は EC2 Auto Scaling の代わりになるのか
AWS Auto Scaling の存在意義として、全Auto Scalingを一括管理するというものがあります。Cloudformation や タグを利用して、各所に散らばったAutoScalingしたいリソースを設定できるので便利ではあります。ただ通常は、AutoScaling 機能がAPI対応しているならば、Terraform 等で設定してしまっているはずなので、既に一元管理しているともいえ、いちいち移すかどうかは微妙なところです。
なので移すメリットを考えてみると、
とりあえず EC2 しか使ってないとしても、Aurora Replica や DynamoDB あたりは結構使う可能性はありますし、他にも今後新しく出たサービスでは AWS Auto Scaling でしかスケーリング設定ができないとしたら、先に統一管理しておく意味は十分にあるといえます。
予測スケジューリングも、とりあえず予測だけ設定しておいて、スケジューリングの実行は停止しておくことができるので、設定だけして実際に予測グラフを確認してから判断するならば、費用は例のごとく実質無料なので損はしません。
ではデメリットはというと・・・
とかでしょうか?あんまり思いつきません。
EC2に関して私見をのべるならば、これまで通り動的スケーリングができ、ON/OFF可能な予測スケジューリングが使え(OFFにしたら入力済みスケジュールは削除される)、自動入力されるスケジュールの手動調整もできる、という機能的なメリットがあり、その他の既存・新サービスの対応という将来のことを踏まえれば、よほどの力強い希望がない限りは移行するまではないものの、新サービスでの構成としては最初から採用していってもよいのではないか、という感触です。
という感じで、AWS Auto Scaling についての雑談はこのへんまでとしておきます:-)
EC2 Auto Scaling を構築する
構成
昔は LaunchConfiguration を使って、複数の AutoScalingGroup を作成することで、複数のインスタンスタイプを同時利用したり、時にはAZも1グループに1AZにしていましたが、今は LaunchTemplates を使うことで複数インスタンスタイプを選択したり、オンデマンドとスポットを任意の割合で起動できるようになったため、そちらを採用しています。それと、AWS Auto Scaling の 予測スケジュールを活用するのですが、まだTerraformで対応されてないので、管理画面でそこだけON/OFFして、あとで対応されたら import するなりなんなりで対応する流れ。
なので使うリソースとしては、LaunchConfiguration + AutoScalingGroup + AutoScalingPlans に、EC2の ScalingPolicy、LifecycleHook、Notifications、それから Lambda あたりまで。
Terraformで管理しているのですが、最近ガッと作り直したのでは、Workspace と local変数 を多用しています。そのまま利用できるコードではないのですが…… 役に立つ人もいるかもなので、あんまり気にしないで改変しないで貼っときます。この辺についてはいつか別に書くかもです……
LaunchTemplates
LaunchConfiguration の時と違ってインスタンスタイプは指定せずに AutoScalingGroup で指定します。それ以外は、基本的なイメージ用の設定をしているだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
resource "aws_launch_template" "default" { count = "${local.on_asg ? 1 : 0}" name_prefix = "default" image_id = "${local.ami_id}" key_name = "${data.terraform_remote_state.common.aws_key_pair_admin_key_name}" vpc_security_group_ids = ["${aws_security_group.base_ssh.id}", "${aws_security_group.web_private.id}"] iam_instance_profile { name = "${data.terraform_remote_state.common.aws_iam_instance_profile_instance_profile_id}" } monitoring { enabled = true } lifecycle { create_before_destroy = true } } |
AutoScalingGroup
インスタンスタイプについてこちらに持ってきたので、その辺が特徴です。4種のスポット利用ができるタイプを指定し、オンデマンドの割合も指定。なんで複数種類かというと、特にスポットに対するリスク分散です。AutoScalingPlans によって min_size 等が変動したり、
オンデマスポット混合機能によって何故か on_demand_base_capacity が勝手に書き換えられることがあるので、そのへんを lifecycle でシカト。
多種タイプ+多AZ が1つのグループに入ることで、何がどう挙動するかは後述します。
ちなみに、2つのグループ scaling / monitoring を作ってるのは、インスタンスの監視系でホスト数を増減させたくない仕組みのために、monitoring グループは固定台数にしたいから、とかそういう理由です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
locals { autoscaling_metrics = ["GroupMinSize", "GroupMaxSize", "GroupDesiredCapacity", "GroupInServiceInstances", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"] } resource "aws_autoscaling_group" "web" { count = "${local.on_asg ? 2 : 0}" name = "${format("%s-%s-%s-%s", local.env, local.service_name, "web", count.index == 0 ? "scaling" : "monitoring")}" target_group_arns = ["${values(local.asg_config_web["target_groups"])}"] vpc_zone_identifier = ["${slice(aws_subnet.public.*.id, 0, lookup(local.asg_config_web["az_num"], "default"))}"] health_check_grace_period = 300 health_check_type = "EC2" enabled_metrics = ["${local.autoscaling_metrics}"] termination_policies = ["OldestInstance"] max_size = 0 min_size = 0 desired_capacity = 0 mixed_instances_policy { launch_template { launch_template_specification { launch_template_id = "${aws_launch_template.default.id}" } override { instance_type = "${lookup(local.asg_config_web["instance_types"], "0")}" } override { instance_type = "${lookup(local.asg_config_web["instance_types"], "1")}" } override { instance_type = "${lookup(local.asg_config_web["instance_types"], "2")}" } override { instance_type = "${lookup(local.asg_config_web["instance_types"], "3")}" } } instances_distribution { on_demand_base_capacity = "${lookup(local.asg_config_web["on_demand_base_capacity"], count.index == 0 ? "scaling" : "monitoring")}" on_demand_percentage_above_base_capacity = "${lookup(local.asg_config_web["on_demand_percentage_above_base_capacity"], count.index == 0 ? "scaling" : "monitoring")}" spot_instance_pools = "${length(local.asg_config_web["instance_types"])}" } } lifecycle { ignore_changes = [ "max_size", "min_size", "desired_capacity", "mixed_instances_policy.0.instances_distribution.0.on_demand_base_capacity" ] } tag { key = "service" value = "${local.service_name}" propagate_at_launch = true } ~snip tags~ } |
AutoScalingPolicy
前は、CPU50%で20%増台、90%超えで80%増台、と二重条件で小細工してましたが、今ならCPU50% を保つように TargetTracking に任せるのがシンプル is 吉っぽいです。AWS Auto Scaling の方で作ると条件が、
安定:40%
バランス:50%
コスト重視:70%
となっています。カスタムも可。この数値をどう考えるかは考察点が2つほどあります。
リソース追加完了までの時間
条件に達してから、実際にリソースが分散グループに入って可動するまでの時間を考えてみます。今回はEC2で考えますが、他も考え方は一緒ということで……ここまでを経て、ようやく全体の平均CPU利用率が下がるわけです。所要時間を合計すると、最も時間変動がありそうなデプロイを加味しても、大雑把に 2分半~4分 くらいはかかるとします。……面倒くさいので、ここでは 5分 としましょうか。
次に負荷が急増する時間帯を考えてみます。メンテ明けとか、年に数度のイベント!みたいなのを除くと、最もトラフィックが急増するのは12時前後で、次点で20~22時くらいなことが多いです。まばらに休息タイムになる夜よりも、昼休みの方が一斉に休む人が集中するからでしょう。その昼休みでも、5分毎に20~30%ずつ増加すれば多い方だったりします。で、12:10過ぎにはピークアウトしていく、と。
傾向はサービスによって違うので、グラフを生成して増加率を実際に認識するのが大事です。で仮に、ここでは厳しく +20%/分 だとしましょう(わかりやすく複利じゃなくて足し算でいきます)。CPU条件はバランス50%だとします。トラフィック増が始まって1分後にはCPU70%、2分後には90%、3分後には110% とキャパオーバー = ユーザーにエラーまたは超遅延 するのに、次のインスタンスはまだ5分経過していないために準備中で解決できないことになります。
これを解決するには、CPU条件を緩和したり、インスタンス準備時間を短くする努力をしたり、サービス運営で人の集中を避ける工夫をしたり、と奔走することになります。実際は50%で大抵十分ですが、70%超 だと厳しい場合もあるかな?という感じだと思います。
なので事故りたくないならば、まずはリソース追加完了までの時間と詳細を把握することと、改善ポイントを探して改善しましょう。無駄な処理を削ったり、デプロイ・ゼロにしたり、各種時間設定を調整したり。そういうのを気にできる人が組織に1~2人でもいればだいぶ変わるはずです。
気まぐれ☆スポットインスタンス
ワタクシ、スポットインスタンスは大好きなんですけど、スポットを使う時は必ず 全★即★死 を前提に構成しなくてはいけません。昔よりも価格が10倍ドーン!とかは見た目上はなくなりましたが、高騰していないのに強制Terminateされたり、起動できなかったり、ということはよくあります。過去には無条件に全スポットインスタンスが全落ち!ということもありました。この辺、内情は見えないし公開もされないため、憶測でしかないのでこれ以上は控えますが、☆何が起きても不思議じゃないよスポットインスタンス☆ という感覚で利用するのが正しい姿勢です。
それを踏まえて、スケーリングのCPU条件について考察すると、
わかりやすくオンデマンドとスポットの割合を 50% かつ CPU条件50% とし、スポット全落ち時にどうなるかというと、残ったインスタンスの平均CPU利用率は 100% に跳ね上がりピンチが到来することになります。いや、インスタンスタイプの性能差や、所属AZによる遅延差からくるCPU利用率差 も考慮すると、平均50% の状態は 45~55% くらいの範囲で各インスタンスが稼働してると思われるため、一部のインスタンスは 110% とキャパオーバーするでしょう。
これも環境によって変わる話ですが、少なくともオンデマンド:スポットの割合を 50:50 にしたいならば、CPU条件は 40% あたりに。CPU条件を 50% にしたいならば、割合を 60 : 40 あたりに調整するとよいでしょう。
on_demand_percentage_above_base_capacity の設定で割合はいつでも変更でき、変更したらすぐに自動的にオンデマンドとスポットの割合を正すようにインスタンスの入れ替えが走るので、最初はスポットを 10~20% とか少なめに始め、一定期間運用してから調整してコストダウンするとよいです。
トラッキング追跡の具体的な増減条件
スケーリングポリシーを TargetTrackingScaling にすると、細かい調整は不要になり、指定したCPU値などに維持してくれるようになります。具体的には、TargetTrackingScaling を追加することで、自動的に CloudWatch アラームにデータが入ります。
(※そのため、CloudWatchアラームは手動編集ができないデータになっています)
例えば CPU=50% と指定してEC2側で作成すると、このような2データが入ります
ただ、CloudWatchAlarmのメトリックアクションとしては、ASGAverageCPUUtilization をターゲット値 50 に維持 としか両方共書いていないので、何台ずつ何%ずつ増減するかはブラックボックスになっています。この%の場合、Increase / Decrease の差が5%しかないので、かなり無駄が少なくなる条件になっていると思われます。
また、AWS Auto Scaling からポリシーを追加すると、バランスとされている 50% 条件にすると、Decrease が 35% になるので、ポリシー追加方法によってCloudWatchで実際に値を確認したほうがよいです。
Terraformコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
resource "aws_autoscaling_policy" "web" { count = "${local.on_asg ? 1 : 0}" autoscaling_group_name = "${aws_autoscaling_group.web.0.name}" name = "cpu-target-tracking" policy_type = "TargetTrackingScaling" estimated_instance_warmup = "${lookup(local.asg_config_web["warmup_seconds"], "default")}" target_tracking_configuration { predefined_metric_specification { predefined_metric_type = "ASGAverageCPUUtilization" } target_value = "${lookup(local.asg_config_web["target_cpu"], "default")}" } } |
LifecycleHook
Decrease する前などに、何か処理させたい時はLifecycle HookでSNSを通してLambdaを起動したりします。例えば、監視サーバーから自ホスト情報を削除したり、です。
instance-id がLambdaで取得できるので、Nameタグをホスト名として色々やったりできます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
resource "aws_autoscaling_lifecycle_hook" "web" { count = "${local.on_asg ? 2 : 0}" name = "Terminating" autoscaling_group_name = "${element(aws_autoscaling_group.web.*.name, count.index)}" role_arn = "${data.terraform_remote_state.common.aws_iam_role_lambda_arn}" notification_target_arn = "${data.terraform_remote_state.system.aws_sns_topic_lifecyclehook_arn}" default_result = "ABANDON" heartbeat_timeout = 300 lifecycle_transition = "autoscaling:EC2_INSTANCE_TERMINATING" } |
Notifications
エラー時にSNSに通知するだけ。
1 2 3 4 5 6 7 8 9 10 11 12 |
resource "aws_autoscaling_notification" "web" { count = "${local.on_asg ? 1 : 0}" group_names = ["${aws_autoscaling_group.web.*.name}"] notifications = [ "autoscaling:EC2_INSTANCE_LAUNCH_ERROR", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR", ] topic_arn = "${local.mail-arns["mail-warning"]}" } |
Plans
AWS Auto Scaling の予測スケジューリングは、まだTerraformに対応されてませんが、そう遠くないうちにされるでしょう。対応されたら、EC2のAutoScalingPolicyを削除して、Plansの方で作成するか、EC2はそのままにPlansで予測スケジューリングだけ作成したり、手動作成したのを import するか、その時になったら考えます。
スポット対策 Lambda
スポットが強制Terminateされる時、2分前にメタデータで検知することができるのですが、正常な終了と違ってロードバランサからの安全な切り離しとかは実行されないので、自前で切り離す必要があります。そのため、後述するOS用のBashデーモンで検知を行い、検知したらこのLambdaを発火させて必要な処理を実行する、という仕組みを入れています。前は、Bash内で必要な処理もやってたんですけど、イメージに埋め込むよりも外部Lambdaで管理したほうがやりやすかったので、そうしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
resource "aws_lambda_function" "spot-termination" { count = "${local.on_system ? 1 : 0}" function_name = "spot-termination" handler = "spot-termination.lambda_handler" s3_bucket = "${local.lambda_bucket}" s3_key = "${local.lambda_key}" s3_object_version = "${data.aws_s3_bucket_object.lambda.version_id}" memory_size = 128 timeout = 30 runtime = "python2.7" role = "${data.terraform_remote_state.common.aws_iam_role_lambda_arn}" environment { variables = "${local.monitoring_env}" } vpc_config { subnet_ids = ["${aws_subnet.private.*.id}"] security_group_ids = ["${aws_security_group.ping.id}"] } } resource "aws_cloudwatch_log_group" "spot-termination" { count = "${local.on_system ? 1 : 0}" name = "${format("%s%s", local.lambda_log_group_prefix, "spot-termination")}" retention_in_days = 7 } |
OSに埋め込むスポット対策
スポット強制終了を検知し、上記Lambdaを発火させるデーモンです。最近はあんまBash使わなくなったけど、こーいうところはまだまだBashですな。
デーモンスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#!/bin/bash interval=5 lambda_name="spot-termination" base_url="http://169.254.169.254/latest/meta-data/" check_url="${base_url}spot/termination-time" id_url="${base_url}instance-id" az_url="${base_url}placement/availability-zone" instance_id=$(curl -s $id_url) region=$(curl -s $az_url | sed -e 's/.$//') jq_query=".Reservations[0].Instances[0].InstanceLifecycle" lifecycle=$(aws ec2 describe-instances --instance-ids "$instance_id" --region "$region" | jq -r "$jq_query") if [ "$lifecycle" != "spot" ]; then echo "This instance is not spot." exit 0 fi test_content='{ "UpdateTime": "2000-01-23T01:23:45.000Z", "Code": "instance-terminated-by-price", "Message": "Your Spot Instance was terminated because your Spot request price was lower than required fulfillment price." }' while : do res=$(curl -s -w '\n%{http_code}' $check_url) status=$(echo "$res" | tail -1) echo "$status" | grep "200" > /dev/null if [ $? -ne 0 ]; then sleep $interval continue fi content=$(echo "$res" | head -n -1) #content="$test_content" payload=$(cat << JSON { "instance_id": "${instance_id}", "response": "${content}" } JSON ) aws lambda invoke --function-name $lambda_name \ --invocation-type Event \ --payload "$payload" \ /tmp/lambda.log \ --region $region > /dev/null break done |
起動設定
1 2 3 4 5 6 7 8 9 10 11 |
[Unit] Description = Monitoring for spot force termination After = network.target [Service] EnvironmentFile = /etc/sysconfig/spot-monitor ExecStart = /usr/local/aws/bin/spot-monitor.sh Restart = no [Install] WantedBy = multi-user.target |
からの
1 2 3 |
touch /etc/sysconfig/spot-monitor systemctl enable spot-monitor systemctl start spot-monitor |
とかなんだけど、まぁ全部、OS構成管理ツールに突っ込んでる系。
インスタンスタイプの混合
LaunchTemplatesによってインスタンスタイプを混合できるようになったので、CPUの性能や増減挙動について検証しておきました。面倒くさいので表は適当メモの画像を貼るのでクリックして見てね!CPU性能
まずはそこそこ把握しておいたほうがよい、各CPU性能についてです。何種類か採取してみて、一部アレな数値もありますが、細かいことは気にせず sysbench thread=1 の数値で c5.large 中心で性能比を算出しています。CPUは世代が変わるごとに、スレッド単体での性能が大体1~2割は上がっていくので、概ね予想通りの数値になりました。値段はあまり変わらないどころか、新しいほうが安かったりするので、可能な限り最新のほうへ寄せていきたいですね。
タイプ数変動による平均CPU利用率
ALBの分散はラウンドロビンのため均等にリクエストを振り分けるので、性能が異なるCPUが混在すると、必然的に高性能インスタンスはCPU利用率が低くなり、逆は高くなります。上記の性能比を利用して、台数が同じだったりアンバランスだった場合に、平均利用率はどうなるかをイメージしておきます。1つのスケーリンググループの中でも、最低と最大利用率の差は 10% 以上になることがある。当然ですが高性能CPUを多用した状態の方が、平均利用率も下がる。ということがわかります。
AZ格差
さらに、実際には AZ格差 による遅延が存在します。ここでいう格差とは、AZ自体の性能ではなく(んなもん無ぇ)、各インスタンスと通信する他リソースとの距離によるネットワーク・レイテンシのことです。例えば、1a, 1c があるとして、1a -> 1a のPingが 0.4ms だとすると、1a -> 1c は 2.4ms くらいかかります。これを具体的にいうと、
(WEB/AP in 1a) -> (DB in 1a)
(WEB/AP in 1a) -> (DB in 1c)
では、WEB/APのCPU利用率に違いが出るということです。
同AZ同士だとレイテンシ低で処理できるため、CPUもザクザク稼働できて利用率は高くなりますが、異AZ間だとレイテンシ高のため、CPUの待機時間が長くなり、処理したくともできず利用率は低くなります。この違いは、DBなら1回のHTTPリクエストで何回SQLを投げるかで程度は変わりますが、CPU利用率にして5%前後くらいの格差が出ると思っておくとよいです。
CPU利用率格差を認識する
ここまで出した数値はわかりやすさのために少々盛ってる部分もあるので、ちゃんと自分で確認してほしい類のものではありますが、ここでは仮数値としてそのまま考えていきます。インスタインスタイプによる格差が多くて 15%、所属AZによる格差が 5% としましょう。合計20% の格差。
最も強いCPUかつ、最も利用されるDBが異AZに存在する、インスタンスが最軽となり、
最も弱いCPUかつ、同上DBが同AZに存在する、インスタンスが最重となります。
平均CPU利用率を50% で台数が均等とすると、最軽の方の利用率は40%、最重の方が60% あたりになるでしょうか。……こう見ると、結構大きな差ですし、実際に私が経験した格差でも、せいぜい12%以下って感じだったので、派手気味に計算にしたのは否めません。なので……
大切なのは認識することですじゃ! ウシャーッ
タイプを適当に選んだり、AZ差の特徴を知らないで、大きな格差に気づかずにいると、スケーリング条件をコスト重視の平均70% にした時に、利用率が最も高くなるインスタンスで常時 85% とかになっていたら、急増トラフィックが訪れた時に、その一部インスタンスでキャパオーバーが発生するかもしれないのです。
CPU利用率を操るという事は できて当然と思う 調整力なんですぞッ!
インスタンス増減の特徴
インスタンス性能とは別に、AutoScalingGroup にタイプと費用形態とAZを混合させた時に、増減する際の挙動はどのようになるかは是非とも知っておきたいところです。よね?完全に自分用のクソメモですけど、貼っておきます。見ないでも全然いいです。
条件
こんな感じで検証しました。それぞれの意味とかは一部後述します。
特徴 – 増加
検証範囲で読み取れたものを箇条書きしていますが、見誤っているかもですし、今後変わっていくでしょうから参考程度にお願いしますだ。特徴 – 減少
まぁ、だから何?って検証かもですが、こういうところを知識というか体感しておくと、安心するんじゃポルナレフ。
AutoScalingの各種設定
いろいろありますが、実際に使用すると思われる項目について説明をまとめておきますInstance Types
オンデマンドを起動するか、スポットを起動するかで扱いが変わります。オンデマンドの場合、枯渇していない限りは1番目に指定したタイプで必ず起動されます。
スポットの場合、指定した条件にもよりますが、デフォルト推奨値の場合は、安価なものから起動され、価格差やAZ偏りを考慮して選択されます。
Spot pool
AZが 1a と 1c を扱うとして、それぞれで利用するインスタンスタイプの数 を指定します。(ドキュメントをみても非常にわかりづらいです!)例えば、2 を指定した場合、1a で起動するスポットは c5 , m5 で、1c では c4 , m5 が使われたりします。一度選択されたらそれはずっと固定です。3 の場合、1a では c5, m5, c4 を、1c では c5, r5, c4 といった具合です。この例を見た後に、もう一度最初の説明を読めば、多分理解できます。
特に変なこだわりがなければ、Instance Types と同じ数を指定することになります。この値が大きいほど、スポットリスクにおける強制Terminateなどを軽減できます。例えば4種2AZにおいて、1c の m5 だけ使えなくなっても、全体から見て 1/8 しか利用不能にならないからです。
ただ、スポットは突然の全スポットダウン が過去に何度か起きているので、その対処にはなりませんし、CPU性能差によるCPU利用率のバラツキといった一応のデメリットはあります。
Ondemand Base / Ondemand Percentage Above Base
Base は最初に必ず起動するオンデマンドの数です。Percentage Above Base は、Baseを超えて Increase する時に、その追加分の中でオンデマンドが占める割合 となります。例えば、Base = 0 の場合、Percentage Above Base のみの条件で起動されていきます。
Base >= 1 の場合、最初にその数オンデマンドが立ち上がり、その数のあとは Above Base のとおりに増えていきます。
そのため、Above Base の仕様を理解することで、想定通りの割合で起動することができます。Above Base が 0% の場合、スポットだけが起動することになります。50% の場合、最初にオンデマンドが起動し、次にスポット、次にオンデマンド、スポット となり、常にオンデマンドが50% を下回らないように増加していきます。
20% の場合、オンデマンド1台のあとにスポット4台、またオンデマンド1台スポット4台とループしていきます。
この調整によって、主にはスポット割合を増やすことで費用を抑えることができますが、スポットリスクがあるため、最初はオンデマンドを半分以上になるように扱い、徐々にスポット割合を増やす形で調整するとよいです。
Termination Policy
デフォルトだと、AZバランスや、次の課金までの時間が早いインスタンスから、とか考慮してくれますが、課金単位が1秒単位になったので、この挙動はそんなに重要じゃなくなったかと思います。なので普通に OldestInstance を選択することで、可能な限り古いインスタンスから削除したらよいと思います。これにより、意図的にインスタンスを入れ替えなくても、増減発動によって半日~1日スパンで内容を常に最新に保つことができます。
ただし、削除時にAZの偏りができる場合は、AZの偏りを解消することを優先して削除するため、インスタンスタイプが同じ場合は、最古ではなく次に古いインスタンスタイプが選択されることもあります。
費用比較
萎びた話ではありますが、費用は最も重要な要素の1つなので、AutoScaling や スポット、リザーブド を活用すると具体的にどのくらい費用が安く変化するかをおさらいしておきます。各種基本費用
この表は、4つのタイプごとに各種費用をまとめたものです。性能比としてはほぼ比例になっているので、価格そのものよりも比率で捉えるのが目的です。spot max / min は、3AZでの max / min という意味です。
オンデマンドと比較すると、スポットは 24~37% の価格、リザーブは 58~71% の価格に抑えることができます。ただし、スポットは最大価格を 自動 = オンデマンド価格 に設定すると、最悪はオンデマと同価格での利用になる、ということだけは認識しておきたいところです。
割合別の費用試算
オンデマンド・リザーブド・スポット を混合させた時に、それぞれの割合を変えると、全てがオンデマンドの時と比べてどのくらい安くなるのかを記録した雑メモになります。100台を色んな形で割り振ってみています。最も性能の良い c5.large を中心に据えていますが、最も安いために、古いタイプを中心にするよりは比率が高くなってしまいました。現実的には、リザーブ前払いなし:スポット=50:50 あたりの 52.7% から、一部オンデマだったりスポット割合減らしたりで、60~70% くらいに抑えられたら御の字って感じでしょうか。
Autoscaling有無による削減
サービスによって削減割合は変わりますが、Autoscaling増減の有無によって台数をかなり削減できます。たいていのサービスはピークタイムが12時 もしくは 20~23時頃 であり、4時頃が最小となります。それに合わせてインスタンスを増減させることで負荷対策と省エネを実現できます。負荷対策としては、必要なリソースの 2倍 = CPU利用率50%維持 を用意しておくものとすると、古来のノンスケールではピークタイムのリソースに合わせて2倍の確保をする必要がありました。常時2倍のリソースを確保すると、深夜には10~20倍ほどのリソースが確保されていることになってしまいます。
トラフィックグラフを見るとたいていのサービスは、
横 : 縦 = トラフィックグラフ24時間 : ゼロ~ピークタイムのリソース量
とした長方形のうち、空白地帯の面積 = 約50~55% であり、利用地帯の面積 = 45~50% になっています。
つまり、ピークタイムに合わせた時の確保リソースに比べて、AutoScaling時に実際に必要なリソースは50%程度になるということです。それすなわち、増減を自動化することで費用も50%に減らすことができる、ということです。
削減効果
例えば、旧式をAll Reserved 前払いなし・Autoscalingなし の場合は上記表の通り 72.0%
とすると、そこそこ現実的な条件であるスポット&スケーリングでは
Reserved前払いなし : スポット = 50 : 50
Autoscalingあり = 52.7% * 50% = 26.4%
となり、旧式と比べると 36.6% の費用となります。元を何として比較するかで、いくらでも見栄えが変わるものなので、あまり好きなヤツじゃないですし……サービスの性質や組織事情でいくらでも変質するかもですが、
サービスに必要な安定度、スポットの状況、組織の懐事情、など色んな要素を踏まえて、現環境にとって可能な限りベストに近い選択ができるとよいですね。AWSはジャンジャンバリバリ儲かってるし、削れるところはガンガン削っていきましょう!
いかがでしたでしょうか(疲)
……長すぎていかがもクソもねぇな、なんだコレ!
この内容が果たして何年くらい保つのか、わからんけど、
もはや俺らインフラエンジニアは、クラウド企業の犬、
そうクラウドッグなのさ~ ルルル~♪(壊)