Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

アカツキでAWS ECS Capacity Providerと格闘して

こんにちは!

この度サーバサイドエンジニアとしてインターンシップで勤務させていただきました、髙津と申します。

本記事では、インターンシップ期間 (2020年7月6日〜7月31日) 中に取り組んだこと、感じたことなどを書いていきます。似たような技術課題に直面している方々、これからインターンシップを受けようと考えている方などの手助けになれば幸いです。

具体的には

の構成でお伝えしていきます。

インターンシップで取り組んだこと

ずばり「AWS ECS Capacity ProviderによるService Auto Scalingの検証」なる課題に取り組み、以下の4点の成果を上げました。

  • 技術に対する知見
  • 基礎的な検証
  • ドキュメント作成
  • 設定の一部自動化

成果報告会時のスライドを一部改変したものを以下に掲載します。

ここでは、2019年12月に登場したばかりの新技術、Capacity Providerの知見と検証結果について簡単にまとめておきます。なお、詳しい内容 (背景や詳しい動作の仕組みなど) はスライドを参照してください (各種用語や略語定義もスライドに準拠とします) 。

知見

Capacity Providerの具体的な設定項目

f:id:stakatsu-aktsk:20200802153712p:plain

Capacity Providerとは上図のように、Auto Scaling Group (ASG) をラップし、ECS上のScalingを便利にするものです。

以下の項目を作成時に指定します。

  • Auto Scaling Group
  • マネージドスケーリング: Yes or No
  • ターゲットキャパシティー%: 1-100の整数値
  • マネージドターミネーション保護: Yes or No

マネージドスケーリングを有効化することで、タスクとインスタンスのScalingが紐付けられ便利になります。ターゲットキャパシティー%は追跡目標となるCapacity Provider Resercation (CPR) 値です。マネージドターミネーション保護は、タスク実行中のインスタンスを終了させないようにする機能です。ラップするASGのインスタンス終了保護をYesにしないと、こちらもYesにできません。

したがって、以下の検証では

  • マネージドスケーリング: Yes
  • ターゲットキャパシティー%: 100
  • マネージドターミネーション保護: Yes

として進めました。

Auto Scalingの仕組みのおさらいと設定項目一覧

基本的にタスク、インスタンスの種類を問わず、Auto Scalingは以下のフローで動作します。

  1. CloudWatchメトリクスによる監視
  2. CloudWatch Alarm (CWA) の発火
  3. Scaling Policy (SP) によるScalingの実行

ここで、設定すべき項目のうち、主要なもの列挙すると以下のようになります (但し、条件によっては評価クールタイムが指定できないものもあります) 。

  • CWA:
    • Scale Out条件: m分間(メトリクスの条件)を満たす
    • Scale In条件: n分間(メトリクスの条件)を満たす
  • SP評価クールタイム:
    • Scale Out: k秒
    • Scale In: l秒

まず、インスタンスScalingの場合には、以下の図のようなフローと設定になります。

f:id:stakatsu-aktsk:20200802155054p:plain

主要な設定項目は以下の通りです。

  • CWA:
    • Scale Out条件: 1分間 Capacity Provider Reservation (CPR) > 100
    • Scale In条件: 15分間 CPR < 100
  • ASP評価クールタイム:
    • Scale Out: 300秒
    • Scale In: 300秒

上記に示した設定は、マネージドスケーリングを有効化した状態でCPを作成した場合に、ASGに対して自動で作成されるデフォルトのCWA・ASPの設定値です。余談ですが、マネージドスケーリングをNoにしてCPを作成した場合、指定されたASGに対するCPRしか作成されず、タスク数の変化に応じて自動でインスタンスScalingを行ってくれません。

次に、タスクScalingの場合には、以下の図のようなフローと設定となります。

f:id:stakatsu-aktsk:20200802155054p:plain

  • CWA:
    • Scale Out条件: 1分間 CPUUtilization > 45
    • Scale In条件: 3分間 CPUUtilization < 40.5
  • SSP評価クールタイム:
    • Scale Out: 300秒
    • Scale In: 300秒

また、Service Scaling Policy (SSP) には「ターゲット追跡」と「ステップスケーリング」の2種類のタイプが存在します。

ターゲット追跡の場合には、ServiceのCPUUtilizationやTargetResponseTimeなどのメトリクスとターゲット値を指定するだけで、自動でCWAやSSPが作成されます。実際に、上記設定はメトリクスをServiceのCPUUtilization、ターゲット値を45とした時に作成される設定項目です。

ステップスケーリングの場合には、上記設定を自分で全て設定可能ですが、CWAをSSP用に新たに作成する手間が掛かってしまいます。

検証

懸念事項

運用上気になる点は、大きく以下の3つが考えられます。

  1. 動作の上で最適な設定は何か?
  2. Scalingは十分に速いか?
  3. 安全にScale Inできるか?

上記3つについて、Locustによって負荷を掛けながらScalingの様子を検証しました。

1. 動作の上で最適な設定は何か?

