Created
July 3, 2015 07:06
-
-
Save greglu/41caeb6ab9489b93d93b to your computer and use it in GitHub Desktop.
Checks reserved instance utilization of an EC2 account
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Checks active, non-spot, EC2 instances against the list reserved instances | |
| # in order to determine which availability-zone/instance-type combinations | |
| # are paying for on-demand prices (that could benefit from reserved instance | |
| # purchase), or current reservations that aren't being used. | |
| # | |
| # Dependencies: | |
| # * awscli (http://aws.amazon.com/cli/) | |
| # * jq (http://stedolan.github.io/jq/) | |
| # * ruby | |
| # | |
| # This script also assumes that the awscli tool is associated to an account. | |
| # | |
| # The MIT License (MIT) | |
| # | |
| # Copyright (c) 2015 Gregory Lu | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in | |
| # all copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| # THE SOFTWARE. | |
| set -e | |
| for i in aws jq ruby; do | |
| if ! which $i &>/dev/null; then | |
| echo "Error: this script requires $i" | |
| exit 1 | |
| fi | |
| done | |
| ACTIVE_INSTANCES_FILE=$(mktemp -t active-instances) | |
| RESERVED_INSTANCES_FILE=$(mktemp -t reserved-instances) | |
| echo -n "Retrieving active instance listings... " | |
| aws ec2 describe-instances > $ACTIVE_INSTANCES_FILE | |
| echo "done" | |
| echo -n "Retrieving reserved instance listings... " | |
| aws ec2 describe-reserved-instances --filters Name=state,Values=active > $RESERVED_INSTANCES_FILE | |
| echo "done" | |
| RESERVED_PARSED=$(mktemp -t reserved-parsed) | |
| ACTIVE_PARSED=$(mktemp -t active-parsed) | |
| cat $RESERVED_INSTANCES_FILE | jq '.ReservedInstances | map({ type: (.AvailabilityZone + "/" + .InstanceType), count: .InstanceCount }) | group_by(.type) | map({key: .[0].type, value: (reduce .[] as $item (0; . + $item.count)) }) | from_entries' > $RESERVED_PARSED | |
| cat $ACTIVE_INSTANCES_FILE | jq '.Reservations | map(.Instances[0] | select(.InstanceLifecycle != "spot") | {type: ((.Placement .AvailabilityZone) + "/" + .InstanceType)}) | group_by(.type) | map({key: .[0].type, value: length}) | from_entries' > $ACTIVE_PARSED | |
| COMBINED=$(ruby -e "require 'json'; active = JSON.load(File.read('${ACTIVE_PARSED}')); reserved = JSON.load(File.read('${RESERVED_PARSED}')); puts JSON.dump(active.merge(reserved) { |_, x, y| x - y })") | |
| echo "" | |
| echo "-------------------------------------------------------------------------------" | |
| echo "Current counts of active, non-spot, instances without an associated reservation" | |
| echo "(paying on-demand pricing for these instances)" | |
| echo "-------------------------------------------------------------------------------" | |
| echo $COMBINED | jq 'to_entries | map(select(.value > 0)) | from_entries' | |
| echo "" | |
| echo "--------------------------------------------------------" | |
| echo "Current reservations without active instances using them" | |
| echo "(already paid for these reserved instances)" | |
| echo "--------------------------------------------------------" | |
| echo $COMBINED | jq 'to_entries | map(select(.value < 0)) | from_entries' | |
| echo "" | |
| exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This has a math/logic bug when you have a Reserved Instance with no corresponding Active Instances because the ruby merge/subtraction block
{ |_, x, y| x - y }never gets called when there isn't a conflict between the two dicts.There's a one-line fix if you're okay with looking at negative values in the second printed section:
Change:
To:
Finally, it should also be noted that this doesn't check for matching Platform (e.g. VPC vs. non-VPC).