AWS CLIでもっと快適に開発環境を起動する

f:id:watass:20141103203916j:plain

最近、時間がないことを理由に開発環境構築だけで、私的にプログラムを全然書いていないのですが、忙しさを理由に平日のコーディングをサボってはいけないなと思えてきました。では、仕事を終えてコードを書こうと決心すると、

  1. AWSマネージメントコンソールにログイン
  2. Authyでワンタイムパスワードを確認して入力
  3. EC2メニューにアクセスして、インスタンスを起動
  4. ステータスチェックを終えたらパブリックDNSを確認
  5. Route53メニューにアクセスして、対象レコードのCNAMEをパブリックDNSで更新

という流れになりますが、まぁ面倒臭い!
これでは平日に帰ってきてからコード書く気力も失ってしまいます。そこで、今回はこの手順をAWS CLIを使うことで、コマンド一発で済ませるようにしてみます。

使用しているバージョン

今回はクライアントPCからAWS上のインスタンスに操作をします。
各バージョンは以下の通りです。

クライアントPC

AWSインスタンス

AWS CLIインスタンスを起動

まず、EC2インスタンス起動までの手順は明らかに冗長ですよね。別にマネージメントコンソールにログインしなくても、ただインスタンスを起動するだけなら、AWS CLIで簡単にできます。導入や使用例は以下の記事で。


start-instanceコマンドで起動してみる

では早速、クライアントPCからインスタンスの起動をやってみましょう。

$ aws ec2 start-instances --instance-ids [インスタンスID]
{
    "StartingInstances": [
        {
            "InstanceId": "[インスタンスID]", 
            "CurrentState": {
                "Code": 0, 
                "Name": "pending"
            }, 
            "PreviousState": {
                "Code": 80, 
                "Name": "stopped"
            }
        }
    ]
}

はい。起動できました。
冒頭で挙げた手順と比較すればだいぶ楽ですが、それでもインスタンスID直打ちって面倒臭いですね。いちいちインスタンスIDなんか覚えてられませんし。
せっかくインスタンスにはNameタグを付与しているので、それをベースにインスタンスの起動とかできないものかと模索してみたのですが、調べた限りでは、残念ながらそういった機能はなさそうでした。

さて、無いなら書きましょう。エンジニアなら当然ですよね。

Nameタグの値からインスタンスIDを取得

まず、実現するためにはNameタグの値からインスタンスIDを取得する手順が実現できなくてはいけません。幸いなことに、インスタンスの情報を取得するdescribe-instancesコマンドがあるので、これを使います。
オプションとして--filtersを設定することで、条件に合致するインスタンスの情報だけを取得することができます。今回はNameタグの値がapp1というインスタンスの情報を取得します。

$ aws ec2 describe-instances --filters "Name=tag-key,Values=Name" --filters "Name=tag-value,Values=app1"
{
    "Reservations": [
        {
            "OwnerId": "XXXXXXXXXX", 
            "ReservationId": "r-XXXXXXX", 
            "Groups": [], 
            "Instances": [
                {
                    "Monitoring": {
                        "State": "disabled"
                    }, 
                    "PublicDnsName": null, 
                    "RootDeviceType": "ebs", 
                    "State": {
                        "Code": 80, 
                        "Name": "stopped"
                    }, 
                    "EbsOptimized": false, 
                    "LaunchTime": "2015-02-15T08:13:32.000Z", 
                    "PrivateIpAddress": "XXX.XXX.XXX.XXX", 
                    "ProductCodes": [], 
                    "VpcId": "vpc-XXXXXXX", 
                    "StateTransitionReason": "User initiated (2015-02-15 11:07:50 GMT)", 
                    "InstanceId": "i-XXXXXXX", 
                    "ImageId": "ami-XXXXXXXX", 

                    /* 中略 */

                    "VirtualizationType": "hvm", 
                    "Tags": [
                        {
                            "Value": "app1", 
                            "Key": "Name"
                        }
                    ], 
                    "AmiLaunchIndex": 0
                }
            ]
        }
    ]
}