1.1. SSPタイプはどちらが良いのか?

以下の条件の下で実験しました。

  • SSPタイプ: ターゲット追跡
  • インスタンスScaling設定
    • CWA:
      • Scale Out条件: 1分間 CPR > 100
      • Scale In条件: 15分間 CPR < 100
    • ASP評価クールタイム:
      • Scale Out: 300秒
      • Scale In: 300秒
  • タスクScaling設定
    • CWA:
      • Scale Out条件: 1分間 CPUUtilization > 45
      • Scale In条件: 3分間 CPUUtilization < 40.5
    • SSP評価クールタイム:
      • Scale Out: 300秒
      • Scale In: 300秒
  • Locust: 負荷一定

その結果、以下の結果が得られました。

f:id:stakatsu-aktsk:20200802185148p:plain

図中の赤い点線よりも上部であればScale Outし、下部であればScale Inすることが期待されます。また、緑色の折れ線は起動中のタスクの数を表しています。

この結果から、以下のことが分かりました。

  • Scalingしない範囲が狭すぎて安定しない
  • 最初のScale Outに10分近く掛かり遅い

一つ目は、SSPタイプをステップスケーリングとすることで解決できそうです。というのも、ターゲット追跡ポリシーでは、安定とする範囲がターゲット値の90~100%に固定されてしまい、もっと広く安定範囲を広げることが難しいためです。

二つ目については色々と原因が考えられるため、現時点では一旦保留として、他の検証と合わせて再度考えることとします。

1.2. Scalingトリガーとして適切なメトリクスは何か?

1.1の結果を受けて、ステップスケーリングタイプで設定すべきだと分かったため、以降ではこの設定とします。

適切なメトリクスがどれかを判定するため、今度は以下の条件で実験を行いました。

  • SSPタイプ: ステップスケーリング
  • インスタンスScaling設定
    • CWA:
      • Scale Out条件: 1分間 CPR > 100
      • Scale Out条件: 15分間 CPR < 100
    • ASP評価クールタイム:
      • Scale Out: 300秒
      • Scale In: 300秒
  • タスクScaling設定
    • CWA:
      • Scale Out条件: 1分間 TargetResponseTime > 4
      • Scale In条件: 2分間 CPUUtilization < 80
    • SSP評価クールタイム:
      • Scale Out: 300秒
      • Scale In: 300秒
  • Locust: 負荷一定

この条件下で以下の結果を得ました。

f:id:stakatsu-aktsk:20200802190821p:plain

この結果から、

  • Scale Out条件のTargetResponseTimeはScaling条件としては不安定 (リソースの数ではなく、API毎に変わるため)
  • Scale In条件としてCPUUtilizationを用いるのは問題なさそう

だということが分かりました。したがって、これ以降はServiceのCPUUtilizationをScalingトリガーメトリクスとして扱った方が良さそうなことが分かりました。

2. Scalingは十分に速いか?

1.1.で話題になったScale Outの遅さについてですが、1.2.では6分ほどに留まっており、そこまで遅くはないことが分かりました。また、その後の検証でScale Inに関しては15分も掛かってしまうという問題がありました。

Scalingの遅さの原因については「各種CWAやScaling Policyの評価時間・クールタイムが長すぎる」ことが考えられます。そこで、デフォルトで作成されるCWAやSPに頼らず、出来る限り短い評価時間・クールタイムに設定したCWAやCPを自作しました。

しかし、今度は過剰にScalingしてしまう問題や、デフォルトのSPは仕様上無効化や改変が不可能なこともあって競合が発生してしまいました。短すぎても上手くいきませんでした。

そこで、Scalingの各フェーズで掛かる時間をここまでの検証で得られたログから見積もり、出来る限りデフォルトの設定も活かしつつ、適切なクールタイムを設定しました。その結果が以下の条件で検証を行った結果、上手くいきました。

  • SSPタイプ: ステップスケーリング
  • インスタンスScaling設定
    • CWA:
      • Scale Out条件 (デフォルト) : 1分間 CPR > 100
      • Scale In条件 (デフォルト) : 15分間 CPR < 100
      • Scale In条件 (デフォルト) : 3分間 CPR < 100
    • ASP評価クールタイム:
      • Scale Out (デフォルト) : 300秒
      • Scale In (デフォルト) : 300秒
      • Scale In: -
  • タスクScaling設定
    • CWA:
      • Scale Out条件: 1分間 CPUUtilization > 100
      • Scale In条件: 3分間 CPUUtilization < 35
    • SSP評価クールタイム:
      • Scale Out: 180秒
      • Scale In: 180秒
  • Locust: Scalingの様子を見て、手動で段階的に上昇

インスタンスScale In設定部分のみ新たに自分で追加し、15分ではなく3分でScale Inするように設定しました。また、CloudWatchメトリクスが条件を満たしてからCWAが実際に発火するまでに、常に2分程度のラグがあり、全体としてScalingにおおよそ5分程度掛かっていたことが多かったため、逆算してSSP評価クールタイムを180秒と設定しました。

