Check CPU/Volume credits on AWS T micro/nano/small instances

Published on Author Artem ButusovLeave a comment

T instances and Gentoo

AWS t2/t3/t3a/t4g nano/micro/small instances is not the best choice for compilation of heavy applications, especially on Gentoo where every package is compiled from source code. However, these small T instances are very cost-effective and could be used as playground or for small projects.

Before upgrading any heavy package like gcc, glibc, kernel it is good to check if your instance has enough CPU credits to compile package without falling back to baseline performance.

You could read more about burstable credit balance concept here: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-credits-baseline-concepts.html

These days you can enable unlimited CPU credits for T instances to avoid performance drop: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-unlimited-mode.html

Also, if T instance is low on memory (like 512MB) then it could be complicated to build heavy packages like gcc, glibc, binutils etc. If you are experimenting with Gentoo on cheapest T instances, then I would recommend to limit compilation to 1 thread, ensure that you have swap, ensure that kernel avoids using swap where possible, and ensure that swap did not exceed EBS volume IOPS burst balance. Keep in mind, that compilation is causing a lot of IO and your EBS volume can run out of IOPS burst balance and fallback to very slow IO causing compilations to take hours.

Recommended configuration for T nano/micro/small instances

Set minimal usage of swap to avoid spending volume IOPS burst:

# set in runtime
sysctl vm.swappiness=1

# persist on reboot
nano /etc/sysctl.conf 
vm.swappiness=1

Setup swap on the same disk or separate volume:

# create swap file
fallocate -l 1G /swap
chmod 600 /swap

# format swap file
mkswap /swap
swaplabel -L swap /swap

# enable swap in runtime
swapon /swap

# persist on reboot
nano /etc/fstab
LABEL="swap"            none            swap            sw              0 0

# check swap status
swapon --show

Limit number of parallel compilation processes. Each process will eat RAM that is limited on nano/micro instances. Set MAKEOPTS="-j1" in /etc/portage/make.conf.

Run portage with lower CPU priority to make system more responsive when something is merging. Set PORTAGE_NICENESS="-20" in /etc/portage/make.conf.

Script to check cpu/volume credits

The script below could be used on t instances to check cpu credit balance (if unlimited balance is not enabled) and also to test volume burst balance. Can be useful to check before running intensive CPU or/and IOPS compilation. Feel free to modify to your use case.

File check-ec2-credits:

#!/bin/bash

CPU_CREDITS_WARN=50 # in percents

# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-credits-baseline-concepts.html
instance_type_max_cpu_credits_t2_nano=72
instance_type_max_cpu_credits_t2_micro=144
instance_type_max_cpu_credits_t2_small=288
instance_type_max_cpu_credits_t2_medium=576
instance_type_max_cpu_credits_t2_large=864
instance_type_max_cpu_credits_t2_xlarge=1296
instance_type_max_cpu_credits_t2_2xlarge=1944
instance_type_max_cpu_credits_t3_nano=144
instance_type_max_cpu_credits_t3_micro=288
instance_type_max_cpu_credits_t3_small=576
instance_type_max_cpu_credits_t3_medium=576
instance_type_max_cpu_credits_t3_large=864
instance_type_max_cpu_credits_t3_xlarge=2304
instance_type_max_cpu_credits_t3_2xlarge=4608
instance_type_max_cpu_credits_t3a_nano=144
instance_type_max_cpu_credits_t3a_micro=288
instance_type_max_cpu_credits_t3a_small=576
instance_type_max_cpu_credits_t3a_medium=576
instance_type_max_cpu_credits_t3a_large=864
instance_type_max_cpu_credits_t3a_xlarge=2304
instance_type_max_cpu_credits_t3a_2xlarge=4608
instance_type_max_cpu_credits_t4g_nano=144
instance_type_max_cpu_credits_t4g_micro=288
instance_type_max_cpu_credits_t4g_small=576
instance_type_max_cpu_credits_t4g_medium=576
instance_type_max_cpu_credits_t4g_large=864
instance_type_max_cpu_credits_t4g_xlarge=2304
instance_type_max_cpu_credits_t4g_2xlarge=4608

