LoginSignup
22
23

More than 5 years have passed since last update.

AWS Lambdaでスポットインスタンスを作成して、UserDataで環境構築や長い処理を自動で実行させる

Last updated at Posted at 2015-09-30

「スポットインスタンスを作成 -> 環境構築 -> 長い処理を実行する」なんてことを、「マネージメントコンソールからUIをポチポチ、SSHでコマンド流し込んで」と手作業でやってたんですが。

毎度同じことをやるのは面倒、ということで、Lambdaで自動化してみました。

Code for Spot Instance creation

長い処理を行うために別途書いたシェルスクリプトを、s3://mybucket/sh/run.shに予め置いてあるという前提で。

console.log('Loading function');

var s3Bucket = 'mybucket';
var shellScriptS3Key = 'sh/run.sh';
var shellScriptS3Path = 's3://' + s3Bucket + '/' + shellScriptS3Key;

var region = 'ap-northeast-1';
var availabilityZone = 'ap-northeast-1a';
var spotPrice = '0.05';
var imageId = 'ami-9a2fb89a';
var instanceType = 'r3.large';

var iamInstanceProfile = 'my_ec2_instance_role';
var securityGroup = 'launch-wizard-1';
var keyName = 'mysshkeypair';

var userData = (function () {/*#!/bin/bash
yum -y update
aws s3 cp %s /home/ec2-user/run.sh
chown ec2-user /home/ec2-user/run.sh
chmod +x /home/ec2-user/run.sh
su - ec2-user /home/ec2-user/run.sh
*/}).toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];

exports.handler = function(event, context) {
    var util = require('util');
    var AWS = require('aws-sdk');
    AWS.config.region = region;

    var userDataFormatted = util.format(userData, shellScriptS3Path);
    var userDataBase64 = new Buffer(userDataFormatted).toString('base64');

    var ec2LaunchParams = {
        SpotPrice: spotPrice, 
        LaunchSpecification : {
            IamInstanceProfile: {
              Name: iamInstanceProfile
            },
            ImageId: imageId,
            InstanceType: instanceType,
            KeyName: keyName,
            Placement: {
              AvailabilityZone: availabilityZone
            },
            SecurityGroups: [
                securityGroup
            ],
            UserData: userDataBase64
        }
    };

    var ec2 = new AWS.EC2();
    ec2.requestSpotInstances(ec2LaunchParams, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            context.fail('[Fail]');
        }
        else {
            console.log(data);
            context.succeed('[Succeed]');
        }
    });
};

Notes

UserData

Userdataは、ヒアドキュメントでインラインに書いちゃってます。以下を参考にさせてもらいました。

SafariでもエラーにならないJavascriptのヒアドキュメントの書き方
http://qiita.com/ampersand/items/c6c773ba7ae9115856d0

あと、注意点としては、Base64にエンコードしてやる必要があるってことですかね。

IAM role for EC2 instance profile required

起動中のEC2内でS3のファイルをダウンロードするには、S3へのアクセス権を振ったIAMロールをインスタンスプロファイルとして設定する必要があります。IAMロールを適当に作って、以下を書き換えてください。

var iamInstanceProfile = 'my_ec2_instance_role';

Security Group

セキュリティグループの指定は必須ではないはずですが、launch-wizard-xxが増えるのもアレなので、既存のを使っちゃうのを推奨しておきます。

var securityGroup = 'launch-wizard-1';

Instance Storage

インスタンスストレージの設定、API documentを見るに、「BlockDeviceMappings」の「VirtualName」で指定と言っているような感じでしょうか?

BlockDeviceMappings: [
    {
         VirtualName: 'ephemeral0'
    }
],

EC2 requestSpotInstances - AWS SDK for JavaScript
http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/EC2.html#requestSpotInstances-property

まあ、SSD付/EBSのみで切り替えるの面倒で、上記のコードでは試していないわけですが。

Terminate Instance after processing - with aws cli

長い処理終わったあと、インスタンスの終了まで自動化したい場合、

  1. インスタンスメタデータで自分自身のインスタンスIDを拾う
  2. aws cliで止める

ってのが、お手軽ですかね。具体的にはこんな感じで。

response=$(curl http://169.254.169.254/latest/meta-data/instance-id)
export AWS_DEFAULT_REGION=ap-northeast-1
aws ec2 terminate-instances --instance-ids $response

EC2のインスタンスプロファイルとして設定するIAMロールに、EC2の操作をする権限の追加が必要にはなりますが。

Terminate Instance after processing - with SNS and Lambda

インスタンスの自動終了については、SNSを飛ばしてLambdaで殺す方が便利ってば便利ですね。長い処理の途中でWarningが出てるのを見つけてしまって、EC2を終了させずに調査したいとかいうときに、SNSとLambdaの連携を切ればよくなるので。

適当にSNS Topic作って、instance-idをmessageに含めつつSNS飛ばす。

current_instance_id=$(curl http://169.254.169.254/latest/meta-data/instance-id)
export AWS_DEFAULT_REGION=ap-northeast-1
aws sns publish --topic-arn arn:aws:sns:ap-northeast-1:xxxxxxxxxxxx:KillEc2Request --subject "kill instance request" --message $current_instance_id

んで、このSNSメッセージを受けるLambdaは以下。

console.log('Loading function');

exports.handler = function(event, context) {
    var message = event.Records[0].Sns.Message.toString();
    console.log('[Start] EC2 Instance kill: ', message);

    var AWS = require('aws-sdk');
    AWS.config.region = 'ap-northeast-1';
    var ec2 = new AWS.EC2();
    var params = {
        InstanceIds: [
            message
        ]
    };

    ec2.terminateInstances(params, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            context.fail('[Error] EC2 Instance kill');
        }
        else {     
            console.log(data);
            context.succeed('[Done] EC2 Instance kill');
        }
    });
};

リージョンを直で書いちゃってるので東京以外でうごかねーじゃんと自分でも思いましたが、その辺は必要に応じて適当に弄ってください。

22
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
23