Allocate AWS Costs with Resource Tags
By: Davide De Paolis
Tags: Serverless, Software Engineering, AWS, Infrastructure As Code

When you start having many projects — each with different environments — in your AWS account, it is very important to have an overview of the costs by project (and/or environment).

Under AWS Cost Management / Cost Explorer is possible to view reports and aggregate costs by Services and you can set up many different filters to have a more granular view.

The one filter I find more useful is by Tag.

Assuming you are deploying your serverless infrastructure via CloudFormation ( directly, or through AWS SAM or Serverless framework) you are immediately able to Filter costs by Tag using the auto-generated Cloudformation:StackName ( which is the name you assign to the service in the yml file).

Often though this is not enough, because maybe your infrastructure is split into multiple separate stacks.

In some of our recent projects for example we have :

a React frontend which we deploy to S3 and serve with a CloudFront Distribution ( which handles caching for us ) + Route53 for the domain name.

which we deploy to and serve with a ( which handles caching for us ) + for the domain name. a RestAPI which relies on API Gateway and many different Lambdas which read and write to DynamoDB , and make use of SQS .

which relies on and many different which read and write to , and make use of . a Cognito User Pool to handle the Authentication to the FrontEnd and allows it to make calls to the RestAPI.

to handle the Authentication to the FrontEnd and allows it to make calls to the RestAPI. some additional CloudFront distributions (+ Route53 ) to expose some specific endpoints of the RestAPI to a 3rd party ( not the Cognito Authorized frontend ) and deal with caching based on query parameters.

That means that these 4 components of the Architecture are described with different cloud formation stacks (in some case we don’t even have a stack because we created the resources via AWS CLI or CDK, or forgive me, directly with the AWS UI Console).

The simple filter by stack name, therefore, is not enough.

This does not mean you can not monitor the costs of the entire Application/Project.

Many AWS services support user-generated tags, therefore it is possible to assign the same tag to resources from different services to indicate that the resources are related. For example, you could assign the same tag to an API Gateway stage that you assign to a CloudWatch Events rule.

User-generated tags are simple key-value pairs assigned to the resource you are creating ( or have already created ).

As usual, the quickest and easy approach could be adding them for each resource directly in the UIConsole:

but this is definitely something you don’t want to do for anything else than a quick test or prototype project.

Also adding tags with the AWS CLI is quick and easy, just check the docs for the CLI for your resource. ( unfortunately, the right way of passing the tags is slightly different from a resource to another).

For example to add tags for a Cognito User Pool is just:

aws cognito-idp tag-resource --resource-arn YOUR_USER_POOL_ARN --tags Project=PROJECT_NAME,Environment=YOUR_ENV

but for S3 you need to pass a TagSet:

# add cost allocation tags export tagSet="TagSet=[{Key=Project,Value=$PROJECT},{Key=Environment,Value=$YOUR_ENV}]" aws s3api put-bucket-tagging --bucket $S3_BUCKET_NAME --tagging=$tagSet

and for CloudFront the tags are defined in an Array called Items. You can, therefore, create a JSON file and use that.

# in tags.json { "Items": [ { "Key": "Project", "Value":"cap" }, { "Key": "Environment", "Value": "dev" } ] } aws cloudfront tag-resource --resource arn:aws:cloudfront::Q1W2E3R4T5:distribution/Q1W2E3R4T5Y6 --tags file://tags.json tags.json

And the examples and differences could go on.

As you see, a better and more maintainable approach is defining your tags along with the resource in your Infrastructure As Code ( be it Terraform, SAM or Serverless).

With serverless framework it is as simple as adding this snippet to the provider section of your serverless.yml:

stackTags: Project: PROJECT_NAME Environment: YOUR_STAGE/ENV

Everything will work out of the box for your Lambdas and for DynamoDb, but again, you might need some adjustments for Cognito, SQS, S3 and API Gateway.

For example for Cognito you need to specify them in the Cognito User Pool properties:

UserPoolTags: {'Project': '${self:provider.stackTags.Project}', "Environment": '${self:provider.stackTags.Environment}'}

For SQS is instead you have to specify key-value pairs under the properties of your Queue.

Properties: Tags: - Key: Project Value: ${self:service} - Key: Environment Value: ${self:provider.stage}

If you have many queues in your serverless.yml it could make sense to create a Custom Variable and refer to it in each queue.

custom: sqsTags: - Key: Project Value: ${self:service} - Key: Environment Value: ${self:provider.stage} // and then in SQS block: Properties Tags: ${self:custom.sqsTags}

As soon as we did that, with just one more google search we found out that there is a plugin to simplify the script.

This has brought us to another plugin needed to tag the ApiGateway (which as you can see by the source code, I guess it’s clearly forked by the SQS one).

In the serverless world, things change fast and it seems that at least for the API Gateway that plugin is not necessary anymore and we can just use the stackTags that come with the framework (alone or together with another node tags that allow customizing even more the API Resources. Honestly, though, I haven't yet figure out what is the difference between using stackTags and tags.

It seems that the tags added with the tags node are added to the APIS/Stages/Configure Tags (together with the stackTags), while the stackTags are added only to APIS/Settings/Configure Tags. If you are interested in more granularity in that tagging you might want to check out the API Gateway Resources That Can Be Tagged

AWS Resource Groups and Tag Editor

Something that I really found useful while working on Cost Allocation Tags is the Tag Editor and Resource Group section of the UI Console.

Tag editor allows you to select all the resources you have in your account, filter them and edit directly their tags.

Resource groups allows you to have an aggregated view of all the resources under specific tags.

These 2 tools are very handy to have an overview of the tags that you have applied and make sure all the different resources from different stacks will belong nicely together when it comes to the Cost Reports.

Unfortunately, CloudFront distributions are not supported( yet ) in the Resource Groups (outside us-east-1) but you still can add tags directly to them. They will not be shown in the Tagged Resource Groups, but they will in the Cost Reports.

Err on the side of using too many tags rather than too few.

As you saw you can play around a lot and customize the tagging for each service. You can set the Tags at the provider level, or at the resource level. And of course, you can do both.

One of the suggestions from AWS in regards to tagging is to be as precise and granular as you like, it is better to have too many tags than too few and this is why they set a rather high limitation of 50 tags for each resource, you will have plenty of room to customize and be specific your tags!

As with many things AWS as soon as you start working on something, you realize there is an enourmous amout of stuff that you do NOT know ( and that’s never good for your Imposter Syndrome.. ).

In the end Tagging resources for Costs Allocation is very simple, but it is only one aspect, because for example you could use tags to grant access to resources ( constraining IAM permissions by specific tags). If you want to know more you can start reading this page

Something else you might want to check out is the version of AWS CLI you have installed on your machine, recently I wasted good 20 minutes trying to figure out why

aws cognito-identity tag-resource --resource-arn arn:aws:cognito-identity:my_region:my_account:identitypool/my_region:my_identity_pool_id --tags MyTagKey=MyTagValue

wasn't working: I was getting the error response Argument Operation: Invalid Choice despite apparently everything being exactly as described here

I then run pip3 install --upgrade awscli and notice that my aws cli was updated from 1.16.70 to 1.16.283. (quite a big jump of patches) and then magically list-tags-for-resource and tag-resource where finally available.

Photo by David Carboni on Unsplash