エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Chaos-Monkey ならぬ Cosmos-Monkey で AWS 費用に秩序をもたらした話

こんにちは、エムスリー ソフトウェアエンジニア 兼 QLife チーフアーキテクトの園田 (@ryoryoryohei) です。

この記事はエムスリー SRE がお届けするブログリレーの7日目です。

Cosmos-Monkey とは

エムスリーグループの QLife では toC のサービスを AWS で運用しています。それらは (当然ですが) 本番環境とは別に開発環境や QA 環境を持っています。

QLife に限らずほとんどの組織で同様だと思いますが、開発環境や QA 環境においては必ずしもサービスが 24 時間稼働である必要はないと思います。

そのため、QLife では 2019 年*1から稼働時間外の EC2 や RDS を停止する仕組みを Lambda 関数で実装し、導入していました。

今回はその仕組みを Golang から TypeScript に移植*2して、さらに Public に公開したのでその紹介をしたいと思います。

github.com

ググれば似たような事例は鬼のように出てくるんですけど、タイムゾーンが考慮されていなかったり、EC2 にしか対応していなかったりと、痒いところに手が届かない例が多かったため、自前で開発しました。

また、Cosmos-Monkey では最重要コンセプトとして「対応リソースを追加しやすく」ということを掲げていて*3、オブジェクト指向のインタフェースや値オブジェクト、ファーストクラスコレクションなど、DDD のエッセンスを (Go で実装していた初期から) 多用しています。

例えば、リソースごとに ResourceManager インタフェースを実装したクラスを追加するのですが、そのインタフェースは以下のように非常にシンプル*4です。このインタフェースを実装したクラスを追加すれば対応リソースが増える仕組みです。

interface ResourceManager<T extends Resource> {
  eachResources(handler: (resource: T) => Promise<void>): Promise<void>
  start(resource: T): Promise<void>
  stop(resource: T): Promise<void>
}

自前実装の背景にはそういった拡張性を担保するためというのもあります。

使い方

CosmosMonkey自体は SAM 用に実装されているため、デプロイは以下のようにするだけです。

yarn build
cp samconfig-example.toml samconfig.toml
$EDITOR samconfig.toml
export AWS_ACCESS_KEY_ID=xxxxxx
export AWS_SECRET_ACCESS_KEY=xxxxxx
sam deploy

CosmosMonkey をデプロイした AWS アカウントにおいて EC2 や RDS のタグに、キーをAutoStartStop、値を +0900 09:00-18:00 と指定すると、日本時間の 9:00 に起動し、18:00 に停止するようになります。

以上、これだけです。

AutoScalingGroup にも対応していて、その場合は AutoScalingGroup 自体に以下のような AutoStartStop タグを設定します。

+0900 09:00-18:00 min=1 max=2 capacity=2

上の例だと、日本時間 9:00 に当該 AutoScalingGroup が minSize=1, maxSize=2, desiredCapacity=2 に更新されます。18:00 になると、minSize=0, maxSize=0, desiredCapacity=0 に更新されます。

さいごに

QLife では Cosmos-Monkey のおかげで Reserved Instance を利用しなくても開発環境の利用料金が 1/3 以下になりました。

今後の拡張としてぱっと思いつくのは Fargate といった ECS タスクのサポートを追加することなんですが、それよりも何よりも、CloudWatch Alarm に対応させたいです。
この仕組でリソースが停止されたことによってアラームが発砲されるのは困るので、アラームを無効化した上でリソース停止を行いたいのです。
ところが、CloudWatch Alarm は Resource Manager を利用しないとタグを付与できないので、今のソースコードのままだと若干面倒です。また、アラーム無効化を先頭に行うなど、依存関係の処理が必要になってきます。

とりあえず、公開時点ですでにコードがスパゲティ化しつつあるので、リファクタリングしながら改善していくつもりです。

We're hiring

エムスリーでは SRE および チーム内 SRE を随時募集しています!

open.talentio.com

open.talentio.com

jobs.m3.com

QLife でも SRE 募集中です。ソフトウェアエンジニアからのロールチェンジも可能です!どしどしご応募ください!

www.qlife.co.jp

www.qlife.co.jp

*1:QLife で AWS を利用しはじめたのは 2018 年末

*2:Node + TypeScript の並行処理の書きやすさから再実装しました

*3:書いてません、心の中で掲げています

*4:eachResources と start, stop が同じインタフェースにいるのは気持ち悪いので Repository と Executor に分割したい。Golang から移植したため eachResources が callback を受け取る方式になっているので AsyncGenerator 使うようにしたい。