この条件での検証結果のグラフが以下の通りです。

f:id:stakatsu-aktsk:20200803084036p:plain f:id:stakatsu-aktsk:20200803084244p:plain

下側の図はLocustの同時接続ユーザー数の推移を表しております。接続ユーザー数が多いほど負荷が上昇します。

やや見えづらいですが、上側の図の赤い点線を超えてから5分程度でScale Outしていることが分かります。これは現状の本番環境と同等のスピードで、十分だと言えます。

3. 安全にScale Inできるか?

安全なScale Inに求められる課題としては、以下2点が考えられます。

  1. Scale In時のタスクの急な終了を避けたい (ダウンタイム回避)
  2. タスク実行中インスタンスを誤って終了させたくない

1.に関しては、ALBのhealth checkのログにstatus code:5XXが出てこないことで確認できます。2.に関しては、マネージドターミネーション保護機能を有効化したため、既に上手く動作してくれることが期待されます。

Locustの設定を「負荷一定下から、Serviceの最低インスタンス数を10から1へ変更する」として、Scale In時の挙動を見てみました。なお、Locust以外は全て2.と同様の設定となっています。

f:id:stakatsu-aktsk:20200803084922p:plain

上の図はCPUUtilizationの推移を、下の図はRequestCountとstatus codeの数の推移を表しております。

下の図に出ている4XXのエラーは、アプリケーション側のデータ不整合に起因するエラーで、health checkのエラーとは関係なく無視しても問題ありません。また、5XXエラーについては、グラフだけでなく、実際にALBのログファイルの中身を確認し一つも5XXエラーが出ていないことも確認しました。

以上の結果から、CPU負荷に対して正常にScale Inしていること、Scale In時にエラーを出さずに正常に終了できていることが分かりました。

まとめ

以上の検証から、Capacity ProviderによるService Scalingは十分導入可能なレベルであることが分かりました。

インターンシップを通じて感じたこと

取り組んだ課題に関連して感じたこと

Capacity Providerの概念の理解には苦戦しました。複数のAWSサービスが複雑に絡み、設定項目が多いこと、新技術ということもあってドキュメントも多くはありませんでした。中には、Web上のコンソールからは設定できるものの、CLI上からは未対応の部分があったり、逆もまた然り...といった事態に遭遇することもあって、本当に最先端の技術を触っているのだと実感しました。しかし、インターン生にもかかわらず、こんなにchallengingな話題に取り組める機会はそうそうないもので、どこかで楽しんでいました。そして、そういった業務でもきちんと信頼して振っていただける文化に感心しました

(インターンシップの内容としては珍しく?)コードはあまり書かないタイプの業務だったため、意識的に分報チャンネルやドキュメントを残し、後から確実に自分の足跡を辿れるように心掛け、結果的にこうしてまとめる時などに大いに役立ちました。

また、こうして大規模なインフラ環境に触れることは学生の立場からすればなかなかないもので、インフラ構成やdeploy周りの処理の流れなど、サーバサイドの仕組みを自分の目で見ることが出来た点は大きな収穫でした。メンターさんからいただいた、設計や実装面での工夫の「雑談」も大変面白く、勉強になりました。

課題以外の面で感じたこと

今回はフルリモートインターンシップということもあって、一度も出社せずに出勤をしました。現場の雰囲気を十分に知ることが出来なかった点は残念でしたが、その一部は感じ取ったつもりです。例えば、実装で詰まった所があった場合には、気軽にモブプロをお願いしたり、プロジェクトを横断したエンジニアのミーティングや、エンジニアマネジメントのLT会が活発に行われていました。

ただ単にコードだけ書いていれば良いという訳ではなく、お互いが自発的に発言して働ける環境があるからこそ、(たとえリモートの環境下であっても) ちゃんとプロジェクトが回っているのだと気づきました。

インターン同期の方やチームの方とのリモートランチも頻繁に開かれ、コンタクトの機会が多く用意されていて、馴染めるかやや不安だった身としては大いに助かりました。

最後に

アカツキではインターン生の希望が実現できる十分な環境が揃っています。幅広く、主体的に強くなりたいエンジニア志望の学生には強くおすすめできます。実際に、インフラ方面の業務希望でサーバサイドで合格をいただきましたが、ブログを探してもなかなかヒットせず、参加前は本当にインターン生でもインフラの業務ができるのか不安な所がありました。しかし、実際に終えてみたらこの通りです。インフラ志望の方もこぞって応募してみてください。

最後になりましたが、この大変な情勢の中、貴重な機会をいただき誠にありがとうございました。

参考資料

公式のreferenceです。

docs.aws.amazon.com

Capacity ProviderによるAuto Scaling周りの話について詳しく説明している公式記事です。

aws.amazon.com

Capacity Providerの基本的な部分についてシンプルにまとめてある記事です。

qiita.com

Capacity Providerの概要、導入シナリオと検証がまとまった2つの記事です。

dev.classmethod.jp

dev.classmethod.jp