プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

UDC三重のイベント『第3回 少子高齢化を斬る!』に参加してきました

先日2019年12月8日(日)に、三重大学工学部大会議室で開催された、UDC三重のイベント『第3回 少子高齢化を斬る!』に参加してきました。

udcmie.connpass.com

その様子を簡単にメモっときます。

江戸橋

三重大学へ行くには、電車を使う場合、近鉄江戸橋駅からテクテクと歩いていくのが定番です。 その途中にあるのが、駅名にもなってる江戸橋。

この江戸橋、ここしばらく架け替え工事をしていたのですが、なんと! いつの間にやら、新しくなって開通しています。

f:id:junichim:20191209000444j:plain

f:id:junichim:20191209000507j:plain

開通前は23号線のほうの橋を通って迂回していたのですが、これが結構遠回りだったので、開通はありがたいです。

会場

会場は三重大学工学部大会議室でした。

写真撮り忘れました・・・

会場はきれいで広くて全然問題ないのですが、会場周辺が道路工事だらけで、入り口までたどり着くのが困難だったのが困りものでした。

講演

イベント前半は講演が2つ。

タイトル 講演者
少子高齢化はシビックテックで 朝日航洋株式会社/IT DART/OSGeo.JP
嘉山 陽一 様
データ分析からみる少子高齢化 株式会社 オービタルネット
植田 粋美 様

一つ目の講演では、OSSや無料(範囲)で使えるクラウドサービスを活用してサービスを作ることができることの紹介と、それをシビックテックに活かしていけばいいという話。あと、講演後の質問で、シビックテック(とかボランティア)の活動と資金の話があって、これも興味深かったです。

二つ目の講演は、可視化することの大事さの話や、合計特殊出生率にまつわる話などを聞きました。三重だと熊野市が合計特殊出生率が2を超えていて、県内一だそうですが、特にアピールしている姿が見えないところの話とかも面白かったですね。

アイデアソン

イベント後半は参加者全員でアイデアソンしました。

今回のアイデアソンは、

  • 自分でテーマを決める
  • テーマを中心にして、思いつくキーワードでマンダラチャートを埋めていく
  • 二人でブレスト(相手を変えて何度か)
  • ブレストを踏まえて、アイデアを3つ書き出す
  • 書いたアイデアをみんなで投票

という流れの進行でした。

最初のほうにあった、テーマから連想されるキーワードを制限時間内に埋めていく作業が大変でしたが、ゲームっぽくて楽しかったですね。 自分の作ったのはこんな感じになりました(自分用のものなので字が読めないのは勘弁してください)。

f:id:junichim:20191209004257j:plain

アイデアは3つ考えてみました。1つはすぐに思いついたのですが、残り二つを絞り出すのが苦労しました。

f:id:junichim:20191209082350j:plain

f:id:junichim:20191209082409j:plain

f:id:junichim:20191209004342j:plain

最後に挙げた、高齢化と車をテーマにしたアイデアが、投票した結果、全体の3位に入りました。ありがとうございます。 (1位と2位のアイデアは写真撮り損ねてて、あやふやな記憶で書くのもはばかられるのでうまくご紹介できません、すいません)

本当は、ここからさらに実現可能な形に向けてアイデアを練っていくのでしょうが、今回はここまででした。

にしても、アイデアソン面白かったです。

あとは、なんとかここから頑張って、UDC2019のコンテストに応募することまで持っていきたいと思います。

urbandata-challenge.jp

UDC2019 のコンテストの応募のエントリー締め切りは12月28日(土)なので、ご興味ある方はぜひ応募してみてください。

あ、ちなみに、上記の写真のキーワードやアイデアも使ってもらっても全然かまいませんので、参考になればぜひ活用してください。

リザーブドインスタンスの有効期限切れを通知

表題の件。
リザーブドインスタンス(以下、RI と呼びます)の有効期限切れを通知する機能ですが、結論からすると、AWS コンソールで実現できます。

docs.aws.amazon.com

これですね。

今年(2019年)の5月から提供開始された機能のようです。

AWS Cost Explorer で予約の有効期限切れアラートを提供開始

Cost Explorer を開いて、ドキュメント通り操作すれば、メールを送ってくれます。

f:id:junichim:20191207212242p:plain

設定画面はこんな感じです。

通知を送る日は、7日前、30日前、60日前と決められた選択肢しかありませんが、まあ十分でしょう。 おまけに、組織のマスターアカウントであれば、連結アカウントで運用している RI 期限切れの通知もしてくれます。 願ったりなかったりですね。

というわけで、当初、これを自分でやってみようと思い、あれこれしたのですが、いまや不要になってしまいました(Cost Explorer のデフォルトの通知日数はどうしても嫌だ、とかメールではなく別の方法で送りたい、という場合には役立つかもしれませんが)。

