0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

URLパラメータで指定したEC2の起動・停止

Last updated at Posted at 2021-09-19

最小限の手順でサクッと構成したい

【2023/2/9更新】※API Gatewayを使わない方法を赤で追記しました。コードの見直しもしています。

「EC2の起動とか停止とかをURLアクセスでできないかね。URLパラメーターでサーバー名を指定して(Nameタグで指定して)起動する、みたいな。」
という要望は、EC2の起動・停止を手動で運用している環境で生じていると思います。
実現する投稿は多くあるけれども、セキュリティや拡張性も考えて実装している例が多く(まあそうするのが当然なので)、その結果として難しくなってしまい、やっぱコンソールで手動操作でいいやとあきらめてしまうことも考えられます。
ちょっともったいないですね。

そこで、API Gateway + Lambda を使用して API Gatewayを使わずLambdaのみで できるだけ少ない手順で 実装してみます。
入力値や変更する選択肢は全て書き起こしてみます。
・EC2は全て ap-northeast-1(東京リージョン)にある前提です。

※AWSでEC2の”起動”はローンチのことを指しますが、ここでは最初のセリフのように”起動”を一般的な使われ方(AWSでは”開始”に当たる)で使っています。

では進めましょう。

1.IAMポリシーの作成

IAM > ポリシー > ポリシーを作成 ボタン
ビジュアルエディタは使いません。"ビジュアルエディタ" タブの右にある "JSON" タブを押します。
以下をコピーしてペーストします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}
  • ポリシー名:ec2startstop (適当な名前を付けます)

2.IAMロールの作成

IAM > IAMロール > ロールを作成 ボタン

  • ユースケースの選択:Lambda
  • アタッチするポリシー:ec2startstop (さっき作ったもの)、AWSLambdaBasicExecutionRole
  • ロール名は ec2startstop とします。(ポリシー名と同じ名前でもエラーになりません)

3.Lambdaの作成

Lambda > 関数 > 関数を作成 ボタン

  • オプション:一から作成
  • Lambda関数名:ec2startstop (他のリソースと同じ名前でもエラーになりません)
  • ランタイム:Python 3.x
  • デフォルトの実行ロールの変更-既存のロールを使用する: ec2startstop

関数の作成ボタンを押す

コード(元のコードを消して、以下のコードをコピペして上書きします。)

lambda_function.py
import boto3
import re

ec2 = boto3.client('ec2')

def lambda_handler(event, context):

    ret_data =  {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': { 'Content-Type': 'text/html' }
    }
    access_ok = False

    if isinstance(event.get('requestContext', {}).get('http', {}).get('sourceIp'), str):
        for ip_prefix in ['1', '2', '3', '4', '5', '6', '7', '8', '9' ]:
            if event['requestContext']['http']['sourceIp'].startswith(ip_prefix):
                access_ok = True
                break

    if not access_ok:
        # アクセス元のIPアドレスがプレフィックスのリストのいずれかと一致しなければ中断
        return dict( { 'body': 'Source IP permission error' }, **ret_data )

    instance_ids = []
    param = event.get('queryStringParameters')

    if param == None or not re.match(r'^(start|stop):[^:]+:[^:]+$', param.get('command', '')):
        # URLパラメーターの最小限のチェックでエラーがあれば中断
        return dict( { 'body': 'URL parameter error' }, **ret_data )

    # URLパラメータ(例:command=start:Name:ServerA01)からコロン区切りで切り出す
    start_stop, tag_key, tag_value = param['command'].split(':')[0:3]

    try:
        ret_describe = ec2.describe_instances(
            Filters=[ { 'Name': 'tag:'+tag_key, 'Values': [tag_value] } ]
        )
    except Exception as e:
        return dict( { 'body': f'API error (Exception: {str(e)})' }, **ret_data )

    # 指定したタグキーと値の組合せに合致するEC2のインスタンスIDをリスト化する
    for rsv in ret_describe['Reservations']:
        for inst in rsv['Instances']:
            instance_ids.append( inst['InstanceId'] )

    ret_data['body'] = f"{param['command']} is valid. "

    if instance_ids == []:
        # 該当EC2が見つからなかったので中断
        ret_data['body'] += 'instance not found error'
        return ret_data
    elif len(instance_ids) >= 7:
        # 該当EC2が多すぎるので中断(セーフティネット)
        ret_data['body'] += 'too many instances error'
        return ret_data

    try:
        if start_stop == 'start':
            ec2.start_instances( InstanceIds = instance_ids )
        else:
            ec2.stop_instances( InstanceIds = instance_ids )

        ret_data['body'] += f'{start_stop}_instances called {str(instance_ids)}'

    except Exception as e:
        ret_data['body'] += f'API error (Exception: {str(e)})'

    return ret_data

