Help us understand the problem. What is going on with this article?

EC2インスタンスをコールドスタンバイにしてみた

More than 3 years have passed since last update.

Serverlessにも憧れるけれど、いろいろな事情によりEC2を使っているパターンってまだまだ多いと思います。
そしてAWS利用料金の中でEC2インスタンスが占める割合って結構高いですよね。
だいたい1位EC2、2位RDS、3位以下なんかいろいろ・・・というところでしょうか。

EC2インスタンスの台数が結構増えてきて、それに伴い利用料金もうなぎ登りなのでケチケチ大作戦をすることにしました。
もともと検証環境とかビジネスタイムだけ起動していれば良いインスタンスに関しては、自動起動停止するようにしているのですが、本番環境とかでもリソースの使用量をみてインスタンスタイプを下げられないか、とか。
30%OFFぐらいになったらいいなぁ・・・なんて感じでやってます。

そんななか、コイツなんとかしたいなーと思ったのがFTPサーバー。
これもいろいろな理由により、SFTPではなくてFTPなのです。
ただし昼夜問わず、定期的にFTPされるので常時起動は必須。
さらにいうと、design for Failの原則に則って、AZ分けて2台起動しています。

・・・とはいっても1台無駄じゃん(´・_・`)

じゃあ普段は1台停止しとけばいいじゃん!
と思うでしょ?
で、起動している方に何かあったらどーやって停止しているインスタンスを起動させるの?

ということでやってみました。

前提条件

  • EC2インスタンスでFTPサーバ2台構成
  • ELBは無し
  • FTPアクセスはIPアドレスではなくドメイン名でのアクセス
  • 1号機、2号機とあって、通常時は1号機がアクティブ、2号機がコールドスタンバイ(=STOP)
  • EC2インスタンスはAvailabilityZoneを分けて、Multi-AZ構成
  • 1号機になにかがあったら2号機が起動して2号機でFTPサービスを継続する
  • 2号機になにかがあったら1号機が(ry
  • FTP通信はLAN内のみ。インターネットからはこない
  • FTPクライアント側は人間ではなくてシステム。他システムのバッチサーバとかからデータ連携みたいなイメージ
  • 諸事情によりS3へのアップロードはできない

使うもの

  • EC2
  • Route53
    • hostzone
    • health checks
  • CloudWatch
    • CloudWatchAlarm
  • SNS
  • Lambda
  • IAM
    • IAM Role

やること

  1. EC2のCloudWatchAlarmsでEC2の起動状態をSNSに通知
  2. SNS通知を受けたらLambdaキックしてもう片方のEC2インスタンスを起動
  3. Route53でDNSフェイルオーバーさせてドメイン名とIPアドレスの紐付けを変更

cold.png
ね、なんかイケる気がするでしょ\(^o^)/

レシピ

EC2

普通にインスタンスを2台作ってください。

CloudWatch

CloudWatchコンソールでCloudWatchAlarmsを作ります。
メトリックスはStatusCheckFailedを選択してください。

スクリーンショット 2016-10-08 20.44.19.png

ポイントとして、ActionsではNotificationをALARMの場合とINSUFFICIENTの場合の2つ作ること。
何かしらの理由でステータスチェックが失敗した場合はもちろんなのですが、EC2インスタンスがSTOPとなったときはEC2のAlarmStatusは"No Data"となって、CloudWatchAlarm的にはStatusCheckFailedとはならずにINSUFFICIENTとなるからです。

スクリーンショット 2016-10-08 20.09.23.png

ちなみにCloudWatchAlarmsのActionsでNotificationではなくEC2Actionではアラームの状態によって以下の動作を選択できます。

スクリーンショット 2016-10-08 20.19.08.png

ココでrebootとかrecoverとか選んでもいいのでは?と思いますよね。
・・・AZ障害も考慮するとそれだとちょっと弱いと思うので・・・というか何よりもLambdaを使いたかったから!w
エンジニアの趣味嗜好を反映させるのって大切だと思うの。

CloudWatchAlarmsを作成したら入力したメールアドレスにSubscriptionの登録メールが届くはずなので承認しておきます。
たまに迷惑メールに入ってしまっていることもあるので、おかしいなと思ったら迷惑メールフォルダも確認しましょう。
承認するとCloudWatchコンソールで作ったAlarmのところに"Pending confirmation"と表示されているのが消えます。
これをEC2インスタンス2台分作成します。

IAM

IAMコンソールでIAM Roleを作ります。
ManagedPoliciesでAWSLambdaExecuteをアタッチしましょう。
そしてEC2起動のみの権限としたいのでInline Policiesで「ec2:StartInstances」のみのポリシーを作りましょう。
Resourceでは「*(全て)」としましたが、対象リソースを絞っても良いと思います。

InlinePolicies

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmtxxxxxxxxxxxx",
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Lambda

そしてここからがお楽しみのLambdaです。
select blue printはすっとばしちゃいましょう。
configure triggersで先程作ったSNS topicsを選びます。

スクリーンショット 2016-10-08 20.59.11.png

Configure Functionではもう片方のEC2インスタンスを起動させるコードを書きます。
ちなみに私はpythonにしました。jsむずい。
スクリーンショット 2016-10-08 21.18.10.png

コードはこんな感じで。

lambda_handler
def lambda_handler(event, context):
    # TODO implement
    import boto3

    ec2 = boto3.resource('ec2')

    #もう片方(=落ちたときに起動させるほう)のEC2インスタンスID
    id = 'i-xxxxxxxx'

    instance = ec2.Instance(id)
    instance.start()
#return 0 ←SNS通知をトリガーにする場合非同期実行となるため戻り値は無しで

Handlerにはコードに書いた関数名を指定します。
私はガチンコインフラ人でpython初心者のため、よくわかんないけどLambdaでpythonを動かすときはこうするらしいです。

※2016/10/9 追記:
記事公開後にとあるお方からこんなアドバイスをいただきました。

import boto3

    ec2 = boto3.resource('ec2')

    #もう片方(=落ちたときに起動させるほう)のEC2インスタンスID
    id = 'i-xxxxxxxx'

この部分、本当はhandlerの外に書いてもいい
Functionが読まれた時に、handler外にあるのが実行されて
そのあと、本当にkickされたときに、ハンドラーの関数が呼ばれる
ライブラリの初期化とかは、毎回やる必要なくて、最初の一回でいいから、外に出した方がベター
繰り返し沢山呼ばれるやつは特にね
今回のケースだと呼ばれる頻度少ないからどっちでもよい

アプリエンジニアな人にはごく普通のことなのかもしれないけど、目からウロコでしたー
教えてくれてありがとうございます!
なので、それにならうとこんな感じかなっと

lambda_handler
    import boto3

    ec2 = boto3.resource('ec2')

    #もう片方(=落ちたときに起動させるほう)のEC2インスタンスID
    id = 'i-xxxxxxxx'

def lambda_handler(event, context):
    # TODO implement
    instance = ec2.Instance(id)
    instance.start()
#return 0 ←SNS通知をトリガーにする場合非同期実行となるため戻り値は無しで

Roleの項目では先程作ったIAM Roleを選択します。

スクリーンショット 2016-10-08 21.17.42.png

メモリも一番小さい128MBにしちゃいましょう、タイムアウトはデフォルトの3秒だとうまくいかなかったのでMAXの5分にしちゃったけど、もっと短くできるような気がしてます。
起動対象のEC2インスタンスはVPC内に作ってるけど、EC2インスタンス起動コマンドを実行できれば良いだけなのでNo VPCでOKです。
・・・というか、VPCを選んだらなんだかよくわかんなかったので・・誰か教えてください。。

ココまで来たらCreateしちゃいましょう。

できあがったFunctionでテストもできるのでSample Event TemplateでSNSを選んで「Save and Test」ボタンをクリックして上手くいくかやってみましょう。
うまく行ったら実際にEC2インスタンスを停止してもう片方が起動してくるか確認してみましょう。
OKであればもう1台分のLambdaも作りましょう。

Route53

Health Checks

Route53でDNSフェイルオーバーの設定をします。
まずはhealth checksを作ります。
以下のような感じで作ってください。
CloudWarchAlarmsは作ったものを指定しましょう。
ココでのポイントとしてCloudWatchAlarmsを作ったときと同様にHealth check statusのWhen the alarm is in the INSUFFICIENT stateという項目では「the status is unhealthy」を選択してください。
こちらも2台分作ってください。

スクリーンショット 2016-10-08 21.30.00.png

Hosted zones

hosted zonesを作ります。
FTP通信はLAN内のみの通信となりますがDNSフェイルオーバーをするにはpublic通信ができないといけないのでpublic zoneとして作成します。
zoneをpublic zoneにしていても、Aレコードでlocal IPアドレスを入れていたらそのアドレスで返ってくるし、DNSがインターネット側の名前解決ができるようになっていれば、そのクエリ結果でアクセスできちゃうんです。
これが結構便利で、同じドメイン名で社内アクセス専用とインターネットアクセス用のレコードの管理をわざわざpublic zoneとprivate zoneの2つに分けなくても同じhosted zoneで管理できちゃうんです。

そしてレコードセットを2つ作ります。
2つのIPアドレスに同じ名前のAレコードを設定します。
Routing PolicyはFailoverとします。

1号機用のは以下のような感じで、Failover Record TypeはPrimary, health check AssosiateではRoute53 health checksで作成したヘルスチェックを指定します。

スクリーンショット 2016-10-08 21.46.37.png

2号機用のは以下のように、今度はFailover Record TypeはSecondaryとします。
レコード名は1号機と同じものにします。

スクリーンショット 2016-10-08 21.49.05.png

1号機と2号機のIPアドレス、health checksを入れ違いにしないように注意しましょう。

フェイルオーバーさせてみる

これで、1号機が起動、2号機が停止状態で1号機が落ちたら2号機が起動してきて、次に2号機が落ちたら1号機が上がってきてnslookupの結果もそれぞれ起動してきた方のIPアドレスが返ってくるはずです。

試したところ・・・切替えが完了してDNSフェイルオーバーしたアドレスが返ってくるまで約10分かかっちゃいました。。。
CloudWatchAlarmsのthresholdやAレコードのTTL、Lambdaのタイムアウト値をもっと短くしたらもっと短縮できるのかなー。
それか、10分程度の断なら許容範囲としてリランで対応できるようにしてしまうかw

FTPサーバだけではなく、バッチサーバとかでも10分程度の断なら許容できるけど長時間止まってるのはマズいってサーバならアリかもですね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした