您可能会用到的AWS CLI Shell脚本。主要功能有根据名字/类别管理EC2、RDS资源,创建资源时自动添加标签,创建EC2实例时可附加卷、定义用户数据文件。可用-h或--help查看支持的命令和参数,GitHub源码

支持的命令: EC2

Usage: ./ec2.sh [command] [args ...]
Commands:
  create-image [instance_name] [image_name] [tags] Create an AMI from an EC2 instance
  delete-image [image_name] Delete image by name
  start-instance [instance_name] Start an EC2 instance
  start-instances [category_name] Start EC2 instances by category
  stop-instance [instance_name] Stop an EC2 instance
  stop-instances [category_name] Stop EC2 instances by category
  reboot-instance [instance_name] Reboot an EC2 instance
  reboot-instances [category_name] Reboot EC2 instances by category
  terminate-instance [instance_name] Terminate an EC2 instance
  terminate-instances [category_name] Terminate EC2 instances by category
  run-instance [instance_name] [image_name] [options] Launch an instance using an AMI
    Options:
      --device-snapshot-name One block device snapshot name
      --init-file an user data file
      --tags One  or more tags
  create-snapshot [instance_name] [device] [snapshot_name] [tags] Creates a snapshot of the specified volume for an instance
  delete-snapshot [snapshot_name] Deletes the specified snapshot
  attach-volume [snapshot_name] [instance_name] [device] [tags] Create a volume from a snapshot, and then attach the volume to an instance
  associate-address [instance_name] [public_ip]  Associates an Elastic IP address with an instance
  replace-route [route_table_name] [nat_instance_name] [cidr] Replaces an existing route within a route table in a VPC

RDS

Usage: ./rds.sh [delete|restore] [args ...]
Commands:
  delete [db-instance-identifier] Delete a DB instance
  restore [db-instance-identifier] [vpc-security-group-ids] Create a new DB instance from a DB snapshot

Cloud Watch

Usage: ./cloudwatch.sh [cpu] [instance_name] [sns_topic_arn]

EC2

公用方法common.sh

#!/bin/bash

INSTANCE_ID_REGEX="i-\w{8,17}"
IMAGE_ID_REGEX="ami-\w{8,17}"
VOLUME_ID_REGEX="vol-\w{8,17}"
SNAPSHOT_ID_REGEX="snap-\w{8,17}"
ROUTETABLE_ID_REGEX="rtb-\w{8,17}"

query_instance_id_by_name() {
  instance_id=$(aws ec2 describe-instances --filter Name=tag:Name,Values="$1" Name=instance-state-name,Values=pending,running,stopped \
              --query 'Reservations[0].[Instances[0].InstanceId]' | grep -o -E "$INSTANCE_ID_REGEX")
  echo ${instance_id}
}

query_instance_ids_by_category() {
  instance_ids=$(aws ec2 describe-instances --filter Name=tag:Category,Values="$1" Name=instance-state-name,Values=pending,running,stopped \
               --query 'Reservations[*].[Instances[*].InstanceId]' | grep -o -E "$INSTANCE_ID_REGEX")
  echo ${instance_ids}
}

wait_instance_ok() {
  instance_id=$(aws ec2 describe-instances --filter Name=tag:Name,Values="$1" Name=instance-state-name,Values=pending,running \
              --query 'Reservations[0].[Instances[0].InstanceId]' | grep -o -E "$INSTANCE_ID_REGEX")
  check_instance_status ${instance_id}
}

check_instance_status() {
  while true
  do
    ok_count=$(aws ec2 describe-instance-status --instance-id $1 | grep -c ok)
    if [[ "$ok_count" -eq 2 ]]; then
      break
    else
      echo "Waiting ..."
      sleep 5
    fi
  done
}

describe_running_instances() {
  instances=$(aws ec2 describe-instances --filter Name=instance-state-name,Values=running \
            --query 'Reservations[*].Instances[*].{State:State.Name,Ip:PrivateIpAddress,InstanceId:InstanceId,Name:Tags[0].Value}')
  echo ${instances}
}