とはいえ、もう組んでしまったので、紹介だけしておきます。

方針

RI の期限切れ、ないと不便だろうなと思い調べてみると、やはり似たようなことを考える人はいるもので、こちらが大変参考になりました。

qiita.com

今回は次のような感じになります。

  • 複数のアカウントをチェック
  • EC2 および RDS のリザーブドインスタンス
  • メールを送信(SNS経由で送信)

以下、実装のご紹介です。

EC2 の RI の有効期限の取得

EC2 の RI の有効期限を取得するのは簡単です。

async function getActiveEC2ReserveInstances(ec2) {
    let param = {
        Filters: [{
            Name: "state",
            Values: ["active"]
        }],
    };
    console.log("getActiveEC2ReserveInstances param: " + JSON.stringify(param));

    return new Promise(function(resolve, reject) {
        ec2.describeReservedInstances(param, function(err, data){
            if (err) {
                console.error("describeReservedInstances: " + JSON.stringify(err));
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

ここで、 ec2 オブジェクトは new AWS.EC2 で生成したものとしています。

いろいろとやり方はあると思いますが、上記では state が active なものに限定して取得しています。 いまから有効期限が切れようとしているものなので稼働中のもののみを対象とするということです。

取得した data.ReservedInstances[].End に RI の有効期限の末日が入っています。

RDS の RI の有効期限の取得

RDS も RI の情報を取得するだけなら簡単なのですが、EC2の場合とことなり、RDSで取得する場合は Filters が使えません。

なので、一旦すべての RI の情報を取得してから、自前で state が active なものを取り出します。こんな感じです。

async function getActiveRDSReserveInstances(rds) {
    let result = [];
    let data = await getRDSReserveInstances(rds);
    for (let instance of data.ReservedDBInstances ) {
        if (instance.State === "active") {
            result.push(instance);
        }
    }
    return result;
}
async function getRDSReserveInstances(rds) {
    return new Promise(function(resolve, reject) {
        rds.describeReservedDBInstances({}, function(err, data){
            if (err) {
                console.error("describeReservedDBInstances: " + JSON.stringify(err));
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

EC2 の場合と似たように、rds オブジェクトは new AWS.RDS で生成したものとしています。

RDS の場合は、Endが直接入っていないので、 StartTime と Duration から End を求めておきます。

async function getRDSReserveInstancesExpireDate(rds) {
    let result = [];
    let instances = await getActiveRDSReserveInstances(rds);
    console.log("active RDS RIs:" + JSON.stringify(instances));

    for (let instance of instances) {
        let edDt = new Date(instance.StartTime);
        edDt.setDate(edDt.getDate() + (instance.Duration / SEC_OF_DAY));

        let res = {
            resoruceType: "RDS",
            reservedInstanceId: instance.ReservedDBInstanceId,
            endDate: edDt,
        }
        result.push(res);
    }
    return result;
}

クロスアカウントアクセスの設定

さて、RI の有効期限が取得できるのはわかったので、異なるアカウントが持つ RI の情報をどう取得するかです。 元ネタとさせていただいた記事にもありますが、これは、クロスアカウントアクセスを設定することで実現できます。

クロスアカウントアクセスってややこしそうですが、まず基本の考えを下記などで紹介されている、IAM ユーザーによるクロスアカウントアクセスで理解しておきます。

わかりやすい記事がたくさんあるので、ここでは設定方法は省略します。

わかりにくいな?と思ったら、一度 AWS コンソール上で実際に試してみるとよく理解できると思います。

Lambda でのクロスアカウントアクセスの設定

さて、上で挙げたクロスアカウントアクセスでは、 あるアカウントの IAM ユーザーが別アカウントのロールに切り替えていました。 Lambda でこれと同じことを行うためには、 IAM ユーザーの代わりに、 Lambda 関数に設定している実行ロールに、切り替え許可(assumeRole)を与えればOKです。

具体的には IAM ユーザーの時と同様に、下記のようなポリシーを定義して、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::別アカウントのアカウントID:role/切り替え先のロール名"
        }
    ]
}

Lambdaの実行ロールに当てればOKです。

Lambda でのクロスアカウント呼び出し

上記の設定ができれば、 Lambda でのクロスアカウント呼び出しが行えるようになります。

RI の情報を取得したい別アカウントの情報を下記のように定義しておきます。

let accountInfo = {
    accountName: "わかりやすいアカウント名",
    accountId: "アカウントID",
    remoteRole: "取得したいアカウントでのロールのARN"
};

これを使い、 STS の assumeRole を呼び、一時クレデンシャルを取得します。

async function getSTSCredential(accountInfo) {
    const sts = new AWS.STS();

    let param = {
        RoleArn: "arn:aws:iam::" + accountInfo.accountId + ":role/" + accountInfo.remoteRole,
        RoleSessionName: "cross_account_by_lambda",
    };
    console.log("getSTSCredential param: " + JSON.stringify(param));
    return new Promise(function(resolve, reject) {
        sts.assumeRole(param, function(err, data) {
            if (err) {
                console.error("getSTSCredential: " + JSON.stringify(err));
                reject(err);
            } else {
                resolve(data.Credentials);
            }
        });
    });
}

一時クレデンシャルが取得できれば、それを使って、AWS.EC2 や AWS.RDS クラスを生成します。

            // STS 経由でcredentialsを取得
            let credentials = await getSTSCredential(accountInfo);
            let param = {
                accessKeyId : credentials.AccessKeyId,
                secretAccessKey : credentials.SecretAccessKey,
                sessionToken: credentials.SessionToken,
            };
            console.log("credentials from STS: " + JSON.stringify(param));

            ec2 = new AWS.EC2(param);
            rds = new AWS.RDS(param);

これらのオブジェクトを使うことで、別アカウント上の RI の情報を取得することができます。

下記記事などにもクロスアカウントでの呼び出し例が載ってますので参考にしてください。

SNS 経由でのメールの送信

取得した RI の期限を調べて、期限が近付いていれば、SNS経由でメールを投げます。

NOTIFY_TO_SNS_TOPIC にSNSのトピック名が設定されており、このトピックのサブスクリプションとして、メールが設定されているとしています。

async function notifyViaSNS(accountName, cand) {
    const sns = new AWS.SNS();

    let param = {
        Subject: "Reserved Instance will be expired soon.",
        Message: 
            "Account Name: " + accountName + 
            "\nResource Type: " + cand.resoruceType + 
            "\nReserved Instance Id: " + cand.reservedInstanceId + 
            "\nEnd date: " + cand.endDate,
        TopicArn: NOTIFY_TO_SNS_TOPIC,
    };
    console.log("notifyViaSNS param: " + JSON.stringify(param));

    return new Promise(function(resolve, reject) {
        sns.publish(param, function(err, data){
            if (err) {
                console.error("publish: " + JSON.stringify(err));
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

上記のように publish を呼び出すことでメールを送ることができます。 Lambda でこれを動かすには、 Lambda のロールにも SNS の publish 呼び出しの権限を与えておく必要があります。

その他実装上の注意点

今回はこの Lambda を動かすアカウントにも EC2 の RI があるので、こちらも併せてチェックするようにしておきます。

あと、自分のアカウント以外にチェックするアカウントに関する情報は配列で持つようにしました。

動作確認

すべて設定できれば、動作確認を行います。 直近に有効期限が切れる RI がないので、200日ほど前をチェックする日時としておきます。

実際に動作させるとちゃんとメールが飛んでくることがわかりました。

最後は、これを CloudWatchEvent から毎日1回呼び出すようにすればOKです。

Gist にコードを載せてありますので、気になる方はチェックしてください。

参考

今年の10月に、EC2について RI の更新予約というのができるようになったそうです。

blog.serverworks.co.jp

今までは、期限切れの前日とかに1日重複されて購入するとか、切れてから少しのタイムラグを挟んで購入するとかでしたが、これを使えば期限切れと同時に自動で購入してくれるとのことです。 いたれりつくせりですね。

QNAP NAS out of memory が起こりました

いまごろ公開してますが、去年(2018年)の12月頃の出来事でした。

2台の QNAP (TS-251+, TS-231P, QTS バージョンはいまとなっては不明ですが、両方ともたぶん 4.3.5.0760 あたりだと思います) で NAS to NAS のバックアップテストをしていたら、 メールに NAS out of memory のエラーメールが飛んできました。

内容を確認すると、

  Simple yet powerful NAS    <http://www.qnap.com/>Go to QNAP  

[Warning][nas01] Hardware Status

NAS Name: nas01
Severity: Warning
Date/Time: 2018/12/02 06:46:02

App Name: Hardware Status
Category: Kernel
Message: [Hardware Status] NAS out of memory. Started kill process: 21074 "wizReq.cgi". Disable some applications to free up memory, or expand the system memory.

<http://www.qnap.com>(c)2018 QNAP Systems, Inc.

というものです。 wizReq.cgi ってのをキーワードにして検索してみると、

WizReq.cgi - high memory usage - QNAP NAS Community Forum

というディスカッションがありました。

どうも、 NAS to NAS でのバックアップに起因するエラーのようです。 このディスカッション先にあるように、 Backup Station から Hybrid backup Sync に切り替えてみて、再度試したところ問題なく動作するようになりました。

取り急ぎ、類似の症状で困ってる人もいるかもしれないのでメモっときます。