シェルスクリプトで Multiple Subnets in VPC

AWS Advent Calender 2012 の15日目担当の @satotech です。


最近は、 AWS CloudFormationAmazon Virtual Private Cloud(Amazon VPC) の話題が多いですね。
CloudFormationは、ホントに便利ですよね!


今回は、シェルスクリプトVPCの構築をしてみたいと思います。
次のようなイメージのVPCを構築します。

複数のAZにそれぞれ複数のサブネットを持つVPCを作成します。

1つ目はPublicサブネットで、Elastic IP を取得、セキュリティグループを作成してEC2 インスタンスを起動します。
2つ目はPrivateサブネットで、バックエンドのEC2 インスタンスやRDS用になります。

手順

  • 10.0.0.0/16 で VPC を作成
  • Zone A に PublicサブネットA 10.0.10.0/24 を作成
  • Zone B に PublicサブネットB 10.0.11.0/24 を作成
  • インターネットゲートウェイを作成
  • Publicサブネットの Route Table を作成
  • Publicサブネットに NetworkACL(NACL) を定義
    • Inbound用にHTTPポートを許可
    • Inbound用にSSHポートを許可
    • Inbound用に1024から65535のダイナミックポートを許可
    • Outbound用にHTTPポートを許可
    • Outbound用にHTTPSポートを許可
    • Outbound用に1024から65535のダイナミックポートを許可
  • Publicサブネットに対してNACLを関連づけ
  • Zone A にPrivateサブネットA 10.0.20.0/24 を作成
  • Zone B にPrivateサブネットB 10.0.21.0/24 を作成
  • Privateサブネットに NACL を定義
    • Inbound用にVPC内との全ポートを許可
    • Outbound用にVPC内との全ポートを許可
  • Publicサブネット用にセキュリティグループを定義
    • Inbound用にHTTPポートを許可
    • Inbound用にSSHポートを許可
  • インスタンスを起動
    • タグを設定
  • EIPを取得

シェルスクリプト

main.sh
#!/bin/bash

source ~/aws/export.cfg
source ~/aws/functions.sh

REGION="ap-northeast-1"
ZONE_A="${REGION}a"
ZONE_B="${REGION}b"
OPTS="--region ${REGION} -H --show-empty-fields"
SSH_PORT="22"
GROUP_NAME="PublicSubnetGroup"
DESCRIPTION="Enable SSH access via port ${SSH_PORT}"
AMI_ID="ami-dcfa4edd"
SIZE="10"
DELETE_VOLUME="true"
KEYPAIR="vpcsample"
TYPE="t1.micro"
AKI_ID="aki-ec5df7ed"
RUN_OPTS="${AMI_ID} -b /dev/sda1=:${SIZE}:${DELETE_VOLUME} -b /dev/sdc=ephemeral0 -k ${KEYPAIR} -t ${TYPE} --instance-initiated-shutdown-behavior stop --kernel ${AKI_ID}"



VPC_ID=`create-vpc 10.0.0.0/16 "${OPTS}"`

PUBLIC_SUBNET_ID_A=`create-subnet ${VPC_ID} 10.0.10.0/24 ${ZONE_A} "${OPTS}"`
PUBLIC_SUBNET_ID_B=`create-subnet ${VPC_ID} 10.0.11.0/24 ${ZONE_B} "${OPTS}"`

IGW_ID=`create-internet-gateway ${VPC_ID} "${OPTS}"`

PUBLIC_ROUTE_TABLE_ID=`create-route-table ${VPC_ID} "${OPTS}"`
ec2-create-route ${PUBLIC_ROUTE_TABLE_ID} -r 0.0.0.0/0 -g ${IGW_ID} ${OPTS}
ec2-associate-route-table ${PUBLIC_ROUTE_TABLE_ID} -s ${PUBLIC_SUBNET_ID_A} ${OPTS}
ec2-associate-route-table ${PUBLIC_ROUTE_TABLE_ID} -s ${PUBLIC_SUBNET_ID_B} ${OPTS}

PUBLIC_ACL_ID=`create-network-acl ${VPC_ID} "${OPTS}"`
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 100 -P 6 -r 0.0.0.0/0 -p 80 --allow ${OPTS}
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 101 -P 6 -r 0.0.0.0/0 -p ${SSH_PORT} --allow ${OPTS}
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 102 -P 6 -r 0.0.0.0/0 -p 1024-65535 --allow ${OPTS}
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 100 --egress -P 6 -r 0.0.0.0/0 -p 80 --allow ${OPTS}
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 101 --egress -P 6 -r 0.0.0.0/0 -p 443 --allow ${OPTS}
ec2-create-network-acl-entry ${PUBLIC_ACL_ID} -n 102 --egress -P 6 -r 0.0.0.0/0 -p 1024-65535 --allow ${OPTS}