query_image_id_by_name() {
  image_id=$(aws ec2 describe-images --filter Name=name,Values="$1" --query Images[0].[ImageId] | grep -o -E "$IMAGE_ID_REGEX")
  echo ${image_id}
}

query_volume_id_by_name() {
  id=$(aws ec2 describe-volumes --filter Name=tag:Name,Values="$1" --query Volumes[0].[VolumeId] | grep -o -E "$VOLUME_ID_REGEX")
  echo ${id}
}

query_volume_ids_by_name() {
  id=$(aws ec2 describe-volumes --filter Name=tag:Name,Values="$1" --query Volumes[*].[VolumeId] | grep -o -E "$VOLUME_ID_REGEX")
  echo ${id}
}

query_volume_id_by_instance_id_and_device() {
  id=$(aws ec2 describe-volumes --filter Name=attachment.instance-id,Values="$1" Name=attachment.device,Values=$2 \
     --query Volumes[0].[VolumeId] | grep -o -E "$VOLUME_ID_REGEX")
  echo ${id}
}

query_snapshot_id_by_name() {
  snapshot_id=$(aws ec2 describe-snapshots --filter Name=tag:Name,Values="$1" --query Snapshots[0].[SnapshotId] | grep -o -E "$SNAPSHOT_ID_REGEX")
  echo ${snapshot_id}
}

query_snapshot_ids_by_image_id() {
  snapshot_ids=$(aws ec2 describe-snapshots --query Snapshots[*].[SnapshotId][*] --filter Name=description,Values=*"$1"* | grep -o -E "$SNAPSHOT_ID_REGEX")
  echo ${snapshot_ids}
}

query_route_table_id_by_name() {
  id=$(aws ec2 describe-route-tables --filter Name=tag:Name,Values="$1" --query RouteTables[0].RouteTableId | grep -o -E "$ROUTETABLE_ID_REGEX")
  echo ${id}
}

query_elb_instance_ids() {
  ids=$(aws elb describe-load-balancers --load-balancer-name "$1" --query LoadBalancerDescriptions[0].[Instances[*].[InstanceId]] | grep -o -E "$INSTANCE_ID_REGEX")
  echo ${ids}
}

create_tags_with_name() {
  resource_id=$1
  name=$2
  tags=$3;

  if [[ -z ${resource_id} ]]; then
    return 1
  fi

  if [[ ${tags} ]]; then
    echo "Add tags: ${tags}"
  fi

  aws ec2 create-tags --resources ${resource_id} --tags Key=Name,Value="${name}" ${tags}
  echo
}

EC2脚本ec2.sh

#!/bin/bash

. $(dirname $0)/common.sh