aws configurationでデフォルトの返り値フォーマットはJSONに指定しています。しかし、情報量が多くてわかりにくいですね。JSONのパースが必要です。
パースといえば、rubyやらpythonやらでスクリプトをガリガリ書くべきかもしれませんが、jqコマンドという便利なコマンドがあるらしいので、これを使います。brewコマンドでインストール。

$ brew install jq

jqコマンドを使うと、コマンドベースでJSONのパースができますので、describe-instancesコマンドの返り値をパイプで繋いでパースします。こんな感じ。

$ aws ec2 describe-instances --filters "Name=tag-key,Values=Name"  --filters "Name=tag-value,Values=app1" | jq -r '.Reservations[].Instances[].InstanceId'

インスタンスIDの情報はJSONのReservations.Instance.InstanceIdに格納されるので、jqコマンドでこの値を指定します。出力にダブルクオーテーションが入ってくると邪魔なので、-rオプションを使います。
これでNameタグ名を入力することでインスタンスIDを返すスクリプトができました。

Nameタグの値からインスタンスを起動する

さて、後はこのインスタンスIDを使ってstart-instancesコマンドを実行するだけです。xargsとか使えばワンライナーでも書けるのですが、いちいち入力するのも面倒臭いほど長いコマンドになるので、bashスクリプトにまとめました。こんな感じです。

