社内でslackから開発機とか起動できたら楽だよね。という会話をしていて実際に作ってみました。
やること
- slackのOutgoing Webhooksを使って特定のチャンネルからEC2インスタンスの一覧取得、起動、停止を行う。
- 起動と停止は開発機など一部のインスタンスに限定する。
動作環境
- Serverless Framework v1.7.0
- nodejs v4.3.2
- slack
Outgoing Webhooksの設定
https://your-team.slack.com/apps/manage/custom-integrations
のOutgoing WebhooksからAdd Configurationで作成。
- Channel:投稿を取得するチャンネル
- Trigger Word(s):webhooksを呼び出す単語。今回は
$server
としています - URL(s):送信先になるURL。一旦は仮置きで問題ありません。
を設定します。
Tokenは今回作成したIntegrationから送信されたものかを確認するためのものになります。
POSTされるデータは
token=1IA4G7C3ay7CRVdZi0kGGkwO
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
timestamp=1355517523.000005
user_id=U2147483697
user_name=Steve
text=googlebot: What is the air-speed velocity of an unladen swallow?
trigger_word=googlebot:
といった形式になります。tokenの検証とtextのパースを行えば良さそうです
APIGateway+Lambdaの設定
serverless frameworkを使ってこれらを構築します。
serverless.yml に受信するためのLambdaを記述します。
functions:
slack:
handler: slack/handler.slack
events:
- http:
path: slack
method: post
今回は /slack
というパスでリクエストを受け付けることにします。
handler
に記述したように、 slack/handler.slack
というファイルを作成します。
module.exports.slack = (event, context, cb) => {
return cb(null, JSON.stringify(event));
}
ここまでで一度デプロイしてendpointを作成します。
% serverless deploy -s dev
このように作成されるので、作成されたURLをOutgoing WebhooksのURL(s)に設定します。
Lambdaの実装
aws-sdkを使ってec2インスタンスの一覧取得、起動、停止をする処理を記述していきます。
インスタンス一覧の取得
'use strict'
const aws = require('aws-sdk');
const ec2 = new aws.EC2({region: 'ap-northeast-1'});
module.exports.describeInstances = function*() {
try {
const data = yield ec2.describeInstances().promise();
return data;
} catch (err) {
throw err;
}
}
これでインスタンスの取得ができます。
戻り値そのままだと余計な情報も多いのでインスタンスID、Nameタグ、stateのみを取得することにします。
function parseInstanceObj(data) {
let instances = [];
data.Reservations.forEach(reservation => {
reservation.Instances.forEach(instance => {
let name = 'unknown server';
if (instance.Tags.length > 0) {
name = instance.Tags.reduce((prev, current) => {
return current.Key === 'Name' ? current.Value : prev;
}, instance.Tags[0].Value);
}
console.log('server name: ', name);
instances.push({
instanceId: instance.InstanceId,
state: instance.State.Name,
name : name
});
});
});
return instances;
}
これで、インスタンスID、Nameタグ、インスタンスの状態(running、stopped、pendding)のみを取得できました。
インスタンス一覧を返すレスポンスの作成
インスタンスの情報が取得できたので、slack側へ返すレスポンスを作成します。
Responding に書いてあるように
{
"text": "African or European?"
}
形式のレスポンスを返します。
'use strict';
const co = require('co');
const _ = require('lodash');
const ec2 = require('ec2');
module.exports.slack = (event, context, cb) => {
co(function*() {
const result = yield ec2.describeInstances();
let text = '';
result.forEach(instance => {
text = `${text}${instance.name}: ${instance.state}\n`;
});
const response_body = {
text: text
};
const response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(response_body)
}
return cb(null, response);
});
}
このLambdaをデプロイ後、slack上で $server
と発言すると、
dev-web01: running
などとslack上で結果が得られます。
起動、停止の権限制御
一覧が取得できるようになったので、サーバ名ベースで起動や停止を行えるようにします。
ただ、slack上から気軽に本番環境のインスタンスなどを停止されてしまうと困るので、
slack
というタグがついているインスタンスのみを起動、停止の対象とします。
以下のようにすることで、Lambdaに制限をかけられます。
iamRoleStatements:
- Effect: "Allow"
Action:
- "ec2:describe*"
Resource:
- "*"
- Effect: "Allow"
Action:
- "ec2:RebootInstances"
- "ec2:StartInstances"
- "ec2:StopInstances"
Resource:
- "arn:aws:ec2:*"
Condition:
"ForAnyValue:StringEquals":
"ec2:ResourceTag/slack": ""
Conditionで ec2:ResourceTag
を指定してやることでタグ単位で操作の制御が可能になります。
Nameタグベースでのインスタンス情報取得
インスタンスIDをPOSTすれば処理は楽なのですが、インスタンスIDを覚えるのは大変なのでNameタグベースで起動、停止を行えるようにします。
module.exports.describeInstancesByName = function*(name) {
if (!name) throw new Error();
const params = {
Filters: [
{
Name: 'tag-key',
Values: ['Name']
},
{
Name: 'tag-value',
Values: [name]
},
{
Name: 'tag-key',
Values: ['slack']
}
],
DryRun: false
}
try {
const data = yield ec2.describeInstances(params).promise();
return parseInstanceObj(data);
} catch (err) {
throw err;
}
}
こうしてやることで、Nameタグに指定された値を持ち、slackというタグがついているインスタンス情報の取得ができます。
あとはこの戻り値を元に startInstances
、stopInstances
を呼出してあげればいいことになります。
確認
slack上で $server status
と発言することでインスタンスの一覧を返してくれるようになりました。
あとはこの一覧ベースで $server [server name] start
などをしてあげれば任意のサーバの起動などもできます。
コマンドの使い方は
$server (status | help | [server name] start | [server name] stop)
といった風にしてあります。
最後に
かなり飛び足でしたが、slack経由でec2インスタンスを操作する方法を紹介しました。
サーバレスアーキテクチャに触れるための第一歩としてこういったツール群を作ってみても面白いかもしれません。
今回作成したコードはgithubでも公開していますので、何かの参考になればと思います。