# 根据Instance Name创建image并添加标签
create_image() {
  instance_name=$1
  image_name=$2
  tags=$3
  echo "Create image for instance ${instance_name}"

  instance_id=$(query_instance_id_by_name "${instance_name}")
  image_id=$(aws ec2 create-image --instance-id "${instance_id}" --name "${image_name}" --description "${image_name}" --no-reboot --query ImageId)
  image_id=${image_id//\"/}
  echo "ImageId: $image_id"

  create_tags_with_name "${image_id}" "${image_name}" "${tags}"
}

# 删除AMI
delete_image() {
  image_name=$1
  echo "Delete image ${image_name}"

  image_id=$(query_image_id_by_name "${image_name}")
  echo "Image id: ${image_id}"

  echo "Deregister image $image_id"
  aws ec2 deregister-image --image-id "${image_id}"

  snapshot_ids=$(query_snapshot_ids_by_image_id "${image_id}")

  for snapshot_id in ${snapshot_ids}
  do
    echo "Delete snapshot ${snapshot_id}"
    aws ec2 delete-snapshot --snapshot-id "${snapshot_id}"
  done
  echo
}

# 根据Name启动EC2 Instance
start_instance() {
  id=$(query_instance_id_by_name "$1")
  aws ec2 start-instances --instance-ids ${id}
}

# 根据类别启动EC2 Instance
start_instances() {
  ids=$(query_instance_ids_by_category "$1")
  aws ec2 start-instances --instance-ids ${ids}
}

# 根据Name停止EC2 Instance
stop_instance() {
  id=$(query_instance_id_by_name "$1")
  aws ec2 stop-instances --instance-ids ${id}
}

# 根据类别停止EC2 Instance
stop_instances() {
  ids=$(query_instance_ids_by_category "$1")
  aws ec2 stop-instances --instance-ids ${ids}
}

# 根据Name重启EC2 Instance
reboot_instance() {
  id=$(query_instance_id_by_name "$1")
  aws ec2 reboot-instances --instance-ids ${id}
}

# 根据类别重启EC2 Instance
reboot_instances() {
  ids=$(query_instance_ids_by_category "$1")
  aws ec2 reboot-instances --instance-ids ${ids}
}

# 根据Name终止EC2 Instance
terminate_instance() {
  id=$(query_instance_id_by_name "$1")
  echo "terminate instance, instance name: $1 instance id: ${id}"

  aws ec2 modify-instance-attribute --instance-id "${id}" --no-disable-api-termination
  aws ec2 terminate-instances --instance-ids ${id}
  echo
}

# 根据类别终止EC2 Instance
terminate_instances() {
  ids=$(query_instance_ids_by_category "$1")
  echo "terminate instances, category: $1 instance-ids: ${ids}"

  for id in ${ids}
  do
    aws ec2 modify-instance-attribute --instance-id "${id}" --no-disable-api-termination
  done
  aws ec2 terminate-instances --instance-ids ${ids}
  echo
}

# 从Image创建EC2 Instance,EC2配置从JSON文件读取,可以附加一个Volume,可以使用用户数据文件,可以添加标签
run_instance() {
  instance_name=$1
  image_name=$2
  device_snapshot_name=$3
  init_file=$4
  tags=$5

  block_device_mappings=" "

  if [[ "${device_snapshot_name}" ]]; then
    snapshot_id=$(query_snapshot_id_by_name "${device_snapshot_name}")
    if [[ "${snapshot_id}" ]]; then
      block_device_mappings="--block-device-mappings DeviceName=/dev/sdf,Ebs={SnapshotId=${snapshot_id},DeleteOnTermination=true,VolumeType=gp2}"
    else
      echo "Please provide a valid volume snapshot name"
      exit 1
    fi
  fi

  image_id=$(query_image_id_by_name "${image_name}")
	echo "Create EC2 instance ${instance_name} from image ${image_name}(${image_id})"

  if [[ "$init_file" ]]; then
    instance_id=$(aws ec2 run-instances --image-id "${image_id}" ${block_device_mappings} --cli-input-json file://json/"${instance_name}".json \
                --user-data file://"${init_file}" --query 'Instances[0].[InstanceId]' | grep -o -E "${INSTANCE_ID_REGEX}")
  else
    instance_id=$(aws ec2 run-instances --image-id "${image_id}" ${block_device_mappings} --cli-input-json file://json/"${instance_name}".json \
                --query 'Instances[0].[InstanceId]' | grep -o -E "${INSTANCE_ID_REGEX}")
  fi

  create_tags_with_name "${instance_id}" "${instance_name}" "${tags}"
}

# 为EC2 Instance的指定卷创建快照并删除以前同名快照
create_snapshot() {
  instance_name=$1
  device=$2
  snapshot_name=$3
  tags=$4

  instance_id=$(query_instance_id_by_name "${instance_name}")

  delete_snapshot "${snapshot_name}"

  volume_id=$(query_volume_id_by_instance_id_and_device ${instance_id} ${device})
  if [[ "${volume_id}" ]]; then
    echo "create snapshot for volume: ${device} of instance ${instance_name}"
    snapshot_id=$(aws ec2 create-snapshot --volume-id ${volume_id} | grep -o -E "${SNAPSHOT_ID_REGEX}")

    create_tags_with_name "${snapshot_id}" "${snapshot_name}" "${tags}"
  fi
}

# 根据名称删除快照
delete_snapshot() {
  snapshot_id=$(query_snapshot_id_by_name "$1")
  if [[ "${snapshot_id}" ]]; then
    echo "delete snapshot: $1"
    aws ec2 delete-snapshot --snapshot-id ${snapshot_id}
  fi
}

# 从快照创建卷并删除旧的重名卷,然后将卷连接到Instance的指定device
attach_volume() {
  snapshot_name=$1
  instance_name=$2
  device=$3
  tags=$4

  availability_zone="cn-north-1a"
  volume_name="$1-1a"

  snapshot_id=$(query_snapshot_id_by_name ${snapshot_name})
  instance_id=$(query_instance_id_by_name ${instance_name})

  if [[ -z "${snapshot_id}" ]]; then
    echo "Please provide valid snapshot name"
    exit 1
  fi

  if [[ -z "${instance_id}" ]]; then
    echo "Please provide valid instance name"
    exit 1
  fi

  old_volume_ids=$(query_volume_ids_by_name "${volume_name}")
  for id in ${old_volume_ids}
  do
    echo "delete old volume: $id"
    aws ec2 delete-volume --volume-id ${id}
  done

  echo "create volume ${volume_name} from snapshot ${snapshot_name}(${snapshot_id})"
  volume_id=$(aws ec2 create-volume --snapshot-id ${snapshot_id} --availability-zone ${availability_zone} --volume-type gp2 --query 'VolumeId' \
            | grep -o -E "${VOLUME_ID_REGEX}")

  count=0
  while [[ "${count}" -le 0 ]]
  do
    echo "Creating volume ${volume_name} ..."
    count=$(aws ec2 describe-volumes --volume-ids ${volume_id} --query Volumes[0].State | grep -c available)
    sleep 3
  done

  echo "attach volume: ${volume_name} to instance ${instance_name}"
  aws ec2 attach-volume --volume-id ${volume_id} --instance-id ${instance_id} --device ${device}

  create_tags_with_name "${volume_id}" "${volume_name}" "${tags}"
}

# 关联弹性IP到Instance
associate_address() {
  instance_id=$(query_instance_id_by_name "$1")
  aws ec2 associate-address --instance-id ${instance_id} --public-ip $2
}

# 更新NAT Instance路由
replace_route() {
  echo "update route table: $1"
  route_table_id=$(query_route_table_id_by_name "$1")
  nat_instance_id=$(query_instance_id_by_name "$2")
  cidr=$3
  aws ec2 replace-route --route-table-id ${route_table_id} --destination-cidr-block ${cidr} --instance-id ${nat_instance_id}
}

echo_usage() {
  echo "Usage: $0 [command] [args ...]"
  echo "Commands:"
  echo "  create-image [instance_name] [image_name] [tags] Create an AMI from an EC2 instance"
  echo "  delete-image [image_name] Delete image by name"
  echo "  start-instance [instance_name] Start an EC2 instance"
  echo "  start-instances [category_name] Start EC2 instances by category"
  echo "  stop-instance [instance_name] Stop an EC2 instance"
  echo "  stop-instances [category_name] Stop EC2 instances by category"
  echo "  reboot-instance [instance_name] Reboot an EC2 instance"
  echo "  reboot-instances [category_name] Reboot EC2 instances by category"
  echo "  terminate-instance [instance_name] Terminate an EC2 instance"
  echo "  terminate-instances [category_name] Terminate EC2 instances by category"
  echo "  run-instance [instance_name] [image_name] [options] Launch an instance using an AMI"
  echo "    Options:"
  echo "      --device-snapshot-name One block device snapshot name"
  echo "      --init-file an user data file"
  echo "      --tags One  or more tags"
  echo "  create-snapshot [instance_name] [device] [snapshot_name] [tags] Creates a snapshot of the specified volume for an instance"
  echo "  delete-snapshot [snapshot_name] Deletes the specified snapshot"
  echo "  attach-volume [snapshot_name] [instance_name] [device] [tags] Create a volume from a snapshot, and then attach the volume to an instance"
  echo "  associate-address [instance_name] [public_ip]  Associates an Elastic IP address with an instance"
  echo "  replace-route [route_table_name] [nat_instance_name] [cidr] Replaces an existing route within a route table in a VPC"
}

if test @$1 = @--help -o @$1 = @-h; then
  echo_usage
  exit 0
fi

if [[ $# -lt 2 ]]; then
  echo_usage
  exit 1
fi

case "$1" in
  create-image)
    create_image "$2" "$3" "$4"
    ;;
  delete-image)
    delete_image "$2"
    ;;
  start-instance)
    start_instance "$2"
    ;;
  start-instances)
    start_instances "$2"
    ;;
  stop-instance)
    stop_instance "$2"
    ;;
  stop-instances)
    stop_instances "$2"
    ;;
  reboot-instance)
    reboot_instance "$2"
    ;;
  reboot-instances)
    reboot_instances "$2"
    ;;
  terminate-instance)
    terminate_instance "$2"
    ;;
  terminate-instances)
    terminate_instances "$2"
    ;;
  run-instance)
    args=`getopt -l init-file:,device-snapshot-name:,tags: -- "$@"`
    if [[ $? != 0 ]] ; then
     exit 1
    fi

    instance_name=$2
    image_name=$3
    device_snapshot_name=""
    init_file=""
    tags=""

    eval set -- "${args}"
    while true
    do
    case "$1" in
      --device-snapshot-name)
        device_snapshot_name="$2"
        shift 2
        ;;
      --init-file)
        init_file="$2"
        shift 2
        ;;
      --tags)
        tags="$2"
        shift 2
        ;;
      --)
        shift
        break
        ;;
    esac
    done

    run_instance "${instance_name}" "${image_name}" "${device_snapshot_name}" "${init_file}" "${tags}"
    ;;
  create-snapshot)
    create_snapshot "$2" "$3" "$4" "$5"
    ;;
  delete-snapshot)
    delete_snapshot "$2"
    ;;
  attach-volume)
    attach_volume "$2" "$3" "$4" "$5"
    ;;
  associate-address)
    associate_address "$2" "$3"
    ;;
  replace-route)
    replace_route "$2" "$3" "$4"
    ;;
  *)
    exit 1
    ;;
