Elasticache比較

Elasticacheとは

インメモリデータベースのサービス。
インメモリデータベースはデータをメモリ上で持ち、ディスクにアクセスする必要性を除くことによって、最小限の応答時間を達成するように設計されている。
すべてのデータはメインメモリにのみ保存および管理されているので、処理やサーバー障害によって失われてしまうリスクがあるが、すべてのオペレーションをログに保存したりスナップショットを取得したりすることで、データを存続することができる。

ElasticacheではKey-Value型のオープンソースのNoSQLデータベースであるRedisと、Key-Value型のオープンソースのキャッシュシステムであるmemcachedを利用出来る。

Redisの特徴

  • リモートディクショナリサーバーの略
  • 高速に値をRead/WriteできるNoSQL
  • データベース、キャッシュ、メッセージブローカー、およびキューとして使用
  • 全てのデータ操作は排他的
  • メモリ内Key-Valueデータストア (データアクセスが高速)
  • メモリを使い果たしたら特定のルールに従って削除する
  • それでもメモリを確保できないときは全ての書込みをエラーにする
  • MASTER-SLAVEのレプリケーション構築が可能 (SLAVEは複数設定可能)
  • レプリケーションは非同期
  • 複数の操作を1回で実行できる
  • MULTIでトランザクション開始
  • 全て実行(EXEC)or全て未実行(DISCARD)
  • 幅広いユースケースに対して効果的
  • keyに対して有効期限を設定できる(SessionID, OneTimeToken)
  • ディスクI/Oを無効化できる
  • イベント駆動アーキテクチャ
  • シングルスレッドで動作 (複数の処理を並列で行えない)
  • CPUの1コアのみを使用 (複数コアを利用する場合は複数台のRedisを立ち上げ推奨)
  • 5つのデータ型が使用可能
    • 文字列型
    • リスト型
    • セット型
    • ソート済みセット型
    • ハッシュ型
  • プライマリ/レプリカアーキテクチャ

ユースケース

  • キャッシュ
  • チャット、メッセージング、キュー
  • ゲームのリーダーボード
  • セッションストア
  • リッチメディアストリーミング
  • 地理空間
  • Machine Learning
  • リアルタイム分析

Memcachedの特徴

  • シンプル
  • キャッシュやセッションストアとして役立ちます
  • 分散型
  • 新しいノードを追加することにより簡単にスケールアウトできます
  • マルチスレッド
  • 特定のノードにおいて複数のコアを利用可能
  • ディスクI/Oが発生しない

ユースケース

  • キャッシュ
  • 永続性が重要ではないセッションストア
  • ウェブ
  • モバイルアプリケーション
  • ゲーム
  • アドテック
  • eコマース

Redis 対 Memcached

どちらも持つ特徴

Redisのみ

  • データ構造のサポート
    • String に加えて、List、Set、Sorted Set、Hash、Bit Array、HyperLogLog をサポートしています。アプリケーションは、これらの柔軟なデータ構造を使用して、さまざまなユースケースをサポートできます。たとえば、Redis の Sorted Set を使用すると、ランク別にソートされたプレイヤーのリストを維持したゲームの順位表を簡単に実装できます。
  • スナップショット
    • アーカイブまたはリカバリーに使用できる特定の時点のスナップショットを使用してデータをディスクに保存できます。
  • レプリケーション
    • Redis プライマリの複数のレプリカを作成できます。これにより、データベースの読み取りを拡張し、可用性の高いクラスターを設定できます。
  • トランザクション
    • 独立したアトミック操作として一連のコマンドを実行できるトランザクションをサポートしています。
  • Pub/Sub
    • 高性能なチャットルーム、リアルタイムのコメントストリーム、ソーシャルメディアフィード、サーバー間通信に使用できるパターンマッチングを備えた Pub/Sub メッセージングをサポートしています。
  • Lua スクリプト
  • 地理空間のサポート
    • 大規模なリアルタイムの地理空間データを処理するための専用コマンドがあります。2 つの要素 (人や場所など) 間の距離を見つけたり、ある点から所定の距離内にあるすべての要素を見つけるなどの操作を実行できます。

Memcachedのみ

  • マルチスレッドであるため複数の処理コアを使用できる

ask-sdk V2 for node.js 開発メモ

ask-sdk も node.js も分からない状態からスキル開発を行いました。その時のメモです。
誰かの役に立つかもしれないのでまとめ方雑ですが載せます。

node.js

strict mode

コードの先頭に"use strict";と記述があるかもしれません。 これは通常よりも厳しくコードのチェックを行う宣言で、エラーではないけど落とし穴になりそうな内容をエラーとして扱います。

具体的には以下のように扱いが変わります。

  • 従来は受け入れていた一部のミスをエラーへ変換
    • 偶発的にグローバル変数を作成できないようにします
    • 代入文で暗黙的に失敗せずに例外が発生するようにします
    • 削除できないプロパティを削除しようとするとエラーが発生します
  • 変数の使用の単純化
    • with を禁止します
    • eval は新しい変数を周囲のスコープに広めません
    • 単純名の削除を禁止します
  • eval および arguments の単純化
    • eval および arguments という名前に対して言語構文でのバインドや代入を不可にします
    • 内部で作成された arguments オブジェクトのプロパティがエイリアスになりません
    • rguments.callee をサポートしません
  • JavaScript の "セキュア化"
    • this として関数に渡された値をオブジェクトへボクシングしません
    • ECMAScript の一般的な実装である拡張を通して JavaScript のスタックを "渡り歩く" ことができません
    • arguments は対応する関数の呼び出し時の変数にアクセスできません
  • 将来の ECMAScript への準備
    • 識別子 implements、interface、let、package、private、protected、public、static、yield を予約語にします
    • スクリプトのトップレベルまたは関数内にない function 文を禁止します

スキルに対してもこの機能を有効にするかどうかの判断が必要になります。

参考 - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Strict_mode - https://www.sejuku.net/blog/58342

var

変数の宣言。関数のスコープで定義する。 最近(ES6)では殆ど使ってないっぽい。 基本は後述するletconstを使う。

let

変数の宣言。ブロックのスコープで定義する。 代入のし直しが可能。 再宣言ができない。エラーとなる。

const

変数の宣言。ブロックのスコープで定義する。 再代入による変更はできず、再宣言もできない。

startsWith

文字列が特定の文字列で始まるかどうかを判断する。 (英文字の)大文字・小文字を区別する。

parseInt

文字列を整数に変換する。

