AWSのEC2インスタンスをShellスクリプトで作成する

 シリーズものになっちゃって、第3弾ですね。VPCとサブネットそしてセキュリティグループとくればEC2ですね。ホントに出し尽くされた感があるし今さらなんですが。まぁここまで来たらやっとくかって感じですね。

◆シリーズ

VPCとサブネットの作成

セキュリティグループの作成

EC2の作成


◆Shellスクリプト本体
#!/bin/bash
set -e

# コマンドライン引数からCSVファイル名を取得
if [ $# -ne 1 ]; then
  echo "Usage: $0 <csv-file>"
  exit 1
fi

CSV_FILE="$1"
AWS_REGION="${AWS_REGION:-ap-northeast-1}"

# ヘッダー行を読み飛ばす
HEADER=$(head -n 1 "$CSV_FILE")

# 2行目以降(実データ)を処理
tail -n +2 "$CSV_FILE" | while IFS=, read -r REGION NAME IMAGEID INSTANCETYPE KEYNAME SUBNETNAME SECURITYGROUPNAMES PRIVATEIPADDRESS DISABLEAPITERMINATION COST_TAG VOLUMESIZE VOLUMETYPE ALLOCATEELASTICIP ADDITIONALVOLUMESIZES ADDITIONALVOLUMETYPES IAMINSTANCEPROFILE OSTYPE
do
  AWS_REGION="${REGION:-ap-northeast-1}"
  echo "インスタンス作成開始 $NAME region $AWS_REGION"

  # OSタイプに応じたデバイス名の設定
  case "$OSTYPE" in
    linux)
      ROOT_DEVICE_NAME="/dev/xvda"
      ADDITIONAL_DEVICE_PREFIX="/dev/xvd"
      ;;
    windows)
      ROOT_DEVICE_NAME="/dev/sda1"
      ADDITIONAL_DEVICE_PREFIX="/dev/sd"
      ;;
    *)
      ROOT_DEVICE_NAME="/dev/xvda"  # デフォルト値
      ADDITIONAL_DEVICE_PREFIX="/dev/xvd"
      ;;
  esac
  echo "Root Device Name: $ROOT_DEVICE_NAME"
  echo "Additional Device Prefix: $ADDITIONAL_DEVICE_PREFIX"

  # ~/.ssh/ ディレクトリを作成(存在しない場合)
  mkdir -p ~/.ssh/

  # キーペア確認と作成
  if ! aws ec2 describe-key-pairs --region "$AWS_REGION" --key-names "$KEYNAME" &> /dev/null; then
    echo "Key pair $KEYNAME not found in $AWS_REGION. Creating a new one..."
    aws ec2 create-key-pair --region "$AWS_REGION" --key-name "$KEYNAME" --query 'KeyMaterial' --output text > ~/.ssh/"$KEYNAME".pem
    chmod 400 ~/.ssh/"$KEYNAME".pem
    echo "Key pair $KEYNAME created and saved to ~/.ssh/$KEYNAME.pem"
  fi


  # サブネットID取得
  if [[ "$SUBNETNAME" =~ ^subnet- ]]; then
    # SUBNETNAMEがサブネットIDの場合
    SUBNETID="$SUBNETNAME"
  else
    # SUBNETNAMEがサブネット名の場合
    SUBNETID=$(aws ec2 describe-subnets --region "$AWS_REGION" --filters "Name=tag:Name,Values=$SUBNETNAME" --query "Subnets[0].SubnetId" --output text)
    [ -z "$SUBNETID" ] && { echo "Error: Subnet $SUBNETNAME not found"; continue; }
  fi
  echo "SUBNET-ID: ${SUBNETID}"

  # VPC ID取得
  VPC_ID=$(aws ec2 describe-subnets --region "$AWS_REGION" --subnet-ids "$SUBNETID" --query "Subnets[0].VpcId" --output text)

  # セキュリティグループID取得
  SECURITYGROUPIDS=()
  if [ -n "$SECURITYGROUPNAMES" ]; then
    IFS=';' read -r -a SG_NAMES <<< "$SECURITYGROUPNAMES"
    for SG_NAME in "${SG_NAMES[@]}"; do
      if [[ "$SG_NAME" =~ ^sg- ]]; then
        # SG_NAMEがセキュリティグループIDの場合
        SG_ID="$SG_NAME"
      else
        # SG_NAMEがセキュリティグループ名の場合
        SG_ID=$(aws ec2 describe-security-groups \
          --region "$AWS_REGION" \
          --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=$SG_NAME" \
          --query "SecurityGroups[0].GroupId" \
          --output text)
        [ -z "$SG_ID" ] && { echo "Error: Security group $SG_NAME not found"; continue; }
      fi
      SECURITYGROUPIDS+=("$SG_ID")
    done
  fi
  echo "SECURITYGROUP-IDS: ${SECURITYGROUPIDS[*]}"

  # タグ定義
  TAG_SPEC="ResourceType=instance,Tags=[{Key=Name,Value=$NAME},{Key=CostDiv,Value=$COST_TAG}]"

  # インスタンス作成
  INSTANCE_ID=$(aws ec2 run-instances \
    --region "$AWS_REGION" \
    --image-id "$IMAGEID" \
    --instance-type "$INSTANCETYPE" \
    --key-name "$KEYNAME" \
    $(if [ ${#SECURITYGROUPIDS[@]} -gt 0 ]; then echo "--security-group-ids ${SECURITYGROUPIDS[*]}"; fi) \
    --subnet-id "$SUBNETID" \
    $(if [ -n "$PRIVATEIPADDRESS" ]; then echo "--private-ip-address $PRIVATEIPADDRESS"; fi) \
    --disable-api-termination \
    --tag-specifications "$TAG_SPEC" \
    --block-device-mappings "[{
      \"DeviceName\":\"$ROOT_DEVICE_NAME\",
      \"Ebs\":{
        \"VolumeSize\":$VOLUMESIZE,
        \"VolumeType\":\"$VOLUMETYPE\",
        \"Encrypted\":true
      }
    }]" \
    --query 'Instances[0].InstanceId' \
    --output text)

  echo "Instance $NAME created successfully in $AWS_REGION"
  echo "Instance ID: $INSTANCE_ID"

  # インスタンスの起動を待つ
  echo "Waiting for instance $INSTANCE_ID to enter 'running' state..."
  for i in {1..10}; do
    STATUS=$(aws ec2 describe-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" --query 'Reservations[0].Instances[0].State.Name' --output text)
    if [[ "$STATUS" == "running" ]]; then
      echo "Instance $INSTANCE_ID is now running."
      break
    fi
    echo "Current status: $STATUS. Retrying in 10 seconds..."
    sleep 10
  done

  # rootボリュームにタグ付与
  ROOT_VOLUME_ID=$(aws ec2 describe-instances --region "$AWS_REGION" --instance-ids "$INSTANCE_ID" --query "Reservations[0].Instances[0].BlockDeviceMappings[0].Ebs.VolumeId" --output text)
  aws ec2 create-tags --resources "$ROOT_VOLUME_ID" --tags "Key=Name,Value=$NAME-root" "Key=CostDiv,Value=$COST_TAG"

  # Elastic IP処理
  if [[ "$ALLOCATEELASTICIP" == "TRUE" ]]; then
    ALLOC_ID=$(aws ec2 allocate-address --region "$AWS_REGION" --query 'AllocationId' --output text)
    aws ec2 associate-address --region "$AWS_REGION" --allocation-id "$ALLOC_ID" --instance-id "$INSTANCE_ID"
    aws ec2 create-tags --region "$AWS_REGION" --resources "$ALLOC_ID" --tags "Key=Name,Value=$NAME" "Key=CostDiv,Value=$COST_TAG"
  fi

  # インスタンスの AZ を取得
  INSTANCE_AZ=$(aws ec2 describe-instances \
    --region "$AWS_REGION" \
    --instance-ids "$INSTANCE_ID" \
    --query 'Reservations[0].Instances[0].Placement.AvailabilityZone' --output text)

  echo "Instance AZ: $INSTANCE_AZ"

  # 追加ストレージ処理
  if [[ -n "$ADDITIONALVOLUMESIZES" && "$ADDITIONALVOLUMESIZES" != "null" ]]; then
    IFS=';' read -r -a SIZES <<< "$ADDITIONALVOLUMESIZES"
    IFS=';' read -r -a TYPES <<< "$ADDITIONALVOLUMETYPES"

    for i in "${!SIZES[@]}"; do
      VOL_ID=$(aws ec2 create-volume \
        --region "$AWS_REGION" \
        --availability-zone "$INSTANCE_AZ" \
        --size "${SIZES[$i]}" \
        --volume-type "${TYPES[$i]}" \
        --query 'VolumeId' --output text)

      echo "Created volume: $VOL_ID in $INSTANCE_AZ"

      # ボリュームがavailableになるまで待機
      VOLUME_STATUS="creating"
      while [[ "$VOLUME_STATUS" != "available" ]]; do
        VOLUME_STATUS=$(aws ec2 describe-volumes --region "$AWS_REGION" --volume-ids "$VOL_ID" --query "Volumes[0].State" --output text)
        echo "Volume $VOL_ID status: $VOLUME_STATUS. Waiting 5 seconds..."
        sleep 5
      done

      # 追加デバイス名を動的に設定
      DEVICE_NAME="${ADDITIONAL_DEVICE_PREFIX}$(echo {b..z} | cut -d ' ' -f $((i+1)))"
      echo "Attaching volume $VOL_ID to $DEVICE_NAME"

      aws ec2 attach-volume \
        --region "$AWS_REGION" \
        --instance-id "$INSTANCE_ID" \
        --device "$DEVICE_NAME" \
        --volume-id "$VOL_ID"

      aws ec2 create-tags --resources "$VOL_ID" --tags "Key=Name,Value=$NAME" "Key=CostDiv,Value=$COST_TAG"
    done
  fi

  # IAMロール処理
  if [[ -n "$IAMINSTANCEPROFILE" && "$IAMINSTANCEPROFILE" != "null" ]]; then
    aws ec2 associate-iam-instance-profile \
      --region "$AWS_REGION" \
      --instance-id "$INSTANCE_ID" \
      --iam-instance-profile "Name=$IAMINSTANCEPROFILE"
  fi

  # 終了保護設定
  if [[ "$DISABLEAPITERMINATION" == "TRUE" ]]; then
    aws ec2 modify-instance-attribute --region "$AWS_REGION" --instance-id "$INSTANCE_ID" --disable-api-termination "Value=true"
    echo "Instance $INSTANCE_ID termination protection enabled."
  else
    aws ec2 modify-instance-attribute --region "$AWS_REGION" --instance-id "$INSTANCE_ID" --disable-api-termination "Value=false"
    echo "Instance $INSTANCE_ID termination protection disabled."
  fi

  echo "インスタンス作成完了 $NAME "
  echo "----------------------------------"
done

まぁこれもVPCとセキュリティグループと同じようにCSVファイルを読み込むタイプですね。CSVもさらしときます。

REGION,NAME,IMAGEID,INSTANCETYPE,KEYNAME,SUBNETNAME,SECURITYGROUPNAMES,PRIVATEIPADDRESS,DISABLEAPITERMINATION,COST_TAG,VOLUMESIZE,VOLUMETYPE,ALLOCATEELASTICIP,ADDITIONALVOLUMESIZES,ADDITIONALVOLUMETYPES,IAMINSTANCEPROFILE,OSTYPE
ap-northeast-1,TEST1,ami-0599b6e53ca798bb2,t3.micro,zabbix-key,handson-cli-vpc-private-subnet-a1,my-sg-1;my-sg-2,10.0.11.5,FALSE,Div-1,30,gp3,FALSE,,,,linux
ap-northeast-1,TEST2,ami-0f2b7d16d1c8c53d0,t3.micro,zabbix-key,handson-cli-vpc-private-subnet-c1,my-sg-3,10.0.12.5,TRUE,Div2,30,gp3,TRUE,30;50,gp3,ec2-test-role,windows
us-east-1,TEST1,ami-08b5b3a93ed654d19,t3.micro,us-key,subnet-0563d47f9536552b9,test-sg,,FALSE,Div3,30,gp3,FALSE,10;10,gp3,ec2-test-role,linux

後はALBとかRDSなんかになるんですが、それはやるかどうかは気力次第かな。ひとまず主力的なところはやれたんで一区切りですかね。


コメント

このブログの人気の投稿

GASでGoogleDriveのサブフォルダとファイル一覧を出力する

マクロ経済学(IS-LM分析)

証券外務員1種勉強(計算式暗記用メモ)