Introduction
In a previous blog post, we compared the metadata features of AWS EC2 and Google Compute Engine (GCE). In that article we explain some limitations of the EC2 metadata implementation, which often result in the use of tags instead of metadata.
Unfortunately, if you need to retrieve tag values from within an EC2 instance, the process is much more complicated than retrieving metadata, which only requires a simple HTTP request.
In this post we explain in detail how you can retrieve tag values from inside an EC2 instance.
Retrieving EC2 Tags via the CLI
If you want to store structured metadata against an EC2 instance and then use that metadata to control the instance’s internal behaviour, you need a way of retrieving the tag information from inside the instance, unless you want to store all the metadata in an unstructured form inside the single user-data metadata field.
Consider the following EC2 tags which are comparable to the GCE metadata values shown in our previous blog post on this subject:
At first this looks to be directly equivalent to GCE metadata. However, there is a problem. The tags are not accessible via the HTTP metadata interface inside the instance. So if we want to retrieve the tags to control the instance’s internal behaviour, we need another approach.
It turns out that the tags can be accessed from within an instance by using the describe-tags sub-command of the aws ec2 command line tool, for example:
aws ec2 describe-tags --region us-east-1 --filter "resource-type=instance" --filter
Note the two filters. The first filter is to only include tags that are associated with instances. The second blank filter is for some reason required to return all tags.
However, there are some issues with this approach:
- By default, the describe-tags command will return all tags, not just for the current instance, but for all instances. So this list needs filtering to return only the tags for the current instance. The command shown above filters to only show tags associated with instances, but we also want to filter to only retrieve tags for the current instance. To do this, we need to specify the instance ID.
- For some reason, we have to provide the command with the region we are in. You would expect that the command would be smart enough to simply default to the region of the instance, if not specified, but unfortunately this is not the case.
- The describe-tags command will fail if the instance has not been granted an appropriate security permissions. See below for more details.
Retrieving Tags for the Current Instance Only
We can retrieve the instance ID for the current instance, on the command line from within the instance, using the following command:
ec2-metadata -i
So to retrieve all tags for the current instance, we can run the following command:
aws ec2 describe-tags --region us-east-1 --filter "resource-type=instance" --filter "Name=resource-id,Values=$(ec2-metadata -i | cut -d ' ' -f2)"
Note that we still need to know the region when running this command. We can retrieve the availability zone of the current instance using the following command:
ec2-metadata --availability-zone | cut -d ' ' -f2
And then drop the last character of the availability zone, to get the region. A bit of a hack but it works. Finally, we need to filter the results so as to only retrieve the value of a tag of a specific name that we are interested in.
The following bash script provides a complete solution:
#!/bin/bash if [ -z $1 ]; then scriptName=`basename "$0"` echo >&2 "Usage: $scriptName <tag_name>" exit 1 fi # check that aws and ec2-metadata commands are installed command -v aws >/dev/null 2>&1 || { echo >&2 'aws command not installed.'; exit 2; } command -v ec2-metadata >/dev/null 2>&1 || { echo >&2 'ec2-metadata command not installed.'; exit 3; } # set filter parameters instanceId=$(ec2-metadata -i | cut -d ' ' -f2) filterParams=( --filters "Name=key,Values=$1" "Name=resource-type,Values=instance" "Name=resource-id,Values=$instanceId" ) # get region region=$(ec2-metadata --availability-zone | cut -d ' ' -f2) region=${region%?} # retrieve tags tagValues=$(aws ec2 describe-tags --output text --region "$region" "${filterParams[@]}") if [ $? -ne 0 ]; then echo >&2 "Error retrieving tag value." exit 4 fi # extract required tag value tagValue=$(echo "$tagValues" | cut -f5) echo "$tagValue"
And now if we save the bash script above in a file called ec2-get-tag we can run a command such as the following, to retrieve the value of the tag with name TAG_NAME:
$ ec2-get-tag TAG_NAME TAG_VALUE $
Obviously, it would be a lot nicer if we could simply call the HTTP metadata service to retrieve this information!
Tag Retrieval Security Configuration
The above trickery with the AWS CLI plus some shell scripting allows us to retrieve tag values from within an EC2 instance. However, by default, EC2 instances do not have permissions to read this data. The commands above will fail unless the EC2 instance has permissions to perform the following action:
"Action": "ec2:DescribeTags"
To achieve this, create a role with the following name and assign this role when you create your instance:
EC2DescribeTags
The easiest way to configure this role is to attach the following managed policy to the role:
AmazonEC2ReadOnlyAccess
But if you want to grant your EC2 instance the bare minimum of permissions, then create a new policy called:
EC2DescribeTags
And then configure it with the following policy document:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ec2:DescribeTags", "Resource": "*" } ] }
Note that unlike tags, metadata is accessible to EC2 instances by default, without any of the additional security configuration being required. Which is another reason why it would be better if AWS supported arbitrary metadata field creation, rather than us having to fall back to using tags.
Any questions or comments, please post below.
Here’s a one-liner alternative, requires only aws CLI and jq:
aws ec2 describe-tags –filter “Name=resource-id,Values=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)” –filter “Name=key,Values=TAGNAME” |jq -r .Tags[0].Value
(replace TAGNAME with the name of the tag you’re looking for)
Note that AWS specifically suggest not taking the “text” output and processing it, without specifying the columns. (They may change the output fields in future.)
A similar one liner, not requiring jq :
aws ec2 describe-tags –filters “Name=key,Values=TAGNAME” “Name=resource-id,Values=$(curl -s 169.254.169.254/latest/meta-data/instance-id)” –output text –query Tags[].Value
(You don’t need to filter “resource-type=instance” if you give it a resource ID of the instance ID. If you don’t specify the region, you must have set it previously with “aws configure” or set AWS_DEFAULT_REGION )
Full command, for this instance which has an “environment” tag of “test”
sh-4.2$ aws ec2 describe-tags –region $(curl -s 169.254.169.254/latest/meta-data/placement/availability-zone | sed ‘s/.$//’ ) –filters “Name=key,Values=environment” “Name=resource-id,Values=$(curl -s 169.254.169.254/latest/meta-data/instance-id)” –output text –query Tags[].Value
test