parseInt()関数は第1引数を文字列に変換し、解析したうえで、整数またはNaNを返します。戻り値は、NaNでなければ、第1引数のstringを第2引数radixの基数によって示す10進数の整数です。たとえば、radixが10なら10進数、8は8進数、16であれば16進数による変換を意味します。10以上の基数については、9より大きい数字はアルファベットで示されます。たとえば、16進数(基数16)ではAからFが用いられます。

indexOf

indexOf() メソッドは、呼び出す String オブジェクト中で、 fromIndex から検索を始め、指定された値が最初に現れたインデックスを返します。値が見つからない場合は -1 を返します。

例)

var paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

var searchTerm = 'dog';
var indexOfFirst = paragraph.indexOf(searchTerm);

console.log('The index of the first "' + searchTerm + '" from the beginning is ' + indexOfFirst);
// expected output: "The index of the first "dog" from the beginning is 40"

console.log('The index of the 2nd "' + searchTerm + '" is ' + paragraph.indexOf(searchTerm, (indexOfFirst + 1)));
// expected output: "The index of the 2nd "dog" is 52"

IsMatch

値がMapオブジェクトとして分類されているかどうかを確認します。 https://www.npmjs.com/package/lodash.ismatch

Array.prototype.join()

join() メソッドは、配列 (または配列風オブジェクト) の全要素を順に連結した文字列を新たに作成して返します。区切り文字はカンマ、または指定された文字列です。

var elements = ['Fire', 'Wind', 'Rain'];

console.log(elements.join());
// expected output: "Fire,Wind,Rain"

console.log(elements.join(''));
// expected output: "FireWindRain"

console.log(elements.join('-'));
// expected output: "Fire-Wind-Rain"

... (ドット3つ)

ドットが3つ連続しているものをスプレッド構文と呼ぶ。 順番に値が取り出されて、個数分の要素が該当部分に入るような配列を作成できる。

var ary = [0, "A", false];
var str = "あいう";
var connectedAry = [...ary, ...str];
console.log(connectedAry);
/*
  [0, "A", false, "あ", "い", "う"]
*/

サンプル

audioData = [
  {
    title: 'Episode 139',
    url: 'test1.url',
  },
  {
    title: 'Episode 140',
    url: 'test2.url',
  },
];

const ary1 = audioData;
console.log(ary1);
// output
// [ { title: 'Episode 139', url: 'test1.url' },
//   { title: 'Episode 140', url: 'test2.url' } ]

const ary2 = [...audioData];
console.log(ary2);
// output
// [ { title: 'Episode 139', url: 'test1.url' },
//   { title: 'Episode 140', url: 'test2.url' } ]

console.log(audioData.keys());
// output
// Object [Array Iterator] {}

const ary3 = [...audioData.keys()];
console.log(ary3);
// output
// [ 0, 1 ]

Array.from()

配列型 (array-like) や反復型 (iterable) オブジェクトから、新しい Array インスタンスを生成します。

const str = "あいう";
const ary = Array.from(str);
console.log(ary);
// output
// [ 'あ', 'い', 'う' ]

サンプル

const aryfrom1 = [...Array("A","B","C")];
console.log(aryfrom1);
// output
// [ 'A', 'B', 'C' ]

const aryfrom2 = [...Array("A","B","C")].length;
console.log(aryfrom2);
// output
// 3

const aryfrom3 = [...Array(3)]
console.log(aryfrom3);
// output
// [ null, null, null ]

const aryfrom4 = [...Array("A","B","C")].length;
const aryfrom5 = [...Array(aryfrom4).keys()];
console.log(aryfrom5);
// output
// [ 0, 1, 2 ]

async

Node.jsのasyncは非同期処理を可読性の高いコードで実装できます。

非同期処理とはページが更新された際などに、更新前と更新後を比較して足りない部分だけをデータ通信する処理のことです。 この非同期処理の事をフロントエンド側の処理ではAjaxと呼ぶ事もあります。

スキルの場合はasyncを handler に設定して、awaitを handler 内の DynamoDB 等と通信する部分に設定すると非同期処理ができる。

== (等価演算子)

== は、文字列と数値の比較の場合、文字列を数値に変換してくれる。

数値と文字列を比較するとき、文字列は数値に変換されます。JavaScript は文字列の数値リテラルを Number 型の数値に変換しようと試みます。最初に、その文字列の数値リテラルから数学的な値を引き出します。次に、最も近い Number 型の値にこの値を丸めます。

=== (厳密等価演算子)

オペランド同士が、型を変換することなく厳密に等しいならば真を返します。

Object.prototype.hasOwnProperty()

オブジェクトが指定されたプロパティを持っているかどうかを示す真偽値を返します。

下記の例では、propという名前のプロパティをオブジェクトが含むか否かを確認しています。

    o = new Object();
    o.prop = 'exists';
    function changeO() {
      o.newprop = o.prop;
      delete o.prop;
    }
    o.hasOwnProperty('prop');   // returns true
    changeO();
    o.hasOwnProperty('prop');   // returns false

Object.getOwnPropertyNames()

与えられたオブジェクトで発見されたすべてのプロパティ (列挙可能・不可能を問わず) の配列を返します。

const object1 = {
  a: 1,
  b: 2,
  c: 3
};

console.log(Object.getOwnPropertyNames(object1));
// expected output: Array ["a", "b", "c"]

Array.length

length プロパティは配列の要素数を取得します。これは符号なし32bitの整数で、常に配列内インデックスの最大値よりも大きな数値になっています。

サンプル

var clothing = ['shoes', 'shirts', 'socks', 'sweaters'];

console.log(clothing.length);
// expected output: 4
var clothing = [{PrefectureName: "北海道", Romanization: "hokkaido", PrefecturalOfficeLocation: "札幌", PrefectureFlower: "ハマナス", PrefectureOrder: 1 },
                {PrefectureName: "鹿児島県", Romanization: "kagoshima", PrefecturalOfficeLocation: "鹿児島", PrefectureFlower: "ミヤマキリシマ", PrefectureOrder: 46 },
                {PrefectureName: "沖縄県", Romanization: "okinawa", PrefecturalOfficeLocation: "那覇", PrefectureFlower: "デイゴ", PrefectureOrder: 47 }
                ];

console.log(clothing.length);
// expected output: 3

Array.keys()

keys() メソッドは、配列の各インデックスのキーを含む、新しい Array Iterator オブジェクトを返します。

var array1 = ['a', 'b', 'c'];
var iterator = array1.keys();

for (let key of iterator) {
  console.log(key);
}

