定期的にEC2インスタンスを停止・起動する -2-

前回は、EC2インスタンスの起動・停止をするLambda関数を作成しました。

ただ、Lambda関数の環境変数にEC2のインスタンスIDを設定する必要があるというのはあまりにもひどいので、別の方法で対象のEC2を指定できるようにします。

参考にした 定期的に EC2・RDS インスタンスを停止・起動する仕組みの CloudFormation テンプレート | Developers.IO という記事では、EC2にタグを設定しておくことで起動・停止できるようになっていたので、同じようにしてみます。

EC2を見つける

AWS SDK for JavaScript - Describe Instances

Managing Amazon EC2 Instances - AWS SDK for JavaScript を見ると、 EC2を見つけるスクリプトはこんな感じになるようです。

// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region 
AWS.config.update({region: 'REGION'});

// Create EC2 service object
ec2 = new AWS.EC2({apiVersion: '2016-11-15'});

// Call EC2 to retrieve the policy for selected bucket
ec2.describeInstances(params, function(err, data) {
  if (err) {
    console.log("Error", err.stack);
  } else {
    console.log("Success", JSON.stringify(data));
  }
});

ec2.describeInstances()

ec2.describeInstances()paramFilterを設定すると、フィルタリングしたEC2のリストを取得できます。

Filter{Name:`tag:${target-tag-key}`, Values:[target-tag-value]}のような形式で設定すると、タグのKeyとValueが一致するものを検索できるようです。Filter[{Name: 'tag-key', Values:[target-tag-key]}, {Name: 'tag-value', Values:[target-tag-value]}]としてしまうと、タグのKeyが一致するものとValueが一致するものが独立して判定されてしまうようなので注意が必要みたいです。

AWS Lambdaで動かす

対象のインスタンスを探す

まずは、IAMロールを修正します。Lambdaの実行に利用しているロールにec2.describeInstances()を実行するのに必要なポリシーを追加します。

一応、こんな感じのポリシーをアタッチして動いています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:CreateLogGroup"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstance*",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}

最終的に、EC2のTagから対象のEC2を自動的にリストアップしたうえで、EC2を起動・停止できるようにしてみました。

const {promisify} = require('util')
const AWS = require('aws-sdk');

function findEC2Targets(event, ec2) {
  const params = {
    Filters: [{
      Name: `tag:${event["target-tag-key"]}`,
      Values: [event["target-tag-value"]]
    }],
    MaxResults: 10000
  }
  return promisify((p, cb) => ec2.describeInstances(p, cb))(params)
      .then(r => r.Reservations.reduce((acc, reservation) => acc.concat(reservation.Instances.map(i => i.InstanceId)), []))
      .catch(e => { throw e })
}

async function handleEC2(event, ec2) {
  const actions = {
    start: (p, cb) => ec2.startInstances(p, cb),
    stop: (p, cb) => ec2.stopInstances(p, cb),
  }

  const targetInstances = await findEC2Targets(event, ec2)

  if(targetInstances.length) {
    return promisify(actions[event.action])({InstanceIds: targetInstances, DryRun: false})
        .then(r => console.log(JSON.stringify(r, null, '  ')))
        .then(() => targetInstances)
        .catch(e => { throw e })
  } else {
    // return Promise.resolve()
    return Promise.resolve("Target instances are not found.")
  }}

exports.handler = async event => {
  console.log(event)

  return await handleEC2(event, new AWS.EC2())
}

実際にCloudWatchから停止・起動する

実際にCloudWatchから停止・起動できるように設定してみます。

とりあえず、CloudWatchの「ルール」ページから「ルールの作成」を選択して、ルールを作ります。「スケジュール」形式のイベントにして、Cronで実行時間を設定します。

例えば、月曜~金曜の7:00(JST)に実行する場合は0 22 ? * MON-FRI *のような感じです。Cron式で指定する時刻はGMTなので、JSTから9時間引く必要があることに注意してください。

次に、「ターゲット」>「ターゲットの追加」から作成したLambda関数を選択します。「入力の設定」は「定数(JSONテキスト)」を選択して、次のように値をJSONで設定します。

Key Value
action start
target-tag-key AUTO_START_TARGET
target-value-key 0700
{"action": "start", "target-tag-key": "AUTO_START_TARGET", "target-value-key": "0700"}

最後に、start-EC2-at-0700-JSTのような名前で保存すれば、AUTO_START_TARGETという名前で0700が値のタグを設定されたEC2が自動的に起動するようになります。

同じようにして、毎日21:00(JST)にEC2を止めるときは、次のように設定します。

0 12 * * ? *
Key Value
action stop
target-tag-key AUTO_STOP_TARGET
target-value-key 2100
{"action": "stop", "target-tag-key": "AUTO_STOP_TARGET", "target-value-key": "2100"}

これで、一番最初に作ってみたかったLambdaを作ることができました。

Comments

comments powered by Disqus