esac

从image运行instance时需要使用json配置文件,位置在json目录下,名称要与instance name相同,内容如下; 示例ec2.json

{
    "DryRun": false, 
    "KeyName": "AWS Prod Key Pair",
    "SecurityGroupIds": [
        "sg-1361c577"
    ],
    "InstanceType": "m4.large", 
    "Placement": {
        "AvailabilityZone": "cn-north-1a", 
        "Tenancy": "default"
    }, 
    "Monitoring": {
        "Enabled": false
    }, 
    "DisableApiTermination": true, 
    "InstanceInitiatedShutdownBehavior": "stop", 
    "NetworkInterfaces": [
        {
            "DeviceIndex": 0,
            "SubnetId": "subnet-d45208b1",
            "PrivateIpAddress": "10.184.12.246",
            "DeleteOnTermination": true,
            "AssociatePublicIpAddress": false
        }
    ],
    "EbsOptimized": false
}

示例

$ ./ec2.sh run-instance test-app1 test-app1 --tags "Key=CCX,Value=DSC003 Key=Project,Value=Test"

RDS

rds.sh

#!/bin/bash

# 根据db-instance-identifier删除数据库,删除前先创建快照,并将db-snapshot-identifier保存在文件中
delete() {
  echo "deleting database $1 ..."
  snapshot_id="$1-$(date +%Y%m%d)"
  aws rds delete-db-instance --db-instance-identifier "$1" --no-skip-final-snapshot --final-db-snapshot-identifier "${snapshot_id}"
  echo "${snapshot_id}" > "$(dirname $0)/$1.snapshot.id"
}