// output
// > 0
// > 1
// > 2

Object.keys()

Object.keys() メソッドは、指定されたオブジェクトが持つ names プロパティの配列を、通常のループで取得するのと同じ順序で返します。

const object1 = {
  a: 'somestring',
  b: 42,
  c: false
};

console.log(Object.keys(object1));
// expected output: Array ["a", "b", "c"]

for...of

オブジェクトの一覧をループで出力。

for...of 文は、iterableオブジェクトに対して反復的な処理をするループを作成します

let iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}
// output
// 11
// 21
// 31


let hoge = [{foo:10}, {bar:20}, {baz:30}];

for (let value of hoge) {
  console.log(value);
}
// output
// [object Object]
// [object Object]
// [object Object]

for (let value of hoge.keys()) {
  console.log(value);
}
// output
// 0
// 1
// 2

オブジェクトとプロパティ

以下はmyCarというオブジェクトの生成です。

let myCar = new Object();

以下のようにしてプロパティを定義することが可能です。

myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = 1969;

console.log(myCar)
// output
// {
//   make: "Ford" ,
//   model: "Mustang" ,
//   year: 1969
// }

未定義のプロパティはundefinedになります。

console.log(myCar.color)
// output
// undefined

オブジェクトのプロパティの名前には、あらゆる有効な JavaScript 文字列(空文字列を含む)か、文字列に変換できるものが使用できます。
しかしながら、JavaScript 識別子として有効ではないプロパティ名(例えば空白やダッシュを含んでいたり、数字で始まったりするプロパティ名)には、ブラケット(角括弧)表記法でのみアクセスできます。
この表記法はプロパティ名を動的に決める場合(プロパティ名が実行時に決まる場合)に便利です。

モジュール

moment

日時を取得するために使用。 https://momentjs.com/

ask-sdk V2

canHandle

Alexa SDK V2の、リクエストハンドラーとエラーハンドラーのインターフェースのメソッド。
SDKによって呼び出され、指定されたハンドラーが受け取ったリクエストやエラーを処理できるかどうかを判断します。

ex) HelloWorldIntentHandlerの処理を、HelloWorldIntentを受け取った時に呼び出す記述。

const HelloWorldIntentHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest' &&
           request.intent.name === 'HelloWorldIntent';
  },
  handle(handlerInput) {
    const speechText = 'こんにちは';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  }
};

Playbackディレクティブ

PlaybackStopped

「アレクサ」と声をかけ、アレクサが待ち受け状態になったときに送信される。

PlaybackController

PlaybackControllerリクエストに応答する場合、AudioPlayerディレクティブでしか応答できません。
応答には、outputSpeech、card、repromptなどの標準のプロパティはいずれも含めることができません。
サポートされていないプロパティを含む応答を送信すると、エラーが発生します。
(次へ前へのボタンを押した動作には発話させられずシンプルカードの表示もできない)

LaunchIntent

スキルの呼び出し時にパラメーターも付与していたときは、LaunchIntentの処理をスキップして後続のパラメーターを処理するHandlerを呼び出す。
LaunchIntentに必要な処理を定義していると、スキップ時は意図しない状態でHandlerを呼び出すこともあるので注意。

Launch時にshouldEndSessionフラグをtrueにするとError retrieving device rendering.が返される?
https://github.com/alexa/skill-sample-nodejs-audio-player/issues/144

カスタムインテント

標準ビルトインインテントと同じフレーズを登録可能。
標準ビルトインインテントと競合するフレーズを登録していた場合、優先して処理される。
標準ビルトインインテントの方が幅広い範囲を網羅できるため、競合させない方が良い。
AudioPlayerを実行した後は受付できない。

標準ビルトインインテント

カスタムインテントより処理の優先度が低い。 インテントによってフレーズの拡張ができるもの、できないものがある。

AudioPlayerインタフェースを有効にすると、ビルトインインテントインテントに登録していなくても 受け付ける。

インテントのサンプル発話に登録できない言葉

仕様で登録できない言葉がある。

  • 終了
  • その他終了に近しい表現

Dialogモデル

slot値を満たせなかったときの発話をskill側で定義して対応可能。 MP3は流せない。

addRequestInterceptors

前処理(RequestInterceptor)を追加します。
LaunchRequestHandlerのcanHandleメソッド ⇒ 前処理 ⇒ LaunchRequestHandlerのhandleメソッド の順で処理。
インテント処理も呼び出すたびに実行される。

addResponseInterceptors

後処理(ResponseInterceptor)を追加します。 handleメソッド ⇒ 後処理 ⇒ 返事。 エラーインテントに振り分けられた場合は実行されていない様子。 正常時はインテント処理を呼び出すたびに実行される。

SSML audioタグ

  • 5つ以上のaudioタグを埋め込んだlambda関数を実行するとスキルの起動に失敗する。
  • 音声再生が240秒を超える音声を再生するとスキルが正しく応答しないというエラーを報告してスキルが終了する。(オーディオタグの再生時に音声の長さを把握しているように見受けられる)
  • 音声再生中でもリプロンプトの応対可能。

SSML breakタグ

連続して複数使用できる?

スキルの待ち受け時間

  • アレクサはユーザからの返答(リプロンプト)を7~8秒待つ
  • その後、リプロンプト処理を行い再度7~8秒待つ
  • 応答がなければスキルを終了する
  • リプロンプト回数は変更できない

アレクサ(スキル) からユーザーへの返答時間

8秒。

プログレッシブ応答により、応答に使える総時間が変わるわけではありません。ユーザーがスキルを呼び出すと、スキルは約8秒以内に完全な応答を返す必要があります。スキルは完全な応答だけでなく、プログレッシブ応答の処理についてもこの時間内に終了する必要があります。 プログレッシブ応答の送信手順

https://developer.amazon.com/ja/docs/custom-skills/send-the-user-a-progressive-response.html

attributes の管理

async/await を使ったattributesの操作を行うときは処理の順序を意識しないと変数が空のままなので注意。

永続アトリビュートのキー

DynamoDB側ではAlexaデバイスにログインしているuserIdをプライマリパーティションキーとする。

ダイアログモード

Dialogインターフェースは、スキルとユーザーとの間のマルチターンの会話を管理するためのディレクティブを提供します。ユーザーのリクエストに応えるために必要な情報をユーザーにたずねるときに使用できます

AudioPlayerインタフェース