[2023/2/9] 上記のコードは、主に4点を更新しました。
正規表現を更新(絞り込みが粗かったため) r'(start|stop):.*:.*'r'^(start|stop):[^:]+:[^:]+$'
アクセス元IPのプレフィックスで許可するようにコードを追加
 (上記コード内では ['1', '2', '3', ・・・, '9' ] として全てのIPを許可にしています。適宜修正ください)
API実行時のExeptionを取得して中断する
セーフティネットとしてインスタンスが7個以上の場合失敗させる
 (上記コード内では >= 7 としています。適宜修正ください)


  • 一般設定 - タイムアウト:3秒 → 10秒 に延ばしておきます
  • 関数URL - 「関数URLを作成」ボタンを押す
    • 認証タイプ:None

関数URL「 https:~xxxxx.lambda-url.xxxxx.on.aws/ 」のリンクが生成されたことを確認します。

4.Lambdaのテスト

関数URL「 https:~xxxxx.lambda-url.xxxxx.on.aws/ 」のURLにアクセスします。
Webブラウザに URL parameter error が返ってくればOKです。
以下も合わせて実施してもよいです。

コードが正しく動作するか一応テストします。
テストタブで、テストを作成します。

{
    "requestContext": { "http": { "sourceIp": "12.34.56.78" } },
    "queryStringParameters": { "command":"start:Name:dummy" }
}

コードの修正に合わせてテストデータに $.requestContext.http.sourceIp とダミーのIPを追加しています。
上記をインプットのテストデータとして、変更を保存します。
テストボタンを押します。結果に instance not found があればOKです。
dummy を存在するEC2のNameタグに変更して保存します。(ServerA01 が存在するなら dummy を ServerA01 に)
再度テストボタンを押します。結果に start_instances called [i-xxxxxxxxxxxxxxx] があればOKです。

5.API Gatewayの作成

関数URLを使う場合は、この API Gatewayの作成は不要です。一応、引用の形式で残します。

API Gateway > APIを作成 > REST API
プロトコル: REST
新しい API の作成: 新しい API
API名: ec2startstop
APIの作成ボタンを押します。

アクション-メソッドの作成 を押すとブランクのボックスが現れるので GET を選択
GET をクリックすると GET - セットアップ 画面になります。
統合タイプ: Lambda 関数
Lambda プロキシ統合の使用: チェックを入れる
Lambda 関数: ec2startstop
保存ボタンを押します。
Lambda 関数に権限を追加する のダイアログでOKボタンを押します。

アクション-APIのデプロイ を押すとダイアログが表示されます。
デプロイされるステージ: [新しいステージ]
ステージ名: s ※何でもいいのですがURLの一部に入ってきますのでそのつもりで
デプロイ ボタンを押します。

ステージエディタの画面で
URL の呼び出し: https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/s
が表示されます。
xxxxxxxx はランダムで割り当てられます。

/s の後にパラメータを付与しないままURLをWebブラウザーで呼び出してみましょう。
URL parameter error
と表示されたらOK。API Gatewayを経由してLambdaが実行されたことになります。

6.URLの組み立て

URLの後ろに
?command=<start または stop>:<タグのキー名>:<タグの値>
を付与してURLを組み立てます。

例:
Nameタグの値が ServerA01 のEC2を起動・停止する場合
https:~xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/s?command=start:Name:ServerA01
https:~xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/s?command=stop:Name:ServerA01
https:~xxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/?command=start:Name:ServerA01
https:~xxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/?command=stop:Name:ServerA01

Envタグの値が Development のEC2をまとめて起動・停止する場合
https:~xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/s?command=start:Env:Development
https:~xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/s?command=stop:Env:Development
https:~xxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/?command=start:Env:Development
https:~xxxxxxxxxx.lambda-url.ap-northeast-1.on.aws/?command=stop:Env:Development

7.実行

組み立てたURLでアクセスします。
EC2の起動・停止ができたでしょうか。


この方法で実装されたURLは、学習のためと割り切ってください。
セキュリティも何もありません。 送信元IPによるアクセス制御が可能です。
URLが意図せずに第3者に漏れてしまったら、、、勝手にOSを停止されたりして大変ですので。

0
2
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?