# 从最近的快照恢复数据库,数据库配置从json文件读取,恢复成功后指定security group,并输出恢复日志到文件中
restore() {
  log_file="$(dirname $0)/restore.log"
  id_file="$(dirname $0)/$1.snapshot.id"
  snapshot_id=$(cat ${id_file})
  echo "Restore database $1 from snapshot ${snapshot_id}" | tee ${log_file}
  aws rds restore-db-instance-from-db-snapshot --db-snapshot-identifier "${snapshot_id}" --cli-input-json file://json/"$1".json | tee ${log_file}

  count=0
  while [[ "${count}" -le 0 ]]
  do
    echo "Creating database $1 ..."
    count=$(aws rds describe-db-instances --db-instance-identifier "$1" --query 'DBInstances[0].[DBInstanceStatus]' | grep -c available)
    sleep 5
  done

  echo "Modify database $1" | tee ${log_file}
  aws rds modify-db-instance --db-instance-identifier "$1" --vpc-security-group-ids $2 | tee ${log_file}
}

echo_usage() {
  echo "Usage: $0 [delete|restore] [args ...]"
  echo "Commands:"
  echo "  delete [db-instance-identifier] Delete a DB instance"
  echo "  restore [db-instance-identifier] [vpc-security-group-ids] Create a new DB instance from a DB snapshot"
}