metadata の設定

  • addAudioPlayerPlayDirective は6つ目の引数に metadata パラメータを付与することで、画面付き端末にタイトル情報や画像を表示できる。
  • SDKのバージョンが古いと metadata を取得しない。(Lambda のfactテンプレートで設定するSDKは古いバージョンのため注意すること。自分はこれでハマった。)
  • 画像の表示はキャッシュを利用して行う。キャッシュはtokenの値に対応しており、同じtokenを使いまわすと意図しない画像を表示する可能性がある。

特定のインテントに該当しないインテントの処理

ErrorHandlerで処理する。

リクエストタイプ

PlaybackController.PlayCommandIssued

ユーザーが再生を開始また再開するためにインテントで「再生」または「再開」ボタンを使用した場合に送信されます。

System.ExceptionEncountered

PlaybackControllerリクエストに対する応答が原因でエラーが発生した場合、System.ExceptionEncounteredリクエストがスキルに送信されます。応答に含まれるディレクティブはすべて無視されます。

発話が一致しない場合にフォールバックを提供する

ユーザーの音声入力がスキルの他のインテントとまったく一致しない場合、AMAZON.FallbackIntent(英語を使用するロケールとドイツ語で利用可能)がトリガーされます。

https://developer.amazon.com/ja/docs/custom-skills/standard-built-in-intents.html#fallback

現状は日本では使用できないため、登録したサンプルと似ない発話もカスタムインテントで処理されることは仕様。

エラー

Unsupported Directive

AudioPlayer is currently an unsupported namespace. Check the device log for more information.

AlexaシミュレータでAudioPlayerによる音声再生をしようとすると発生。 AlexaシミュレータがAudioPlayerインタフェースの再生に対応していないため実機のechoを使う必要がある。

RequestHandlerChain not found!

条件に合致するHandlerが無いと発生する。 スキルのI/O入力とLambdaで定義しているHandlerの条件を確認し、意図しないパラメーターを保持していないか確認する。

Cannot read property 'trim' of undefined

returnで返す値に未定義のものがあると発生する。 repromptの定義忘れとかありませんか?

Task timed out after 3.00 seconds

Lambdaがタイムアウトしている。 Lambdaのタイムアウト時間を調整してあげよう。

Unable to find a suitable request handler.

不明。

DynamoDB

DynamoDB.DocumentClient

SDK

DynamoDBはテーブル作成時にプライマリキーとして以下の2つのキーを登録できる。

DynamoDBからGetを行うときはプライマリキーを指定する。 パーティションキーのみを定義した場合はパーティションキーのみの指定で良いが、 ソートキーも定義した場合は、パーティションキーおよびソートキーの指定が必要になる。

git でリモートリポジトリに ssh するときに Permission denied (publickey).

概要

ローカルからリモートリポジトリに push しようとして以下のエラーが出ちゃったときの話。

$ git push origin master
username@hostname.com: Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

環境

実施手順

1. 鍵の作成

$ ssh-keygen -t rsa -b 4096 -C "your@address.com"

2. ~/.ssh/config の設定

Host backlog
  User username
  HostName hostname.com
  IdentityFile ~/.ssh/id_rsa
  IdentitiesOnly yes
  port 22

ここまでやって、リモートリポジトリに push したらエラーになったので、以下実施。

3. ssh-agent に秘密鍵を追加

$ eval `ssh-agent`
Agent pid XXXXX
$ ssh-add ~/.ssh/id_rsa
Identity added: /c/Users/name/.ssh/id_rsa (/c/Users/name/.ssh/id_rsa)

4. 鍵の登録状態を確認

$ ssh-add -l
4096 SHA256:XXXXXXX/XXXXXXXXXXXXXXXXX/XXXXXXXXXXXXXXXXX /c/Users/name/.ssh/id_rsa (RSA)

リモートリポジトリに push して成功。

CloudWatchのログをS3に送る

以下記事の続き。

dafukui.hatenablog.com

今回は以下のイメージで、EC2で稼働するApacheのログデータをS3に保管したいと思います。

f:id:dafukui:20190522183714p:plain

Kinesis Firehose の設定

WEBコンソールで設定します。

Firehoseの配信ストリームの作成に進む。

f:id:dafukui:20190524100658p:plain

f:id:dafukui:20190524112812p:plain

f:id:dafukui:20190524112814p:plain

f:id:dafukui:20190524112816p:plain

f:id:dafukui:20190524113300p:plain

送信されるデータは標準で圧縮処理が行われているため、Firehose側での対応は不要です。

CloudWatch Logs から Amazon Kinesis Data Firehose に送信されたデータは、すでに gzip レベル 6 圧縮で圧縮されているため、Kinesis Data Firehose 配信ストリーム内で圧縮を使用する必要はありません。

docs.aws.amazon.com

f:id:dafukui:20190524112819p:plain

IAM roleは作成後に選択を行うことで設定に反映されます。

ログ転送に必要な設定を awscli で実施