replace-network-acl-association ${PUBLIC_SUBNET_ID_A} ${PUBLIC_ACL_ID} "${OPTS}"
replace-network-acl-association ${PUBLIC_SUBNET_ID_B} ${PUBLIC_ACL_ID} "${OPTS}"

PRIVATE_SUBNET_ID_A=`create-subnet ${VPC_ID} 10.0.20.0/24 ${ZONE_A} "${OPTS}"`
PRIVATE_SUBNET_ID_B=`create-subnet ${VPC_ID} 10.0.21.0/24 ${ZONE_B} "${OPTS}"`

PRIVATE_ROUTE_TABLE_ID=`create-route-table ${VPC_ID} "${OPTS}"`
ec2-associate-route-table ${PRIVATE_ROUTE_TABLE_ID} -s ${PRIVATE_SUBNET_ID_A} ${OPTS}
ec2-associate-route-table ${PRIVATE_ROUTE_TABLE_ID} -s ${PRIVATE_SUBNET_ID_B} ${OPTS}

PRIVATE_ACL_ID=`create-network-acl ${VPC_ID} "${OPTS}"`
ec2-create-network-acl-entry ${PRIVATE_ACL_ID} -n 100 -P all -r 10.0.0.0/16 --allow ${OPTS}
ec2-create-network-acl-entry ${PRIVATE_ACL_ID} -n 100 --egress -P all -r 10.0.0.0/16 --allow ${OPTS}

replace-network-acl-association ${PRIVATE_SUBNET_ID_A} ${PRIVATE_ACL_ID} "${OPTS}"
replace-network-acl-association ${PRIVATE_SUBNET_ID_B} ${PRIVATE_ACL_ID} "${OPTS}"

PUBLIC_GROUP_ID=`create-group ${GROUP_NAME} "${DESCRIPTION}" ${VPC_ID} "${OPTS}"`
ec2-authorize ${PUBLIC_GROUP_ID} -P 6 -p 80 -s 0.0.0.0/0 ${OPTS}
ec2-authorize ${PUBLIC_GROUP_ID} -P 6 -p ${SSH_PORT} -s 0.0.0.0/0 ${OPTS}

INSTANCE_ID_A=`run-instances "${RUN_OPTS}" ${PUBLIC_GROUP_ID} ${ZONE_A} ${PUBLIC_SUBNET_ID_A} 10.0.10.5 host00 "${OPTS}"`
INSTANCE_ID_B=`run-instances "${RUN_OPTS}" ${PUBLIC_GROUP_ID} ${ZONE_B} ${PUBLIC_SUBNET_ID_B} 10.0.11.5 host01  "${OPTS}"`

ALLOCATION_ID_A=`associate-address ${INSTANCE_ID_A} "${OPTS}"`
ALLOCATION_ID_B=`associate-address ${INSTANCE_ID_B} "${OPTS}"`
functions.sh
#!/bin/bash

function create-vpc()
{
    local VPC_CIDR=$1
    local OPTS=$2
    local VPC_ID=""

    while [[ -z ${VPC_ID} ]]; do
        VPC_ID=$(ec2-create-vpc ${VPC_CIDR} ${OPTS} | grep VPC | awk '{print $2}')
    done

    while [[ -z "$(ec2-describe-vpcs ${VPC_ID} --filter \"state=available\" ${OPTS})" ]]; do
        sleep 10
    done

    echo "${VPC_ID}"
}

function create-subnet()
{
    local VPC_ID=$1
    local CIDR=$2
    local ZONE=$3
    local OPTS=$4
    local SUBNET_ID=""

    while [[ -z ${SUBNET_ID} ]]; do
        SUBNET_ID=$(ec2-create-subnet -c ${VPC_ID} -i ${CIDR} -z ${ZONE} ${OPTS} | grep SUBNET | awk '{print $2}')
    done

    while [[ -z "$(ec2-describe-subnets ${SUBNET_ID} --filter \"state=available\" ${OPTS})" ]]; do
        sleep 10
    done

    echo "${SUBNET_ID}"
}

function create-internet-gateway()
{
    local VPC_ID=$1
    local OPTS=$2
    local IGW_ID=""

    while [[ -z ${IGW_ID} ]]; do
        IGW_ID=$(ec2-create-internet-gateway ${OPTS} | grep ^INTERNETGATEWAY | awk '{print $2}')
    done

    ec2-attach-internet-gateway ${IGW_ID} -c ${VPC_ID} ${OPTS} > /dev/null 2>&1

    while [[ -z "$(ec2-describe-internet-gateways ${IGW_ID} --filter \"attachment.vpc-id=${VPC_ID}\" --filter \"attachment.state=available\" ${OPTS})" ]]; do
        sleep 10
    done

    echo "${IGW_ID}"
}