#!/bin/bash
# example: sh instance-controler.sh [action] [name]
# action -> [start,stop]
# name -> ec2 tag name
if [ $# -ne 2 ]; then
        echo "Error: Invalid argument count." 1>&2
        exit 1
fi
action=$1
name=$2
id=`aws ec2 describe-instances \
      --filters "Name=tag-key,Values=Name" \
      --filters "Name=tag-value,Values=${name}" | jq -r '.Reservations[].Instances[].InstanceId'`
 
if [ ${id} ]; then
        if [ ${action} = "start" ]; then
                aws ec2 start-instances --instance-ids ${id}
        elif [ ${action} = "stop" ]; then
                aws ec2 stop-instances --instance-ids ${id}
        else
                echo "Error: Invalid action." 1>&2
                exit 1
        fi
else
        echo "Error: ${name} instance not found." 1>&2
        exit 1
fi

極めて簡単なスクリプトですが、一応停止もできるようにしてあります。実行が成功すれば、通常のstart-instancesやstop-instancesの返り値が標準出力で返ってきます。入力されたNameタグの値を検証せずにdescribe-instancesコマンドに渡すのはちょっと気持ち悪いですが、あくまでも個人利用専用なので、まぁよしとします。

こちらのスクリプトは一応Gistにあげておきますので、何かご指摘ありましたら、コメントいただけると嬉しいです。

cli53でRoute53のレコードを更新

さて、インスタンスの起動だけなら、このように単純なのですが、問題はここから。そう、起動したインスタンスのパブリックDNSを如何にしてRoute53に自動で登録させるかですね。
とはいえ、Route53に対する操作もAWS CLIを用いればできるはずですから、そこまで難しくないはず。AWS CLIを使ってもできるのですが、用意されている機能では、レコードの更新のためにJSONを用意して、それをインポートするような形にならざるを得ないようで、ちょっと面倒臭いです。
そこで、単純なコマンドでRoute53に対する操作ができるcli53を使って、レコード更新を自動化してみましょう。

cli53のインストール

cli53はGitHubで公開されているスクリプトです。GitHubからバイナリを落としてきてもよいのですが、pipでインストールしたほうが楽そうなので、pipでインストールしましょう。

$ sudo pip install cli53

helpを見てインストールできたか確認

$ cli53 --help
usage: cli53 [-h] [-d] [--logconfig LOGCONFIG]
             
             {info,xml,create,list,instances,export,rrdelete,rrcreate,import,rrlist,rrpurge,delete}
             ...

route53 command line tool

positional arguments:
  {info,xml,create,list,instances,export,rrdelete,rrcreate,import,rrlist,rrpurge,delete}
                        sub-command help
    list                list hosted zones
    info                get details of a hosted zone
    export              export dns in bind format
    import              import dns in bind format
    instances           dynamically update your dns with instance names
    create              create a hosted zone
    delete              delete a hosted zone
    rrcreate            create a resource record
    rrdelete            delete a resource record
    rrpurge             purge all resource records
    rrlist              list all resource records

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           Turn on debugging
  --logconfig LOGCONFIG
                        Specify logging configuration

よさそう。

自身のインスタンスのパブリックDNSを取得する

まず、インスタンス上で自身のパブリックDNSを取得する方法を確認します。
AWSでは、パブリックDNSなどのインスタンスメタデータを取得する方法が公式に案内されています。以下のように、URLをcurlコマンドなどで叩けば取得できます。

$ curl -s http://169.254.169.254/latest/meta-data/public-hostname

これで返り値が実行したインスタンスのパブリックDNSになります。接続先のIPアドレスは固定らしいですね。
後は、この値を引数に、cli53コマンドを叩くだけです。

cli53で自身のパブリックDNSをRoute53に登録する

cli53コマンドを使うと、以下のようなコマンドでRoute53のレコード更新ができます。

$ cli53 rrcreate example.com www CNAME [パブリックDNS名] -x 60 -r

rrcreateはレコードの作成を行う明示です。第1引数にドメイン、第2引数にサブドメイン、第3引数に種別、第4引数にセットする値を記述します。-xオプションはTTLの設定で、-rオプションはレコードを更新するという意味です。

これでRoute53のレコード更新ができますので、パブリックDNS取得とあわせて、パイプで繋いでやれば、自分のパブリックDNSをRoute53に登録するスクリプトのできあがりです。

$ curl -s http://169.254.169.254/latest/meta-data/public-hostname | xargs -i cli53 rrcreate example.com www CNAME {} -x 60 -r --replace

あ、もちろん、対象のインスタンスにはRoute53に対するフルアクセス権限をIAM roleで指定してあげないとダメですよ。僕はIAM roleをNoneにしてインスタンスを作成してしまっていたので、AMI Imageとって、最初から作りなおしました。こういった作業も一瞬でできちゃうのはいいことですね。

crontabに登録して、起動時にパブリックDNSをRoute53に自動登録

このコマンドをインスタンス起動時に実行させるだけです。crontabの@rebootに記載します。

$ crontab -e
@reboot curl -s http://169.254.169.254/latest/meta-data/public-hostname | xargs -i cli53 rrcreate example.com www CNAME {} -x 60 -r --replace

これで起動時に自動でRoute53にパブリックDNSを登録してくれます!
あれだけ複雑だった手順が

$ sh instance-controller.sh start app1

だけで解決するようになったわけですから、非常に有益だと言えるでしょう。

まとめ

  • AWS CLIインスタンス起動がコマンド一発で
  • ネームタグからインスタンスIDを取得できればネームタグベースで起動もできる
  • cli53をcronの@rebootに仕込めば起動時にパブリックDNSをRoute53に自動登録できる

これで毎日のコーディングが楽になりますね

やはり面倒なオペレーションは簡単する努力をしないといけませんね。ただ、あまりこういった作業に手間をかけ過ぎると開発が進まないという問題はありますが・・・
環境はとりあえず構築できたので、バリバリFuelPHPでコード書いていきます!

参考

aws-cli ec2 describe-instances --filterオプションでタグの値を指定する
# describe-instancesコマンドでハマったときに助けていただきました。
jq コマンドを使う日常のご紹介 - Qiita
# jqコマンドについて。否定論もあるようですが、あれば便利だなと思います。
jq コマンドが強力すぎてヤバい件 | CUBE SUGAR STORAGE
# jqコマンドで色々できるよという話。大変参考になりました。
barnybug/cli53 · GitHub
# cli53のGitHubページ。基本的な導入方法や使用方法はこちらで。
EC2インスタンス起動時にRoute53のリソースレコードを更新する - azuki note
# Route53の自動登録はほぼこちらの記事を参考にさせていただきました。ありがとうございます。
cli53 | インフラ関係のメモ書き
# cli53の操作方法で色々参考になりました。