まず、設定に使用する以下のファイルを作成します。

  • TrustPolicyForCWL.json
{
  "Statement": {
    "Effect": "Allow",
    "Principal": { "Service": "logs.ap-northeast-1.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }
}
  • PermissionsForCWL.json
{
  "Statement":[
    {
      "Effect":"Allow",
      "Action":["firehose:*"],
      "Resource":["arn:aws:firehose:ap-northeast-1:999999999999:*"]
    },
    {
      "Effect":"Allow",
      "Action":["iam:PassRole"],
      "Resource":["arn:aws:iam::999999999999:role/CWLtoKinesisFirehoseRole"]
    }
  ]
}

docs.aws.amazon.com

今回は以下のような構造にしました。

CWLtoKinesisFirehose/
┣ PermissionsForCWL.json
┗ TrustPolicyForCWL.json

続いて、信頼ポリシーに CloudWatch を指定するIAMロールを作成します。 このロールは CloudWatch だけが引き受けることのできる限定的なロールです。

ubuntu@ip-10-0-0-183:~/CWLtoKinesisFirehose$ aws iam create-role \
>       --role-name CWLtoKinesisFirehoseRole \
>       --assume-role-policy-document file://~/CWLtoKinesisFirehose/TrustPolicyForCWL.json
{
    "Role": {
        "Path": "/",
        "RoleName": "CWLtoKinesisFirehoseRole",
        "RoleId": "AROA4DFXVDA2FTKIJUU62",
        "Arn": "arn:aws:iam::999999999999:role/CWLtoKinesisFirehoseRole",
        "CreateDate": "2019-05-24T00:48:46Z",
        "AssumeRolePolicyDocument": {
            "Statement": {
                "Effect": "Allow",
                "Principal": {
                    "Service": "logs.ap-northeast-1.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
            }
        }
    }
}

Firehoseに対してログデータの送信を許可するポリシーを先ほど作成したCWLtoKinesisFirehoseRoleに設定します。

ubuntu@ip-10-0-0-183:~/CWLtoKinesisFirehose$ aws iam put-role-policy --role-name CWLtoKinesisFirehoseRole \
>     --policy-name Permissions-Policy-For-CWL \
>     --policy-document file://~/CWLtoKinesisFirehose/PermissionsForCWL.json

iam:PassRoleは条件キーiam:PassedToServiceのことで、特定のサービスにのみロールを渡すことができるように制限が行えます。

docs.aws.amazon.com

CloudWatch Logs のサブスクリプションフィルタを作成します。

ubuntu@ip-10-0-0-183:~/CWLtoKinesisFirehose$ aws logs put-subscription-filter \
>     --log-group-name "/var/log/apache2/error.log" \
>     --filter-name "All" \
>     --filter-pattern "" \
>     --destination-arn "arn:aws:firehose:ap-northeast-1:999999999999:deliverystream/test-cwl-archive" \
>     --role-arn "arn:aws:iam::999999999999:role/CWLtoKinesisFirehoseRole"

CloudWatch のサブスクリプションに反映されました。

f:id:dafukui:20190524110931p:plain

Apacheのerrorlogを出力すると、S3にもログが転送されました。

f:id:dafukui:20190524113703p:plain

余談

ロールを設定するjsonファイルでregionを指定し忘れていたため、以下のエラーにはまってしましました。

ubuntu@ip-10-0-0-183:~/CWLtoKinesisFirehose$ aws logs put-subscription-filter \
>     --log-group-name "/var/log/apache2/error.log" \
>     --filter-name "All" \
>     --filter-pattern "" \
>     --destination-arn "arn:aws:firehose:ap-northeast-1:999999999999:deliverystream/test-cwl-archive" \
>     --role-arn "arn:aws:iam::999999999999:role/CWLtoKinesisFirehoseRole"

An error occurred (InvalidParameterException) when calling the PutSubscriptionFilter operation: Could not deliver test message to specified Firehose stream. Check if the given Firehose stream is in ACTIVE state.

Windows の awscli から実行したときはエラーになったので諦めました。

PS C:\Users\fukui> aws logs put-subscription-filter --log-group-name "/var/log/apache2/error.log" --filter-name "All" --filter-pattern "" --destination-arn "arn:aws:firehose:ap-northeast-1:999999999999:deliverystream/Test-Fukui-Applog" --role-arn "arn:aws:iam::999999999999:role/CWLtoKinesisFirehoseRole"
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
__main__.py: error: argument --filter-pattern: expected one argument
PS C:\Users\fukui>

参考

docs.aws.amazon.com

CloudWatch でアプリケーションのログを取得

環境

software version
ubuntu 18.04 LTS

CloudWatch Logs Agent の導入

CloudWatch でアプリケーションのログを取得するには、対象となるサーバに CloudWatch Logs Agent を導入する。

ubuntu@ip-10-0-0-183:~$ curl https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py -O
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 56093  100 56093    0     0  70824      0 --:--:-- --:--:-- --:--:-- 70735
ubuntu@ip-10-0-0-183:~$
ubuntu@ip-10-0-0-183:~$
ubuntu@ip-10-0-0-183:~$ python -V
Python 2.7.15rc1
ubuntu@ip-10-0-0-183:~$
ubuntu@ip-10-0-0-183:~$ python3 -V
Python 3.6.7

python3 でインストールしようとしたら怒られた。

ubuntu@ip-10-0-0-183:~$ sudo python3 ./awslogs-agent-setup.py --region us-east-1
ERROR: This script only supports python version 2.6 - 3.5

python でインストール。

ubuntu@ip-10-0-0-183:~$ sudo python ./awslogs-agent-setup.py --region us-east-1
Launching interactive setup of CloudWatch Logs agent ...

Step 1 of 5: Installing pip ...libyaml-dev does not exist in system DONE

Step 2 of 5: Downloading the latest CloudWatch Logs agent bits ... DONE

Step 3 of 5: Configuring AWS CLI ...
AWS Access Key ID [****************LIIA]:
AWS Secret Access Key [****************pQqi]:
Default region name [us-east-1]: ap-northeast-1
Default output format [None]: json

Step 4 of 5: Configuring the CloudWatch Logs Agent ...
Path of log file to upload [/var/log/syslog]:
Destination Log Group name [/var/log/syslog]:

Choose Log Stream name:
  1. Use EC2 instance id.
  2. Use hostname.
  3. Custom.
Enter choice [1]:

Choose Log Event timestamp format:
  1. %b %d %H:%M:%S    (Dec 31 23:59:59)
  2. %d/%b/%Y:%H:%M:%S (10/Oct/2000:13:55:36)
  3. %Y-%m-%d %H:%M:%S (2008-09-08 11:52:54)
  4. Custom
Enter choice [1]: 3

Choose initial position of upload:
  1. From start of file.
  2. From end of file.
Enter choice [1]:
More log files to configure? [Y]: n

Step 5 of 5: Setting up agent as a daemon ...DONE


------------------------------------------------------
- Configuration file successfully saved at: /var/awslogs/etc/awslogs.conf
- You can begin accessing new log events after a few moments at https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logs:
- You can use 'sudo service awslogs start|stop|status|restart' to control the daemon.
- To see diagnostic information for the CloudWatch Logs Agent, see /var/log/awslogs.log
- You can rerun interactive setup using 'sudo python ./awslogs-agent-setup.py --region us-east-1 --only-generate-config'
------------------------------------------------------
ubuntu@ip-10-0-0-183:~$

CloudWatch Logs Agent の設定確認

/var/awslogs/etc/awslogs.confが保管先になる。

$ sudo cat /var/awslogs/etc/awslogs.conf

(途中省略)

[/var/log/syslog]
datetime_format = %Y-%m-%d %H:%M:%S
file = /var/log/syslog
buffer_duration = 5000
log_stream_name = {instance_id}
initial_position = start_of_file
log_group_name = /var/log/syslog

errorlogを意図的に出力させたかったのでアクセスを許可しないファイルを用意。

ubuntu@ip-10-0-0-183:/var/www/html$ ll
total 20
drwxr-xr-x 2 root root  4096 May 21 14:30 ./
drwxr-xr-x 3 root root  4096 May 21 14:09 ../
-r-------- 1 root root     0 May 21 14:30 denied.html
-rw-r--r-- 1 root root 10918 May 21 14:09 index.html
ubuntu@ip-10-0-0-183:/var/www/html$

ブラウザからアクセスしてエラーが出力されることを確認。

ubuntu@ip-10-0-0-183:/var/www/html$ tail /var/log/apache2/error.log
[Tue May 21 14:09:06.072766 2019] [mpm_event:notice] [pid 25946:tid 140322336525248] AH00489: Apache/2.4.29 (Ubuntu) configured -- resuming normal operations
[Tue May 21 14:09:06.072862 2019] [core:notice] [pid 25946:tid 140322336525248] AH00094: Command line: '/usr/sbin/apache2'
[Tue May 21 14:31:34.997717 2019] [core:error] [pid 25948:tid 140322109630208] (13)Permission denied: [client 180.45.162.2:63040] AH00132: file permissions deny server access: /var/www/html/denied.html

CloudWatch Logs Agent を再起動。

ubuntu@ip-10-0-0-183:/var/www/html$ sudo systemctl restart awslogs

再度ブラウザからアクセスしてapacheのエラーを出力。 しかし、CloudWatch Logs でログが確認出来ず。

CloudWatch Logs Agent のログを確認。

ubuntu@ip-10-0-0-183:~$ sudo tail /var/log/awslogs.log

(省略)

ClientError: An error occurred (AccessDeniedException) when calling the PutLogEvents operation: User: arn:aws:iam::123456789012:user/iamuser_name is not authorized to perform: logs:PutLogEvents on resource: arn:aws:logs:ap-northeast-1:123456789012:log-group:/var/log/apache2/error.log:log-stream:i-xxxxxxxxxxxxxxxxxxx
2019-05-21 15:29:30,566 - cwlogs.push.reader - WARNING - 28881 - Thread-4 - Fall back to previous event time: {'timestamp': 1558420113000, 'start_position': 56897L, 'end_position': 56979L}, previousEventTime: 1558420113000, reason: timestamp could not be parsed from message.

検証していた環境では awscli の credentials を登録していたため、設定したEC2ロールではなく awscli の credentials 情報を使用していた様子。 awscli の credentials 情報を削除するとエラーが解消された。

CloudWatch のログメトリクスにも出力されるようになった。

f:id:dafukui:20190521155254p:plain

django + mysql メモ

環境

software version
amazon linux 2018.03
python 3.7.3
django 2.2.1
mysql 5.7

DjangoMySQLを接続するにあたって公式の推奨に従い、mysqlclientを選択する。

docs.djangoproject.com

python の設定

以下を参考に実施する。

danieleriksson.net

パッケージをダウンロードする。

$ curl -O https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz

解凍する。

$ tar -Jxvf Python-3.7.3.tar.xz

コンパイラとライブラリの導入。

[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ sudo yum groupinstall "Development Tools"

libffi-develが参考記事のコマンドには含まれていないが、必要なため追記。

$ sudo yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel expat-devel libffi-devel

コンパイラが無いとビルド時にエラーになる。

[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ ./configure
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking for python3.7... no
checking for python3... no
checking for python... python
checking for --enable-universalsdk... no
checking for --with-universal-archs... no
checking MACHDEP... checking for --without-gcc... no
checking for --with-icc... no
checking for gcc... no
checking for cc... no
checking for cl.exe... no
configure: error: in `/home/ec2-user/Python-3.7.3':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details
[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$

stackoverflow.com

ビルドする。

[ec2-user@ip-xx-xx-1-192 ~]$ cd Python-3.7.3
[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ sudo ./configure --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ sudo make && make altinstall
[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ sudo make altinstall

python3.7 で実行できるようになる。

[ec2-user@ip-xx-xx-1-192 Python-3.7.3]$ which python3.7
/usr/local/bin/python3.7

pip3.7 も利用可能になっている。

[ec2-user@ip-xx-xx-1-192 bin]$ which pip3.7
/usr/local/bin/pip3.7

シンボリックリンク設定

python3.7 を python3 で実行できるようにしたいので、シンボリックリンクを設定する。

$ sudo ln -s /usr/local/bin/python3.7 python3
[ec2-user@ip-xx-xx-1-192 ~]$ python3
Python 3.7.3 (default, May 13 2019, 02:47:06)
[GCC 7.2.1 20170915 (Red Hat 7.2.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

pip3.7 も pip3 で実行できるようにする。

$ sudo ln -s /usr/local/bin/pip3.7 pip3
[ec2-user@ip-xx-xx-1-192 ~]$ pip3 list
Package    Version
---------- -------
pip        19.0.3
setuptools 40.8.0
You are using pip version 19.0.3, however version 19.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

django の設定

django の導入。

$ sudo pip3 install django

プロジェクトの作成。

my_projectという名前のプロジェクトを作成することにした。

[ec2-user@ip-xx-xx-1-192 ~]$ django-admin startproject my_project

setting.pyの設定。

setting.py を編集するためディレクトリを移動する。

[ec2-user@ip-xx-xx-1-192 ~]$ cd my_project/my_project/
[ec2-user@ip-xx-xx-1-192 my_project]$ ll
total 12
-rw-r--r-- 1 ec2-user ec2-user    0 May 13 04:39 __init__.py
-rw-r--r-- 1 ec2-user ec2-user 3117 May 13 04:44 settings.py
-rw-r--r-- 1 ec2-user ec2-user  752 May 13 04:39 urls.py
-rw-r--r-- 1 ec2-user ec2-user  397 May 13 04:39 wsgi.py
[ec2-user@ip-xx-xx-1-192 my_project]$

settings.py の変更箇所は以下の通り。

ALLOWED_HOSTS = ['xxx.xxx.xxx.19']  # public ip を入力

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

mysql5.7 の設定

amazon linux のため el6 を選択する。

mysql80のリポジトリ追加

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum localinstall https://dev.mysql.com/get/mysql80-community-release-el6-3.noarch.rpm

mysql8.0 のリポジトリを無効化

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum-config-manager --disable mysql80-community

mysql5.7 のリポジトリを有効化

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum-config-manager --enable mysql57-community

mysql57 の情報が確認できるようになる。

[ec2-user@ip-xx-xx-1-192 my_project]$ yum info mysql-community-server
Loaded plugins: priorities, update-motd, upgrade-helper
mysql-connectors-community                                                           | 2.5 kB  00:00:00
mysql-tools-community                                                                | 2.5 kB  00:00:00
mysql57-community                                                                    | 2.5 kB  00:00:00
mysql57-community/x86_64/primary_db                                                  | 182 kB  00:00:00
24 packages excluded due to repository priority protections
Available Packages
Name        : mysql-community-server
Arch        : x86_64
Version     : 5.7.26
Release     : 1.el6
Size        : 153 M
Repo        : mysql57-community/x86_64
Summary     : A very fast and reliable SQL database server
URL         : http://www.mysql.com/
License     : Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Under GPLv2
            : license as shown in the Description field.
Description : The MySQL(TM) software delivers a very fast, multi-threaded, multi-user,
            : and robust SQL (Structured Query Language) database server. MySQL Server
            : is intended for mission-critical, heavy-load production systems as well
            : as for embedding into mass-deployed software. MySQL is a trademark of
            : Oracle and/or its affiliates
            :
            : The MySQL software has Dual Licensing, which means you can use the MySQL
            : software free of charge under the GNU General Public License
            : (http://www.gnu.org/licenses/). You can also purchase commercial MySQL
            : licenses from Oracle and/or its affiliates if you do not wish to be bound by the terms of
            : the GPL. See the chapter "Licensing and Support" in the manual for
            : further info.
            :
            : The MySQL web site (http://www.mysql.com/) provides the latest news and
            : information about the MySQL software.  Also please see the documentation
            : and the manual for more information.
            :
            : This package includes the MySQL server binary as well as related utilities
            : to run and administer a MySQL server.

mysql-devel の導入

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum install mysql57-devel.x86_64

mysql57の導入

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum install mysql-community-server
[ec2-user@ip-xx-xx-1-192 my_project]$ mysql --version
mysql  Ver 14.14 Distrib 5.7.26, for Linux (x86_64) using  EditLine wrapper
[ec2-user@ip-xx-xx-1-192 my_project]$ yum list installed | grep mysql
mysql-community-client.x86_64    5.7.26-1.el6                 @mysql57-community
mysql-community-common.x86_64    5.7.26-1.el6                 @mysql57-community
mysql-community-devel.x86_64     5.7.26-1.el6                 @mysql57-community
mysql-community-libs.x86_64      5.7.26-1.el6                 @mysql57-community
mysql80-community-release.noarch el6-3                        installed
[ec2-user@ip-xx-xx-1-192 my_project]$

mysql-devel が無いと後述する mysqlclient のインストールや django の makemigrations がエラーになる

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo pip3 install mysqlclient
Collecting mysqlclient
  Using cached https://files.pythonhosted.org/packages/f4/f1/3bb6f64ca7a429729413e6556b7ba5976df06019a5245a43d36032f1061e/mysqlclient-1.4.2.post1.tar.gz
    ERROR: Complete output from command python setup.py egg_info:
    ERROR: /bin/sh: mysql_config: command not found
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-wvc2e8ay/mysqlclient/setup.py", line 16, in <module>
        metadata, options = get_config()
      File "/tmp/pip-install-wvc2e8ay/mysqlclient/setup_posix.py", line 51, in get_config
        libs = mysql_config("libs")
      File "/tmp/pip-install-wvc2e8ay/mysqlclient/setup_posix.py", line 29, in mysql_config
        raise EnvironmentError("%s not found" % (_mysql_config_path,))
    OSError: mysql_config not found
    ----------------------------------------
ERROR: Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-wvc2e8ay/mysqlclient/
[ec2-user@ip-xx-xx-1-192 my_project]$
[ec2-user@ip-xx-xx-1-192 my_project]$python3.7 manage.py makemigrations
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/django/db/backends/mysql/base.py", line 15, in <module>
    import MySQLdb as Database
ModuleNotFoundError: No module named 'MySQLdb'

(途中省略)

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?
[ec2-user@ip-xx-xx-1-192 my_project]$

参考 qiita.com

※mysql57 をインストールしてから mysql-devel をインストールしようとすると、バージョンに起因するエラーが出た。インストールした mysql をアンインストールして、mysql-devel ⇒ mysql57 の順番でインストールするとバージョンの問題を解消できた。

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo yum install mysql57-devel
Loaded plugins: priorities, update-motd, upgrade-helper
amzn-main                                                                            | 2.1 kB  00:00:00
amzn-updates                                                                         | 2.5 kB  00:00:00
24 packages excluded due to repository priority protections
Resolving Dependencies
--> Running transaction check
---> Package mysql57-devel.x86_64 0:5.7.25-1.11.amzn1 will be installed
--> Processing Dependency: mysql57-libs(x86-64) = 5.7.25-1.11.amzn1 for package: mysql57-devel-5.7.25-1.11.amzn1.x86_64
--> Processing Dependency: mysql57(alternatives) for package: mysql57-devel-5.7.25-1.11.amzn1.x86_64
--> Processing Dependency: mysql57(alternatives) for package: mysql57-devel-5.7.25-1.11.amzn1.x86_64
--> Processing Dependency: libmysqlclient.so.1020()(64bit) for package: mysql57-devel-5.7.25-1.11.amzn1.x86_64
--> Running transaction check
---> Package mysql57.x86_64 0:5.7.25-1.11.amzn1 will be installed
--> Processing Dependency: mysql57-common(x86-64) = 5.7.25-1.11.amzn1 for package: mysql57-5.7.25-1.11.amzn1.x86_64
---> Package mysql57-libs.x86_64 0:5.7.25-1.11.amzn1 will be installed
--> Running transaction check
---> Package mysql57-common.x86_64 0:5.7.25-1.11.amzn1 will be installed
--> Processing Dependency: mysql-config for package: mysql57-common-5.7.25-1.11.amzn1.x86_64
--> Running transaction check
---> Package mysql-config.x86_64 0:5.5.62-1.23.amzn1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

============================================================================================================
 Package                    Arch               Version                       Repository                Size
============================================================================================================
Installing:
 mysql57-devel              x86_64             5.7.25-1.11.amzn1             amzn-updates             293 k
Installing for dependencies:
 mysql-config               x86_64             5.5.62-1.23.amzn1             amzn-updates              49 k
 mysql57                    x86_64             5.7.25-1.11.amzn1             amzn-updates              14 M
 mysql57-common             x86_64             5.7.25-1.11.amzn1             amzn-updates              90 k
 mysql57-libs               x86_64             5.7.25-1.11.amzn1             amzn-updates             1.2 M

Transaction Summary
============================================================================================================
Install  1 Package (+4 Dependent packages)

Total size: 15 M
Installed size: 49 M
Is this ok [y/d/N]: y
Downloading packages:
Running transaction check
Running transaction test


Transaction check error:
  file /etc/my.cnf from install of mysql-config-5.5.62-1.23.amzn1.x86_64 conflicts with file from package mysql-community-server-5.7.26-1.el6.x86_64

Error Summary
-------------

[ec2-user@ip-xx-xx-1-192 my_project]$

mysqlclient の導入

$ sudo pip3 install mysqlclient

pypi.org

mysql57-server の導入

[ec2-user@ip-xx-xx-1-192 my_project]$ yum install mysql57-server

サービスの開始

[ec2-user@ip-xx-xx-1-192 my_project]$ sudo service mysqld start

Starting mysqld:                                           [  OK  ]

ログイン

[ec2-user@ip-xx-xx-1-192 my_project]$ mysql -u root

DB 作成

mysql> CREATE DATABASE my_project;
Query OK, 1 row affected (0.00 sec)

mysql>
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| my_project         |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.01 sec)

mysql>

setting.py に以下変更を反映。

DATABASES = {
#    'default': {
#        'ENGINE': 'django.db.backends.sqlite3',
#        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#    }
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'my_project',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

マイグレーション実行

[ec2-user@ip-xx-xx-1-192 my_project]$ python3 manage.py makemigrations
[ec2-user@ip-xx-xx-1-192 my_project]$ python3 manage.py migrate

Amazon EC2 Auto Scaling 入門

Amazon EC2 Auto Scaling ってどんな機能?

耐障害性の向上のため、または可用性の向上のために、予め設定した範囲の中でEC2 インスタンスの数を増減させることができるサービス。

例えば以下のような使い方ができる。

なお、 AWS はアプリケーションの Auto Scaling (AWS Auto Scaling)も可能だが、それは本記事の EC2 Auto Scaling とは異なるものになる。

Amazon EC2 Auto Scaling を構成する要素

Amazon EC2 Auto Scaling は以下の要素で構成される。

  • グループ
  • 設定テンプレート
  • スケーリングのオプション

グループ

複数の EC2 インスタンスを論理的に1つの集まりとして管理する。
グループには EC2 インスタンスの最小数、最大数、および希望する数を指定する。

  • 最小数
    • グループの EC2 インスタンス数はこのサイズよりも小さくなることはない
  • 最大数
    • グループの EC2 インスタンス数はこのサイズよりも大きくなることはない
  • 希望数
    • グループの EC2 インスタンス数は通常この数だけ起動している
    • 今時点で必要な台数

グループ内のインスタンスに対して定期的にヘルスチェックを行い、異常のある EC2 インスタンスは終了して新たに起動する別の EC2 インスタンスと置き換えられる。
グループ内でオンデマンドインスタンスリザーブインスタンス、スポットインスタンスを併用することが可能。
インスタンスタイプは複数指定することが可能。インスタンスタイプに選択の優先度を設定して制御する。

起動テンプレート

起動するインスタンスの設定情報をテンプレートとして保存する。
再利用、共有が可能。
テンプレートはバージョン管理される。

設定情報は以下のとおり。

起動設定との違いとして、起動テンプレートは複数のバージョンのテンプレートを使用することができる

スケーリングのオプション

Amazon EC2 Auto Scaling グループをスケーリングする方法のこと。以下がある。

  • 現在のインスタンスレベルの常時維持
  • 手動スケーリング
  • スケジュールに基づくスケーリング
  • 要求に基づくスケーリング (動的スケーリング)

現在のインスタンスレベルの常時維持

ユースケース: Auto Healing

実行中のインスタンスを指定した数は常に維持するようにする。 Auto Scaling グループ内で実行中のインスタンスで定期的なヘルスチェックを実行し、インスタンスに異常があると判断すると、そのインスタンスを終了して新しいインスタンスを起動する。

  • 最大値 : 最小値=1 : 1 の場合
    常に1台を起動し、異常があれば自動で1台起動し直す
  • 最大値 : 最小値=2 : N の場合
    常に2台以上を起動。2台の片側に異常があってももう1台で維持する。

手動スケーリング

Auto Scaling グループの最大値、最小値、または希望数の変更のみを指定し、Amazon EC2 Auto Scaling が、更新された容量を維持するためにインスタンスを作成または終了するプロセスを管理する。 希望数はグループの最大値と同じかそれ以下である必要があり、希望数が最大値よりも大きい場合は最大値を更新する必要がある。

スケジュールに基づくスケーリング

インスタンスの数を増減しなければならない状況が予測でき、日付と時刻に基づいて自動的に実行されるスケーリングアクション。

  • スケジュールされたアクションの実行順序は、同じグループ内で実行される場合は維持する。複数のグループ間で実行される場合は必ずしも維持しない
  • 1 つの Auto Scaling グループあたり最大 125 のスケジュールされたアクションを作成できる
  • アクションのスケジュールを重ねることはできない。他のスケーリングアクティビティが既にスケジュールされているときにアクティビティをスケジュールしようとすると、呼び出しは拒否され、競合を知らせるエラーメッセージが表示される
  • クールダウン期間はサポートしない。

要求に基づくスケーリング (動的スケーリング)

以下の2つのポリシーがある。

  • Target tracking scaling (ターゲットの追跡スケーリングポリシー)
  • Step scaling
Target tracking scaling

需要の増減に応じてスケールする方法を定義する。 たとえば、平均 CPU 使用率が 15 分間にわたって 90% を超えると EC2 インスタンス群の拡大する等のポリシーを作成する。

Step scaling

ポリシーで指定したスケーリング調整値を使用して Auto Scaling グループの現在の容量が変更される。
メトリクスに下限と上限の閾値を設定し、その閾値を超えた割合に応じて増減させる。 スケーリング調整では、グループの容量が最大グループサイズより大きい容量にも、最小グループサイズよりも小さい容量にも変更されることはない。

スケールイン時のインスタンス削除制御

デフォルトの終了ポリシー(順序)は以下のとおり。

  1. インスタンスが最も多いAZのインスタンスを選択
  2. 候補となるインスタンスが複数ある場合、最も古い起動設定またはテンプレートを使用しているインスタンスを選択
  3. インスタンスが同じ起動設定から起動されている場合、Auto Scaling グループは次の課金時間に最も近いインスタンスを選択して終了

デフォルトの終了ポリシーはほとんどの状況に対応しているが、カスタマイズされたポリシーに置き換えることも可能。 カスタマイズしたポリシーへの置き換えは、起動するインスタンスが不均等なAZのインスタンスに適用する。均等であればすべてのAZに適用する。