if test @$1 = @--help -o @$1 = @-h; then
  echo_usage
  exit 0
fi

if [[ $# -lt 2 ]]; then
  echo_usage
  exit 1
fi

case "$1" in
  delete)
    delete "$2"
    ;;
  restore)
    restore "$2" "$3"
    ;;
  *)
    exit 1
    ;;
esac

示例dn.json

{
    "DBInstanceIdentifier": "test",
    "DBInstanceClass": "db.r3.large", 
    "Port": 1521, 
    "AvailabilityZone": "cn-north-1a", 
    "DBSubnetGroupName": "test-db",
    "MultiAZ": false, 
    "PubliclyAccessible": false, 
    "AutoMinorVersionUpgrade": true, 
    "LicenseModel": "license-included", 
    "DBName": "test",
    "Engine": "oracle-se1", 
    "OptionGroupName": "default:oracle-se1-11-2", 
    "StorageType": "standard", 
    "CopyTagsToSnapshot": true
}

CloudWatch

cloudwatch.sh

#!/bin/bash

. $(dirname $0)/common.sh

# 当EC2 Instance CPU利用率超过90%时发送邮件告警
cpu() {
  instance_name=$1
  sns_topic_arn=$2

  instance_id=$(query_instance_id_by_name "${instance_name}")

  aws cloudwatch put-metric-alarm --alarm-name "cpu-${instance_name}" --alarm-description "Alarm when CPU exceeds 90 percent" --metric-name CPUUtilization \
  --namespace AWS/EC2 --statistic Average --period 300 --threshold 90 --comparison-operator GreaterThanThreshold \
  --dimensions "Name=InstanceId,Value=${instance_id}" --evaluation-periods 2 --unit Percent --alarm-actions $2
}

echo_usage() {
  echo "Usage: $0 [cpu] [instance_name] [sns_topic_arn]"
}

if test @$1 = @--help -o @$1 = @-h; then
  echo_usage
  exit 0
fi

if [[ $# -lt 2 ]]; then
  echo_usage
  exit 1
fi

case "$1" in
  "cpu")
    cpu "$2" "$3"
    ;;
  *)
    exit 1
    ;;
esac