クラウド事業部の原口です。

AWSのEFSが発表されてから「これを待っていた!!」というエンジニアの方も多かったと思いますが、割と落とし穴的な制限が多く、
その中の一つとしてEFSの「バースト」と「プロビジョニング」の2つのスループットモードがあります。

バーストスループットモードとは何か。

誤解を恐れず簡単に説明しますと、スマフォの帯域制限みたいな機能となります。
EFSではバーストクレジットと呼ばれる値があり、このクレジットがある間は非常に高速なスループットでデータのやり取りが可能なのですが
クレジットがなくなると制限がかかりめちゃくちゃ低速なファイルストレージになってしまいます。
これはプロダクション環境ではサーバ障害とも言えるレベルでの低速さになります。

バーストクレジットを回復するには?

基本的にファイルストレージを使っていなければ回復していきます。
そのため、EFSの利用頻度がクレジットの回復力を下回る場合はクレジットは減る事がありません。

また、使っているEFS内のサイズによってもクレジットのそもそもの量が異なります。
大きいファイルが多数あるなど、EFS内のデータ量が多ければクレジットのそのものの絶対量が多く付与されます。

これを理解してない場合に起こる問題

基本的には運用時、EFSはバーストスループットモードとして動作する事がほとんどですが、
よくある事例としては「テスト段階では高速で快適に動いていたのに、本番運用していたら急激に低速になり障害があった」という事例です。

CloudWatchのメトリクスに「BurstCreditBalance」というメトリクスがありますが、こちらがバーストクレジットで、
ゼロになると低速なストレージになってしまいます。

f:id:cs_sonar:20190826124939p:plain

上記のようなグラフの場合は運用で徐々にクレジットが減っていってるのでずっと運用しているといつかは障害となりそうです。

プロビジョニングスループット

さて、バーストクレジットを使い果たして低速となってしまった場合はどうすればいいのでしょうか?

もちろん「大きなファイルを作ってバーストクレジットを増やす」というのも一つの解決策ですが、
一般的にはバーストモードからプロビジョニングスループットというモードへ変更する事で対策できます。

プロビジョニングスループットは帯域保証のような機能で50(MB/秒)といった値を設定する形でスループット値を保証させるモードです。
このモードではクレジットなどに関係なく設定した速度は必ず保証されます。

しかし・・・このプロビジョニングスループットは非常に高額です。
1MB/秒 の保証にあたり月額7.2USDもかかります。
件のように50MB/秒を保証させると月額360USD ≒ 38,000円。
ただでさえEFSは高額なのにこの価格は無視できる額ではありませんよね。

プロビジョニングとバーストクレジットを交互に設定をする事でコスト削減を図る

実はバーストクレジットからプロビジョニングスループットに変更すると
プロビジョニングスループット中はバーストクレジットが回復していきます。

f:id:cs_sonar:20190826125825p:plain

8/23の途中からプロビジョニングスループットとしていますが、3日ほどでクレジット最大値まで回復していますね。

つまり、最もコスト効率がいいのは

クレジットを限界まで使い直前でプロビジョニングスループットに変更 。
クレジットが回復したらバーストスループットに変更。

という形となります。

CloudWatch + SNS + lambda を使ったバーストとプロビジョニングの変更を自動化

ここからが本題。
上記の形がコスト効率が最もよいのですがこんなの人の手でぽちぽちやってれないので自動化します。

まずは、バーストとプロビジョニングの変更を行うlambda関数を作成します
バーストモードにするものとプロビジョニングにするものの2つ

EFS-to-bursting

console.log('Loading function');
var https = require('https');
var http = require('http');
const aws = require('aws-sdk');
var efs = new aws.EFS();
exports.handler = (event, context) => {
(event.Records || []).forEach(function (rec) {
if (rec.Sns) {
var message = JSON.parse(rec.Sns.Message);
var params = {
FileSystemId: message.AlarmDescription, /* required */
ThroughputMode: 'bursting'
};
efs.updateFileSystem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else     console.log(data);           // successful response
});
// ここでslack等に通知するといいと思う
}
});
};

EFS-to-provisioned-iops

console.log('Loading function');
var https = require('https');
var http = require('http');
const aws = require('aws-sdk');
var efs = new aws.EFS();
exports.handler = (event, context) => {
(event.Records || []).forEach(function (rec) {
if (rec.Sns) {
var message = JSON.parse(rec.Sns.Message);
var params = {
FileSystemId: message.AlarmDescription, /* required */
ProvisionedThroughputInMibps: '10', // プロビジョニング10MB
ThroughputMode: 'provisioned'
};
efs.updateFileSystem(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else     console.log(data);           // successful response
});
// ここでslack等に通知するといいと思う
}
});
};

FileSystemId: message.AlarmDescriptionの部分ですが
受け取ったCloudWatchアラームにはどのEFSがアラームを出したか、というものが取得しずらかったので
CloudWatchアラームのDescriptionにEFSのIDを載せる形にしました

SNS(Simple Notification Service)のトピックを作成してlambdaに連携します

SNSは通知先にメールやhttpなどだけでなくlambdaに通知できます。
ですので、バーストモードにするlambdaに連携するトピックと、
プロビジョンドにするlambdaに連携するトピックの2つを作成します。

f:id:cs_sonar:20190826131112p:plain

cloudwatchにてアラームを作成

BurstCreditBalanceを監視し、上記作成のSNSに通知するアラームを作成します。
こちらも、
・バーストクレジットが一定数以上の時 -> プロビジョンモードにするSNSに発火
・バーストクレジットが一定数以下の時 -> バーストモードにするSNSに発火
という形で2つ作成します。

f:id:cs_sonar:20190826131630p:plain

lambdaの所でも書いた通り、DescriptionにはEFSのIDを入力します

f:id:cs_sonar:20190826131750p:plain

バーストとプロビジョニングの変更を自動化できました

このように複数EFSあると便利さがわかりますね!

f:id:cs_sonar:20190826132003p:plain

クレジットが減るとプロビジョニングになり、クレジットが充足するとバーストモードになる形です

注意点

EFSの制限で、スループットモードの変更は1日に1回までしかできません!
そのため、1日でバーストクレジットを使い果たしてしまうような環境ではずっとプロビジョニングモードで動かす必要があります。

本日は以上で!