function create-route-table()
{
    local VPC_ID=$1
    local OPTS=$2
    local ROUTE_TABLE_ID=""

    while [[ -z ${ROUTE_TABLE_ID} ]]; do
        ROUTE_TABLE_ID=$(ec2-create-route-table ${VPC_ID} ${OPTS} | grep ROUTETABLE | awk '{print $2}')
    done

    echo "${ROUTE_TABLE_ID}"
}

function create-network-acl()
{
    local VPC_ID=$1
    local OPTS=$2
    local ACL_ID=""

    while [[ -z ${ACL_ID} ]]; do
        ACL_ID=$(ec2-create-network-acl ${VPC_ID} ${OPTS} | grep NETWORKACL | awk '{print $2}')
    done

    echo "${ACL_ID}"
}

function replace-network-acl-association()
{
    local SUBNET_ID=$1
    local ACL_ID=$2
    local OPTS=$3
    local ASSOCIATION=""

    while [[ -z ${ASSOCIATION} ]]; do
        ASSOCIATION=$(ec2-describe-network-acls --filter \"association.subnet-id=${SUBNET_ID}\" ${OPTS} | grep ASSOCIATION | grep ${SUBNET_ID} | awk '{print $2}')
    done

    ec2-replace-network-acl-association ${ASSOCIATION} -a ${ACL_ID} ${OPTS} > /dev/null 2>&1

    echo "${ASSOCIATION}"
}

function create-group()
{
    local GROUP_NAME=$1
    local DESCRIPTION=$2
    local VPC_ID=$3
    local OPTS=$4
    local GROUP_ID=""

    while [[ -z ${GROUP_ID} ]]; do
        GROUP_ID=$(ec2-create-group ${GROUP_NAME} -d "${DESCRIPTION}" -c ${VPC_ID} ${OPTS} | grep GROUP | awk '{print $2}')
    done

    echo "${GROUP_ID}"
}

function run-instances()
{
    local RUN_OPTS=$1
    local GROUP_ID=$2
    local ZONE=$3
    local SUBNET_ID=$4
    local PRIVATE_IP=$5
    local SERVER_NAME=$6
    local OPTS=$7
    local INSTANCE_ID=""

    while [[ -z ${INSTANCE_ID} ]]; do
        INSTANCE_ID=$(ec2-run-instances ${RUN_OPTS} -g ${GROUP_ID} -z ${ZONE} -s ${SUBNET_ID} --private-ip-address ${PRIVATE_IP} ${OPTS} | grep INSTANCE | awk '{print $2}')
    done

    ec2-create-tags ${INSTANCE_ID} --tag Name=${SERVER_NAME} ${OPTS} > /dev/null 2>&1

    while [[ -z "$(ec2-describe-instance-status ${INSTANCE_ID} ${OPTS} | grep INSTANCESTATUS | grep passed)" ]]; do
        sleep 10
    done

    echo "${INSTANCE_ID}"
}

function associate-address()
{
    local INSTANCE_ID=$1
    local OPTS=$2
    local ALLOCATION_ID=""

    ALLOCATION_ID=$(ec2-allocate-address -d vpc ${OPTS} | grep ADDRESS | awk '{print $5}')

    ec2-associate-address -a ${ALLOCATION_ID} -i ${INSTANCE_ID} ${OPTS} > /dev/null 2>&1

    echo "${ALLOCATION_ID}"
}

main部分は、手順とほぼ同じステップで記述できました。
では、スクリプトを実行していきます。


Macbook Air のファンの音が大きくなってきましたw
Management Console で各項目を見ながら進行状況を確認します...
...
3分40秒で終了しました。
確認してみましょう。



無事、EIPとの関連づけまでできていることを確認できました。


確認も終わったので削除します。

後片づけ

Management Console から次の4ステップで終わりです。
簡単ですね!

感想

今回、 ELB や RDS の配置まで書こうと思っていたのですが、間に合いませんでした。。
やっぱり CloudFormation は便利ですね!!

あとがき

先日ラスベガスで行なわれた AWS re:Invent ツアーに参加させていただきました。

Keynote や 各Session( CDP 盛り上がりましたね!)はもちろん、 Technical Bootcamp や Code Challenge 、シアトルのAWS本社訪問など刺激を受ける場面ばかりでした。
ご一緒させていただいた皆様、ありがとうございました。


来年もラスベガスでの開催が決まっているそうです。
気になる方はぜひご参加下さい!


ありがとうございました!