読者です 読者をやめる 読者になる 読者になる

bauer's diary

凡人の凡人による凡人のための備忘録

AWS LambdaでサーバーレスにEC2メンテナンスをslackに通知する 〜その4〜

4回に分けて連載しましたが、本記事で最後となります。
ここでは、Lambda関数の実装解説と、実践で困ったことを紹介します。

インストール

ご紹介するコードを含めたツールは、実行が可能な状態でnpmかGitHubからインストールできます。
前回までの手順を元にLambda関数をAWSに設定しておくことで、毎回AWSコンソールから実行することなくローカルでも実行できるようにしているのが特徴です。

www.npmjs.com
github.com

実行する前に、aws-cliをグローバルにインストール・configureした後、AWSに設定した関数名を下記に渡してください。

  1. .envのFUNCTION_NAME
  2. zipDeploy.shに渡す第一引数


コード解説

実際のコードを少しずつ解説します。

require('dotenv').config();

const AWS = require('aws-sdk');
AWS.config.update({
  region: process.env.REGION,
});
const ec2 = new AWS.EC2({});

ローカルで実行する場合は、.envに必要な環境変数を指定して、それをdotenvで読み込んでいます。
尚、aws-sdkAWS上で実行する場合は不要ですが、ローカルで実行するためにdependenciesに指定しています。

exports.handler = () => {

Lambda 関数を作成するときに、ハンドラーを指定しましたが、これはサービス内でコードを実行する際に AWS Lambda が呼び出すことができる関数です。
コールバックパラメーターは省略可能で、呼び出し元に情報を返す場合には使用しますが、今回はslackにPOSTするだけなのでcallbackは呼びません。

const params = {
  IncludeAllInstances: true,
};

…

exports.handler = () => {
  ec2.describeInstanceStatus(params, (err, res) => {  

DescribeInstanceStatus - Amazon Elastic Compute Cloud
こちらのAWSの公式APIを使ってイベント情報を取得しています。

前回の記事でも触れましたが、"IncludeAllInstances: true"にすれば、Configurationでサブネットに指定したサブネット以外の全インスタンスのステータスが取れました。

console.log(`Instance num: ${res.InstanceStatuses.length}`);

console.log() ステートメントは、受信イベントデータの一部を CloudWatch Logsに記録します。

function report(text) {
  slack.setWebhook(process.env.WEBHOOK_URI);
  slack.webhook({
    channel: process.env.SLACK_CHANNEL,
    username: process.env.SLACK_USERNAME,
    icon_emoji: process.env.SLACK_ICON_EMOJI,
    text: toMentionText(text),
  }, (err, res) => {
    console.log(err, res);
  });
}

最後に取得した全イベントをslackに通知します。

困ったこと

if (!err) {}if (err !== null) {}

普通のJavaScriptでは、変数の存在判定でnullかどうかも判別できます。
しかし、Lambda上ではできなかったので仕方なくerrがnullかどうかを指定しました。

if (status.Events.length > 0) {
  events.push({
    status.InstanceId,   // Syntaxエラー
    status.InstanceState,   // Syntaxエラー
    status.Events,   // Syntaxエラー
  });
}if (status.Events.length > 0) {
  events.push({
    InstanceId: status.InstanceId,
    InstanceState: status.InstanceState,
    Events: status.Events,
  });
}

PropertyShorthandも使えませんでした。


最も困ったのは、対応済のイベントもAPIで取得してしまうことでした。
Webコンソールだと、下記のようなリクエストパラメータで、対応完了したイベントは取得しません。

https://ap-northeast-1.console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Events:resourceTypeFilter=all-resource-types;eventsStatusFilter=in-progress-and-scheduled-events;eventsTypeFilter=in-progress-and-scheduled-events

"継続中と予定"を示す、"eventsStatusFilter=in-progress-and-scheduled-events"であれば、対応済のイベントは表示されませんが、"all-statuses"だと表示されてしまいます。
APIのFilterパラメータ仕様を見ても、対応済のイベントを取得しない方法が見つからなかったので、下記のようにDecriptionの"[Completed]"で判断する運びとなりました。

const events = _
  .chain(res.InstanceStatuses)
  .filter(status => status.Events.length > 0)
  .map(status => {
    return {
      InstanceId: status.InstanceId,
      InstanceState: status.InstanceState,
      Events: status.Events,
    };
  })
  .filter(instance => {   // for exclude completed event
    let filtered = _.filter(instance.Events, (event) => {
      return !event.Description.startsWith('[Completed]');
    });

    return filtered.length > 0;
  })
  .value();


おわりに

Labmdaはサーバーレスのさきがけとして、Alexa SkillをAmazon Echoに追加できたりと重要性が高まっているため、これ以外にも日常業務で使えそうなツールを開発していきたいです。