metadata="$(curl -sL http://169.254.169.254/latest/dynamic/instance-identity/document)" 
region=$(echo "$metadata" | jq -r .region)
instance_id=$(echo "$metadata" | jq -r .instanceId)
instance_type=$(echo "$metadata" | jq -r .instanceType)

# metrics could delay, so check for lat 10 minutes
start_time=$(date -u -d "10 minutes ago" +%Y-%m-%dT%H:%M:%S)
end_time=$(date -u +%Y-%m-%dT%H:%M:%S)

cpu_credits_value=$(
   aws cloudwatch get-metric-statistics \
        --namespace "AWS/EC2" \
        --region "${region}" \
        --metric-name "CPUCreditBalance" \
        --start-time "${start_time}" \
        --end-time "${end_time}" \
        --period 60 \
        --statistics Average \
        --dimensions Name=InstanceId,Value="${instance_id}" \
        --query 'Datapoints | reverse(sort_by(@, &Timestamp))[0].Average' \
        --output text
)

cpu_credits_max_var=instance_type_max_cpu_credits_${instance_type/./_}
cpu_credits_max=${!cpu_credits_max_var}
cpu_credits_ratio=$(echo "$cpu_credits_value / $cpu_credits_max * 100" | bc -l)
cpu_credits_warn=$(echo "$cpu_credits_ratio <= $CPU_CREDITS_WARN" | bc -l)

echo "Instance ID: $instance_id"
echo "Instance Type: $instance_type"
echo "Region ID: $region"
echo "CPU Credits: $(printf %.0f $cpu_credits_value)/$(printf %.0f $cpu_credits_max) -> $(printf %.0f $cpu_credits_ratio)%"

instance_volumes=$(
    aws ec2 describe-instances \
        --instance-ids i-016138d31fdd842aa \
        --query 'Reservations[].Instances[].BlockDeviceMappings[].Ebs.VolumeId' \
        --output text
)

for volume_id in $instance_volumes; do
    volume_burst_balance=$(
        aws cloudwatch get-metric-statistics \
            --namespace "AWS/EBS" \
            --region "${region}" \
            --metric-name "BurstBalance" \
            --start-time "${start_time}" \
            --end-time "${end_time}" \
            --period 60 \
            --statistics Average \
            --dimensions Name=VolumeId,Value="${volume_id}" \
            --query 'Datapoints | reverse(sort_by(@, &Timestamp))[0].Average'
    )

    echo "Volume burst balance: $volume_id -> $(echo "$volume_burst_balance / 1" | bc)%" 
done

exit $cpu_credits_warn

Install dependencies

Please note, this script depends on bash, jq and aws:

  • bash is preinstalled
  • jq could be installed with emerge jq
  • aws could be installed with emerge awscli

Configure AWS credentials

aws requires configuration on instance where you will use it. You could follow official article: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-quick-configuration

You will also need to grant some permission to be able to read metadata.

Your policy could look like (where A.B.C.D is ip address of your instance):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
               "cloudwatch:GetMetricStatistics",
               "ec2:DescribeInstances"
            ],
            "Resource": "*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "A.B.C.D/32"
                }
            }
        }
    ]
}

The policy behind is a primitive example. The better approach is to not hardcode any credentials on instance, and attach IAM policy to instance instead, plus limit ec2:DescribeInstances to be runnable only for specific instance.

Checklist:

  • log into aws console
  • create user
  • remember Access Key ID and Secret Access Key
  • add inline policy
  • log into instance and configure aws

Testing

Run script and you should be able to see something like below:

# check-ec2-credits 
Instance ID: i-01234567890123456
Instance Type: t2.nano
Region ID: us-east-1
CPU Credits: 25/72 -> 34%
Volume burst balance: vol-00112233445566778 -> 89%

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.